Skip to content

Commit

Permalink
[FAB-8924] add non-revocation proof to idemix
Browse files Browse the repository at this point in the history
This commit extends idemix signatures with proofs of
non-revocation suporting different revocation algorithms.
Currently, only a "NO_REVOCATION" algorithm is included,
but future CRs will extend this with meaningful revocation
algorithms.

Change-Id: I4ba46b36adb59b1f00ccf3b33c041d367066dfe3
Signed-off-by: Manu Drijvers <mdr@zurich.ibm.com>
  • Loading branch information
Manu Drijvers authored and Saad Karim committed Sep 4, 2018
1 parent be173ba commit 68faf09
Show file tree
Hide file tree
Showing 10 changed files with 658 additions and 52 deletions.
126 changes: 94 additions & 32 deletions src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixSignature.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@

package org.hyperledger.fabric.sdk.idemix;

import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.google.common.primitives.Ints;
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.FP12;
import org.apache.milagro.amcl.FP256BN.PAIR;
import org.apache.milagro.amcl.RAND;
import org.hyperledger.fabric.protos.idemix.Idemix;
import org.hyperledger.fabric.sdk.exception.CryptoException;

/**
* IdemixSignature represents an idemix signature, which is a zero knowledge proof
Expand All @@ -47,6 +50,10 @@ public class IdemixSignature {
private final BIG nonce;
private final ECP nym;
private final BIG proofSRNym;
private Idemix.ECP2 revocationPk;
private byte[] revocationPKSig;
private long epoch;
private Idemix.NonRevocationProof nonRevocationProof;

private static final String SIGN_LABEL = "sign";

Expand All @@ -59,11 +66,28 @@ public class IdemixSignature {
* @param ipk the issuer public key
* @param disclosure a bool-array that steers the disclosure of attributes
* @param msg the message to be signed
* @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) {
if (c == null || sk == null || pseudonym == null || pseudonym.getNym() == null || pseudonym.getRandNym() == null || ipk == null || disclosure == null || msg == null) {
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");
}

if (disclosure.length != c.getAttrs().length) {
throw new IllegalArgumentException("Disclosure length must be the same as the number of attributes");
}

if (cri.getRevocationAlg() >= RevocationAlgorithm.values().length) {
throw new IllegalArgumentException("CRI specifies unknown revocation algorithm");
}

if (cri.getRevocationAlg() != RevocationAlgorithm.ALG_NO_REVOCATION.ordinal() && disclosure[rhIndex]) {
throw new IllegalArgumentException("Attribute " + rhIndex + " is disclosed but also used a revocation handle attribute, which should remain hidden");
}

RevocationAlgorithm revocationAlgorithm = RevocationAlgorithm.values()[cri.getRevocationAlg()];

int[] hiddenIndices = hiddenIndices(disclosure);
final RAND rng = IdemixUtils.getRand();
// Start signature
Expand Down Expand Up @@ -96,6 +120,18 @@ public class IdemixSignature {
rAttrs[i] = IdemixUtils.randModOrder(rng);
}

// Compute non-revoked proof
NonRevocationProver prover = NonRevocationProver.getNonRevocationProver(revocationAlgorithm);
int hiddenRHIndex = Ints.indexOf(hiddenIndices, rhIndex);
if (hiddenRHIndex < 0) {
// rhIndex is not present, set to last index position
hiddenRHIndex = hiddenIndices.length;
}
byte[] nonRevokedProofHashData = prover.getFSContribution(BIG.fromBytes(c.getAttrs()[rhIndex]), rAttrs[hiddenRHIndex], cri);
if (nonRevokedProofHashData == null) {
throw new RuntimeException("Failed to compute non-revoked proof");
}

ECP t1 = aPrime.mul2(re, ipk.getHRand(), rR2);
ECP t2 = PAIR.G1mul(ipk.getHRand(), rSPrime);
t2.add(bPrime.mul2(rR3, ipk.getHsk(), rsk));
Expand Down Expand Up @@ -132,29 +168,12 @@ public class IdemixSignature {

proofC = IdemixUtils.hashModOrder(finalProofData);

proofSSk = new BIG(rsk);
proofSSk.add(BIG.modmul(proofC, sk, IdemixUtils.GROUP_ORDER));
proofSSk.mod(IdemixUtils.GROUP_ORDER);

proofSE = new BIG(re);
proofSE.add(BIG.modneg(BIG.modmul(proofC, c.getE(), IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER));
proofSE.mod(IdemixUtils.GROUP_ORDER);

proofSR2 = new BIG(rR2);
proofSR2.add(BIG.modmul(proofC, r2, IdemixUtils.GROUP_ORDER));
proofSR2.mod(IdemixUtils.GROUP_ORDER);

proofSR3 = new BIG(rR3);
proofSR3.add(BIG.modneg(BIG.modmul(proofC, r3, IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER));
proofSR3.mod(IdemixUtils.GROUP_ORDER);

proofSSPrime = new BIG(rSPrime);
proofSSPrime.add(BIG.modmul(proofC, sPrime, IdemixUtils.GROUP_ORDER));
proofSSPrime.mod(IdemixUtils.GROUP_ORDER);

proofSRNym = new BIG(rRNym);
proofSRNym.add(BIG.modmul(proofC, pseudonym.getRandNym(), IdemixUtils.GROUP_ORDER));
proofSRNym.mod(IdemixUtils.GROUP_ORDER);
proofSSk = IdemixUtils.modAdd(rsk, BIG.modmul(proofC, sk, IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER);
proofSE = IdemixUtils.modSub(re, BIG.modmul(proofC, c.getE(), IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER);
proofSR2 = IdemixUtils.modAdd(rR2, BIG.modmul(proofC, r2, IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER);
proofSR3 = IdemixUtils.modSub(rR3, BIG.modmul(proofC, r3, IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER);
proofSSPrime = IdemixUtils.modAdd(rSPrime, BIG.modmul(proofC, sPrime, IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER);
proofSRNym = IdemixUtils.modAdd(rRNym, BIG.modmul(proofC, pseudonym.getRandNym(), IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER);

nym = new ECP();
nym.copy(pseudonym.getNym());
Expand All @@ -166,6 +185,11 @@ public class IdemixSignature {
proofSAttrs[i].mod(IdemixUtils.GROUP_ORDER);
}

// include non-revocation proof in signature
this.revocationPk = cri.getEpochPk();
this.revocationPKSig = cri.getEpochPkSig().toByteArray();
this.epoch = cri.getEpoch();
this.nonRevocationProof = prover.getNonRevocationProof(this.proofC);
}

/**
Expand Down Expand Up @@ -193,6 +217,11 @@ public class IdemixSignature {
for (int i = 0; i < proto.getProofSAttrsCount(); i++) {
proofSAttrs[i] = BIG.fromBytes(proto.getProofSAttrs(i).toByteArray());
}

revocationPk = proto.getRevocationEpochPk();
revocationPKSig = proto.getRevocationPkSig().toByteArray();
epoch = proto.getEpoch();
nonRevocationProof = proto.getNonRevocationProof();
}

/**
Expand All @@ -201,10 +230,13 @@ public class IdemixSignature {
* @param disclosure an array indicating which attributes it expects to be disclosed
* @param ipk the issuer public key
* @param msg the message that should be signed in this signature
* @param attributeValues BIG array with attributeValues[i] contains the desired attribute value for the i-th undisclosed attribute in disclosure
* @param attributeValues BIG array where attributeValues[i] contains the desired attribute value for the i-th attribute if its disclosed
* @param rhIndex index of the attribute that represents the revocation-handle
* @param revPk the long term public key used to authenticate CRIs
* @param epoch monotonically increasing counter representing a time window
* @return true iff valid
*/
boolean verify(boolean[] disclosure, IdemixIssuerPublicKey ipk, byte[] msg, BIG[] attributeValues) {
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 All @@ -215,14 +247,27 @@ boolean verify(boolean[] disclosure, IdemixIssuerPublicKey ipk, byte[] msg, BIG[
}

int[] hiddenIndices = hiddenIndices(disclosure);

if (proofSAttrs.length != hiddenIndices.length) {
return false;
}

if (aPrime.is_infinity()) {
return false;
}
if (nonRevocationProof.getRevocationAlg() >= RevocationAlgorithm.values().length) {
throw new IllegalArgumentException("CRI specifies unknown revocation algorithm");
}

RevocationAlgorithm revocationAlgorithm = RevocationAlgorithm.values()[nonRevocationProof.getRevocationAlg()];

if (disclosure[rhIndex]) {
throw new IllegalArgumentException("Attribute " + rhIndex + " is disclosed but also used a revocation handle attribute, which should remain hidden");
}

// Verify EpochPK
if (!RevocationAuthority.verifyEpochPK(revPk, this.revocationPk, this.revocationPKSig, epoch, revocationAlgorithm)) {
// Signature is based on an invalid revocation epoch public key
return false;
}

FP12 temp1 = PAIR.ate(ipk.getW(), aPrime);
FP12 temp2 = PAIR.ate(IdemixUtils.genG2, aBar);
Expand Down Expand Up @@ -261,6 +306,19 @@ boolean verify(boolean[] disclosure, IdemixIssuerPublicKey ipk, byte[] msg, BIG[
ECP t3 = ipk.getHsk().mul2(proofSSk, ipk.getHRand(), proofSRNym);
t3.sub(nym.mul(proofC));

// Check with non-revoked-verifier
NonRevocationVerifier nonRevokedVerifier = NonRevocationVerifier.getNonRevocationVerifier(revocationAlgorithm);
int hiddenRHIndex = Ints.indexOf(hiddenIndices, rhIndex);
if (hiddenRHIndex < 0) {
// rhIndex is not present, set to last index position
hiddenRHIndex = hiddenIndices.length;
}
BIG proofSRh = proofSAttrs[hiddenRHIndex];
byte[] nonRevokedProofBytes = nonRevokedVerifier.recomputeFSContribution(this.nonRevocationProof, proofC, IdemixUtils.transformFromProto(this.revocationPk), proofSRh);
if (nonRevokedProofBytes == null) {
return false;
}

// create proofData such that it can contain the sign label, 7 elements in G1 (each of size 2*FIELD_BYTES+1),
// the ipk hash, the disclosure array, and the message
byte[] proofData = new byte[0];
Expand Down Expand Up @@ -304,7 +362,11 @@ Idemix.Signature toProto() {
.setProofSR3(ByteString.copyFrom(IdemixUtils.bigToBytes(proofSR3)))
.setProofSRNym(ByteString.copyFrom(IdemixUtils.bigToBytes(proofSRNym)))
.setProofSSPrime(ByteString.copyFrom(IdemixUtils.bigToBytes(proofSSPrime)))
.setNonce(ByteString.copyFrom(IdemixUtils.bigToBytes(nonce)));
.setNonce(ByteString.copyFrom(IdemixUtils.bigToBytes(nonce)))
.setRevocationEpochPk(revocationPk)
.setRevocationPkSig(ByteString.copyFrom(revocationPKSig))
.setEpoch(epoch)
.setNonRevocationProof(nonRevocationProof);

for (BIG attr : proofSAttrs) {
builder.addProofSAttrs(ByteString.copyFrom(IdemixUtils.bigToBytes(attr)));
Expand All @@ -323,15 +385,15 @@ private int[] hiddenIndices(boolean[] disclosure) {
if (disclosure == null) {
throw new IllegalArgumentException("cannot compute hidden indices of null disclosure");
}
List<Integer> hiddenIndicesList = new ArrayList<Integer>();
List<Integer> hiddenIndicesList = new ArrayList<>();
for (int i = 0; i < disclosure.length; i++) {
if (!disclosure[i]) {
hiddenIndicesList.add(i);
}
}
int[] hiddenIndices = new int[hiddenIndicesList.size()];
for (int i = 0; i < hiddenIndicesList.size(); i++) {
hiddenIndices[i] = hiddenIndicesList.get(i).intValue();
hiddenIndices[i] = hiddenIndicesList.get(i);
}

return hiddenIndices;
Expand Down
39 changes: 34 additions & 5 deletions src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
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.FP12;
import org.apache.milagro.amcl.FP256BN.FP2;
import org.apache.milagro.amcl.FP256BN.PAIR;
import org.apache.milagro.amcl.FP256BN.ROM;
import org.apache.milagro.amcl.HASH256;
import org.apache.milagro.amcl.RAND;
Expand All @@ -39,17 +41,18 @@
public final class IdemixUtils {
private static final BIG gx = new BIG(ROM.CURVE_Gx);
private static final BIG gy = new BIG(ROM.CURVE_Gy);
protected static final ECP genG1 = new ECP(gx, gy);
static final ECP genG1 = new ECP(gx, gy);
private static final BIG pxa = new BIG(ROM.CURVE_Pxa);
private static final BIG pxb = new BIG(ROM.CURVE_Pxb);
private static final FP2 px = new FP2(pxa, pxb);
private static final BIG pya = new BIG(ROM.CURVE_Pya);
private static final BIG pyb = new BIG(ROM.CURVE_Pyb);
private static final FP2 py = new FP2(pya, pyb);
protected static final ECP2 genG2 = new ECP2(px, py);
protected static final BIG GROUP_ORDER = new BIG(ROM.CURVE_Order);
protected static final int FIELD_BYTES = BIG.MODBYTES;
protected static final RAND RNG = getRand();
static final ECP2 genG2 = new ECP2(px, py);
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 @@ -244,4 +247,30 @@ static Idemix.ECP transformToProto(ECP w) {

return Idemix.ECP.newBuilder().setX(ByteString.copyFrom(valueX)).setY(ByteString.copyFrom(valueY)).build();
}

/**
* Takes input BIGs a, b, m and returns a+b modulo m
*
* @param a the first BIG to add
* @param b the second BIG to add
* @param m the modulus
* @return Returns a+b (mod m)
*/
public static BIG modAdd(BIG a, BIG b, BIG m) {
BIG c = a.plus(b);
c.mod(m);
return c;
}

/**
* Modsub takes input BIGs a, b, m and returns a-b modulo m
*
* @param a the minuend of the modular subtraction
* @param b the subtrahend of the modular subtraction
* @param m the modulus
* @return returns a-b (mod m)
*/
public static BIG modSub(BIG a, BIG b, BIG m) {
return modAdd(a, BIG.modneg(b, m), m);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
*
* 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 org.apache.milagro.amcl.FP256BN.BIG;
import org.hyperledger.fabric.protos.idemix.Idemix;

/**
* A NonRevocationProver is a prover that can prove that an identity mixer credential is not revoked.
* For every RevocationAlgorithm, there will be an instantiation of NonRevocationProver.
*/
interface NonRevocationProver {

/**
* This method provides a concrete non-revocation for a given Revocation algorithm
*
* @param algorithm Revocation mechanism to use
* @return A concrete NonRevocationProver for the given revocation mechanism
*/
static NonRevocationProver getNonRevocationProver(RevocationAlgorithm algorithm) {
if (algorithm == null) {
throw new IllegalArgumentException("Revocation algorithm cannot be null");
}
switch (algorithm) {
case ALG_NO_REVOCATION:
return new NopNonRevocationProver();
default:
// Revocation algorithm not supported
throw new IllegalArgumentException("Revocation algorithm " + algorithm.name() + " not supported");
}
}

/**
* getFSContribution performs the first round of a two-round zero-knowledge proof,
* proving that a credential with some revocation handle is not revoked.
*
* @param rh Revocation handle
* @param rRh r-value used in proving knowledge of rh
* @param cri Credential revocation information
* @return proof
*/
byte[] getFSContribution(BIG rh, BIG rRh, Idemix.CredentialRevocationInformation cri);

/**
* getNonRevocationProof performs the second round of a two-round zero-knowledge proof,
* proving that a credential with some revocation handle is not revoked.
*
* @param challenge Fiat-Shamir challenge of the zero-knowledge proof
* @return proof
*/
Idemix.NonRevocationProof getNonRevocationProof(BIG challenge);

}
Loading

0 comments on commit 68faf09

Please sign in to comment.