From 7d5c6d912db310822ee5e1285d8a9712e51a1239 Mon Sep 17 00:00:00 2001 From: Oleksandr Brezhniev Date: Fri, 22 Mar 2024 14:43:03 +0000 Subject: [PATCH 1/6] Fix Num2Bits usage --- circuits/lib/query/comparators.circom | 4 ++-- circuits/lib/utils/claimUtils.circom | 6 +++--- circuits/lib/utils/idUtils.circom | 15 +++++++++------ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/circuits/lib/query/comparators.circom b/circuits/lib/query/comparators.circom index 2e089bb..b668c22 100644 --- a/circuits/lib/query/comparators.circom +++ b/circuits/lib/query/comparators.circom @@ -59,10 +59,10 @@ template LessThan254() { signal input in[2]; signal output out; - component n0b = Num2Bits(254); + component n0b = Num2Bits_strict(); n0b.in <== in[0]; - component n1b = Num2Bits(254); + component n1b = Num2Bits_strict(); n1b.in <== in[1]; // numbers for high 4 bits diff --git a/circuits/lib/utils/claimUtils.circom b/circuits/lib/utils/claimUtils.circom index 82a29e6..d3dcd87 100644 --- a/circuits/lib/utils/claimUtils.circom +++ b/circuits/lib/utils/claimUtils.circom @@ -89,7 +89,7 @@ template getClaimHeader() { signal output schema; signal output {binary} claimFlags[32]; - component i0Bits = Num2Bits(254); + component i0Bits = Num2Bits_strict(); i0Bits.in <== claim[0]; component schemaNum = Bits2Num(128); @@ -120,7 +120,7 @@ template getClaimRevNonce() { component claimRevNonce = Bits2Num(64); - component v0Bits = Num2Bits(254); + component v0Bits = Num2Bits_strict(); v0Bits.in <== claim[4]; for (var i=0; i<64; i++) { claimRevNonce.in[i] <== v0Bits.out[i]; @@ -311,7 +311,7 @@ template getClaimExpiration() { component expirationBits = Bits2Num(64); - component v0Bits = Num2Bits(254); + component v0Bits = Num2Bits_strict(); v0Bits.in <== claim[4]; for (var i=0; i<64; i++) { expirationBits.in[i] <== v0Bits.out[i+64]; diff --git a/circuits/lib/utils/idUtils.circom b/circuits/lib/utils/idUtils.circom index 89065e2..51e1d0c 100644 --- a/circuits/lib/utils/idUtils.circom +++ b/circuits/lib/utils/idUtils.circom @@ -103,7 +103,7 @@ template TakeNBits(n) { signal input in; signal output out; // We take only n least significant bits from 254 bit number. - component bits = Num2Bits(254); + component bits = Num2Bits_strict(); bits.in <== in; component outBits = Bits2Num(n); @@ -178,9 +178,9 @@ template cutId() { signal input in; signal output out; - signal idBits[256] <== Num2Bits(256)(in); + signal idBits[254] <== Num2Bits_strict()(in); - component cut = Bits2Num(256-16-16-8); + component cut = Bits2Num(216); for (var i=16; i<256-16-8; i++) { cut.in[i-16] <== idBits[i]; } @@ -191,12 +191,15 @@ template cutState() { signal input in; signal output out; - signal stateBits[256] <== Num2Bits(256)(in); + signal stateBits[254] <== Num2Bits_strict()(in); - component cut = Bits2Num(256-16-16-8); - for (var i=0; i<256-16-16-8; i++) { + component cut = Bits2Num(216); + // two most significant bits of 256-bit number are always 0, because we have 254-bit prime field + for (var i=0; i<216-2; i++) { cut.in[i] <== stateBits[i+16+16+8]; } + cut.in[214] <== 0; + cut.in[215] <== 0; out <== cut.out; } From 3ea0c503b744188c506e208b7d89d8317e145aec Mon Sep 17 00:00:00 2001 From: Oleksandr Brezhniev Date: Fri, 22 Mar 2024 15:15:45 +0000 Subject: [PATCH 2/6] Add check that signature r8x & r8y are on the curve. Small signal name fix --- circuits/lib/stateTransition.circom | 4 ++-- circuits/lib/utils/babyjubjub.circom | 20 ++++++++++++++++++++ circuits/lib/utils/claimUtils.circom | 5 +++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 circuits/lib/utils/babyjubjub.circom diff --git a/circuits/lib/stateTransition.circom b/circuits/lib/stateTransition.circom index 7271fe2..0f1c374 100644 --- a/circuits/lib/stateTransition.circom +++ b/circuits/lib/stateTransition.circom @@ -55,8 +55,8 @@ template StateTransition(IdOwnershipLevels) { ForceEqualIfEnabled()(one, [stateIsNotZero, 0]); // old & new state checks - signal oldNewNotEqual <== IsEqual()([oldUserState, newUserState]); - ForceEqualIfEnabled()(one, [oldNewNotEqual, 0]); + signal oldNewEqual <== IsEqual()([oldUserState, newUserState]); + ForceEqualIfEnabled()(one, [oldNewEqual, 0]); // check userID ownership by correct signature of a hash of old state and new state signal challenge <== Poseidon(2)([oldUserState, newUserState]); diff --git a/circuits/lib/utils/babyjubjub.circom b/circuits/lib/utils/babyjubjub.circom new file mode 100644 index 0000000..1c11458 --- /dev/null +++ b/circuits/lib/utils/babyjubjub.circom @@ -0,0 +1,20 @@ +pragma circom 2.1.5; + +include "../../../node_modules/circomlib/circuits/comparators.circom"; + +template ForceBabyCheckIfEnabled() { + signal input {binary} enabled; + signal input x; + signal input y; + + signal x2; + signal y2; + + var a = 168700; + var d = 168696; + + x2 <== x*x; + y2 <== y*y; + + ForceEqualIfEnabled()(enabled, [a*x2 + y2, 1 + d*x2*y2]); +} \ No newline at end of file diff --git a/circuits/lib/utils/claimUtils.circom b/circuits/lib/utils/claimUtils.circom index d3dcd87..8ec4166 100644 --- a/circuits/lib/utils/claimUtils.circom +++ b/circuits/lib/utils/claimUtils.circom @@ -7,6 +7,7 @@ include "../../../node_modules/circomlib/circuits/mux3.circom"; include "../../../node_modules/circomlib/circuits/mux1.circom"; include "../../../node_modules/circomlib/circuits/mux2.circom"; include "./idUtils.circom"; +include "./babyjubjub.circom"; // getClaimSubjectOtherIden checks that a claim Subject is OtherIden and outputs the identity within. template getClaimSubjectOtherIden() { @@ -213,6 +214,8 @@ template verifyClaimSignature() { signal input pubKeyX; signal input pubKeyY; + ForceBabyCheckIfEnabled()(enabled, sigR8x, sigR8y); + // signature verification EdDSAPoseidonVerifier()( enabled <== enabled, @@ -236,6 +239,8 @@ template checkDataSignatureWithPubKeyInClaim() { component getPubKey = getPubKeyFromClaim(); getPubKey.claim <== claim; + ForceBabyCheckIfEnabled()(enabled, signatureR8X, signatureR8Y); + EdDSAPoseidonVerifier()( enabled <== enabled, Ax <== getPubKey.Ax, From 8cea69d94598bd3039c7ac87d27e72ed8a7bf7e4 Mon Sep 17 00:00:00 2001 From: Oleksandr Brezhniev Date: Fri, 22 Mar 2024 15:46:43 +0000 Subject: [PATCH 3/6] Restrict input values for exists operator to 0 and 1 --- circuits/lib/query/processQueryWithModifiers.circom | 12 ++++++++++-- circuits/lib/query/query.circom | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/circuits/lib/query/processQueryWithModifiers.circom b/circuits/lib/query/processQueryWithModifiers.circom index 5a08391..4dd0fb4 100644 --- a/circuits/lib/query/processQueryWithModifiers.circom +++ b/circuits/lib/query/processQueryWithModifiers.circom @@ -29,9 +29,11 @@ template ProcessQueryWithModifiers(claimLevels, maxValueArraySize){ signal isOpNoop <== IsZero()(operator); signal merklizedAndEnabled <== AND()(enabled, merklized); + signal isOpExists <== IsEqual()([operator, 11]); + // if operator == exists and value[0] == 0 ($exists == false), then claimPathNotExists = 1 (check non-inclusion), // otherwise claimPathNotExists = 0 (check inclusion) - signal claimPathNotExists <== AND()(IsEqual()([operator, 11]), IsZero()(value[0])); + signal claimPathNotExists <== AND()(isOpExists, IsZero()(value[0])); // check path/in node exists in merkle tree specified by jsonldRoot SMTVerifier(claimLevels)( @@ -58,12 +60,18 @@ template ProcessQueryWithModifiers(claimLevels, maxValueArraySize){ ); // For non-merklized credentials exists / non-exist operators should always fail - signal isOpExists <== IsEqual()([operator, 11]); ForceEqualIfEnabled()( AND()(enabled, NOT()(merklized)), [isOpExists, 0] ); + // Restrict exists operator input values to 0 and 1 + ForceEqualIfEnabled()( + AND()(enabled, isOpExists), + [value[0] * (value[0] - 1), 0] + ); + + ///////////////////////////////////////////////////////////////// // Query Operator Processing ///////////////////////////////////////////////////////////////// diff --git a/circuits/lib/query/query.circom b/circuits/lib/query/query.circom index eff2a9c..27216e7 100644 --- a/circuits/lib/query/query.circom +++ b/circuits/lib/query/query.circom @@ -20,7 +20,7 @@ include "comparators.circom"; 8 - greater than or equal 9 - between (value[0] <= in <= value[1]) 10 - not between - 11 - exist (true / false) + 11 - exists (true / false) Modifier/computation operators: 16 - selective disclosure (16 = 10000 binary) */ From 4318bc7bc297513ba6d10a28867db0d2e18d9a1c Mon Sep 17 00:00:00 2001 From: Oleksandr Brezhniev Date: Fri, 29 Mar 2024 12:27:33 +0000 Subject: [PATCH 4/6] Check checksum --- circuits/auth/authV2.circom | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/circuits/auth/authV2.circom b/circuits/auth/authV2.circom index 7fe9efb..d55bb85 100644 --- a/circuits/auth/authV2.circom +++ b/circuits/auth/authV2.circom @@ -136,18 +136,28 @@ template checkAuthV2(IdOwnershipLevels, onChainLevels) { challengeSignatureS ); - /* Check on-chain SMT inclusion existence */ - signal cutId <== cutId()(genesisID); + /* Check if state is genesis and if genesisId is valid */ signal cutState <== cutState()(state); - signal isStateGenesis <== IsEqual()([cutId, cutState]); + component genesisIdParts = SplitID(); + genesisIdParts.id <== genesisID; + + signal calculatedChecksum <== CalculateIdChecksum()(genesisIdParts.typ, genesisIdParts.genesis); + ForceEqualIfEnabled()( + enabled, + [genesisIdParts.checksum, calculatedChecksum] + ); + + signal isStateGenesis <== IsEqual()([genesisIdParts.genesis, cutState]); + + /* Check on-chain SMT inclusion existence */ signal genesisIDHash <== Poseidon(1)([genesisID]); SMTVerifier(onChainLevels)( enabled <== enabled, - fnc <== isStateGenesis, // non-inclusion in case if genesis state, otherwise inclusion + fnc <== isStateGenesis, // non-inclusion in case of genesis state, otherwise inclusion root <== gistRoot, siblings <== gistMtp, oldKey <== gistMtpAuxHi, From da072fa5b2a140bf87baf4fe4ce02d9e652f0b89 Mon Sep 17 00:00:00 2001 From: Oleksandr Brezhniev Date: Fri, 12 Apr 2024 01:01:22 +0100 Subject: [PATCH 5/6] Small fixes --- circuits/lib/utils/claimUtils.circom | 2 +- circuits/lib/utils/idUtils.circom | 3 +++ circuits/onchain/credentialAtomicQueryV3OnChain.circom | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/circuits/lib/utils/claimUtils.circom b/circuits/lib/utils/claimUtils.circom index 8ec4166..8349210 100644 --- a/circuits/lib/utils/claimUtils.circom +++ b/circuits/lib/utils/claimUtils.circom @@ -286,7 +286,7 @@ template getValueByIndex(){ value <== mux.out; } -// verify that the claim has expiration time and it is less then timestamp +// verify that provided timestamp is less than claim expiration time template verifyExpirationTime() { signal input {binary} expirationFlag; // claimFlags[3] (expiration flag) is set signal input claim[8]; diff --git a/circuits/lib/utils/idUtils.circom b/circuits/lib/utils/idUtils.circom index 51e1d0c..81e9abe 100644 --- a/circuits/lib/utils/idUtils.circom +++ b/circuits/lib/utils/idUtils.circom @@ -102,6 +102,9 @@ template GatherID() { template TakeNBits(n) { signal input in; signal output out; + + assert(n <= 254); + // We take only n least significant bits from 254 bit number. component bits = Num2Bits_strict(); bits.in <== in; diff --git a/circuits/onchain/credentialAtomicQueryV3OnChain.circom b/circuits/onchain/credentialAtomicQueryV3OnChain.circom index 92548df..a008d56 100644 --- a/circuits/onchain/credentialAtomicQueryV3OnChain.circom +++ b/circuits/onchain/credentialAtomicQueryV3OnChain.circom @@ -29,7 +29,6 @@ issuerLevels - Merkle tree depth level for claims issued by the issuer claimLevels - Merkle tree depth level for claim JSON-LD document maxValueArraySize - Number of elements in comparison array for in/notin operation if level = 3 number of values for comparison ["1", "2", "3"] -idOwnershipLevels - Merkle tree depth level for personal claims onChainLevels - Merkle tree depth level for Auth claim on-chain */ template credentialAtomicQueryV3OnChain(issuerLevels, claimLevels, maxValueArraySize, idOwnershipLevels, onChainLevels) { From 9f81ef6fc7162219b1493bf677436410fe870998 Mon Sep 17 00:00:00 2001 From: Oleksandr Brezhniev Date: Wed, 17 Apr 2024 15:02:08 +0100 Subject: [PATCH 6/6] Restrict Num2Bits to 248 bits in cutId --- circuits/lib/utils/idUtils.circom | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/circuits/lib/utils/idUtils.circom b/circuits/lib/utils/idUtils.circom index 81e9abe..207ab23 100644 --- a/circuits/lib/utils/idUtils.circom +++ b/circuits/lib/utils/idUtils.circom @@ -181,10 +181,10 @@ template cutId() { signal input in; signal output out; - signal idBits[254] <== Num2Bits_strict()(in); + signal idBits[248] <== Num2Bits(248)(in); component cut = Bits2Num(216); - for (var i=16; i<256-16-8; i++) { + for (var i=16; i<248-16; i++) { cut.in[i-16] <== idBits[i]; } out <== cut.out;