diff --git a/packages/circuits/README.md b/packages/circuits/README.md index 6367a74b8..232f94a6a 100644 --- a/packages/circuits/README.md +++ b/packages/circuits/README.md @@ -125,7 +125,9 @@ Base64Decode: Decodes a base64 encoded string into binary data. ## Utils -This section provides an overview of utility circom templates available in the `@zk-email/circuits/utils` directory. These templates assist in the construction of zk circuits for various applications beyond the core ZK Email functionalities. +This section provides an overview of utility circom templates available in the `@zk-email/circuits/utils` directory. These templates assist in the construction of ZK circuits for various applications beyond the core ZK Email functionalities. + +> Important: When using these templates outside of zk-email, please ensure you read the assumptions on the input signals that are documented above each template source code. You would need to constrain the inputs accordingly before you pass them to these utility circuits. ### `utils/array.circom` @@ -276,14 +278,14 @@ Constants: Defines a set of constants used across various circom circuits for st
-log2Ceil: Calculates the ceiling of the base 2 logarithm of a given number. +log2Ceil: Calculate log2 of a number and round it up - **[Source](utils/functions.circom#L2-L10)** - **Inputs**: - - `a`: The input number for which the base 2 logarithm ceiling is to be calculated. + - `a`: The input number for which the `ceil(log2())` needs to be calculated. - **Outputs**: - - Returns the smallest integer greater than or equal to the base 2 logarithm of the input number. + - Returns `ceil(log2())` of the input number.
diff --git a/packages/circuits/email-verifier.circom b/packages/circuits/email-verifier.circom index 970bfb33a..f6388434b 100644 --- a/packages/circuits/email-verifier.circom +++ b/packages/circuits/email-verifier.circom @@ -44,8 +44,13 @@ template EmailVerifier(maxHeadersLength, maxBodyLength, n, k, ignoreBodyHashChec signal output pubkeyHash; - // Assert emailHeader data after emailHeaderLength are zeros - AssertZeroPadding(maxHeadersLength)(emailHeader, emailHeaderLength + 1); + // Assert `emailHeaderLength` fits in `ceil(log2(maxHeadersLength))` + component n2bHeaderLength = Num2Bits(log2Ceil(maxHeadersLength)); + n2bHeaderLength.in <== emailHeaderLength; + + + // Assert `emailHeader` data after `emailHeaderLength` are zeros + AssertZeroPadding(maxHeadersLength)(emailHeader, emailHeaderLength); // Calculate SHA256 hash of the `emailHeader` - 506,670 constraints @@ -84,8 +89,15 @@ template EmailVerifier(maxHeadersLength, maxBodyLength, n, k, ignoreBodyHashChec signal input emailBody[maxBodyLength]; signal input emailBodyLength; - // Assert data after the body (maxBodyLength - emailBody.length) is all zeroes - AssertZeroPadding(maxBodyLength)(emailBody, emailBodyLength + 1); + + // Assert `emailBodyLength` fits in `ceil(log2(maxBodyLength))` + component n2bBodyLength = Num2Bits(log2Ceil(maxBodyLength)); + n2bBodyLength.in <== emailBodyLength; + + + // Assert data after the body (`maxBodyLength - emailBody.length`) is all zeroes + AssertZeroPadding(maxBodyLength)(emailBody, emailBodyLength); + // Body hash regex - 617,597 constraints // Extract the body hash from the header (i.e. the part after bh= within the DKIM-signature section) @@ -96,8 +108,7 @@ template EmailVerifier(maxHeadersLength, maxBodyLength, n, k, ignoreBodyHashChec signal bhBase64[shaB64Length] <== SelectRegexReveal(maxHeadersLength, shaB64Length)(bhReveal, bodyHashIndex); signal headerBodyHash[32] <== Base64Decode(32)(bhBase64); - - // Compute SHA256 of email body : 760,142 constraints + // Compute SHA256 of email body : 760,142 constraints (for maxBodyLength = 1536) // We are using a technique to save constraints by precomputing the SHA hash of the body till the area we want to extract // It doesn't have an impact on security since a user must have known the pre-image of a signed message to be able to fake it signal computedBodyHash[256] <== Sha256BytesPartial(maxBodyLength)(emailBody, emailBodyLength, precomputedSHA); diff --git a/packages/circuits/lib/base64.circom b/packages/circuits/lib/base64.circom index 4ee0cfde1..a7a31ebda 100644 --- a/packages/circuits/lib/base64.circom +++ b/packages/circuits/lib/base64.circom @@ -5,8 +5,11 @@ include "circomlib/circuits/comparators.circom"; /// @title Base64Decode /// @notice Decodes a Base64 encoded string to array of bytes. +/// @notice Only support inputs with length = `byteLength` (no 0 padding). +/// @notice It is known that padding char '=' can be replaed with `A` to produce the same output +/// as Base64Lookup returns `0` for both, but a pracical attack from this is unlikely. /// @param byteLength Byte length of the encoded value - length of the output array. -/// @input in Base64 encoded string. +/// @input in Base64 encoded string; assumes elements to be valid Base64 characters. /// @output out Decoded array of bytes. template Base64Decode(byteLength) { var charLength = 4 * ((byteLength + 2) \ 3); // 4 chars encode 3 bytes @@ -63,7 +66,7 @@ template Base64Decode(byteLength) { /// @title Base64Lookup /// @notice http://0x80.pl/notesen/2016-01-17-sse-base64-decoding.html#vector-lookup-base -/// @input in input character. +/// @input in input character; assumes input to be valid Base64 character (though constrained implicitly). /// @output out output bit value. template Base64Lookup() { signal input in; diff --git a/packages/circuits/lib/bigint-func.circom b/packages/circuits/lib/bigint-func.circom index 89df69257..bcd0d09bb 100644 --- a/packages/circuits/lib/bigint-func.circom +++ b/packages/circuits/lib/bigint-func.circom @@ -1,9 +1,5 @@ pragma circom 2.1.6; -function isNegative(x) { - // half babyjubjub field size - return x > 10944121435919637611123202872628637544274182200208017171849102093287904247808 ? 1 : 0; -} function div_ceil(m, n) { var ret = 0; @@ -26,14 +22,6 @@ function log_ceil(n) { return 254; } -function SplitFn(in, n, m) { - return [in % (1 << n), (in \ (1 << n)) % (1 << m)]; -} - -function SplitThreeFn(in, n, m, k) { - return [in % (1 << n), (in \ (1 << n)) % (1 << m), (in \ (1 << n + m)) % (1 << k)]; -} - // m bits per overflowed register (values are potentially negative) // n bits per properly-sized register // in has k registers @@ -188,7 +176,7 @@ function long_div(n, k, m, a, b){ } m -= k; - var remainder[200]; + var remainder[100]; for (var i = 0; i < m + k; i++) { remainder[i] = a[i]; } @@ -262,9 +250,9 @@ function short_div(n, k, a, b) { var scale = (1 << n) \ (1 + b[k - 1]); // k + 2 registers now - var norm_a[200] = long_scalar_mult(n, k + 1, scale, a); + var norm_a[100] = long_scalar_mult(n, k + 1, scale, a); // k + 1 registers now - var norm_b[200] = long_scalar_mult(n, k, scale, b); + var norm_b[100] = long_scalar_mult(n, k, scale, b); var ret; if (norm_b[k] != 0) { @@ -274,169 +262,3 @@ function short_div(n, k, a, b) { } return ret; } - -// n bits per register -// a and b both have k registers -// out[0] has length 2 * k -// adapted from BigMulShortLong and LongToShortNoEndCarry2 witness computation -function prod(n, k, a, b) { - // first compute the intermediate values. taken from BigMulShortLong - var prod_val[100]; // length is 2 * k - 1 - for (var i = 0; i < 2 * k - 1; i++) { - prod_val[i] = 0; - if (i < k) { - for (var a_idx = 0; a_idx <= i; a_idx++) { - prod_val[i] = prod_val[i] + a[a_idx] * b[i - a_idx]; - } - } else { - for (var a_idx = i - k + 1; a_idx < k; a_idx++) { - prod_val[i] = prod_val[i] + a[a_idx] * b[i - a_idx]; - } - } - } - - // now do a bunch of carrying to make sure registers not overflowed. taken from LongToShortNoEndCarry2 - var out[100]; // length is 2 * k - - var split[100][3]; // first dimension has length 2 * k - 1 - for (var i = 0; i < 2 * k - 1; i++) { - split[i] = SplitThreeFn(prod_val[i], n, n, n); - } - - var carry[100]; // length is 2 * k - 1 - carry[0] = 0; - out[0] = split[0][0]; - if (2 * k - 1 > 1) { - var sumAndCarry[2] = SplitFn(split[0][1] + split[1][0], n, n); - out[1] = sumAndCarry[0]; - carry[1] = sumAndCarry[1]; - } - if (2 * k - 1 > 2) { - for (var i = 2; i < 2 * k - 1; i++) { - var sumAndCarry[2] = SplitFn(split[i][0] + split[i-1][1] + split[i-2][2] + carry[i-1], n, n); - out[i] = sumAndCarry[0]; - carry[i] = sumAndCarry[1]; - } - out[2 * k - 1] = split[2*k-2][1] + split[2*k-3][2] + carry[2*k-2]; - } - return out; -} - -// n bits per register -// a has k registers -// p has k registers -// e has k registers -// k * n <= 500 -// p is a prime -// computes a^e mod p -function mod_exp(n, k, a, p, e) { - var eBits[500]; // length is k * n - for (var i = 0; i < k; i++) { - for (var j = 0; j < n; j++) { - eBits[j + n * i] = (e[i] >> j) & 1; - } - } - - var out[100]; // length is k - for (var i = 0; i < 100; i++) { - out[i] = 0; - } - out[0] = 1; - - // repeated squaring - for (var i = k * n - 1; i >= 0; i--) { - // multiply by a if bit is 0 - if (eBits[i] == 1) { - var temp[200]; // length 2 * k - temp = prod(n, k, out, a); - var temp2[2][100]; - temp2 = long_div(n, k, k, temp, p); - out = temp2[1]; - } - - // square, unless we're at the end - if (i > 0) { - var temp[200]; // length 2 * k - temp = prod(n, k, out, out); - var temp2[2][100]; - temp2 = long_div(n, k, k, temp, p); - out = temp2[1]; - } - - } - return out; -} - -// n bits per register -// a has k registers -// p has k registers -// k * n <= 500 -// p is a prime -// if a == 0 mod p, returns 0 -// else computes inv = a^(p-2) mod p -function mod_inv(n, k, a, p) { - var isZero = 1; - for (var i = 0; i < k; i++) { - if (a[i] != 0) { - isZero = 0; - } - } - if (isZero == 1) { - var ret[100]; - for (var i = 0; i < k; i++) { - ret[i] = 0; - } - return ret; - } - - var pCopy[100]; - for (var i = 0; i < 100; i++) { - if (i < k) { - pCopy[i] = p[i]; - } else { - pCopy[i] = 0; - } - } - - var two[100]; - for (var i = 0; i < 100; i++) { - two[i] = 0; - } - two[0] = 2; - - var pMinusTwo[100]; - pMinusTwo = long_sub(n, k, pCopy, two); // length k - var out[100]; - out = mod_exp(n, k, a, pCopy, pMinusTwo); - return out; -} - -// a, b and out are all n bits k registers -function long_sub_mod_p(n, k, a, b, p){ - var gt = long_gt(n, k, a, b); - var tmp[100]; - if(gt){ - tmp = long_sub(n, k, a, b); - } - else{ - tmp = long_sub(n, k, b, a); - } - var out[2][100]; - for(var i = k;i < 2 * k; i++){ - tmp[i] = 0; - } - out = long_div(n, k, k, tmp, p); - if(gt==0){ - tmp = long_sub(n, k, p, out[1]); - } - return tmp; -} - -// a, b, p and out are all n bits k registers -function prod_mod_p(n, k, a, b, p){ - var tmp[100]; - var result[2][100]; - tmp = prod(n, k, a, b); - result = long_div(n, k, k, tmp, p); - return result[1]; -} diff --git a/packages/circuits/lib/bigint.circom b/packages/circuits/lib/bigint.circom index 2e63a73b3..61bd91c3e 100644 --- a/packages/circuits/lib/bigint.circom +++ b/packages/circuits/lib/bigint.circom @@ -6,295 +6,13 @@ include "circomlib/circuits/gates.circom"; include "./bigint-func.circom"; -// addition mod 2**n with carry bit -template ModSum(n) { - assert(n <= 252); - signal input a; - signal input b; - signal output sum; - signal output carry; - - component n2b = Num2Bits(n + 1); - n2b.in <== a + b; - carry <== n2b.out[n]; - sum <== a + b - carry * (1 << n); -} - -// a - b -template ModSub(n) { - assert(n <= 252); - signal input a; - signal input b; - signal output out; - signal output borrow; - component lt = LessThan(n); - lt.in[0] <== a; - lt.in[1] <== b; - borrow <== lt.out; - out <== borrow * (1 << n) + a - b; -} - -// a - b - c -// assume a - b - c + 2**n >= 0 -template ModSubThree(n) { - assert(n + 2 <= 253); - signal input a; - signal input b; - signal input c; - assert(a - b - c + (1 << n) >= 0); - signal output out; - signal output borrow; - signal b_plus_c; - b_plus_c <== b + c; - component lt = LessThan(n + 1); - lt.in[0] <== a; - lt.in[1] <== b_plus_c; - borrow <== lt.out; - out <== borrow * (1 << n) + a - b_plus_c; -} - -template ModSumThree(n) { - assert(n + 2 <= 253); - signal input a; - signal input b; - signal input c; - signal output sum; - signal output carry; - - component n2b = Num2Bits(n + 2); - n2b.in <== a + b + c; - carry <== n2b.out[n] + 2 * n2b.out[n + 1]; - sum <== a + b + c - carry * (1 << n); -} - -template ModSumFour(n) { - assert(n + 2 <= 253); - signal input a; - signal input b; - signal input c; - signal input d; - signal output sum; - signal output carry; - - component n2b = Num2Bits(n + 2); - n2b.in <== a + b + c + d; - carry <== n2b.out[n] + 2 * n2b.out[n + 1]; - sum <== a + b + c + d - carry * (1 << n); -} - -// product mod 2**n with carry -template ModProd(n) { - assert(n <= 126); - signal input a; - signal input b; - signal output prod; - signal output carry; - - component n2b = Num2Bits(2 * n); - n2b.in <== a * b; - - component b2n1 = Bits2Num(n); - component b2n2 = Bits2Num(n); - var i; - for (i = 0; i < n; i++) { - b2n1.in[i] <== n2b.out[i]; - b2n2.in[i] <== n2b.out[i + n]; - } - prod <== b2n1.out; - carry <== b2n2.out; -} - -// split a n + m bit input into two outputs -template Split(n, m) { - assert(n <= 126); - signal input in; - signal output small; - signal output big; - - small <-- in % (1 << n); - big <-- in \ (1 << n); - - component n2b_small = Num2Bits(n); - n2b_small.in <== small; - component n2b_big = Num2Bits(m); - n2b_big.in <== big; - - in === small + big * (1 << n); -} - -// split a n + m + k bit input into three outputs -template SplitThree(n, m, k) { - assert(n <= 126); - signal input in; - signal output small; - signal output medium; - signal output big; - - small <-- in % (1 << n); - medium <-- (in \ (1 << n)) % (1 << m); - big <-- in \ (1 << n + m); - - component n2b_small = Num2Bits(n); - n2b_small.in <== small; - component n2b_medium = Num2Bits(m); - n2b_medium.in <== medium; - component n2b_big = Num2Bits(k); - n2b_big.in <== big; - - in === small + medium * (1 << n) + big * (1 << n + m); -} - -// a[i], b[i] in 0... 2**n-1 -// represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) -template BigAdd(n, k) { - assert(n <= 252); - signal input a[k]; - signal input b[k]; - signal output out[k + 1]; - - component unit0 = ModSum(n); - unit0.a <== a[0]; - unit0.b <== b[0]; - out[0] <== unit0.sum; - - component unit[k - 1]; - for (var i = 1; i < k; i++) { - unit[i - 1] = ModSumThree(n); - unit[i - 1].a <== a[i]; - unit[i - 1].b <== b[i]; - if (i == 1) { - unit[i - 1].c <== unit0.carry; - } else { - unit[i - 1].c <== unit[i - 2].carry; - } - out[i] <== unit[i - 1].sum; - } - out[k] <== unit[k - 2].carry; -} - -// a and b have n-bit registers -// a has ka registers, each with NONNEGATIVE ma-bit values (ma can be > n) -// b has kb registers, each with NONNEGATIVE mb-bit values (mb can be > n) -// out has ka + kb - 1 registers, each with (ma + mb + ceil(log(max(ka, kb))))-bit values -template BigMultNoCarry(n, ma, mb, ka, kb) { - assert(ma + mb <= 253); - signal input a[ka]; - signal input b[kb]; - signal output out[ka + kb - 1]; - - var prod_val[ka + kb - 1]; - for (var i = 0; i < ka + kb - 1; i++) { - prod_val[i] = 0; - } - for (var i = 0; i < ka; i++) { - for (var j = 0; j < kb; j++) { - prod_val[i + j] += a[i] * b[j]; - } - } - for (var i = 0; i < ka + kb - 1; i++) { - out[i] <-- prod_val[i]; - } - - var a_poly[ka + kb - 1]; - var b_poly[ka + kb - 1]; - var out_poly[ka + kb - 1]; - for (var i = 0; i < ka + kb - 1; i++) { - out_poly[i] = 0; - a_poly[i] = 0; - b_poly[i] = 0; - for (var j = 0; j < ka + kb - 1; j++) { - out_poly[i] = out_poly[i] + out[j] * (i ** j); - } - for (var j = 0; j < ka; j++) { - a_poly[i] = a_poly[i] + a[j] * (i ** j); - } - for (var j = 0; j < kb; j++) { - b_poly[i] = b_poly[i] + b[j] * (i ** j); - } - } - for (var i = 0; i < ka + kb - 1; i++) { - out_poly[i] === a_poly[i] * b_poly[i]; - } -} - - -// in[i] contains longs -// out[i] contains shorts -template LongToShortNoEndCarry(n, k) { - assert(n <= 126); - signal input in[k]; - signal output out[k+1]; - - var split[k][3]; - for (var i = 0; i < k; i++) { - split[i] = SplitThreeFn(in[i], n, n, n); - } - - var carry[k]; - carry[0] = 0; - out[0] <-- split[0][0]; - if (k == 1) { - out[1] <-- split[0][1]; - } - if (k > 1) { - var sumAndCarry[2] = SplitFn(split[0][1] + split[1][0], n, n); - out[1] <-- sumAndCarry[0]; - carry[1] = sumAndCarry[1]; - } - if (k == 2) { - out[2] <-- split[1][1] + split[0][2] + carry[1]; - } - if (k > 2) { - for (var i = 2; i < k; i++) { - var sumAndCarry[2] = SplitFn(split[i][0] + split[i-1][1] + split[i-2][2] + carry[i-1], n, n); - out[i] <-- sumAndCarry[0]; - carry[i] = sumAndCarry[1]; - } - out[k] <-- split[k-1][1] + split[k-2][2] + carry[k-1]; - } - - component outRangeChecks[k+1]; - for (var i = 0; i < k+1; i++) { - outRangeChecks[i] = Num2Bits(n); - outRangeChecks[i].in <== out[i]; - } - - signal runningCarry[k]; - component runningCarryRangeChecks[k]; - runningCarry[0] <-- (in[0] - out[0]) / (1 << n); - runningCarryRangeChecks[0] = Num2Bits(n + log_ceil(k)); - runningCarryRangeChecks[0].in <== runningCarry[0]; - runningCarry[0] * (1 << n) === in[0] - out[0]; - for (var i = 1; i < k; i++) { - runningCarry[i] <-- (in[i] - out[i] + runningCarry[i-1]) / (1 << n); - runningCarryRangeChecks[i] = Num2Bits(n + log_ceil(k)); - runningCarryRangeChecks[i].in <== runningCarry[i]; - runningCarry[i] * (1 << n) === in[i] - out[i] + runningCarry[i-1]; - } - runningCarry[k-1] === out[k]; -} - -template BigMult(n, k) { - signal input a[k]; - signal input b[k]; - signal output out[2 * k]; - - component mult = BigMultNoCarry(n, n, n, k, k); - for (var i = 0; i < k; i++) { - mult.a[i] <== a[i]; - mult.b[i] <== b[i]; - } - - // no carry is possible in the highest order register - component longshort = LongToShortNoEndCarry(n, 2 * k - 1); - for (var i = 0; i < 2 * k - 1; i++) { - longshort.in[i] <== mult.out[i]; - } - for (var i = 0; i < 2 * k; i++) { - out[i] <== longshort.out[i]; - } -} - +/// @template BigLessThan +/// @notice Less than comparison for big integers +/// @param n The number of bits in each chunk +/// @param k The number of chunks +/// @param a The first bigint; assumes to consist of `k` chunks, each of which must fit in `n` bits +/// @param b The second bigint; assumes to consist of `k` chunks, each of which must fit in `n` bits +/// @param out The output of the comparison template BigLessThan(n, k){ signal input a[k]; signal input b[k]; @@ -341,203 +59,13 @@ template BigLessThan(n, k){ out <== ors[0].out; } -template BigIsEqual(k){ - signal input in[2][k]; - signal output out; - component isEqual[k+1]; - var sum = 0; - for(var i = 0; i < k; i++){ - isEqual[i] = IsEqual(); - isEqual[i].in[0] <== in[0][i]; - isEqual[i].in[1] <== in[1][i]; - sum = sum + isEqual[i].out; - } - - isEqual[k] = IsEqual(); - isEqual[k].in[0] <== sum; - isEqual[k].in[1] <== k; - out <== isEqual[k].out; -} - -// leading register of b should be non-zero -template BigMod(n, k) { - assert(n <= 126); - signal input a[2 * k]; - signal input b[k]; - - signal output div[k + 1]; - signal output mod[k]; - - var longdiv[2][100] = long_div(n, k, k, a, b); - for (var i = 0; i < k; i++) { - div[i] <-- longdiv[0][i]; - mod[i] <-- longdiv[1][i]; - } - div[k] <-- longdiv[0][k]; - component div_range_checks[k + 1]; - for (var i = 0; i <= k; i++) { - div_range_checks[i] = Num2Bits(n); - div_range_checks[i].in <== div[i]; - } - component mod_range_checks[k]; - for (var i = 0; i < k; i++) { - mod_range_checks[i] = Num2Bits(n); - mod_range_checks[i].in <== mod[i]; - } - - component mul = BigMult(n, k + 1); - for (var i = 0; i < k; i++) { - mul.a[i] <== div[i]; - mul.b[i] <== b[i]; - } - mul.a[k] <== div[k]; - mul.b[k] <== 0; - - component add = BigAdd(n, 2 * k + 2); - for (var i = 0; i < 2 * k; i++) { - add.a[i] <== mul.out[i]; - if (i < k) { - add.b[i] <== mod[i]; - } else { - add.b[i] <== 0; - } - } - add.a[2 * k] <== mul.out[2 * k]; - add.a[2 * k + 1] <== mul.out[2 * k + 1]; - add.b[2 * k] <== 0; - add.b[2 * k + 1] <== 0; - - for (var i = 0; i < 2 * k; i++) { - add.out[i] === a[i]; - } - add.out[2 * k] === 0; - add.out[2 * k + 1] === 0; - - component lt = BigLessThan(n, k); - for (var i = 0; i < k; i++) { - lt.a[i] <== mod[i]; - lt.b[i] <== b[i]; - } - lt.out === 1; -} - -// a[i], b[i] in 0... 2**n-1 -// represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) -// assume a >= b -template BigSub(n, k) { - assert(n <= 252); - signal input a[k]; - signal input b[k]; - signal output out[k]; - signal output underflow; - - component unit0 = ModSub(n); - unit0.a <== a[0]; - unit0.b <== b[0]; - out[0] <== unit0.out; - - component unit[k - 1]; - for (var i = 1; i < k; i++) { - unit[i - 1] = ModSubThree(n); - unit[i - 1].a <== a[i]; - unit[i - 1].b <== b[i]; - if (i == 1) { - unit[i - 1].c <== unit0.borrow; - } else { - unit[i - 1].c <== unit[i - 2].borrow; - } - out[i] <== unit[i - 1].out; - } - underflow <== unit[k - 2].borrow; -} - -// calculates (a - b) % p, where a, b < p -// note: does not assume a >= b -template BigSubModP(n, k){ - assert(n <= 252); - signal input a[k]; - signal input b[k]; - signal input p[k]; - signal output out[k]; - component sub = BigSub(n, k); - for (var i = 0; i < k; i++){ - sub.a[i] <== a[i]; - sub.b[i] <== b[i]; - } - signal flag; - flag <== sub.underflow; - component add = BigAdd(n, k); - for (var i = 0; i < k; i++){ - add.a[i] <== sub.out[i]; - add.b[i] <== flag * p[i]; - } - for (var i = 0; i < k; i++){ - out[i] <== add.out[i]; - } -} - -template BigMultModP(n, k) { - assert(n <= 252); - signal input a[k]; - signal input b[k]; - signal input p[k]; - signal output out[k]; - - component big_mult = BigMult(n, k); - for (var i = 0; i < k; i++) { - big_mult.a[i] <== a[i]; - big_mult.b[i] <== b[i]; - } - component big_mod = BigMod(n, k); - for (var i = 0; i < 2 * k; i++) { - big_mod.a[i] <== big_mult.out[i]; - } - for (var i = 0; i < k; i++) { - big_mod.b[i] <== p[i]; - } - for (var i = 0; i < k; i++) { - out[i] <== big_mod.mod[i]; - } -} - -template BigModInv(n, k) { - assert(n <= 252); - signal input in[k]; - signal input p[k]; - signal output out[k]; - - // length k - var inv[100] = mod_inv(n, k, in, p); - for (var i = 0; i < k; i++) { - out[i] <-- inv[i]; - } - component range_checks[k]; - for (var i = 0; i < k; i++) { - range_checks[i] = Num2Bits(n); - range_checks[i].in <== out[i]; - } - - component mult = BigMult(n, k); - for (var i = 0; i < k; i++) { - mult.a[i] <== in[i]; - mult.b[i] <== out[i]; - } - component mod = BigMod(n, k); - for (var i = 0; i < 2 * k; i++) { - mod.a[i] <== mult.out[i]; - } - for (var i = 0; i < k; i++) { - mod.b[i] <== p[i]; - } - mod.mod[0] === 1; - for (var i = 1; i < k; i++) { - mod.mod[i] === 0; - } -} -// in[i] contains values in the range -2^(m-1) to 2^(m-1) -// constrain that in[] as a big integer is zero -// each limbs is n bits +/// @template CheckCarryToZero +/// @notice Check that in[] as a big integer is zero +/// @param n The number of bits in each chunk +/// @param m +/// @param k The number of chunks +/// @input in The input big integer; assumes elements to be in the range -2^(m-1) to 2^(m-1) template CheckCarryToZero(n, m, k) { assert(k >= 2); diff --git a/packages/circuits/lib/fp.circom b/packages/circuits/lib/fp.circom index 3b96bc647..e6059a2e7 100644 --- a/packages/circuits/lib/fp.circom +++ b/packages/circuits/lib/fp.circom @@ -6,25 +6,16 @@ include "circomlib/circuits/sign.circom"; include "./bigint.circom"; include "./bigint-func.circom"; -// These functions operate over values in Z/Zp for some integer p (typically, -// but not necessarily prime). Values are stored as standard bignums with k -// chunks of n bits, but intermediate values often have "overflow" bits inside -// various chunks. -// -// These Fp functions will always correctly generate witnesses mod p, but they -// do not *check* that values are normalized to < p; they only check that -// values are correct mod p. This is to save the comparison circuit. -// They *will* always check for intended results mod p (soundness), but it may -// not have a unique intermediate signal. -// -// Conversely, some templates may not be satisfiable if the input witnesses are -// not < p. This does not break completeness, as honest provers will always -// generate witnesses which are canonical (between 0 and p). -// a * b = r mod p -// a * b - p * q - r for some q +/// @title FpMul +/// @notice Multiple two numbers in Fp +/// @param a Input 1 to FpMul; assumes to consist of `k` chunks, each of which must fit in `n` bits +/// @param b Input 2 to FpMul; assumes to consist of `k` chunks, each of which must fit in `n` bits +/// @param p The modulus; assumes to consist of `k` chunks, each of which must fit in `n` bits +/// @output out The result of the FpMul template FpMul(n, k) { assert(n + n + log_ceil(k) + 2 <= 252); + signal input a[k]; signal input b[k]; signal input p[k]; @@ -40,7 +31,7 @@ template FpMul(n, k) { var ab[200] = poly_interp(2*k-1, v_ab); // ab_proper has length 2*k - var ab_proper[200] = getProperRepresentation(n + n + log_ceil(k), n, 2*k-1, ab); + var ab_proper[100] = getProperRepresentation(n + n + log_ceil(k), n, 2*k-1, ab); var long_div_out[2][100] = long_div(n, k, k, ab_proper, p); @@ -83,62 +74,3 @@ template FpMul(n, k) { out[i] <== r[i]; } } - -// Lifted from https://sourcegraph.com/github.com/darkforest-eth/circuits/-/blob/range_proof/circuit.circom -// NB: RangeProof is inclusive. -// input: field element, whose abs is claimed to be less than max_abs_value -// output: none -// we also want something like 4 * (abs(in) + max_abs_value) < 2 ** bits -// and bits << 256 -// NB: RangeProof is inclusive. -// input: field element, whose abs is claimed to be <= than max_abs_value -// output: none -// also checks that both max and abs(in) are expressible in `bits` bits -template RangeProof(bits) { - signal input in; - signal input max_abs_value; - - /* check that both max and abs(in) are expressible in `bits` bits */ - component n2b1 = Num2Bits(bits+1); - n2b1.in <== in + (1 << bits); - component n2b2 = Num2Bits(bits); - n2b2.in <== max_abs_value; - - /* check that in + max is between 0 and 2*max */ - component lowerBound = LessThan(bits+1); - component upperBound = LessThan(bits+1); - - lowerBound.in[0] <== max_abs_value + in; - lowerBound.in[1] <== 0; - lowerBound.out === 0; - - upperBound.in[0] <== 2 * max_abs_value; - upperBound.in[1] <== max_abs_value + in; - upperBound.out === 0; -} - -// input: n field elements, whose abs are claimed to be less than max_abs_value -// output: none -template MultiRangeProof(n, bits) { - signal input in[n]; - signal input max_abs_value; - component rangeProofs[n]; - - for (var i = 0; i < n; i++) { - rangeProofs[i] = RangeProof(bits); - rangeProofs[i].in <== in[i]; - rangeProofs[i].max_abs_value <== max_abs_value; - } -} - -template IsNegative(){ - signal input in; - signal output out; - component n2b = Num2Bits(254); - component sign = Sign(); - in ==> n2b.in; - for (var i = 0; i<254; i++) { - n2b.out[i] ==> sign.in[i]; - } - sign.sign ==> out; -} diff --git a/packages/circuits/lib/rsa.circom b/packages/circuits/lib/rsa.circom index 48e2eec45..f82e487b9 100644 --- a/packages/circuits/lib/rsa.circom +++ b/packages/circuits/lib/rsa.circom @@ -7,9 +7,9 @@ include "./fp.circom"; /// @notice Verifies an RSA signature with exponent 65537. /// @param n Number of bits per chunk the modulus is split into. Recommended to be 121. /// @param k Number of chunks the modulus is split into. Recommended to be 17. -/// @input message The message that was signed. -/// @input signature[k] The signature to verify. -/// @input modulus[k] The modulus of the RSA key (pubkey). +/// @input message[k] The message that was signed; assumes to consist of `k` chunks that fit in `n` bits (also constrained implicitly). +/// @input signature[k] The signature to verify; assumes to consist of `k` chunks that fit in `n` bits (also constrained implicitly). +/// @input modulus[k] The modulus of the RSA key (pubkey); assumes to consist of `k` chunks that fit in `n` bits (also constrained implicitly). template RSAVerifier65537(n, k) { signal input message[k]; signal input signature[k]; @@ -51,7 +51,9 @@ template RSAVerifier65537(n, k) { /// @dev Does not necessarily reduce fully mod modulus (the answer could be too big by a multiple of modulus) /// @param n Number of bits per chunk the modulus is split into. /// @param k Number of chunks the modulus is split into. -/// @input base The base to exponentiate. +/// @input base The base to exponentiate; assumes to consist of `k` chunks, each of which must fit in `n` bits +/// @input modulus The modulus; assumes to consist of `k` chunks, each of which must fit in `n` bits +/// @output out The result of the exponentiation. template FpPow65537Mod(n, k) { signal input base[k]; signal input modulus[k]; @@ -162,7 +164,8 @@ template RSAPad(n, k) { } // The RFC guarantees at least 8 octets of 0xff padding. - assert(baseLen + 8 + 65 <= n*k); + assert(baseLen + 8 + 65 <= n * k); + for (var i = baseLen + 8; i < baseLen + 8 + 65; i++) { paddedMessageBits[i] === 1; } diff --git a/packages/circuits/lib/sha.circom b/packages/circuits/lib/sha.circom index af9fadd56..a2bf4ef2e 100644 --- a/packages/circuits/lib/sha.circom +++ b/packages/circuits/lib/sha.circom @@ -11,9 +11,9 @@ include "../utils/functions.circom"; /// @title Sha256Bytes /// @notice Computes the SHA256 hash of input bytes -/// @input paddedIn: Message to hash padded as per the SHA256 specification -/// @input paddedInLength: Length of the message in bytes including padding -/// @output out: The 256-bit hash of the input message +/// @input paddedIn Message to hash, padded as per the SHA256 specification; assumes to consist of bytes +/// @input paddedInLength Length of the padded message; assumes to be in `ceil(log2(8 * maxByteLength))` bits +/// @output out The 256-bit hash of the input message template Sha256Bytes(maxByteLength) { signal input paddedIn[maxByteLength]; signal input paddedInLength; @@ -40,12 +40,13 @@ template Sha256Bytes(maxByteLength) { /// @title Sha256BytesPartial /// @notice Computes the SHA256 hash of input bytes with a precomputed state -/// @input paddedIn Message to hash padded as per the SHA256 specification -/// @input paddedInLength Length of the message in bytes including padding +/// @input paddedIn Message to hash padded as per the SHA256 specification; assumes to consist of bytes +/// @input paddedInLength Length of the padded message; assumes to be in `ceil(log2(8 * maxByteLength))` bits /// @input preHash The precomputed state of the hash /// @output out SHA hash the input message with the precomputed state template Sha256BytesPartial(maxByteLength) { assert(maxByteLength % 32 == 0); + signal input paddedIn[maxByteLength]; signal input paddedInLength; signal input preHash[32]; @@ -82,15 +83,15 @@ template Sha256BytesPartial(maxByteLength) { /// @title Sha256General /// @notice A modified version of the SHA256 circuit that allows specified length messages up to a /// max to all work via array indexing on the SHA256 compression circuit. -/// @input paddedIn: Message to hash padded as per the SHA256 specification -/// @input paddedInLength: Length of the message in bits including padding -/// @output out: The 256-bit hash of the input message +/// @input paddedIn Message to hash padded as per the SHA256 specification; assumes to consist of bits +/// @input paddedInLength Length of the padded message; assumes to be in `ceil(log2(maxBitLength))` bits +/// @output out The 256-bit hash of the input message template Sha256General(maxBitLength) { - // maxBitLength must be a multiple of 512, and the bit circuits in this file are limited to 15 so must be raised if the message is longer. + // maxBitLength must be a multiple of 512 + // the bit circuits in this file are limited to 15 so must be raised if the message is longer. assert(maxBitLength % 512 == 0); - var maxBitsPaddedBits = log2Ceil(maxBitLength); - assert(2 ** maxBitsPaddedBits > maxBitLength); + var maxBitsPaddedBits = log2Ceil(maxBitLength); // Note that maxBitLength = maxBits + 64 signal input paddedIn[maxBitLength]; @@ -204,15 +205,16 @@ template Sha256General(maxBitLength) { /// @title Sha256Partial /// @notice Calculates the SHA256 hash of a message with a precomputed state -/// @input paddedIn: Message to hash padded as per the SHA256 specification -/// @input paddedInLength: Length of the message in bits including padding -/// @input preHash: The precomputed state of the hash -/// @output out: The 256-bit hash of the input message +/// @input paddedIn Message to hash padded as per the SHA256 specification; assumes to consist of bits +/// @input paddedInLength Length of the padded message; assumes to be in `ceil(log2(maxBitLength))` bits +/// @input preHash The precomputed state of the hash; assumes to consist of bits +/// @output out The 256-bit hash of the input message template Sha256Partial(maxBitLength) { - // maxBitLength must be a multiple of 512, and the bit circuits in this file are limited to 15 so must be raised if the message is longer. + // maxBitLength must be a multiple of 512 + // the bit circuits in this file are limited to 15 so must be raised if the message is longer. assert(maxBitLength % 512 == 0); + var maxBitsPaddedBits = log2Ceil(maxBitLength); - assert(2 ** maxBitsPaddedBits > maxBitLength); // Note that maxBitLength = maxBits + 64 signal input paddedIn[maxBitLength]; diff --git a/packages/circuits/utils/array.circom b/packages/circuits/utils/array.circom index 042234b06..d743aaa95 100644 --- a/packages/circuits/utils/array.circom +++ b/packages/circuits/utils/array.circom @@ -7,27 +7,20 @@ include "./functions.circom"; /// @title ItemAtIndex /// @notice Select item at given index from the input array -/// @notice This is QuinSelector from MACI https://github.com/privacy-scaling-explorations/maci/ +/// @notice This template that the index is valid +/// @notice This is a modified version of QuinSelector from MACI https://github.com/privacy-scaling-explorations/maci/ /// @param maxArrayLen The number of elements in the array /// @input in The input array /// @input index The index of the element to select /// @output out The selected element template ItemAtIndex(maxArrayLen) { - var bitLength = log2Ceil(maxArrayLen); - assert(2 ** bitLength > maxArrayLen); - signal input in[maxArrayLen]; signal input index; signal output out; - // Ensure that index < maxArrayLen - component lessThan = LessThan(bitLength); - lessThan.in[0] <== index; - lessThan.in[1] <== maxArrayLen; - lessThan.out === 1; - - component calcTotal = CalculateTotal(maxArrayLen); + component calcTotalValue = CalculateTotal(maxArrayLen); + component calcTotalIndex = CalculateTotal(maxArrayLen); component eqs[maxArrayLen]; // For each item, check whether its index equals the input index. @@ -36,20 +29,24 @@ template ItemAtIndex(maxArrayLen) { eqs[i].in[0] <== i; eqs[i].in[1] <== index; - // eqs[i].out is 1 if the index matches. As such, at most one input to - // calcTotal is not 0. - calcTotal.nums[i] <== eqs[i].out * in[i]; + // eqs[i].out is 1 if the index matches - so calcTotal is sum of 0s + 1 * valueAtIndex + calcTotalValue.nums[i] <== eqs[i].out * in[i]; + + // Take the sum of all eqs[i].out and assert that it is at most 1. + calcTotalIndex.nums[i] <== eqs[i].out; } - // Returns 0 + 0 + ... + item - out <== calcTotal.sum; + // Assert that the sum of eqs[i].out is 1. This is to ensure the index passed is valid. + calcTotalIndex.sum === 1; + + out <== calcTotalValue.sum; } /// @title CalculateTotal /// @notice Calculate the sum of an array /// @param n The number of elements in the array -/// @input nums The input array +/// @input nums The input array; assumes elements are small enough that their sum does not overflow the field /// @output sum The sum of the input array template CalculateTotal(n) { signal input nums[n]; @@ -70,12 +67,12 @@ template CalculateTotal(n) { /// @title SelectSubArray /// @notice Select sub array from an array given a `startIndex` and `length` /// @notice This is same as `VarShiftLeft` but with elements after `length` set to zero -/// @notice This is not used in ZK-Email circuits anywhere +/// @notice This is not used in core ZK-Email circuits at the moment /// @param maxArrayLen: the maximum number of bytes in the input array /// @param maxSubArrayLen: the maximum number of integers in the output array -/// @input in: the input byte array -/// @input startIndex: the start index of the sub array -/// @input length: the length of the sub array +/// @input in: the input array +/// @input startIndex: the start index of the sub array; assumes a valid index +/// @input length: the length of the sub array; assumes to fit in `ceil(log2(maxArrayLen))` bits /// @output out: array of `maxSubArrayLen` size, items starting from `startIndex`, and items after `length` set to zero template SelectSubArray(maxArrayLen, maxSubArrayLen) { assert(maxSubArrayLen < maxArrayLen); @@ -86,8 +83,6 @@ template SelectSubArray(maxArrayLen, maxSubArrayLen) { signal output out[maxSubArrayLen]; - assert(length <= maxSubArrayLen); - component shifter = VarShiftLeft(maxArrayLen, maxSubArrayLen); shifter.in <== in; shifter.shift <== startIndex; @@ -114,10 +109,10 @@ template SelectSubArray(maxArrayLen, maxSubArrayLen) { /// @input shift The number of indices to shift the array to the left /// @output out hifted subarray template VarShiftLeft(maxArrayLen, maxOutArrayLen) { - var bitLength = log2Ceil(maxArrayLen); - assert(2 ** bitLength > maxArrayLen); assert(maxOutArrayLen <= maxArrayLen); + var bitLength = log2Ceil(maxArrayLen); + signal input in[maxArrayLen]; signal input shift; @@ -149,17 +144,14 @@ template VarShiftLeft(maxArrayLen, maxOutArrayLen) { /// @title AssertZeroPadding /// @notice Assert that the input array is zero-padded from the given `startIndex` /// @param maxArrayLen The maximum number of elements in the input array -/// @input in The input array -/// @input startIndex The index from which the array should be zero-padded +/// @input in The input array; +/// @input startIndex The index from which the elements should be 0; assumes `startIndex - 1` to fit in `ceil(log2(maxArrayLen))` bits template AssertZeroPadding(maxArrayLen) { var bitLength = log2Ceil(maxArrayLen); - assert(maxArrayLen <= (1 << bitLength)); signal input in[maxArrayLen]; signal input startIndex; - assert(startIndex < maxArrayLen); - component lessThans[maxArrayLen]; for (var i = 0; i < maxArrayLen; i++) { diff --git a/packages/circuits/utils/bytes.circom b/packages/circuits/utils/bytes.circom index e01662047..2091d076e 100644 --- a/packages/circuits/utils/bytes.circom +++ b/packages/circuits/utils/bytes.circom @@ -22,9 +22,9 @@ function computeIntChunkLength(byteLength) { /// @title PackBytes /// @notice Pack an array of bytes to numbers that fit in the field -/// @param maxBytes: the maximum number of bytes in the input array -/// @input in: the input byte array -/// @output out: the output integer array +/// @param maxBytes the maximum number of bytes in the input array +/// @input in the input byte array; assumes elements to be bytes +/// @output out the output integer array template PackBytes(maxBytes) { var packSize = MAX_BYTES_IN_FIELD(); var maxInts = computeIntChunkLength(maxBytes); @@ -63,12 +63,12 @@ template PackBytes(maxBytes) { /// @title PackByteSubArray /// @notice Select sub array from the input array and pack it to numbers that fit in the field /// @notice This is not used in ZK-Email circuits anywhere -/// @param maxArrayLen: the maximum number of elements in the input array -/// @param maxSubArrayLen: the maximum number of elements in the sub array -/// @input in: the input byte array -/// @input startIndex: the start index of the sub array -/// @input length: the length of the sub array -/// @output out: the output integer array +/// @param maxArrayLen the maximum number of elements in the input array +/// @param maxSubArrayLen the maximum number of elements in the sub array +/// @input in the input byte array; assumes elements to be bytes +/// @input startIndex the start index of the sub array; assumes to be a valid index +/// @input length the length of the sub array; assumes to fit in `ceil(log2(maxSubArrayLen))` bits +/// @output out the output integer array template PackByteSubArray(maxArrayLen, maxSubArrayLen) { assert(maxSubArrayLen < maxArrayLen); var chunkLength = computeIntChunkLength(maxSubArrayLen); @@ -79,8 +79,6 @@ template PackByteSubArray(maxArrayLen, maxSubArrayLen) { signal output out[chunkLength]; - assert(length <= maxSubArrayLen); - component SelectSubArray = SelectSubArray(maxArrayLen, maxSubArrayLen); SelectSubArray.in <== in; SelectSubArray.startIndex <== startIndex; @@ -96,9 +94,9 @@ template PackByteSubArray(maxArrayLen, maxSubArrayLen) { /// @title DigitBytesToInt /// @notice Converts a byte array representing digits to an integer /// @notice Assumes the output number fits in the field -/// @param n: the number of bytes in the input array -/// @input in: the input byte array - big-endtian digit string of `out` -/// @output out: the output integer +/// @param n The number of bytes in the input array +/// @input in The input byte array; assumes elements are between 48 and 57 (ASCII numbers) +/// @output out The output integer; assumes to fit in the field template DigitBytesToInt(n) { signal input in[n]; @@ -107,8 +105,6 @@ template DigitBytesToInt(n) { signal sums[n+1]; sums[0] <== 0; - // TODO: Should we constrain the input ASCII to be between 48 and 57? - for(var i = 0; i < n; i++) { sums[i + 1] <== 10 * sums[i] + (in[i] - 48); } diff --git a/packages/circuits/utils/functions.circom b/packages/circuits/utils/functions.circom index e2e4c5d9c..7c76024fb 100644 --- a/packages/circuits/utils/functions.circom +++ b/packages/circuits/utils/functions.circom @@ -1,10 +1,17 @@ +pragma circom 2.1.6; +/// @function log2Ceil +/// @notice Calculate log2 of a number and round it up +/// @param a The input value +/// @return The result of the log2Ceil function log2Ceil(a) { - var n = a+1; + var n = a - 1; var r = 0; - while (n>0) { + + while (n > 0) { r++; n \= 2; } + return r; } diff --git a/packages/circuits/utils/hash.circom b/packages/circuits/utils/hash.circom index c0b966746..2e5945f88 100644 --- a/packages/circuits/utils/hash.circom +++ b/packages/circuits/utils/hash.circom @@ -3,17 +3,17 @@ pragma circom 2.1.6; /// @title PoseidonLarge /// @notice Circuit to calculate Poseidon hash of inputs more than 16 -/// @notice Merges two consecutive chunks to bring size < 16 assuming ints are chunks of a large number (a + bytesPerChunk * b) +/// @notice Merges two consecutive chunks to bring size < 16 assuming ints are chunks of a large number (a + bitsPerChunk * b) /// @notice Assumes merging of two ints fit in field /// @notice Can be made more generic by taking hash with any size inputs with nesting -/// @param bytesPerChunk Number of bits in each chunk +/// @param bitsPerChunk Number of bits in each chunk /// @param chunkSize Number of chunks in input -/// @input in: Array of chunkSize elements +/// @input in: Array of chunkSize elements; assumes elements to fit in `bitsPerChunk` bits /// @output out: Poseidon hash of input where consecutive elements are merged -template PoseidonLarge(bytesPerChunk, chunkSize) { +template PoseidonLarge(bitsPerChunk, chunkSize) { assert(chunkSize > 16); // Can use regular Poseidon for smaller chunks assert(chunkSize <= 32); // We only support up to 32 chunks. i.e half should be less than 16 - assert(bytesPerChunk * 2 < 251); + assert(bitsPerChunk * 2 < 251); var halfChunkSize = chunkSize >> 1; if (chunkSize % 2 == 1) { @@ -26,10 +26,10 @@ template PoseidonLarge(bytesPerChunk, chunkSize) { signal poseidonInput[halfChunkSize]; for(var i = 0; i < halfChunkSize; i++) { - if (i == halfChunkSize - 1 && halfChunkSize % 2 == 1) { + if (i == halfChunkSize - 1 && chunkSize % 2 == 1) { poseidonInput[i] <== in[2 * i]; } else { - poseidonInput[i] <== in[2 * i] + (1 << bytesPerChunk) * in[2 * i + 1]; + poseidonInput[i] <== in[2 * i] + (1 << bitsPerChunk) * in[2 * i + 1]; } } diff --git a/packages/circuits/utils/regex.circom b/packages/circuits/utils/regex.circom index 92b924469..efddce282 100644 --- a/packages/circuits/utils/regex.circom +++ b/packages/circuits/utils/regex.circom @@ -9,8 +9,9 @@ include "./bytes.circom"; /// @notice Verifies data before and after (maxRevealLen) reveal part is zero /// @param maxArrayLen Maximum length of the input array /// @param maxRevealLen Maximum length of the reveal part -/// @input in Input array -/// @input startIndex Index of the start of the reveal part +/// @input in Input array; assumes elements to be bytes +/// @input startIndex The index from which reveal part starts; assumes a valid index, +/// and `startIndex + maxRevealLen - 1` fits in `ceil(log2((maxArrayLen))` bits. /// @output out Revealed data array template SelectRegexReveal(maxArrayLen, maxRevealLen) { signal input in[maxArrayLen]; @@ -54,8 +55,8 @@ template SelectRegexReveal(maxArrayLen, maxRevealLen) { /// @notice Packs reveal data from a regex match into int[] /// @param maxArrayLen Maximum length of the input array /// @param maxRevealLen Maximum length of the reveal part -/// @input in Input array -/// @input startIndex Index of the start of the reveal part +/// @input in Input array; assumes elements to be bytes +/// @input startIndex Index of the start of the reveal part; assumes a valid index /// @output out Packed int array template PackRegexReveal(maxArrayLen, maxRevealLen) { var chunkSize = computeIntChunkLength(maxRevealLen);