diff --git a/README.md b/README.md index aaf8d5069..3c3dc198e 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ Everything we write is MIT licensed. Note that circom and circomlib is GPL. Broa - warning[CA02]: In template "Base64Decode(32)": Subcomponent input/output signal bits_out[10][2].out does not appear in any constraint of the father component - warning[CA01]: In template "TwitterResetRegex(1536)": Local signal states[1536][0] does not appear in any constraint - warning[CA02]: In template "EmailVerify(1024,1536,121,17,7)": Subcomponent input/output signal dkim_header_regex.reveal[0] does not appear in any constraint of the father component - - warning[CA02]: In template "RSAVerify65537(121,17)": Array of subcomponent input/output signals signatureRangeCheck[13].out contains a total of 121 signals that do not appear in any constraint of the father component + - warning[CA02]: In template "RSAVerifier65537(121,17)": Array of subcomponent input/output signals signatureRangeCheck[13].out contains a total of 121 signals that do not appear in any constraint of the father component = For example: signatureRangeCheck[13].out[0], signatureRangeCheck[13].out[100]. - warning[CA02]: In template "LessThan(8)": Array of subcomponent input/output signals n2b.out contains a total of 8 signals that do not appear in any constraint of the father component = For example: n2b.out[0], n2b.out[1]. diff --git a/packages/circuits/helpers/sha.circom b/packages/circuits/helpers/sha.circom deleted file mode 100644 index df61a768f..000000000 --- a/packages/circuits/helpers/sha.circom +++ /dev/null @@ -1,104 +0,0 @@ -pragma circom 2.1.5; - -include "circomlib/circuits/bitify.circom"; -include "./sha256general.circom"; -include "./sha256partial.circom"; - -template Sha256Bytes(max_num_bytes) { - signal input in_padded[max_num_bytes]; - signal input in_len_padded_bytes; - signal output out[256]; - - var num_bits = max_num_bytes * 8; - component sha = Sha256General(num_bits); - - component bytes[max_num_bytes]; - for (var i = 0; i < max_num_bytes; i++) { - bytes[i] = Num2Bits(8); - bytes[i].in <== in_padded[i]; - for (var j = 0; j < 8; j++) { - sha.paddedIn[i*8+j] <== bytes[i].out[7-j]; - } - } - sha.in_len_padded_bits <== in_len_padded_bytes * 8; - - for (var i = 0; i < 256; i++) { - out[i] <== sha.out[i]; - } -} - -template Sha256BytesPartial(max_num_bytes) { - assert(max_num_bytes % 32 == 0); - signal input in_padded[max_num_bytes]; - signal input in_len_padded_bytes; - signal input pre_hash[32]; - signal output out[256]; - - var num_bits = max_num_bytes * 8; - component sha = Sha256Partial(num_bits); - - component bytes[max_num_bytes]; - for (var i = 0; i < max_num_bytes; i++) { - bytes[i] = Num2Bits(8); - bytes[i].in <== in_padded[i]; - for (var j = 0; j < 8; j++) { - sha.paddedIn[i*8+j] <== bytes[i].out[7-j]; - } - } - sha.in_len_padded_bits <== in_len_padded_bytes * 8; - - component states[32]; - for (var i = 0; i < 32; i++) { - states[i] = Num2Bits(8); - states[i].in <== pre_hash[i]; - for (var j = 0; j < 8; j++) { - sha.pre_state[8*i+j] <== states[i].out[7-j]; - } - } - - for (var i = 0; i < 256; i++) { - out[i] <== sha.out[i]; - } -} - -// Takes in 2^(8 * 31)-sized integers, not bytes, to save calldata. n is usually 31. -// max_num_n_bytes is the number of n-byte size inputs we have. expected to be max_num_bytes / (n + 1) -// template Sha256NBytes(max_num_n_bytes, n) { -// assert(1 << log_ceil(max_num_n_bytes) == max_num_n_bytes); // max_num_n_bytes is a power of 2 -// assert(1 << log_ceil(n+1) == n+1); // n+1 is a power of 2 - -// // assert(1 << log_ceil(in_len_padded_bytes) == in_len_padded_bytes); // in_len_padded_bytes is a power of 2 -// // assert(in_len_padded_bytes <= max_num_n_bytes * n) -// signal input in_padded[max_num_n_bytes]; -// signal input in_len_padded_bytes; // Keep this in bytes for now. Can make n_bytes later. -// signal output out[256]; - -// var num_bits = max_num_n_bytes * 8 * (n + 1); // makes it a power of 2, though we waste 3% of constraints -// assert(1 << log_ceil(num_bits) == num_bits); // num_bits is a power of 2 - -// component sha = Sha256General(num_bits); - -// component n_bytes[max_num_n_bytes]; -// for (var i = 0; i < max_num_n_bytes; i++) { -// n_bytes[i] = Num2Bits(8 * n); -// n_bytes[i].in <== in_padded[i]; -// for (var k = 0; k < n; k++){ -// for (var j = 0; j < 8; j++) { -// sha.paddedIn[i * 8 * n + k * 8 + j] <== n_bytes[i].out[k * 8 + 7 - j]; // Big/little endian handled here -// } -// } -// } -// for (var j = 0; j < 8; j++) { -// sha.paddedIn[max_num_n_bytes * 8 * n + j] <== 0; -// } - -// sha.in_len_padded_bits <== in_len_padded_bytes * 8; - -// for (var i = 0; i < 256; i++) { -// out[i] <== sha.out[i]; -// } -// } - -// component main { public [ in_padded, in_len_padded_bytes ] } = Sha256Bytes(448); -// Note that Sha256NBytes is an unnecesary optimization. -// component main { public [ in_padded, in_len_padded_bytes ] } = Sha256NBytes(64, 31); diff --git a/packages/circuits/helpers/sha256general.circom b/packages/circuits/helpers/sha256general.circom deleted file mode 100644 index 6790ed454..000000000 --- a/packages/circuits/helpers/sha256general.circom +++ /dev/null @@ -1,123 +0,0 @@ -pragma circom 2.1.5; - -include "circomlib/circuits/sha256/constants.circom"; -include "circomlib/circuits/sha256/sha256compression.circom"; -include "circomlib/circuits/comparators.circom"; -include "./utils.circom"; - -// 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. -template Sha256General(maxBitsPadded) { - // maxBitsPadded 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. - assert(maxBitsPadded % 512 == 0); - var maxBitsPaddedBits = log2_ceil(maxBitsPadded); - assert(2 ** maxBitsPaddedBits > maxBitsPadded); - - // Note that maxBitsPadded = maxBits + 64 - signal input paddedIn[maxBitsPadded]; - signal output out[256]; - signal input in_len_padded_bits; // This is the padded length of the message pre-hash. - signal inBlockIndex; - - var i; - var k; - var j; - var maxBlocks; - var bitsLastBlock; - maxBlocks = (maxBitsPadded\512); - var maxBlocksBits = log2_ceil(maxBlocks); - assert(2 ** maxBlocksBits > maxBlocks); - - inBlockIndex <-- (in_len_padded_bits >> 9); - in_len_padded_bits === inBlockIndex * 512; - - // These verify the unconstrained floor calculation is the uniquely correct integer that represents the floor - // component floorVerifierUnder = LessEqThan(maxBitsPaddedBits); // todo verify the length passed in is less than nbits. note that maxBitsPaddedBits can likely be lowered or made it a fn of maxbits - // floorVerifierUnder.in[0] <== (inBlockIndex)*512; - // floorVerifierUnder.in[1] <== in_len_padded_bits; - // floorVerifierUnder.out === 1; - - // component floorVerifierOver = GreaterThan(maxBitsPaddedBits); - // floorVerifierOver.in[0] <== (inBlockIndex+1)*512; - // floorVerifierOver.in[1] <== in_len_padded_bits; - // floorVerifierOver.out === 1; - - // These verify we pass in a valid number of bits to the SHA256 compression circuit. - component bitLengthVerifier = LessEqThan(maxBitsPaddedBits); // todo verify the length passed in is less than nbits. note that maxBitsPaddedBits can likely be lowered or made it a fn of maxbits - bitLengthVerifier.in[0] <== in_len_padded_bits; - bitLengthVerifier.in[1] <== maxBitsPadded; - bitLengthVerifier.out === 1; - - // Note that we can no longer do padded verification efficiently inside the SHA because it requires non deterministic array indexing. - // We can do it if we add a constraint, but since guessing a valid SHA2 preimage is hard anyways, we'll just do it outside the circuit. - - // signal paddedIn[maxBlocks*512]; - // for (k=0; k> k)&1; - // } - - 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[maxBlocks]; - - for (i=0; i maxBitsPadded); - - // Note that maxBitsPadded = maxBits + 64 - signal input paddedIn[maxBitsPadded]; - signal input pre_state[256]; - signal output out[256]; - signal input in_len_padded_bits; // This is the padded length of the message pre-hash. - - signal inBlockIndex; - - var i; - var k; - var j; - var maxBlocks; - var bitsLastBlock; - maxBlocks = (maxBitsPadded\512); - var maxBlocksBits = log2_ceil(maxBlocks); - assert(2 ** maxBlocksBits > maxBlocks); - - inBlockIndex <-- (in_len_padded_bits >> 9); - in_len_padded_bits === inBlockIndex * 512; - - // These verify we pass in a valid number of bits to the SHA256 compression circuit. - component bitLengthVerifier = LessEqThan(maxBitsPaddedBits); // todo verify the length passed in is less than nbits. note that maxBitsPaddedBits can likely be lowered or made it a fn of maxbits - bitLengthVerifier.in[0] <== in_len_padded_bits; - bitLengthVerifier.in[1] <== maxBitsPadded; - bitLengthVerifier.out === 1; - - 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[maxBlocks]; - - for (i=0; i0) { - r++; - n \= 2; - } - return r; -} // returns ceil(log2(a+1)) function count_packed(n, chunks) { diff --git a/packages/circuits/lib/sha.circom b/packages/circuits/lib/sha.circom new file mode 100644 index 000000000..d912524ba --- /dev/null +++ b/packages/circuits/lib/sha.circom @@ -0,0 +1,313 @@ +pragma circom 2.1.5; + +include "circomlib/circuits/bitify.circom"; +include "circomlib/circuits/sha256/constants.circom"; +include "circomlib/circuits/sha256/sha256compression.circom"; +include "circomlib/circuits/comparators.circom"; +include "./fp.circom"; +include "../helpers/utils.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 +template Sha256Bytes(maxByteLength) { + signal input paddedIn[maxByteLength]; + signal input paddedInLength; + signal output out[256]; + + var maxBits = maxByteLength * 8; + component sha = Sha256General(maxBits); + + component bytes[maxByteLength]; + for (var i = 0; i < maxByteLength; i++) { + bytes[i] = Num2Bits(8); + bytes[i].in <== paddedIn[i]; + for (var j = 0; j < 8; j++) { + sha.paddedIn[i*8+j] <== bytes[i].out[7-j]; + } + } + sha.paddedInLength <== paddedInLength * 8; + + for (var i = 0; i < 256; i++) { + out[i] <== sha.out[i]; + } +} + + +/// @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 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]; + signal output out[256]; + + var maxBits = maxByteLength * 8; + component sha = Sha256Partial(maxBits); + + component bytes[maxByteLength]; + for (var i = 0; i < maxByteLength; i++) { + bytes[i] = Num2Bits(8); + bytes[i].in <== paddedIn[i]; + for (var j = 0; j < 8; j++) { + sha.paddedIn[i*8+j] <== bytes[i].out[7-j]; + } + } + sha.paddedInLength <== paddedInLength * 8; + + component states[32]; + for (var i = 0; i < 32; i++) { + states[i] = Num2Bits(8); + states[i].in <== preHash[i]; + for (var j = 0; j < 8; j++) { + sha.preHash[8*i+j] <== states[i].out[7-j]; + } + } + + for (var i = 0; i < 256; i++) { + out[i] <== sha.out[i]; + } +} + + +/// @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 +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. + assert(maxBitLength % 512 == 0); + var maxBitsPaddedBits = log2Ceil(maxBitLength); + assert(2 ** maxBitsPaddedBits > maxBitLength); + + + // Note that maxBitLength = maxBits + 64 + signal input paddedIn[maxBitLength]; + signal input paddedInLength; + + signal output out[256]; + + signal inBlockIndex; + + var i; + var k; + var j; + var maxBlocks; + var bitsLastBlock; + maxBlocks = (maxBitLength\512); + var maxBlocksBits = log2Ceil(maxBlocks); + assert(2 ** maxBlocksBits > maxBlocks); + + inBlockIndex <-- (paddedInLength >> 9); + paddedInLength === inBlockIndex * 512; + + // These verify the unconstrained floor calculation is the uniquely correct integer that represents the floor + // component floorVerifierUnder = LessEqThan(maxBitsPaddedBits); // todo verify the length passed in is less than nbits. note that maxBitsPaddedBits can likely be lowered or made it a fn of maxbits + // floorVerifierUnder.in[0] <== (inBlockIndex)*512; + // floorVerifierUnder.in[1] <== paddedInLength; + // floorVerifierUnder.out === 1; + + // component floorVerifierOver = GreaterThan(maxBitsPaddedBits); + // floorVerifierOver.in[0] <== (inBlockIndex+1)*512; + // floorVerifierOver.in[1] <== paddedInLength; + // floorVerifierOver.out === 1; + + // These verify we pass in a valid number of bits to the SHA256 compression circuit. + component bitLengthVerifier = LessEqThan(maxBitsPaddedBits); // todo verify the length passed in is less than nbits. note that maxBitsPaddedBits can likely be lowered or made it a fn of maxbits + bitLengthVerifier.in[0] <== paddedInLength; + bitLengthVerifier.in[1] <== maxBitLength; + bitLengthVerifier.out === 1; + + // Note that we can no longer do padded verification efficiently inside the SHA because it requires non deterministic array indexing. + // We can do it if we add a constraint, but since guessing a valid SHA2 preimage is hard anyways, we'll just do it outside the circuit. + + // signal paddedIn[maxBlocks*512]; + // for (k=0; k> k)&1; + // } + + 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[maxBlocks]; + + for (i=0; i maxBitLength); + + // Note that maxBitLength = maxBits + 64 + signal input paddedIn[maxBitLength]; + signal input paddedInLength; + signal input preHash[256]; + + signal output out[256]; + + signal inBlockIndex; + + var i; + var k; + var j; + var maxBlocks; + var bitsLastBlock; + maxBlocks = (maxBitLength\512); + var maxBlocksBits = log2Ceil(maxBlocks); + assert(2 ** maxBlocksBits > maxBlocks); + + inBlockIndex <-- (paddedInLength >> 9); + paddedInLength === inBlockIndex * 512; + + // These verify we pass in a valid number of bits to the SHA256 compression circuit. + component bitLengthVerifier = LessEqThan(maxBitsPaddedBits); // todo verify the length passed in is less than nbits. note that maxBitsPaddedBits can likely be lowered or made it a fn of maxbits + bitLengthVerifier.in[0] <== paddedInLength; + bitLengthVerifier.in[1] <== maxBitLength; + bitLengthVerifier.out === 1; + + 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[maxBlocks]; + + for (i=0; i0) { + r++; + n \= 2; + } + return r; +} diff --git a/packages/circuits/tests/sha256.test.ts b/packages/circuits/tests/sha.test.ts similarity index 88% rename from packages/circuits/tests/sha256.test.ts rename to packages/circuits/tests/sha.test.ts index f29c4cef1..5aaf31963 100644 --- a/packages/circuits/tests/sha256.test.ts +++ b/packages/circuits/tests/sha.test.ts @@ -15,7 +15,7 @@ describe("SHA256 for email header", () => { beforeAll(async () => { circuit = await wasm_tester( - path.join(__dirname, "./test-circuits/sha256-test.circom"), + path.join(__dirname, "./test-circuits/sha-test.circom"), { recompile: true, include: path.join(__dirname, "../../../node_modules"), @@ -37,8 +37,8 @@ describe("SHA256 for email header", () => { ) const witness = await circuit.calculateWitness({ - in_len_padded_bytes: messageLen, - in_padded: Uint8ArrayToCharArray(paddedMsg) + paddedIn: Uint8ArrayToCharArray(paddedMsg), + paddedInLength: messageLen, }); await circuit.checkConstraints(witness); diff --git a/packages/circuits/tests/test-circuits/sha-test.circom b/packages/circuits/tests/test-circuits/sha-test.circom new file mode 100644 index 000000000..5f70554b8 --- /dev/null +++ b/packages/circuits/tests/test-circuits/sha-test.circom @@ -0,0 +1,5 @@ +pragma circom 2.1.5; + +include "../../lib/sha.circom"; + +component main { public [paddedIn, paddedInLength] } = Sha256Bytes(640); diff --git a/packages/circuits/tests/test-circuits/sha256-test.circom b/packages/circuits/tests/test-circuits/sha256-test.circom deleted file mode 100644 index bd8beee31..000000000 --- a/packages/circuits/tests/test-circuits/sha256-test.circom +++ /dev/null @@ -1,5 +0,0 @@ -pragma circom 2.1.5; - -include "../helpers/sha.circom"; - -component main { public [in_padded, in_len_padded_bytes] } = Sha256Bytes(640); \ No newline at end of file