Skip to content

Commit

Permalink
[FAB-6682] Adds identity mixer crypto in java
Browse files Browse the repository at this point in the history
This commit provides the identity mixer crypto in java,
which is equivalent to the functionality provided in the
fabric/idemix golang package. This package can be used
to integrate anonymous signing using identity mixer in
the java sdk.

Change-Id: I4a7dc9f1fd319c6aacba50cf84b84c2de7788382
Signed-off-by: Manu Drijvers <mdr@zurich.ibm.com>
Signed-off-by: Saad Karim <skarim@us.ibm.com>
  • Loading branch information
Manu Drijvers authored and Saad Karim committed Sep 4, 2018
1 parent b649868 commit be173ba
Show file tree
Hide file tree
Showing 11 changed files with 1,654 additions and 39 deletions.
9 changes: 8 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@
<version>1.23</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.miracl.milagro.amcl/milagro-crypto-java -->
<dependency>
<groupId>org.miracl.milagro.amcl</groupId>
<artifactId>milagro-crypto-java</artifactId>
<version>0.4.0</version>
</dependency>

</dependencies>


Expand Down Expand Up @@ -308,7 +315,7 @@
<version>2.10.4</version>
<configuration>
<excludePackageNames>
org.hyperledger.fabric_ca.sdk.helper:org.hyperledger.fabric.protos.*:org.hyperledger.fabric.sdk.helper:org.hyperledger.fabric.sdk.transaction:org.hyperledger.fabric.sdk.security
org.hyperledger.fabric_ca.sdk.helper:org.hyperledger.fabric.protos.*:org.hyperledger.fabric.sdk.helper:org.hyperledger.fabric.sdk.transaction:org.hyperledger.fabric.sdk.security:org.hyperledger.fabric.sdk.idemix
</excludePackageNames>
<show>public</show>
<doctitle>Hyperledger Fabric Java SDK</doctitle>
Expand Down
154 changes: 154 additions & 0 deletions src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixCredRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
*
* Copyright 2017, 2018 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.hyperledger.fabric.sdk.idemix;

import java.util.ArrayList;
import java.util.Arrays;

import com.google.protobuf.ByteString;
import org.apache.milagro.amcl.FP256BN.BIG;
import org.apache.milagro.amcl.FP256BN.ECP;
import org.apache.milagro.amcl.RAND;
import org.hyperledger.fabric.protos.idemix.Idemix;

/**
* IdemixCredRequest represents the first message of the idemix issuance protocol,
* in which the user requests a credential from the issuer.
*/
public class IdemixCredRequest {
private final ECP nym;
private final BIG issuerNonce;
private final BIG proofC;
private final BIG proofS;

private static final String CREDREQUEST_LABEL = "credRequest";


/**
* Constructor
*
* @param sk the secret key of the user
* @param issuerNonce a nonce
* @param ipk the issuer public key
*/
IdemixCredRequest(BIG sk, BIG issuerNonce, IdemixIssuerPublicKey ipk) {
if (sk == null) {
throw new IllegalArgumentException("Cannot create idemix credrequest from null Secret Key input");
}

if (issuerNonce == null) {
throw new IllegalArgumentException("Cannot create idemix credrequest from null issuer nonce input");
}

if (ipk == null) {
throw new IllegalArgumentException("Cannot create idemix credrequest from null Issuer Public Key input");
}
final RAND rng = IdemixUtils.getRand();
nym = ipk.getHsk().mul(sk);
this.issuerNonce = new BIG(issuerNonce);

// Create Zero Knowledge Proof
BIG rsk = IdemixUtils.randModOrder(rng);
ECP t = ipk.getHsk().mul(rsk);

// Make proofData: total 3 elements of G1, each 2*FIELD_BYTES+1 (ECP),
// plus length of String array,
// plus one BIG
byte[] proofData = new byte[0];
proofData = IdemixUtils.append(proofData, CREDREQUEST_LABEL.getBytes());
proofData = IdemixUtils.append(proofData, IdemixUtils.ecpToBytes(t));
proofData = IdemixUtils.append(proofData, IdemixUtils.ecpToBytes(ipk.getHsk()));
proofData = IdemixUtils.append(proofData, IdemixUtils.ecpToBytes(nym));
proofData = IdemixUtils.append(proofData, IdemixUtils.bigToBytes(issuerNonce));
proofData = IdemixUtils.append(proofData, ipk.getHash());

proofC = IdemixUtils.hashModOrder(proofData);

// Compute proofS = ...
proofS = BIG.modmul(proofC, sk, IdemixUtils.GROUP_ORDER).plus(rsk);
proofS.mod(IdemixUtils.GROUP_ORDER);
}

/**
* Construct a IdemixCredRequest from a serialized credrequest
*
* @param proto a protobuf representation of a credential request
*/
IdemixCredRequest(Idemix.CredRequest proto) {
if (proto == null) {
throw new IllegalArgumentException("Cannot create idemix credrequest from null input");
}
nym = IdemixUtils.transformFromProto(proto.getNym());
proofC = BIG.fromBytes(proto.getProofC().toByteArray());
proofS = BIG.fromBytes(proto.getProofS().toByteArray());
issuerNonce = BIG.fromBytes(proto.getIssuerNonce().toByteArray());
}

/**
* @return a pseudonym of the credential requester
*/
ECP getNym() {
return nym;
}

/**
* @return a proto version of this IdemixCredRequest
*/
Idemix.CredRequest toProto() {
return Idemix.CredRequest.newBuilder()
.setNym(IdemixUtils.transformToProto(nym))
.setProofC(ByteString.copyFrom(IdemixUtils.bigToBytes(proofC)))
.setProofS(ByteString.copyFrom(IdemixUtils.bigToBytes(proofS)))
.setIssuerNonce(ByteString.copyFrom(IdemixUtils.bigToBytes(issuerNonce)))
.build();
}


/**
* Cryptographically verify the IdemixCredRequest
*
* @param ipk the issuer public key
* @return true iff valid
*/
boolean check(IdemixIssuerPublicKey ipk) {

if (nym == null ||
issuerNonce == null ||
proofC == null ||
proofS == null ||
ipk == null) {
return false;
}

ECP t = ipk.getHsk().mul(proofS);
t.sub(nym.mul(proofC));

byte[] proofData = new byte[0];
proofData = IdemixUtils.append(proofData, CREDREQUEST_LABEL.getBytes());
proofData = IdemixUtils.append(proofData, IdemixUtils.ecpToBytes(t));
proofData = IdemixUtils.append(proofData, IdemixUtils.ecpToBytes(ipk.getHsk()));
proofData = IdemixUtils.append(proofData, IdemixUtils.ecpToBytes(nym));
proofData = IdemixUtils.append(proofData, IdemixUtils.bigToBytes(issuerNonce));
proofData = IdemixUtils.append(proofData, ipk.getHash());


// Hash proofData to hproofdata
byte[] hproofdata = IdemixUtils.bigToBytes(IdemixUtils.hashModOrder(proofData));

return Arrays.equals(IdemixUtils.bigToBytes(proofC), hproofdata);
}
}
177 changes: 177 additions & 0 deletions src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixCredential.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
*
* Copyright 2017, 2018 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.hyperledger.fabric.sdk.idemix;

import com.google.protobuf.ByteString;
import org.apache.milagro.amcl.FP256BN.BIG;
import org.apache.milagro.amcl.FP256BN.ECP;
import org.apache.milagro.amcl.FP256BN.ECP2;
import org.apache.milagro.amcl.FP256BN.PAIR;
import org.apache.milagro.amcl.RAND;
import org.hyperledger.fabric.protos.idemix.Idemix;

/**
* IdemixCredential represents a user's idemix credential,
* which is a BBS+ signature (see "Constant-Size Dynamic k-TAA" by Man Ho Au, Willy Susilo, Yi Mu)
* on the user's secret key and attribute values.
*/
public class IdemixCredential {

private final ECP A;
private final ECP B;
private final BIG E;
private final BIG S;
private final byte[][] Attrs;

/**
* Constructor creating a new credential
*
* @param key the issuer key pair
* @param m a credential request
* @param attrs an array of attribute values as BIG
*/
IdemixCredential(IdemixIssuerKey key, IdemixCredRequest m, BIG[] attrs) {
if (key == null || key.getIpk() == null || m == null || attrs == null) {
throw new IllegalArgumentException("Cannot create idemix credential from null input");
}
if (attrs.length != key.getIpk().getAttributeNames().length) {
throw new IllegalArgumentException("Amount of attribute values does not match amount of attributes in issuer public key");
}
final RAND rng = IdemixUtils.getRand();
// Place a BBS+ signature on the user key and the attribute values
// (For BBS+, see "Constant-Size Dynamic k-TAA" by Man Ho Au, Willy Susilo, Yi Mu)
E = IdemixUtils.randModOrder(rng);
S = IdemixUtils.randModOrder(rng);

B = new ECP();
B.copy(IdemixUtils.genG1);
B.add(m.getNym());
B.add(key.getIpk().getHRand().mul(S));

for (int i = 0; i < attrs.length / 2; i++) {
B.add(key.getIpk().getHAttrs()[2 * i].mul2(attrs[2 * i], key.getIpk().getHAttrs()[2 * i + 1], attrs[2 * i + 1]));
}
if (attrs.length % 2 != 0) {
B.add(key.getIpk().getHAttrs()[attrs.length - 1].mul(attrs[attrs.length - 1]));
}

BIG exp = new BIG(key.getIsk()).plus(E);
exp.mod(IdemixUtils.GROUP_ORDER);
exp.invmodp(IdemixUtils.GROUP_ORDER);
A = B.mul(exp);

Attrs = new byte[attrs.length][IdemixUtils.FIELD_BYTES];
byte[] b = new byte[IdemixUtils.FIELD_BYTES];
for (int i = 0; i < attrs.length; i++) {
attrs[i].toBytes(b);
System.arraycopy(b, 0, Attrs[i], 0, IdemixUtils.FIELD_BYTES);
}
}

/**
* Construct an IdemixCredential from a serialized credential
*
* @param proto a protobuf representation of a credential
*/
IdemixCredential(Idemix.Credential proto) {
if (proto == null) {
throw new IllegalArgumentException("Cannot create idemix credential from null input");
}

A = IdemixUtils.transformFromProto(proto.getA());
B = IdemixUtils.transformFromProto(proto.getB());
E = BIG.fromBytes(proto.getE().toByteArray());
S = BIG.fromBytes(proto.getS().toByteArray());
Attrs = new byte[proto.getAttrsCount()][];
for (int i = 0; i < proto.getAttrsCount(); i++) {
Attrs[i] = proto.getAttrs(i).toByteArray();
}
}

ECP getA() {
return A;
}

ECP getB() {
return B;
}

BIG getE() {
return E;
}

BIG getS() {
return S;
}

byte[][] getAttrs() {
return Attrs;
}

/**
* verify cryptographically verifies the credential
*
* @param sk the secret key of the user
* @param ipk the public key of the issuer
* @return true iff valid
*/
boolean verify(BIG sk, IdemixIssuerPublicKey ipk) {
if (ipk == null || Attrs.length != ipk.getAttributeNames().length) {
return false;
}
for (byte[] attr : Attrs) {
if (attr == null) {
return false;
}
}

ECP bPrime = new ECP();
bPrime.copy(IdemixUtils.genG1);
bPrime.add(ipk.getHsk().mul2(sk, ipk.getHRand(), S));
for (int i = 0; i < Attrs.length / 2; i++) {
bPrime.add(ipk.getHAttrs()[2 * i].mul2(BIG.fromBytes(Attrs[2 * i]), ipk.getHAttrs()[2 * i + 1], BIG.fromBytes(Attrs[2 * i + 1])));
}
if (Attrs.length % 2 != 0) {
bPrime.add(ipk.getHAttrs()[Attrs.length - 1].mul(BIG.fromBytes(Attrs[Attrs.length - 1])));
}
if (!B.equals(bPrime)) {
return false;
}

ECP2 a = IdemixUtils.genG2.mul(E);
a.add(ipk.getW());
a.affine();
return PAIR.fexp(PAIR.ate(a, A)).equals(PAIR.fexp(PAIR.ate(IdemixUtils.genG2, B)));
}

/**
* @return A proto representation of this credential
*/
Idemix.Credential toProto() {
Idemix.Credential.Builder builder = Idemix.Credential.newBuilder()
.setA(IdemixUtils.transformToProto(A))
.setB(IdemixUtils.transformToProto(B))
.setE(ByteString.copyFrom(IdemixUtils.bigToBytes(E)))
.setS(ByteString.copyFrom(IdemixUtils.bigToBytes(S)));

for (byte[] attr : Attrs) {
builder.addAttrs(ByteString.copyFrom(attr));
}

return builder.build();
}
}
Loading

0 comments on commit be173ba

Please sign in to comment.