Skip to content

Commit

Permalink
[FAB-6836] Implement Idemix Signing Identity
Browse files Browse the repository at this point in the history
This commit adds the Identity and SigningIdentity interfaces
and their implementation with Idemix crypto library
for transaction signing and further integration with the sdk.
The tests use crypto material (configs) that is generated by
the idemixgen tool from fabric, so the crypto implementations
in Go and Java are fully compatible.

Change-Id: I13d43bac6e87be36fd46fa77b6762aa56b9b1e0d
Signed-off-by: Maria Dubovitskaya <mdu@zurich.ibm.com>
Signed-off-by: Manu Drijvers <mdr@zurich.ibm.com>
Signed-off-by: Saad Karim <skarim@us.ibm.com>
Signed-off-by: Rafa Torres <rtm@zurich.ibm.com>
  • Loading branch information
Maria Dubovitskaya authored and Saad Karim committed Sep 4, 2018
1 parent 68faf09 commit 814bf79
Show file tree
Hide file tree
Showing 47 changed files with 1,106 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public class IdemixCredential {
*
* @param proto a protobuf representation of a credential
*/
IdemixCredential(Idemix.Credential proto) {
public IdemixCredential(Idemix.Credential proto) {
if (proto == null) {
throw new IllegalArgumentException("Cannot create idemix credential from null input");
}
Expand Down Expand Up @@ -118,7 +118,7 @@ BIG getS() {
return S;
}

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

Expand All @@ -129,7 +129,7 @@ byte[][] getAttrs() {
* @param ipk the public key of the issuer
* @return true iff valid
*/
boolean verify(BIG sk, IdemixIssuerPublicKey ipk) {
public boolean verify(BIG sk, IdemixIssuerPublicKey ipk) {
if (ipk == null || Attrs.length != ipk.getAttributeNames().length) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public class IdemixIssuerPublicKey {
*
* @param proto a protobuf representation of an issuer public key
*/
IdemixIssuerPublicKey(Idemix.IssuerPublicKey proto) {
public IdemixIssuerPublicKey(Idemix.IssuerPublicKey proto) {
// check for bad input
if (proto == null) {
throw new IllegalArgumentException("Cannot create IdemixIssuerPublicKey from null input");
Expand Down Expand Up @@ -160,7 +160,7 @@ public class IdemixIssuerPublicKey {
*
* @return true iff valid
*/
boolean check() {
public boolean check() {
// check formalities of IdemixIssuerPublicKey
if (AttributeNames == null || Hsk == null || HRand == null || HAttrs == null
|| BarG1 == null || BarG1.is_infinity() || BarG2 == null
Expand Down Expand Up @@ -221,7 +221,7 @@ Idemix.IssuerPublicKey toProto() {
/**
* @return The names of the attributes certified with this issuer public key
*/
String[] getAttributeNames() {
public String[] getAttributeNames() {
return AttributeNames;
}

Expand All @@ -244,7 +244,7 @@ protected ECP2 getW() {
/**
* @return A digest of this issuer public key
*/
byte[] getHash() {
public byte[] getHash() {
return Hash;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class IdemixPseudonym {
* @param sk the secret key of the user
* @param ipk the public key of the issuer
*/
IdemixPseudonym(BIG sk, IdemixIssuerPublicKey ipk) {
public IdemixPseudonym(BIG sk, IdemixIssuerPublicKey ipk) {
if (sk == null || ipk == null) {
throw new IllegalArgumentException("Cannot construct idemix pseudonym from null input");
}
Expand All @@ -47,7 +47,7 @@ public class IdemixPseudonym {
/**
* @return the value of the pseudonym as an ECP
*/
ECP getNym() {
public ECP getNym() {
return Nym;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class IdemixPseudonymSignature {
* @param ipk the issuer public key
* @param msg the message to be signed
*/
IdemixPseudonymSignature(BIG sk, IdemixPseudonym pseudonym, IdemixIssuerPublicKey ipk, byte[] msg) {
public IdemixPseudonymSignature(BIG sk, IdemixPseudonym pseudonym, IdemixIssuerPublicKey ipk, byte[] msg) {
if (sk == null || pseudonym == null || pseudonym.getNym() == null || pseudonym.getRandNym() == null || ipk == null || msg == null) {
throw new IllegalArgumentException("Cannot create IdemixPseudonymSignature from null input");
}
Expand Down Expand Up @@ -84,7 +84,7 @@ public class IdemixPseudonymSignature {
*
* @param proto a protobuf object representing an IdemixPseudonymSignature
*/
IdemixPseudonymSignature(Idemix.NymSignature proto) {
public IdemixPseudonymSignature(Idemix.NymSignature proto) {
if (proto == null) {
throw new IllegalArgumentException("Cannot create idemix nym signature from null input");
}
Expand All @@ -102,7 +102,7 @@ public class IdemixPseudonymSignature {
* @param msg the message that should be signed in this signature
* @return true iff valid
*/
boolean verify(ECP nym, IdemixIssuerPublicKey ipk, byte[] msg) {
public boolean verify(ECP nym, IdemixIssuerPublicKey ipk, byte[] msg) {
if (nym == null || ipk == null || msg == null) {
return false;
}
Expand Down Expand Up @@ -131,7 +131,7 @@ boolean verify(ECP nym, IdemixIssuerPublicKey ipk, byte[] msg) {
/**
* @return A proto object representing this IdemixPseudonymSignature
*/
Idemix.NymSignature toProto() {
public Idemix.NymSignature toProto() {
return Idemix.NymSignature.newBuilder()
.setProofC(ByteString.copyFrom(IdemixUtils.bigToBytes(proofC)))
.setProofSSk(ByteString.copyFrom(IdemixUtils.bigToBytes(proofSSk)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public class IdemixSignature {
* @param rhIndex the index of the attribute that represents the revocation handle
* @param cri the credential revocation information that allows the signer to prove non-revocation
*/
IdemixSignature(IdemixCredential c, BIG sk, IdemixPseudonym pseudonym, IdemixIssuerPublicKey ipk, boolean[] disclosure, byte[] msg, int rhIndex, Idemix.CredentialRevocationInformation cri) {
public IdemixSignature(IdemixCredential c, BIG sk, IdemixPseudonym pseudonym, IdemixIssuerPublicKey ipk, boolean[] disclosure, byte[] msg, int rhIndex, Idemix.CredentialRevocationInformation cri) {
if (c == null || sk == null || pseudonym == null || pseudonym.getNym() == null || pseudonym.getRandNym() == null || ipk == null || disclosure == null || msg == null || cri == null) {
throw new IllegalArgumentException("Cannot construct idemix signature from null input");
}
Expand Down Expand Up @@ -197,7 +197,7 @@ public class IdemixSignature {
*
* @param proto a protobuf object representing an IdemixSignature
*/
IdemixSignature(Idemix.Signature proto) {
public IdemixSignature(Idemix.Signature proto) {
if (proto == null) {
throw new IllegalArgumentException("Cannot construct idemix signature from null input");
}
Expand Down Expand Up @@ -236,7 +236,7 @@ public class IdemixSignature {
* @param epoch monotonically increasing counter representing a time window
* @return true iff valid
*/
boolean verify(boolean[] disclosure, IdemixIssuerPublicKey ipk, byte[] msg, BIG[] attributeValues, int rhIndex, PublicKey revPk, int epoch) throws CryptoException {
public boolean verify(boolean[] disclosure, IdemixIssuerPublicKey ipk, byte[] msg, BIG[] attributeValues, int rhIndex, PublicKey revPk, int epoch) throws CryptoException {
if (disclosure == null || ipk == null || msg == null || attributeValues == null || attributeValues.length != ipk.getAttributeNames().length || disclosure.length != ipk.getAttributeNames().length) {
return false;
}
Expand Down Expand Up @@ -349,7 +349,7 @@ boolean verify(boolean[] disclosure, IdemixIssuerPublicKey ipk, byte[] msg, BIG[
*
* @return a protobuf object representing this IdemixSignature
*/
Idemix.Signature toProto() {
public Idemix.Signature toProto() {
Idemix.Signature.Builder builder = Idemix.Signature.newBuilder()
.setAPrime(IdemixUtils.transformToProto(aPrime))
.setABar(IdemixUtils.transformToProto(aBar))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ public final class IdemixUtils {
static final FP12 genGT = PAIR.fexp(PAIR.ate(genG2, genG1));
static final BIG GROUP_ORDER = new BIG(ROM.CURVE_Order);
static final int FIELD_BYTES = BIG.MODBYTES;
private static final RAND RNG = getRand();

private IdemixUtils() {
// private constructor as there shouldn't be instances of this utility class
Expand Down Expand Up @@ -95,7 +94,7 @@ public static BIG randModOrder(RAND rng) {
* @param data the data to be hashed
* @return a BIG in 0, ..., GROUP_ORDER-1 that is the hash of the data
*/
static BIG hashModOrder(byte[] data) {
public static BIG hashModOrder(byte[] data) {
HASH256 hash = new HASH256();
for (byte b : data) {
hash.process(b);
Expand All @@ -115,7 +114,7 @@ static BIG hashModOrder(byte[] data) {
* @param big the BIG to turn into bytes
* @return a byte array representation of the BIG
*/
static byte[] bigToBytes(BIG big) {
public static byte[] bigToBytes(BIG big) {
byte[] ret = new byte[IdemixUtils.FIELD_BYTES];
big.toBytes(ret);
return ret;
Expand Down Expand Up @@ -256,7 +255,7 @@ static Idemix.ECP transformToProto(ECP w) {
* @param m the modulus
* @return Returns a+b (mod m)
*/
public static BIG modAdd(BIG a, BIG b, BIG m) {
static BIG modAdd(BIG a, BIG b, BIG m) {
BIG c = a.plus(b);
c.mod(m);
return c;
Expand All @@ -270,7 +269,7 @@ public static BIG modAdd(BIG a, BIG b, BIG m) {
* @param m the modulus
* @return returns a-b (mod m)
*/
public static BIG modSub(BIG a, BIG b, BIG m) {
static BIG modSub(BIG a, BIG b, BIG m) {
return modAdd(a, BIG.modneg(b, m), m);
}
}
200 changes: 200 additions & 0 deletions src/main/java/org/hyperledger/fabric/sdk/identity/IdemixIdentity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
*
* Copyright 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.identity;

import java.util.Arrays;

import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.milagro.amcl.FP256BN.BIG;
import org.apache.milagro.amcl.FP256BN.ECP;
import org.hyperledger.fabric.protos.common.MspPrincipal;
import org.hyperledger.fabric.protos.idemix.Idemix;
import org.hyperledger.fabric.protos.msp.Identities;
import org.hyperledger.fabric.sdk.exception.CryptoException;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.idemix.IdemixIssuerPublicKey;
import org.hyperledger.fabric.sdk.idemix.IdemixSignature;
import org.hyperledger.fabric.sdk.idemix.IdemixUtils;

/**
* IdemixIdentity is a public serializable part of the IdemixSigningIdentity.
* It contains an (un)linkable pseudonym, revealed attribute values, and a
* corresponding proof of possession of an Idemix credential
*/
public class IdemixIdentity implements Identity {

private static final Log logger = LogFactory.getLog(IdemixIdentity.class);

// MSP identifier
private final String mspId;

private final byte[] ipkHash;

// Idemix Pseudonym
private final ECP pseudonym;

// Organization Unit attribute
private final String ou;

// Role attribute
private final boolean role;

// Proof of possession of Idemix credential
// with respect to the pseudonym (nym)
// and the corresponding attributes (ou, role)
private final IdemixSignature associationProof;

/**
* Create Idemix Identity from a Serialized Identity
*
* @param proto
*/
public IdemixIdentity(Identities.SerializedIdentity proto) throws CryptoException, InvalidArgumentException {
if (proto == null) {
throw new InvalidArgumentException("Input must not be null");
}

this.mspId = proto.getMspid();

try {
logger.trace("Fetching Idemix Proto");
Identities.SerializedIdemixIdentity idemixProto = Identities.SerializedIdemixIdentity.parseFrom(proto.getIdBytes());

if (idemixProto == null) {
throw new IllegalArgumentException("The identity does not contain a serialized idemix identity");
}
logger.trace("Deserializing Nym and attribute values");
this.pseudonym = new ECP(BIG.fromBytes(idemixProto.getNymX().toByteArray()),
BIG.fromBytes(idemixProto.getNymY().toByteArray()));

MspPrincipal.OrganizationUnit ou = MspPrincipal.OrganizationUnit.parseFrom(idemixProto.getOu());
MspPrincipal.MSPRole role = MspPrincipal.MSPRole.parseFrom(idemixProto.getRole());

this.ou = ou.getOrganizationalUnitIdentifier();
this.role = role.getRole().getNumber() == 1;
this.ipkHash = ou.getCertifiersIdentifier().toByteArray();

logger.trace("Deserializing Proof");
this.associationProof = new IdemixSignature(Idemix.Signature.parseFrom(idemixProto.getProof().toByteArray()));

} catch (InvalidProtocolBufferException e) {
throw new CryptoException("Cannot deserialize MSP ID", e);
}
}

/**
* Create Idemix Identity from the following inputs:
*
* @param mspId is MSP ID sting
* @param nym is Identity Mixer Pseudonym
* @param ou is OU attribute
* @param role is Role attribute
* @param proof is Proof
*/
public IdemixIdentity(String mspId, IdemixIssuerPublicKey ipk, ECP nym, String ou, boolean role, IdemixSignature proof)
throws InvalidArgumentException {

if (mspId == null) {
throw new InvalidArgumentException("MSP ID must not be null");
}

if (mspId.isEmpty()) {
throw new InvalidArgumentException("MSP ID must not be empty");
}

if (ipk == null) {
throw new InvalidArgumentException("Issuer Public Key must not be empty");
}

if (nym == null) {
throw new InvalidArgumentException("Identity Mixer Pseudonym (nym) must not be null");
}

if (ou == null) {
throw new InvalidArgumentException("OU attribute must not be null");
}

if (ou.isEmpty()) {
throw new InvalidArgumentException("OU attribute must not be empty");
}

if (proof == null) {
throw new InvalidArgumentException("Proof must not be null");
}


this.mspId = mspId;
this.ipkHash = ipk.getHash();
this.pseudonym = nym;
this.ou = ou;
this.role = role;
this.associationProof = proof;
}

/**
* Serialize Idemix Identity
*/
@Override
public Identities.SerializedIdentity createSerializedIdentity() {
MspPrincipal.OrganizationUnit ou = MspPrincipal.OrganizationUnit.newBuilder()
.setCertifiersIdentifier(ByteString.copyFrom(this.ipkHash))
.setMspIdentifier(this.mspId)
.setOrganizationalUnitIdentifier(this.ou)
.build();

MspPrincipal.MSPRole role = MspPrincipal.MSPRole.newBuilder()
.setRole(this.role ? MspPrincipal.MSPRole.MSPRoleType.ADMIN : MspPrincipal.MSPRole.MSPRoleType.MEMBER)
.setMspIdentifier(this.mspId)
.build();

Identities.SerializedIdemixIdentity serializedIdemixIdentity = Identities.SerializedIdemixIdentity.newBuilder()
.setProof(ByteString.copyFrom(this.associationProof.toProto().toByteArray()))
.setOu(ByteString.copyFrom(ou.toByteArray()))
.setRole(ByteString.copyFrom(role.toByteArray()))
.setNymY(ByteString.copyFrom(IdemixUtils.bigToBytes(this.pseudonym.getY())))
.setNymX(ByteString.copyFrom(IdemixUtils.bigToBytes(this.pseudonym.getX())))
.build();

return Identities.SerializedIdentity.newBuilder()
.setIdBytes(ByteString.copyFrom(serializedIdemixIdentity.toByteArray()))
.setMspid(this.mspId)
.build();
}

public String getOuValue() {
return this.ou;
}

public boolean getRoleValue() {
return this.role;
}

@Override
public String toString() {
return "IdemixIdentity" +
" [ MSP ID: " + this.mspId +
" Issuer Public Key Hash: " + Arrays.toString(this.ipkHash) +
" Pseudonym: " + this.pseudonym.toRawString() +
" OU: " + this.ou +
" Role: " + this.role +
" Association Proof: " + this.associationProof.toProto().toString() +
" ]";
}
}
Loading

0 comments on commit 814bf79

Please sign in to comment.