Skip to content

Commit

Permalink
Merge pull request #121 from iden3/feature/value-arr-size-input
Browse files Browse the repository at this point in the history
value arr size input
  • Loading branch information
volodymyr-basiuk authored Feb 22, 2024
2 parents 1a1f212 + 693de6b commit 00154aa
Show file tree
Hide file tree
Showing 88 changed files with 639 additions and 134 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ jobs:
- name: Setup Circom
run: wget https://github.com/iden3/circom/releases/latest/download/circom-linux-amd64 && sudo mv ./circom-linux-amd64 /usr/bin/circom && sudo chmod +x /usr/bin/circom

- name: Generate testvectors
run: cd testvectorgen && go test ./...

- name: Install node_modules
if: steps.modules-cache.outputs.cache-hit != 'true'
run: npm install
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ build
.vscode
.DS_Store
powersOfTau28_hez_final_17.ptau
testvectorgen/**/testdata
1 change: 1 addition & 0 deletions circuits/credentialAtomicQueryV3.circom
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ component main{public [requestID,
claimPathNotExists,
operator,
value,
valueArraySize,
timestamp,
isRevocationChecked,
proofType,
Expand Down
24 changes: 24 additions & 0 deletions circuits/lib/query/comparators.circom
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@ template IN (valueArraySize){
out <== isEq[valueArraySize];
}

// Same as IN but stops checking on valueArraySize
template InWithDynamicArraySize (maxValueArraySize){
signal input in;
signal input value[maxValueArraySize];
signal input valueArraySize;
signal output out;

assert(maxValueArraySize <= 256);

component eq[maxValueArraySize];
signal isEq[maxValueArraySize+1];
signal lt[maxValueArraySize];
isEq[0] <== 0;
for (var i=0; i<maxValueArraySize; i++) {
lt[i] <== LessThan(8)([i, valueArraySize]);
eq[i] = IsEqual();
eq[i].in[0] <== in;
eq[i].in[1] <== value[i];
isEq[i+1] <== OR()(isEq[i], AND()(eq[i].out, lt[i]));
}

out <== isEq[maxValueArraySize];
}

// As LessThan but for all possible numbers from field (not only 252-bit-max like LessThan)
template LessThan254() {
signal input in[2];
Expand Down
21 changes: 18 additions & 3 deletions circuits/lib/query/processQueryWithModifiers.circom
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ include "../../../node_modules/circomlib/circuits/comparators.circom";
include "query.circom";
include "modifiers.circom";
include "../utils/claimUtils.circom";
include "../utils/arraySizeValidator.circom";

template ProcessQueryWithModifiers(claimLevels, valueArraySize){
template ProcessQueryWithModifiers(claimLevels, maxValueArraySize){
signal input enabled;
signal input claimPathNotExists; // 0 for inclusion, 1 for non-inclusion
signal input claimPathMtp[claimLevels];
Expand All @@ -16,7 +17,8 @@ template ProcessQueryWithModifiers(claimLevels, valueArraySize){
signal input claimPathValue; // value in this path in merklized json-ld document
signal input slotIndex;
signal input operator;
signal input value[valueArraySize];
signal input value[maxValueArraySize];
signal input valueArraySize;

signal input issuerClaim[8];
signal input merklized;
Expand Down Expand Up @@ -55,11 +57,24 @@ template ProcessQueryWithModifiers(claimLevels, valueArraySize){
// Query Operator Processing
/////////////////////////////////////////////////////////////////

// verify value array length
// 802 constraints (ArraySizeValidator+ForceEqualIfEnabled)
signal arrSizeSatisfied <== ArraySizeValidator(maxValueArraySize)(
valueArraySize <== valueArraySize,
operator <== operator
);

ForceEqualIfEnabled()(
enabled,
[arrSizeSatisfied, 1]
);

// verify query
// 1756 constraints (Query+LessThan+ForceEqualIfEnabled)
signal querySatisfied <== Query(valueArraySize)(
signal querySatisfied <== Query(maxValueArraySize)(
in <== fieldValue,
value <== value,
valueArraySize <== valueArraySize,
operator <== operator
);

Expand Down
12 changes: 7 additions & 5 deletions circuits/lib/query/query.circom
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ include "comparators.circom";
7 - less than or equal
8 - greater than or equal
9 - between
10 - not between
Modifier/computation operators:
16 - selective disclosure (16 = 10000 binary)
*/

// Query template works only with Query operators (0-15), for the rest returns 0
template Query (valueArraySize) {
template Query (maxValueArraySize) {
// signals
signal input in;
signal input value[valueArraySize];
signal input value[maxValueArraySize];
signal input valueArraySize;
signal input operator;
signal output out;

Expand All @@ -46,8 +48,8 @@ template Query (valueArraySize) {
// gte
signal gte <== NOT()(lt); // gte === !lt

// in
signal inComp <== IN(valueArraySize)(in, value);
// in w/o - 65668, IN - 65860( 192), InWithDynamicArraySize - 66372 (704)
signal inComp <== InWithDynamicArraySize(maxValueArraySize)(in, value, valueArraySize);

// between (value[0] <= in <= value[1])
signal gt2 <== GreaterThan254()([in, value[1]]);
Expand All @@ -73,7 +75,7 @@ template Query (valueArraySize) {
queryOpSatisfied.c[7] <== lte; // lte === !gt
queryOpSatisfied.c[8] <== gte; // gte === !lt
queryOpSatisfied.c[9] <== between; // between
queryOpSatisfied.c[10] <== 0; // not used
queryOpSatisfied.c[10] <== NOT()(between); // not between
queryOpSatisfied.c[11] <== 0; // not used
queryOpSatisfied.c[12] <== 0; // not used
queryOpSatisfied.c[13] <== 0; // not used
Expand Down
79 changes: 79 additions & 0 deletions circuits/lib/utils/arraySizeValidator.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
pragma circom 2.1.1;
include "../../../node_modules/circomlib/circuits/mux1.circom";
include "../../../node_modules/circomlib/circuits/mux4.circom";
include "../../../node_modules/circomlib/circuits/bitify.circom";
include "../../../node_modules/circomlib/circuits/comparators.circom";
include "../../../node_modules/circomlib/circuits/gates.circom";
include "../query/comparators.circom";
/*
Operators:
Query operators - valueArraySize
0 - noop - 0 elements
1 - equals - 1 element
2 - less than - 1 element
3 - greater than - 1 element
4 - in - less or eq than maxValueArraySize
5 - not in - less or eq than maxValueArraySize
6 - not equals - 1 element
7 - less than or equal - 1 element
8 - greater than or equal - 1 element
9 - between - 2 elements
10 - not between - 2 elements
Modifier/computation operators:
16 - selective disclosure (16 = 10000 binary) - 0 elements
17-31 - 0 elements
*/

// ArraySizeValidator template check valueArraySize for query operators
template ArraySizeValidator (maxValueArraySize) {
// signals
signal input valueArraySize;
signal input operator;
signal output out;

signal sizeEqZero <== IsEqual()([valueArraySize, 0]);
signal sizeEqOne <== IsEqual()([valueArraySize, 1]);
signal sizeEqTwo <== IsEqual()([valueArraySize, 2]);
signal sizeLessOrEqMax <== LessThan(8)([valueArraySize, maxValueArraySize + 1]);

signal sizeNotEqZero <== NOT()(sizeEqZero);
signal moreThanZeroLessOrEqMax <== AND()(sizeNotEqZero, sizeLessOrEqMax);

signal opBits[5] <== Num2Bits(5)(operator); // values 0-15 are query operators, 16-31 - modifiers/computations

assert(maxValueArraySize <= 256);

// query operator mux
component mux = Mux4();
mux.s <== [opBits[0], opBits[1], opBits[2], opBits[3]];

// We don't use 5th bit (opBits[4]) here; which specifies whether operator is query or
// modifier/computation operator. It's used in the final mux.
_ <== opBits[4];

mux.c[0] <== sizeEqZero; // noop; skip execution
mux.c[1] <== sizeEqOne; // equals
mux.c[2] <== sizeEqOne; // lt
mux.c[3] <== sizeEqOne; // gt
mux.c[4] <== moreThanZeroLessOrEqMax; // in
mux.c[5] <== moreThanZeroLessOrEqMax; // nin
mux.c[6] <== sizeEqOne; // neq
mux.c[7] <== sizeEqOne; // lte
mux.c[8] <== sizeEqOne; // gte
mux.c[9] <== sizeEqTwo; // between
mux.c[10] <== sizeEqTwo; // not between
mux.c[11] <== sizeEqZero; // not used
mux.c[12] <== sizeEqZero; // not used
mux.c[13] <== sizeEqZero; // not used
mux.c[14] <== sizeEqZero; // not used
mux.c[15] <== sizeEqZero; // not used

// final output mux
out <== Mux1()(
s <== opBits[4], // specifies whether operator is query or modifier/computation operator
c <== [mux.out, sizeEqZero]
);

}


12 changes: 7 additions & 5 deletions circuits/offchain/credentialAtomicQueryV3OffChain.circom
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ include "../lib/utils/nullify.circom";
include "../lib/utils/idUtils.circom";
include "../lib/utils/safeOne.circom";

template credentialAtomicQueryV3OffChain(issuerLevels, claimLevels, valueArraySize) {
template credentialAtomicQueryV3OffChain(issuerLevels, claimLevels, maxValueArraySize) {
// common outputs for Sig and MTP
signal output merklized;
signal output userID;
Expand Down Expand Up @@ -50,8 +50,8 @@ template credentialAtomicQueryV3OffChain(issuerLevels, claimLevels, valueArraySi
signal input claimPathValue; // value in this path in merklized json-ld document
signal input slotIndex;
signal input operator;
signal input value[valueArraySize];

signal input value[maxValueArraySize];
signal input valueArraySize;
signal input issuerClaim[8];

// MTP specific
Expand Down Expand Up @@ -210,8 +210,9 @@ template credentialAtomicQueryV3OffChain(issuerLevels, claimLevels, valueArraySi
merklized <== merklize.flag;

// check path/in node exists in merkletree specified by jsonldRoot
signal operatorNotNoop <== NOT()(IsZero()(operator));
SMTVerifier(claimLevels)(
enabled <== merklize.flag, // if merklize flag 0 skip MTP verification
enabled <== AND()(merklize.flag, operatorNotNoop), // if merklize flag 0 or NOOP operator skip MTP verification
fnc <== claimPathNotExists, // inclusion
root <== merklize.out,
siblings <== claimPathMtp,
Expand All @@ -237,7 +238,7 @@ template credentialAtomicQueryV3OffChain(issuerLevels, claimLevels, valueArraySi
// Process Query with Modifiers
/////////////////////////////////////////////////////////////////
// output value only if modifier operation was selected
operatorOutput <== ProcessQueryWithModifiers(claimLevels, valueArraySize)(
operatorOutput <== ProcessQueryWithModifiers(claimLevels, maxValueArraySize)(
one,
claimPathNotExists,
claimPathMtp,
Expand All @@ -249,6 +250,7 @@ template credentialAtomicQueryV3OffChain(issuerLevels, claimLevels, valueArraySi
slotIndex,
operator,
value,
valueArraySize,
issuerClaim,
merklized,
merklize.out
Expand Down
17 changes: 10 additions & 7 deletions circuits/onchain/credentialAtomicQueryV3OnChain.circom
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ checks:
idOwnershipLevels - Merkle tree depth level for personal claims
issuerLevels - Merkle tree depth level for claims issued by the issuer
claimLevels - Merkle tree depth level for claim JSON-LD document
valueArraySize - Number of elements in comparison array for in/notin operation if level = 3 number of values for
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, valueArraySize, idOwnershipLevels, onChainLevels) {
template credentialAtomicQueryV3OnChain(issuerLevels, claimLevels, maxValueArraySize, idOwnershipLevels, onChainLevels) {
// flag indicates if merklized flag set in issuer claim (if set MTP is used to verify that
// claimPathValue and claimPathKey are stored in the merkle tree) and verification is performed
// on root stored in the index or value slot
Expand Down Expand Up @@ -123,7 +123,8 @@ template credentialAtomicQueryV3OnChain(issuerLevels, claimLevels, valueArraySiz

signal input slotIndex;
signal input operator;
signal input value[valueArraySize];
signal input value[maxValueArraySize];
signal input valueArraySize;

// MTP specific
signal input issuerClaimMtp[issuerLevels];
Expand Down Expand Up @@ -203,7 +204,7 @@ template credentialAtomicQueryV3OnChain(issuerLevels, claimLevels, valueArraySiz
// Claim checks
/////////////////////////////////////////////////////////////////

(merklized, userID, issuerState, linkID, nullifier, operatorOutput) <== credentialAtomicQueryV3OffChain(issuerLevels, claimLevels, valueArraySize)(
(merklized, userID, issuerState, linkID, nullifier, operatorOutput) <== credentialAtomicQueryV3OffChain(issuerLevels, claimLevels, maxValueArraySize)(
proofType <== proofType,
requestID <== requestID,
userGenesisID <== userGenesisID,
Expand Down Expand Up @@ -231,6 +232,7 @@ template credentialAtomicQueryV3OnChain(issuerLevels, claimLevels, valueArraySiz
slotIndex <== slotIndex,
operator <== operator,
value <== value,
valueArraySize <== valueArraySize,
issuerClaim <== issuerClaim,
issuerClaimMtp <== issuerClaimMtp,
issuerClaimClaimsTreeRoot <== issuerClaimClaimsTreeRoot,
Expand Down Expand Up @@ -258,14 +260,15 @@ template credentialAtomicQueryV3OnChain(issuerLevels, claimLevels, valueArraySiz
/////////////////////////////////////////////////////////////////
// Verify query hash matches
/////////////////////////////////////////////////////////////////
signal valueHash <== SpongeHash(valueArraySize, 6)(value); // 6 - max size of poseidon hash available on-chain
signal valueHash <== SpongeHash(maxValueArraySize, 6)(value); // 6 - max size of poseidon hash available on-chain

circuitQueryHash <== Poseidon(6)([
circuitQueryHash <== Poseidon(7)([
claimSchema,
slotIndex,
operator,
claimPathKey,
claimPathNotExists,
valueHash
valueHash,
valueArraySize
]);
}
5 changes: 5 additions & 0 deletions test/circuits/utils/utils_arraySizeValidatorTest.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma circom 2.1.1;

include "../../../circuits/lib/utils/arraySizeValidator.circom";

component main = ArraySizeValidator(64);
23 changes: 22 additions & 1 deletion test/offchain/credentialAtomicQueryV3OffChain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ describe("Test credentialAtomicQueryV3OffChain.circom", function () {
require(`${sigBasePath}/nullify.json`),
require(`${sigBasePath}/revoked_claim_without_revocation_check.json`),
require(`${sigBasePath}/jsonld_non_inclusion.json`),
require(`${sigBasePath}/noop_operator.json`),
require(`${sigBasePath}/not_between_operator.json`),
require(`${sigBasePath}/in_operator.json`),

// mtp
require(`${mtpBasePath}/claimIssuedOnProfileID.json`),
Expand All @@ -54,6 +57,9 @@ describe("Test credentialAtomicQueryV3OffChain.circom", function () {
require(`${mtpBasePath}/selective_disclosure.json`),
require(`${mtpBasePath}/nullify.json`),
require(`${mtpBasePath}/revoked_claim_without_revocation_check.json`),
require(`${mtpBasePath}/noop_operator.json`),
require(`${mtpBasePath}/not_between_operator.json`),
require(`${mtpBasePath}/in_operator.json`),
];

tests.forEach(({ desc, inputs, expOut }) => {
Expand All @@ -67,7 +73,7 @@ describe("Test credentialAtomicQueryV3OffChain.circom", function () {
const failTestCase = [
require(`${sigBasePath}/revoked_claim_with_revocation_check.json`),
require(`${mtpBasePath}/revoked_claim_with_revocation_check.json`),
]
];

failTestCase.forEach(({ desc, inputs, expOut }) => {
it(`${desc}`, async function () {
Expand All @@ -78,4 +84,19 @@ describe("Test credentialAtomicQueryV3OffChain.circom", function () {
expect(error.message).to.include("Error in template checkClaimNotRevoked");
})
});

const failInTestCase = [
require(`${sigBasePath}/in_operator_failed_0.json`),
require(`${mtpBasePath}/in_operator_failed_0.json`),
];

failInTestCase.forEach(({ desc, inputs, expOut }) => {
it(`${desc}`, async function () {
let error;
await circuit.calculateWitness(inputs, true).catch((err) => {
error = err;
});
expect(error.message).to.include("Error in template ProcessQueryWithModifiers");
})
});
});
Loading

0 comments on commit 00154aa

Please sign in to comment.