diff --git a/.gitignore b/.gitignore index f62a47e40e..51daaa88e2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,9 @@ fastcrypto/Cargo.lock fastcrypto-derive/Cargo.lock # .DS_Store files -.DS_Store \ No newline at end of file +.DS_Store + +# JS +node_modules/ + +fastcrypto/openid-zkp-auth/artifacts/ diff --git a/zklogin-circuits/README.md b/zklogin-circuits/README.md new file mode 100644 index 0000000000..1dbfe86f11 --- /dev/null +++ b/zklogin-circuits/README.md @@ -0,0 +1,82 @@ +# zkLogin circuits + +Install via `tsc` and `npm install` + +## Filetree Description + +```bash +circuits/ + zklogin.circom # Main circuit code + zklogin_wrapper.circom # Circom runner + helpers/ + base64.circom + hasher.circom + jwtchecks.circom + misc.circom + sha256.circom + strings.circom +src/ + circuitutils.ts # Circuit utilities + constants.ts # Circuit params + decideparams.ts # A script to decide circuit params based on real JWTs + jwtutils.ts # JWT utilities + prove.ts # Helper script to run the ZKP using a given zkey, vkey, JWT + utils.ts # Generic utilities + verify.ts +test/ + testutils.js # Test utilities + xyz.circom.test.js # testing circom code + abc.test.js # testing js code +testvectors/ + realJWTs.ts # Real JWTs + sampleWalletInputs.json + sampleZKPInputs.json +``` + +## Steps to generate a OpenID signature (using snarkJS prover) + +1. Create a folder named `artifacts` inside `openid-zkp-auth`. + +``` +cd fastcrypto/openid-zkp-auth && mkdir artifacts +``` + +2. Get pre-generated trusted setup and place it inside `artifacts`. + +``` +cd artifacts && wget https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_20.ptau +``` + +3. Generate R1CS and witness generator (WASM): + +``` +cd ../ && circom circuits/zklogin.circom --r1cs --wasm --output artifacts + +# maybe need +npm install +``` + +4. Run circuit-specific trusted setup: `cd artifacts && snarkjs groth16 setup zklogin.r1cs powersOfTau28_hez_final_20.ptau zklogin.zkey` + +5. Export verification key: `snarkjs zkey export verificationkey zklogin.zkey zklogin.vkey` + +6. Create a folder named `proof` inside the `artifacts` directory. Generate a OpenID signature: ``npm run prove <provider> <jwt>``. The last two arguments are optional. `provider` can be either `google` (default) or `twitch`. Default JWTs for both are in `testvectors/realJWTs.js`. + +It generates three files: the zk proof (`zkp.proof`), auxiliary inputs to the verifier (`aux.json`) and public inputs to the ZKP (`public.json`) inside the `proof` folder. + +## Tests + +``tsc; npm test`` + +# Steps to access the back-end + +The back-end takes `WalletInputs` as input and returns `PartialZKLoginSig`. Both these structs are defined in `src/common.ts`. + +## Example via cURL + +``` +1. cd testvectors +2. curl -X POST -H "Content-Type: application/json" -d @./sampleWalletInputs.json http://185.209.177.123:8000/zkp +``` + +Note that the command reads the `WalletInputs` from the JSON file `testvectors/sampleWalletInputs.json`. diff --git a/zklogin-circuits/circuits/helpers/base64.circom b/zklogin-circuits/circuits/helpers/base64.circom new file mode 100644 index 0000000000..51331c5aee --- /dev/null +++ b/zklogin-circuits/circuits/helpers/base64.circom @@ -0,0 +1,144 @@ +pragma circom 2.1.3; + +include "misc.circom"; + +function asciiToBase64Url(i) { + var base64 = 0; + if (i >= 65 && i <= 90) { // A to Z + base64 = i - 65; + } else if (i >= 97 && i <= 122) { // a to z + base64 = i - 71; + } else if (i >= 48 && i <= 57) { // 0 to 9 + base64 = i + 4; + } else if (i == 45) { // - + base64 = 62; + } else if (i == 95) { // _ + base64 = 63; + } + return base64; +} + +/** +Takes as input a base64url character and outputs the corresponding 6-bit value. +If not a valid base64 character, outputs 0. + +Cost: 73 constraints + +- in: The base64url character. Assumed to be a 8-bit number, i.e., in [0, 256) + NOTE: Behavior is undefined otherwise. +- out: The 6-bit value +*/ +template Base64URLToBits() { + signal input in; + signal output out[6]; + + // Compute the base64 representation of in, e.g., if in = 66 ('B') then outascii = 1 + // This step does not add any constraints. + // We check the correctness of the base64 representation next. + signal outascii; + outascii <-- asciiToBase64Url(in); + + component lt91; + lt91 = LessThan(8); + lt91.in[0] <== in; + lt91.in[1] <== 91; + + component gt64; + gt64 = GreaterThan(8); + gt64.in[0] <== in; + gt64.in[1] <== 64; + + // If in is in [65, 90], then outascii = in - 65 (line 8 of asciiToBase64Url) + component forceequal1; + forceequal1 = MyForceEqualIfEnabled(); + forceequal1.enabled <== lt91.out * gt64.out; + forceequal1.in[0] <== outascii; + forceequal1.in[1] <== in - 65; + + component lt123; + lt123 = LessThan(8); + lt123.in[0] <== in; + lt123.in[1] <== 123; + + component gt96; + gt96 = GreaterThan(8); + gt96.in[0] <== in; + gt96.in[1] <== 96; + + // If in is in [97, 122], then outascii = in - 71 (line 10 of asciiToBase64Url) + component forceequal2; + forceequal2 = MyForceEqualIfEnabled(); + forceequal2.enabled <== lt123.out * gt96.out; + forceequal2.in[0] <== outascii; + forceequal2.in[1] <== in - 71; + + component lt58; + lt58 = LessThan(8); + lt58.in[0] <== in; + lt58.in[1] <== 58; + + component gt47; + gt47 = GreaterThan(8); + gt47.in[0] <== in; + gt47.in[1] <== 47; + + // If in is in [48, 57], then outascii = in + 4 (line 12 of asciiToBase64Url) + component forceequal3; + forceequal3 = MyForceEqualIfEnabled(); + forceequal3.enabled <== lt58.out * gt47.out; + forceequal3.in[0] <== outascii; + forceequal3.in[1] <== in + 4; + + component eq45; + eq45 = IsEqual(); + eq45.in[0] <== in; + eq45.in[1] <== 45; + + // If in is 45, then outascii = 62 (line 14 of asciiToBase64Url) + component forceequal4; + forceequal4 = MyForceEqualIfEnabled(); + forceequal4.enabled <== eq45.out; + forceequal4.in[0] <== outascii; + forceequal4.in[1] <== 62; + + component eq95; + eq95 = IsEqual(); + eq95.in[0] <== in; + eq95.in[1] <== 95; + + // If in is 95, then outascii = 63 (line 16 of asciiToBase64Url) + component forceequal5; + forceequal5 = MyForceEqualIfEnabled(); + forceequal5.enabled <== eq95.out; + forceequal5.in[0] <== outascii; + forceequal5.in[1] <== 63; + + // Note: any = 0 happens only if all the enabled signals are 0. + // This is because all the enabled signals are guaranteed to be either 0 or 1. + var any = 1 - (forceequal1.enabled + forceequal2.enabled + forceequal3.enabled + forceequal4.enabled + forceequal5.enabled); + + // If in is not a valid base64url character, then outascii = 0 + component forceequal6; + forceequal6 = MyForceEqualIfEnabled(); + forceequal6.enabled <== any; + forceequal6.in[0] <== outascii; + forceequal6.in[1] <== 0; + + component convert = Num2BitsBE(6); + convert.in <== outascii; + for (var i = 0; i < 6; i++) { + out[i] <== convert.out[i]; + } +} + +template MultiBase64URLToBits(n) { + signal input in[n]; + signal output out[n * 6]; + + for (var i = 0; i < n; i++) { + var bits[6] = Base64URLToBits()(in[i]); + for (var j = 0; j < 6; j++) { + out[i * 6 + j] <== bits[j]; + } + } +} \ No newline at end of file diff --git a/zklogin-circuits/circuits/helpers/hasher.circom b/zklogin-circuits/circuits/helpers/hasher.circom new file mode 100644 index 0000000000..0ca5cbeee5 --- /dev/null +++ b/zklogin-circuits/circuits/helpers/hasher.circom @@ -0,0 +1,33 @@ +pragma circom 2.1.3; + +include "../../node_modules/circomlib/circuits/poseidon.circom"; + +template Hasher(nInputs) { + signal input in[nInputs]; + signal output out; + + component pos1, pos2; + // See https://github.com/iden3/circomlib/blob/master/circuits/poseidon.circom#L78. + // It implicitly forces t <= 17 or nInputs <= 16. + // TODO: The current limitation of 15 comes from the rust library we use (https://github.com/Lightprotocol/light-poseidon). + if (nInputs <= 15) { + out <== Poseidon(nInputs)(in); + } else if (nInputs <= 30) { + pos1 = Poseidon(15); + pos2 = Poseidon(nInputs - 15); + + for (var i = 0; i < 15; i++) { + pos1.inputs[i] <== in[i]; + } + for (var i = 15; i < nInputs; i++) { + pos2.inputs[i - 15] <== in[i]; + } + + out <== Poseidon(2)([ + pos1.out, + pos2.out + ]); + } else { // Yet to be implemented + 1 === 0; + } +} \ No newline at end of file diff --git a/zklogin-circuits/circuits/helpers/jwtchecks.circom b/zklogin-circuits/circuits/helpers/jwtchecks.circom new file mode 100644 index 0000000000..7b2dbc7fae --- /dev/null +++ b/zklogin-circuits/circuits/helpers/jwtchecks.circom @@ -0,0 +1,151 @@ +pragma circom 2.1.3; + +include "strings.circom"; +include "misc.circom"; +include "hasher.circom"; + +template StringChecker(maxLen) { + signal input str[maxLen]; + signal input len; + + // first char is a quote + str[0] === 34; // '"' + + // last char is a quote + signal last_quote <== SingleMultiplexer(maxLen)(str, len - 1); + last_quote === 34; // '"' + + // str[i] == 0 for all i >= len + signal sigt[maxLen] <== GTBitVector(maxLen)(len - 1); + for (var i = 0; i < maxLen; i++) { + sigt[i] * str[i] === 0; + } +} + +template isWhitespace() { + signal input c; + + signal is_space <== IsEqual()([c, 0x20]); // 0x20 = decimal 32 + signal is_tab <== IsEqual()([c, 0x09]); // 0x09 = decimal 9 + signal is_newline <== IsEqual()([c, 0x0A]); // 0x0A = decimal 10 + signal is_carriage_return <== IsEqual()([c, 0x0D]); // 0x0D = decimal 13 + + signal output is_whitespace <== is_space + is_tab + is_newline + is_carriage_return; +} + +/** +ExtendedClaimParser + +string ws : ws value ws E + | | | | | + n c vs ve l => n = name_len, c = ':', vs = value_start, ve = value_end, l = length +**/ +template ExtendedClaimParser(maxExtendedClaimLen, maxKeyClaimNameLen, maxKeyClaimValueLen) { + assert(maxExtendedClaimLen > 0); + assert(maxKeyClaimNameLen > 0); + assert(maxKeyClaimValueLen > 0); + assert(maxExtendedClaimLen == maxKeyClaimNameLen + maxKeyClaimValueLen + 2); // +2 for colon and comma/brace + + // TODO: Add range checks for all inputs + signal input extended_claim[maxExtendedClaimLen]; + signal input length; + + signal input name_len; + signal input colon_index; + signal input value_start; + signal input value_len; + + signal output name[maxKeyClaimNameLen] <== Slice(maxExtendedClaimLen, maxKeyClaimNameLen)( + extended_claim, + 0, + name_len + ); + // Is name a valid JSON string? (All JSON keys are strings) + StringChecker(maxKeyClaimNameLen)(name, name_len); + + signal output value[maxKeyClaimValueLen] <== Slice(maxExtendedClaimLen, maxKeyClaimValueLen)( + extended_claim, + value_start, + value_len + ); + // Is value a valid JSON string? + StringChecker(maxKeyClaimValueLen)(value, value_len); + // NOTE: In theory, JSON values need not be strings. But we only use this parser for parsing strings, so this is fine. + + // Whitespaces + signal is_whitespace[maxExtendedClaimLen]; + for (var i = 0; i < maxExtendedClaimLen; i++) { + is_whitespace[i] <== isWhitespace()(extended_claim[i]); + } + + signal is_gt_n[maxExtendedClaimLen] <== GTBitVector(maxExtendedClaimLen)(name_len - 1); + signal is_lt_c[maxExtendedClaimLen] <== LTBitVector(maxExtendedClaimLen)(colon_index); + signal selector1[maxExtendedClaimLen] <== vectorAND(maxExtendedClaimLen)(is_gt_n, is_lt_c); + for (var i = 0; i < maxExtendedClaimLen; i++) { + selector1[i] * (1 - is_whitespace[i]) === 0; + } + + signal is_gt_c[maxExtendedClaimLen] <== GTBitVector(maxExtendedClaimLen)(colon_index); + signal is_lt_vs[maxExtendedClaimLen] <== LTBitVector(maxExtendedClaimLen)(value_start); + signal selector2[maxExtendedClaimLen] <== vectorAND(maxExtendedClaimLen)(is_gt_c, is_lt_vs); + for (var i = 0; i < maxExtendedClaimLen; i++) { + selector2[i] * (1 - is_whitespace[i]) === 0; + } + + signal is_gt_ve[maxExtendedClaimLen] <== GTBitVector(maxExtendedClaimLen)(value_start + value_len - 1); + signal is_lt_l[maxExtendedClaimLen] <== LTBitVector(maxExtendedClaimLen)(length - 1); + signal selector3[maxExtendedClaimLen] <== vectorAND(maxExtendedClaimLen)(is_gt_ve, is_lt_l); + for (var i = 0; i < maxExtendedClaimLen; i++) { + selector3[i] * (1 - is_whitespace[i]) === 0; + } + + // Colon is at index colon_index + signal colon <== SingleMultiplexer(maxExtendedClaimLen)(extended_claim, colon_index); + colon === 58; // ':' + + // Last char is either end-brace or comma + signal last_char <== SingleMultiplexer(maxExtendedClaimLen)(extended_claim, length - 1); + (last_char - 125) * (last_char - 44) === 0; // '}' or ',' +} + +/** +NonceChecker: Checks that the extended claim string representing nonce is valid. + a) The first 9 chars are '"nonce":"' + b) The last two chars are '"}' or '",' + c) The chars in between are base64url encoded bits of the nonce + +Construction Params: + nonceValueLength: length of the extended claim string representing nonce + nonceBitLen: length of the nonce in bits +**/ +template NonceChecker(nonceValueLength, nonceBitLen) { + assert(nonceValueLength > 0); + assert(nonceBitLen > 0); + + signal input expected_nonce; + signal expected_bits[nonceBitLen] <== Num2BitsBE(nonceBitLen)(expected_nonce); + + signal input actual_nonce[nonceValueLength]; + + // first char is a quote + actual_nonce[0] === 34; // '"' + + // last char is a quote + actual_nonce[nonceValueLength - 1] === 34; // '"' + + // Remove the 9 character prefix and two character suffix to get the actual nonce + var nonceLength = nonceValueLength - 2; + var value[nonceLength]; + for (var i = 0; i < nonceLength; i++) { + value[i] = actual_nonce[i + 1]; + } + + // Convert the base64url encoded nonce to bits + signal actual_bits[6 * nonceLength] <== MultiBase64URLToBits(nonceLength)(value); + + // Check every bit of expected nonce against the actual nonce + assert(6 * nonceLength >= nonceBitLen); + for (var i = 0; i < nonceBitLen; i++) { + expected_bits[i] === actual_bits[i]; + } +} diff --git a/zklogin-circuits/circuits/helpers/misc.circom b/zklogin-circuits/circuits/helpers/misc.circom new file mode 100644 index 0000000000..2aca8be011 --- /dev/null +++ b/zklogin-circuits/circuits/helpers/misc.circom @@ -0,0 +1,338 @@ +pragma circom 2.1.3; + +include "../../node_modules/circomlib/circuits/comparators.circom"; +include "../../node_modules/circomlib/circuits/gates.circom"; +include "../../node_modules/circomlib/circuits/mux2.circom"; +include "../../node_modules/circomlib/circuits/multiplexer.circom"; + +include "hasher.circom"; + +/** +DivideMod2Power: Returns quotient (in / 2^p) and remainder (in % 2^p) + +Range checks: + - 0 <= in < 2^n (Checked in Num2Bits) + - 0 < p < n +**/ +template DivideMod2Power(n, p) { + assert(n <= 252); // n <= log(p) - 2 + assert(p < n); + assert(p > 0); + + signal input in; + component toBits = Num2Bits(n); + toBits.in <== in; + + component fromBitsQ = Bits2Num(n - p); + for (var i = 0; i < n - p; i++) { + fromBitsQ.in[i] <== toBits.out[i + p]; + } + signal output quotient <== fromBitsQ.out; + + component fromBitsR = Bits2Num(p); + for (var i = 0; i < p; i++) { + fromBitsR.in[i] <== toBits.out[i]; + } + signal output remainder <== fromBitsR.out; +} + +/** +RemainderMod4: Calculates in % 4. + +Construction Params: + - n: The bitwidth of in. + +Range checks: + - 0 <= in < 2^n (Checked in Num2Bits) +**/ +template RemainderMod4(n) { + assert(n <= 252); // n <= log(p) - 2 + + signal input in; + signal output out; + + component toBits = Num2Bits(n); + toBits.in <== in; + out <== 2 * toBits.out[1] + toBits.out[0]; +} + +/** +RangeCheck: Checks if 0 <= in <= max. + +Construction params: + - n: The bitwidth of in and max. + - max: The maximum value that in can take. + +Range checks: + - 0 <= in (Checked in Num2Bits) + - in <= max +**/ +template RangeCheck(n, max) { + assert(n <= 252); // n <= log(p) - 2 + assert(max >= 0); + assert(numBits(max) <= n); + + signal input in; + var unusedVar[n] = Num2Bits(n)(in); + + signal leq <== LessEqThan(n)([in, max]); + leq === 1; +} + +// Returns the number of bits needed to represent a number. +// Helper function intended to operate only over construction params. +function numBits(a) { + assert(a >= 0); + if (a == 0 || a == 1) { + return 1; + } + return 1 + numBits(a >> 1); +} + +/** +Num2BitsBE: Converts a number to a list of bits, in big-endian order. + +Range checks: + - 0 <= in < 2^n (Like with Num2Bits). +**/ +template Num2BitsBE(n) { + signal input in; + signal output out[n]; + var lc1 = 0; + + var e2 = 1; + for (var i = 0; i < n; i++) { + var b = (n - 1) - i; + out[b] <-- (in >> i) & 1; + out[b] * (out[b] - 1 ) === 0; + lc1 += out[b] * e2; + e2 = e2 + e2; + } + + lc1 === in; +} + +/** +Bits2NumBE: Converts a list of bits to a number, in big-endian order. + +Note: It is assumed that each input bit is either 0 or 1. +**/ +template Bits2NumBE(n) { + signal input in[n]; + signal output out; + var lc1=0; + + var e2 = 1; + for (var i = 0; i < n; i++) { + lc1 += in[(n - 1) - i] * e2; + e2 = e2 + e2; + } + + lc1 ==> out; +} + +/** +Segments2NumBE: Converts a list of w-bit segments to a number, in big-endian order. + +Note: It is assumed that each input segment is a number between 0 and 2^w - 1. +**/ +template Segments2NumBE(n, w) { + assert(n * w <= 253); + + signal input in[n]; + signal output out; + var lc1=0; + + var e2 = 1; + for (var i = 0; i < n; i++) { + lc1 += in[(n - 1) - i] * e2; + e2 = e2 * (1 << w); + } + + lc1 ==> out; +} + +/** +MyForceEqualIfEnabled: Optimized version of the official ForceEqualIfEnabled + +If enabled is 1, then in[0] == in[1]. Otherwise, in[0] and in[1] can be anything. + +Original: https://github.com/iden3/circomlib/blob/master/circuits/comparators.circom#L48 +**/ +template MyForceEqualIfEnabled() { + signal input enabled; + signal input in[2]; + + (in[1] - in[0]) * enabled === 0; +} + +/** +Sum: Calculates the sum of all the input elements. +**/ +template Sum(n) { + signal input nums[n]; + signal output sum; + + var lc; + for (var i = 0; i < n; i++) { + lc += nums[i]; + } + sum <== lc; +} + +/** +OneBitVector: Given an index, returns a vector of size n with a 1 in the index-th position. + +out[i] = 1 if i = index, 0 otherwise. + +Range checks: + - index in [0, n). Fails otherwise. +**/ +template OneBitVector(n) { + signal input index; + signal output out[n]; + + component X = Decoder(n); + X.inp <== index; + + X.success === 1; + out <== X.out; +} + +/** +GTBitVector: Given an index, returns a vector of size n with a 1 in all indices greater than index. + +out[i] = 1 if i > index, 0 otherwise + +Range checks: + - index in [0, n). Fails otherwise. +**/ +template GTBitVector(n) { + signal input index; + signal output out[n]; + + signal eq[n] <== OneBitVector(n)(index); + + out[0] <== 0; + for (var i = 1; i < n; i++) { + out[i] <== eq[i - 1] + out[i - 1]; + } +} + +/** +LTBitVector: Given an index, returns a vector of size n with a 1 in all indices less than index. + +out[i] = 1 if i < index, 0 otherwise + +Range checks: + - index in (0, n]. Fails otherwise. +**/ +template LTBitVector(n) { + signal input index; + signal output out[n]; + + signal eq[n] <== OneBitVector(n)(index - 1); + + out[n-1] <== eq[n-1]; + for (var i = n-2; i >= 0; i--) { + out[i] <== eq[i] + out[i + 1]; + } +} + +/** +SingleMultiplexer: Given a list of inputs, selects one of them based on the value of sel. + +More precisely, out = inp[sel]. + +Range checks: + - 0 <= sel < nIn (Checked in OneBitVector) +**/ +template SingleMultiplexer(nIn) { + signal input inp[nIn]; + signal input sel; + signal output out; + + component dec = OneBitVector(nIn); + sel ==> dec.index; + EscalarProduct(nIn)(inp, dec.out) ==> out; +} + +// Returns the number of output chunks (each of size outWidth) needed to represent inBits. +function getBaseConvertedOutputSize(inBits, outWidth) { + var outCount = inBits \ outWidth; + if (inBits % outWidth != 0) { + outCount++; + } + return outCount; +} + +/** +ConvertBase: Given a list of numbers, each of a specified bitwidth, + converts it into another list of numbers with a different bitwidth. + +- inWidth: The bitwidth of each input number. +- inCount: The number of input numbers. +- outWidth: The bitwidth of each output number. +- outCount: The number of output numbers. (Should be inCount * inWidth / outWidth, rounded up.) +*/ +template ConvertBase(inWidth, inCount, outWidth, outCount) { + signal input in[inCount]; + signal output out[outCount]; + + var inBits = inCount * inWidth; + var myOutCount = getBaseConvertedOutputSize(inBits, outWidth); + assert(myOutCount == outCount); + + component expander[inCount]; + for (var i = 0; i < inCount; i++) { + expander[i] = Num2BitsBE(inWidth); + expander[i].in <== in[i]; + } + + component compressor[outCount]; + for (var i = 0; i < outCount; i++) { + compressor[i] = Bits2NumBE(outWidth); + } + + for(var i = 0; i < inBits; i++) { + var oB = i % outWidth; + var o = (i - oB) \ outWidth; + + var mB = i % inWidth; + var m = (i - mB) \ inWidth; + + compressor[o].in[oB] <== expander[m].out[mB]; + } + + if (inBits % outWidth != 0) { + var outExtra = inBits % outWidth; + for(var i = outExtra; i < outWidth; i++) { + compressor[outCount - 1].in[i] <== 0; + } + } + + for(var i = 0; i < outCount; i++) { + out[i] <== compressor[i].out; + } +} + +template MapToField(maxLen) { + var inWidth = 8; + var packWidth = 248; // smallest multiple of 8 less than 256 + signal input str[maxLen]; + + var outCount = getBaseConvertedOutputSize(maxLen * inWidth, packWidth); + signal packed[outCount] <== ConvertBase(inWidth, maxLen, packWidth, outCount)(str); + signal output str_F <== Hasher(outCount)(packed); +} + +template vectorAND(n) { + signal input a[n]; + signal input b[n]; + + signal output out[n]; + + for (var i = 0; i < n; i++) { + out[i] <== a[i] * b[i]; + } +} \ No newline at end of file diff --git a/zklogin-circuits/circuits/helpers/sha256.circom b/zklogin-circuits/circuits/helpers/sha256.circom new file mode 100644 index 0000000000..217a9caadc --- /dev/null +++ b/zklogin-circuits/circuits/helpers/sha256.circom @@ -0,0 +1,178 @@ +pragma circom 2.1.3; + +include "../../node_modules/circomlib/circuits/sha256/constants.circom"; +include "../../node_modules/circomlib/circuits/sha256/sha256compression.circom"; +include "../../node_modules/circomlib/circuits/comparators.circom"; +include "misc.circom"; + +/** +SHA256 Unsafe + Calculates the SHA256 hash of the input, using a signal to select the output round corresponding to the number of + non-empty input blocks. This implementation is referred to as "unsafe", as it relies upon the caller to ensure that + the input is padded correctly, and to ensure that the num_sha2_blocks input corresponds to the actual terminating data block. + Crafted inputs could result in Length Extension Attacks. + + Construction Parameters: + - nBlocks: Maximum number of 512-bit blocks for payload input + + Inputs: + - in: An array of blocks exactly nBlocks in length, each block containing an array of exactly 512 bits. + Padding of the input according to RFC4634 Section 4.1 is left to the caller. + - num_sha2_blocks: A number representing the number of 64-byte blocks to consider from the input. + + Outputs: + - out: An array of 256 bits corresponding to the SHA256 output. + We hash the blocks starting from in[0] upto in[num_sha2_blocks-1] (inclusive). +*/ +template Sha256_unsafe(nBlocks) { + signal input in[nBlocks][512]; + signal input num_sha2_blocks; + + signal output out[256]; + + component ha0 = H(0); + component hb0 = H(1); + component hc0 = H(2); + component hd0 = H(3); + component he0 = H(4); + component hf0 = H(5); + component hg0 = H(6); + component hh0 = H(7); + + component sha256compression[nBlocks]; + + for(var i = 0; i < nBlocks; i++) { + sha256compression[i] = Sha256compression(); + if (i==0) { + for(var k = 0; k < 32; k++) { + sha256compression[i].hin[0*32+k] <== ha0.out[k]; + sha256compression[i].hin[1*32+k] <== hb0.out[k]; + sha256compression[i].hin[2*32+k] <== hc0.out[k]; + sha256compression[i].hin[3*32+k] <== hd0.out[k]; + sha256compression[i].hin[4*32+k] <== he0.out[k]; + sha256compression[i].hin[5*32+k] <== hf0.out[k]; + sha256compression[i].hin[6*32+k] <== hg0.out[k]; + sha256compression[i].hin[7*32+k] <== hh0.out[k]; + } + } else { + for(var k = 0; k < 32; k++) { + sha256compression[i].hin[32*0+k] <== sha256compression[i-1].out[32*0+31-k]; + sha256compression[i].hin[32*1+k] <== sha256compression[i-1].out[32*1+31-k]; + sha256compression[i].hin[32*2+k] <== sha256compression[i-1].out[32*2+31-k]; + sha256compression[i].hin[32*3+k] <== sha256compression[i-1].out[32*3+31-k]; + sha256compression[i].hin[32*4+k] <== sha256compression[i-1].out[32*4+31-k]; + sha256compression[i].hin[32*5+k] <== sha256compression[i-1].out[32*5+31-k]; + sha256compression[i].hin[32*6+k] <== sha256compression[i-1].out[32*6+31-k]; + sha256compression[i].hin[32*7+k] <== sha256compression[i-1].out[32*7+31-k]; + } + } + + for (var k = 0; k < 512; k++) { + sha256compression[i].inp[k] <== in[i][k]; + } + } + + // Collapse the hashing result at the terminating data block + component totals[256]; + signal eqs[nBlocks] <== OneBitVector(nBlocks)(num_sha2_blocks - 1); + + // For each bit of the output + for(var k = 0; k < 256; k++) { + totals[k] = Sum(nBlocks); + + // For each possible block + for (var i = 0; i < nBlocks; i++) { + // eqs[i].out is 1 if the index matches. As such, at most one input to totals is not 0. + // The bit corresponding to the terminating data block will be raised + totals[k].nums[i] <== eqs[i] * sha256compression[i].out[k]; + } + out[k] <== totals[k].sum; + } +} + +/** +SHA2_Wrapper + + Calculates the SHA2 hash of an arbitrarily shaped input using SHA256_unsafe internally. + Additionally, it packs the output and checks that all inputs after num_sha2_blocks are indeed 0. + + Construction Parameters: + - inWidth: Width of each input segment in bits + - inCount: Number of input segments + + Inputs: + - in: An array of segments exactly inCount in length, each segment containing an array of exactly inWidth bits. + Padding of the input according to RFC4634 Section 4.1 is left to the caller. + - num_sha2_blocks: An integer corresponding to the terminating block of the input, which contains the message padding. + + Outputs: + - hash: An array of size 2 corresponding to the SHA256 output as of the terminating block split into two. + The first element contains the first 128 bits of the hash, and the second element contains the last 128 bits. +*/ +template Sha2_wrapper(inWidth, inCount, outWidth, outCount) { + // Segments must divide evenly into 512 bit blocks + var inBits = inCount * inWidth; + assert(inBits % 512 == 0); + + signal input in[inCount]; + signal input num_sha2_blocks; + + assert(outWidth * outCount == 256); + signal output hash[outCount]; + + // The content is decomposed to 512-bit blocks for SHA-256 + var nBlocks = (inWidth * inCount) / 512; + component sha256 = Sha256_unsafe(nBlocks); + + // How many segments are in each block + assert(inWidth <= 512); + assert(512 % inWidth == 0); + var nSegments = 512 / inWidth; + component sha256_blocks[nBlocks][nSegments]; + + // For each 512-bit block going into SHA-256 + for (var b = 0; b < nBlocks; b++) { + // For each segment going into that block + for (var s = 0; s < nSegments; s++) { + // The index from the content is offset by the block we're composing times the number of segments per block, + // s is then the segment offset within the block. + var payloadIndex = (b * nSegments) + s; + + // Decompose each segment into an array of individual bits + sha256_blocks[b][s] = Num2BitsBE(inWidth); + sha256_blocks[b][s].in <== in[payloadIndex]; + + // The bit index going into the current SHA-256 block is offset by the segment number times the bit width + // of each content segment. sOffset + i is then the bit offset within the block (0-511). + var sOffset = s * inWidth; + for (var i = 0; i < inWidth; i++) { + sha256.in[b][sOffset + i] <== sha256_blocks[b][s].out[i]; + } + } + } + sha256.num_sha2_blocks <== num_sha2_blocks; + + /** + Pack the output of the SHA-256 hash into a vector of size outCount where each element has outWidth bits. + **/ + component hash_packer[outCount]; + for (var i = 0; i < outCount; i++) { + hash_packer[i] = Bits2NumBE(outWidth); + for (var j = 0; j < outWidth; j++) { + hash_packer[i].in[j] <== sha256.out[i * outWidth + j]; + } + hash_packer[i].out ==> hash[i]; + } + + /** + Verify that content[i] for all blocks >= num_sha2_blocks is zero. + **/ + signal gte[nBlocks] <== GTBitVector(nBlocks)(num_sha2_blocks - 1); + + for (var b = 0; b < nBlocks; b++) { + for (var s = 0; s < nSegments; s++) { + var payloadIndex = (b * nSegments) + s; + gte[b] * in[payloadIndex] === 0; + } + } +} \ No newline at end of file diff --git a/zklogin-circuits/circuits/helpers/strings.circom b/zklogin-circuits/circuits/helpers/strings.circom new file mode 100644 index 0000000000..c4e34fcb36 --- /dev/null +++ b/zklogin-circuits/circuits/helpers/strings.circom @@ -0,0 +1,299 @@ +pragma circom 2.1.3; + +include "../../node_modules/circomlib/circuits/comparators.circom"; +include "../../node_modules/circomlib/circuits/multiplexer.circom"; +include "../../node_modules/circomlib/circuits/gates.circom"; +include "misc.circom"; +include "base64.circom"; + +/** +SliceFixed: Returns a fixed-length slice of an array. +More precisely, in[index:index+outLen] (both inclusive). + +Cost: inLen + outLen * inLen + +Range checks: + - index in [0, inLen) + - index + outLen in [0, inLen] + - outLen > 0 +**/ +template SliceFixed(inLen, outLen) { + assert(outLen > 0); + + signal input in[inLen]; + signal input index; + signal output out[outLen]; + + RangeCheck(numBits(inLen), inLen - 1)(index); // index in [0, inLen - 1] + RangeCheck(numBits(inLen), inLen)(index + outLen); // index + outLen in [0, inLen] + + // eqs[index] = 1, 0 otherwise + signal eqs[inLen] <== OneBitVector(inLen)(index); + for(var i = 0; i < outLen; i++) { + // arr[i + index] = 1 (if i + index < inLen), 0 otherwise + var arr[inLen]; + for (var j = 0; j < inLen; j++) { + if (j < i) { + arr[j] = 0; + } else { + arr[j] = eqs[j - i]; + } + } + out[i] <== EscalarProduct(inLen)(arr, in); + } +} + +/** +Slice: Returns a variable-length slice of an array. +More precisely, in[index:index+length] + [0] * (outLen - length). + +Cost: Roughly (inLen + outLen + outLen * inLen) + +Range checks: + - index in [0, inLen) + - length in (0, outLen] + - index + length in [0, inLen] + - outLen > 0 +**/ +template Slice(inLen, outLen) { + assert(outLen > 0); + + signal input in[inLen]; + signal input index; + signal input length; + + RangeCheck(numBits(inLen), inLen - 1)(index); // index in [0, inLen - 1] + RangeCheck(numBits(outLen), outLen - 1)(length - 1); // length in [1, outLen] + RangeCheck(numBits(inLen), inLen)(index + length); // index + length in [0, inLen] + + signal output out[outLen]; + + // eqs[i] = 1 if i = index, 0 otherwise + signal eqs[inLen] <== OneBitVector(inLen)(index); + // lt[i] = 1 if i < length, 0 otherwise + signal lts[outLen] <== LTBitVector(outLen)(length); + + signal tmp[outLen]; + for(var i = 0; i < outLen; i++) { + var arr[inLen]; + for (var j = 0; j < inLen; j++) { + if (j < i) { + arr[j] = 0; + } else { + arr[j] = eqs[j - i]; + } + } + tmp[i] <== EscalarProduct(inLen)(arr, in); + out[i] <== tmp[i] * lts[i]; + } +} + +// Return -1 if x is not a power of 2. Else, return log2(x) +function logBase2(x) { + var res = 0; + while (x > 1) { + if (x % 2 == 1) { + return -1; + } + x = x >> 1; + res = res + 1; + } + return res; +} + +function ceil(n, q) { + if (n % q == 0) { + return n \ q; + } else { + return (n \ q) + 1; + } +} + +/** +SliceGrouped: Same functionality as Slice but more efficient! +Works by grouping the inputs and then slicing the grouped array. + +Cost: Roughly inLen / g + outLen / g + (outLen * inLen / 2 * g) where g = numsPerGroup. +TODO: Add multiplexer, ConvertBase costs. + +NOTE: Unlike Slice, this circuit might return extra characters at the end of the array. +In particular, if (index + length - 1) % g != 1 and outLen is big enough, then upto g-1 more characters +than expected might be returned. + +Range checks: + - index in [0, inLen) + - length in (0, outLen] + - index + length in [0, inLen] + - numsPerGroup is a power of 2 (checked at compile time) +**/ +template SliceGrouped(inWidth, numsPerGroup, inLen, outLen) { + assert(inLen % numsPerGroup == 0); + + signal input in[inLen]; + signal input index; + signal input length; + + RangeCheck(numBits(inLen), inLen - 1)(index); // index in [0, inLen - 1] + RangeCheck(numBits(outLen), outLen - 1)(length - 1); // length in [1, outLen] + RangeCheck(numBits(inLen), inLen)(index + length); // index + length in [0, inLen] + + var groupedInWidth = numsPerGroup * inWidth; + assert(groupedInWidth < 253); + + var groupedInLen = inLen \ numsPerGroup; + + signal inGrouped[groupedInLen]; + for (var i = 0; i < groupedInLen; i++) { + var arr[numsPerGroup]; + for (var j = 0; j < numsPerGroup; j++) { + arr[j] = in[i * numsPerGroup + j]; + } + inGrouped[i] <== Segments2NumBE(numsPerGroup, inWidth)(arr); + } + + signal startIdxByP, startIdxModP, endIdxByP, _endIdxModP; // P = numsPerGroup + + var L = logBase2(numsPerGroup); + assert(L != -1); // This requirement comes from DivideMod2Power + var maxNumBits = numBits(inLen); + (startIdxByP, startIdxModP) <== DivideMod2Power(maxNumBits, L)(index); + (endIdxByP, _endIdxModP) <== DivideMod2Power(maxNumBits, L)(index + length - 1); + + /** + Q) Given a list of elements chunked into groups of size g and length of the sublist n, + return the maximum number of groups that the sublist can span. + A) If the sublist starts at the beginning of a group, then the number of groups is ceil(n / g). + In the worst case, the sublist starts at the end of a group. In this case, the first element + of the sublist is the last element of the group. Thus, the number of groups is + ceil((n-1) / g) + 1. + **/ + var groupedOutLen = 1 + ceil(outLen - 1, numsPerGroup); // See explanation above + signal outGrouped[groupedOutLen] <== Slice(groupedInLen, groupedOutLen)( + inGrouped, startIdxByP, endIdxByP - startIdxByP + 1 + ); + + var X = numsPerGroup * groupedOutLen; + signal outFinal[X] <== ConvertBase(groupedInWidth, groupedOutLen, inWidth, X)(outGrouped); + + // Assertion only for illustrative purposes. It is always true if groupedOutLen is set as above. + assert((outLen - 1) + (numsPerGroup - 1) <= X - 1); + + signal outOptions[numsPerGroup][outLen]; + for (var i = 0; i < outLen; i++) { + for (var j = 0; j < numsPerGroup; j++) { + outOptions[j][i] <== outFinal[i + j]; + } + } + signal output out[outLen] <== Multiplexer(outLen, numsPerGroup)(outOptions, startIdxModP); + + // e.g., if numsPerGroup == 16, we could do the following (which is slightly more efficient): + // signal outOptions[outLen][16]; + // for (var i = 0; i < outLen; i++) { + // for (var j = 0; j < 16; j++) { + // outOptions[i][j] <== outFinal[i + j]; + // } + // } + // signal selector[4] <== Num2Bits(4)(startIdxModP); + // signal output out[outLen] <== MultiMux4(outLen)(outOptions, selector); +} + +/** +Checks if an ASCII-encoded substring exists in a Base64-encoded string. + +Cost: Slice is the costliest since b64StrLen is big in practice. + +Construction Parameters: + b64StrLen: The length of the Base64-encoded string + maxA: The maximum length of the ASCII-encoded substring + (must be a multiple of 3) + +Input: + b64Str[b64StrLen]: The Base64-encoded string to search in + lenB: The length of the Base64-encoded substring to check + BIndex: The index of the first character of the + Base64-encoded substring to check. For the check to + work, it should represent just the part of b64Str that + contains A. + A[maxA]: The ASCII-encoded substring to search for padded with 0s + e.g., A = ,"sub":"12345",0000 and lenA = 15 + lenA: The length of the ASCII-encoded substring + payloadIndex: The index of the first character of the payload + +Output: + The function checks if the ASCII-encoded substring exists in the + Base64-encoded string with an offset of 0, 1, or 2. + +Range checks: + 0 < lenB <= maxB (checked in Slice) + 0 <= BIndex < b64StrLen (checked in Slice) + 0 <= BIndex + lenB <= b64StrLen (checked in Slice) + maxB <= b64StrLen (checked in Slice) + 0 < lenA <= maxA (checked in LTBitVector) + payloadIndex <= BIndex (checked in RemainderMod4) +*/ +template ASCIISubstrExistsInB64(b64StrLen, maxA, groupWidth) { + assert(maxA % 3 == 0); // for simplicity + var maxB = 1 + ((maxA / 3) * 4); // max(b64Len(maxA, i)) for any i + + signal input b64Str[b64StrLen]; + signal input lenB; + signal input BIndex; + signal B[maxB] <== SliceGrouped(8, groupWidth, b64StrLen, maxB)( + b64Str, BIndex, lenB + ); + + var B_bit_len = maxB * 6; + signal B_in_bits[B_bit_len] <== MultiBase64URLToBits(maxB)(B); + + signal input A[maxA]; + signal input lenA; + + var A_bit_len = 8 * maxA; + signal A_in_bits[A_bit_len]; + for (var i = 0; i < maxA; i++) { + var X[8] = Num2BitsBE(8)(A[i]); + for (var j = 0; j < 8; j++) { + A_in_bits[i * 8 + j] <== X[j]; + } + } + + signal input payloadIndex; + var BIndexInPayload = BIndex - payloadIndex; + signal expectedOffset <== RemainderMod4(numBits(b64StrLen))(BIndexInPayload); + signal eq_0 <== IsEqual()([expectedOffset, 0]); + signal eq_1 <== IsEqual()([expectedOffset, 1]); + signal eq_2 <== IsEqual()([expectedOffset, 2]); + eq_0 + eq_1 + eq_2 === 1; // ensure offset is 0, 1, or 2 + + signal tmp[maxA] <== LTBitVector(maxA)(lenA); + + signal enabled_0[maxA]; + // A_bit_len <= B_bit_len is guaranteed by the condition on maxB + assert(A_bit_len <= B_bit_len); + for (var i = 0; i < A_bit_len; i++) { + if (i % 8 == 0) { + enabled_0[i \ 8] <== tmp[i \ 8] * eq_0; + } + MyForceEqualIfEnabled()(enabled_0[i \ 8], [A_in_bits[i], B_in_bits[i]]); + } + + signal enabled_1[maxA]; + // A_bit_len + 2 <= B_bit_len is guaranteed by the condition on maxB + assert(A_bit_len + 2 <= B_bit_len); + for (var i = 0; i < A_bit_len; i++) { + if (i % 8 == 0) { + enabled_1[i \ 8] <== tmp[i \ 8] * eq_1; + } + MyForceEqualIfEnabled()(enabled_1[i \ 8], [A_in_bits[i], B_in_bits[i + 2]]); + } + + signal enabled_2[maxA]; + // A_bit_len + 4 <= B_bit_len is guaranteed by the condition on maxB + assert(A_bit_len + 4 <= B_bit_len); + for (var i = 0; i < A_bit_len; i++) { + if (i % 8 == 0) { + enabled_2[i \ 8] <== tmp[i \ 8] * eq_2; + } + MyForceEqualIfEnabled()(enabled_2[i \ 8], [A_in_bits[i], B_in_bits[i + 4]]); + } +} \ No newline at end of file diff --git a/zklogin-circuits/circuits/zklogin.circom b/zklogin-circuits/circuits/zklogin.circom new file mode 100644 index 0000000000..8b109c4c86 --- /dev/null +++ b/zklogin-circuits/circuits/zklogin.circom @@ -0,0 +1,7 @@ +pragma circom 2.1.3; + +include "zkloginMain.circom"; + +component main { + public [all_inputs_hash] +} = ZKLogin(64*12, 66, 10); \ No newline at end of file diff --git a/zklogin-circuits/circuits/zkloginMain.circom b/zklogin-circuits/circuits/zkloginMain.circom new file mode 100644 index 0000000000..01c97927ce --- /dev/null +++ b/zklogin-circuits/circuits/zkloginMain.circom @@ -0,0 +1,246 @@ +pragma circom 2.1.3; + +include "helpers/sha256.circom"; +include "helpers/misc.circom"; +include "helpers/strings.circom"; +include "helpers/hasher.circom"; +include "helpers/jwtchecks.circom"; + +/** +ZKLogin circuit + +Constraints (rough): (maxContentLength/64)*30k + maxContentLength * (maxExtKeyClaimLength + maxNonceLength) +The first term is incurred by Sha2_wrapper and the second term is incurred by Slice. + + Construction params: + - maxContentLength: Maximum length of the JWT + SHA2 padding in bytes. Must be a multiple of 64. + - maxExtKeyClaimLength: Maximum length of the extended_key_claim (in ascii) + + Private Inputs: + - content[inCount]: X in bytes where X is the + decoded JWT header + decoded JWT payload + SHA-2 padding + zeroes + + - extended_key_claim[maxExtKeyClaimLength]: + The claim name and value for the first claim_length_ascii characters and 0s for the rest + - claim_length_ascii: Length of the extended_key_claim in ASCII, e.g., for ',"sub":12345,' it is 13 + - claim_index_b64: The index of extended_key_claim encoded into Base64 in the JWT payload + - claim_length_b64: The length of extended_key_claim in Base64 + - key_claim_name_length: Length of the key_claim_name in ASCII, e.g., for "sub" it is 3 + - subject_pin: A 128-bit PIN to keep the extended_key_claim private + + - extended_nonce[maxNonceLength]: + The nonce for the first nonce_length_ascii characters and 0s for the rest + - nonce_claim_index_b64: The index of extended_nonce encoded into Base64 in the JWT payload + - nonce_length_b64: The length of extended_nonce in Base64 + + - mask[inCount]: A binary mask over X, i.e., mask[i] = 0 or 1 + - jwt_randomness: A 128-bit random number to keep the sensitive parts of JWT hidden. + + Circuit signals revealed to the verifier along with the ZK proof: + - jwt_sha2_hash: The SHA2 hash of the JWT header + JWT payload + SHA-2 padding + - num_sha2_blocks: Number of SHA2 (64-byte) blocks the SHA2-padded JWT consumes + - key_claim_name_F: MapToField(key_claim_name) + - address_seed: Poseidon([value, PIN]). It will be used to derive + subject_addr = Blake2b(flag, iss, key_claim_name_F, address_seed) + - payload_start_index: The index of the payload in the content + - payload_len: The length of the payload + - masked_content: The content with the values of "iss" and "aud" is expected (though chosen by the prover). + Rest of it is masked. + - eph_public_key[2]: The ephemeral public key split into two 128-bit values + - max_epoch: The maximum epoch for which the eph_public_key is valid + + Public Inputs: + - all_inputs_hash: H(jwt_sha2_hash[2] || masked_content_hash || payload_start_index || payload_len + eph_public_key[2] || max_epoch || num_sha2_blocks || key_claim_name_F || address_seed) + +Notes: +- nonce = Poseidon([eph_public_key, max_epoch, jwt_randomness]) +*/ +template ZKLogin(maxContentLength, maxExtKeyClaimLength, maxKeyClaimNameLen) { + var inWidth = 8; // input is in bytes + var inCount = maxContentLength; + signal input content[inCount]; + + /** + 1. SHA2(content) + */ + signal input num_sha2_blocks; + var hashCount = 2; + var hashWidth = 256 / hashCount; + signal jwt_sha2_hash[hashCount] <== Sha2_wrapper(inWidth, inCount, hashWidth, hashCount)( + content, + num_sha2_blocks + ); + + /** + 2. Checks on extended_key_claim + a) Is it in the JWT payload? + b) Is extended_key_claim[i] == 0 for all i >= claim_length_ascii? + c) Is the prefix and suffix of extended_key_claim well-formed? + + Note that the OpenID standard permits extended_key_claim to be any valid JSON member. + But the below logic is more restrictive: it assumes that the exact same string is + returned by the server every time the user logs in. + */ + var subInWidth = 8; + signal input extended_key_claim[maxExtKeyClaimLength]; + signal input claim_length_ascii; // Check if we ensure it is >= 1 and <= maxExtKeyClaimLength + + signal input claim_index_b64; + signal input claim_length_b64; + + signal input payload_start_index; + + var numsPerGroup = 16; // since inWidth is 8, this is the maximum we can set + ASCIISubstrExistsInB64( + inCount, + maxExtKeyClaimLength, + numsPerGroup + )( + b64Str <== content, + BIndex <== claim_index_b64, + lenB <== claim_length_b64, + A <== extended_key_claim, + lenA <== claim_length_ascii, + payloadIndex <== payload_start_index + ); + + signal input key_claim_name_length; + signal input key_claim_colon_index; + signal input key_claim_value_start; + signal input key_claim_value_length; + + + var maxKeyClaimNameLenWithQuotes = maxKeyClaimNameLen + 2; + signal key_claim_name_with_quotes[maxKeyClaimNameLen + 2]; + + var maxKeyClaimValueLen = maxExtKeyClaimLength - maxKeyClaimNameLen - 6; + signal key_claim_value_with_quotes[maxKeyClaimValueLen + 2]; + + (key_claim_name_with_quotes, key_claim_value_with_quotes) <== ExtendedClaimParser( + maxExtKeyClaimLength, maxKeyClaimNameLen + 2, maxKeyClaimValueLen + 2 + )( + extended_claim <== extended_key_claim, + length <== claim_length_ascii, + name_len <== key_claim_name_length, + colon_index <== key_claim_colon_index, + value_start <== key_claim_value_start, + value_len <== key_claim_value_length + ); + + // MapToFields for later use + signal key_claim_name[maxKeyClaimNameLen] <== Slice(maxKeyClaimNameLen + 2, maxKeyClaimNameLen)( + key_claim_name_with_quotes, + 1, + key_claim_name_length - 2 + ); + signal key_claim_name_F <== MapToField(maxKeyClaimNameLen)(key_claim_name); + + signal key_claim_value[maxKeyClaimValueLen] <== Slice(maxKeyClaimValueLen + 2, maxKeyClaimValueLen)( + key_claim_value_with_quotes, + 1, + key_claim_value_length - 2 + ); + signal key_claim_value_F <== MapToField(maxKeyClaimValueLen)(key_claim_value); + + /** + 3. Derive address from extended_key_claim. We exclude its last character because it can be either ',' or '}'. + **/ + signal input subject_pin; + signal address_seed <== Hasher(2)([ + key_claim_value_F, subject_pin + ]); + + /** + 4. Masking + **/ + signal input mask[inCount]; + signal masked_content[inCount]; + + for(var i = 0; i < inCount; i++) { + // Ensure mask is binary + mask[i] * (1 - mask[i]) === 0; + // If mask is 0, then replace with '=' (ASCII 61) to avoid conflicts with base64 characters + masked_content[i] <== content[i] * mask[i] + (1 - mask[i]) * 61; + } + signal masked_content_hash <== MapToField(inCount)(masked_content); + + /** + 5. Checks on extended_nonce + a) Is it in the JWT payload? + b) Calculate nonce from public key, epoch, and randomness + c) Check that nonce appears in extended_nonce + **/ + var extNonceLength = 43 + 11; // 43 for Base64 encoding of 256 bits + 11 for prefix and suffix + + // 5a) Is it in the JWT payload? + signal input extended_nonce[extNonceLength]; + + signal input nonce_claim_index_b64; + signal input nonce_length_b64; + + ASCIISubstrExistsInB64( + inCount, + extNonceLength, + numsPerGroup + )( + b64Str <== content, + BIndex <== nonce_claim_index_b64, + lenB <== nonce_length_b64, + A <== extended_nonce, + lenA <== extNonceLength, + payloadIndex <== payload_start_index + ); + + // 5b) Calculate nonce + signal input eph_public_key[2]; + signal input max_epoch; + signal input jwt_randomness; + component size_checker_2 = Num2Bits(128); + size_checker_2.in <== jwt_randomness; // ensure it is 16 bytes + + signal nonce <== Hasher(4)([ + eph_public_key[0], + eph_public_key[1], + max_epoch, + jwt_randomness + ]); + + // 5c) Check that nonce appears in extended_nonce + // NonceChecker(extNonceLength, 256)( + // expected_nonce <== nonce, + // actual_extended_nonce <== extended_nonce + // ); + + /** + 6. Misc checks: + - Ensure mask[i] == 1 for all i >= payload_start_index + payload_len + **/ + signal input payload_len; + signal payload_len_actual <== payload_start_index + payload_len; + + // set pllt[i] = 1 if i >= payload_len_actual, 0 otherwise + signal plgt[inCount] <== GTBitVector(inCount)(payload_len_actual); + for (var i = 0; i < inCount; i++) { + plgt[i] * (1 - mask[i]) === 0; // if pllt[i] == 1, then mask[i] == 1 + } + + /** + 7. Hash all signals revealed to the verifier outside + **/ + signal input all_inputs_hash; + signal all_inputs_hash_actual <== Hasher(11)([ + jwt_sha2_hash[0], + jwt_sha2_hash[1], + masked_content_hash, + payload_start_index, + payload_len, + eph_public_key[0], + eph_public_key[1], + max_epoch, + num_sha2_blocks, + key_claim_name_F, + address_seed + ]); + all_inputs_hash === all_inputs_hash_actual; +} \ No newline at end of file diff --git a/zklogin-circuits/create-backend.md b/zklogin-circuits/create-backend.md new file mode 100644 index 0000000000..1f478d6623 --- /dev/null +++ b/zklogin-circuits/create-backend.md @@ -0,0 +1,7 @@ +# Steps to create a back-end service that returns zkLogin proofs + +1. Follow the instructions given at https://github.com/mskd12/rapidsnark#compile-prover-in-server-mode and https://github.com/mskd12/rapidsnark#launch-prover-in-server-mode. You'd also need to copy the zklogin.dat file along with the binary to the `build` folder. + +2. Currently, the code above assumes that fastcrypto is cloned at `~`. (See https://github.com/mskd12/rapidsnark/blob/main/src/fullprover.cpp#L120) + +3. Set LD_LIBRARY_PATH appropriately before running the `proverServer` binary. The required file is in depends. So if rapidsnark was installed at `~`, then you'd need to set `export LD_LIBRARY_PATH=/home/ubuntu/rapidsnark/depends/pistache/build/src/` and then run `export $LD_LIBRARY_PATH` diff --git a/zklogin-circuits/package-lock.json b/zklogin-circuits/package-lock.json new file mode 100644 index 0000000000..78562f83c5 --- /dev/null +++ b/zklogin-circuits/package-lock.json @@ -0,0 +1,3068 @@ +{ + "name": "openid-zkp-auth", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "openid-zkp-auth", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "bigint-buffer": "^1.1.5", + "circomlib": "^2.0.5", + "jsonwebtoken": "^9.0.0", + "jwk-to-pem": "^2.0.5", + "node-rsa": "^1.1.1", + "snarkjs": "^0.7.0", + "temp": "^0.9.4", + "yargs": "^17.7.2" + }, + "bin": { + "zkpinputs": "js/genZKPinputs.js" + }, + "devDependencies": { + "@types/jsonwebtoken": "^9.0.2", + "@types/jwk-to-pem": "^2.0.1", + "@types/node": "^20.2.5", + "@types/yargs": "^17.0.24", + "circom_tester": "^0.0.19", + "circomlibjs": "^0.1.7", + "jose": "^4.3.7", + "mocha": "^10.2.0", + "node-fetch": "^3.3.1", + "node-jose": "^2.0.0", + "poseidon-lite": "^0.2.0", + "typescript": "^5.0.4" + } + }, + "node_modules/@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "node_modules/@ethersproject/basex": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", + "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "node_modules/@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/hdnode": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", + "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", + "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ] + }, + "node_modules/@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", + "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/sha2": "^5.7.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/providers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", + "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "node_modules/@ethersproject/random": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", + "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", + "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/solidity": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", + "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" + } + }, + "node_modules/@ethersproject/units": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", + "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", + "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/json-wallets": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/wordlists": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", + "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@iden3/bigarray": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@iden3/bigarray/-/bigarray-0.0.2.tgz", + "integrity": "sha512-Xzdyxqm1bOFF6pdIsiHLLl3HkSLjbhqJHVyqaTxXt3RqXBEnmsUmEW47H7VOi/ak7TdkRpNkxjyK5Zbkm+y52g==" + }, + "node_modules/@iden3/binfileutils": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@iden3/binfileutils/-/binfileutils-0.0.11.tgz", + "integrity": "sha512-LylnJoZ0CTdgErnKY8OxohvW4K+p6UHD3sxt+3P9AmMyBQjYR4IpoqoYZZ+9aMj89cmCQ21UvdhndAx04er3NA==", + "dependencies": { + "fastfile": "0.0.20", + "ffjavascript": "^0.2.48" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jwk-to-pem": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/jwk-to-pem/-/jwk-to-pem-2.0.1.tgz", + "integrity": "sha512-QXmRPhR/LPzvXBHTPfG2BBfMTkNLUD7NyRcPft8m5xFCeANa1BZyLgT0Gw+OxdWx6i1WCpT27EqyggP4UUHMrA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.2.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", + "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "dev": true + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "dev": true + }, + "node_modules/bfj": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz", + "integrity": "sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==", + "dependencies": { + "bluebird": "^3.5.5", + "check-types": "^11.1.1", + "hoopy": "^0.1.4", + "tryer": "^1.0.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/bigint-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", + "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.3.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/blake-hash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/blake-hash/-/blake-hash-2.0.0.tgz", + "integrity": "sha512-Igj8YowDu1PRkRsxZA7NVkdFNxH5rKv5cpLxQ0CVXSIA77pVYwCPRQJ2sMew/oneUpfuYRyjG6r8SmmmnbZb1w==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/blake2b": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/blake2b/-/blake2b-2.1.4.tgz", + "integrity": "sha512-AyBuuJNI64gIvwx13qiICz6H6hpmjvYS5DGkG6jbXMOT8Z3WUJ3V1X0FlhIoT1b/5JtHE3ki+xjtMvu1nn+t9A==", + "dev": true, + "dependencies": { + "blake2b-wasm": "^2.4.0", + "nanoassert": "^2.0.0" + } + }, + "node_modules/blake2b-wasm": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/blake2b-wasm/-/blake2b-wasm-2.4.0.tgz", + "integrity": "sha512-S1kwmW2ZhZFFFOghcx73+ZajEfKBqhP82JMssxtLVMxlaPea1p9uoLiUZ5WYyHn0KddwbLc+0vh4wR0KBNoT5w==", + "dependencies": { + "b4a": "^1.0.1", + "nanoassert": "^2.0.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/check-types": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz", + "integrity": "sha512-HBiYvXvn9Z70Z88XKjz3AEKd4HJhBXsa3j7xFnITAzoS8+q6eIGi8qDB8FKPBAjtuxjI/zFpwuiCb8oDtKOYrA==" + }, + "node_modules/child_process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", + "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==", + "dev": true + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/circom_runtime": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/circom_runtime/-/circom_runtime-0.1.22.tgz", + "integrity": "sha512-V/XYZWBhbZY8SotkaGH4FbiDYAZ8a1Md++MBiKPDOuWS/NIJB+Q+XIiTC8zKMgoDaa9cd2OiTvsC9J6te7twNg==", + "dependencies": { + "ffjavascript": "0.2.57" + }, + "bin": { + "calcwit": "calcwit.js" + } + }, + "node_modules/circom_runtime/node_modules/ffjavascript": { + "version": "0.2.57", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.57.tgz", + "integrity": "sha512-V+vxZ/zPNcthrWmqfe/1YGgqdkTamJeXiED0tsk7B84g40DKlrTdx47IqZuiygqAVG6zMw4qYuvXftIJWsmfKQ==", + "dependencies": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.0", + "web-worker": "^1.2.0" + } + }, + "node_modules/circom_runtime/node_modules/wasmcurves": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz", + "integrity": "sha512-3e2rbxdujOwaod657gxgmdhZNn+i1qKdHO3Y/bK+8E7bV8ttV/fu5FO4/WLBACF375cK0QDLOP+65Na63qYuWA==", + "dependencies": { + "wasmbuilder": "0.0.16" + } + }, + "node_modules/circom_tester": { + "version": "0.0.19", + "resolved": "https://registry.npmjs.org/circom_tester/-/circom_tester-0.0.19.tgz", + "integrity": "sha512-SNHaBsGxcBH6XsVWfsRbRPA7NF8m8AMKJI9dtJJCFGUtOTT2+zsoIqAwi50z6XCnO4TtjyXq7AeXa1PLHqT0tw==", + "dev": true, + "dependencies": { + "chai": "^4.3.6", + "child_process": "^1.0.2", + "ffjavascript": "^0.2.56", + "fnv-plus": "^1.3.1", + "r1csfile": "^0.0.41", + "snarkjs": "0.5.0", + "tmp-promise": "^3.0.3", + "util": "^0.12.4" + } + }, + "node_modules/circom_tester/node_modules/circom_runtime": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/circom_runtime/-/circom_runtime-0.1.21.tgz", + "integrity": "sha512-qTkud630B/GK8y76hnOaaS1aNuF6prfV0dTrkeRsiJKnlP1ryQbP2FWLgDOPqn6aKyaPlam+Z+DTbBhkEzh8dA==", + "dev": true, + "dependencies": { + "ffjavascript": "0.2.56" + }, + "bin": { + "calcwit": "calcwit.js" + } + }, + "node_modules/circom_tester/node_modules/ffjavascript": { + "version": "0.2.56", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.56.tgz", + "integrity": "sha512-em6G5Lrj7ucIqj4TYEgyoHs/j99Urwwqa4+YxEVY2hggnpRimVj+noX5pZQTxI1pvtiekZI4rG65JBf0xraXrg==", + "dev": true, + "dependencies": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.0", + "web-worker": "^1.2.0" + } + }, + "node_modules/circom_tester/node_modules/snarkjs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/snarkjs/-/snarkjs-0.5.0.tgz", + "integrity": "sha512-KWz8mZ2Y+6wvn6GGkQo6/ZlKwETdAGohd40Lzpwp5TUZCn6N6O4Az1SuX1rw/qREGL6Im+ycb19suCFE8/xaKA==", + "dev": true, + "dependencies": { + "@iden3/binfileutils": "0.0.11", + "bfj": "^7.0.2", + "blake2b-wasm": "^2.4.0", + "circom_runtime": "0.1.21", + "ejs": "^3.1.6", + "fastfile": "0.0.20", + "ffjavascript": "0.2.56", + "js-sha3": "^0.8.0", + "logplease": "^1.2.15", + "r1csfile": "0.0.41" + }, + "bin": { + "snarkjs": "build/cli.cjs" + } + }, + "node_modules/circom_tester/node_modules/wasmcurves": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz", + "integrity": "sha512-3e2rbxdujOwaod657gxgmdhZNn+i1qKdHO3Y/bK+8E7bV8ttV/fu5FO4/WLBACF375cK0QDLOP+65Na63qYuWA==", + "dev": true, + "dependencies": { + "wasmbuilder": "0.0.16" + } + }, + "node_modules/circomlib": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/circomlib/-/circomlib-2.0.5.tgz", + "integrity": "sha512-O7NQ8OS+J4eshBuoy36z/TwQU0YHw8W3zxZcs4hVwpEll3e4hDm3mgkIPqItN8FDeLEKZFK3YeT/+k8TiLF3/A==" + }, + "node_modules/circomlibjs": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/circomlibjs/-/circomlibjs-0.1.7.tgz", + "integrity": "sha512-GRAUoAlKAsiiTa+PA725G9RmEmJJRc8tRFxw/zKktUxlQISGznT4hH4ESvW8FNTsrGg/nNd06sGP/Wlx0LUHVg==", + "dev": true, + "dependencies": { + "blake-hash": "^2.0.0", + "blake2b": "^2.1.3", + "ethers": "^5.5.1", + "ffjavascript": "^0.2.45" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ejs": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, + "node_modules/fastfile": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/fastfile/-/fastfile-0.0.20.tgz", + "integrity": "sha512-r5ZDbgImvVWCP0lA/cGNgQcZqR+aYdFx3u+CtJqUE510pBUVGMn4ulL/iRTI4tACTYsNJ736uzFxEBXesPAktA==" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/ffjavascript": { + "version": "0.2.59", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.59.tgz", + "integrity": "sha512-QssOEUv+wilz9Sg7Zaj6KWAm7QceOAEsFuEBTltUsDo1cjn11rA/LGYvzFBPbzNfxRlZxwgJ7uxpCQcdDlrNfw==", + "dependencies": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.1", + "web-worker": "^1.2.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fnv-plus": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fnv-plus/-/fnv-plus-1.3.1.tgz", + "integrity": "sha512-Gz1EvfOneuFfk4yG458dJ3TLJ7gV19q3OM/vVvvHf7eT02Hm1DleB4edsia6ahbKgAYxO9gvyQ1ioWZR+a00Yw==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jake": { + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwk-to-pem": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.5.tgz", + "integrity": "sha512-L90jwellhO8jRKYwbssU9ifaMVqajzj3fpRjDKcsDzrslU9syRbFqfkXtT4B89HYAap+xsxNcxgBSB09ig+a7A==", + "dependencies": { + "asn1.js": "^5.3.0", + "elliptic": "^6.5.4", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logplease": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/logplease/-/logplease-1.2.15.tgz", + "integrity": "sha512-jLlHnlsPSJjpwUfcNyUxXCl33AYg2cHhIf9QhGL2T4iPT0XPB+xP1LRKFPgIg1M/sg9kAJvy94w9CzBNrfnstA==" + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "dev": true + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, + "node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nanoassert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", + "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==" + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", + "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "dev": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-jose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-2.2.0.tgz", + "integrity": "sha512-XPCvJRr94SjLrSIm4pbYHKLEaOsDvJCpyFw/6V/KK/IXmyZ6SFBzAUDO9HQf4DB/nTEFcRGH87mNciOP23kFjw==", + "dev": true, + "dependencies": { + "base64url": "^3.0.1", + "buffer": "^6.0.3", + "es6-promise": "^4.2.8", + "lodash": "^4.17.21", + "long": "^5.2.0", + "node-forge": "^1.2.1", + "pako": "^2.0.4", + "process": "^0.11.10", + "uuid": "^9.0.0" + } + }, + "node_modules/node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", + "dependencies": { + "asn1": "^0.2.4" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/poseidon-lite": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/poseidon-lite/-/poseidon-lite-0.2.0.tgz", + "integrity": "sha512-vivDZnGmz8W4G/GzVA72PXkfYStjilu83rjjUfpL4PueKcC8nfX6hCPh2XhoC5FBgC6y0TA3YuUeUo5YCcNoig==", + "dev": true + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/r1csfile": { + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/r1csfile/-/r1csfile-0.0.41.tgz", + "integrity": "sha512-Q1WDF3u1vYeAwjHo4YuddkA8Aq0TulbKjmGm99+Atn13Lf5fTsMZBnBV9T741w8iSyPFG6Uh6sapQby77sREqA==", + "dev": true, + "dependencies": { + "@iden3/bigarray": "0.0.2", + "@iden3/binfileutils": "0.0.11", + "fastfile": "0.0.20", + "ffjavascript": "0.2.56" + } + }, + "node_modules/r1csfile/node_modules/ffjavascript": { + "version": "0.2.56", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.56.tgz", + "integrity": "sha512-em6G5Lrj7ucIqj4TYEgyoHs/j99Urwwqa4+YxEVY2hggnpRimVj+noX5pZQTxI1pvtiekZI4rG65JBf0xraXrg==", + "dev": true, + "dependencies": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.0", + "web-worker": "^1.2.0" + } + }, + "node_modules/r1csfile/node_modules/wasmcurves": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz", + "integrity": "sha512-3e2rbxdujOwaod657gxgmdhZNn+i1qKdHO3Y/bK+8E7bV8ttV/fu5FO4/WLBACF375cK0QDLOP+65Na63qYuWA==", + "dev": true, + "dependencies": { + "wasmbuilder": "0.0.16" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "dev": true + }, + "node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/snarkjs": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/snarkjs/-/snarkjs-0.7.0.tgz", + "integrity": "sha512-Vu5W+0Va6X1xvlCllpZ2r3/S7MafnL6IrAv09lk/F+VNDHuHEHx3xopR9Kr70p2KpbBBJ/HB9VCDZWism8WGlA==", + "dependencies": { + "@iden3/binfileutils": "0.0.11", + "bfj": "^7.0.2", + "blake2b-wasm": "^2.4.0", + "circom_runtime": "0.1.22", + "ejs": "^3.1.6", + "fastfile": "0.0.20", + "ffjavascript": "0.2.59", + "js-sha3": "^0.8.0", + "logplease": "^1.2.15", + "r1csfile": "0.0.45" + }, + "bin": { + "snarkjs": "build/cli.cjs" + } + }, + "node_modules/snarkjs/node_modules/ffjavascript": { + "version": "0.2.57", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.57.tgz", + "integrity": "sha512-V+vxZ/zPNcthrWmqfe/1YGgqdkTamJeXiED0tsk7B84g40DKlrTdx47IqZuiygqAVG6zMw4qYuvXftIJWsmfKQ==", + "dependencies": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.0", + "web-worker": "^1.2.0" + } + }, + "node_modules/snarkjs/node_modules/r1csfile": { + "version": "0.0.45", + "resolved": "https://registry.npmjs.org/r1csfile/-/r1csfile-0.0.45.tgz", + "integrity": "sha512-YKIp4D441aZ6OoI9y+bfAyb2j4Cl+OFq/iiX6pPWDrL4ZO968h0dq0w07i65edvrTt7/G43mTnl0qEuLXyp/Yw==", + "dependencies": { + "@iden3/bigarray": "0.0.2", + "@iden3/binfileutils": "0.0.11", + "fastfile": "0.0.20", + "ffjavascript": "0.2.57" + } + }, + "node_modules/snarkjs/node_modules/wasmcurves": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz", + "integrity": "sha512-3e2rbxdujOwaod657gxgmdhZNn+i1qKdHO3Y/bK+8E7bV8ttV/fu5FO4/WLBACF375cK0QDLOP+65Na63qYuWA==", + "dependencies": { + "wasmbuilder": "0.0.16" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/tmp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", + "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/wasmbuilder": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", + "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==" + }, + "node_modules/wasmcurves": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.1.tgz", + "integrity": "sha512-9ciO7bUE5bgpbOcdK7IO3enrSVIKHwrQmPibok4GLJWaCA7Wyqc9PRYnu5HbiFv9NDFNqVKPtU5R6Is5KujBLg==", + "dependencies": { + "wasmbuilder": "0.0.16" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/web-worker": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", + "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==" + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/zklogin-circuits/package.json b/zklogin-circuits/package.json new file mode 100644 index 0000000000..df463a03be --- /dev/null +++ b/zklogin-circuits/package.json @@ -0,0 +1,38 @@ +{ + "name": "openid-zkp-auth", + "version": "1.0.0", + "description": "Verify OIDC JWT with SNARKs", + "scripts": { + "test": "tsc; mocha --max-old-space-size=16000 -t 10000s", + "prove": "node js/prove.js" + }, + "bin": { + "zkpinputs": "js/genZKPinputs.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "bigint-buffer": "^1.1.5", + "circomlib": "^2.0.5", + "jsonwebtoken": "^9.0.0", + "jwk-to-pem": "^2.0.5", + "node-rsa": "^1.1.1", + "snarkjs": "^0.7.0", + "temp": "^0.9.4", + "yargs": "^17.7.2" + }, + "devDependencies": { + "@types/jsonwebtoken": "^9.0.2", + "@types/jwk-to-pem": "^2.0.1", + "@types/node": "^20.2.5", + "@types/yargs": "^17.0.24", + "circom_tester": "^0.0.19", + "circomlibjs": "^0.1.7", + "jose": "^4.3.7", + "mocha": "^10.2.0", + "node-fetch": "^3.3.1", + "node-jose": "^2.0.0", + "poseidon-lite": "^0.2.0", + "typescript": "^5.0.4" + } +} diff --git a/zklogin-circuits/src/circuitutils.ts b/zklogin-circuits/src/circuitutils.ts new file mode 100644 index 0000000000..e0f2fa9cc5 --- /dev/null +++ b/zklogin-circuits/src/circuitutils.ts @@ -0,0 +1,296 @@ +import crypto from "crypto"; +import { toBigIntBE, toBufferBE } from 'bigint-buffer'; + +import * as utils from './utils'; +import * as jwtutils from './jwtutils'; + +import { PartialAuxInputs, KCCheckInputs, ZKInputs, constants, bit, NonceCheckInputs, WalletInputs, CircuitConstants } from './common'; + +const claimsToReveal = constants.claimsToReveal; +const devVars = constants.dev; +const nWidth = constants.inWidth; +const packWidth = constants.pack_width; +const poseidonHash = require('./utils').poseidonHash; + +// https://datatracker.ietf.org/doc/html/rfc4634#section-4.1 +function padMessage(bits: bit[]): bit[] { + const L = bits.length; + const K = (512 + 448 - (L % 512 + 1)) % 512; + + bits = bits.concat([1]); + if(K > 0) { + bits = bits.concat(Array(K).fill(0)); + } + bits = bits.concat(utils.buffer2BitArray(Buffer.from(L.toString(16).padStart(16, '0'), 'hex'))); + return bits; +} + +function genJwtMask(input: string, fields: string[]): bit[] { + const [header, payload] = input.split('.'); + var payloadMask = Array(payload.length).fill(0); + for(const field of fields) { + var [start, len] = jwtutils.indicesOfB64(payload, field); + for(var i = 0; i < len; i++) { + payloadMask[start + i] = 1; + } + } + + return Array(header.length + 1).fill(1).concat(payloadMask); +} + +function genSha256Inputs(input: string, nCount: number, nWidth = 8) { + var segments = utils.arrayChunk(padMessage(utils.buffer2BitArray(Buffer.from(input))), nWidth); + const num_sha2_blocks = (segments.length * nWidth) / 512; + + if ((segments.length * nWidth) % 512 != 0) { + throw new Error("Padding error: Padded message length is not a multiple of 512"); + } + + if(segments.length < nCount) { + segments = segments.concat(Array(nCount-segments.length).fill(Array(nWidth).fill(0))); + } + + if(segments.length > nCount) { + throw new Error(`Padded message (${segments.length}) exceeds maximum length supported by circuit (${nCount})`); + } + + return { + ["content"]: segments.map(bits => toBigIntBE(utils.bitArray2Buffer(bits))), + "num_sha2_blocks": num_sha2_blocks + }; +} + +async function computeNonce( + ephemeral_public_key = devVars.ephPK, + max_epoch = devVars.maxEpoch, + jwt_randomness = devVars.jwtRand, +) { + const eph_public_key_0 = ephemeral_public_key / 2n**128n; + const eph_public_key_1 = ephemeral_public_key % 2n**128n; + + const buildPoseidon = require("circomlibjs").buildPoseidon; + const poseidon = await buildPoseidon(); + const bignum = poseidonHash([ + eph_public_key_0, + eph_public_key_1, + max_epoch, + jwt_randomness + ], poseidon); + + const Z = toBufferBE(bignum, 32); // padded to 32 bytes + const nonce = Z.toString('base64url'); + + if (nonce.length != constants.nonceLen) { + throw new Error(`Length of nonce ${nonce} (${nonce.length}) is not equal to ${constants.nonceLen}`); + } + + return nonce; +} + +async function genNonceCheckInputs( + payload: string, payloadIndex: number, + ephemeral_public_key = devVars.ephPK, + max_epoch = devVars.maxEpoch, + jwt_randomness = devVars.jwtRand, +): Promise<NonceCheckInputs> { + const decoded_payload = Buffer.from(payload, 'base64url').toString(); + const extended_nonce = jwtutils.getExtendedClaim(decoded_payload, "nonce"); + const [start, len] = jwtutils.indicesOfB64(payload, 'nonce'); + + if (extended_nonce.length != constants.extNonceLen) { + throw new Error(`Length of nonce claim ${extended_nonce} (${extended_nonce.length}) is not equal to ${constants.extNonceLen} characters`); + } + + const eph_public_key: [bigint, bigint] = [ephemeral_public_key / 2n**128n, ephemeral_public_key % 2n**128n]; + + return { + "extended_nonce": extended_nonce.split('').map(c => c.charCodeAt(0)), + "nonce_claim_index_b64": start + payloadIndex, + "nonce_length_b64": len, + "eph_public_key": eph_public_key, + "max_epoch": max_epoch, + "jwt_randomness": jwt_randomness + }; +} + +async function genKeyClaimCheckInputs( + payload: string, maxExtClaimLen: number, + payloadIndex: number, userPIN: bigint, + keyClaimName = "sub" +): Promise<KCCheckInputs> { + const decoded_payload = Buffer.from(payload, 'base64url').toString(); + const parse_inputs = genExtClaimParserInputs(decoded_payload, keyClaimName, maxExtClaimLen); + + const [start, len] = jwtutils.indicesOfB64(payload, keyClaimName); + + return { + "extended_key_claim": parse_inputs.extended_claim, + "claim_length_ascii": parse_inputs.length, + "claim_index_b64": start + payloadIndex, + "claim_length_b64": len, + "subject_pin": userPIN, + "key_claim_name_length": parse_inputs.name_len, + "key_claim_colon_index": parse_inputs.colon_index, + "key_claim_value_start": parse_inputs.value_start, + "key_claim_value_length": parse_inputs.value_len + }; +} + +function genExtClaimParserInputs( + payload: string, + claim_name: string, + max_extended_claim_length: number +) { + const output = jwtutils.parseClaim(payload, claim_name); + const extended_claim = output.extended_claim; + + return { + "extended_claim": utils.strToVec(extended_claim, max_extended_claim_length), + "length": extended_claim.length, + "name_len": claim_name.length + 2, // +2 for the quotes + "colon_index": output.colon_index, + "value_start": output.value_index, + "value_len": output.raw_value.length + } +} + +async function sanityChecks( + payload: string, + I: WalletInputs +) { + const nonce = await computeNonce(I.eph_public_key, I.max_epoch, I.jwt_rand); + + const decoded_payload = Buffer.from(payload, 'base64url').toString(); + const json_nonce = JSON.parse(decoded_payload).nonce; + if (json_nonce !== nonce) { + throw new Error(`Nonce in the JSON ${json_nonce} does not match computed nonce ${nonce}`); + } +} + +/** + * + * @param I The wallet inputs + * @param P The circuit constants + * @param dev a boolean indicating whether to perform sanity checks (default: true) + * @returns + */ +async function genZKLoginInputs( + I: WalletInputs, + P: CircuitConstants, + dev = true +): Promise<[ZKInputs, PartialAuxInputs]> { + var zk_inputs = new ZKInputs(); + var aux_inputs = new PartialAuxInputs(); + + // set SHA-2 inputs + const unsigned_jwt = I.unsigned_jwt; + let sha256inputs = genSha256Inputs( + unsigned_jwt, + P.max_padded_unsigned_jwt_len, + nWidth + ); + zk_inputs.content = sha256inputs.content; + zk_inputs.num_sha2_blocks = sha256inputs.num_sha2_blocks; + + // set indices + zk_inputs.payload_start_index = unsigned_jwt.split('.')[0].length + 1; // 4x+1, 4x, 4x-1 + const payload = unsigned_jwt.split('.')[1]; + zk_inputs.payload_len = payload.length; + + // set the key claim inputs + const key_claim_inputs = await genKeyClaimCheckInputs( + payload, + P.max_extended_key_claim_len, + zk_inputs.payload_start_index, + I.user_pin, + I.key_claim_name + ); + zk_inputs.extended_key_claim = key_claim_inputs.extended_key_claim; + zk_inputs.claim_length_ascii = key_claim_inputs.claim_length_ascii; + zk_inputs.claim_index_b64 = key_claim_inputs.claim_index_b64; + zk_inputs.claim_length_b64 = key_claim_inputs.claim_length_b64; + zk_inputs.subject_pin = key_claim_inputs.subject_pin; + zk_inputs.key_claim_name_length = key_claim_inputs.key_claim_name_length; + zk_inputs.key_claim_colon_index = key_claim_inputs.key_claim_colon_index; + zk_inputs.key_claim_value_start = key_claim_inputs.key_claim_value_start; + zk_inputs.key_claim_value_length = key_claim_inputs.key_claim_value_length; + + // set hash + const hash = BigInt("0x" + crypto.createHash("sha256").update(unsigned_jwt).digest("hex")); + aux_inputs.jwt_sha2_hash = [hash / 2n**128n, hash % 2n**128n]; + + // masking + zk_inputs.mask = genJwtMask(unsigned_jwt, claimsToReveal).concat(Array(P.max_padded_unsigned_jwt_len - unsigned_jwt.length).fill(1)); + aux_inputs.masked_content = utils.applyMask(zk_inputs.content.map(Number), zk_inputs.mask); + + // set nonce-related inputs + const nonce_inputs = await genNonceCheckInputs( + payload, zk_inputs.payload_start_index, + I.eph_public_key, I.max_epoch, I.jwt_rand + ); + zk_inputs.extended_nonce = nonce_inputs.extended_nonce; + zk_inputs.nonce_claim_index_b64 = nonce_inputs.nonce_claim_index_b64; + zk_inputs.nonce_length_b64 = nonce_inputs.nonce_length_b64; + zk_inputs.eph_public_key = nonce_inputs.eph_public_key; + zk_inputs.max_epoch = nonce_inputs.max_epoch; + zk_inputs.jwt_randomness = nonce_inputs.jwt_randomness; + + // derive address seed + const decoded_payload = Buffer.from(payload, 'base64url').toString(); + const key_claim_value = jwtutils.getClaimValue(decoded_payload, I.key_claim_name); + aux_inputs.addr_seed = await utils.deriveAddrSeed( + key_claim_value, I.user_pin, P.max_key_claim_value_len + ); + console.log(`Seed ${aux_inputs.addr_seed} derived from ID ${key_claim_value} and PIN ${I.user_pin}`); + + aux_inputs.payload_start_index = zk_inputs.payload_start_index; + aux_inputs.payload_len = zk_inputs.payload_len; + aux_inputs.eph_public_key = zk_inputs.eph_public_key; + aux_inputs.max_epoch = zk_inputs.max_epoch; + aux_inputs.num_sha2_blocks = zk_inputs.num_sha2_blocks; + aux_inputs.key_claim_name = I.key_claim_name; + + zk_inputs.all_inputs_hash = await calcAllInputsHash(aux_inputs, P.max_key_claim_name_len); + + if (dev) { + sanityChecks(payload, I); + } + + return [zk_inputs, aux_inputs]; +} + +async function calcAllInputsHash( + aux_inputs: PartialAuxInputs, + maxKeyClaimNameLen: number, +): Promise<bigint> { + const buildPoseidon = require("circomlibjs").buildPoseidon; + const poseidon = await buildPoseidon(); + + const packed = utils.pack(aux_inputs.masked_content.map(BigInt), 8, packWidth); // TODO: BigInt -> Number -> BigInt + const masked_content_hash = poseidonHash(packed, poseidon); + + const key_claim_name_F = await utils.mapToField(aux_inputs.key_claim_name, maxKeyClaimNameLen); + + return poseidonHash([ + aux_inputs.jwt_sha2_hash[0], + aux_inputs.jwt_sha2_hash[1], + masked_content_hash, + aux_inputs.payload_start_index, + aux_inputs.payload_len, + aux_inputs.eph_public_key[0], + aux_inputs.eph_public_key[1], + aux_inputs.max_epoch, + aux_inputs.num_sha2_blocks, + key_claim_name_F, + aux_inputs.addr_seed + ], poseidon); +} + +export { + padMessage, + genJwtMask, + genSha256Inputs, + genZKLoginInputs, + genExtClaimParserInputs, + computeNonce +} diff --git a/zklogin-circuits/src/common.ts b/zklogin-circuits/src/common.ts new file mode 100644 index 0000000000..bc88f552ca --- /dev/null +++ b/zklogin-circuits/src/common.ts @@ -0,0 +1,114 @@ +export type bit = 0 | 1; + +export interface WalletInputs { + unsigned_jwt: string, + eph_public_key: bigint, + max_epoch: number, + jwt_rand: bigint, + user_pin: bigint, + key_claim_name: string +} + +export interface KCCheckInputs { + "extended_key_claim": number[]; + "claim_length_ascii": number; + "claim_index_b64": number; + "claim_length_b64": number; + "subject_pin": bigint; + "key_claim_name_length": number; + "key_claim_colon_index": number; + "key_claim_value_start": number; + "key_claim_value_length": number; +} + +export interface NonceCheckInputs { + "extended_nonce": number[]; + "nonce_claim_index_b64": number; + "nonce_length_b64": number; + "eph_public_key": [bigint, bigint]; + "max_epoch": number; + "jwt_randomness": bigint; +} + +export class ZKInputs implements KCCheckInputs, NonceCheckInputs { + "content": bigint[]; + "num_sha2_blocks": number; + + "payload_start_index": number; + "payload_len": number; + + "mask": bit[]; + + // KCCheckInputs + "extended_key_claim": number[]; + "claim_length_ascii": number; + "claim_index_b64": number; + "claim_length_b64": number; + "subject_pin": bigint; + "key_claim_name_length": number; + "key_claim_colon_index": number; + "key_claim_value_start": number; + "key_claim_value_length": number; + + // NonceCheckInputs + "extended_nonce": number[]; + "nonce_claim_index_b64": number; + "nonce_length_b64": number; + "eph_public_key": [bigint, bigint]; + "max_epoch": number; + "jwt_randomness": bigint; + + "all_inputs_hash": bigint; +} + +// AuxInputs minus JWT signature +export class PartialAuxInputs { + "masked_content": number[]; + "jwt_sha2_hash": [bigint, bigint]; + "payload_start_index": number; + "payload_len": number; + "eph_public_key": [bigint, bigint]; + "max_epoch": number; + "num_sha2_blocks": number; + "key_claim_name": string; + "addr_seed": bigint; +} + +// ZKLoginSig minus the tx signature +export class PartialZKLoginSig { + "zkproof": any; + "public_inputs": any; + "auxiliary_inputs": PartialAuxInputs; +} + +export interface CircuitConstants { + max_padded_unsigned_jwt_len: number, + max_extended_key_claim_len: number, + max_key_claim_name_len: number, + max_key_claim_value_len: number, +} + +export const circuit_params: CircuitConstants = { + max_padded_unsigned_jwt_len: 64*12, + max_extended_key_claim_len: 66, // name + value + 6 chars (four '"', one ':' and one ',' / '}') + max_key_claim_name_len: 10, + max_key_claim_value_len: 50, +}; + +export const constants = { + P: 21888242871839275222246405745257275088548364400416034343698204186575808495617n, + flag: 5, + inWidth: 8, + // const eph_public_key = BigInt("0x" + crypto.randomBytes(32).toString('hex')); + dev: { // NOTE: Constants meant to be used for dev + pin: 283089722053851751073973683904920435104n, + ephPK: 0x0d7dab358c8dadaa4efa0049a75b07436555b10a368219bb680f70571349d775n, + maxEpoch: 10000, + jwtRand: 100681567828351849884072155819400689117n + }, + pack_width: 248, + maskValue: '='.charCodeAt(0), + nonceLen: Math.ceil(256 / 6), // 43 + extNonceLen: Math.ceil(256 / 6) + 11, // 11 for prefix and suffix + claimsToReveal: ["iss", "aud"] +} diff --git a/zklogin-circuits/src/decideparams.js b/zklogin-circuits/src/decideparams.js new file mode 100644 index 0000000000..e6503775a6 --- /dev/null +++ b/zklogin-circuits/src/decideparams.js @@ -0,0 +1,64 @@ +const jwtutils = require("./jwtutils"); + +const GOOGLE = require("../testvectors/realJWTs").google; +const TWITCH = require("../testvectors/realJWTs").twitch; +const BUFFER1 = 0.15; // 15 percent +const BUFFER2 = 0.15; // 15 percent + +// JWT header + JWT payload + SHA2 padding +function computeNumSHA2Blocks(jwt) { + const header = jwt.split('.')[0]; + const payload = jwt.split('.')[1]; + + const jwtMaxLen = header.length + payload.length; + + const L = jwtMaxLen * 8; + const K = (512 + 448 - (L % 512 + 1)) % 512; + + const paddingLen = 1 + K + 64; + + if ((L + paddingLen) % 512 !== 0) { + throw new Error("Shouldn't happen... Invalid implementation"); + } + + return (L + paddingLen) / 512; +} + +function computeSubLen(jwt) { + const payload = Buffer.from(jwt.split('.')[1], 'base64url').toString(); + return jwtutils.getExtendedClaim(payload, "sub").length; +} + +function computeAudLen(jwt) { + const payload = Buffer.from(jwt.split('.')[1], 'base64url').toString(); + return jwtutils.getExtendedClaim(payload, "aud").length; +} + +function decide(jwt, buffer1, buffer2) { + const p1 = computeNumSHA2Blocks(jwt); + // TODO: Move from floor to ceil? + const maxSHA2Blocks = Math.floor(p1 * (1 + buffer1)); + console.log(`SHA2 blocks: ${p1}, Max SHA2 blocks: ${maxSHA2Blocks}`); + + const p2 = computeSubLen(jwt); + var maxSubLen = Math.floor(p2 * (1 + buffer2)); + + // Round maxSubLen to the nearest multiple of 3 + maxSubLen = Math.ceil(maxSubLen / 3) * 3; + console.log(`Sub length: ${p2}, Max sub length: ${maxSubLen}`); + + const p3 = computeAudLen(jwt); + var maxAudLen = Math.floor(p3 * (1 + buffer2)); + + // Round maxAudLen to the nearest multiple of 3 + maxAudLen = Math.ceil(maxAudLen / 3) * 3; + console.log(`Aud length: ${p3}, Max aud length: ${maxAudLen}`); +} + +if (require.main === module) { + console.log("GOOGLE"); + decide(GOOGLE.jwt, BUFFER1, BUFFER2); + + console.log("TWITCH"); + decide(TWITCH.jwt, BUFFER1, BUFFER2); +} \ No newline at end of file diff --git a/zklogin-circuits/src/genAddrSeed.ts b/zklogin-circuits/src/genAddrSeed.ts new file mode 100644 index 0000000000..a9c2b76497 --- /dev/null +++ b/zklogin-circuits/src/genAddrSeed.ts @@ -0,0 +1,27 @@ +import { deriveAddrSeed } from './utils'; +import { constants } from './common'; + +if (require.main === module) { + const claimValue = process.argv[2]; + if (!claimValue) { + console.log("Usage: ts-node genAddrSeed.ts <claim_value>"); + process.exit(1); + } + + let pin = process.argv[3]; + if (!pin) { + pin = constants.dev.pin.toString(); + console.log("Using default pin:", pin); + } + + (async () => { + try { + const res = await deriveAddrSeed(claimValue, BigInt(pin)); + console.log(res.toString()); + process.exit(0); + } + catch (err) { + console.error("Error:", (err as Error).message); + } + })(); +} diff --git a/zklogin-circuits/src/genNonce.ts b/zklogin-circuits/src/genNonce.ts new file mode 100644 index 0000000000..4a8a76a9cd --- /dev/null +++ b/zklogin-circuits/src/genNonce.ts @@ -0,0 +1,14 @@ +import * as circuitutils from './circuitutils'; + +if (require.main === module) { + (async () => { + try { + const res = await circuitutils.computeNonce(); + console.log("nonce", res); + process.exit(0); + } + catch (err) { + console.error("Error:", (err as Error).message); + } + })(); +} \ No newline at end of file diff --git a/zklogin-circuits/src/genZKPinputs.ts b/zklogin-circuits/src/genZKPinputs.ts new file mode 100644 index 0000000000..0191d35b61 --- /dev/null +++ b/zklogin-circuits/src/genZKPinputs.ts @@ -0,0 +1,92 @@ +#!/usr/bin/env node + +import fs from 'fs'; +import * as circuit from './circuitutils'; +import * as utils from './utils'; +import { WalletInputs, circuit_params} from './common'; + +function readJsonFile(filename: string): WalletInputs { + try { + if (!fs.existsSync(filename)) { + throw new Error(`File doesn't exist: ${filename}`); + } + + const rawdata = fs.readFileSync(filename); + const jsonData = JSON.parse(rawdata.toString()); + + // Validate JSON structure + const requiredFields = ['jwt', 'eph_public_key', 'max_epoch', 'jwt_rand', 'user_pin', 'key_claim_name']; + for (let field of requiredFields) { + if (!jsonData.hasOwnProperty(field)) { + throw new Error(`Missing required field: ${field}`); + } + } + + const walletinputs: WalletInputs = { + unsigned_jwt: jsonData.jwt, + eph_public_key: BigInt(jsonData.eph_public_key), + max_epoch: Number(jsonData.max_epoch), + jwt_rand: BigInt(jsonData.jwt_rand), + user_pin: BigInt(jsonData.user_pin), + key_claim_name: jsonData.key_claim_name, + } + + return walletinputs; + } catch (err) { + console.error(`Error reading or parsing JSON file: ${(err as Error).message}`); + process.exit(1); // Exit with failure status + } +} + +async function genZKPInputs ( + walletinputs: WalletInputs, + ZKP_INPUTS_FILE_PATH: string, + AUX_INPUTS_FILE_PATH: string +) { + const [zk_inputs, auxiliary_inputs] = await circuit.genZKLoginInputs( + walletinputs, + circuit_params + ); + + console.log(`Writing inputs to ${ZKP_INPUTS_FILE_PATH}...`); + utils.writeJSONToFile(zk_inputs, ZKP_INPUTS_FILE_PATH); + + console.log(`Writing auxiliary inputs to ${AUX_INPUTS_FILE_PATH}...`); + utils.writeJSONToFile(auxiliary_inputs, AUX_INPUTS_FILE_PATH); +}; + +if (require.main === module) { + if (process.argv.length < 3) { + console.log("Usage: node genZKPinputs.js <walletinputs.json> <zkinputs.json> <auxinputs.json>"); + console.log("Last two arguments are optional. If not specified, default filenames will be used."); + process.exit(1); + } + + const data = readJsonFile(process.argv[2]); + + let zk_inputs_file = process.argv[3]; + // If no output file is specified, use the default + if (!zk_inputs_file) { + zk_inputs_file = "zkinputs.json"; + } + + let aux_inputs_file = process.argv[4]; + // If no output file is specified, use the default + if (!aux_inputs_file) { + aux_inputs_file = "auxinputs.json"; + } + + // Log the data object + console.log(data); + + (async () => { + try { + await genZKPInputs( + data, zk_inputs_file, aux_inputs_file + ); + process.exit(0); + } catch (error) { + console.error("Error in processJWT:", error); + } + })(); +} diff --git a/zklogin-circuits/src/jwtutils.ts b/zklogin-circuits/src/jwtutils.ts new file mode 100644 index 0000000000..240c3b3be6 --- /dev/null +++ b/zklogin-circuits/src/jwtutils.ts @@ -0,0 +1,319 @@ +function removeSig(jwt: string): string { + const parts = jwt.split('.'); + if (parts.length != 3) { + throw new Error("Invalid JWT: " + jwt); + } + return parts[0] + "." + parts[1]; +} + +function getClaimValue(decoded_payload: string, claim: string): any { + const json_input = JSON.parse(decoded_payload); + if (!json_input.hasOwnProperty(claim)) { + throw new Error("Field " + claim + " not found in " + decoded_payload); + } + return json_input[claim]; +} + +// A copy of getExtendedClaim but returns more things. TODO: Refactor and merge the two. +function parseClaim(decoded_payload: string, claim_name: string) { + const raw_value = JSON.stringify(getClaimValue(decoded_payload, claim_name)); + const kv_pair = `"${claim_name}":${raw_value}`; + const claimStart = decoded_payload.indexOf(`"${claim_name}"`); + const kv_pair_expanded = kv_pair + decoded_payload[claimStart + kv_pair.length]; + + if (!decoded_payload.includes(kv_pair_expanded)) { + throw new Error("Unimplemented. The string " + kv_pair_expanded + " is not found in " + decoded_payload); + } + + var lastchar = kv_pair_expanded[kv_pair_expanded.length - 1]; + if (!(lastchar == '}' || lastchar == ',')) { + throw new Error("Something is wrong with the decoded payload"); + } + return { + "raw_value": raw_value, + "extended_claim": kv_pair_expanded, + "colon_index": kv_pair_expanded.indexOf(":"), + "value_index": kv_pair_expanded.indexOf(raw_value), // index of value in the extended claim + }; +} + +/** + * Returns a claim as it appears in the decoded JWT. + * We take a conservative approach, e.g., assume that the claim value does not have spaces. + * In such cases, the code will fail. + * The last character after the claim value is also returned (either a comma or a closing brace). + * + * @param {*} decoded_payload e.g., {"sub":"1234567890","name":"John Doe","iat":1516239022} + * @param {*} claim e.g., sub + * @returns e.g., "sub":"1234567890", + */ +function getExtendedClaim(decoded_payload: string, claim: string): string { + const json_input = JSON.parse(decoded_payload); + if (!json_input.hasOwnProperty(claim)) { + throw new Error("Field " + claim + " not found in " + decoded_payload); + } + + const field_value = JSON.stringify(json_input[claim]); + const kv_pair = `"${claim}":${field_value}`; + const claimStart = decoded_payload.indexOf(`"${claim}"`); + const kv_pair_expanded = kv_pair + decoded_payload[claimStart + kv_pair.length]; + + if (decoded_payload.includes(kv_pair_expanded)) { + var lastchar = kv_pair_expanded[kv_pair_expanded.length - 1]; + if (!(lastchar == '}' || lastchar == ',')) { + throw new Error("Something is wrong with the decoded payload"); + } + return kv_pair_expanded; + } + + // Facebook is escaping the '/' characters in the JWT payload + const escaped_field_value = field_value.replace(/([/])/g, '\\$1'); + const escaped_kv_pair = `"${claim}":${escaped_field_value}`; + const escaped_kv_pair_expanded = escaped_kv_pair + + decoded_payload[claimStart + escaped_kv_pair.length]; + + if (decoded_payload.includes(escaped_kv_pair_expanded)) { + var lastchar = escaped_kv_pair_expanded[escaped_kv_pair_expanded.length - 1]; + if (!(lastchar == '}' || lastchar == ',')) { + throw new Error("Something is wrong with the decoded payload"); + } + + return escaped_kv_pair_expanded; + } + + throw new Error("Fields " + kv_pair_expanded + " or " + escaped_kv_pair_expanded + " not found in " + decoded_payload); +} + +/** + * @param {String} payload A Base64 encoded string, e.g., a JWT + * @param {String} field A claim string, e.g., "sub" (without quotes) + * @returns [start, length] The start index and length of the (base64) encoded + * claim string in the input. + * + * The returned indices are tight, i.e., both payload[start] and payload[start + length - 1] + * contain at least some bits of the claim string. + */ +function indicesOfB64(payload: string, field: string): [number, number] { + const decoded = Buffer.from(payload, 'base64url').toString(); + + const kv_pair = getExtendedClaim(decoded, field); + const start_index_ascii = decoded.indexOf(kv_pair); + const length_b64 = b64Len(kv_pair.length, start_index_ascii); + const start_b64 = b64Index(start_index_ascii); + + // test + const expectedB64Variant = payload.slice(start_b64, start_b64 + length_b64); + if (payload.indexOf(expectedB64Variant) == -1) { + throw new Error("Field " + kv_pair + " not found in the Base64"); + } + + return [start_b64, length_b64]; +} + +// If a character appears at an index i in a string S, +// return the index at which it would appear in the base64 representation of S +function b64Index(i: number): number { + var q = 4 * Math.floor(i / 3); + + if (i % 3 == 0) { + /** + * - - - - <=> . . . + * - - - - <=> . . . + * X x <=> i + */ + return q; + } else if (i % 3 == 1) { + /** + * - - - - <=> . . . + * - - - - <=> . . . + * _ X x <=> . i + */ + return q + 1; + } else if (i % 3 == 2) { + /** + * - - - - <=> . . . + * - - - - <=> . . . + * _ _ X x <=> . . i + */ + return q + 2; + } else { + throw new Error(`Something is wrong with the index ${i}`); + } +} + +// Given an ascii string of length n starting at index i, +// return the length of its base64 representation +function b64Len(n: number, i: number): number { + var q = 4 * Math.floor(n / 3); + if (i % 3 == 0) { + if (n % 3 == 0) { + /** + * a - - => 4 + * - - b => 4 + */ + return q; + } else if (n % 3 == 1) { + /** + * a - - => 4 + * - - - => 4 + * b => 2 + */ + return q + 2; + } else { + /** + * a - - => 4 + * - - - => 4 + * - b => 3 + */ + return q + 3; + } + } else if (i % 3 == 1) { + if (n % 3 == 0) { + /** + * a - => 3 + * - - - => 4 + * b => 2 + */ + return q + 1; + } else if (n % 3 == 1) { + /** + * a - => 3 + * - - - => 4 + * - b => 3 + */ + return q + 2; + } else { + /** + * a - => 3 + * - - - => 4 + * - - b => 4 + */ + return q + 3; + } + } else if (i % 3 == 2) { + if (n % 3 == 0) { + /** + * a => 2 + * - - - => 4 + * - b => 3 + */ + return q + 1; + } else if (n % 3 == 1) { + /** + * a => 2 + * - - - => 4 + * - - b => 4 + */ + return q + 2; + } else { + /** + * a => 2 + * - - - => 4 + * - - - => 4 + * b => 2 + */ + return q + 4; + } + } else { + throw new Error(`Something is wrong with the index ${i}`); + } +} + +function base64UrlCharTo6Bits(base64UrlChar: string): number[] { + if (base64UrlChar.length !== 1) { + throw new Error('Invalid base64Url character: ' + base64UrlChar); + } + + // Define the base64URL character set + const base64UrlCharacterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + // Find the index of the input character in the base64URL character set + const index = base64UrlCharacterSet.indexOf(base64UrlChar); + + if (index === -1) { + throw new Error('Invalid base64Url character: ' + base64UrlChar); + } + + // Convert the index to a 6-bit binary string + const binaryString = index.toString(2).padStart(6, '0'); + + // Convert the binary string to an array of bits + const bits = Array.from(binaryString).map(Number); + + return bits; +} + +function base64UrlStringToBitVector(base64UrlString: string) { + let bitVector: number[] = []; + for (let i = 0; i < base64UrlString.length; i++) { + const base64UrlChar = base64UrlString.charAt(i); + const bits = base64UrlCharTo6Bits(base64UrlChar); + bitVector = bitVector.concat(bits); + } + return bitVector; +} + +/** + * Decode a Base64URL substring `s` that appears at index `i` of a valid Base64URL string. + * + * @param {string} s - a Base64URL substring + * @param {number} i - the index at which `s` appears in the Base64URL string + * @returns {string} the decoded string + * + * Like before, we assume tight packing, i.e., both s[i] and s[i + s.length - 1] carry + * non-zero bits of the encoded string. + */ +function decodeBase64URL(s: string, i: number): string { + if (s.length < 2) { + throw new Error(`Input (s = ${s}) is not tightly packed because s.length < 2`); + } + var bits = base64UrlStringToBitVector(s); + + const first_char_offset = i % 4; + if (first_char_offset == 0) { + // skip + } else if (first_char_offset == 1) { + bits = bits.slice(2); + } else if (first_char_offset == 2) { + bits = bits.slice(4); + } else { // (offset == 3) + throw new Error(`Input (s = ${s}) is not tightly packed because i%4 = 3 (i = ${i}))`); + } + + const last_char_offset = (i + s.length - 1) % 4; + if (last_char_offset == 3) { + // skip + } else if (last_char_offset == 2) { + bits = bits.slice(0, bits.length - 2); + } else if (last_char_offset == 1) { + bits = bits.slice(0, bits.length - 4); + } else { // (offset == 0) + throw new Error(`Input (s = ${s}) is not tightly packed because (i + s.length - 1)%4 = 0 (i = ${i}))`); + } + + if (bits.length % 8 != 0) { + throw new Error(`We should never reach here...`); + } + + var bytes = []; + for (let i = 0; i < bits.length; i += 8) { + const bitChunk = bits.slice(i, i + 8); + + // Convert the 8-bit chunk to a byte and add it to the bytes array + const byte = parseInt(bitChunk.join(''), 2); + bytes.push(byte); + } + + return Buffer.from(bytes).toString(); +} + +export { + removeSig, + getClaimValue, + parseClaim, + getExtendedClaim, + indicesOfB64, + b64Len, + decodeBase64URL, + base64UrlCharTo6Bits +} \ No newline at end of file diff --git a/zklogin-circuits/src/prove.ts b/zklogin-circuits/src/prove.ts new file mode 100644 index 0000000000..ca297e4e35 --- /dev/null +++ b/zklogin-circuits/src/prove.ts @@ -0,0 +1,213 @@ +const snarkjs = require("snarkjs"); + +import fs from 'fs'; +import * as circuit from './circuitutils'; +import { constants, circuit_params as P, PartialZKLoginSig, WalletInputs } from './common'; +import * as utils from './utils'; +import * as verifier from './verify'; +import { GOOGLE, TWITCH } from "../testvectors/realJWTs"; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import { JWK } from 'jwk-to-pem'; + +const ARTIFACTS_DIR = "./artifacts"; +const PROJ_NAME = "zklogin"; +const PROOF_DIR = ARTIFACTS_DIR + "/proof"; + +const groth16Prove = async (inputs: any, wasm_file: string, zkey_file: string) => { + const { proof, publicSignals } = await snarkjs.groth16.fullProve( + inputs, + wasm_file, + zkey_file + ); + + return { proof, publicSignals }; +} + +const groth16Verify = async (proof: any, public_inputs: any, vkey_file: string) => { + const vkey = JSON.parse(fs.readFileSync(vkey_file, 'utf-8')); + + const res = await snarkjs.groth16.verify(vkey, public_inputs, proof); + + if (res === true) { + console.log("Verification OK"); + } else { + throw new Error("Invalid Proof"); + } +} + +// Generate a ZKP for a JWT. If a JWK is provided, the JWT is verified first (sanity check). +async function zkOpenIDProve( + jwt: string, + ephPK: bigint, + maxEpoch: number, + jwtRand: bigint, + userPIN: bigint, + keyClaim='sub', + jwk?: JWK, + write_to_file=false +): Promise<PartialZKLoginSig> { + // Check if the JWT is a valid OpenID Connect ID Token if a JWK is provided + if (typeof jwk !== 'undefined') { + console.log("Verifying JWT with JWK..."); + verifier.verifyJwt(jwt, jwk); + } + + console.time('prove'); + // Split the JWT into its three parts + const [header, payload, signature] = jwt.split('.'); + const input = header + '.' + payload; + + const I : WalletInputs = { + unsigned_jwt: input, + eph_public_key: ephPK, + max_epoch: maxEpoch, + jwt_rand: jwtRand, + user_pin: userPIN, + key_claim_name: keyClaim + }; + + var [inputs, auxiliary_inputs] = await circuit.genZKLoginInputs(I, P); + + // Generate ZKP + console.log("Generating ZKP..."); + const WASM_FILE_PATH = `${ARTIFACTS_DIR}/${PROJ_NAME}_js/${PROJ_NAME}.wasm`; + const ZKEY_FILE_PATH = `${ARTIFACTS_DIR}/${PROJ_NAME}.zkey`; + const { proof, publicSignals: public_signals } = await groth16Prove(inputs, WASM_FILE_PATH, ZKEY_FILE_PATH); + + console.timeEnd('prove'); + if (write_to_file) { + const PROOF_FILE_PATH = `${PROOF_DIR}/zkp.json`; + const AUX_INPUTS_FILE_PATH = `${PROOF_DIR}/aux.json`; + const PUBLIC_INPUTS_FILE_PATH = `${PROOF_DIR}/public.json`; + + console.log("Writing proof..."); + utils.writeJSONToFile(proof, PROOF_FILE_PATH); + utils.writeJSONToFile(public_signals, PUBLIC_INPUTS_FILE_PATH); + utils.writeJSONToFile(auxiliary_inputs, AUX_INPUTS_FILE_PATH); + } + + return { + "zkproof": proof, + "public_inputs": public_signals, + "auxiliary_inputs": auxiliary_inputs + } +}; + +// Not a full implementation: only implements some of the checks. +// For a full implementation, see the Authenticator code in Rust. +const zkOpenIDVerify = async (proof: PartialZKLoginSig) => { + const { zkproof, public_inputs, auxiliary_inputs: auxiliary_inputs } = proof; + + // Verify ZKP + console.log("Verifying ZKP..."); + console.time('zk verify'); + const VKEY_FILE_PATH = `${ARTIFACTS_DIR}/${PROJ_NAME}.vkey`; + await groth16Verify(zkproof, public_inputs, VKEY_FILE_PATH); + console.timeEnd('zk verify'); + + verifier.verifyAuxInputs(auxiliary_inputs, P.max_padded_unsigned_jwt_len); +} + +type CliArgs = { + provider: string; + jwt: string; + eph_public_key: string; + max_epoch: string; + jwt_rand: string; + user_pin: string; + key_claim_name: string; + public_key_path: string; +}; + +if (require.main === module) { + const argv = yargs(hideBin(process.argv)) + .option('provider', { + alias: 'p', + type: 'string', + description: 'Specify the provider', + default: 'google', + choices: ['google', 'twitch'] + }) + .option('jwt', { + alias: 'j', + type: 'string', + description: 'JWT token' + }) + .option('eph_public_key', { + alias: 'e', + type: 'string', + description: 'Ephemeral public key', + default: constants.dev.ephPK, + }) + .option('max_epoch', { + alias: 'm', + type: 'string', + description: 'Max epoch', + default: constants.dev.maxEpoch, + }) + .option('jwt_rand', { + alias: 'r', + type: 'string', + description: 'JWT rand', + default: constants.dev.jwtRand, + }) + .option('user_pin', { + alias: 'u', + type: 'string', + description: 'User PIN', + default: constants.dev.pin, + }) + .option('key_claim_name', { + alias: 'k', + type: 'string', + description: 'Key claim name', + default: 'sub', + }) + .option('public_key_path', { + alias: 'pk', + type: 'string', + description: 'Public key path', + }) + .help() + .argv as unknown as CliArgs; + + argv.jwt = argv.jwt || (argv.provider === "google" ? GOOGLE["jwt"] : TWITCH["jwt"]); + + let jwk: JWK | undefined; + if (!argv.public_key_path) { + jwk = (argv.provider === "google") ? GOOGLE.jwk : TWITCH.jwk; + } else { + fs.readFile(argv.public_key_path, "utf8", (err, jwkJson) => { + if (err) { + console.error("Error reading JWK:", err.message); + process.exit(1); + } + jwk = JSON.parse(jwkJson); + }); + } + + console.log(`Provider -> ${argv.provider}`); + console.log(`JWT -> ${argv.jwt}`); + console.log(`Ephemeral public key -> ${argv.eph_public_key}`); + console.log(`Max epoch -> ${argv.max_epoch}`); + console.log(`JWT rand -> ${argv.jwt_rand}`); + console.log(`User PIN -> ${argv.user_pin}`); + console.log(`Key claim name -> ${argv.key_claim_name}`); + console.log(`Public key path -> ${argv.public_key_path}`); + + (async () => { + try { + const proof = await zkOpenIDProve(argv.jwt, BigInt(argv.eph_public_key), Number(argv.max_epoch), + BigInt(argv.jwt_rand), BigInt(argv.user_pin), argv.key_claim_name, jwk, true); + console.log("--------------------"); + + // Verify the proof + await zkOpenIDVerify(proof); + + process.exit(0); + } catch (error) { + console.error("Error in processJWT:", error); + } + })(); +} \ No newline at end of file diff --git a/zklogin-circuits/src/utils.ts b/zklogin-circuits/src/utils.ts new file mode 100644 index 0000000000..a8f207427c --- /dev/null +++ b/zklogin-circuits/src/utils.ts @@ -0,0 +1,162 @@ +import { constants, bit, circuit_params } from './common'; +import * as fs from 'fs'; + +const pack_width = constants.pack_width; + +function getNumFieldElements(asciiSize: number, packWidth = pack_width): number { + if (packWidth % 8 !== 0) throw new Error("packWidth must be a multiple of 8"); + + const packWidthInBytes = packWidth / 8; + return Math.ceil(asciiSize / packWidthInBytes); +} + +function arrayChunk<T>(array: T[], chunk_size: number): T[][] { + return Array(Math.ceil(array.length / chunk_size)).fill(undefined).map((_, index) => index * chunk_size). + map(begin => array.slice(begin, begin + chunk_size)); +} + +function trimEndByChar(string: string, character: string) { + if (character.length !== 1) throw new Error("character must be a single character"); + + const arr = Array.from(string); + const last = arr.reverse().findIndex(char => char !== character); + return string.substring(0, string.length - last); +} + +function buffer2BitArray(b: Buffer): bit[] { + return b.reduce((bitArray: bit[], byte) => { + const binaryString = byte.toString(2).padStart(8, '0'); + const bitValues = binaryString.split('').map(bit => bit === '1' ? 1 : 0); + return bitArray.concat(bitValues); + }, []); +} + +function bitArray2Buffer(a: bit[]): Buffer { + return Buffer.from(arrayChunk(a, 8).map(byte => parseInt(byte.join(''), 2))) +} + +function bigIntArray2Bits(arr: bigint[], intSize = 16): bit[] { + return arr.reduce((bitArray: bit[], n) => { + const binaryString = n.toString(2).padStart(intSize, '0'); + const bitValues = binaryString.split('').map(bit => bit === '1' ? 1 : 0); + return bitArray.concat(bitValues); + }, []); +} + +function bigIntArray2Buffer(arr: bigint[], intSize=16): Buffer { + return bitArray2Buffer(bigIntArray2Bits(arr, intSize)); +} + +// Pack into an array of chunks each outWidth bits +function pack(inArr: bigint[], inWidth: number, outWidth: number): bigint[] { + const bits = bigIntArray2Bits(inArr, inWidth); + + const extra_bits = bits.length % outWidth == 0 ? 0 : outWidth - (bits.length % outWidth); + const bits_padded = bits.concat(Array(extra_bits).fill(0)); + if (bits_padded.length % outWidth != 0) throw new Error("Invalid logic"); + + const packed = arrayChunk(bits_padded, outWidth).map(chunk => BigInt("0b" + chunk.join(''))); + return packed; +} + +// Pack into exactly outCount chunks of outWidth bits each +function pack2(inArr: bigint[], inWidth: number, outWidth: number, outCount: number): bigint[] { + const packed = pack(inArr, inWidth, outWidth); + if (packed.length > outCount) throw new Error("packed is big enough"); + + return packed.concat(Array(outCount - packed.length).fill(0)); +} + +function padWithZeroes<T>(inArr: T[], outCount: number) { + if (inArr.length > outCount) throw new Error("inArr is big enough"); + + const extra_zeroes = outCount - inArr.length; + const arr_padded = inArr.concat(Array(extra_zeroes).fill(0)); + return arr_padded; +} + +// A helper function to convert a string into a format that can be fed into the circuit +function strToVec(str: string, maxLen: number): number[] { + const arr = Array.from(str).map(c => c.charCodeAt(0)); + return padWithZeroes(arr, maxLen); +} + +// Poseidon is marked as any because circomlibjs does not have typescript definitions +function poseidonHash(inputs: bigint[], poseidon: any) { + if (inputs.length == 1) { + return poseidon.F.toObject(poseidon([inputs])) + } else if (inputs.length <= 15) { + return poseidon.F.toObject(poseidon(inputs)) + } else if (inputs.length <= 30) { + const hash1 = poseidon(inputs.slice(0, 15)); + const hash2 = poseidon(inputs.slice(15)); + return poseidon.F.toObject(poseidon([hash1, hash2])); + } else { + throw new Error(`Yet to implement: Unable to hash a vector of length ${inputs.length}`); + } +} + +/** + * + * @param input A Base64-decoded JWT chunked into bytes + * @param mask A vector of 0s and 1s of the same length as input + * @returns + */ +function applyMask(input: number[], mask: bit[]) { + if (input.length != mask.length) { + throw new Error("Input and mask must be of the same length"); + } + return input.map((charCode, index) => (mask[index] == 1) + ? charCode + : constants.maskValue + ); +} + +async function deriveAddrSeed( + claim_value: string, pin: bigint, + maxKeyClaimValueLen = circuit_params.max_key_claim_value_len, + packWidth = pack_width +): Promise<bigint> { + const claim_val_F = await mapToField(claim_value, maxKeyClaimValueLen, packWidth); + const buildPoseidon = require("circomlibjs").buildPoseidon; + const poseidon = await buildPoseidon(); + + return poseidonHash([ + claim_val_F, pin + ], poseidon); +} + +// Map str into a field element after padding it to maxSize chars +async function mapToField(str: string, maxSize: number, packWidth=pack_width) { + if (str.length > maxSize) { + throw new Error(`String ${str} is longer than ${maxSize} chars`); + } + + const numElements = getNumFieldElements(maxSize, packWidth); + const packed = pack2(str.split('').map(c => BigInt(c.charCodeAt(0))), 8, packWidth, numElements); + + const buildPoseidon = require("circomlibjs").buildPoseidon; + const poseidon = await buildPoseidon(); + return poseidonHash(packed, poseidon); +} + +function writeJSONToFile(inputs: object, file_name = "inputs.json") { + fs.writeFileSync(file_name, JSON.stringify(inputs, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); +} + +export { + arrayChunk, + trimEndByChar, + buffer2BitArray, + bitArray2Buffer, + bigIntArray2Bits, + bigIntArray2Buffer, + applyMask, + padWithZeroes, + strToVec, + pack, + deriveAddrSeed, + poseidonHash, + writeJSONToFile, + mapToField +} diff --git a/zklogin-circuits/src/verify.ts b/zklogin-circuits/src/verify.ts new file mode 100644 index 0000000000..a14f509ea5 --- /dev/null +++ b/zklogin-circuits/src/verify.ts @@ -0,0 +1,121 @@ +import * as jwt from "jsonwebtoken"; +import jwkToPem, { JWK } from "jwk-to-pem"; +import * as jwtutils from './jwtutils'; +import { toBigIntBE } from "bigint-buffer"; +import { PartialAuxInputs } from "./common"; + +// JWT Token, JWK Public Key +const verifyJwt = (token: string, jwkPublicKey: JWK) => { + try { + // Convert the JWK to PEM format + const publicKey = jwkToPem(jwkPublicKey); + + const verifyOptions: jwt.VerifyOptions = { + algorithms: ["RS256"], + ignoreExpiration: true + }; + + const decoded = jwt.verify(token, publicKey, verifyOptions); + console.log("JWT is valid:", decoded); + } catch (error) { + console.error("Invalid JWT:", (error as Error).message); + } +}; + +// A partial implementation of the on-chain proof verification logic. Only checks the masked_content. +const verifyAuxInputs = (auxiliary_inputs: PartialAuxInputs, MAX_JWT_LENGTH: number) => { + checkMaskedContent( + auxiliary_inputs["masked_content"], + auxiliary_inputs["num_sha2_blocks"], + auxiliary_inputs["payload_start_index"], + auxiliary_inputs["payload_len"], + MAX_JWT_LENGTH + ); +} + +function checkMaskedContent ( + masked_content: number[], + num_sha2_blocks: number, + expected_payload_start_index: number, + expected_payload_len: number, + expected_length: number +) { + if (masked_content.length != expected_length) throw new Error("Invalid length"); + if (num_sha2_blocks * 64 > masked_content.length) throw new Error("Invalid last block"); + + // Process any extra padding + const extra_padding = masked_content.slice(num_sha2_blocks * 64); + console.log("Length of extra padding:", extra_padding.length); + if (extra_padding.length !== 0) { + if (extra_padding.some(e => e != 0)) throw new Error("Invalid extra padding"); + masked_content = masked_content.slice(0, num_sha2_blocks * 64); + } + + // Process header + const header_length = masked_content.indexOf('.'.charCodeAt(0)); + if (header_length == -1 || header_length != expected_payload_start_index - 1) throw new Error("Invalid header length"); + + const encodedHeader = masked_content.slice(0, header_length).map(e => String.fromCharCode(e)).join(''); + const header = Buffer.from(encodedHeader, 'base64url').toString('utf8'); + // console.log("header", header); + // ...JSON Parse header... + + // Process SHA-2 padding + const payload_and_sha2pad = masked_content.slice(header_length + 1); + const header_and_payload_len_in_bits_bigint = toBigIntBE(Buffer.from(payload_and_sha2pad.slice(-8))); + if (header_and_payload_len_in_bits_bigint > Number.MAX_SAFE_INTEGER) { // 2^53 - 1 + throw new Error("Too large header_and_payload_len_in_bits"); + } + // casting to a number should work for our use case as the numbers aren't big + const header_and_payload_len_in_bits = Number(header_and_payload_len_in_bits_bigint); + if (header_and_payload_len_in_bits % 8 != 0) throw new Error("Invalid header_and_payload_len_in_bits"); + const header_and_payload_len = header_and_payload_len_in_bits / 8; + + const payload_len = header_and_payload_len - expected_payload_start_index; + if (payload_len != expected_payload_len) throw new Error(`Invalid payload length: ${payload_len} != ${expected_payload_len}`); + + const payload = payload_and_sha2pad.slice(0, payload_len); + const sha2pad = payload_and_sha2pad.slice(payload_len); + + if (sha2pad[0] != 128) throw new Error("Invalid sha2pad start byte"); + if (sha2pad.slice(1, -8).some(e => e != 0)) throw new Error("Invalid sha2pad"); + // TODO: Check that the length of sha2pad.slice(1, -8) satisfies 4.1(b) from https://datatracker.ietf.org/doc/html/rfc4634#section-4.1 + + // Process payload + const maskedPayload = payload.map(e => String.fromCharCode(e)).join(''); + console.log("Masked payload:", maskedPayload); + const claims = extractClaims(maskedPayload); + console.log("Revealed claims:", claims); + + for (const claim of claims) { + if (claim[0] !== '"') { + // First character of each extracted_claim must be '"' (extractClaims omits partial bits at the start) + console.log("Invalid claim", claim); + throw new Error("Invalid claim"); + } + + if (!(claim.slice(-1) === '}' || claim.slice(-1) === ',')) { + // Last character of each extracted_claim must be '}' or ',' + console.log("Invalid claim", claim); + throw new Error("Invalid claim"); + } + } +} + +// Extracts the claims from the masked payload. +// 1. Extract continguous sets of non-masked characters +// 2. For each group of Base64 chars, find its starting index and prefix-pad with enough '0's before Base64 decoding. +function extractClaims(maskedPayload: string) { + return maskedPayload.split(/=+/).filter(e => e !== '').map( + e => { + const pos = maskedPayload.indexOf(e); + return jwtutils.decodeBase64URL(e, pos % 4); + } + ); +} + +export { + verifyJwt, + verifyAuxInputs, + extractClaims +}; \ No newline at end of file diff --git a/zklogin-circuits/test/base64.circom.test.js b/zklogin-circuits/test/base64.circom.test.js new file mode 100644 index 0000000000..8a8186f6d3 --- /dev/null +++ b/zklogin-circuits/test/base64.circom.test.js @@ -0,0 +1,36 @@ +const path = require("path"); +const assert = require("chai").assert; + +const testutils = require("./testutils"); +const jwtutils = require("../js/src/jwtutils"); + +describe("Base64 checks", () => { + before(async() => { + cir = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "base64.circom"), "Base64URLToBits"); + await cir.loadSymbols(); + }); + + it("Should convert all valid base64 url characters", async () => { + const input = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + for (let i = 0; i < input.length; i++) { + const witness = await cir.calculateWitness({ "in": input.charCodeAt(i) }); + const output = testutils.getWitnessArray(witness, cir.symbols, "main.out"); + assert.deepEqual(output.map(Number), jwtutils.base64UrlCharTo6Bits(input.charAt(i))); + } + }) + + it("Should fail for non-base64 characters", async () => { + const base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + const ascii = base64.split('').map(x => x.charCodeAt(0)); + + // generate all possible 8-bit values that are not valid base64 characters + for (let i = 0; i < 256; i++) { + if (!ascii.includes(i)) { + const witness = await cir.calculateWitness({ "in": i }); + const output = testutils.getWitnessArray(witness, cir.symbols, "main.out"); + assert.deepEqual(output, [ 0n, 0n, 0n, 0n, 0n, 0n ]); + } + } + }) +}) \ No newline at end of file diff --git a/zklogin-circuits/test/hasher.circom.test.js b/zklogin-circuits/test/hasher.circom.test.js new file mode 100644 index 0000000000..ba6698514b --- /dev/null +++ b/zklogin-circuits/test/hasher.circom.test.js @@ -0,0 +1,141 @@ +const chai = require("chai"); +const crypto = require("crypto"); +const path = require("path"); +const assert = chai.assert; + +const utils = require("../js/src/utils"); + +const testutils = require("./testutils"); + +const buildPoseidon = require("circomlibjs").buildPoseidon; + +const poseidonlite = require("poseidon-lite"); + +const poseidonNumToHashFN = [ + undefined, + poseidonlite.poseidon1, + poseidonlite.poseidon2, + poseidonlite.poseidon3, + poseidonlite.poseidon4, + poseidonlite.poseidon5, + poseidonlite.poseidon6, + poseidonlite.poseidon7, + poseidonlite.poseidon8, + poseidonlite.poseidon9, + poseidonlite.poseidon10, + poseidonlite.poseidon11, + poseidonlite.poseidon12, + poseidonlite.poseidon13, + poseidonlite.poseidon14, + poseidonlite.poseidon15, +]; + +function litePoseidonHash(inputs) { + const hashFN = poseidonNumToHashFN[inputs.length]; + if (hashFN) { + return hashFN(inputs); + } else if (inputs.length <= 30) { + const hash1 = litePoseidonHash(inputs.slice(0, 15)); + const hash2 = litePoseidonHash(inputs.slice(15)); + return litePoseidonHash([hash1, hash2]); + } else { + throw new Error( + `Yet to implement: Unable to hash a vector of length ${inputs.length}` + ); + } +} + +describe("Zk-friendly hashing (Poseidon) tests", () => { + const P = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617"); + const circuit_path = path.join(__dirname, "../circuits/helpers", "hasher.circom"); + + before(async () => { + poseidon = await buildPoseidon(); + }); + + it("Hashes a single value", async () => { + cir = await testutils.genMain(circuit_path, "Hasher", [1]); + await cir.loadSymbols(); + input = [1]; + const expected_hash = utils.poseidonHash(input, poseidon); + + const witness = await cir.calculateWitness({ "in": input }); + + assert.deepEqual(testutils.getWitnessValue(witness, cir.symbols, "main.out"), expected_hash); + assert.deepEqual(litePoseidonHash(input), expected_hash); + }); + + it("Hashes two values", async () => { + cir = await testutils.genMain(circuit_path, "Hasher", [2]); + await cir.loadSymbols(); + input = [1, 2]; + const expected_hash = utils.poseidonHash(input, poseidon); + + const witness = await cir.calculateWitness({ "in": input }); + + assert.deepEqual(testutils.getWitnessValue(witness, cir.symbols, "main.out"), expected_hash); + assert.deepEqual(litePoseidonHash(input), expected_hash); + }); + + it("Hashes 15 values", async () => { + cir = await testutils.genMain(circuit_path, "Hasher", [15]); + await cir.loadSymbols(); + input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + const expected_hash = utils.poseidonHash(input, poseidon); + + const witness = await cir.calculateWitness({ "in": input }); + + assert.deepEqual(testutils.getWitnessValue(witness, cir.symbols, "main.out"), expected_hash); + assert.deepEqual(litePoseidonHash(input), expected_hash); + }); + + it("Hashes 16 values", async () => { + cir = await testutils.genMain(circuit_path, "Hasher", [16]); + await cir.loadSymbols(); + input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + const expected_hash = utils.poseidonHash(input, poseidon); + + const witness = await cir.calculateWitness({ "in": input }); + + assert.deepEqual(testutils.getWitnessValue(witness, cir.symbols, "main.out"), expected_hash); + assert.deepEqual(litePoseidonHash(input), expected_hash); + }); + + it("Hashes 30 values", async () => { + cir = await testutils.genMain(circuit_path, "Hasher", [30]); + await cir.loadSymbols(); + input = []; + for (let i = 0; i < 30; i++) { + input.push(i); + } + const expected_hash = utils.poseidonHash(input, poseidon); + + const witness = await cir.calculateWitness({ "in": input }); + + assert.deepEqual(testutils.getWitnessValue(witness, cir.symbols, "main.out"), expected_hash); + assert.deepEqual(litePoseidonHash(input), expected_hash); + }); + + it("Nonce test", async () => { + cir = await testutils.genMain(circuit_path, "Hasher", [4]); + await cir.loadSymbols(); + + const ephPubKey = BigInt("0x" + crypto.randomBytes(32).toString('hex')); + const maxEpoch = 100; + const randomness = BigInt("0x" + crypto.randomBytes(31).toString('hex')); + + assert.isTrue(randomness < P); + + // Breaking it into two chunks to avoid overflow in case ephPubKey > P + const ephPubKey_0 = ephPubKey % 2n**128n; + const ephPubKey_1 = ephPubKey / 2n**128n; + + assert.isTrue(ephPubKey_0 < P); + assert.isTrue(ephPubKey_1 < P); + + const nonceExpected = utils.poseidonHash([ephPubKey_0, ephPubKey_1, maxEpoch, randomness], poseidon); + const witness = await cir.calculateWitness({in: [ephPubKey_0, ephPubKey_1, maxEpoch, randomness]}, true); + const nonceActual = testutils.getWitnessValue(witness, cir.symbols, "main.out"); + assert.deepEqual(nonceActual, nonceExpected); + }); +}); diff --git a/zklogin-circuits/test/jwtchecks.circom.test.js b/zklogin-circuits/test/jwtchecks.circom.test.js new file mode 100644 index 0000000000..e4f48b3db3 --- /dev/null +++ b/zklogin-circuits/test/jwtchecks.circom.test.js @@ -0,0 +1,74 @@ +const chai = require("chai"); +const path = require("path"); +const assert = chai.assert; + +const params = require('../js/src/common').circuit_params; +const utils = require('../js/src/utils'); +const testutils = require("./testutils"); +const circuitutils = require("../js/src/circuitutils"); + +describe("Extended claim parser", () => { + it("Without any whitespaces", async () => { + const maxKeyClaimNameLenWithQuotes = 20; + const maxKeyClaimValueLenWithQuotes = 30; + const maxExtendedClaimLen = maxKeyClaimNameLenWithQuotes + maxKeyClaimValueLenWithQuotes + 2; + const circuit = await testutils.genMain( + path.join(__dirname, "../circuits/helpers", "jwtchecks.circom"), "ExtendedClaimParser", [ + maxExtendedClaimLen, maxKeyClaimNameLenWithQuotes, maxKeyClaimValueLenWithQuotes + ] + ); + await circuit.loadSymbols(); + + const payload = JSON.stringify({ + 'sub': '1234', + 'email': 'abcd@example.com', + 'name': 'John Doe' + }); + + const inputs = circuitutils.genExtClaimParserInputs(payload, "email", maxExtendedClaimLen); + const witness = await circuit.calculateWitness(inputs, true); + + const parsed_name = testutils.getWitnessArray(witness, circuit.symbols, "main.name").map(Number); + const parsed_value = testutils.getWitnessArray(witness, circuit.symbols, "main.value").map(Number); + + assert.deepEqual(parsed_name, utils.strToVec('"email"', maxKeyClaimNameLenWithQuotes)); + assert.deepEqual(parsed_value, utils.strToVec('"abcd@example.com"', maxKeyClaimValueLenWithQuotes)); + }); + + it("With whitespaces, newlines and tabs", async () => { + const maxKeyClaimNameLenWithQuotes = 20; + const maxKeyClaimValueLenWithQuotes = 30; + const maxExtendedClaimLen = maxKeyClaimNameLenWithQuotes + maxKeyClaimValueLenWithQuotes + 2; + const circuit = await testutils.genMain( + path.join(__dirname, "../circuits/helpers", "jwtchecks.circom"), "ExtendedClaimParser", [ + maxExtendedClaimLen, maxKeyClaimNameLenWithQuotes, maxKeyClaimValueLenWithQuotes + ] + ); + await circuit.loadSymbols(); + + const name = '"email"'; // name.length <= maxKeyClaimNameLenWithQuotes + const value = '"abcd@example.com"'; // value.length <= maxKeyClaimValueLenWithQuotes + const extended_claim = name + ' : \n\t ' + value + ' ,'; + assert.isAtMost(extended_claim.length, maxExtendedClaimLen); + const inputs = { + "extended_claim": utils.padWithZeroes(extended_claim.split('').map(c => c.charCodeAt()), maxExtendedClaimLen), + "name_len": name.length, + "colon_index": extended_claim.indexOf(':'), + "value_start": extended_claim.indexOf(value), + "value_len": value.length, + "length": extended_claim.length, + }; + + const witness = await circuit.calculateWitness(inputs, true); + await circuit.checkConstraints(witness); + + const parsed_name = testutils.getWitnessArray(witness, circuit.symbols, "main.name").map(Number); + const parsed_value = testutils.getWitnessArray(witness, circuit.symbols, "main.value").map(Number); + + assert.deepEqual(parsed_name, utils.strToVec('"email"', maxKeyClaimNameLenWithQuotes)); + assert.deepEqual(parsed_value, utils.strToVec('"abcd@example.com"', maxKeyClaimValueLenWithQuotes)); + }); + + // TODO: Add failing tests + // TODO: Add tests for corner cases: extended_claim_len = maxExtLength, ... +}); \ No newline at end of file diff --git a/zklogin-circuits/test/main.circom.test.js b/zklogin-circuits/test/main.circom.test.js new file mode 100644 index 0000000000..ef1763cd17 --- /dev/null +++ b/zklogin-circuits/test/main.circom.test.js @@ -0,0 +1,305 @@ +const chai = require("chai"); +const path = require("path"); +const assert = chai.assert; +const expect = chai.expect; + +const jwtutils = require("../js/src/jwtutils"); +const circuitutils = require("../js/src/circuitutils"); +const constants = require('../js/src/common').constants; +const devVars = constants.dev; +const utils = require("../js/src/utils"); +const verify = require('../js/src/verify'); + +const testutils = require("./testutils"); + +const circuit_constants = require('../js/src/common').circuit_params; +const dev_inputs = { + unsigned_jwt: "", + eph_public_key: constants.dev.ephPK, + max_epoch: constants.dev.maxEpoch, + jwt_rand: constants.dev.jwtRand, + user_pin: constants.dev.pin, + key_claim_name : "sub", +} + +async function genCircuit(params = circuit_constants) { + return await testutils.genMain( + path.join(__dirname, "../circuits", "zkloginMain.circom"), "ZKLogin", [ + params.max_padded_unsigned_jwt_len, + params.max_extended_key_claim_len, + params.max_key_claim_name_len + ] + ); +} + +async function genProof( + circuit, I, P = circuit_constants +) { + var [inputs, auxiliary_inputs] = await circuitutils.genZKLoginInputs(I, P); + utils.writeJSONToFile(inputs, "inputs.json"); + + const w = await circuit.calculateWitness(inputs, true); + await circuit.checkConstraints(w); + + verify.verifyAuxInputs( + auxiliary_inputs, + P.max_padded_unsigned_jwt_len + ); + + return [inputs, auxiliary_inputs]; +} + +describe("JWT Proof", function() { + const GOOGLE = require("../js/testvectors/realJWTs").GOOGLE; + const TWITCH = require("../js/testvectors/realJWTs").TWITCH; + + const test_vectors = { + google: { + jwt: GOOGLE["jwt"], + jwk: GOOGLE["jwk"] + }, + twitch: { + jwt: TWITCH["jwt"], + jwk: TWITCH["jwk"] + } + } + + for (const [provider, values] of Object.entries(test_vectors)) { + describe(provider, async function() { + it.skip("Verify JWT with JWK", async function() { + verify.verifyJwt(values["jwt"], values["jwk"]); + }); + + it("Prove w/ sub", async function() { + const circuit = await genCircuit(); + const I = { + ...dev_inputs, + unsigned_jwt: jwtutils.removeSig(values["jwt"]) + } + await genProof(circuit, I); + }); + + it("Prove w/ email", async function() { + const circuit = await genCircuit(); + const I = { + ...dev_inputs, + unsigned_jwt: jwtutils.removeSig(values["jwt"]), + key_claim_name: "email" + } + await genProof(circuit, I); + }); + }); + } +}); + +// Stringify and convert to base64. +// Note: Signature is omitted as this function is only meant for testing. +function constructJWT(header, payload) { + header = JSON.stringify(header); + payload = JSON.stringify(payload); + const b64header = Buffer.from(header).toString('base64url'); + const b64payload = Buffer.from(payload).toString('base64url'); + + if (b64header.slice(-1) === '=' || b64payload.slice(-1) === '=') { + throw new Error("Unexpected '=' in base64url string"); + } + + return b64header + "." + b64payload + "."; +} + +describe("Tests with crafted JWTs", () => { + const header = { + "alg":"RS256", + "kid":"827917329", + "typ":"JWT" + }; + const claim_string = '"sub":"4840061",'; + const pin = devVars.pin; + const nonce = "GCwq2zCuqtsa1BhaAc2SElwUoYv8jKhE6vs6Vmepu2M"; + const payload = { // Resembles Google's JWT + iss: 'google.com', + azp: 'example.com', + aud: 'example.com', + sub: '4840061', + email: 'example@gmail.com', + nonce: nonce, + iat: 4, + exp: 4, + jti: 'a8a0728a' + }; + + const jwt = constructJWT(header, payload); + const unsigned_jwt = jwtutils.removeSig(jwt); + + const test_params = { + ...circuit_constants, + "max_padded_unsigned_jwt_len": 64 * 6, + } + + const seed_sub = 3933397123257831927251308270714554907807704888576094124721682124818019353989n; + const seed_email = 1973999242154691951111604273911528395925144468932358877866874679764640280443n; + + before(async () => { + expect(jwtutils.getExtendedClaim(JSON.stringify(payload), "sub")).equals(claim_string); + expect(claim_string.length).at.most(test_params.max_extended_key_claim_len); + expect(await circuitutils.computeNonce()).equals(nonce); + expect(payload.sub.length).at.most(test_params.max_key_claim_value_len); + expect(await utils.deriveAddrSeed(payload.sub, pin, test_params.max_key_claim_value_len)).equals(seed_sub); + expect(payload.email.length).at.most(test_params.max_key_claim_value_len); + expect(await utils.deriveAddrSeed(payload.email, pin, test_params.max_key_claim_value_len)).equals(seed_email); + console.log("JWT: ", jwt); + }); + + beforeEach(async () => { + circuit = await genCircuit(test_params); + }); + + it("No change", async function() { + const inputs = { + ...dev_inputs, + unsigned_jwt: unsigned_jwt, + }; + const [_, aux] = await genProof( + circuit, + inputs, + test_params + ); + expect(aux["addr_seed"]).equals(seed_sub); + }); + + it("Sub claim comes first!", async function() { + const new_payload = { + sub: '4840061', + iss: 'google.com', + azp: 'example.com', + aud: 'example.com', + nonce: nonce, + iat: 4, + exp: 4, + jti: 'a8a0728a' + }; + const new_jwt = constructJWT(header, new_payload); + const inputs = { + ...dev_inputs, + unsigned_jwt: jwtutils.removeSig(new_jwt), + }; + const [_, aux] = await genProof( + circuit, + inputs, + test_params + ); + expect(aux["addr_seed"]).equals(seed_sub); + }); + + it("Sub claim comes last!", async function() { + const new_payload = { + iss: 'google.com', + azp: 'example.com', + aud: 'example.com', + nonce: nonce, + iat: 4, + exp: 4, + jti: 'a8a0728a', + sub: '4840061' + }; + const new_jwt = constructJWT(header, new_payload); + const inputs = { + ...dev_inputs, + unsigned_jwt: jwtutils.removeSig(new_jwt), + }; + const [_, aux] = await genProof( + circuit, + inputs, + test_params + ); + expect(aux["addr_seed"]).equals(seed_sub); + }); + + it("Order of claims is jumbled!", async function() { + const new_payload = { + iat: 4, + iss: 'google.com', + aud: 'example.com', + jti: 'a8a0728a', + exp: 4, + sub: '4840061', + azp: 'example.com', + nonce: nonce, + }; + const new_jwt = constructJWT(header, new_payload); + const inputs = { + ...dev_inputs, + unsigned_jwt: jwtutils.removeSig(new_jwt), + }; + const [_, aux] = await genProof( + circuit, + inputs, + test_params + ); + expect(aux["addr_seed"]).equals(seed_sub); + }); + + it("(Fail) Nonce has invalid value!", async () => { + const new_payload = { + sub: '4840061', + iss: 'google.com', + azp: 'example.com', + aud: 'example.com', + nonce: 'JMi6c_3qXn1H8UX5la1P6YDwThkN5LZxqagTyjfiYwU', // incorrect nonce + iat: 4, + exp: 4, + jti: 'a8a0728a' + }; + const new_jwt = constructJWT(header, new_payload); + try { + const inputs = { + ...dev_inputs, + unsigned_jwt: jwtutils.removeSig(new_jwt), + } + + var [zk_inputs, ] = await circuitutils.genZKLoginInputs( + inputs, + test_params, + false // set to false to turn off JS sanity checks + ); + await circuit.calculateWitness(zk_inputs, true); + } catch (error) { + assert.include(error.message, 'Error in template NonceChecker'); + } + }); + + // TODO: Test with an email of length 50 + it("No change w/ email", async function() { + const inputs = { + ...dev_inputs, + unsigned_jwt: jwtutils.removeSig(jwt), + key_claim_name: "email", + }; + const [_, aux] = await genProof( + circuit, + inputs, + test_params + ); + expect(aux["addr_seed"]).equals(seed_email); + }); + + it.skip("With spaces and tabs", async function () { + const payload = `{"iss":"google.com","azp":"example.com","aud":"example.com", "sub": "4840061" + ,"email":"example@gmail.com","nonce":"GCwq2zCuqtsa1BhaAc2SElwUoYv8jKhE6vs6Vmepu2M","iat":4,"exp":4,"jti":"a8a0728a"}`; + expect(JSON.parse(payload).sub).to.equal("4840061"); // the spaces don't matter + + const b64header = Buffer.from(JSON.stringify(header)).toString('base64url'); + const b64payload = Buffer.from(payload).toString('base64url'); + + const inputs = { + ...dev_inputs, + unsigned_jwt: `${b64header}.${b64payload}`, + }; + const [_, aux] = await genProof( + circuit, + inputs, + test_params + ); + expect(aux["addr_seed"]).equals(seed_sub); + }) +}); \ No newline at end of file diff --git a/zklogin-circuits/test/masking.test.js b/zklogin-circuits/test/masking.test.js new file mode 100644 index 0000000000..91947b038d --- /dev/null +++ b/zklogin-circuits/test/masking.test.js @@ -0,0 +1,162 @@ +const chai = require("chai"); +const assert = chai.assert; + +const circuit = require("../js/src/circuitutils"); +const utils = require("../js/src/utils"); +const jwtutils = require("../js/src/jwtutils"); +const verify = require("../js/src/verify"); + +const GOOGLE1 = require("../js/testvectors/realJWTs").GOOGLE.jwt; +const GOOGLE2 = require("../js/testvectors/realJWTs").GOOGLE_OLD.jwt; +const FB = require("../js/testvectors/realJWTs").FACEBOOK.jwt; + +function getAllClaims(jwt) { + const payload = Buffer.from(jwt.split('.')[1], 'base64url').toString(); + const json = JSON.parse(payload); + return Object.keys(json); +} + +function maskTesting(jwt, claimsToReveal, print=false) { + if (print) console.log("Reveal:", claimsToReveal); + var mask = circuit.genJwtMask(jwt, claimsToReveal); + if (print) console.log("Mask:", mask.join('')); + + var masked_jwt = utils.applyMask(new Uint8Array(Buffer.from(jwt)) , mask); + masked_jwt = Buffer.from(masked_jwt).toString(); + if (print) console.log("Masked JWT:", masked_jwt); + + const header_length = masked_jwt.indexOf('.'); + if (header_length == -1) throw new Error("Invalid header length"); + + const encoded_header = masked_jwt.slice(0, header_length); + // const extracted_header = Buffer.from(encoded_header, 'base64url').toString('utf8'); + if (encoded_header !== jwt.split('.')[0]) { + console.log("header", encoded_header, "\njwt", jwt.split('.')[0]); + throw new Error("Header not found in masked JWT"); + } + + const encoded_payload = masked_jwt.slice(header_length + 1); + const extracted_claims = verify.extractClaims(encoded_payload); + + // We just check that each full claim string is present (somewhere) in the masked JWT. In practice, these would need to parsed out. + // Note that some claims might not be at the start of an extracted_claim, e.g., if consecutive claims are being revealed. + for (const claim of claimsToReveal) { + const claim_string = jwtutils.getExtendedClaim(Buffer.from(jwt.split('.')[1], 'base64url').toString(), claim); + if (!extracted_claims.some(e => e.includes(claim_string))) { + console.log("Can't find claim", claim, "in", extracted_claims); + throw new Error("Claim not found in masked JWT"); + } + } + + for (const claim of extracted_claims) { + if (claim[0] !== '"') { + // First character of each extracted_claim must be '"' (extractClaims omits partial bits at the start) + console.log("Invalid claim", claim); + throw new Error("Invalid claim"); + } + + if (!(claim.slice(-1) === '}' || claim.slice(-1) === ',')) { + // Last character of each extracted_claim must be '}' or ',' + console.log("Invalid claim", claim); + throw new Error("Invalid claim"); + } + } + + if (print) console.log("Extracted claims:", extracted_claims); + if (print) console.log('\n'); + return extracted_claims; +} + +function subsets(array) { + return array.reduce( + (subsets, value) => subsets.concat( + subsets.map(set => [value, ...set]) + ), + [[]] + ); +} + +describe("Masking with dummy JWTs", function() { + // Creates a JWT-like string from a header and payload + const constructJWT = (header, payload) => { + jwt = utils.trimEndByChar(Buffer.from(header).toString('base64url'), '=') + + '.' + utils.trimEndByChar(Buffer.from(payload).toString('base64url'), '='); + return jwt; + } + + it(("#1"), function() { + header = '{"kid":abc}'; + payload = '{"iss":123,"azp":"gogle","iat":7890,"exp":101112}'; + + // Create a JWT + jwt = constructJWT(header, payload); + + // Test for all possible subsets of claims + const claims = getAllClaims(jwt); + assert.deepEqual(claims.sort(), ["iss", "azp", "iat", "exp"].sort()); + for (const subset of subsets(claims)) { + maskTesting(jwt, subset); + } + }); + + it(("#2"), function() { + header = '{"kid":abc}'; + payload = '{"iss":1234,"azp":"gogle","iat":7890,"exp":101112}'; + jwt = constructJWT(header, payload); + const claims = getAllClaims(jwt); + for (const subset of subsets(claims)) { + maskTesting(jwt, subset); + } + }); + + it(("#3"), function() { + header = '{"kid":abc}'; + payload = '{"iss":12345,"azp":"gogle","iat":7890,"exp":101112}'; + jwt = constructJWT(header, payload); + const claims = getAllClaims(jwt); + for (const subset of subsets(claims)) { + maskTesting(jwt, subset); + } + }); +}) + +describe("Masking with real JWTs", function() { + it("Google", function() { + const jwt = GOOGLE1.split('.').slice(0,2).join('.'); + const claims = getAllClaims(jwt); + assert.deepEqual(claims.sort(), [ + 'iss', 'azp', + 'aud', 'sub', + 'nonce', 'iat', + 'exp', 'jti', + 'email', 'email_verified' + ].sort()); + for (const subset of subsets(claims)) { + maskTesting(jwt, subset); + } + }); + + it("Google again", function() { + const jwt = GOOGLE2.split('.').slice(0,2).join('.'); + const claims = getAllClaims(jwt); + for (const subset of subsets(claims)) { + maskTesting(jwt, subset); + } + }); + + it("Facebook", function() { + const jwt = FB.split('.').slice(0,2).join('.'); + const claims = getAllClaims(jwt); + assert.deepEqual(claims.sort(), [ + 'aud', 'exp', + 'family_name', 'given_name', + 'iat', 'iss', + 'jti', 'name', + 'nonce', 'picture', + 'sub' + ].sort()); + for (const subset of subsets(claims)) { + maskTesting(jwt, subset); + } + }); +}); \ No newline at end of file diff --git a/zklogin-circuits/test/misc.circom.test.js b/zklogin-circuits/test/misc.circom.test.js new file mode 100644 index 0000000000..7b8aa92374 --- /dev/null +++ b/zklogin-circuits/test/misc.circom.test.js @@ -0,0 +1,497 @@ +const chai = require("chai"); +const path = require("path"); +const assert = chai.assert; +const expect = chai.expect; + +const utils = require("../js/src/utils"); + +const testutils = require("./testutils"); + +describe("Num2BitsBE(8)", () => { + beforeEach(async () => { + cir = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "Num2BitsBE", [8]); + await cir.loadSymbols(); + }); + + it ("Check 0", async () => { + const witness = await cir.calculateWitness({"in": 0}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessArray(witness, cir.symbols, "main.out"); + assert.deepEqual(out, [0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]); + }); + + it ("Check 1", async () => { + const witness = await cir.calculateWitness({"in": 1}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessArray(witness, cir.symbols, "main.out"); + assert.deepEqual(out, [0n, 0n, 0n, 0n, 0n, 0n, 0n, 1n]); + }); + + it ("Check 255", async () => { + const witness = await cir.calculateWitness({"in": 255}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessArray(witness, cir.symbols, "main.out"); + assert.deepEqual(out, [1n, 1n, 1n, 1n, 1n, 1n, 1n, 1n]); + }); + + it("Check 256: must throw an error", async () => { + try { + const witness = await cir.calculateWitness({"in": 256}, true); + await cir.checkConstraints(witness); + assert.fail("Should have failed"); + } catch (error) { + assert.include(error.message, "Error in template Num2BitsBE"); + } + }); + + it ("Check -1: must throw an error", async () => { + try { + const witness = await cir.calculateWitness({"in": -1}, true); + await cir.checkConstraints(witness); + assert.fail("Should have failed"); + } catch (error) { + assert.include(error.message, "Error in template Num2BitsBE"); + } + }); +}) + +describe("Num2BitsBE(256)", async () => { + const L = 256; + const P = 21888242871839275222246405745257275088548364400416034343698204186575808495617n; + + cir = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "Num2BitsBE", [L]); + await cir.loadSymbols(); + const crypto = require("crypto"); + const num = BigInt("0x" + crypto.randomBytes(32).toString('hex')) % P; + + const witness = await cir.calculateWitness({"in": num}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessArray(witness, cir.symbols, "main.out").map(x => Number(x)); + assert.equal(out.length, L); + + const out1 = (num % P).toString(2).padStart(L, '0').split('').map(x => Number(x)); + assert.equal(out1.length, L); + + assert.deepEqual(out, out1); +}) + +// Note: Although it is an offical circuit, tests for illustrative purposes and my own sanity +describe("Num2Bits", () => { + beforeEach(async () => { + cir = await testutils.genMain(path.join(__dirname, "../node_modules/circomlib/circuits", "bitify.circom"), "Num2Bits", [8]); + await cir.loadSymbols(); + }); + + it ("Check 0", async () => { + const witness = await cir.calculateWitness({"in": 0}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessArray(witness, cir.symbols, "main.out"); + assert.deepEqual(out, [0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]); + }); + + it ("Check 1", async () => { + const witness = await cir.calculateWitness({"in": 1}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessArray(witness, cir.symbols, "main.out"); + assert.deepEqual(out, [1n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]); + }); + + it ("Check 255", async () => { + const witness = await cir.calculateWitness({"in": 255}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessArray(witness, cir.symbols, "main.out"); + assert.deepEqual(out, [1n, 1n, 1n, 1n, 1n, 1n, 1n, 1n]); + }); + + it("Check 256: must throw an error", async () => { + try { + const witness = await cir.calculateWitness({"in": 256}, true); + await cir.checkConstraints(witness); + assert.fail("Should have failed"); + } catch (error) { + assert.include(error.message, "Error in template Num2Bits"); + } + }); + + it ("Check -1: must throw an error", async () => { + try { + const witness = await cir.calculateWitness({"in": -1}, true); + await cir.checkConstraints(witness); + assert.fail("Should have failed"); + } catch (error) { + assert.include(error.message, "Error in template Num2Bits"); + } + }); +}); + +describe("Bits2NumBE", () => { + before (async () => { + cir = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "Bits2NumBE", [8]); + await cir.loadSymbols(); + }); + + it ("Check 0", async () => { + const witness = await cir.calculateWitness({"in": [0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessValue(witness, cir.symbols, "main.out"); + assert.equal(out, 0n); + }); + + it ("Check 1", async () => { + const witness = await cir.calculateWitness({"in": [0n, 0n, 0n, 0n, 0n, 0n, 0n, 1n]}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessValue(witness, cir.symbols, "main.out"); + assert.equal(out, 1n); + }); + + it ("Check 255", async () => { + const witness = await cir.calculateWitness({"in": [1n, 1n, 1n, 1n, 1n, 1n, 1n, 1n]}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessValue(witness, cir.symbols, "main.out"); + assert.equal(out, 255n); + }); +}); + +describe("Segments2NumBE", () => { + before (async () => { + cir = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "Segments2NumBE", [8, 3]); + await cir.loadSymbols(); + }); + + it ("Check 0", async () => { + const witness = await cir.calculateWitness({"in": [0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessValue(witness, cir.symbols, "main.out"); + assert.equal(out, 0n); + }); + + it ("Check 8", async () => { + const witness = await cir.calculateWitness({"in": [0n, 0n, 0n, 0n, 0n, 0n, 1n, 0n]}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessValue(witness, cir.symbols, "main.out"); + assert.equal(out, 8n); + }); + + it ("Check 511", async () => { + const witness = await cir.calculateWitness({"in": [0n, 0n, 0n, 0n, 0n, 7n, 7n, 7n]}, true); + await cir.checkConstraints(witness); + const out = testutils.getWitnessValue(witness, cir.symbols, "main.out"); + assert.equal(out, 511n); + }); +}) + +describe("ConvertBase checks", () => { + it("Checking ConvertBase Case 0: input and output should be same", async () => { + cir_fixed = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "ConvertBase", [4, 4, 4, 4]); + await cir_fixed.loadSymbols(); + input = [1,2,3,4]; + const witness = await cir_fixed.calculateWitness({ "in": input }); + + const out = testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out").map(e => Number(e) - '0'); + assert.deepEqual(out, [1, 2, 3, 4]); + assert.deepEqual(out, utils.pack(input, 4, 4).map(Number)); + }); + + it("Checking ConvertBase Case 1: Output width is multiple of input width", async () => { + cir_fixed = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "ConvertBase", [4, 4, 8, 2]); + await cir_fixed.loadSymbols(); + input = [1,2,3,4]; + const witness = await cir_fixed.calculateWitness({ "in": input }); + + const out = testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out").map(e => Number(e) - '0'); + assert.deepEqual(out, [18, 52]); + assert.deepEqual(out, utils.pack(input, 4, 8).map(Number)); + }); + + it("Checking ConvertBase Case 2: Output width is not a multiple of input width", async () => { + cir_fixed = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "ConvertBase", [4, 4, 6, 3]); + await cir_fixed.loadSymbols(); + input = [1,2,3,4]; + const witness = await cir_fixed.calculateWitness({ "in": input }); + + const out = testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out").map(e => Number(e) - '0'); + assert.deepEqual(out, [4, 35, 16]); + assert.deepEqual(out, utils.pack(input, 4, 6).map(Number)); + }); + + it("Checking ConvertBase Case 3: Edge case - just one input", async () => { + cir_fixed = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "ConvertBase", [1, 1, 6, 1]); + await cir_fixed.loadSymbols(); + input = [1]; + const witness = await cir_fixed.calculateWitness({ "in": input }); + + const out = testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out").map(e => Number(e) - '0'); + assert.deepEqual(out, [32]); + assert.deepEqual(out, utils.pack(input, 1, 6).map(Number)); + }); + + it("Checking ConvertBase Case 4: Edge case - just one output", async () => { + cir_fixed = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "ConvertBase", [4, 4, 16, 1]); + await cir_fixed.loadSymbols(); + input = [1, 2, 3, 4]; + const witness = await cir_fixed.calculateWitness({ "in": input }); + + const out = testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out").map(e => Number(e) - '0'); + assert.deepEqual(out, [4660]); + assert.deepEqual(out, utils.pack(input, 4, 16).map(Number)); + }); + + it("Checking ConvertBase Case 5: Assert fail for myOutCount != outCount", async () => { + try { + cir_fixed = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "ConvertBase", [4, 4, 16, 2]); + assert.fail("Should have failed"); + } catch (error) { + assert.include(error.message, "assert(myOutCount == outCount)"); + } + }); + + it("Checking ConvertBase Case 6: Another test of correct padding", async () => { + cir_fixed = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "ConvertBase", [4, 4, 7, 3]); + await cir_fixed.loadSymbols(); + input = [7,1,8,2]; + const witness = await cir_fixed.calculateWitness({ "in": input }); + + const out = testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out").map(e => Number(e) - '0'); + assert.deepEqual(out, [56, 96, 64]); + assert.deepEqual(out, utils.pack(input, 4, 7).map(Number)); + }); +}); + +describe("RemainderMod4 checks", () => { + it("Positive + Negative cases", async () => { + cir_fixed = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "RemainderMod4", [3]); + await cir_fixed.loadSymbols(); + inputs = [1,2,3,4,5,6,7]; + for (let i = 0; i < inputs.length; i++) { // Should pass + const witness = await cir_fixed.calculateWitness({ "in": inputs[i] }); + await cir_fixed.checkConstraints(witness); + const out = testutils.getWitnessValue(witness, cir_fixed.symbols, "main.out"); + assert.deepEqual(out, BigInt(inputs[i] % 4)); + } + + try { + const witness = await cir_fixed.calculateWitness({ "in": 8 }); + await cir_fixed.checkConstraints(witness); + assert.fail("Should have failed"); + } + catch (error) { + assert.include(error.message, 'Error in template Num2Bits', error.message); // Num2Bits does the length check + } + }) +}); + +describe("DivideMod2Power checks", () => { + it("With 2^2 = 4", async () => { + circuit = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "DivideMod2Power", [4, 2]); + await circuit.loadSymbols(); + + for (let i = 0; i < 16; i++) { + const w = await circuit.calculateWitness({ "in": i }); + await circuit.checkConstraints(w); + const quotient = testutils.getWitnessValue(w, circuit.symbols, "main.quotient"); + const remainder = testutils.getWitnessValue(w, circuit.symbols, "main.remainder"); + + assert.deepEqual(quotient, BigInt(Math.floor(i / 4))); + assert.deepEqual(remainder, BigInt(i % 4)); + } + }) +}) + +describe("Number to bit vector checks", () => { + it("OneBitVector", async () => { + circuit = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "OneBitVector", [4]); + await circuit.loadSymbols(); + + // Success + for (let i = 0; i < 4; i++) { + const w = await circuit.calculateWitness({ "index": i }); + await circuit.checkConstraints(w); + var ans = [0n, 0n, 0n, 0n]; + ans[i] = 1n; + assert.sameOrderedMembers( + testutils.getWitnessArray(w, circuit.symbols, "main.out"), ans + ); + } + + // Failure + for (let i of [-1, 4, 8]) { + try { + const w = await circuit.calculateWitness({ "index": i }); + await circuit.checkConstraints(w); + assert.fail("Should have failed"); + } catch (error) { + assert.include(error.message, 'Error in template OneBitVector', error.message); + } + } + }) + + it("GTBitVector", async () => { + circuit = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "GTBitVector", [4]); + await circuit.loadSymbols(); + + // Success + for (let i = 0; i < 4; i++) { + const w = await circuit.calculateWitness({ "index": i }); + await circuit.checkConstraints(w); + var ans = [0n, 0n, 0n, 0n]; + for (let j = i + 1; j < 4; j++) { + ans[j] = 1n; + } + assert.sameOrderedMembers( + testutils.getWitnessArray(w, circuit.symbols, "main.out"), ans + ); + } + + // Failure + for (let i of [-1, 4, 5, 8]) { + try { + const w = await circuit.calculateWitness({ "index": i }); + await circuit.checkConstraints(w); + assert.fail(`Should have failed: index = ${i}`); + } catch (error) { + assert.include(error.message, 'Error in template GTBitVector', error.message); + } + } + }) + + it("LTBitVector", async () => { + circuit = await testutils.genMain(path.join(__dirname, "../circuits/helpers", "misc.circom"), "LTBitVector", [4]); + await circuit.loadSymbols(); + + // Success + for (let i = 1; i <= 4; i++) { + const w = await circuit.calculateWitness({ "index": i }); + await circuit.checkConstraints(w); + var ans = [0n, 0n, 0n, 0n]; + for (let j = 0; j < i; j++) { + ans[j] = 1n; + } + assert.sameOrderedMembers( + testutils.getWitnessArray(w, circuit.symbols, "main.out"), ans + ); + } + + // Failure + for (let i of [-1, 0, 5, 8]) { + try { + const w = await circuit.calculateWitness({ "index": i }); + await circuit.checkConstraints(w); + assert.fail("Should have failed"); + } catch (error) { + assert.include(error.message, 'Error in template LTBitVector', error.message); + } + } + }) +}); + +describe("RangeCheck", () => { + async function genCircuit(nBits, max) { + return await testutils.genMain( + path.join(__dirname, "../circuits/helpers", "misc.circom"), + "RangeCheck", + [nBits, max] + ); + } + + it("Positive", async () => { + var nBits = 4; + var max = 8; + + circuit = await genCircuit(nBits, max); + + for (var i = 0; i <= max; i++) { + const w = await circuit.calculateWitness({ "in": 0}); + await circuit.checkConstraints(w); + } + }) + + describe("Negative", async () => { + var nBits = 4; + var max = 8; + + before(async () => { + circuit = await genCircuit(nBits, max); + }); + + it("max < in < 2^nBits", async () => { + for (var i of [9, 10, 11, 15]) { // + try { + const w = await circuit.calculateWitness({ "in": i}); + await circuit.checkConstraints(w); + assert.fail("Should have failed"); + } catch (error) { + assert.include(error.message, 'Error in template RangeCheck'); + } + } + }); + + it("in < 0", async () => { + for (var i of [-1, -2, -3, -4]) { + try { + const w = await circuit.calculateWitness({ "in": i}); + await circuit.checkConstraints(w); + assert.fail("Should have failed"); + } catch (error) { + assert.include(error.message, 'Error in template RangeCheck'); + } + } + }); + + it("in >= 2^nBits", async () => { + for (var i of [16, 17, 18, 19, 20]) { + try { + const w = await circuit.calculateWitness({ "in": i}); + await circuit.checkConstraints(w); + assert.fail("Should have failed"); + } catch (error) { + assert.include(error.message, 'Error in template RangeCheck'); + } + } + }); + }); +}); + +describe("mapToField", () => { + var circuit; + const maxStrLen = 20; + + before(async () => { + circuit = await testutils.genMain( + path.join(__dirname, "../circuits/helpers", "misc.circom"), "MapToField", [maxStrLen] + ); + await circuit.loadSymbols(); + }); + + it("Normal string", async () => { + const str = 'abracadabra'; + const witness = await circuit.calculateWitness({ + "str": utils.strToVec(str, maxStrLen) + }, true); + await circuit.checkConstraints(witness); + + const x = testutils.getWitnessValue(witness, circuit.symbols, "main.str_F"); + expect(x).equals(await utils.mapToField(str, maxStrLen)); + }); + + it("empty string", async () => { + const str = ''; + const witness = await circuit.calculateWitness({ + "str": utils.strToVec(str, maxStrLen) + }, true); + await circuit.checkConstraints(witness); + + const x = testutils.getWitnessValue(witness, circuit.symbols, "main.str_F"); + expect(x).equals(await utils.mapToField(str, maxStrLen)); + }); + + it("String length == maxLen", async () => { + const str = 'a'.repeat(maxStrLen); + const witness = await circuit.calculateWitness({ + "str": utils.strToVec(str, maxStrLen) + }, true); + await circuit.checkConstraints(witness); + + const x = testutils.getWitnessValue(witness, circuit.symbols, "main.str_F"); + expect(x).equals(await utils.mapToField(str, maxStrLen)); + }); +}); \ No newline at end of file diff --git a/zklogin-circuits/test/sha256.circom.test.js b/zklogin-circuits/test/sha256.circom.test.js new file mode 100644 index 0000000000..b7bcbc361b --- /dev/null +++ b/zklogin-circuits/test/sha256.circom.test.js @@ -0,0 +1,107 @@ +const chai = require("chai"); +const path = require("path"); +const assert = chai.assert; +const crypto = require("crypto"); + +const {toBigIntBE} = require('bigint-buffer'); + +const utils = require("../js/src/utils"); +const circuit = require("../js/src/circuitutils"); + +const testutils = require("./testutils"); +const inWidth = require("../js/src/common").constants.inWidth; + +describe("Unsafe SHA256", () => { + const nBlocks = 4; + const nWidth = inWidth; + const nCount = nBlocks * 512 / nWidth; + + const outWidth = 128; + const outCount = 256 / outWidth; + + const bytesToBlock = 512/8; // 64 + var cir; + + before(async() => { + cir = await testutils.genMain( + path.join(__dirname, "../circuits/helpers", "sha256.circom"), + "Sha2_wrapper", + [nWidth, nCount, outWidth, outCount] + ); + await cir.loadSymbols(); + }); + + async function test(i, expected_num_sha2_blocks) { + const input = crypto.randomBytes(i * bytesToBlock); + + const hash = crypto.createHash("sha256").update(input).digest("hex"); + + var inputs = circuit.genSha256Inputs(input, nCount, nWidth); + + var final_inputs = {}; + final_inputs["in"] = inputs["content"]; + final_inputs["num_sha2_blocks"] = inputs["num_sha2_blocks"]; + + assert.equal(final_inputs["num_sha2_blocks"], expected_num_sha2_blocks); + console.log(`num_sha2_blocks = ${final_inputs["num_sha2_blocks"]}`); + + const witness = await cir.calculateWitness(final_inputs, true); + + const hash2 = testutils.getWitnessBuffer(witness, cir.symbols, "main.hash", outWidth).toString("hex"); + console.log(`hash = ${hash2}`); + + assert.equal(hash2, hash); + } + + it(`Hashing produces expected output for 0.5 block`, async () => { + await test(0.5, 1); // num_sha2_blocks = 1 + }); + + it(`Hashing produces expected output for 1 block`, async () => { + await test(1, 2); + }); + + it(`Hashing produces expected output for 2 blocks`, async () => { + await test(2, 3); + }); + + it(`Corner case: num_sha2_blocks = nBlocks`, async () => { + await test(nBlocks - 1, nBlocks); + }); + + it(`Fails when the last byte is non-zero`, async () => { + const input = crypto.randomBytes(1 * bytesToBlock); + + var inputs = circuit.genSha256Inputs(input, nCount, nWidth); + + var final_inputs = {}; + final_inputs["in"] = inputs["content"]; + final_inputs["num_sha2_blocks"] = inputs["num_sha2_blocks"]; + + final_inputs["in"][final_inputs["in"].length - 1] = 1n; // Make the last byte non-zero + try { + await cir.calculateWitness(final_inputs, true); + assert.fail("Should have thrown an error"); + } catch (e) { + assert.include(e.message, "Error in template Sha2_wrapper"); + } + }); + + it(`Fails when the first byte post SHA2-padding is non-zero`, async () => { + const input = crypto.randomBytes(1 * bytesToBlock); + + var inputs = circuit.genSha256Inputs(input, nCount, nWidth); + var final_inputs = {}; + final_inputs["in"] = inputs["content"]; + final_inputs["num_sha2_blocks"] = inputs["num_sha2_blocks"]; + + var num_sha2_blocks = final_inputs["num_sha2_blocks"]; + final_inputs["in"][num_sha2_blocks * 512 / nWidth] = 1n; // Make the first byte post SHA2-padding non-zero + try { + await cir.calculateWitness(final_inputs, true); + assert.fail("Should have thrown an error"); + } catch (e) { + assert.include(e.message, "Error in template Sha2_wrapper"); + } + }); +}); \ No newline at end of file diff --git a/zklogin-circuits/test/strings.circom.test.js b/zklogin-circuits/test/strings.circom.test.js new file mode 100644 index 0000000000..18f2efa0d1 --- /dev/null +++ b/zklogin-circuits/test/strings.circom.test.js @@ -0,0 +1,574 @@ +const chai = require("chai"); +const path = require("path"); +const assert = chai.assert; +const expect = chai.expect; + +const jwtutils = require("../js/src/jwtutils"); +const utils = require("../js/src/utils"); + +const testutils = require("./testutils"); + +describe("Slices", () => { + const file = path.join(__dirname, "../circuits/helpers", "strings.circom"); + + describe("Fixed length", () => { + it("(6, 2)", async () => { + cir_fixed = await testutils.genMain(file, "SliceFixed", [6, 2]); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + + const witness = await cir_fixed.calculateWitness({ "in": input, "index": 1 }); + + assert.sameOrderedMembers(testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out"), [2n, 3n]); + }); + + it("(6, 6)", async () => { + cir_fixed = await testutils.genMain(file, "SliceFixed", [6, 6]); + await cir_fixed.loadSymbols(); + input = [3,1,5,9,2,6]; + + const witness = await cir_fixed.calculateWitness({ "in": input, "index": 0 }); + + assert.sameOrderedMembers(testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out"), [3n, 1n, 5n, 9n, 2n, 6n]); + }); + + it("Fails when OOB: index >= inputLength", async () => { + cir_fixed = await testutils.genMain(file, "SliceFixed", [6, 4]); + await cir_fixed.loadSymbols(); + input = [3,1,5,9,2,6]; + + try { + await cir_fixed.calculateWitness({ "in": input, "index": 6 }); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + + it("Slice outside: index + outputLength > inputLength", async () => { + cir_fixed = await testutils.genMain(file, "SliceFixed", [6, 4]); + await cir_fixed.loadSymbols(); + input = [3,1,5,9,2,6]; + + try { + await cir_fixed.calculateWitness({ "in": input, "index": 4 }); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + }) + + describe("Variable length", () => { + it("(6, 4), 2", async () => { + cir_fixed = await testutils.genMain(file, "Slice", [6, 4]); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + + const witness = await cir_fixed.calculateWitness({ "in": input, "index": 1, "length": 2 }); + + assert.sameOrderedMembers(testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out"), [2n, 3n, 0n, 0n]); + }); + + it("Corner case: length = outputLength", async () => { + cir_fixed = await testutils.genMain(file, "Slice", [6, 4]); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + + const witness = await cir_fixed.calculateWitness({ "in": input, "index": 1, "length": 4 }); + + assert.sameOrderedMembers(testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out"), [2n, 3n, 4n, 5n]); + }); + + it("Fails when index >= inputLength", async () => { + cir_fixed = await testutils.genMain(file, "Slice", [6, 4]); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + + try { + await cir_fixed.calculateWitness({ "in": input, "index": 6, "length": 4 }); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + + it("Fails when length > outputLength", async () => { + cir_fixed = await testutils.genMain(file, "Slice", [6, 4]); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + + try { + await cir_fixed.calculateWitness({ "in": input, "index": 1, "length": 5 }); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + + it("Fails when length = 0", async () => { + cir_fixed = await testutils.genMain(file, "Slice", [6, 4]); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + + try { + await cir_fixed.calculateWitness({ "in": input, "index": 1, "length": 0 }); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + + it("Fails when index + length > inputLength", async () => { + cir_fixed = await testutils.genMain(file, "Slice", [6, 4]); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + try { + await cir_fixed.calculateWitness({ "in": input, "index": 4, "length": 4 }); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + + it("Slice outside: inputLength >= index + length > outputLength", async () => { + cir_fixed = await testutils.genMain(file, "Slice", [8, 6]); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6,7,8]; + const witness = await cir_fixed.calculateWitness({ "in": input, "index": 4, "length": 4 }); + assert.sameOrderedMembers(testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out"), [5n, 6n, 7n, 8n, 0n, 0n]); + }); + }); + + describe("Variable length with SliceGrouped", () => { + var template_name = "SliceGrouped"; + + describe("numsPerGroup = 2", () => { + var inWidth = 3; + var numsPerGroup = 2; + var inLen = 6; + var outLen = 4; + var params = [inWidth, numsPerGroup, inLen, outLen]; + + before(() => { + expect(inLen % numsPerGroup).to.equal(0); + }); + + it("(6, 4), 2", async () => { + cir_fixed = await testutils.genMain(file, template_name, params); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + const witness = await cir_fixed.calculateWitness({ "in": input, "index": 1, "length": 2 }); + assert.sameOrderedMembers( + testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out").slice(0, 2), + [2n, 3n] + ); + }); + + it("Corner case: length = outputLength", async () => { + cir_fixed = await testutils.genMain(file, template_name, params); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + const witness = await cir_fixed.calculateWitness({ "in": input, "index": 1, "length": 4 }); + assert.sameOrderedMembers( + testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out"), + [2n, 3n, 4n, 5n] + ); + }); + + it("Slice outside: inputLength = (index + length) > outputLength", async () => { + cir_fixed = await testutils.genMain(file, template_name, [4, numsPerGroup, 8, 6]); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6,7,8]; + const witness = await cir_fixed.calculateWitness({ "in": input, "index": 4, "length": 4 }); + assert.sameOrderedMembers( + testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out").slice(0, 4), + [5n, 6n, 7n, 8n] + ); + }); + + it("Fails when length = 0", async () => { + cir_fixed = await testutils.genMain(file, template_name, params); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + + try { + await cir_fixed.calculateWitness({ "in": input, "index": 1, "length": 0 }); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + + it("Fails when index >= inputLength", async () => { + cir_fixed = await testutils.genMain(file, template_name, params); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + try { + await cir_fixed.calculateWitness({ "in": input, "index": 6, "length": 4 }); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + + it("Fails when length > outputLength", async () => { + cir_fixed = await testutils.genMain(file, template_name, params); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + try { + await cir_fixed.calculateWitness({ "in": input, "index": 1, "length": 5 }); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + + it("Fails when index + length > inputLength", async () => { + cir_fixed = await testutils.genMain(file, template_name, params); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6]; + try { + await cir_fixed.calculateWitness({ "in": input, "index": 4, "length": 4 }); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + }); + + describe("numsPerGroup = 4", () => { + var inWidth = 4; + var numsPerGroup = 4; + + describe("OutLen tests", () => { + for (var outLen = 1; outLen <= 20; outLen += 1) { + (function(outLen) { + it(`outLen: ${outLen}`, async () => { + var inLen = 12; + expect(inLen % numsPerGroup).to.equal(0); + var params = [inWidth, numsPerGroup, inLen, outLen]; + cir_fixed = await testutils.genMain(file, template_name, params); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6,7,8,9,10,11,12]; + try { + const witness = await cir_fixed.calculateWitness({ "in": input, "index": 1, "length": 4 }); + const output = testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out"); + assert.sameOrderedMembers( + output.slice(0, 4), + [2n, 3n, 4n, 5n] + ); + if (outLen < 4) { + assert.fail("Should have failed"); + } + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + })(outLen); + } + }); + + describe("Index tests", () => { + var inLen = 12; + var outLen = 8; + + for (var index = 0; index < 12; index += 1) { + (function(index) { + it(`index: ${index}`, async () => { + expect(inLen % numsPerGroup).to.equal(0); + var params = [inWidth, numsPerGroup, inLen, outLen]; + cir_fixed = await testutils.genMain(file, template_name, params); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6,7,8,9,10,11,12]; + try { + const witness = await cir_fixed.calculateWitness({ "in": input, "index": index, "length": 4 }); + const output = testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out"); + assert.sameOrderedMembers( + output.slice(0, 4).map(Number), + input.slice(index, index + 4) + ); + if (index + 4 > inLen) { + assert.fail("Should have failed"); + } + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + })(index); + } + }); + + describe("Length tests", () => { + var inLen = 12; + var outLen = 8; + for (var length = 1; length <= 12; length += 1) { + (function(length) { + it(`length: ${length}`, async () => { + expect(inLen % numsPerGroup).to.equal(0); + var params = [inWidth, numsPerGroup, inLen, outLen]; + cir_fixed = await testutils.genMain(file, template_name, params); + await cir_fixed.loadSymbols(); + input = [1,2,3,4,5,6,7,8,9,10,11,12]; + try { + const witness = await cir_fixed.calculateWitness({ "in": input, "index": 1, "length": length }); + const output = testutils.getWitnessArray(witness, cir_fixed.symbols, "main.out"); + assert.sameOrderedMembers( + output.slice(0, length).map(Number), + input.slice(1, 1 + length) + ); + if (1 + length > inLen || length > outLen) { + assert.fail("Should have failed"); + } + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + } + }); + })(length); + } + }); + + }); + }); + +}) + +describe("ASCIISubstrExistsInB64" , () => { + const numsPerGroup = 8; + + async function genCircuit(maxJwtLen, maxA, numsPerGroup) { + return await testutils.genMain( + path.join(__dirname, "../circuits/helpers", "strings.circom"), + "ASCIISubstrExistsInB64", + [maxJwtLen, maxA, numsPerGroup] + ); + } + + async function genProof(circuit, jwt, maxJwtLen, A, maxA, indexB, lenB, payloadIndex, lenA=A.length) { + assert.isTrue(jwt.length <= maxJwtLen, "JWT too long"); + assert.isTrue(A.length <= maxA, "A too long"); + + const inputs = { + "b64Str": utils.padWithZeroes(jwt.split("").map(c => c.charCodeAt(0)), maxJwtLen), // pad with 0s + "lenB": lenB, + "BIndex": indexB, + "A": utils.padWithZeroes(A.split("").map(c => c.charCodeAt(0)), maxA), // pad with 0s + "lenA": lenA, + "payloadIndex": payloadIndex + }; + + const witness = await circuit.calculateWitness(inputs); + await circuit.checkConstraints(witness); + } + + describe("lenA < maxA", () => { + const maxJwtLen = 200; + const A = '"sub":"4840061"}'; + const maxA = A.length + 14; + const maxB = 1 + ((maxA / 3) * 4); + var circuit; + + before(() => { + assert.equal(maxA % 3, 0); + assert.isTrue(maxB <= maxJwtLen); + assert.equal(maxJwtLen % numsPerGroup, 0); + }) + + describe("Simple JWTs", () => { + // Prefixes chosen such that index of A in the JWT is 0, 1, 2 + const prefixes = ['{ ', '{', '{ ']; + const decoded_jwts = prefixes.map(prefix => prefix + A); + const jwts = decoded_jwts.map(jwt => Buffer.from(jwt).toString("base64url")); + + const X = jwts.map(jwt => jwtutils.indicesOfB64(jwt, 'sub')); + const indicesB = X.map(tuple => tuple[0]); + const lensB = X.map(tuple => tuple[1]); + + before(async () => { + assert.equal(maxA % 3, 0); + assert.isTrue(maxB <= maxJwtLen); + for (let i = 0; i < decoded_jwts.length; i++) { + assert.deepEqual(decoded_jwts[i].indexOf(A) % 4 , i); + assert.deepEqual(jwtutils.getExtendedClaim(decoded_jwts[i], 'sub'), A); + assert.deepEqual(jwtutils.decodeBase64URL( + jwts[i].slice(indicesB[i], indicesB[i] + lensB[i]), + indicesB[i] + ), A); + } + }); + + for (let offset = 0; offset < 3; offset++) { + it(`Succeeds when A is at offset ${offset}`, async () => { + circuit = await genCircuit(maxJwtLen, maxA, numsPerGroup); + await genProof(circuit, jwts[offset], maxJwtLen, A, maxA, indicesB[offset], lensB[offset], 0); + }); + + it(`Fails when substring index is either large or small. A offset ${offset}`, async () => { + for (let i in [1, -1]) { + try { + circuit = await genCircuit(maxJwtLen, maxA, numsPerGroup); + await genProof(circuit, jwts[offset], maxJwtLen, A, maxA, indicesB[offset] + i, lensB[offset], 0); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template ASCIISubstrExistsInB64"); + } + } + }); + + // NOTE: Removed "Fails when lenB is small" tests after the change to SliceGrouped as it might succeed in some cases now. + + it(`Succeeds when lenB is large. A offset ${offset}`, async() => { + circuit = await genCircuit(maxJwtLen, maxA, numsPerGroup); + await genProof(circuit, jwts[offset], maxJwtLen, A, maxA, indicesB[offset], lensB[offset] + 1, 0); + }); + } + + it("Fails when substring index < payload index", async () => { + try { + circuit = await genCircuit(maxJwtLen, maxA, numsPerGroup); + await genProof(circuit, jwts[0], maxJwtLen, A, maxA, indicesB[0], lensB[0], indicesB[0] + 1); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template RemainderMod4"); + assert.include(e.message, "Error in template Num2Bits"); + } + }); + }); + + describe("Bigger JWTs", async () => { + const payload = JSON.stringify({ + "iat": 1616421600, + "exp": 1616425200, + "name": "John Doe", + "sub": "4840061" + }); + const encoded_payload = Buffer.from(payload).toString("base64url"); + + it("No header", async () => { + circuit = await genCircuit(maxJwtLen, maxA, numsPerGroup); + const jwt = encoded_payload; + [index, len] = jwtutils.indicesOfB64(jwt, 'sub'); + await genProof(circuit, jwt, maxJwtLen, A, maxA, index, len, 0); + }); + + it("With header", async () => { + circuit = await genCircuit(maxJwtLen, maxA, numsPerGroup); + const header = JSON.stringify({ + "alg": "RS256", + "typ": "JWT" + }); + const jwt = Buffer.from(header).toString("base64url") + "." + encoded_payload; + [index, len] = jwtutils.indicesOfB64(encoded_payload, 'sub'); + const payload_index = jwt.indexOf(encoded_payload); + await genProof(circuit, jwt, maxJwtLen, A, maxA, index + payload_index, len, payload_index); + }); + + it("lenB = maxB", async () => { + circuit = await genCircuit(maxJwtLen, maxA, numsPerGroup); + const jwt = encoded_payload; + [index, len] = jwtutils.indicesOfB64(jwt, 'sub'); + await genProof(circuit, jwt, maxJwtLen, A, maxA, index, maxB, 0); + }); + + it("Fails when lenB > maxB or lenB < 0", async () => { + const jwt = encoded_payload; + [index, len] = jwtutils.indicesOfB64(jwt, 'sub'); + for (lenB of [-1, maxB + 1]) { + circuit = await genCircuit(maxJwtLen, maxA, numsPerGroup); + try { + await genProof(circuit, jwt, maxJwtLen, A, maxA, index, lenB, 0); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template RangeCheck"); + assert.include(e.message, "Error in template Slice"); + } + } + }) + + it("Fails when lenA > maxA or lenA < 0", async () => { + const jwt = encoded_payload; + [index, len] = jwtutils.indicesOfB64(jwt, 'sub'); + for (lenA of [maxA + 1, -1]) { + circuit = await genCircuit(maxJwtLen, maxA, numsPerGroup); + try { + await genProof(circuit, jwt, maxJwtLen, A, maxA, index, len, 0, lenA); + assert.fail("Should have failed"); + } catch (e) { + assert.include(e.message, "Error in template LTBitVector"); + } + } + }); + + }); + }); + + it("lenA = maxA", async () => { + const maxJwtLen = 200; + const A = '"sub":"484061",'; // 15 chars + assert.deepEqual(A.length % 3, 0); + + const lenA = A.length; + const maxA = lenA; + const maxB = 1 + ((maxA / 3) * 4); + const header = JSON.stringify({ + "alg": "RS256", + "typ": "JWT" + }); + const payload = JSON.stringify({ + "sub": "484061", + "iat": 1616421600, + "exp": 1616425200, + "name": "John Doe" + }); + assert.deepEqual(jwtutils.getExtendedClaim(payload, 'sub'), A); + const encoded_payload = Buffer.from(payload).toString("base64url"); + const jwt = Buffer.from(header).toString("base64url") + "." + encoded_payload; + + const payload_index = jwt.indexOf(encoded_payload); + + [index, len] = jwtutils.indicesOfB64(encoded_payload, 'sub'); + assert.isAtMost(len, maxB); + + const circuit = await genCircuit(maxJwtLen, maxA, numsPerGroup); + await genProof(circuit, jwt, maxJwtLen, A, maxA, index + payload_index, len, payload_index); + }); + + it("Nonce", async() => { + const maxJwtLen = 200; + const bignum = 8679359968269066238270369971672891012793979385072768529748854974904529914083n; + const numbits = bignum.toString(2).length; + expect(numbits).to.be.at.most(254); + + const bignum_in_base64 = Buffer.from(bignum.toString(16), "hex").toString("base64url"); + chai.expect(bignum_in_base64.length).to.equal(Math.ceil(numbits / 6)); + + const A = '"nonce":"' + bignum_in_base64 + '",'; // <= 11 + Math.ceil(254/6) = 54 chars + + const lenA = A.length; + const maxA = lenA; + expect(maxA).to.be.at.most(54); + const maxB = 1 + ((maxA / 3) * 4); + + const header = JSON.stringify({ + "alg": "RS256", + "typ": "JWT" + }); + const payload = JSON.stringify({ + "sub": "484061", + "iat": 1616421600, + "exp": 1616425200, + "nonce": bignum_in_base64, + "name": "John Doe" + }); + assert.deepEqual(jwtutils.getExtendedClaim(payload, 'nonce'), A); + + const encoded_payload = Buffer.from(payload).toString("base64url"); + const jwt = Buffer.from(header).toString("base64url") + "." + encoded_payload; + const payload_index = jwt.indexOf(encoded_payload); + const [index, len] = jwtutils.indicesOfB64(encoded_payload, 'nonce'); + assert.isAtMost(len, maxB); + + const circuit = await genCircuit(maxJwtLen, maxA, numsPerGroup); + await genProof(circuit, jwt, maxJwtLen, A, maxA, index + payload_index, len, payload_index); + }); +}); diff --git a/zklogin-circuits/test/testutils.js b/zklogin-circuits/test/testutils.js new file mode 100644 index 0000000000..07082879b8 --- /dev/null +++ b/zklogin-circuits/test/testutils.js @@ -0,0 +1,57 @@ +const temp = require("temp"); +const path = require("path"); +const fs = require("fs"); + +const circom_wasm = require("circom_tester").wasm; +const utils = require("../js/src/utils"); + +function getWitnessValue(witness, symbols, varName) { + return witness[symbols[varName]['varIdx']]; +} + +function getWitnessMap(witness, symbols, arrName) { + return Object.entries(symbols).filter(([index, symbol]) => index.startsWith(arrName)).map(([index, symbol]) => Object.assign({}, symbol, { "name": index, "value": witness[symbol['varIdx']] }) ); +} + +function getWitnessArray(witness, symbols, arrName) { + return Object.entries(symbols).filter(([index, symbol]) => index.startsWith(`${arrName}[`)).map(([index, symbol]) => witness[symbol['varIdx']] ); +} + +function getWitnessBuffer(witness, symbols, arrName, varSize=1) { + const witnessArray = getWitnessArray(witness, symbols, arrName); + if(varSize == 1) { + return utils.bitArray2Buffer(witnessArray); + } else { + return utils.bigIntArray2Buffer(witnessArray, varSize); + } +} + +async function genMain(template_file, template_name, params = [], file_name) { + temp.track(); + + const temp_circuit = await temp.open({prefix: template_name, suffix: ".circom"}); + const include_path = path.relative(temp_circuit.path, template_file); + const params_string = JSON.stringify(params).slice(1, -1); + + fs.writeSync(temp_circuit.fd, ` +pragma circom 2.0.0; + +include "${include_path}"; + +component main = ${template_name}(${params_string}); + `); + + if (file_name !== undefined) { + fs.copyFileSync(temp_circuit.path, file_name); + } + + return circom_wasm(temp_circuit.path); +} + +module.exports = { + getWitnessValue: getWitnessValue, + getWitnessMap: getWitnessMap, + getWitnessArray: getWitnessArray, + getWitnessBuffer: getWitnessBuffer, + genMain: genMain +} \ No newline at end of file diff --git a/zklogin-circuits/test/utils.test.js b/zklogin-circuits/test/utils.test.js new file mode 100644 index 0000000000..6b5b47c4ee --- /dev/null +++ b/zklogin-circuits/test/utils.test.js @@ -0,0 +1,255 @@ +// Tests for important functions in utils.js and jwtutils.js + +const chai = require("chai"); +const assert = chai.assert; +const expect = chai.expect; +const crypto = require("crypto"); + +const circuit = require("../js/src/circuitutils"); +const utils = require("../js/src/utils"); +const jwtutils = require("../js/src/jwtutils"); + +describe("Circuit Utilities", () => { + it("Buffer to/from bit array works as expected", async () => { + const input = crypto.randomBytes(20*32).toString("hex"); + + const bits = utils.buffer2BitArray(Buffer.from(input)); + const buffer = utils.bitArray2Buffer(bits); + + assert.equal(input, buffer.toString()); + }); + + it("rfc4634#4.1 padding conforms: L % 512 = 0", async () => { + const input = crypto.randomBytes(512/8/2).toString("hex"); + + const bits = utils.buffer2BitArray(Buffer.from(input)); + const padded = circuit.padMessage(bits); + + assert.equal(bits.length, 512); + assert.equal(padded.length, 1024); // Padding a 448+-bit message requires an additional block + assert.equal(1, padded.slice(-512, -511)); // Padding begins with 1 + assert.equal(bits.length, parseInt(padded.slice(-64).join(''), 2)); // base2(L) + }); + + it("rfc4634#4.1 padding conforms: L % 512 = 65", async () => { + const input = crypto.randomBytes(512/8/2).toString("hex"); + + const bits = utils.buffer2BitArray(Buffer.from(input)).slice(0, 447); + const padded = circuit.padMessage(bits); + + assert.equal(bits.length, 447); + assert.equal(padded.length, 512); + assert.equal(1, padded.slice(-65, -64)); // Padding begins with 1 + assert.equal(bits.length, parseInt(padded.slice(-64).join(''), 2)); + }); + + it("rfc4634#4.1 padding conforms: L % 512 = 100", async () => { + const input = crypto.randomBytes(512/8/2).toString("hex"); + + const bits = utils.buffer2BitArray(Buffer.from(input)).slice(0, 412); + const padded = circuit.padMessage(bits); + + assert.equal(bits.length, 412); + assert.equal(padded.length, 512); + assert.equal(1, padded.slice(-100, -99)); // Padding begins with 1 + assert.equal(bits.length, parseInt(padded.slice(-64).join(''), 2)); + }); +}); + +describe("JWT utils tests", () => { + const getExtendedClaim = jwtutils.getExtendedClaim; + const b64Len = jwtutils.b64Len; + const indicesOfB64 = jwtutils.indicesOfB64; + const decodeBase64URL = jwtutils.decodeBase64URL; + + describe("getExtendedClaim", () => { + it("Normal strings", () => { + // Without quotes, not end + assert.deepEqual( + getExtendedClaim('{"iss":12345,"sub":45678,"aud":"https://example.com"}', "iss"), + '"iss":12345,' + ); + + // With quotes, not end + assert.deepEqual( + getExtendedClaim('{"iss":"12345","sub":45678,"aud":"https://example.com"}', "iss"), + '"iss":"12345",' + ); + + // Without quotes, end + assert.deepEqual( + getExtendedClaim('{"iss":12345,"sub":45678}', "sub"), + '"sub":45678}' + ); + + // With quotes, end + assert.deepEqual( + getExtendedClaim('{"iss":12345,"sub":45678,"aud":"https://example.com"}', "aud"), + '"aud":"https://example.com"}' + ); + }) + + it("With escapes", () => { + assert.deepEqual( + getExtendedClaim('{"iss":"https:\\/\\/www.facebook.com","sub":45678,"aud":12345}', "iss"), + '"iss":"https:\\/\\/www.facebook.com",' + ); + + assert.deepEqual( + getExtendedClaim('{"iss":"https:\\/\\/www.facebook.com","sub":45678,"picture":"https:\\/\\/platform-lookaside.fbsbx.com\\/platform\\/profilepic\\/?asid=708562611009525&height=100&width=100&ext=1684596798&hash=AeRIgRL_XooqrdDidNY"}', "picture"), + '"picture":"https:\\/\\/platform-lookaside.fbsbx.com\\/platform\\/profilepic\\/?asid=708562611009525&height=100&width=100&ext=1684596798&hash=AeRIgRL_XooqrdDidNY"}' + ); + }); + }); + + it("b64Len", () => { + assert.deepEqual(b64Len(0, 0), 0, "Test case 1"); + assert.deepEqual(b64Len(3, 0), 4, "Test case 2"); + assert.deepEqual(b64Len(3, 1), 5, "Test case 3"); + assert.deepEqual(b64Len(3, 2), 5, "Test case 4"); + assert.deepEqual(b64Len(6, 0), 8, "Test case 5"); + assert.deepEqual(b64Len(6, 1), 9, "Test case 6"); + assert.deepEqual(b64Len(6, 2), 9, "Test case 7"); + assert.deepEqual(b64Len(9, 0), 12, "Test case 8"); + assert.deepEqual(b64Len(9, 1), 13, "Test case 9"); + assert.deepEqual(b64Len(9, 2), 13, "Test case 10"); + }); + + describe("decodeBase64URL", () => { + it("Corner case: Two length strings", () => { + const input = Buffer.from("H").toString('base64url'); + assert.deepEqual(input.length, 2); + assert.deepEqual(decodeBase64URL(input, 0), 'H'); + + const input2 = Buffer.from("He").toString('base64url').slice(1); + assert.deepEqual(input2.length, 2); + assert.deepEqual(decodeBase64URL(input2, 1), 'e'); + + const input3 = Buffer.from("Hel").toString('base64url').slice(2); + assert.deepEqual(input3.length, 2); + assert.deepEqual(decodeBase64URL(input3, 2), 'l'); + }); + + it('should decode a tightly packed base64URL string with i % 4 == 0', () => { + const input = Buffer.from("Hello, world!").toString('base64url'); + const i = 0; + const expected = "Hello, world!"; + + const result = decodeBase64URL(input, i); + assert.deepEqual(result, expected); + }); + + it('should decode a tightly packed base64URL string with i % 4 == 1', () => { + const input = Buffer.from("Hello, world").toString('base64url').slice(1); + const i = 1; + const expected = 'ello, world'; + + const result = decodeBase64URL(input, i); + assert.deepEqual(result, expected); + }); + + it('should decode a tightly packed base64URL string with i % 4 == 2', () => { + const input = Buffer.from("Hello, world").toString('base64url').slice(2); + const i = 2; + const expected = 'llo, world'; + + const result = decodeBase64URL(input, i); + assert.deepEqual(result, expected); + }); + + it('should throw an error when i % 4 == 3', () => { + const input = Buffer.from("Hello, world").toString('base64url'); + + try { + decodeBase64URL(input, 3); + assert.fail(); + } catch (e) { + assert.include(e.message, "not tightly packed because i%4 = 3"); + } + }); + + it('should throw an error when (i + s.length - 1) % 4 == 0', () => { + const input = Buffer.from("Hello, world").toString('base64url').slice(1); + const i = 2; + assert.deepEqual((i + input.length - 1) % 4, 0); + try { + decodeBase64URL(input, i); + assert.fail(); + } catch (e) { + assert.include(e.message, "not tightly packed because (i + s.length - 1)%4 = 0"); + } + }); + + it("Base64url testing", () => { + // this input has a different base64 and base64url encoding + const extendedInput = ',' + 'abc/?' + '}'; + const b64 = utils.trimEndByChar(Buffer.from(extendedInput).toString('base64'), '='); + const Base64URL = Buffer.from(extendedInput).toString('base64url'); + assert.isTrue(b64 !== Base64URL); + + assert.deepEqual(decodeBase64URL(Base64URL, 0), extendedInput); + }) + }) + + describe("indicesOfB64, b64Len, b64Index", () => { + it("Crafted JWTs", () => { + const sub_claim = '"sub":45678'; + [ + '{"sub":45678,"iss":12345}', // At the start + '{"iss":12345,"sub":45678}', // At the end + '{"iss":12345,"sub":45678,"aud":"https://example.com"}' // In the middle + ].forEach(input => { + assert.isTrue(input.includes(sub_claim)); + const sub_claim_with_last_char = getExtendedClaim(input, "sub"); + assert.deepEqual(sub_claim_with_last_char.slice(0, -1), sub_claim); + assert.isTrue(sub_claim_with_last_char.slice(-1) === ',' || sub_claim_with_last_char.slice(-1) === '}'); + + jwt = Buffer.from(input).toString('base64url'); + const [start, len] = indicesOfB64(jwt, "sub"); + + const substr = jwt.slice(start, start + len); + const decoded = decodeBase64URL(substr, start % 4); + assert.deepEqual(decoded, sub_claim_with_last_char); + }); + }) + + it("Google JWT", () => { + const jwt = require('../js/testvectors/realJWTs').GOOGLE.jwt; + const payload = jwt.split('.')[1]; + const decoded_payload = Buffer.from(payload, 'base64url').toString(); + const sub_claim_with_last_char = getExtendedClaim(decoded_payload, "sub"); + assert.deepEqual(sub_claim_with_last_char, '"sub":"110463452167303598383",'); + + const [start, len] = indicesOfB64(payload, "sub"); + const substr = payload.slice(start, start + len); + const decoded = decodeBase64URL(substr, start % 4); + assert.deepEqual(decoded, sub_claim_with_last_char); + }) + + it("Twitch JWT", () => { + const jwt = require('../js/testvectors/realJWTs').TWITCH.jwt; + const payload = jwt.split('.')[1]; + const decoded_payload = Buffer.from(payload, 'base64url').toString(); + + const sub_claim_with_last_char = getExtendedClaim(decoded_payload, "sub"); + assert.deepEqual(sub_claim_with_last_char, '"sub":"904448692",'); + const [start, len] = indicesOfB64(payload, "sub"); + const substr = payload.slice(start, start + len); + const decoded = decodeBase64URL(substr, start % 4); + assert.deepEqual(decoded, sub_claim_with_last_char); + }) + + it.skip("Twitch JWT with username", () => { + const jwt = require('../testvectors/realJWTs').twitch.jwt; + const payload = jwt.split('.')[1]; + const decoded_payload = Buffer.from(payload, 'base64url').toString(); + + const username = getExtendedClaim(decoded_payload, "preferred_username"); + assert.deepEqual(username, '"preferred_username":"joyqvq"}'); + const [start2, len2] = indicesOfB64(payload, "preferred_username"); + const substr2 = payload.slice(start2, start2 + len2); + const decoded2 = decodeBase64URL(substr2, start2 % 4); + assert.deepEqual(decoded2, username); + }) + }) +}); \ No newline at end of file diff --git a/zklogin-circuits/testvectors/realJWTs.ts b/zklogin-circuits/testvectors/realJWTs.ts new file mode 100644 index 0000000000..70d5d7fc40 --- /dev/null +++ b/zklogin-circuits/testvectors/realJWTs.ts @@ -0,0 +1,82 @@ +import { JWK } from "jwk-to-pem"; + +class TestVector { + "jwt": string; + "jwk": JWK; + header?: any; + payload?: any; +}; + +const GOOGLE: TestVector = { // From extension + jwt: "eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTA0NjM0NTIxNjczMDM1OTgzODMiLCJlbWFpbCI6IndhbmdxaWFveWkuam95QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJub25jZSI6IkdDd3EyekN1cXRzYTFCaGFBYzJTRWx3VW9ZdjhqS2hFNnZzNlZtZXB1Mk0iLCJpYXQiOjE2ODMzMjMyNjksImV4cCI6MTY4MzMyNjg2OSwianRpIjoiMDEzMzA2YjY1MmY0Zjg1MjUxZTU1OGVhNGFhOWJkYWI3ZmQxNzk3ZiJ9.", + jwk: { + "e": "AQAB", + "kty": "RSA", + "n": "t0VFy4n4MGtbMWJKk5qfCY2WGBja2WSWQ2zsLziSx9p1QE0QgXtr1x85PnQYaYrAvOBiXm2mrxWnZ42MxaUUu9xyykTDxsNWHK--ufchdaqJwfqd5Ecu-tHvFkMIs2g39pmG8QfXJHKMqczKrvcHHJrpTqZuos1uhYM9gxOLVP8wTAUPNqa1caiLbsszUC7yaMO3LY1WLQST79Z8u5xttKXShXFv1CCNs8-7vQ1IB5DWQSR2um1KV4t42d31Un4-8cNiURx9HmJNJzOXbTG-vDeD6sapFf5OGDsCLO4YvzzkzTsYBIQy_p88qNX0a6AeU13enxhbasSc-ApPqlxBdQ" + }, + header: { + "alg": "RS256", + "kid": "c9afda3682ebf09eb3055c1c4bd39b751fbf8195", + "typ": "JWT" + }, + payload: { + "iss": "https://accounts.google.com", + "azp": "575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com", + "aud": "575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com", + "sub": "110463452167303598383", + "email": "wangqiaoyi.joy@gmail.com", + "email_verified": true, + "nonce": "GCwq2zCuqtsa1BhaAc2SElwUoYv8jKhE6vs6Vmepu2M", + "iat": 1683323269, + "exp": 1683326869, + "jti": "013306b652f4f85251e558ea4aa9bdab7fd1797f" + } +} + +const FACEBOOK: TestVector = { + jwt: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjhhYjM3MTc1MjdhZTQwMWRlNWRjMGRmNGY5ZjJmZTZkNjUwY2NhYWUifQ.eyJpc3MiOiJodHRwczpcL1wvd3d3LmZhY2Vib29rLmNvbSIsImF1ZCI6IjEyNDE1NTkzNjY3NTUyMTQiLCJzdWIiOiI3MDg1NjI2MTEwMDk1MjUiLCJpYXQiOjE2ODIwMDQ3OTcsImV4cCI6MTY4MjAwODM5NywianRpIjoiR3NFMy5iNTY2YmI4Mjg4ZTQ3ZTMxMjIwZDM3NzY4ZWJlMWM5NTIzOTM0YzYxZmE3ZjkzYWM3MWI3NTcxZjg5M2Q3NzZlIiwibm9uY2UiOiIxNjYzNzkxODgxMzkwODA2MDI2MTg3MDUyODkwMzk5NDAzODcyMTY2OTc5OTYxMzgwMzYwMTYxNjY3ODE1NTUxMjE4MTI3MzI4OTQ3NyIsImdpdmVuX25hbWUiOiJKb3kiLCJmYW1pbHlfbmFtZSI6IldhbmciLCJuYW1lIjoiSm95IFdhbmciLCJwaWN0dXJlIjoiaHR0cHM6XC9cL3BsYXRmb3JtLWxvb2thc2lkZS5mYnNieC5jb21cL3BsYXRmb3JtXC9wcm9maWxlcGljXC8_YXNpZD03MDg1NjI2MTEwMDk1MjUmaGVpZ2h0PTEwMCZ3aWR0aD0xMDAmZXh0PTE2ODQ1OTY3OTgmaGFzaD1BZVJJZ1JMX1hvb3FyZERpZE5ZIn0.taavbVRWSJYQAGVfADLb0Un1gHakURX1lbGO7wjOjRgOZxnoF_1fAOE9QoSftZPMpg4-WhYYl-sC0SxETX6rW9lULT7oNomuO8Jm0kgyxeITvi7oWK_QLt8VWJZPAM2ZP2-xEFR92juQKTnbsuAB14fl2gXKlt_QZDtAmi3Gno0By94E7bWmSPd1mQJA1M0GUu4LDNZe0_mGHEQ9ygamyQVfB9u3STTeb1HOfNKO3HXmwmTJRhdIuc_z96wWBf8-JR52d1gAL0MWL5my6yPyvqtpfti-8-jWYPUuR-KOzhdj-IsaGZMzgJUJZg7wg6z9_P2Uqn3Muh-BSzTNYxVYPQ", + jwk: { + "kty": "RSA", + "n": "xirBDhLBy2BlSheGTJx3_jWdUado6QHTD5_rZK3_26m02zCFGqkHbJJihDwwTdmpVanrw0cb_7OIlw0hZjVNPnjhPHnwy-zT1XYK7qAfVBD0wm9GJhGsAe5dWSCjee7U-uRHAijNYXBeQn5Oh2w1KCCDr6Ccgk65xk5cuMCanKtAf6yXzVnKZJyvBxSzZD4p0Bw8MSOzSXY5srvUSGuCeajV8D-IDaP4LEXWswLUJKD3DrOlKH8QHHUhnPHjXAUZId_PMttmK32TqkWJeL0sjJ2KT68QHXCgL2RDWCg_TiOM8yx_LVGpeg0mLaadw0UNVT_nfmH_1s1S9XVpEu42Xw", + "e": "AQAB" + } +} + +const GOOGLE_OLD: TestVector = { // from playground + jwt: 'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijk4NmVlOWEzYjc1MjBiNDk0ZGY1NGZlMzJlM2U1YzRjYTY4NWM4OWQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTc5MTI3MzU2NTg1NDEzMzY2NDYiLCJhdF9oYXNoIjoicm9aYm11cUVXdmNHSThDR2N1SnJmUSIsImlhdCI6MTY3OTY3NDE0NSwiZXhwIjoxNjc5Njc3NzQ1fQ.G8cciXefORmYvdwrfVAO6DjDy7DUWe6NxyanGg4w7EQBu8Ab7PJAeXhU7HL5w_LtTgiLA3Ew07RRzuNuaFITvs_m9lIolxHOl0BZSyGIGlI9BRiBFQQK2OZ2b8xetWz3B1mezcwlrrQMgbLQI0puuaA6917h_3MjIgZu_bQkjQH3Lwl3kkZWp0W-PRuK20KAQneNFB9ehTvSeRkImIr5QlZU6LMb7M3rI_-gP6ePRryAN9UCGBASzNEYLaQz-eMIdYFw-WmqkesTX1IDLQT0n44BhG9-9mWIA6kNRSBo9FV89VGKvYION9PTDds1vsf5h3smBQZjourR2H5pLJ_MUA', + jwk: { + "e": "AQAB", + "kty": "RSA", + "n": "onb-s1Mvbpti06Sp-ZsHH5eeJxdvMhRgfmx5zK7cVlcAajI_0rKu8ylU2CkfgPlMe9-8W5ayozm1h2yx2ToS7P7qoR1sMINXbKxobu8xy9zOBuFAr3WvEoor6lo0Qp747_4bN1sVU6GBEBEXLjb8vHN-o_yoBv8NSB_yP7XbEaS3U5MJ4V2s5o7LziIIRP9PtzF0m3kWm7DuyEzGvCaW8s9bOiMd3eZyXXyfKjlBB727eBXgwqcV-PttECRw6JCLO-11__lmqfKIj5CBw18Pb4ZrNwBa-XrGXfHSSAJXFkR4LR7Bj24sWzlOcKXN2Ew4h3WDJfxtN_StNSYoagyaFQ" + } +} + +const TWITCH: TestVector = { + jwt: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLCJleHAiOjE2ODMzMjQyNjgsImlhdCI6MTY4MzMyMzM2OCwiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiwic3ViIjoiOTA0NDQ4NjkyIiwiZW1haWwiOiJ3YW5nam95am95MjAxOUBnbWFpbC5jb20iLCJub25jZSI6IkdDd3EyekN1cXRzYTFCaGFBYzJTRWx3VW9ZdjhqS2hFNnZzNlZtZXB1Mk0ifQ.", + jwk: { + "e": "AQAB", + "kty": "RSA", + "n": "6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw" + }, + header: { + "alg": "RS256", + "typ": "JWT", + "kid": "1" + }, + payload: { + "aud": "d31icql6l8xzpa7ef31ztxyss46ock", + "exp": 1683324268, + "iat": 1683323368, + "iss": "https://id.twitch.tv/oauth2", + "sub": "904448692", + "email": "wangjoyjoy2019@gmail.com", + "nonce": "GCwq2zCuqtsa1BhaAc2SElwUoYv8jKhE6vs6Vmepu2M" + } +} + +export { + GOOGLE, + GOOGLE_OLD, + FACEBOOK, + TWITCH +} \ No newline at end of file diff --git a/zklogin-circuits/testvectors/sampleWalletInputs.json b/zklogin-circuits/testvectors/sampleWalletInputs.json new file mode 100644 index 0000000000..d09ee3e10a --- /dev/null +++ b/zklogin-circuits/testvectors/sampleWalletInputs.json @@ -0,0 +1,8 @@ +{ + "jwt": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTA0NjM0NTIxNjczMDM1OTgzODMiLCJlbWFpbCI6IndhbmdxaWFveWkuam95QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJub25jZSI6IkdDd3EyekN1cXRzYTFCaGFBYzJTRWx3VW9ZdjhqS2hFNnZzNlZtZXB1Mk0iLCJpYXQiOjE2ODMzMjMyNjksImV4cCI6MTY4MzMyNjg2OSwianRpIjoiMDEzMzA2YjY1MmY0Zjg1MjUxZTU1OGVhNGFhOWJkYWI3ZmQxNzk3ZiJ9.", + "eph_public_key": "6102104556984239574011716987889141562529524682626013161214470481167897974645", + "max_epoch": 10000, + "jwt_rand": "100681567828351849884072155819400689117", + "user_pin": "283089722053851751073973683904920435104", + "key_claim_name": "sub" +} diff --git a/zklogin-circuits/testvectors/sampleZKPInputs.json b/zklogin-circuits/testvectors/sampleZKPInputs.json new file mode 100644 index 0000000000..b500f78181 --- /dev/null +++ b/zklogin-circuits/testvectors/sampleZKPInputs.json @@ -0,0 +1,1683 @@ +{ + "content": [ + "101", + "121", + "74", + "104", + "98", + "71", + "99", + "105", + "79", + "105", + "74", + "83", + "85", + "122", + "73", + "49", + "78", + "105", + "73", + "115", + "73", + "109", + "116", + "112", + "90", + "67", + "73", + "54", + "73", + "109", + "77", + "53", + "89", + "87", + "90", + "107", + "89", + "84", + "77", + "50", + "79", + "68", + "74", + "108", + "89", + "109", + "89", + "119", + "79", + "87", + "86", + "105", + "77", + "122", + "65", + "49", + "78", + "87", + "77", + "120", + "89", + "122", + "82", + "105", + "90", + "68", + "77", + "53", + "89", + "106", + "99", + "49", + "77", + "87", + "90", + "105", + "90", + "106", + "103", + "120", + "79", + "84", + "85", + "105", + "76", + "67", + "74", + "48", + "101", + "88", + "65", + "105", + "79", + "105", + "74", + "75", + "86", + "49", + "81", + "105", + "102", + "81", + "46", + "101", + "121", + "74", + "112", + "99", + "51", + "77", + "105", + "79", + "105", + "74", + "111", + "100", + "72", + "82", + "119", + "99", + "122", + "111", + "118", + "76", + "50", + "70", + "106", + "89", + "50", + "57", + "49", + "98", + "110", + "82", + "122", + "76", + "109", + "100", + "118", + "98", + "50", + "100", + "115", + "90", + "83", + "53", + "106", + "98", + "50", + "48", + "105", + "76", + "67", + "74", + "104", + "101", + "110", + "65", + "105", + "79", + "105", + "73", + "49", + "78", + "122", + "85", + "49", + "77", + "84", + "107", + "121", + "77", + "68", + "81", + "121", + "77", + "122", + "99", + "116", + "98", + "88", + "78", + "118", + "99", + "68", + "108", + "108", + "99", + "68", + "81", + "49", + "100", + "84", + "74", + "49", + "98", + "122", + "107", + "52", + "97", + "71", + "70", + "119", + "99", + "87", + "49", + "117", + "90", + "51", + "89", + "52", + "90", + "68", + "103", + "48", + "99", + "87", + "82", + "106", + "79", + "71", + "115", + "117", + "89", + "88", + "66", + "119", + "99", + "121", + "53", + "110", + "98", + "50", + "57", + "110", + "98", + "71", + "86", + "49", + "99", + "50", + "86", + "121", + "89", + "50", + "57", + "117", + "100", + "71", + "86", + "117", + "100", + "67", + "53", + "106", + "98", + "50", + "48", + "105", + "76", + "67", + "74", + "104", + "100", + "87", + "81", + "105", + "79", + "105", + "73", + "49", + "78", + "122", + "85", + "49", + "77", + "84", + "107", + "121", + "77", + "68", + "81", + "121", + "77", + "122", + "99", + "116", + "98", + "88", + "78", + "118", + "99", + "68", + "108", + "108", + "99", + "68", + "81", + "49", + "100", + "84", + "74", + "49", + "98", + "122", + "107", + "52", + "97", + "71", + "70", + "119", + "99", + "87", + "49", + "117", + "90", + "51", + "89", + "52", + "90", + "68", + "103", + "48", + "99", + "87", + "82", + "106", + "79", + "71", + "115", + "117", + "89", + "88", + "66", + "119", + "99", + "121", + "53", + "110", + "98", + "50", + "57", + "110", + "98", + "71", + "86", + "49", + "99", + "50", + "86", + "121", + "89", + "50", + "57", + "117", + "100", + "71", + "86", + "117", + "100", + "67", + "53", + "106", + "98", + "50", + "48", + "105", + "76", + "67", + "74", + "122", + "100", + "87", + "73", + "105", + "79", + "105", + "73", + "120", + "77", + "84", + "65", + "48", + "78", + "106", + "77", + "48", + "78", + "84", + "73", + "120", + "78", + "106", + "99", + "122", + "77", + "68", + "77", + "49", + "79", + "84", + "103", + "122", + "79", + "68", + "77", + "105", + "76", + "67", + "74", + "108", + "98", + "87", + "70", + "112", + "98", + "67", + "73", + "54", + "73", + "110", + "100", + "104", + "98", + "109", + "100", + "120", + "97", + "87", + "70", + "118", + "101", + "87", + "107", + "117", + "97", + "109", + "57", + "53", + "81", + "71", + "100", + "116", + "89", + "87", + "108", + "115", + "76", + "109", + "78", + "118", + "98", + "83", + "73", + "115", + "73", + "109", + "86", + "116", + "89", + "87", + "108", + "115", + "88", + "51", + "90", + "108", + "99", + "109", + "108", + "109", + "97", + "87", + "86", + "107", + "73", + "106", + "112", + "48", + "99", + "110", + "86", + "108", + "76", + "67", + "74", + "117", + "98", + "50", + "53", + "106", + "90", + "83", + "73", + "54", + "73", + "107", + "100", + "68", + "100", + "51", + "69", + "121", + "101", + "107", + "78", + "49", + "99", + "88", + "82", + "122", + "89", + "84", + "70", + "67", + "97", + "71", + "70", + "66", + "89", + "122", + "74", + "84", + "82", + "87", + "120", + "51", + "86", + "87", + "57", + "90", + "100", + "106", + "104", + "113", + "83", + "50", + "104", + "70", + "78", + "110", + "90", + "122", + "78", + "108", + "90", + "116", + "90", + "88", + "66", + "49", + "77", + "107", + "48", + "105", + "76", + "67", + "74", + "112", + "89", + "88", + "81", + "105", + "79", + "106", + "69", + "50", + "79", + "68", + "77", + "122", + "77", + "106", + "77", + "121", + "78", + "106", + "107", + "115", + "73", + "109", + "86", + "52", + "99", + "67", + "73", + "54", + "77", + "84", + "89", + "52", + "77", + "122", + "77", + "121", + "78", + "106", + "103", + "50", + "79", + "83", + "119", + "105", + "97", + "110", + "82", + "112", + "73", + "106", + "111", + "105", + "77", + "68", + "69", + "122", + "77", + "122", + "65", + "50", + "89", + "106", + "89", + "49", + "77", + "109", + "89", + "48", + "90", + "106", + "103", + "49", + "77", + "106", + "85", + "120", + "90", + "84", + "85", + "49", + "79", + "71", + "86", + "104", + "78", + "71", + "70", + "104", + "79", + "87", + "74", + "107", + "89", + "87", + "73", + "51", + "90", + "109", + "81", + "120", + "78", + "122", + "107", + "51", + "90", + "105", + "74", + "57", + "128", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "20", + "216", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + "num_sha2_blocks": 11, + "payload_start_index": 103, + "payload_len": 564, + "extended_key_claim": [ + 34, + 115, + 117, + 98, + 34, + 58, + 34, + 49, + 49, + 48, + 52, + 54, + 51, + 52, + 53, + 50, + 49, + 54, + 55, + 51, + 48, + 51, + 53, + 57, + 56, + 51, + 56, + 51, + 34, + 44, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "claim_length_ascii": 30, + "claim_index_b64": 368, + "claim_length_b64": 41, + "subject_pin": "283089722053851751073973683904920435104", + "key_claim_name_length": 3, + "mask": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "extended_nonce": [ + 34, + 110, + 111, + 110, + 99, + 101, + 34, + 58, + 34, + 71, + 67, + 119, + 113, + 50, + 122, + 67, + 117, + 113, + 116, + 115, + 97, + 49, + 66, + 104, + 97, + 65, + 99, + 50, + 83, + 69, + 108, + 119, + 85, + 111, + 89, + 118, + 56, + 106, + 75, + 104, + 69, + 54, + 118, + 115, + 54, + 86, + 109, + 101, + 112, + 117, + 50, + 77, + 34, + 44 + ], + "nonce_claim_index_b64": 484, + "nonce_length_b64": 73, + "eph_public_key": [ + "17932473587154777519561053972421347139", + "134696963602902907403122104327765350261" + ], + "max_epoch": 10000, + "jwt_randomness": "100681567828351849884072155819400689117", + "all_inputs_hash": "2487117669597822357956926047501254969190518860900347921480370492048882803688" +} \ No newline at end of file diff --git a/zklogin-circuits/tsconfig.json b/zklogin-circuits/tsconfig.json new file mode 100644 index 0000000000..d0a441aebd --- /dev/null +++ b/zklogin-circuits/tsconfig.json @@ -0,0 +1,110 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./js", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": ["src/**/*"] +}