Skip to content
This repository was archived by the owner on Apr 22, 2025. It is now read-only.

Commit 68faf09

Browse files
Manu DrijversSaad Karim
authored andcommitted
[FAB-8924] add non-revocation proof to idemix
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>
1 parent be173ba commit 68faf09

File tree

10 files changed

+658
-52
lines changed

10 files changed

+658
-52
lines changed

src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixSignature.java

Lines changed: 94 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,20 @@
1616

1717
package org.hyperledger.fabric.sdk.idemix;
1818

19+
import java.security.PublicKey;
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.List;
2223

24+
import com.google.common.primitives.Ints;
2325
import com.google.protobuf.ByteString;
2426
import org.apache.milagro.amcl.FP256BN.BIG;
2527
import org.apache.milagro.amcl.FP256BN.ECP;
2628
import org.apache.milagro.amcl.FP256BN.FP12;
2729
import org.apache.milagro.amcl.FP256BN.PAIR;
2830
import org.apache.milagro.amcl.RAND;
2931
import org.hyperledger.fabric.protos.idemix.Idemix;
32+
import org.hyperledger.fabric.sdk.exception.CryptoException;
3033

3134
/**
3235
* IdemixSignature represents an idemix signature, which is a zero knowledge proof
@@ -47,6 +50,10 @@ public class IdemixSignature {
4750
private final BIG nonce;
4851
private final ECP nym;
4952
private final BIG proofSRNym;
53+
private Idemix.ECP2 revocationPk;
54+
private byte[] revocationPKSig;
55+
private long epoch;
56+
private Idemix.NonRevocationProof nonRevocationProof;
5057

5158
private static final String SIGN_LABEL = "sign";
5259

@@ -59,11 +66,28 @@ public class IdemixSignature {
5966
* @param ipk the issuer public key
6067
* @param disclosure a bool-array that steers the disclosure of attributes
6168
* @param msg the message to be signed
69+
* @param rhIndex the index of the attribute that represents the revocation handle
70+
* @param cri the credential revocation information that allows the signer to prove non-revocation
6271
*/
63-
IdemixSignature(IdemixCredential c, BIG sk, IdemixPseudonym pseudonym, IdemixIssuerPublicKey ipk, boolean[] disclosure, byte[] msg) {
64-
if (c == null || sk == null || pseudonym == null || pseudonym.getNym() == null || pseudonym.getRandNym() == null || ipk == null || disclosure == null || msg == null) {
72+
IdemixSignature(IdemixCredential c, BIG sk, IdemixPseudonym pseudonym, IdemixIssuerPublicKey ipk, boolean[] disclosure, byte[] msg, int rhIndex, Idemix.CredentialRevocationInformation cri) {
73+
if (c == null || sk == null || pseudonym == null || pseudonym.getNym() == null || pseudonym.getRandNym() == null || ipk == null || disclosure == null || msg == null || cri == null) {
6574
throw new IllegalArgumentException("Cannot construct idemix signature from null input");
6675
}
76+
77+
if (disclosure.length != c.getAttrs().length) {
78+
throw new IllegalArgumentException("Disclosure length must be the same as the number of attributes");
79+
}
80+
81+
if (cri.getRevocationAlg() >= RevocationAlgorithm.values().length) {
82+
throw new IllegalArgumentException("CRI specifies unknown revocation algorithm");
83+
}
84+
85+
if (cri.getRevocationAlg() != RevocationAlgorithm.ALG_NO_REVOCATION.ordinal() && disclosure[rhIndex]) {
86+
throw new IllegalArgumentException("Attribute " + rhIndex + " is disclosed but also used a revocation handle attribute, which should remain hidden");
87+
}
88+
89+
RevocationAlgorithm revocationAlgorithm = RevocationAlgorithm.values()[cri.getRevocationAlg()];
90+
6791
int[] hiddenIndices = hiddenIndices(disclosure);
6892
final RAND rng = IdemixUtils.getRand();
6993
// Start signature
@@ -96,6 +120,18 @@ public class IdemixSignature {
96120
rAttrs[i] = IdemixUtils.randModOrder(rng);
97121
}
98122

123+
// Compute non-revoked proof
124+
NonRevocationProver prover = NonRevocationProver.getNonRevocationProver(revocationAlgorithm);
125+
int hiddenRHIndex = Ints.indexOf(hiddenIndices, rhIndex);
126+
if (hiddenRHIndex < 0) {
127+
// rhIndex is not present, set to last index position
128+
hiddenRHIndex = hiddenIndices.length;
129+
}
130+
byte[] nonRevokedProofHashData = prover.getFSContribution(BIG.fromBytes(c.getAttrs()[rhIndex]), rAttrs[hiddenRHIndex], cri);
131+
if (nonRevokedProofHashData == null) {
132+
throw new RuntimeException("Failed to compute non-revoked proof");
133+
}
134+
99135
ECP t1 = aPrime.mul2(re, ipk.getHRand(), rR2);
100136
ECP t2 = PAIR.G1mul(ipk.getHRand(), rSPrime);
101137
t2.add(bPrime.mul2(rR3, ipk.getHsk(), rsk));
@@ -132,29 +168,12 @@ public class IdemixSignature {
132168

133169
proofC = IdemixUtils.hashModOrder(finalProofData);
134170

135-
proofSSk = new BIG(rsk);
136-
proofSSk.add(BIG.modmul(proofC, sk, IdemixUtils.GROUP_ORDER));
137-
proofSSk.mod(IdemixUtils.GROUP_ORDER);
138-
139-
proofSE = new BIG(re);
140-
proofSE.add(BIG.modneg(BIG.modmul(proofC, c.getE(), IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER));
141-
proofSE.mod(IdemixUtils.GROUP_ORDER);
142-
143-
proofSR2 = new BIG(rR2);
144-
proofSR2.add(BIG.modmul(proofC, r2, IdemixUtils.GROUP_ORDER));
145-
proofSR2.mod(IdemixUtils.GROUP_ORDER);
146-
147-
proofSR3 = new BIG(rR3);
148-
proofSR3.add(BIG.modneg(BIG.modmul(proofC, r3, IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER));
149-
proofSR3.mod(IdemixUtils.GROUP_ORDER);
150-
151-
proofSSPrime = new BIG(rSPrime);
152-
proofSSPrime.add(BIG.modmul(proofC, sPrime, IdemixUtils.GROUP_ORDER));
153-
proofSSPrime.mod(IdemixUtils.GROUP_ORDER);
154-
155-
proofSRNym = new BIG(rRNym);
156-
proofSRNym.add(BIG.modmul(proofC, pseudonym.getRandNym(), IdemixUtils.GROUP_ORDER));
157-
proofSRNym.mod(IdemixUtils.GROUP_ORDER);
171+
proofSSk = IdemixUtils.modAdd(rsk, BIG.modmul(proofC, sk, IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER);
172+
proofSE = IdemixUtils.modSub(re, BIG.modmul(proofC, c.getE(), IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER);
173+
proofSR2 = IdemixUtils.modAdd(rR2, BIG.modmul(proofC, r2, IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER);
174+
proofSR3 = IdemixUtils.modSub(rR3, BIG.modmul(proofC, r3, IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER);
175+
proofSSPrime = IdemixUtils.modAdd(rSPrime, BIG.modmul(proofC, sPrime, IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER);
176+
proofSRNym = IdemixUtils.modAdd(rRNym, BIG.modmul(proofC, pseudonym.getRandNym(), IdemixUtils.GROUP_ORDER), IdemixUtils.GROUP_ORDER);
158177

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

188+
// include non-revocation proof in signature
189+
this.revocationPk = cri.getEpochPk();
190+
this.revocationPKSig = cri.getEpochPkSig().toByteArray();
191+
this.epoch = cri.getEpoch();
192+
this.nonRevocationProof = prover.getNonRevocationProof(this.proofC);
169193
}
170194

171195
/**
@@ -193,6 +217,11 @@ public class IdemixSignature {
193217
for (int i = 0; i < proto.getProofSAttrsCount(); i++) {
194218
proofSAttrs[i] = BIG.fromBytes(proto.getProofSAttrs(i).toByteArray());
195219
}
220+
221+
revocationPk = proto.getRevocationEpochPk();
222+
revocationPKSig = proto.getRevocationPkSig().toByteArray();
223+
epoch = proto.getEpoch();
224+
nonRevocationProof = proto.getNonRevocationProof();
196225
}
197226

198227
/**
@@ -201,10 +230,13 @@ public class IdemixSignature {
201230
* @param disclosure an array indicating which attributes it expects to be disclosed
202231
* @param ipk the issuer public key
203232
* @param msg the message that should be signed in this signature
204-
* @param attributeValues BIG array with attributeValues[i] contains the desired attribute value for the i-th undisclosed attribute in disclosure
233+
* @param attributeValues BIG array where attributeValues[i] contains the desired attribute value for the i-th attribute if its disclosed
234+
* @param rhIndex index of the attribute that represents the revocation-handle
235+
* @param revPk the long term public key used to authenticate CRIs
236+
* @param epoch monotonically increasing counter representing a time window
205237
* @return true iff valid
206238
*/
207-
boolean verify(boolean[] disclosure, IdemixIssuerPublicKey ipk, byte[] msg, BIG[] attributeValues) {
239+
boolean verify(boolean[] disclosure, IdemixIssuerPublicKey ipk, byte[] msg, BIG[] attributeValues, int rhIndex, PublicKey revPk, int epoch) throws CryptoException {
208240
if (disclosure == null || ipk == null || msg == null || attributeValues == null || attributeValues.length != ipk.getAttributeNames().length || disclosure.length != ipk.getAttributeNames().length) {
209241
return false;
210242
}
@@ -215,14 +247,27 @@ boolean verify(boolean[] disclosure, IdemixIssuerPublicKey ipk, byte[] msg, BIG[
215247
}
216248

217249
int[] hiddenIndices = hiddenIndices(disclosure);
218-
219250
if (proofSAttrs.length != hiddenIndices.length) {
220251
return false;
221252
}
222-
223253
if (aPrime.is_infinity()) {
224254
return false;
225255
}
256+
if (nonRevocationProof.getRevocationAlg() >= RevocationAlgorithm.values().length) {
257+
throw new IllegalArgumentException("CRI specifies unknown revocation algorithm");
258+
}
259+
260+
RevocationAlgorithm revocationAlgorithm = RevocationAlgorithm.values()[nonRevocationProof.getRevocationAlg()];
261+
262+
if (disclosure[rhIndex]) {
263+
throw new IllegalArgumentException("Attribute " + rhIndex + " is disclosed but also used a revocation handle attribute, which should remain hidden");
264+
}
265+
266+
// Verify EpochPK
267+
if (!RevocationAuthority.verifyEpochPK(revPk, this.revocationPk, this.revocationPKSig, epoch, revocationAlgorithm)) {
268+
// Signature is based on an invalid revocation epoch public key
269+
return false;
270+
}
226271

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

309+
// Check with non-revoked-verifier
310+
NonRevocationVerifier nonRevokedVerifier = NonRevocationVerifier.getNonRevocationVerifier(revocationAlgorithm);
311+
int hiddenRHIndex = Ints.indexOf(hiddenIndices, rhIndex);
312+
if (hiddenRHIndex < 0) {
313+
// rhIndex is not present, set to last index position
314+
hiddenRHIndex = hiddenIndices.length;
315+
}
316+
BIG proofSRh = proofSAttrs[hiddenRHIndex];
317+
byte[] nonRevokedProofBytes = nonRevokedVerifier.recomputeFSContribution(this.nonRevocationProof, proofC, IdemixUtils.transformFromProto(this.revocationPk), proofSRh);
318+
if (nonRevokedProofBytes == null) {
319+
return false;
320+
}
321+
264322
// create proofData such that it can contain the sign label, 7 elements in G1 (each of size 2*FIELD_BYTES+1),
265323
// the ipk hash, the disclosure array, and the message
266324
byte[] proofData = new byte[0];
@@ -304,7 +362,11 @@ Idemix.Signature toProto() {
304362
.setProofSR3(ByteString.copyFrom(IdemixUtils.bigToBytes(proofSR3)))
305363
.setProofSRNym(ByteString.copyFrom(IdemixUtils.bigToBytes(proofSRNym)))
306364
.setProofSSPrime(ByteString.copyFrom(IdemixUtils.bigToBytes(proofSSPrime)))
307-
.setNonce(ByteString.copyFrom(IdemixUtils.bigToBytes(nonce)));
365+
.setNonce(ByteString.copyFrom(IdemixUtils.bigToBytes(nonce)))
366+
.setRevocationEpochPk(revocationPk)
367+
.setRevocationPkSig(ByteString.copyFrom(revocationPKSig))
368+
.setEpoch(epoch)
369+
.setNonRevocationProof(nonRevocationProof);
308370

309371
for (BIG attr : proofSAttrs) {
310372
builder.addProofSAttrs(ByteString.copyFrom(IdemixUtils.bigToBytes(attr)));
@@ -323,15 +385,15 @@ private int[] hiddenIndices(boolean[] disclosure) {
323385
if (disclosure == null) {
324386
throw new IllegalArgumentException("cannot compute hidden indices of null disclosure");
325387
}
326-
List<Integer> hiddenIndicesList = new ArrayList<Integer>();
388+
List<Integer> hiddenIndicesList = new ArrayList<>();
327389
for (int i = 0; i < disclosure.length; i++) {
328390
if (!disclosure[i]) {
329391
hiddenIndicesList.add(i);
330392
}
331393
}
332394
int[] hiddenIndices = new int[hiddenIndicesList.size()];
333395
for (int i = 0; i < hiddenIndicesList.size(); i++) {
334-
hiddenIndices[i] = hiddenIndicesList.get(i).intValue();
396+
hiddenIndices[i] = hiddenIndicesList.get(i);
335397
}
336398

337399
return hiddenIndices;

src/main/java/org/hyperledger/fabric/sdk/idemix/IdemixUtils.java

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import org.apache.milagro.amcl.FP256BN.BIG;
2727
import org.apache.milagro.amcl.FP256BN.ECP;
2828
import org.apache.milagro.amcl.FP256BN.ECP2;
29+
import org.apache.milagro.amcl.FP256BN.FP12;
2930
import org.apache.milagro.amcl.FP256BN.FP2;
31+
import org.apache.milagro.amcl.FP256BN.PAIR;
3032
import org.apache.milagro.amcl.FP256BN.ROM;
3133
import org.apache.milagro.amcl.HASH256;
3234
import org.apache.milagro.amcl.RAND;
@@ -39,17 +41,18 @@
3941
public final class IdemixUtils {
4042
private static final BIG gx = new BIG(ROM.CURVE_Gx);
4143
private static final BIG gy = new BIG(ROM.CURVE_Gy);
42-
protected static final ECP genG1 = new ECP(gx, gy);
44+
static final ECP genG1 = new ECP(gx, gy);
4345
private static final BIG pxa = new BIG(ROM.CURVE_Pxa);
4446
private static final BIG pxb = new BIG(ROM.CURVE_Pxb);
4547
private static final FP2 px = new FP2(pxa, pxb);
4648
private static final BIG pya = new BIG(ROM.CURVE_Pya);
4749
private static final BIG pyb = new BIG(ROM.CURVE_Pyb);
4850
private static final FP2 py = new FP2(pya, pyb);
49-
protected static final ECP2 genG2 = new ECP2(px, py);
50-
protected static final BIG GROUP_ORDER = new BIG(ROM.CURVE_Order);
51-
protected static final int FIELD_BYTES = BIG.MODBYTES;
52-
protected static final RAND RNG = getRand();
51+
static final ECP2 genG2 = new ECP2(px, py);
52+
static final FP12 genGT = PAIR.fexp(PAIR.ate(genG2, genG1));
53+
static final BIG GROUP_ORDER = new BIG(ROM.CURVE_Order);
54+
static final int FIELD_BYTES = BIG.MODBYTES;
55+
private static final RAND RNG = getRand();
5356

5457
private IdemixUtils() {
5558
// private constructor as there shouldn't be instances of this utility class
@@ -244,4 +247,30 @@ static Idemix.ECP transformToProto(ECP w) {
244247

245248
return Idemix.ECP.newBuilder().setX(ByteString.copyFrom(valueX)).setY(ByteString.copyFrom(valueY)).build();
246249
}
250+
251+
/**
252+
* Takes input BIGs a, b, m and returns a+b modulo m
253+
*
254+
* @param a the first BIG to add
255+
* @param b the second BIG to add
256+
* @param m the modulus
257+
* @return Returns a+b (mod m)
258+
*/
259+
public static BIG modAdd(BIG a, BIG b, BIG m) {
260+
BIG c = a.plus(b);
261+
c.mod(m);
262+
return c;
263+
}
264+
265+
/**
266+
* Modsub takes input BIGs a, b, m and returns a-b modulo m
267+
*
268+
* @param a the minuend of the modular subtraction
269+
* @param b the subtrahend of the modular subtraction
270+
* @param m the modulus
271+
* @return returns a-b (mod m)
272+
*/
273+
public static BIG modSub(BIG a, BIG b, BIG m) {
274+
return modAdd(a, BIG.modneg(b, m), m);
275+
}
247276
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
*
3+
* Copyright 2017, 2018 IBM Corp. All Rights Reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*
15+
*/
16+
package org.hyperledger.fabric.sdk.idemix;
17+
18+
import org.apache.milagro.amcl.FP256BN.BIG;
19+
import org.hyperledger.fabric.protos.idemix.Idemix;
20+
21+
/**
22+
* A NonRevocationProver is a prover that can prove that an identity mixer credential is not revoked.
23+
* For every RevocationAlgorithm, there will be an instantiation of NonRevocationProver.
24+
*/
25+
interface NonRevocationProver {
26+
27+
/**
28+
* This method provides a concrete non-revocation for a given Revocation algorithm
29+
*
30+
* @param algorithm Revocation mechanism to use
31+
* @return A concrete NonRevocationProver for the given revocation mechanism
32+
*/
33+
static NonRevocationProver getNonRevocationProver(RevocationAlgorithm algorithm) {
34+
if (algorithm == null) {
35+
throw new IllegalArgumentException("Revocation algorithm cannot be null");
36+
}
37+
switch (algorithm) {
38+
case ALG_NO_REVOCATION:
39+
return new NopNonRevocationProver();
40+
default:
41+
// Revocation algorithm not supported
42+
throw new IllegalArgumentException("Revocation algorithm " + algorithm.name() + " not supported");
43+
}
44+
}
45+
46+
/**
47+
* getFSContribution performs the first round of a two-round zero-knowledge proof,
48+
* proving that a credential with some revocation handle is not revoked.
49+
*
50+
* @param rh Revocation handle
51+
* @param rRh r-value used in proving knowledge of rh
52+
* @param cri Credential revocation information
53+
* @return proof
54+
*/
55+
byte[] getFSContribution(BIG rh, BIG rRh, Idemix.CredentialRevocationInformation cri);
56+
57+
/**
58+
* getNonRevocationProof performs the second round of a two-round zero-knowledge proof,
59+
* proving that a credential with some revocation handle is not revoked.
60+
*
61+
* @param challenge Fiat-Shamir challenge of the zero-knowledge proof
62+
* @return proof
63+
*/
64+
Idemix.NonRevocationProof getNonRevocationProof(BIG challenge);
65+
66+
}

0 commit comments

Comments
 (0)