From eb5fa815c5c25adc87e7ac1f5ac09b1a0773e1dc Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 3 Dec 2024 16:35:17 +0100 Subject: [PATCH 01/25] clarify sha2 or sha3 in examples --- flake.lock | 4 ++-- src/examples/zkapps/hashing/hash.ts | 6 +++--- src/lib/provable/gadgets/sha256.ts | 7 ++++++- tests/vk-regression/plain-constraint-system.ts | 10 +++++----- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/flake.lock b/flake.lock index 79916af26b..9aaa016eb8 100644 --- a/flake.lock +++ b/flake.lock @@ -265,8 +265,8 @@ "utils": "utils" }, "locked": { - "lastModified": 1731611028, - "narHash": "sha256-sehBPO+H9mtShGb3KNhdDVHj0pogt/tmiL9tc2ICkaU=", + "lastModified": 1733157072, + "narHash": "sha256-SaQSHg6hd7tSAU0MlIU1AMPTEpZHXldihr2PzZPcN3Q=", "path": "src/mina", "type": "path" }, diff --git a/src/examples/zkapps/hashing/hash.ts b/src/examples/zkapps/hashing/hash.ts index 3dbb62cef7..dadb52e059 100644 --- a/src/examples/zkapps/hashing/hash.ts +++ b/src/examples/zkapps/hashing/hash.ts @@ -24,19 +24,19 @@ export class HashStorage extends SmartContract { this.commitment.set(initialCommitment); } - @method async SHA256(xs: Bytes32) { + @method async SHA3_256(xs: Bytes32) { const shaHash = Hash.SHA3_256.hash(xs); const commitment = Hash.hash(shaHash.toFields()); this.commitment.set(commitment); } - @method async SHA384(xs: Bytes32) { + @method async SHA3_384(xs: Bytes32) { const shaHash = Hash.SHA3_384.hash(xs); const commitment = Hash.hash(shaHash.toFields()); this.commitment.set(commitment); } - @method async SHA512(xs: Bytes32) { + @method async SHA3_512(xs: Bytes32) { const shaHash = Hash.SHA3_512.hash(xs); const commitment = Hash.hash(shaHash.toFields()); this.commitment.set(commitment); diff --git a/src/lib/provable/gadgets/sha256.ts b/src/lib/provable/gadgets/sha256.ts index 414b9b188b..b4c91122ef 100644 --- a/src/lib/provable/gadgets/sha256.ts +++ b/src/lib/provable/gadgets/sha256.ts @@ -1,4 +1,9 @@ -// https://csrc.nist.gov/pubs/fips/180-4/upd1/final +/** SHA2 + * + * This module provides a SHA2 provable gadget, including SHA256. + * + * https://csrc.nist.gov/pubs/fips/180-4/upd1/final + */ import { mod } from '../../../bindings/crypto/finite-field.js'; import { Field } from '../wrapped.js'; import { UInt32, UInt8 } from '../int.js'; diff --git a/tests/vk-regression/plain-constraint-system.ts b/tests/vk-regression/plain-constraint-system.ts index cb5375401d..500a211e99 100644 --- a/tests/vk-regression/plain-constraint-system.ts +++ b/tests/vk-regression/plain-constraint-system.ts @@ -107,24 +107,24 @@ const BitwiseCS = constraintSystem('Bitwise Primitive', { Gadgets.or(a, b, 32); Gadgets.or(a, b, 48); Gadgets.or(a, b, 64); - } + }, }); const Bytes32 = Bytes(32); const bytes32 = Bytes32.from([]); const HashCS = constraintSystem('Hashes', { - SHA256() { + SHA3_256() { let xs = Provable.witness(Bytes32, () => bytes32); Hash.SHA3_256.hash(xs); }, - SHA384() { + SHA3_384() { let xs = Provable.witness(Bytes32, () => bytes32); Hash.SHA3_384.hash(xs); }, - SHA512() { + SHA3_512() { let xs = Provable.witness(Bytes32, () => bytes32); Hash.SHA3_512.hash(xs); }, @@ -144,7 +144,7 @@ const HashCS = constraintSystem('Hashes', { BLAKE2B() { let xs = Provable.witness(Bytes32, () => bytes32); Hash.BLAKE2B.hash(xs); - } + }, }); const witness = () => Provable.witness(Field, () => Field(0)); From ff313c6ba7020926f952d8c5b41dc9750eb039b1 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 3 Dec 2024 18:35:56 +0100 Subject: [PATCH 02/25] bytes conversion for UInt64 --- src/lib/provable/int.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/lib/provable/int.ts b/src/lib/provable/int.ts index 939c0cd775..b2e20cff2e 100644 --- a/src/lib/provable/int.ts +++ b/src/lib/provable/int.ts @@ -523,6 +523,28 @@ class UInt64 extends CircuitValue { ): InstanceType { return UInt64.from(x) as any; } + + /** + * Split a UInt64 into 8 UInt8s, in big-endian order. + */ + toBytesBE() { + return TupleN.fromArray(8, wordToBytes(this.value, 8).reverse()); + } + + /** + * Combine 8 UInt8s into a UInt64, in little-endian order. + */ + static fromBytes(bytes: UInt8[]): UInt64 { + assert(bytes.length === 8, '8 bytes needed to create a uint64'); + return UInt64.Unsafe.fromField(bytesToWord(bytes)); + } + + /** + * Combine 8 UInt8s into a UInt64, in big-endian order. + */ + static fromBytesBE(bytes: UInt8[]): UInt64 { + return UInt64.fromBytes([...bytes].reverse()); + } } /** * A 32 bit unsigned integer with values ranging from 0 to 4,294,967,295. From d040fdc75f4258e427870e603836ee73c00db545 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 3 Dec 2024 18:36:12 +0100 Subject: [PATCH 03/25] constants for sha2 variants --- src/lib/provable/crypto/sha2.ts | 165 ++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 src/lib/provable/crypto/sha2.ts diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts new file mode 100644 index 0000000000..2ff2bac9e8 --- /dev/null +++ b/src/lib/provable/crypto/sha2.ts @@ -0,0 +1,165 @@ +import { mod } from '../../../bindings/crypto/finite-field.js'; +import { Field } from '../wrapped.js'; +import { UInt32, UInt64, UInt8 } from '../int.js'; +import { exists } from '../core/exists.js'; +import { FlexibleBytes } from '../bytes.js'; +import { Bytes } from '../wrapped-classes.js'; +import { chunk } from '../../util/arrays.js'; +import { TupleN } from '../../util/types.js'; +import { divMod32 } from '../gadgets/arithmetic.js'; +import { bitSlice } from '../gadgets/common.js'; +import { rangeCheck16 } from '../gadgets/range-check.js'; + +// SHA2 CONSTANTS + +// Bit length of the blocks used in SHA2-224 and SHA2-256 +const SHA2_224_256_BLOCK_LENGTH = 512n; + +// Bit length of the blocks used in SHA2-384 and SHA2-512 +const SHA2_384_512_BLOCK_LENGTH = 1024n; + +// Value used in the padding equation for SHA2-224 and SHA2-256 +const SHA2_224_256_PADDING_VALUE = 448n; + +// Value used in the padding equation for SHA2-384 and SHA2-512 +const SHA2_384_512_PADDING_VALUE = 896n; + +// Bits used to store the length in the padding of SHA2-224 and SHA2-256 +// It corresponds to 512 - 448 = 64 +const SHA2_224_256_LENGTH_CHUNK = 64; + +// Bits used to store the length in the padding of SHA2-384 and SHA2-512 +// It corresponds to 1024 - 896 = 128 +const SHA2_384_512_LENGTH_CHUNK = 128; + +const SHA2Constants = { + // constants for SHA2-224 and SHA2-256 §4.2.2 + K224_256: [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + ], + // constants for SHA2-384 and SHA2-512 §4.2.3 + K384_512: [ + 0x428a2f98d728ae22n, + 0x7137449123ef65cdn, + 0xb5c0fbcfec4d3b2fn, + 0xe9b5dba58189dbbcn, + 0x3956c25bf348b538n, + 0x59f111f1b605d019n, + 0x923f82a4af194f9bn, + 0xab1c5ed5da6d8118n, + 0xd807aa98a3030242n, + 0x12835b0145706fben, + 0x243185be4ee4b28cn, + 0x550c7dc3d5ffb4e2n, + 0x72be5d74f27b896fn, + 0x80deb1fe3b1696b1n, + 0x9bdc06a725c71235n, + 0xc19bf174cf692694n, + 0xe49b69c19ef14ad2n, + 0xefbe4786384f25e3n, + 0x0fc19dc68b8cd5b5n, + 0x240ca1cc77ac9c65n, + 0x2de92c6f592b0275n, + 0x4a7484aa6ea6e483n, + 0x5cb0a9dcbd41fbd4n, + 0x76f988da831153b5n, + 0x983e5152ee66dfabn, + 0xa831c66d2db43210n, + 0xb00327c898fb213fn, + 0xbf597fc7beef0ee4n, + 0xc6e00bf33da88fc2n, + 0xd5a79147930aa725n, + 0x06ca6351e003826fn, + 0x142929670a0e6e70n, + 0x27b70a8546d22ffcn, + 0x2e1b21385c26c926n, + 0x4d2c6dfc5ac42aedn, + 0x53380d139d95b3dfn, + 0x650a73548baf63den, + 0x766a0abb3c77b2a8n, + 0x81c2c92e47edaee6n, + 0x92722c851482353bn, + 0xa2bfe8a14cf10364n, + 0xa81a664bbc423001n, + 0xc24b8b70d0f89791n, + 0xc76c51a30654be30n, + 0xd192e819d6ef5218n, + 0xd69906245565a910n, + 0xf40e35855771202an, + 0x106aa07032bbd1b8n, + 0x19a4c116b8d2d0c8n, + 0x1e376c085141ab53n, + 0x2748774cdf8eeb99n, + 0x34b0bcb5e19b48a8n, + 0x391c0cb3c5c95a63n, + 0x4ed8aa4ae3418acbn, + 0x5b9cca4f7763e373n, + 0x682e6ff3d6b2b8a3n, + 0x748f82ee5defb2fcn, + 0x78a5636f43172f60n, + 0x84c87814a1f0ab72n, + 0x8cc702081a6439ecn, + 0x90befffa23631e28n, + 0xa4506cebde82bde9n, + 0xbef9a3f7b2c67915n, + 0xc67178f2e372532bn, + 0xca273eceea26619cn, + 0xd186b8c721c0c207n, + 0xeada7dd6cde0eb1en, + 0xf57d4f7fee6ed178n, + 0x06f067aa72176fban, + 0x0a637dc5a2c898a6n, + 0x113f9804bef90daen, + 0x1b710b35131c471bn, + 0x28db77f523047d84n, + 0x32caab7b40c72493n, + 0x3c9ebe0a15c9bebcn, + 0x431d67c49c100d4cn, + 0x4cc5d4becb3e42b6n, + 0x597f299cfc657e2an, + 0x5fcb6fab3ad6faecn, + 0x6c44198c4a475817n, + ], + // initial hash values for SHA2-224 §5.3.2 + H224: [ + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, + 0x64f98fa7, 0xbefa4fa4, + ], + // initial hash values for SHA-256 §5.3.3 + H256: [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19, + ], + // initial hash values for SHA2-384 §5.3.4 + H384: [ + 0xcbbb9d5dc1059ed8n, + 0x629a292a367cd507n, + 0x9159015a3070dd17n, + 0x152fecd8f70e5939n, + 0x67332667ffc00b31n, + 0x8eb44a8768581511n, + 0xdb0c2e0d64f98fa7n, + 0x47b5481dbefa4fa4n, + ], + // initial hash values for SHA2-512 §5.3.5 + H512: [ + 0x6a09e667f3bcc908n, + 0xbb67ae8584caa73bn, + 0x3c6ef372fe94f82bn, + 0xa54ff53a5f1d36f1n, + 0x510e527fade682d1n, + 0x9b05688c2b3e6c1fn, + 0x1f83d9abfb41bd6bn, + 0x5be0cd19137e2179n, + ], +}; From ff1ff4d4738322701ebf2b7d63ebad73763095d0 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 3 Dec 2024 18:36:43 +0100 Subject: [PATCH 04/25] preliminary sha2 interface --- src/lib/provable/crypto/sha2.ts | 67 +++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts index 2ff2bac9e8..ec75ec6a97 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/crypto/sha2.ts @@ -10,6 +10,8 @@ import { divMod32 } from '../gadgets/arithmetic.js'; import { bitSlice } from '../gadgets/common.js'; import { rangeCheck16 } from '../gadgets/range-check.js'; +export { SHA2 }; + // SHA2 CONSTANTS // Bit length of the blocks used in SHA2-224 and SHA2-256 @@ -163,3 +165,68 @@ const SHA2Constants = { 0x5be0cd19137e2179n, ], }; + +const SHA2 = { + /** + * Implementation of [NIST SHA-2](https://csrc.nist.gov/pubs/fips/180-4/upd1/final) + * hash Function. Supports output lengths of 224, 256, 384, or 512 bits. + * + * Applies the SHA-3 hash function to a list of big-endian byte-sized {@link Field} + * elements, flexible to handle varying output lengths (224, 256, 384, 512 bits) as specified. + * + * The function accepts {@link Bytes} as the input message, which is a type that + * represents a static-length list of byte-sized field elements (range-checked + * using {@link Gadgets.rangeCheck8}). + * Alternatively, you can pass plain `number[]` of `Uint8Array` to perform a hash + * outside provable code. + * + * Produces an output of {@link Bytes} that conforms to the chosen bit length. + * Both input and output bytes are big-endian. + * + * @param len - Desired output length in bits. Valid options: 224, 256, 384, 512. + * @param message - Big-endian {@link Bytes} representing the message to hash. + * + * ```ts + * let preimage = Bytes.fromString("hello world"); + * let digest224 = SHA2.hash(224, preimage); + * let digest256 = SHA2.hash(256, preimage); + * let digest384 = SHA2.hash(384, preimage); + * let digest512 = SHA2.hash(512, preimage); + * ``` + * + */ + hash(data: FlexibleBytes) { + // preprocessing §6.2 + // padding the message $5.1.1 into blocks that are a multiple of 512 + let messageBlocks = padding(SHA2.length, data); + + let H = SHA2.initialState; + const N = messageBlocks.length; + + for (let i = 0; i < N; i++) { + const W = createMessageSchedule(messageBlocks[i]); + H = sha256Compression(H, W); + } + + // the working variables H[i] are 32bit, however we want to decompose them into bytes to be more compatible + return Bytes.from(H.map((x) => x.toBytesBE()).flat()); + }, + length: 224 | 256 | 384 | 512, + compression: sha256Compression, + createMessageSchedule, + padding, + get initialState() { + switch (SHA2.length) { + case 224: + return SHA2Constants.H224.map((x) => UInt32.from(x)); + case 256: + return SHA2Constants.H256.map((x) => UInt32.from(x)); + case 384: + return SHA2Constants.H384.map((x) => UInt64.from(x)); + case 512: + return SHA2Constants.H512.map((x) => UInt64.from(x)); + default: + throw new Error('Invalid hash length'); + } + }, +}; From b31eddabfbd01d89192f0d669429a9a5ba2fdafd Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 3 Dec 2024 18:37:06 +0100 Subject: [PATCH 05/25] padding function for short and long sha2 --- src/lib/provable/crypto/sha2.ts | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts index ec75ec6a97..8f4cdbef80 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/crypto/sha2.ts @@ -230,3 +230,81 @@ const SHA2 = { } }, }; + +// The only difference between the padding used in SHA2-224/256 and SHA2-384/512 +// is the size of the word (32bit vs 64bit). In the first case, UInt32[][] is +// returned, in the second case UInt64[][] is returned. +function padding(len: number, data: FlexibleBytes): UInt32[][] | UInt64[][] { + // create a provable Bytes instance from the input data + // the Bytes class will be static sized according to the length of the input data + let message = Bytes.from(data); + + // Whether this is a short SHA2 (SHA2-224 or SHA2-256) or not (SHA2-384 or SHA2-512) + const is_short = len <= 256; + + const blockLength = is_short + ? SHA2_224_256_BLOCK_LENGTH + : SHA2_384_512_BLOCK_LENGTH; + const paddingValue = is_short + ? SHA2_224_256_PADDING_VALUE + : SHA2_384_512_PADDING_VALUE; + const lengthChunk = is_short + ? SHA2_224_256_LENGTH_CHUNK + : SHA2_384_512_LENGTH_CHUNK; + + // now pad the data to reach the format expected by SHA2 + // pad 1 bit, followed by k zero bits where k is the smallest non-negative solution to + // l + 1 + k = (448 | 896) mod (512 | 1024) + // then append a (64 | 128)-bit block containing the length of the original message in bits + // it holds that PADDING_VALUE = BLOCK_LENGTH - LENGTH_CHUNK + // this way the padded message will be a multiple of the BLOCK_LENGTH + + let l = message.length * 8; // length in bits + let k = Number(mod(paddingValue - (BigInt(l) + 1n), blockLength)); + + let lBinary = l.toString(2); + + let paddingBits = ( + '1' + // append 1 bit + '0'.repeat(k) + // append k zero bits + '0'.repeat(lengthChunk - lBinary.length) + // append 64|128-bit containing the length of the original message + lBinary + ).match(/.{1,8}/g)!; // this should always be divisible by 8 + + // map the padding bit string to UInt8 elements + let padding = paddingBits.map((x) => UInt8.from(BigInt('0b' + x))); + + // concatenate the padding with the original padded data + let paddedMessage = message.bytes.concat(padding); + + // Create chunks based on whether we are dealing with SHA2-224/256 or SHA2-384/512 + // split the message into (32 | 64)-bit chunks + let chunks = is_short + ? createChunks(paddedMessage, 4, UInt32.fromBytesBE) + : createChunks(paddedMessage, 8, UInt64.fromBytesBE); + + // SHA2-224 and SHA2-256: + // split the message into 16 elements of 32 bits, what gives a block of 512 bits + // SHA2-384 and SHA2-512: + // split the message into 16 elements of 64 bits, what gives a block of 1024 bits + if (is_short) { + return chunk(chunks as UInt32[], 16); + } else { + return chunk(chunks as UInt64[], 16); + } +} + +// Helper function to create chunks based on the size and type (UInt32 or UInt64) +function createChunks( + paddedMessage: UInt8[], + byteSize: number, + fromBytes: Function +): UInt32[] | UInt64[] { + let chunks: any[] = []; + // bytesToWord expects little endian, so we reverse the bytes + for (let i = 0; i < paddedMessage.length; i += byteSize) { + // Chunk the data based on the specified byte size (4 bytes for UInt32, 8 bytes for UInt64) + chunks.push(fromBytes(paddedMessage.slice(i, i + byteSize))); + } + return chunks; +} From 8847397d59eb083983f9aebfee32ce5122240981 Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 4 Dec 2024 17:23:45 +0100 Subject: [PATCH 06/25] generic message schedule function --- src/lib/provable/crypto/sha2.ts | 118 ++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 36 deletions(-) diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts index 8f4cdbef80..43348f2c2b 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/crypto/sha2.ts @@ -6,35 +6,40 @@ import { FlexibleBytes } from '../bytes.js'; import { Bytes } from '../wrapped-classes.js'; import { chunk } from '../../util/arrays.js'; import { TupleN } from '../../util/types.js'; -import { divMod32 } from '../gadgets/arithmetic.js'; +import { divMod32, divMod64 } from '../gadgets/arithmetic.js'; import { bitSlice } from '../gadgets/common.js'; import { rangeCheck16 } from '../gadgets/range-check.js'; export { SHA2 }; // SHA2 CONSTANTS +const SHA2Constants = { + // Bit length of the blocks used in SHA2-224 and SHA2-256 + BLOCK_LENGTH_224_256: 512n, -// Bit length of the blocks used in SHA2-224 and SHA2-256 -const SHA2_224_256_BLOCK_LENGTH = 512n; + // Bit length of the blocks used in SHA2-384 and SHA2-512 + BLOCK_LENGTH_384_512: 1024n, -// Bit length of the blocks used in SHA2-384 and SHA2-512 -const SHA2_384_512_BLOCK_LENGTH = 1024n; + // Value used in the padding equation for SHA2-224 and SHA2-256 + PADDING_VALUE_224_256: 448n, -// Value used in the padding equation for SHA2-224 and SHA2-256 -const SHA2_224_256_PADDING_VALUE = 448n; + // Value used in the padding equation for SHA2-384 and SHA2-512 + PADDING_VALUE_384_512: 896n, -// Value used in the padding equation for SHA2-384 and SHA2-512 -const SHA2_384_512_PADDING_VALUE = 896n; + // Bits used to store the length in the padding of SHA2-224 and SHA2-256 + // It corresponds to 512 - 448 = 64 + LENGTH_CHUNK_224_256: 64, -// Bits used to store the length in the padding of SHA2-224 and SHA2-256 -// It corresponds to 512 - 448 = 64 -const SHA2_224_256_LENGTH_CHUNK = 64; + // Bits used to store the length in the padding of SHA2-384 and SHA2-512 + // It corresponds to 1024 - 896 = 128 + LENGTH_CHUNK_384_512: 128, -// Bits used to store the length in the padding of SHA2-384 and SHA2-512 -// It corresponds to 1024 - 896 = 128 -const SHA2_384_512_LENGTH_CHUNK = 128; + // Number of words in message schedule for SHA2-224 and SHA2-256 + SCHEDULE_WORDS_224_256: 64, + + // Number of words in message schedule for SHA2-384 and SHA2-512 + SCHEDULE_WORDS_384_512: 80, -const SHA2Constants = { // constants for SHA2-224 and SHA2-256 §4.2.2 K224_256: [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, @@ -196,15 +201,19 @@ const SHA2 = { * */ hash(data: FlexibleBytes) { + const is_short = SHA2.length <= 256; + + let typ = is_short ? UInt32 : UInt64; + // preprocessing §6.2 // padding the message $5.1.1 into blocks that are a multiple of 512 - let messageBlocks = padding(SHA2.length, data); + let messageBlocks = padding(data, is_short); let H = SHA2.initialState; const N = messageBlocks.length; for (let i = 0; i < N; i++) { - const W = createMessageSchedule(messageBlocks[i]); + const W = createMessageSchedule(messageBlocks[i], is_short); H = sha256Compression(H, W); } @@ -234,23 +243,26 @@ const SHA2 = { // The only difference between the padding used in SHA2-224/256 and SHA2-384/512 // is the size of the word (32bit vs 64bit). In the first case, UInt32[][] is // returned, in the second case UInt64[][] is returned. -function padding(len: number, data: FlexibleBytes): UInt32[][] | UInt64[][] { +function padding( + data: FlexibleBytes, + is_short: boolean +): T[][] { // create a provable Bytes instance from the input data // the Bytes class will be static sized according to the length of the input data let message = Bytes.from(data); // Whether this is a short SHA2 (SHA2-224 or SHA2-256) or not (SHA2-384 or SHA2-512) - const is_short = len <= 256; - const blockLength = is_short - ? SHA2_224_256_BLOCK_LENGTH - : SHA2_384_512_BLOCK_LENGTH; + ? SHA2Constants.BLOCK_LENGTH_224_256 + : SHA2Constants.BLOCK_LENGTH_384_512; + const paddingValue = is_short - ? SHA2_224_256_PADDING_VALUE - : SHA2_384_512_PADDING_VALUE; + ? SHA2Constants.PADDING_VALUE_224_256 + : SHA2Constants.PADDING_VALUE_384_512; + const lengthChunk = is_short - ? SHA2_224_256_LENGTH_CHUNK - : SHA2_384_512_LENGTH_CHUNK; + ? SHA2Constants.LENGTH_CHUNK_224_256 + : SHA2Constants.LENGTH_CHUNK_384_512; // now pad the data to reach the format expected by SHA2 // pad 1 bit, followed by k zero bits where k is the smallest non-negative solution to @@ -283,15 +295,9 @@ function padding(len: number, data: FlexibleBytes): UInt32[][] | UInt64[][] { ? createChunks(paddedMessage, 4, UInt32.fromBytesBE) : createChunks(paddedMessage, 8, UInt64.fromBytesBE); - // SHA2-224 and SHA2-256: - // split the message into 16 elements of 32 bits, what gives a block of 512 bits - // SHA2-384 and SHA2-512: - // split the message into 16 elements of 64 bits, what gives a block of 1024 bits - if (is_short) { - return chunk(chunks as UInt32[], 16); - } else { - return chunk(chunks as UInt64[], 16); - } + // SHA2-224 and SHA2-256 | SHA2-384 and SHA2-512: + // split the message into 16 elements of 32 | 64 bits, what gives a block of 512 | 1024 bits + return chunk(chunks as T[], 16); } // Helper function to create chunks based on the size and type (UInt32 or UInt64) @@ -308,3 +314,43 @@ function createChunks( } return chunks; } + +/** + * Prepares the message schedule for the SHA2 compression function from the given message block. + * + * @param M - The 512-bit message block (16-element array of UInt32) + * or the 1024-bit message block (16-element array of UInt64). + * @returns The message schedule (64-element array of UInt32 or 80-element array of UInt64). + */ +function createMessageSchedule( + M: T[], + is_short: boolean +): T[] { + // Declare W as an empty array of type T[] (generic array) + const W: T[] = []; + + // for each message block of 16 x 32bit | 64bit do: + + let scheduleWords = is_short + ? SHA2Constants.SCHEDULE_WORDS_224_256 + : SHA2Constants.SCHEDULE_WORDS_384_512; + + // prepare message block + for (let t = 0; t <= 15; t++) W[t] = M[t]; + for (let t = 16; t < scheduleWords; t++) { + // the field element is unreduced and not proven to be 32bit | 64bit, + // we will do this later to save constraints + let unreduced = DeltaOne(W[t - 2]) + .value.add(W[t - 7].value) + .add(DeltaZero(W[t - 15]).value.add(W[t - 16].value)); + + // mod 32 | 64 bit the unreduced field element + if (is_short) { + W[t] = UInt32.Unsafe.fromField(divMod32(unreduced, 48).remainder) as T; + } else { + W[t] = UInt64.Unsafe.fromField(divMod64(unreduced).remainder) as T; + } + } + + return W; +} From a72e52ca40faa4c9d09a6a56b101b45572c742b2 Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 4 Dec 2024 17:40:18 +0100 Subject: [PATCH 07/25] infer flag from instanceof generic T to avoid passing is_short --- src/lib/provable/crypto/sha2.ts | 48 +++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts index 43348f2c2b..7b004f6217 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/crypto/sha2.ts @@ -201,19 +201,15 @@ const SHA2 = { * */ hash(data: FlexibleBytes) { - const is_short = SHA2.length <= 256; - - let typ = is_short ? UInt32 : UInt64; - // preprocessing §6.2 // padding the message $5.1.1 into blocks that are a multiple of 512 - let messageBlocks = padding(data, is_short); + let messageBlocks = padding(data); let H = SHA2.initialState; const N = messageBlocks.length; for (let i = 0; i < N; i++) { - const W = createMessageSchedule(messageBlocks[i], is_short); + const W = createMessageSchedule(messageBlocks[i]); H = sha256Compression(H, W); } @@ -240,27 +236,36 @@ const SHA2 = { }, }; +/** + * Padding function for SHA2, as specified in §5.1.1, §5.1.2, + * + * @param data The message to hash + * @param is_short Whether this is a short SHA2 (SHA2-224 or SHA2-256) or not (SHA2-384 or SHA2-512) + * @returns + */ // The only difference between the padding used in SHA2-224/256 and SHA2-384/512 // is the size of the word (32bit vs 64bit). In the first case, UInt32[][] is // returned, in the second case UInt64[][] is returned. -function padding( - data: FlexibleBytes, - is_short: boolean -): T[][] { +function padding(data: FlexibleBytes): T[][] { + // Using instance check to create the flag + // isShort = true if the hash is SHA2-224 or SHA2-256 + // isShort = false if the hash is SHA2-384 or SHA2-512 + const isShort: boolean = ({} as T) instanceof UInt32; + // create a provable Bytes instance from the input data // the Bytes class will be static sized according to the length of the input data let message = Bytes.from(data); // Whether this is a short SHA2 (SHA2-224 or SHA2-256) or not (SHA2-384 or SHA2-512) - const blockLength = is_short + const blockLength = isShort ? SHA2Constants.BLOCK_LENGTH_224_256 : SHA2Constants.BLOCK_LENGTH_384_512; - const paddingValue = is_short + const paddingValue = isShort ? SHA2Constants.PADDING_VALUE_224_256 : SHA2Constants.PADDING_VALUE_384_512; - const lengthChunk = is_short + const lengthChunk = isShort ? SHA2Constants.LENGTH_CHUNK_224_256 : SHA2Constants.LENGTH_CHUNK_384_512; @@ -291,7 +296,7 @@ function padding( // Create chunks based on whether we are dealing with SHA2-224/256 or SHA2-384/512 // split the message into (32 | 64)-bit chunks - let chunks = is_short + let chunks = isShort ? createChunks(paddedMessage, 4, UInt32.fromBytesBE) : createChunks(paddedMessage, 8, UInt64.fromBytesBE); @@ -317,21 +322,24 @@ function createChunks( /** * Prepares the message schedule for the SHA2 compression function from the given message block. + * §6.2.2.1 and §6.4.2.1 * * @param M - The 512-bit message block (16-element array of UInt32) * or the 1024-bit message block (16-element array of UInt64). * @returns The message schedule (64-element array of UInt32 or 80-element array of UInt64). */ -function createMessageSchedule( - M: T[], - is_short: boolean -): T[] { +function createMessageSchedule(M: T[]): T[] { // Declare W as an empty array of type T[] (generic array) const W: T[] = []; + // Using instance check to create the flag + // isShort = true if the hash is SHA2-224 or SHA2-256 + // isShort = false if the hash is SHA2-384 or SHA2-512 + const isShort: boolean = ({} as T) instanceof UInt32; + // for each message block of 16 x 32bit | 64bit do: - let scheduleWords = is_short + let scheduleWords = isShort ? SHA2Constants.SCHEDULE_WORDS_224_256 : SHA2Constants.SCHEDULE_WORDS_384_512; @@ -345,7 +353,7 @@ function createMessageSchedule( .add(DeltaZero(W[t - 15]).value.add(W[t - 16].value)); // mod 32 | 64 bit the unreduced field element - if (is_short) { + if (isShort) { W[t] = UInt32.Unsafe.fromField(divMod32(unreduced, 48).remainder) as T; } else { W[t] = UInt64.Unsafe.fromField(divMod64(unreduced).remainder) as T; From 9c5e9885c49207df580f1f36dbb6a3560a02f159 Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 4 Dec 2024 19:04:47 +0100 Subject: [PATCH 08/25] compression function and auxiliary subroutines from sha256 module --- src/lib/provable/crypto/sha2.ts | 219 ++++++++++++++++++++++++++++++-- 1 file changed, 208 insertions(+), 11 deletions(-) diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts index 7b004f6217..4222006e28 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/crypto/sha2.ts @@ -9,6 +9,7 @@ import { TupleN } from '../../util/types.js'; import { divMod32, divMod64 } from '../gadgets/arithmetic.js'; import { bitSlice } from '../gadgets/common.js'; import { rangeCheck16 } from '../gadgets/range-check.js'; +import { Uint } from 'web3'; export { SHA2 }; @@ -200,36 +201,39 @@ const SHA2 = { * ``` * */ - hash(data: FlexibleBytes) { + hash(length: 224 | 256 | 384 | 512, data: FlexibleBytes) { + // Infer the type T based on the value of `length` (conditional type) + type WordType = typeof length extends 224 | 256 ? UInt32 : UInt64; + // preprocessing §6.2 // padding the message $5.1.1 into blocks that are a multiple of 512 - let messageBlocks = padding(data); + let messageBlocks = padding(data); - let H = SHA2.initialState; + let H = SHA2.initialState(); const N = messageBlocks.length; for (let i = 0; i < N; i++) { const W = createMessageSchedule(messageBlocks[i]); - H = sha256Compression(H, W); + H = compression(H, W); } - // the working variables H[i] are 32bit, however we want to decompose them into bytes to be more compatible + // the working variables H[i] are 32 | 64 bit, however we want to decompose them into bytes to be more compatible return Bytes.from(H.map((x) => x.toBytesBE()).flat()); }, length: 224 | 256 | 384 | 512, - compression: sha256Compression, + compression, createMessageSchedule, padding, - get initialState() { + initialState() { switch (SHA2.length) { case 224: - return SHA2Constants.H224.map((x) => UInt32.from(x)); + return SHA2Constants.H224.map((x) => UInt32.from(x) as T); case 256: - return SHA2Constants.H256.map((x) => UInt32.from(x)); + return SHA2Constants.H256.map((x) => UInt32.from(x) as T); case 384: - return SHA2Constants.H384.map((x) => UInt64.from(x)); + return SHA2Constants.H384.map((x) => UInt64.from(x) as T); case 512: - return SHA2Constants.H512.map((x) => UInt64.from(x)); + return SHA2Constants.H512.map((x) => UInt64.from(x) as T); default: throw new Error('Invalid hash length'); } @@ -362,3 +366,196 @@ function createMessageSchedule(M: T[]): T[] { return W; } + +/** + * Performs the SHA-2 compression function on the given hash values and message schedule. + * + * @param H - The initial or intermediate hash values (8-element array of T). + * @param W - The message schedule (64-element array of T). + * + * @returns The updated intermediate hash values after compression. + */ +function compression([...H]: T[], W: T[]) { + // initialize working variables + let a = H[0]; + let b = H[1]; + let c = H[2]; + let d = H[3]; + let e = H[4]; + let f = H[5]; + let g = H[6]; + let h = H[7]; + + // main loop + for (let t = 0; t <= 63; t++) { + // T1 is unreduced and not proven to be 32bit, we will do this later to save constraints + const unreducedT1 = h.value + .add(SigmaOne(e).value) + .add(Ch(e, f, g).value) + .add(SHA256Constants.K[t]) + .add(W[t].value) + .seal(); + + // T2 is also unreduced + const unreducedT2 = SigmaZero(a).value.add(Maj(a, b, c).value); + + h = g; + g = f; + f = e; + e = UInt32.Unsafe.fromField( + divMod32(d.value.add(unreducedT1), 48).remainder + ); // mod 32bit the unreduced field element + d = c; + c = b; + b = a; + a = UInt32.Unsafe.fromField( + divMod32(unreducedT2.add(unreducedT1), 48).remainder + ); // mod 32bit + } + + // new intermediate hash value + H[0] = H[0].addMod32(a); + H[1] = H[1].addMod32(b); + H[2] = H[2].addMod32(c); + H[3] = H[3].addMod32(d); + H[4] = H[4].addMod32(e); + H[5] = H[5].addMod32(f); + H[6] = H[6].addMod32(g); + H[7] = H[7].addMod32(h); + + return H; +} + +// Subroutines for SHA2 + +function Ch(x: T, y: T, z: T) { + // ch(x, y, z) = (x & y) ^ (~x & z) + // = (x & y) + (~x & z) (since x & ~x = 0) + let xAndY = x.and(y).value; + let xNotAndZ = x.not().and(z).value; + let ch = xAndY.add(xNotAndZ).seal(); + return UInt32.Unsafe.fromField(ch); +} + +function Maj(x: T, y: T, z: T) { + // maj(x, y, z) = (x & y) ^ (x & z) ^ (y & z) + // = (x + y + z - (x ^ y ^ z)) / 2 + let sum = x.value.add(y.value).add(z.value).seal(); + let xor = x.xor(y).xor(z).value; + let maj = sum.sub(xor).div(2).seal(); + return UInt32.Unsafe.fromField(maj); +} + +function SigmaZero(x: T) { + return sigma(x, [2, 13, 22]); +} + +function SigmaOne(x: T) { + return sigma(x, [6, 11, 25]); +} + +// lowercase sigma = delta to avoid confusing function names + +function DeltaZero(x: T) { + return sigma(x, [3, 7, 18], true); +} + +function DeltaOne(x: T) { + return sigma(x, [10, 17, 19], true); +} + +function ROTR(n: number, x: T) { + return x.rotate(n, 'right'); +} + +function SHR(n: number, x: T) { + let val = x.rightShift(n); + return val; +} + +function sigmaSimple( + u: T, + bits: TupleN, + firstShifted = false +) { + let [r0, r1, r2] = bits; + let rot0 = firstShifted ? SHR(r0, u) : ROTR(r0, u); + let rot1 = ROTR(r1, u); + let rot2 = ROTR(r2, u); + return rot0.xor(rot1).xor(rot2); +} + +function sigma( + u: T, + bits: TupleN, + firstShifted = false +) { + if (u.isConstant()) return sigmaSimple(u, bits, firstShifted); + + let [r0, r1, r2] = bits; // TODO assert bits are sorted + let x = u.value; + + let d0 = r0; + let d1 = r1 - r0; + let d2 = r2 - r1; + let d3 = 32 - r2; + + // decompose x into 4 chunks of size d0, d1, d2, d3 + let [x0, x1, x2, x3] = exists(4, () => { + let xx = x.toBigInt(); + return [ + bitSlice(xx, 0, d0), + bitSlice(xx, r0, d1), + bitSlice(xx, r1, d2), + bitSlice(xx, r2, d3), + ]; + }); + + // range check each chunk + // we only need to range check to 16 bits relying on the requirement that + // the rotated values are range-checked to 32 bits later; see comments below + rangeCheck16(x0); + rangeCheck16(x1); + rangeCheck16(x2); + rangeCheck16(x3); + + // prove x decomposition + + // x === x0 + x1*2^d0 + x2*2^(d0+d1) + x3*2^(d0+d1+d2) + let x23 = x2.add(x3.mul(1 << d2)).seal(); + let x123 = x1.add(x23.mul(1 << d1)).seal(); + x0.add(x123.mul(1 << d0)).assertEquals(x); + // ^ proves that 2^(32-d3)*x3 < x < 2^32 => x3 < 2^d3 + + // reassemble chunks into rotated values + + let xRotR0: Field; + + if (!firstShifted) { + // rotr(x, r0) = x1 + x2*2^d1 + x3*2^(d1+d2) + x0*2^(d1+d2+d3) + xRotR0 = x123.add(x0.mul(1 << (d1 + d2 + d3))).seal(); + // ^ proves that 2^(32-d0)*x0 < xRotR0 => x0 < 2^d0 if we check xRotR0 < 2^32 later + } else { + // shr(x, r0) = x1 + x2*2^d1 + x3*2^(d1+d2) + xRotR0 = x123; + + // finish x0 < 2^d0 proof: + rangeCheck16(x0.mul(1 << (16 - d0)).seal()); + } + + // rotr(x, r1) = x2 + x3*2^d2 + x0*2^(d2+d3) + x1*2^(d2+d3+d0) + let x01 = x0.add(x1.mul(1 << d0)).seal(); + let xRotR1 = x23.add(x01.mul(1 << (d2 + d3))).seal(); + // ^ proves that 2^(32-d1)*x1 < xRotR1 => x1 < 2^d1 if we check xRotR1 < 2^32 later + + // rotr(x, r2) = x3 + x0*2^d3 + x1*2^(d3+d0) + x2*2^(d3+d0+d1) + let x012 = x01.add(x2.mul(1 << (d0 + d1))).seal(); + let xRotR2 = x3.add(x012.mul(1 << d3)).seal(); + // ^ proves that 2^(32-d2)*x2 < xRotR2 => x2 < 2^d2 if we check xRotR2 < 2^32 later + + // since xor() is implicitly range-checking both of its inputs, this provides the missing + // proof that xRotR0, xRotR1, xRotR2 < 2^32, which implies x0 < 2^d0, x1 < 2^d1, x2 < 2^d2 + return UInt32.Unsafe.fromField(xRotR0) + .xor(UInt32.Unsafe.fromField(xRotR1)) + .xor(UInt32.Unsafe.fromField(xRotR2)); +} From 98be5f2fcc0b766d27707a12c80f9c0fa3e72e5a Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 4 Dec 2024 19:44:44 +0100 Subject: [PATCH 09/25] different parameters for sigma function depending on variant --- src/lib/provable/crypto/sha2.ts | 68 ++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts index 4222006e28..77dc14377e 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/crypto/sha2.ts @@ -41,6 +41,30 @@ const SHA2Constants = { // Number of words in message schedule for SHA2-384 and SHA2-512 SCHEDULE_WORDS_384_512: 80, + // Offsets for the DeltaZero function for SHA2 + // - SHA2-224 and SHA2-256: §4.1.2 eq.4.6 + DELTA_ZERO_224_256: [3, 7, 18] as TupleN, + // - SHA2-384 and SHA2-512: §4.1.3 eq.4.12 + DELTA_ZERO_384_512: [7, 1, 8] as TupleN, + + // Offsets for the DeltaOne function for SHA2. + // - SHA2-224 and SHA2-256: §4.1.2 eq.4.7 + DELTA_ONE_224_256: [10, 17, 19] as TupleN, + // - SHA2-384 and SHA2-512: §4.1.3 eq.4.13 + DELTA_ONE_384_512: [6, 19, 61] as TupleN, + + // Offsets for the SigmaZero function for SHA2. + // - SHA2-224 and SHA2-256: §4.1.2 eq.4.4 + SIGMA_ZERO_224_256: [2, 13, 22] as TupleN, + // - SHA2-384 and SHA2-512: §4.1.3 eq.4.10 + SIGMA_ZERO_384_512: [28, 34, 39] as TupleN, + + // Offsets for the SigmaOne function for SHA2. + // - SHA2-224 and SHA2-256: §4.1.2 eq.4.5 + SIGMA_ONE_224_256: [6, 11, 25] as TupleN, + // - SHA2-384 and SHA2-512: §4.1.3 eq.4.11 + SIGMA_ONE_384_512: [14, 18, 41] as TupleN, + // constants for SHA2-224 and SHA2-256 §4.2.2 K224_256: [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, @@ -251,25 +275,22 @@ const SHA2 = { // is the size of the word (32bit vs 64bit). In the first case, UInt32[][] is // returned, in the second case UInt64[][] is returned. function padding(data: FlexibleBytes): T[][] { - // Using instance check to create the flag - // isShort = true if the hash is SHA2-224 or SHA2-256 - // isShort = false if the hash is SHA2-384 or SHA2-512 - const isShort: boolean = ({} as T) instanceof UInt32; + const is_short = isShort(); // create a provable Bytes instance from the input data // the Bytes class will be static sized according to the length of the input data let message = Bytes.from(data); // Whether this is a short SHA2 (SHA2-224 or SHA2-256) or not (SHA2-384 or SHA2-512) - const blockLength = isShort + const blockLength = is_short ? SHA2Constants.BLOCK_LENGTH_224_256 : SHA2Constants.BLOCK_LENGTH_384_512; - const paddingValue = isShort + const paddingValue = is_short ? SHA2Constants.PADDING_VALUE_224_256 : SHA2Constants.PADDING_VALUE_384_512; - const lengthChunk = isShort + const lengthChunk = is_short ? SHA2Constants.LENGTH_CHUNK_224_256 : SHA2Constants.LENGTH_CHUNK_384_512; @@ -300,7 +321,7 @@ function padding(data: FlexibleBytes): T[][] { // Create chunks based on whether we are dealing with SHA2-224/256 or SHA2-384/512 // split the message into (32 | 64)-bit chunks - let chunks = isShort + let chunks = is_short ? createChunks(paddedMessage, 4, UInt32.fromBytesBE) : createChunks(paddedMessage, 8, UInt64.fromBytesBE); @@ -336,14 +357,11 @@ function createMessageSchedule(M: T[]): T[] { // Declare W as an empty array of type T[] (generic array) const W: T[] = []; - // Using instance check to create the flag - // isShort = true if the hash is SHA2-224 or SHA2-256 - // isShort = false if the hash is SHA2-384 or SHA2-512 - const isShort: boolean = ({} as T) instanceof UInt32; + let is_short = isShort(); // for each message block of 16 x 32bit | 64bit do: - let scheduleWords = isShort + let scheduleWords = is_short ? SHA2Constants.SCHEDULE_WORDS_224_256 : SHA2Constants.SCHEDULE_WORDS_384_512; @@ -357,7 +375,7 @@ function createMessageSchedule(M: T[]): T[] { .add(DeltaZero(W[t - 15]).value.add(W[t - 16].value)); // mod 32 | 64 bit the unreduced field element - if (isShort) { + if (is_short) { W[t] = UInt32.Unsafe.fromField(divMod32(unreduced, 48).remainder) as T; } else { W[t] = UInt64.Unsafe.fromField(divMod64(unreduced).remainder) as T; @@ -426,6 +444,12 @@ function compression([...H]: T[], W: T[]) { return H; } +// Helper function to check if the hash length is short (SHA2-224 or SHA2-256) +// depending on the generic type T used for the word size (UInt32 or UInt64) +function isShort(): boolean { + return ({} as T) instanceof UInt32; +} + // Subroutines for SHA2 function Ch(x: T, y: T, z: T) { @@ -447,21 +471,29 @@ function Maj(x: T, y: T, z: T) { } function SigmaZero(x: T) { - return sigma(x, [2, 13, 22]); + return isShort() + ? sigma(x, SHA2Constants.SIGMA_ZERO_224_256) + : sigma(x, SHA2Constants.SIGMA_ZERO_384_512); } function SigmaOne(x: T) { - return sigma(x, [6, 11, 25]); + return isShort() + ? sigma(x, SHA2Constants.SIGMA_ONE_224_256) + : sigma(x, SHA2Constants.SIGMA_ONE_384_512); } // lowercase sigma = delta to avoid confusing function names function DeltaZero(x: T) { - return sigma(x, [3, 7, 18], true); + return isShort() + ? sigma(x, SHA2Constants.DELTA_ZERO_224_256, true) + : sigma(x, SHA2Constants.DELTA_ZERO_384_512, true); } function DeltaOne(x: T) { - return sigma(x, [10, 17, 19], true); + return isShort() + ? sigma(x, SHA2Constants.DELTA_ONE_224_256, true) + : sigma(x, SHA2Constants.DELTA_ONE_384_512, true); } function ROTR(n: number, x: T) { From 3b6c837c9899088378893f06d4846f42d219c657 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 5 Dec 2024 14:37:26 +0100 Subject: [PATCH 10/25] generic compression function --- src/lib/provable/crypto/sha2.ts | 89 ++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 29 deletions(-) diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts index 77dc14377e..fa321bab33 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/crypto/sha2.ts @@ -35,11 +35,11 @@ const SHA2Constants = { // It corresponds to 1024 - 896 = 128 LENGTH_CHUNK_384_512: 128, - // Number of words in message schedule for SHA2-224 and SHA2-256 - SCHEDULE_WORDS_224_256: 64, + // Number of words in message schedule and compression for SHA2-224 and SHA2-256 + NUM_WORDS_224_256: 64, - // Number of words in message schedule for SHA2-384 and SHA2-512 - SCHEDULE_WORDS_384_512: 80, + // Number of words in message schedule and compression for SHA2-384 and SHA2-512 + NUM_WORDS_384_512: 80, // Offsets for the DeltaZero function for SHA2 // - SHA2-224 and SHA2-256: §4.1.2 eq.4.6 @@ -331,11 +331,11 @@ function padding(data: FlexibleBytes): T[][] { } // Helper function to create chunks based on the size and type (UInt32 or UInt64) -function createChunks( +function createChunks( paddedMessage: UInt8[], byteSize: number, fromBytes: Function -): UInt32[] | UInt64[] { +): T[] { let chunks: any[] = []; // bytesToWord expects little endian, so we reverse the bytes for (let i = 0; i < paddedMessage.length; i += byteSize) { @@ -347,13 +347,14 @@ function createChunks( /** * Prepares the message schedule for the SHA2 compression function from the given message block. - * §6.2.2.1 and §6.4.2.1 * * @param M - The 512-bit message block (16-element array of UInt32) * or the 1024-bit message block (16-element array of UInt64). * @returns The message schedule (64-element array of UInt32 or 80-element array of UInt64). */ function createMessageSchedule(M: T[]): T[] { + // §6.2.2.1 and §6.4.2.1 + // Declare W as an empty array of type T[] (generic array) const W: T[] = []; @@ -361,13 +362,13 @@ function createMessageSchedule(M: T[]): T[] { // for each message block of 16 x 32bit | 64bit do: - let scheduleWords = is_short - ? SHA2Constants.SCHEDULE_WORDS_224_256 - : SHA2Constants.SCHEDULE_WORDS_384_512; + let numWords = is_short + ? SHA2Constants.NUM_WORDS_224_256 + : SHA2Constants.NUM_WORDS_384_512; // prepare message block for (let t = 0; t <= 15; t++) W[t] = M[t]; - for (let t = 16; t < scheduleWords; t++) { + for (let t = 16; t < numWords; t++) { // the field element is unreduced and not proven to be 32bit | 64bit, // we will do this later to save constraints let unreduced = DeltaOne(W[t - 2]) @@ -378,7 +379,9 @@ function createMessageSchedule(M: T[]): T[] { if (is_short) { W[t] = UInt32.Unsafe.fromField(divMod32(unreduced, 48).remainder) as T; } else { - W[t] = UInt64.Unsafe.fromField(divMod64(unreduced).remainder) as T; + W[t] = UInt64.Unsafe.fromField( + divMod64(unreduced, 64 + 16).remainder + ) as T; } } @@ -394,6 +397,14 @@ function createMessageSchedule(M: T[]): T[] { * @returns The updated intermediate hash values after compression. */ function compression([...H]: T[], W: T[]) { + let is_short = isShort(); + let numWords = is_short + ? SHA2Constants.NUM_WORDS_224_256 + : SHA2Constants.NUM_WORDS_384_512; + + let k = is_short ? SHA2Constants.K224_256 : SHA2Constants.K384_512; + + // §6.2.2.2 and §6.4.2.2: // initialize working variables let a = H[0]; let b = H[1]; @@ -404,13 +415,15 @@ function compression([...H]: T[], W: T[]) { let g = H[6]; let h = H[7]; + // §6.2.2.3 and §6.4.2.3: // main loop - for (let t = 0; t <= 63; t++) { - // T1 is unreduced and not proven to be 32bit, we will do this later to save constraints + for (let t = 0; t < numWords; t++) { + // T1 is unreduced and not proven to be 32 | 64 bit, + // we will do this later to save constraints const unreducedT1 = h.value .add(SigmaOne(e).value) .add(Ch(e, f, g).value) - .add(SHA256Constants.K[t]) + .add(k[t]) .add(W[t].value) .seal(); @@ -420,30 +433,48 @@ function compression([...H]: T[], W: T[]) { h = g; g = f; f = e; - e = UInt32.Unsafe.fromField( - divMod32(d.value.add(unreducedT1), 48).remainder - ); // mod 32bit the unreduced field element + e = is_short + ? (UInt32.Unsafe.fromField( + divMod32(d.value.add(unreducedT1), 48).remainder + ) as T) + : (UInt64.Unsafe.fromField( + divMod64(d.value.add(unreducedT1), 64 + 16).remainder + ) as T); + // mod 32 | 64 bit the unreduced field element d = c; c = b; b = a; - a = UInt32.Unsafe.fromField( - divMod32(unreducedT2.add(unreducedT1), 48).remainder - ); // mod 32bit + a = is_short + ? (UInt32.Unsafe.fromField( + divMod32(unreducedT2.add(unreducedT1), 48).remainder + ) as T) + : (UInt64.Unsafe.fromField( + divMod64(unreducedT2.add(unreducedT1), 64 + 16).remainder + ) as T); + // mod 32 | 64 bit } + // §6.2.2.4 and §6.4.2.4 // new intermediate hash value - H[0] = H[0].addMod32(a); - H[1] = H[1].addMod32(b); - H[2] = H[2].addMod32(c); - H[3] = H[3].addMod32(d); - H[4] = H[4].addMod32(e); - H[5] = H[5].addMod32(f); - H[6] = H[6].addMod32(g); - H[7] = H[7].addMod32(h); + if (is_short) { + intermediateHash([a, b, c, d, e, f, g, h], H, UInt32.prototype.addMod32); + } else { + intermediateHash([a, b, c, d, e, f, g, h], H, UInt64.prototype.addMod64); + } return H; } +function intermediateHash( + variables: T[], + H: T[], + addMod: Function +) { + for (let i = 0; i < 8; i++) { + H[i] = addMod(variables[i], H[i]) as T; + } +} + // Helper function to check if the hash length is short (SHA2-224 or SHA2-256) // depending on the generic type T used for the word size (UInt32 or UInt64) function isShort(): boolean { From 9f001b1d82830c150cfb6b004cbc5c47ce760da4 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 5 Dec 2024 16:30:55 +0100 Subject: [PATCH 11/25] auxiliary functions for reduction modulo 32 64 bits to reduce number of lines --- src/lib/provable/crypto/sha2.ts | 77 +++++++++++++++------------------ 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts index fa321bab33..6c9c79c075 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/crypto/sha2.ts @@ -330,21 +330,6 @@ function padding(data: FlexibleBytes): T[][] { return chunk(chunks as T[], 16); } -// Helper function to create chunks based on the size and type (UInt32 or UInt64) -function createChunks( - paddedMessage: UInt8[], - byteSize: number, - fromBytes: Function -): T[] { - let chunks: any[] = []; - // bytesToWord expects little endian, so we reverse the bytes - for (let i = 0; i < paddedMessage.length; i += byteSize) { - // Chunk the data based on the specified byte size (4 bytes for UInt32, 8 bytes for UInt64) - chunks.push(fromBytes(paddedMessage.slice(i, i + byteSize))); - } - return chunks; -} - /** * Prepares the message schedule for the SHA2 compression function from the given message block. * @@ -376,13 +361,7 @@ function createMessageSchedule(M: T[]): T[] { .add(DeltaZero(W[t - 15]).value.add(W[t - 16].value)); // mod 32 | 64 bit the unreduced field element - if (is_short) { - W[t] = UInt32.Unsafe.fromField(divMod32(unreduced, 48).remainder) as T; - } else { - W[t] = UInt64.Unsafe.fromField( - divMod64(unreduced, 64 + 16).remainder - ) as T; - } + W[t] = reduceMod(unreduced); } return W; @@ -433,24 +412,12 @@ function compression([...H]: T[], W: T[]) { h = g; g = f; f = e; - e = is_short - ? (UInt32.Unsafe.fromField( - divMod32(d.value.add(unreducedT1), 48).remainder - ) as T) - : (UInt64.Unsafe.fromField( - divMod64(d.value.add(unreducedT1), 64 + 16).remainder - ) as T); + e = reduceMod(d.value.add(unreducedT1)); // mod 32 | 64 bit the unreduced field element d = c; c = b; b = a; - a = is_short - ? (UInt32.Unsafe.fromField( - divMod32(unreducedT2.add(unreducedT1), 48).remainder - ) as T) - : (UInt64.Unsafe.fromField( - divMod64(unreducedT2.add(unreducedT1), 64 + 16).remainder - ) as T); + a = reduceMod(unreducedT2.add(unreducedT1)); // mod 32 | 64 bit } @@ -465,6 +432,38 @@ function compression([...H]: T[], W: T[]) { return H; } +// Helper functions + +// Helper function to check if the hash length is short (SHA2-224 or SHA2-256) +// depending on the generic type T used for the word size (UInt32 or UInt64) +function isShort(): boolean { + return ({} as T) instanceof UInt32; +} + +// Shorthand to reduce a field element modulo 32 or 64 bits depending on T +function reduceMod(x: Field): T { + if (isShort()) { + return UInt32.Unsafe.fromField(divMod32(x, 32 + 16).remainder) as T; + } else { + return UInt64.Unsafe.fromField(divMod64(x, 64 + 16).remainder) as T; + } +} + +// Helper function to create chunks based on the size and type (UInt32 or UInt64) +function createChunks( + paddedMessage: UInt8[], + byteSize: number, + fromBytes: Function +): T[] { + let chunks: any[] = []; + // bytesToWord expects little endian, so we reverse the bytes + for (let i = 0; i < paddedMessage.length; i += byteSize) { + // Chunk the data based on the specified byte size (4 bytes for UInt32, 8 bytes for UInt64) + chunks.push(fromBytes(paddedMessage.slice(i, i + byteSize))); + } + return chunks; +} + function intermediateHash( variables: T[], H: T[], @@ -475,12 +474,6 @@ function intermediateHash( } } -// Helper function to check if the hash length is short (SHA2-224 or SHA2-256) -// depending on the generic type T used for the word size (UInt32 or UInt64) -function isShort(): boolean { - return ({} as T) instanceof UInt32; -} - // Subroutines for SHA2 function Ch(x: T, y: T, z: T) { From 1bde9515b638db79fd4b37008cf31d764c3735c1 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 5 Dec 2024 17:19:22 +0100 Subject: [PATCH 12/25] adapt auxiliary functions for generic case --- src/lib/provable/crypto/sha2.ts | 76 +++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts index 6c9c79c075..a78b229645 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/crypto/sha2.ts @@ -45,7 +45,7 @@ const SHA2Constants = { // - SHA2-224 and SHA2-256: §4.1.2 eq.4.6 DELTA_ZERO_224_256: [3, 7, 18] as TupleN, // - SHA2-384 and SHA2-512: §4.1.3 eq.4.12 - DELTA_ZERO_384_512: [7, 1, 8] as TupleN, + DELTA_ZERO_384_512: [7, 8, 1] as TupleN, // Offsets for the DeltaOne function for SHA2. // - SHA2-224 and SHA2-256: §4.1.2 eq.4.7 @@ -237,7 +237,7 @@ const SHA2 = { const N = messageBlocks.length; for (let i = 0; i < N; i++) { - const W = createMessageSchedule(messageBlocks[i]); + const W = messageSchedule(messageBlocks[i]); H = compression(H, W); } @@ -246,7 +246,7 @@ const SHA2 = { }, length: 224 | 256 | 384 | 512, compression, - createMessageSchedule, + messageSchedule, padding, initialState() { switch (SHA2.length) { @@ -337,7 +337,7 @@ function padding(data: FlexibleBytes): T[][] { * or the 1024-bit message block (16-element array of UInt64). * @returns The message schedule (64-element array of UInt32 or 80-element array of UInt64). */ -function createMessageSchedule(M: T[]): T[] { +function messageSchedule(M: T[]): T[] { // §6.2.2.1 and §6.4.2.1 // Declare W as an empty array of type T[] (generic array) @@ -352,7 +352,7 @@ function createMessageSchedule(M: T[]): T[] { : SHA2Constants.NUM_WORDS_384_512; // prepare message block - for (let t = 0; t <= 15; t++) W[t] = M[t]; + for (let t = 0; t < 16; t++) W[t] = M[t]; for (let t = 16; t < numWords; t++) { // the field element is unreduced and not proven to be 32bit | 64bit, // we will do this later to save constraints @@ -476,22 +476,36 @@ function intermediateHash( // Subroutines for SHA2 -function Ch(x: T, y: T, z: T) { +function Ch(x: T, y: T, z: T): T { // ch(x, y, z) = (x & y) ^ (~x & z) // = (x & y) + (~x & z) (since x & ~x = 0) - let xAndY = x.and(y).value; - let xNotAndZ = x.not().and(z).value; - let ch = xAndY.add(xNotAndZ).seal(); - return UInt32.Unsafe.fromField(ch); + if (isShort()) { + let xAndY = (x as UInt32).and(y as UInt32).value; + let xNotAndZ = (x as UInt32).not().and(z as UInt32).value; + let ch = xAndY.add(xNotAndZ).seal(); + return UInt32.Unsafe.fromField(ch) as T; + } else { + let xAndY = (x as UInt64).and(y as UInt64).value; + let xNotAndZ = (x as UInt64).not().and(z as UInt64).value; + let ch = xAndY.add(xNotAndZ).seal(); + return UInt64.Unsafe.fromField(ch) as T; + } } -function Maj(x: T, y: T, z: T) { +function Maj(x: T, y: T, z: T): T { // maj(x, y, z) = (x & y) ^ (x & z) ^ (y & z) // = (x + y + z - (x ^ y ^ z)) / 2 - let sum = x.value.add(y.value).add(z.value).seal(); - let xor = x.xor(y).xor(z).value; - let maj = sum.sub(xor).div(2).seal(); - return UInt32.Unsafe.fromField(maj); + if (isShort()) { + let sum = (x as UInt32).value.add(y.value).add(z.value).seal(); + let xor = (x as UInt32).xor(y as UInt32).xor(z as UInt32).value; + let maj = sum.sub(xor).div(2).seal(); + return UInt32.Unsafe.fromField(maj) as T; + } else { + let sum = (x as UInt64).value.add(y.value).add(z.value).seal(); + let xor = (x as UInt64).xor(y as UInt64).xor(z as UInt64).value; + let maj = sum.sub(xor).div(2).seal(); + return UInt64.Unsafe.fromField(maj) as T; + } } function SigmaZero(x: T) { @@ -508,44 +522,50 @@ function SigmaOne(x: T) { // lowercase sigma = delta to avoid confusing function names -function DeltaZero(x: T) { +function DeltaZero(x: T): T { return isShort() ? sigma(x, SHA2Constants.DELTA_ZERO_224_256, true) : sigma(x, SHA2Constants.DELTA_ZERO_384_512, true); } -function DeltaOne(x: T) { +function DeltaOne(x: T): T { return isShort() ? sigma(x, SHA2Constants.DELTA_ONE_224_256, true) : sigma(x, SHA2Constants.DELTA_ONE_384_512, true); } -function ROTR(n: number, x: T) { - return x.rotate(n, 'right'); +function ROTR(n: number, x: T): T { + return x.rotate(n, 'right') as T; } -function SHR(n: number, x: T) { - let val = x.rightShift(n); - return val; +function SHR(n: number, x: T): T { + return x.rightShift(n) as T; } function sigmaSimple( u: T, bits: TupleN, firstShifted = false -) { +): T { let [r0, r1, r2] = bits; - let rot0 = firstShifted ? SHR(r0, u) : ROTR(r0, u); - let rot1 = ROTR(r1, u); - let rot2 = ROTR(r2, u); - return rot0.xor(rot1).xor(rot2); + if (isShort()) { + let rot0 = firstShifted ? (SHR(r0, u) as UInt32) : (ROTR(r0, u) as UInt32); + let rot1 = ROTR(r1, u) as UInt32; + let rot2 = ROTR(r2, u) as UInt32; + return rot0.xor(rot1).xor(rot2) as T; + } else { + let rot0 = firstShifted ? (SHR(r0, u) as UInt64) : (ROTR(r0, u) as UInt64); + let rot1 = ROTR(r1, u) as UInt64; + let rot2 = ROTR(r2, u) as UInt64; + return rot0.xor(rot1).xor(rot2) as T; + } } function sigma( u: T, bits: TupleN, firstShifted = false -) { +): T { if (u.isConstant()) return sigmaSimple(u, bits, firstShifted); let [r0, r1, r2] = bits; // TODO assert bits are sorted From 62c4ae54c9ec1e340ac422d32f659617ad17d837 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 5 Dec 2024 17:43:49 +0100 Subject: [PATCH 13/25] prettify and sigma for 256 and 512 --- src/lib/provable/crypto/sha2.ts | 130 ++++++++------------------------ 1 file changed, 32 insertions(+), 98 deletions(-) diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts index a78b229645..0137a71d05 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/crypto/sha2.ts @@ -80,87 +80,28 @@ const SHA2Constants = { 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, ], // constants for SHA2-384 and SHA2-512 §4.2.3 + // prettier-ignore K384_512: [ - 0x428a2f98d728ae22n, - 0x7137449123ef65cdn, - 0xb5c0fbcfec4d3b2fn, - 0xe9b5dba58189dbbcn, - 0x3956c25bf348b538n, - 0x59f111f1b605d019n, - 0x923f82a4af194f9bn, - 0xab1c5ed5da6d8118n, - 0xd807aa98a3030242n, - 0x12835b0145706fben, - 0x243185be4ee4b28cn, - 0x550c7dc3d5ffb4e2n, - 0x72be5d74f27b896fn, - 0x80deb1fe3b1696b1n, - 0x9bdc06a725c71235n, - 0xc19bf174cf692694n, - 0xe49b69c19ef14ad2n, - 0xefbe4786384f25e3n, - 0x0fc19dc68b8cd5b5n, - 0x240ca1cc77ac9c65n, - 0x2de92c6f592b0275n, - 0x4a7484aa6ea6e483n, - 0x5cb0a9dcbd41fbd4n, - 0x76f988da831153b5n, - 0x983e5152ee66dfabn, - 0xa831c66d2db43210n, - 0xb00327c898fb213fn, - 0xbf597fc7beef0ee4n, - 0xc6e00bf33da88fc2n, - 0xd5a79147930aa725n, - 0x06ca6351e003826fn, - 0x142929670a0e6e70n, - 0x27b70a8546d22ffcn, - 0x2e1b21385c26c926n, - 0x4d2c6dfc5ac42aedn, - 0x53380d139d95b3dfn, - 0x650a73548baf63den, - 0x766a0abb3c77b2a8n, - 0x81c2c92e47edaee6n, - 0x92722c851482353bn, - 0xa2bfe8a14cf10364n, - 0xa81a664bbc423001n, - 0xc24b8b70d0f89791n, - 0xc76c51a30654be30n, - 0xd192e819d6ef5218n, - 0xd69906245565a910n, - 0xf40e35855771202an, - 0x106aa07032bbd1b8n, - 0x19a4c116b8d2d0c8n, - 0x1e376c085141ab53n, - 0x2748774cdf8eeb99n, - 0x34b0bcb5e19b48a8n, - 0x391c0cb3c5c95a63n, - 0x4ed8aa4ae3418acbn, - 0x5b9cca4f7763e373n, - 0x682e6ff3d6b2b8a3n, - 0x748f82ee5defb2fcn, - 0x78a5636f43172f60n, - 0x84c87814a1f0ab72n, - 0x8cc702081a6439ecn, - 0x90befffa23631e28n, - 0xa4506cebde82bde9n, - 0xbef9a3f7b2c67915n, - 0xc67178f2e372532bn, - 0xca273eceea26619cn, - 0xd186b8c721c0c207n, - 0xeada7dd6cde0eb1en, - 0xf57d4f7fee6ed178n, - 0x06f067aa72176fban, - 0x0a637dc5a2c898a6n, - 0x113f9804bef90daen, - 0x1b710b35131c471bn, - 0x28db77f523047d84n, - 0x32caab7b40c72493n, - 0x3c9ebe0a15c9bebcn, - 0x431d67c49c100d4cn, - 0x4cc5d4becb3e42b6n, - 0x597f299cfc657e2an, - 0x5fcb6fab3ad6faecn, - 0x6c44198c4a475817n, + 0x428a2f98d728ae22n, 0x7137449123ef65cdn, 0xb5c0fbcfec4d3b2fn, 0xe9b5dba58189dbbcn, + 0x3956c25bf348b538n, 0x59f111f1b605d019n, 0x923f82a4af194f9bn, 0xab1c5ed5da6d8118n, + 0xd807aa98a3030242n, 0x12835b0145706fben, 0x243185be4ee4b28cn, 0x550c7dc3d5ffb4e2n, + 0x72be5d74f27b896fn, 0x80deb1fe3b1696b1n, 0x9bdc06a725c71235n, 0xc19bf174cf692694n, + 0xe49b69c19ef14ad2n, 0xefbe4786384f25e3n, 0x0fc19dc68b8cd5b5n, 0x240ca1cc77ac9c65n, + 0x2de92c6f592b0275n, 0x4a7484aa6ea6e483n, 0x5cb0a9dcbd41fbd4n, 0x76f988da831153b5n, + 0x983e5152ee66dfabn, 0xa831c66d2db43210n, 0xb00327c898fb213fn, 0xbf597fc7beef0ee4n, + 0xc6e00bf33da88fc2n, 0xd5a79147930aa725n, 0x06ca6351e003826fn, 0x142929670a0e6e70n, + 0x27b70a8546d22ffcn, 0x2e1b21385c26c926n, 0x4d2c6dfc5ac42aedn, 0x53380d139d95b3dfn, + 0x650a73548baf63den, 0x766a0abb3c77b2a8n, 0x81c2c92e47edaee6n, 0x92722c851482353bn, + 0xa2bfe8a14cf10364n, 0xa81a664bbc423001n, 0xc24b8b70d0f89791n, 0xc76c51a30654be30n, + 0xd192e819d6ef5218n, 0xd69906245565a910n, 0xf40e35855771202an, 0x106aa07032bbd1b8n, + 0x19a4c116b8d2d0c8n, 0x1e376c085141ab53n, 0x2748774cdf8eeb99n, 0x34b0bcb5e19b48a8n, + 0x391c0cb3c5c95a63n, 0x4ed8aa4ae3418acbn, 0x5b9cca4f7763e373n, 0x682e6ff3d6b2b8a3n, + 0x748f82ee5defb2fcn, 0x78a5636f43172f60n, 0x84c87814a1f0ab72n, 0x8cc702081a6439ecn, + 0x90befffa23631e28n, 0xa4506cebde82bde9n, 0xbef9a3f7b2c67915n, 0xc67178f2e372532bn, + 0xca273eceea26619cn, 0xd186b8c721c0c207n, 0xeada7dd6cde0eb1en, 0xf57d4f7fee6ed178n, + 0x06f067aa72176fban, 0x0a637dc5a2c898a6n, 0x113f9804bef90daen, 0x1b710b35131c471bn, + 0x28db77f523047d84n, 0x32caab7b40c72493n, 0x3c9ebe0a15c9bebcn, 0x431d67c49c100d4cn, + 0x4cc5d4becb3e42b6n, 0x597f299cfc657e2an, 0x5fcb6fab3ad6faecn, 0x6c44198c4a475817n, ], // initial hash values for SHA2-224 §5.3.2 H224: [ @@ -173,26 +114,16 @@ const SHA2Constants = { 0x1f83d9ab, 0x5be0cd19, ], // initial hash values for SHA2-384 §5.3.4 + // prettier-ignore H384: [ - 0xcbbb9d5dc1059ed8n, - 0x629a292a367cd507n, - 0x9159015a3070dd17n, - 0x152fecd8f70e5939n, - 0x67332667ffc00b31n, - 0x8eb44a8768581511n, - 0xdb0c2e0d64f98fa7n, - 0x47b5481dbefa4fa4n, + 0xcbbb9d5dc1059ed8n, 0x629a292a367cd507n, 0x9159015a3070dd17n, 0x152fecd8f70e5939n, + 0x67332667ffc00b31n, 0x8eb44a8768581511n, 0xdb0c2e0d64f98fa7n, 0x47b5481dbefa4fa4n, ], // initial hash values for SHA2-512 §5.3.5 + // prettier-ignore H512: [ - 0x6a09e667f3bcc908n, - 0xbb67ae8584caa73bn, - 0x3c6ef372fe94f82bn, - 0xa54ff53a5f1d36f1n, - 0x510e527fade682d1n, - 0x9b05688c2b3e6c1fn, - 0x1f83d9abfb41bd6bn, - 0x5be0cd19137e2179n, + 0x6a09e667f3bcc908n, 0xbb67ae8584caa73bn, 0x3c6ef372fe94f82bn, 0xa54ff53a5f1d36f1n, + 0x510e527fade682d1n, 0x9b05688c2b3e6c1fn, 0x1f83d9abfb41bd6bn, 0x5be0cd19137e2179n, ], }; @@ -566,7 +497,10 @@ function sigma( bits: TupleN, firstShifted = false ): T { - if (u.isConstant()) return sigmaSimple(u, bits, firstShifted); + if (u.isConstant() || !isShort()) return sigmaSimple(u, bits, firstShifted); + + // When T is UInt64, 64-bit rotation is natively supported in the gadgets. + // However, 32-bit rotation is not natively supported, thus the following: let [r0, r1, r2] = bits; // TODO assert bits are sorted let x = u.value; @@ -633,5 +567,5 @@ function sigma( // proof that xRotR0, xRotR1, xRotR2 < 2^32, which implies x0 < 2^d0, x1 < 2^d1, x2 < 2^d2 return UInt32.Unsafe.fromField(xRotR0) .xor(UInt32.Unsafe.fromField(xRotR1)) - .xor(UInt32.Unsafe.fromField(xRotR2)); + .xor(UInt32.Unsafe.fromField(xRotR2)) as T; } From 5f4527ae218f4a64bf5247e754b5b35005b2c84f Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 5 Dec 2024 18:00:04 +0100 Subject: [PATCH 14/25] truncate for 224 and 384 variants --- src/lib/provable/crypto/sha2.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts index 0137a71d05..52cf9785c0 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/crypto/sha2.ts @@ -172,8 +172,16 @@ const SHA2 = { H = compression(H, W); } - // the working variables H[i] are 32 | 64 bit, however we want to decompose them into bytes to be more compatible - return Bytes.from(H.map((x) => x.toBytesBE()).flat()); + // the working variables H[i] are 32 | 64 bit, however we want to decompose + // them into bytes to be more compatible + let digest = Bytes.from(H.map((x) => x.toBytesBE()).flat()); + + // Take the first `length` bits of the digest. This has no effect in + // SHA256 and SHA512, because 8 words of 32 | 64 bits needs no truncation. + // Truncation is required for SHA224 and SHA384 though. + digest.bytes = digest.bytes.slice(0, length / 8); + + return digest; }, length: 224 | 256 | 384 | 512, compression, From 854cb19b73ef3229582041eb3a004ee61fb8cfc5 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 5 Dec 2024 18:05:09 +0100 Subject: [PATCH 15/25] remove length property --- src/lib/provable/crypto/sha2.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/crypto/sha2.ts index 52cf9785c0..c528c19416 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/crypto/sha2.ts @@ -156,7 +156,7 @@ const SHA2 = { * ``` * */ - hash(length: 224 | 256 | 384 | 512, data: FlexibleBytes) { + hash(length: 224 | 256 | 384 | 512, data: FlexibleBytes): Bytes { // Infer the type T based on the value of `length` (conditional type) type WordType = typeof length extends 224 | 256 ? UInt32 : UInt64; @@ -164,7 +164,7 @@ const SHA2 = { // padding the message $5.1.1 into blocks that are a multiple of 512 let messageBlocks = padding(data); - let H = SHA2.initialState(); + let H = SHA2.initialState(length); const N = messageBlocks.length; for (let i = 0; i < N; i++) { @@ -183,12 +183,11 @@ const SHA2 = { return digest; }, - length: 224 | 256 | 384 | 512, compression, messageSchedule, padding, - initialState() { - switch (SHA2.length) { + initialState(length: 224 | 256 | 384 | 512): T[] { + switch (length) { case 224: return SHA2Constants.H224.map((x) => UInt32.from(x) as T); case 256: From 086f927309b619b5f680f3cf317b79708036d7a2 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 5 Dec 2024 19:04:38 +0100 Subject: [PATCH 16/25] unit tests for sha2 --- src/lib/provable/gadgets/gadgets.ts | 29 +- src/lib/provable/{crypto => gadgets}/sha2.ts | 6 +- src/lib/provable/gadgets/sha256.ts | 2 +- src/lib/provable/test/sha2.unit-test.ts | 309 +++++++++++++++++++ 4 files changed, 338 insertions(+), 8 deletions(-) rename src/lib/provable/{crypto => gadgets}/sha2.ts (99%) create mode 100644 src/lib/provable/test/sha2.unit-test.ts diff --git a/src/lib/provable/gadgets/gadgets.ts b/src/lib/provable/gadgets/gadgets.ts index a1dfda5637..6d79f7dd21 100644 --- a/src/lib/provable/gadgets/gadgets.ts +++ b/src/lib/provable/gadgets/gadgets.ts @@ -29,6 +29,7 @@ import { Sum as ForeignFieldSum, } from './foreign-field.js'; import { divMod32, addMod32, divMod64, addMod64 } from './arithmetic.js'; +import { SHA2 } from './sha2.js'; import { SHA256 } from './sha256.js'; import { BLAKE2B } from './blake2b.js'; import { rangeCheck3x12 } from './lookup.js'; @@ -439,7 +440,7 @@ const Gadgets = { * Bitwise AND gadget on {@link Field} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND). * The AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise. * - * It can be checked by a double generic gate that verifies the following relationship between the values + * It can be checked by a double generic gate that verifies the following relationship between the values * below (in the process it also invokes the {@link Gadgets.xor} gadget which will create additional constraints depending on `length`). * * The generic gate verifies:\ @@ -452,7 +453,7 @@ const Gadgets = { * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) * * The `length` parameter lets you define how many bits should be compared. `length` is rounded - * to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values + * to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values * are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well. * * **Note:** Specifying a larger `length` parameter adds additional constraints. @@ -476,8 +477,8 @@ const Gadgets = { * Bitwise OR gadget on {@link Field} elements. Equivalent to the [bitwise OR `|` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR). * The OR gate works by comparing two bits and returning `1` if at least one bit is `1`, and `0` otherwise. * - * The `length` parameter lets you define how many bits should be compared. `length` is rounded - * to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values + * The `length` parameter lets you define how many bits should be compared. `length` is rounded + * to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values * are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well. * * **Note:** Specifying a larger `length` parameter adds additional constraints. @@ -973,6 +974,26 @@ const Gadgets = { */ SHA256: SHA256, + /** + * Implementation of the [SHA2 hash function.](https://en.wikipedia.org/wiki/SHA-2) Hash function + * with 224 | 256 | 384 | 512 bit output. + * + * Applies the SHA2 hash function to a list of byte-sized elements. + * + * The function accepts {@link Bytes} as the input message, which is a type that represents a static-length list of byte-sized field elements (range-checked using {@link Gadgets.rangeCheck8}). + * Alternatively, you can pass plain `number[]`, `bigint[]` or `Uint8Array` to perform a hash outside provable code. + * + * Produces an output of {@link Bytes} that conforms to the chosen bit length. + * + * @param data - {@link Bytes} representing the message to hash. + * + * ```ts + * let preimage = Bytes.fromString("hello world"); + * let digest = Gadgets.SHA2.hash(512, preimage); + * ``` + * */ + SHA2: SHA2, + /** * Implementation of the [BLAKE2b hash function.](https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2) Hash function with arbitrary length output. * diff --git a/src/lib/provable/crypto/sha2.ts b/src/lib/provable/gadgets/sha2.ts similarity index 99% rename from src/lib/provable/crypto/sha2.ts rename to src/lib/provable/gadgets/sha2.ts index c528c19416..f787d051ac 100644 --- a/src/lib/provable/crypto/sha2.ts +++ b/src/lib/provable/gadgets/sha2.ts @@ -6,9 +6,9 @@ import { FlexibleBytes } from '../bytes.js'; import { Bytes } from '../wrapped-classes.js'; import { chunk } from '../../util/arrays.js'; import { TupleN } from '../../util/types.js'; -import { divMod32, divMod64 } from '../gadgets/arithmetic.js'; -import { bitSlice } from '../gadgets/common.js'; -import { rangeCheck16 } from '../gadgets/range-check.js'; +import { divMod32, divMod64 } from './arithmetic.js'; +import { bitSlice } from './common.js'; +import { rangeCheck16 } from './range-check.js'; import { Uint } from 'web3'; export { SHA2 }; diff --git a/src/lib/provable/gadgets/sha256.ts b/src/lib/provable/gadgets/sha256.ts index b4c91122ef..594251063b 100644 --- a/src/lib/provable/gadgets/sha256.ts +++ b/src/lib/provable/gadgets/sha256.ts @@ -16,7 +16,7 @@ import { divMod32 } from './arithmetic.js'; import { bitSlice } from './common.js'; import { rangeCheck16 } from './range-check.js'; -export { SHA256 }; +export { SHA2 }; const SHA256Constants = { // constants §4.2.2 diff --git a/src/lib/provable/test/sha2.unit-test.ts b/src/lib/provable/test/sha2.unit-test.ts new file mode 100644 index 0000000000..339ade11fa --- /dev/null +++ b/src/lib/provable/test/sha2.unit-test.ts @@ -0,0 +1,309 @@ +// In-circuit tests of the SHA2 family of hash functions together with +// test vectors extracted from https://www.di-mgt.com.au/sha_testvectors.html + +import { ZkProgram } from '../../proof-system/zkprogram.js'; +import { Bytes } from '../wrapped-classes.js'; +import { Gadgets } from '../gadgets/gadgets.js'; +import { + sha256 as nobleSha256, + sha224 as nobleSha224, +} from '@noble/hashes/sha256'; +import { + sha384 as nobleSha384, + sha512 as nobleSha512, +} from '@noble/hashes/sha512'; +import { bytes } from './test-utils.js'; +import { + equivalentAsync, + equivalentProvable, +} from '../../testing/equivalent.js'; +import { Random, sample } from '../../testing/random.js'; +import { expect } from 'expect'; + +// SHA2-224 TESTS +{ + sample(Random.nat(400), 5).forEach((preimageLength) => { + let inputBytes = bytes(preimageLength); + let outputBytes = bytes(224 / 8); + + equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( + (x) => nobleSha224(x), + (x) => Gadgets.SHA2.hash(224, x), + `sha224 preimage length ${preimageLength}` + ); + }); + + const Sha2_224Program = ZkProgram({ + name: `sha2-224`, + publicOutput: Bytes(28), + methods: { + sha224: { + privateInputs: [Bytes(192)], + async method(preImage: Bytes) { + return { + publicOutput: Gadgets.SHA2.hash(224, preImage), + }; + }, + }, + }, + }); + + const RUNS = 2; + + await Sha2_224Program.compile(); + + await equivalentAsync( + { + from: [bytes(192)], + to: bytes(28), + }, + { runs: RUNS } + )(nobleSha224, async (x) => { + const { proof } = await Sha2_224Program.sha224(x); + await Sha2_224Program.verify(proof); + return proof.publicOutput; + }); + + for (let { preimage, hash } of testVectors()) { + let actual = Gadgets.SHA2.hash(224, Bytes.fromString(preimage)); + expect(actual.toHex()).toEqual(hash); + } + + function testVectors() { + return [ + { + preimage: 'abc', + hash: '23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7', + }, + { + preimage: 'a'.repeat(1000000), + hash: '20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67', + }, + { + preimage: '', + hash: 'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f', + }, + { + preimage: + 'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu', + hash: 'c97ca9a559850ce97a04a96def6d99a9e0e0e2ab14e6b8df265fc0b3', + }, + ]; + } +} + +// SHA2-256 TESTS +{ + sample(Random.nat(400), 5).forEach((preimageLength) => { + let inputBytes = bytes(preimageLength); + let outputBytes = bytes(256 / 8); + + equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( + (x) => nobleSha256(x), + (x) => Gadgets.SHA2.hash(256, x), + `sha256 preimage length ${preimageLength}` + ); + }); + + const Sha2_256Program = ZkProgram({ + name: `sha2-256`, + publicOutput: Bytes(32), + methods: { + sha256: { + privateInputs: [Bytes(192)], + async method(preImage: Bytes) { + return { + publicOutput: Gadgets.SHA2.hash(256, preImage), + }; + }, + }, + }, + }); + + const RUNS = 2; + + await Sha2_256Program.compile(); + + await equivalentAsync( + { + from: [bytes(192)], + to: bytes(32), + }, + { runs: RUNS } + )(nobleSha256, async (x) => { + const { proof } = await Sha2_256Program.sha256(x); + await Sha2_256Program.verify(proof); + return proof.publicOutput; + }); + + for (let { preimage, hash } of testVectors()) { + let actual = Gadgets.SHA2.hash(256, Bytes.fromString(preimage)); + expect(actual.toHex()).toEqual(hash); + } + + function testVectors() { + return [ + { + preimage: 'abc', + hash: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', + }, + { + preimage: 'a'.repeat(1000000), + hash: 'cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0', + }, + { + preimage: '', + hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + { + preimage: + 'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu', + hash: 'cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1', + }, + ]; + } +} + +// SHA2-384 TESTS +{ + sample(Random.nat(400), 5).forEach((preimageLength) => { + let inputBytes = bytes(preimageLength); + let outputBytes = bytes(384 / 8); + + equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( + (x) => nobleSha384(x), + (x) => Gadgets.SHA2.hash(384, x), + `sha384 preimage length ${preimageLength}` + ); + }); + + const Sha2_384Program = ZkProgram({ + name: `sha2-384`, + publicOutput: Bytes(48), + methods: { + sha384: { + privateInputs: [Bytes(192)], + async method(preImage: Bytes) { + return { + publicOutput: Gadgets.SHA2.hash(384, preImage), + }; + }, + }, + }, + }); + + const RUNS = 2; + + await Sha2_384Program.compile(); + + await equivalentAsync( + { + from: [bytes(192)], + to: bytes(48), + }, + { runs: RUNS } + )(nobleSha384, async (x) => { + const { proof } = await Sha2_384Program.sha384(x); + await Sha2_384Program.verify(proof); + return proof.publicOutput; + }); + + for (let { preimage, hash } of testVectors()) { + let actual = Gadgets.SHA2.hash(384, Bytes.fromString(preimage)); + expect(actual.toHex()).toEqual(hash); + } + + function testVectors() { + return [ + { + preimage: 'abc', + hash: 'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7', + }, + { + preimage: 'a'.repeat(1000000), + hash: '9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985', + }, + { + preimage: '', + hash: '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b', + }, + { + preimage: + 'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu', + hash: '09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039', + }, + ]; + } +} + +// SHA2-512 TESTS +{ + sample(Random.nat(400), 5).forEach((preimageLength) => { + let inputBytes = bytes(preimageLength); + let outputBytes = bytes(512 / 8); + + equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( + (x) => nobleSha512(x), + (x) => Gadgets.SHA2.hash(512, x), + `sha512 preimage length ${preimageLength}` + ); + }); + + const Sha2_512Program = ZkProgram({ + name: `sha2-512`, + publicOutput: Bytes(64), + methods: { + sha512: { + privateInputs: [Bytes(192)], + async method(preImage: Bytes) { + return { + publicOutput: Gadgets.SHA2.hash(512, preImage), + }; + }, + }, + }, + }); + + const RUNS = 2; + + await Sha2_512Program.compile(); + + await equivalentAsync( + { + from: [bytes(192)], + to: bytes(64), + }, + { runs: RUNS } + )(nobleSha512, async (x) => { + const { proof } = await Sha2_512Program.sha512(x); + await Sha2_512Program.verify(proof); + return proof.publicOutput; + }); + + for (let { preimage, hash } of testVectors()) { + let actual = Gadgets.SHA2.hash(512, Bytes.fromString(preimage)); + expect(actual.toHex()).toEqual(hash); + } + + function testVectors() { + return [ + { + preimage: 'abc', + hash: 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f', + }, + { + preimage: 'a'.repeat(1000000), + hash: 'e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b', + }, + { + preimage: '', + hash: 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e', + }, + { + preimage: + 'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu', + hash: '8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909', + }, + ]; + } +} From 4f40f97605d93f8494b450fd6923bc87aa4b346c Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 5 Dec 2024 19:56:51 +0100 Subject: [PATCH 17/25] undo name change --- src/lib/provable/gadgets/sha256.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/provable/gadgets/sha256.ts b/src/lib/provable/gadgets/sha256.ts index 594251063b..b4c91122ef 100644 --- a/src/lib/provable/gadgets/sha256.ts +++ b/src/lib/provable/gadgets/sha256.ts @@ -16,7 +16,7 @@ import { divMod32 } from './arithmetic.js'; import { bitSlice } from './common.js'; import { rangeCheck16 } from './range-check.js'; -export { SHA2 }; +export { SHA256 }; const SHA256Constants = { // constants §4.2.2 From 924bb8f69e2d201a929953a832c9205be4961e43 Mon Sep 17 00:00:00 2001 From: querolita Date: Mon, 16 Dec 2024 16:18:25 +0100 Subject: [PATCH 18/25] remove unused web3 type import --- flake.lock | 4 ++-- src/lib/provable/gadgets/sha2.ts | 1 - src/lib/provable/test/sha2.unit-test.ts | 5 ++++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index 49061b3547..7653c47f47 100644 --- a/flake.lock +++ b/flake.lock @@ -265,8 +265,8 @@ "utils": "utils" }, "locked": { - "lastModified": 1733429866, - "narHash": "sha256-/ZEGYdZ2hLjBwdEzG/BIjlDehOjuGWmBqzD55nXfZoY=", + "lastModified": 1734361061, + "narHash": "sha256-3e82deloDgZaV7hB8P6NfMEavRyrH/pB/E7AqWbxOs8=", "path": "src/mina", "type": "path" }, diff --git a/src/lib/provable/gadgets/sha2.ts b/src/lib/provable/gadgets/sha2.ts index f787d051ac..e4d13b940c 100644 --- a/src/lib/provable/gadgets/sha2.ts +++ b/src/lib/provable/gadgets/sha2.ts @@ -9,7 +9,6 @@ import { TupleN } from '../../util/types.js'; import { divMod32, divMod64 } from './arithmetic.js'; import { bitSlice } from './common.js'; import { rangeCheck16 } from './range-check.js'; -import { Uint } from 'web3'; export { SHA2 }; diff --git a/src/lib/provable/test/sha2.unit-test.ts b/src/lib/provable/test/sha2.unit-test.ts index 339ade11fa..3ff6f4b29b 100644 --- a/src/lib/provable/test/sha2.unit-test.ts +++ b/src/lib/provable/test/sha2.unit-test.ts @@ -19,7 +19,7 @@ import { } from '../../testing/equivalent.js'; import { Random, sample } from '../../testing/random.js'; import { expect } from 'expect'; - +/* // SHA2-224 TESTS { sample(Random.nat(400), 5).forEach((preimageLength) => { @@ -91,6 +91,7 @@ import { expect } from 'expect'; ]; } } + */ // SHA2-256 TESTS { @@ -164,6 +165,7 @@ import { expect } from 'expect'; } } +/* // SHA2-384 TESTS { sample(Random.nat(400), 5).forEach((preimageLength) => { @@ -307,3 +309,4 @@ import { expect } from 'expect'; ]; } } +*/ From 0b934e7aa1e2cd8926eaf4c3b227efbb88251fc4 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 17 Dec 2024 20:27:50 +0100 Subject: [PATCH 19/25] pass flag because ts cannot infer types from runtime values --- src/lib/provable/gadgets/sha2.ts | 138 ++++++++++++------------ src/lib/provable/test/sha2.unit-test.ts | 5 +- 2 files changed, 67 insertions(+), 76 deletions(-) diff --git a/src/lib/provable/gadgets/sha2.ts b/src/lib/provable/gadgets/sha2.ts index e4d13b940c..6c61c6495b 100644 --- a/src/lib/provable/gadgets/sha2.ts +++ b/src/lib/provable/gadgets/sha2.ts @@ -155,20 +155,23 @@ const SHA2 = { * ``` * */ - hash(length: 224 | 256 | 384 | 512, data: FlexibleBytes): Bytes { + hash(length: T, data: FlexibleBytes): Bytes { // Infer the type T based on the value of `length` (conditional type) - type WordType = typeof length extends 224 | 256 ? UInt32 : UInt64; + type Type = T extends 224 | 256 ? UInt32 : UInt64; + + let is256 = length === 224 || length === 256; // preprocessing §6.2 // padding the message $5.1.1 into blocks that are a multiple of 512 - let messageBlocks = padding(data); + let messageBlocks = padding(data, is256); + + let H = SHA2.initialState(length); - let H = SHA2.initialState(length); const N = messageBlocks.length; for (let i = 0; i < N; i++) { - const W = messageSchedule(messageBlocks[i]); - H = compression(H, W); + const W = messageSchedule(messageBlocks[i], is256); + H = compression(H, W, is256); } // the working variables H[i] are 32 | 64 bit, however we want to decompose @@ -205,29 +208,28 @@ const SHA2 = { * Padding function for SHA2, as specified in §5.1.1, §5.1.2, * * @param data The message to hash - * @param is_short Whether this is a short SHA2 (SHA2-224 or SHA2-256) or not (SHA2-384 or SHA2-512) + * @param is256 Whether this is a short SHA2 (SHA2-224 or SHA2-256) or not (SHA2-384 or SHA2-512) * @returns */ // The only difference between the padding used in SHA2-224/256 and SHA2-384/512 // is the size of the word (32bit vs 64bit). In the first case, UInt32[][] is // returned, in the second case UInt64[][] is returned. -function padding(data: FlexibleBytes): T[][] { - const is_short = isShort(); +function padding(data: FlexibleBytes, is256: boolean): T[][] { // create a provable Bytes instance from the input data // the Bytes class will be static sized according to the length of the input data let message = Bytes.from(data); // Whether this is a short SHA2 (SHA2-224 or SHA2-256) or not (SHA2-384 or SHA2-512) - const blockLength = is_short + const blockLength = is256 ? SHA2Constants.BLOCK_LENGTH_224_256 : SHA2Constants.BLOCK_LENGTH_384_512; - const paddingValue = is_short + const paddingValue = is256 ? SHA2Constants.PADDING_VALUE_224_256 : SHA2Constants.PADDING_VALUE_384_512; - const lengthChunk = is_short + const lengthChunk = is256 ? SHA2Constants.LENGTH_CHUNK_224_256 : SHA2Constants.LENGTH_CHUNK_384_512; @@ -258,7 +260,7 @@ function padding(data: FlexibleBytes): T[][] { // Create chunks based on whether we are dealing with SHA2-224/256 or SHA2-384/512 // split the message into (32 | 64)-bit chunks - let chunks = is_short + let chunks = is256 ? createChunks(paddedMessage, 4, UInt32.fromBytesBE) : createChunks(paddedMessage, 8, UInt64.fromBytesBE); @@ -274,17 +276,15 @@ function padding(data: FlexibleBytes): T[][] { * or the 1024-bit message block (16-element array of UInt64). * @returns The message schedule (64-element array of UInt32 or 80-element array of UInt64). */ -function messageSchedule(M: T[]): T[] { +function messageSchedule(M: T[], is256: boolean): T[] { // §6.2.2.1 and §6.4.2.1 // Declare W as an empty array of type T[] (generic array) const W: T[] = []; - let is_short = isShort(); - // for each message block of 16 x 32bit | 64bit do: - let numWords = is_short + let numWords = is256 ? SHA2Constants.NUM_WORDS_224_256 : SHA2Constants.NUM_WORDS_384_512; @@ -293,12 +293,12 @@ function messageSchedule(M: T[]): T[] { for (let t = 16; t < numWords; t++) { // the field element is unreduced and not proven to be 32bit | 64bit, // we will do this later to save constraints - let unreduced = DeltaOne(W[t - 2]) + let unreduced = DeltaOne(W[t - 2], is256) .value.add(W[t - 7].value) - .add(DeltaZero(W[t - 15]).value.add(W[t - 16].value)); + .add(DeltaZero(W[t - 15], is256).value.add(W[t - 16].value)); // mod 32 | 64 bit the unreduced field element - W[t] = reduceMod(unreduced); + W[t] = reduceMod(unreduced, is256); } return W; @@ -312,13 +312,12 @@ function messageSchedule(M: T[]): T[] { * * @returns The updated intermediate hash values after compression. */ -function compression([...H]: T[], W: T[]) { - let is_short = isShort(); - let numWords = is_short +function compression([...H]: T[], W: T[], is256: boolean) { + let numWords = is256 ? SHA2Constants.NUM_WORDS_224_256 : SHA2Constants.NUM_WORDS_384_512; - let k = is_short ? SHA2Constants.K224_256 : SHA2Constants.K384_512; + let k = is256 ? SHA2Constants.K224_256 : SHA2Constants.K384_512; // §6.2.2.2 and §6.4.2.2: // initialize working variables @@ -337,49 +336,40 @@ function compression([...H]: T[], W: T[]) { // T1 is unreduced and not proven to be 32 | 64 bit, // we will do this later to save constraints const unreducedT1 = h.value - .add(SigmaOne(e).value) - .add(Ch(e, f, g).value) + .add(SigmaOne(e, is256).value) + .add(Ch(e, f, g, is256).value) .add(k[t]) .add(W[t].value) .seal(); // T2 is also unreduced - const unreducedT2 = SigmaZero(a).value.add(Maj(a, b, c).value); + const unreducedT2 = SigmaZero(a, is256).value.add(Maj(a, b, c, is256).value); h = g; g = f; f = e; - e = reduceMod(d.value.add(unreducedT1)); + e = reduceMod(d.value.add(unreducedT1), is256); // mod 32 | 64 bit the unreduced field element d = c; c = b; b = a; - a = reduceMod(unreducedT2.add(unreducedT1)); + a = reduceMod(unreducedT2.add(unreducedT1), is256); // mod 32 | 64 bit } // §6.2.2.4 and §6.4.2.4 // new intermediate hash value - if (is_short) { - intermediateHash([a, b, c, d, e, f, g, h], H, UInt32.prototype.addMod32); - } else { - intermediateHash([a, b, c, d, e, f, g, h], H, UInt64.prototype.addMod64); - } + intermediateHash([a, b, c, d, e, f, g, h], H, is256); return H; } // Helper functions -// Helper function to check if the hash length is short (SHA2-224 or SHA2-256) -// depending on the generic type T used for the word size (UInt32 or UInt64) -function isShort(): boolean { - return ({} as T) instanceof UInt32; -} // Shorthand to reduce a field element modulo 32 or 64 bits depending on T -function reduceMod(x: Field): T { - if (isShort()) { +function reduceMod(x: Field, is256: boolean): T { + if (is256) { return UInt32.Unsafe.fromField(divMod32(x, 32 + 16).remainder) as T; } else { return UInt64.Unsafe.fromField(divMod64(x, 64 + 16).remainder) as T; @@ -401,22 +391,24 @@ function createChunks( return chunks; } -function intermediateHash( - variables: T[], - H: T[], - addMod: Function -) { - for (let i = 0; i < 8; i++) { - H[i] = addMod(variables[i], H[i]) as T; +function intermediateHash(variables: T[], H: T[], is256: boolean) { + if (is256) { + for (let i = 0; i < 8; i++) { + H[i] = (variables[i] as UInt32).addMod32(H[i] as UInt32) as T; + } + } else { + for (let i = 0; i < 8; i++) { + H[i] = (variables[i] as UInt64).addMod64(H[i] as UInt64) as T; + } } } // Subroutines for SHA2 -function Ch(x: T, y: T, z: T): T { +function Ch(x: T, y: T, z: T, is256: boolean): T { // ch(x, y, z) = (x & y) ^ (~x & z) // = (x & y) + (~x & z) (since x & ~x = 0) - if (isShort()) { + if (is256) { let xAndY = (x as UInt32).and(y as UInt32).value; let xNotAndZ = (x as UInt32).not().and(z as UInt32).value; let ch = xAndY.add(xNotAndZ).seal(); @@ -429,10 +421,10 @@ function Ch(x: T, y: T, z: T): T { } } -function Maj(x: T, y: T, z: T): T { +function Maj(x: T, y: T, z: T, is256: boolean): T { // maj(x, y, z) = (x & y) ^ (x & z) ^ (y & z) // = (x + y + z - (x ^ y ^ z)) / 2 - if (isShort()) { + if (is256) { let sum = (x as UInt32).value.add(y.value).add(z.value).seal(); let xor = (x as UInt32).xor(y as UInt32).xor(z as UInt32).value; let maj = sum.sub(xor).div(2).seal(); @@ -445,30 +437,30 @@ function Maj(x: T, y: T, z: T): T { } } -function SigmaZero(x: T) { - return isShort() - ? sigma(x, SHA2Constants.SIGMA_ZERO_224_256) - : sigma(x, SHA2Constants.SIGMA_ZERO_384_512); +function SigmaZero(x: T, is256: boolean) { + return is256 + ? sigma(x, SHA2Constants.SIGMA_ZERO_224_256, false, is256) + : sigma(x, SHA2Constants.SIGMA_ZERO_384_512, false, is256); } -function SigmaOne(x: T) { - return isShort() - ? sigma(x, SHA2Constants.SIGMA_ONE_224_256) - : sigma(x, SHA2Constants.SIGMA_ONE_384_512); +function SigmaOne(x: T, is256: boolean) { + return is256 + ? sigma(x, SHA2Constants.SIGMA_ONE_224_256, false, is256) + : sigma(x, SHA2Constants.SIGMA_ONE_384_512, false, is256); } // lowercase sigma = delta to avoid confusing function names -function DeltaZero(x: T): T { - return isShort() - ? sigma(x, SHA2Constants.DELTA_ZERO_224_256, true) - : sigma(x, SHA2Constants.DELTA_ZERO_384_512, true); +function DeltaZero(x: T, is256: boolean): T { + return is256 + ? sigma(x, SHA2Constants.DELTA_ZERO_224_256, true, is256) + : sigma(x, SHA2Constants.DELTA_ZERO_384_512, true, is256); } -function DeltaOne(x: T): T { - return isShort() - ? sigma(x, SHA2Constants.DELTA_ONE_224_256, true) - : sigma(x, SHA2Constants.DELTA_ONE_384_512, true); +function DeltaOne(x: T, is256: boolean): T { + return is256 + ? sigma(x, SHA2Constants.DELTA_ONE_224_256, true, is256) + : sigma(x, SHA2Constants.DELTA_ONE_384_512, true, is256); } function ROTR(n: number, x: T): T { @@ -482,10 +474,11 @@ function SHR(n: number, x: T): T { function sigmaSimple( u: T, bits: TupleN, - firstShifted = false + firstShifted = false, + is256: boolean ): T { let [r0, r1, r2] = bits; - if (isShort()) { + if (is256) { let rot0 = firstShifted ? (SHR(r0, u) as UInt32) : (ROTR(r0, u) as UInt32); let rot1 = ROTR(r1, u) as UInt32; let rot2 = ROTR(r2, u) as UInt32; @@ -501,9 +494,10 @@ function sigmaSimple( function sigma( u: T, bits: TupleN, - firstShifted = false + firstShifted = false, + is256: boolean ): T { - if (u.isConstant() || !isShort()) return sigmaSimple(u, bits, firstShifted); + if (u.isConstant() || !is256) return sigmaSimple(u, bits, firstShifted, is256); // When T is UInt64, 64-bit rotation is natively supported in the gadgets. // However, 32-bit rotation is not natively supported, thus the following: diff --git a/src/lib/provable/test/sha2.unit-test.ts b/src/lib/provable/test/sha2.unit-test.ts index 3ff6f4b29b..339ade11fa 100644 --- a/src/lib/provable/test/sha2.unit-test.ts +++ b/src/lib/provable/test/sha2.unit-test.ts @@ -19,7 +19,7 @@ import { } from '../../testing/equivalent.js'; import { Random, sample } from '../../testing/random.js'; import { expect } from 'expect'; -/* + // SHA2-224 TESTS { sample(Random.nat(400), 5).forEach((preimageLength) => { @@ -91,7 +91,6 @@ import { expect } from 'expect'; ]; } } - */ // SHA2-256 TESTS { @@ -165,7 +164,6 @@ import { expect } from 'expect'; } } -/* // SHA2-384 TESTS { sample(Random.nat(400), 5).forEach((preimageLength) => { @@ -309,4 +307,3 @@ import { expect } from 'expect'; ]; } } -*/ From d2fef76f8e1c1cfbf801900a49597acda751ca37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Wed, 18 Dec 2024 15:29:16 +0000 Subject: [PATCH 20/25] refactor constants --- src/lib/provable/gadgets/sha2.ts | 233 +++++++++++++++++-------------- 1 file changed, 129 insertions(+), 104 deletions(-) diff --git a/src/lib/provable/gadgets/sha2.ts b/src/lib/provable/gadgets/sha2.ts index 6c61c6495b..7ea829fdcc 100644 --- a/src/lib/provable/gadgets/sha2.ts +++ b/src/lib/provable/gadgets/sha2.ts @@ -12,60 +12,9 @@ import { rangeCheck16 } from './range-check.js'; export { SHA2 }; -// SHA2 CONSTANTS -const SHA2Constants = { - // Bit length of the blocks used in SHA2-224 and SHA2-256 - BLOCK_LENGTH_224_256: 512n, - - // Bit length of the blocks used in SHA2-384 and SHA2-512 - BLOCK_LENGTH_384_512: 1024n, - - // Value used in the padding equation for SHA2-224 and SHA2-256 - PADDING_VALUE_224_256: 448n, - - // Value used in the padding equation for SHA2-384 and SHA2-512 - PADDING_VALUE_384_512: 896n, - - // Bits used to store the length in the padding of SHA2-224 and SHA2-256 - // It corresponds to 512 - 448 = 64 - LENGTH_CHUNK_224_256: 64, - - // Bits used to store the length in the padding of SHA2-384 and SHA2-512 - // It corresponds to 1024 - 896 = 128 - LENGTH_CHUNK_384_512: 128, - - // Number of words in message schedule and compression for SHA2-224 and SHA2-256 - NUM_WORDS_224_256: 64, - - // Number of words in message schedule and compression for SHA2-384 and SHA2-512 - NUM_WORDS_384_512: 80, - - // Offsets for the DeltaZero function for SHA2 - // - SHA2-224 and SHA2-256: §4.1.2 eq.4.6 - DELTA_ZERO_224_256: [3, 7, 18] as TupleN, - // - SHA2-384 and SHA2-512: §4.1.3 eq.4.12 - DELTA_ZERO_384_512: [7, 8, 1] as TupleN, - - // Offsets for the DeltaOne function for SHA2. - // - SHA2-224 and SHA2-256: §4.1.2 eq.4.7 - DELTA_ONE_224_256: [10, 17, 19] as TupleN, - // - SHA2-384 and SHA2-512: §4.1.3 eq.4.13 - DELTA_ONE_384_512: [6, 19, 61] as TupleN, - - // Offsets for the SigmaZero function for SHA2. - // - SHA2-224 and SHA2-256: §4.1.2 eq.4.4 - SIGMA_ZERO_224_256: [2, 13, 22] as TupleN, - // - SHA2-384 and SHA2-512: §4.1.3 eq.4.10 - SIGMA_ZERO_384_512: [28, 34, 39] as TupleN, - - // Offsets for the SigmaOne function for SHA2. - // - SHA2-224 and SHA2-256: §4.1.2 eq.4.5 - SIGMA_ONE_224_256: [6, 11, 25] as TupleN, - // - SHA2-384 and SHA2-512: §4.1.3 eq.4.11 - SIGMA_ONE_384_512: [14, 18, 41] as TupleN, // constants for SHA2-224 and SHA2-256 §4.2.2 - K224_256: [ + const K224_256= [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, @@ -77,10 +26,11 @@ const SHA2Constants = { 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, - ], + ]; + // constants for SHA2-384 and SHA2-512 §4.2.3 // prettier-ignore - K384_512: [ + const K384_512 = [ 0x428a2f98d728ae22n, 0x7137449123ef65cdn, 0xb5c0fbcfec4d3b2fn, 0xe9b5dba58189dbbcn, 0x3956c25bf348b538n, 0x59f111f1b605d019n, 0x923f82a4af194f9bn, 0xab1c5ed5da6d8118n, 0xd807aa98a3030242n, 0x12835b0145706fben, 0x243185be4ee4b28cn, 0x550c7dc3d5ffb4e2n, @@ -101,29 +51,108 @@ const SHA2Constants = { 0x06f067aa72176fban, 0x0a637dc5a2c898a6n, 0x113f9804bef90daen, 0x1b710b35131c471bn, 0x28db77f523047d84n, 0x32caab7b40c72493n, 0x3c9ebe0a15c9bebcn, 0x431d67c49c100d4cn, 0x4cc5d4becb3e42b6n, 0x597f299cfc657e2an, 0x5fcb6fab3ad6faecn, 0x6c44198c4a475817n, - ], - // initial hash values for SHA2-224 §5.3.2 - H224: [ - 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, - 0x64f98fa7, 0xbefa4fa4, - ], - // initial hash values for SHA-256 §5.3.3 - H256: [ - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, - 0x1f83d9ab, 0x5be0cd19, - ], - // initial hash values for SHA2-384 §5.3.4 - // prettier-ignore - H384: [ - 0xcbbb9d5dc1059ed8n, 0x629a292a367cd507n, 0x9159015a3070dd17n, 0x152fecd8f70e5939n, - 0x67332667ffc00b31n, 0x8eb44a8768581511n, 0xdb0c2e0d64f98fa7n, 0x47b5481dbefa4fa4n, - ], - // initial hash values for SHA2-512 §5.3.5 - // prettier-ignore - H512: [ - 0x6a09e667f3bcc908n, 0xbb67ae8584caa73bn, 0x3c6ef372fe94f82bn, 0xa54ff53a5f1d36f1n, - 0x510e527fade682d1n, 0x9b05688c2b3e6c1fn, 0x1f83d9abfb41bd6bn, 0x5be0cd19137e2179n, - ], + ]; + +// SHA2 CONSTANTS +const SHA2Constants = { + + // Bit length of the blocks + BLOCK_LENGTH: { + // SHA2-224 and SHA2-256 + 256: 512n, + // SHA2-384 and SHA2-512 + 512: 1024n, + }, + + // Value used in the padding equation + PADDING_VALUE: { + // SHA2-224 and SHA2-256 + 256: 448n, + // SHA2-384 and SHA2-512 + 512: 1024n, + }, + + // Bits used to store the length in the padding + // It corresponds to BLOCK_LENGTH - PADDING_VALUE + LENGTH_CHUNK: { + // SHA2-224 and SHA2-256 + 256: 64, + // SHA2-384 and SHA2-512 + 512: 128, + }, + + // Number of words in message schedule and compression + NUM_WORDS: { + // SHA2-224 and SHA2-256 + 256: 64n, + // SHA2-384 and SHA2-512 + 512: 80n, + }, + + // Offsets for the DeltaZero function + DELTA_ZERO: { + // SHA2-224 and SHA2-256: §4.1.2 eq.4.6 + 256: [3, 7, 18] as TupleN, + // SHA2-384 and SHA2-512: §4.1.3 eq.4.12 + 512: [7, 8, 1] as TupleN, + }, + + // Offsets for the DeltaOne function + DELTA_ONE: { + // SHA2-224 and SHA2-256: §4.1.2 eq.4.7 + 256: [10, 17, 19] as TupleN, + // - SHA2-384 and SHA2-512: §4.1.3 eq.4.13 + 384: [6, 19, 61] as TupleN, + 512: [6, 19, 61] as TupleN, + }, + + // Offsets for the SigmaZero function + SIGMA_ZERO: { + // SHA2-224 and SHA2-256: §4.1.2 eq.4.4 + 256: [2, 13, 22] as TupleN, + // SHA2-384 and SHA2-512: §4.1.3 eq.4.10 + 512: [28, 34, 39] as TupleN, + }, + + // Offsets for the SigmaOne function for SHA2. + SIGMA_ONE: { + // SHA2-224 and SHA2-256: §4.1.2 eq.4.5 + 256: [6, 11, 25] as TupleN, + // SHA2-384 and SHA2-512: §4.1.3 eq.4.11 + 512: [14, 18, 41] as TupleN, + }, + + // Initia hash values + H: { + // SHA2-224 §5.3.2 + 224: [ + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, + 0x64f98fa7, 0xbefa4fa4, + ], + // SHA-256 §5.3.3 + 256: [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19, + ], + // SHA2-384 §5.3.4 + 384:[ + 0xcbbb9d5dc1059ed8n, 0x629a292a367cd507n, 0x9159015a3070dd17n, 0x152fecd8f70e5939n, + 0x67332667ffc00b31n, 0x8eb44a8768581511n, 0xdb0c2e0d64f98fa7n, 0x47b5481dbefa4fa4n, + ], + // SHA2-512 §5.3.5 + 512:[ + 0x6a09e667f3bcc908n, 0xbb67ae8584caa73bn, 0x3c6ef372fe94f82bn, 0xa54ff53a5f1d36f1n, + 0x510e527fade682d1n, 0x9b05688c2b3e6c1fn, 0x1f83d9abfb41bd6bn, 0x5be0cd19137e2179n, + ], + }, + + K: { + 224: K224_256, + 256: K224_256, + 384: K384_512, + 512: K384_512, + }, + }; const SHA2 = { @@ -190,14 +219,10 @@ const SHA2 = { padding, initialState(length: 224 | 256 | 384 | 512): T[] { switch (length) { - case 224: - return SHA2Constants.H224.map((x) => UInt32.from(x) as T); - case 256: - return SHA2Constants.H256.map((x) => UInt32.from(x) as T); - case 384: - return SHA2Constants.H384.map((x) => UInt64.from(x) as T); - case 512: - return SHA2Constants.H512.map((x) => UInt64.from(x) as T); + case 224 | 256: + return SHA2Constants.H[length].map((x) => UInt32.from(x) as T); + case 384 | 512: + return SHA2Constants.H[length].map((x) => UInt64.from(x) as T); default: throw new Error('Invalid hash length'); } @@ -222,16 +247,16 @@ function padding(data: FlexibleBytes, is256: boolean) // Whether this is a short SHA2 (SHA2-224 or SHA2-256) or not (SHA2-384 or SHA2-512) const blockLength = is256 - ? SHA2Constants.BLOCK_LENGTH_224_256 - : SHA2Constants.BLOCK_LENGTH_384_512; + ? SHA2Constants.BLOCK_LENGTH[256] + : SHA2Constants.BLOCK_LENGTH[512]; const paddingValue = is256 - ? SHA2Constants.PADDING_VALUE_224_256 - : SHA2Constants.PADDING_VALUE_384_512; + ? SHA2Constants.PADDING_VALUE[256] + : SHA2Constants.PADDING_VALUE[512]; const lengthChunk = is256 - ? SHA2Constants.LENGTH_CHUNK_224_256 - : SHA2Constants.LENGTH_CHUNK_384_512; + ? SHA2Constants.LENGTH_CHUNK[256] + : SHA2Constants.LENGTH_CHUNK[512]; // now pad the data to reach the format expected by SHA2 // pad 1 bit, followed by k zero bits where k is the smallest non-negative solution to @@ -285,8 +310,8 @@ function messageSchedule(M: T[], is256: boolean): T[] // for each message block of 16 x 32bit | 64bit do: let numWords = is256 - ? SHA2Constants.NUM_WORDS_224_256 - : SHA2Constants.NUM_WORDS_384_512; + ? SHA2Constants.NUM_WORDS[256] + : SHA2Constants.NUM_WORDS[512]; // prepare message block for (let t = 0; t < 16; t++) W[t] = M[t]; @@ -314,10 +339,10 @@ function messageSchedule(M: T[], is256: boolean): T[] */ function compression([...H]: T[], W: T[], is256: boolean) { let numWords = is256 - ? SHA2Constants.NUM_WORDS_224_256 - : SHA2Constants.NUM_WORDS_384_512; + ? SHA2Constants.NUM_WORDS[256] + : SHA2Constants.NUM_WORDS[512]; - let k = is256 ? SHA2Constants.K224_256 : SHA2Constants.K384_512; + let k = is256 ? SHA2Constants.K[256] : SHA2Constants.K[512]; // §6.2.2.2 and §6.4.2.2: // initialize working variables @@ -439,28 +464,28 @@ function Maj(x: T, y: T, z: T, is256: boolean): T { function SigmaZero(x: T, is256: boolean) { return is256 - ? sigma(x, SHA2Constants.SIGMA_ZERO_224_256, false, is256) - : sigma(x, SHA2Constants.SIGMA_ZERO_384_512, false, is256); + ? sigma(x, SHA2Constants.SIGMA_ZERO[256], false, is256) + : sigma(x, SHA2Constants.SIGMA_ZERO[512], false, is256); } function SigmaOne(x: T, is256: boolean) { return is256 - ? sigma(x, SHA2Constants.SIGMA_ONE_224_256, false, is256) - : sigma(x, SHA2Constants.SIGMA_ONE_384_512, false, is256); + ? sigma(x, SHA2Constants.SIGMA_ONE[256], false, is256) + : sigma(x, SHA2Constants.SIGMA_ONE[512], false, is256); } // lowercase sigma = delta to avoid confusing function names function DeltaZero(x: T, is256: boolean): T { return is256 - ? sigma(x, SHA2Constants.DELTA_ZERO_224_256, true, is256) - : sigma(x, SHA2Constants.DELTA_ZERO_384_512, true, is256); + ? sigma(x, SHA2Constants.DELTA_ZERO[256], true, is256) + : sigma(x, SHA2Constants.DELTA_ZERO[512], true, is256); } function DeltaOne(x: T, is256: boolean): T { return is256 - ? sigma(x, SHA2Constants.DELTA_ONE_224_256, true, is256) - : sigma(x, SHA2Constants.DELTA_ONE_384_512, true, is256); + ? sigma(x, SHA2Constants.DELTA_ONE[256], true, is256) + : sigma(x, SHA2Constants.DELTA_ONE[512], true, is256); } function ROTR(n: number, x: T): T { From 47e12dae7843fa035299867bd785e0dce69ab2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Wed, 18 Dec 2024 16:27:24 +0000 Subject: [PATCH 21/25] pass length to functions to improve readability --- src/lib/provable/gadgets/sha2.ts | 231 +++++++++++++++++-------------- 1 file changed, 128 insertions(+), 103 deletions(-) diff --git a/src/lib/provable/gadgets/sha2.ts b/src/lib/provable/gadgets/sha2.ts index 7ea829fdcc..b379ce966b 100644 --- a/src/lib/provable/gadgets/sha2.ts +++ b/src/lib/provable/gadgets/sha2.ts @@ -12,25 +12,24 @@ import { rangeCheck16 } from './range-check.js'; export { SHA2 }; - - // constants for SHA2-224 and SHA2-256 §4.2.2 - const K224_256= [ - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, - 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, - 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, - 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, - 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, - 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, - 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, - 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, - 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, - ]; - - // constants for SHA2-384 and SHA2-512 §4.2.3 - // prettier-ignore - const K384_512 = [ +// constants for SHA2-224 and SHA2-256 §4.2.2 +const K224_256 = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +]; + +// constants for SHA2-384 and SHA2-512 §4.2.3 +// prettier-ignore +const K384_512 = [ 0x428a2f98d728ae22n, 0x7137449123ef65cdn, 0xb5c0fbcfec4d3b2fn, 0xe9b5dba58189dbbcn, 0x3956c25bf348b538n, 0x59f111f1b605d019n, 0x923f82a4af194f9bn, 0xab1c5ed5da6d8118n, 0xd807aa98a3030242n, 0x12835b0145706fben, 0x243185be4ee4b28cn, 0x550c7dc3d5ffb4e2n, @@ -53,53 +52,65 @@ export { SHA2 }; 0x4cc5d4becb3e42b6n, 0x597f299cfc657e2an, 0x5fcb6fab3ad6faecn, 0x6c44198c4a475817n, ]; +type Length = 224 | 256 | 384 | 512; + // SHA2 CONSTANTS const SHA2Constants = { - // Bit length of the blocks BLOCK_LENGTH: { // SHA2-224 and SHA2-256 + 224: 512n, 256: 512n, // SHA2-384 and SHA2-512 + 384: 1024n, 512: 1024n, }, // Value used in the padding equation PADDING_VALUE: { // SHA2-224 and SHA2-256 + 224: 448n, 256: 448n, // SHA2-384 and SHA2-512 - 512: 1024n, - }, + 384: 896n, + 512: 896n, + }, // Bits used to store the length in the padding // It corresponds to BLOCK_LENGTH - PADDING_VALUE LENGTH_CHUNK: { // SHA2-224 and SHA2-256 + 224: 64, 256: 64, // SHA2-384 and SHA2-512 + 384: 128, 512: 128, }, // Number of words in message schedule and compression NUM_WORDS: { // SHA2-224 and SHA2-256 + 224: 64n, 256: 64n, // SHA2-384 and SHA2-512 + 384: 80n, 512: 80n, - }, + }, // Offsets for the DeltaZero function DELTA_ZERO: { // SHA2-224 and SHA2-256: §4.1.2 eq.4.6 + 224: [3, 7, 18] as TupleN, 256: [3, 7, 18] as TupleN, // SHA2-384 and SHA2-512: §4.1.3 eq.4.12 + 384: [7, 8, 1] as TupleN, 512: [7, 8, 1] as TupleN, }, - // Offsets for the DeltaOne function + // Offsets for the DeltaOne function DELTA_ONE: { // SHA2-224 and SHA2-256: §4.1.2 eq.4.7 + 224: [10, 17, 19] as TupleN, 256: [10, 17, 19] as TupleN, // - SHA2-384 and SHA2-512: §4.1.3 eq.4.13 384: [6, 19, 61] as TupleN, @@ -109,16 +120,20 @@ const SHA2Constants = { // Offsets for the SigmaZero function SIGMA_ZERO: { // SHA2-224 and SHA2-256: §4.1.2 eq.4.4 + 224: [2, 13, 22] as TupleN, 256: [2, 13, 22] as TupleN, - // SHA2-384 and SHA2-512: §4.1.3 eq.4.10 + // SHA2-384 and SHA2-512: §4.1.3 eq.4.10 + 384: [28, 34, 39] as TupleN, 512: [28, 34, 39] as TupleN, }, // Offsets for the SigmaOne function for SHA2. SIGMA_ONE: { // SHA2-224 and SHA2-256: §4.1.2 eq.4.5 + 224: [6, 11, 25] as TupleN, 256: [6, 11, 25] as TupleN, // SHA2-384 and SHA2-512: §4.1.3 eq.4.11 + 384: [14, 18, 41] as TupleN, 512: [14, 18, 41] as TupleN, }, @@ -135,14 +150,26 @@ const SHA2Constants = { 0x1f83d9ab, 0x5be0cd19, ], // SHA2-384 §5.3.4 - 384:[ - 0xcbbb9d5dc1059ed8n, 0x629a292a367cd507n, 0x9159015a3070dd17n, 0x152fecd8f70e5939n, - 0x67332667ffc00b31n, 0x8eb44a8768581511n, 0xdb0c2e0d64f98fa7n, 0x47b5481dbefa4fa4n, + 384: [ + 0xcbbb9d5dc1059ed8n, + 0x629a292a367cd507n, + 0x9159015a3070dd17n, + 0x152fecd8f70e5939n, + 0x67332667ffc00b31n, + 0x8eb44a8768581511n, + 0xdb0c2e0d64f98fa7n, + 0x47b5481dbefa4fa4n, ], // SHA2-512 §5.3.5 - 512:[ - 0x6a09e667f3bcc908n, 0xbb67ae8584caa73bn, 0x3c6ef372fe94f82bn, 0xa54ff53a5f1d36f1n, - 0x510e527fade682d1n, 0x9b05688c2b3e6c1fn, 0x1f83d9abfb41bd6bn, 0x5be0cd19137e2179n, + 512: [ + 0x6a09e667f3bcc908n, + 0xbb67ae8584caa73bn, + 0x3c6ef372fe94f82bn, + 0xa54ff53a5f1d36f1n, + 0x510e527fade682d1n, + 0x9b05688c2b3e6c1fn, + 0x1f83d9abfb41bd6bn, + 0x5be0cd19137e2179n, ], }, @@ -152,7 +179,6 @@ const SHA2Constants = { 384: K384_512, 512: K384_512, }, - }; const SHA2 = { @@ -184,23 +210,21 @@ const SHA2 = { * ``` * */ - hash(length: T, data: FlexibleBytes): Bytes { + hash(length: T, data: FlexibleBytes): Bytes { // Infer the type T based on the value of `length` (conditional type) type Type = T extends 224 | 256 ? UInt32 : UInt64; - let is256 = length === 224 || length === 256; - // preprocessing §6.2 // padding the message $5.1.1 into blocks that are a multiple of 512 - let messageBlocks = padding(data, is256); + let messageBlocks = padding(length, data); let H = SHA2.initialState(length); const N = messageBlocks.length; for (let i = 0; i < N; i++) { - const W = messageSchedule(messageBlocks[i], is256); - H = compression(H, W, is256); + const W = messageSchedule(length, messageBlocks[i]); + H = compression(length, H, W); } // the working variables H[i] are 32 | 64 bit, however we want to decompose @@ -217,7 +241,7 @@ const SHA2 = { compression, messageSchedule, padding, - initialState(length: 224 | 256 | 384 | 512): T[] { + initialState(length: Length): T[] { switch (length) { case 224 | 256: return SHA2Constants.H[length].map((x) => UInt32.from(x) as T); @@ -233,30 +257,23 @@ const SHA2 = { * Padding function for SHA2, as specified in §5.1.1, §5.1.2, * * @param data The message to hash - * @param is256 Whether this is a short SHA2 (SHA2-224 or SHA2-256) or not (SHA2-384 or SHA2-512) + * @param length Whether this is a SHA2-224 or SHA2-256 or SHA2-384 or SHA2-512 * @returns */ // The only difference between the padding used in SHA2-224/256 and SHA2-384/512 // is the size of the word (32bit vs 64bit). In the first case, UInt32[][] is // returned, in the second case UInt64[][] is returned. -function padding(data: FlexibleBytes, is256: boolean): T[][] { - +function padding( + length: Length, + data: FlexibleBytes +): T[][] { // create a provable Bytes instance from the input data // the Bytes class will be static sized according to the length of the input data let message = Bytes.from(data); - // Whether this is a short SHA2 (SHA2-224 or SHA2-256) or not (SHA2-384 or SHA2-512) - const blockLength = is256 - ? SHA2Constants.BLOCK_LENGTH[256] - : SHA2Constants.BLOCK_LENGTH[512]; - - const paddingValue = is256 - ? SHA2Constants.PADDING_VALUE[256] - : SHA2Constants.PADDING_VALUE[512]; - - const lengthChunk = is256 - ? SHA2Constants.LENGTH_CHUNK[256] - : SHA2Constants.LENGTH_CHUNK[512]; + const blockLength = SHA2Constants.BLOCK_LENGTH[length]; + const paddingValue = SHA2Constants.PADDING_VALUE[length]; + const lengthChunk = SHA2Constants.LENGTH_CHUNK[length]; // now pad the data to reach the format expected by SHA2 // pad 1 bit, followed by k zero bits where k is the smallest non-negative solution to @@ -285,7 +302,7 @@ function padding(data: FlexibleBytes, is256: boolean) // Create chunks based on whether we are dealing with SHA2-224/256 or SHA2-384/512 // split the message into (32 | 64)-bit chunks - let chunks = is256 + let chunks = isShort(length) ? createChunks(paddedMessage, 4, UInt32.fromBytesBE) : createChunks(paddedMessage, 8, UInt64.fromBytesBE); @@ -297,11 +314,15 @@ function padding(data: FlexibleBytes, is256: boolean) /** * Prepares the message schedule for the SHA2 compression function from the given message block. * + * @param length Whether this is a SHA2-224 or SHA2-256 or SHA2-384 or SHA2-512 * @param M - The 512-bit message block (16-element array of UInt32) * or the 1024-bit message block (16-element array of UInt64). * @returns The message schedule (64-element array of UInt32 or 80-element array of UInt64). */ -function messageSchedule(M: T[], is256: boolean): T[] { +function messageSchedule( + length: Length, + M: T[] +): T[] { // §6.2.2.1 and §6.4.2.1 // Declare W as an empty array of type T[] (generic array) @@ -309,21 +330,19 @@ function messageSchedule(M: T[], is256: boolean): T[] // for each message block of 16 x 32bit | 64bit do: - let numWords = is256 - ? SHA2Constants.NUM_WORDS[256] - : SHA2Constants.NUM_WORDS[512]; + let numWords = SHA2Constants.NUM_WORDS[length]; // prepare message block for (let t = 0; t < 16; t++) W[t] = M[t]; for (let t = 16; t < numWords; t++) { // the field element is unreduced and not proven to be 32bit | 64bit, // we will do this later to save constraints - let unreduced = DeltaOne(W[t - 2], is256) + let unreduced = DeltaOne(length, W[t - 2]) .value.add(W[t - 7].value) - .add(DeltaZero(W[t - 15], is256).value.add(W[t - 16].value)); + .add(DeltaZero(length, W[t - 15]).value.add(W[t - 16].value)); // mod 32 | 64 bit the unreduced field element - W[t] = reduceMod(unreduced, is256); + W[t] = reduceMod(length, unreduced); } return W; @@ -332,17 +351,20 @@ function messageSchedule(M: T[], is256: boolean): T[] /** * Performs the SHA-2 compression function on the given hash values and message schedule. * + * @param length Whether this is a SHA2-224 or SHA2-256 or SHA2-384 or SHA2-512 * @param H - The initial or intermediate hash values (8-element array of T). * @param W - The message schedule (64-element array of T). * * @returns The updated intermediate hash values after compression. */ -function compression([...H]: T[], W: T[], is256: boolean) { - let numWords = is256 - ? SHA2Constants.NUM_WORDS[256] - : SHA2Constants.NUM_WORDS[512]; +function compression( + length: Length, + [...H]: T[], + W: T[] +) { + let numWords = SHA2Constants.NUM_WORDS[length]; - let k = is256 ? SHA2Constants.K[256] : SHA2Constants.K[512]; + let k = SHA2Constants.K[length]; // §6.2.2.2 and §6.4.2.2: // initialize working variables @@ -361,40 +383,46 @@ function compression([...H]: T[], W: T[], is256: bool // T1 is unreduced and not proven to be 32 | 64 bit, // we will do this later to save constraints const unreducedT1 = h.value - .add(SigmaOne(e, is256).value) - .add(Ch(e, f, g, is256).value) + .add(SigmaOne(length, e).value) + .add(Ch(length, e, f, g).value) .add(k[t]) .add(W[t].value) .seal(); // T2 is also unreduced - const unreducedT2 = SigmaZero(a, is256).value.add(Maj(a, b, c, is256).value); + const unreducedT2 = SigmaZero(length, a).value.add( + Maj(length, a, b, c).value + ); h = g; g = f; f = e; - e = reduceMod(d.value.add(unreducedT1), is256); + e = reduceMod(length, d.value.add(unreducedT1)); // mod 32 | 64 bit the unreduced field element d = c; c = b; b = a; - a = reduceMod(unreducedT2.add(unreducedT1), is256); + a = reduceMod(length, unreducedT2.add(unreducedT1)); // mod 32 | 64 bit } // §6.2.2.4 and §6.4.2.4 // new intermediate hash value - intermediateHash([a, b, c, d, e, f, g, h], H, is256); + intermediateHash(length, [a, b, c, d, e, f, g, h], H); return H; } // Helper functions +// Helper function to check if it is a short SHA2 (SHA2-224 or SHA2-256) or not +function isShort(length: Length): boolean { + return length === 224 || length === 256; +} // Shorthand to reduce a field element modulo 32 or 64 bits depending on T -function reduceMod(x: Field, is256: boolean): T { - if (is256) { +function reduceMod(length: Length, x: Field): T { + if (isShort(length)) { return UInt32.Unsafe.fromField(divMod32(x, 32 + 16).remainder) as T; } else { return UInt64.Unsafe.fromField(divMod64(x, 64 + 16).remainder) as T; @@ -416,8 +444,12 @@ function createChunks( return chunks; } -function intermediateHash(variables: T[], H: T[], is256: boolean) { - if (is256) { +function intermediateHash( + length: Length, + variables: T[], + H: T[] +) { + if (isShort(length)) { for (let i = 0; i < 8; i++) { H[i] = (variables[i] as UInt32).addMod32(H[i] as UInt32) as T; } @@ -430,10 +462,10 @@ function intermediateHash(variables: T[], H: T[], is2 // Subroutines for SHA2 -function Ch(x: T, y: T, z: T, is256: boolean): T { +function Ch(length: Length, x: T, y: T, z: T): T { // ch(x, y, z) = (x & y) ^ (~x & z) // = (x & y) + (~x & z) (since x & ~x = 0) - if (is256) { + if (isShort(length)) { let xAndY = (x as UInt32).and(y as UInt32).value; let xNotAndZ = (x as UInt32).not().and(z as UInt32).value; let ch = xAndY.add(xNotAndZ).seal(); @@ -446,10 +478,10 @@ function Ch(x: T, y: T, z: T, is256: boolean): T { } } -function Maj(x: T, y: T, z: T, is256: boolean): T { +function Maj(length: Length, x: T, y: T, z: T): T { // maj(x, y, z) = (x & y) ^ (x & z) ^ (y & z) // = (x + y + z - (x ^ y ^ z)) / 2 - if (is256) { + if (isShort(length)) { let sum = (x as UInt32).value.add(y.value).add(z.value).seal(); let xor = (x as UInt32).xor(y as UInt32).xor(z as UInt32).value; let maj = sum.sub(xor).div(2).seal(); @@ -462,30 +494,22 @@ function Maj(x: T, y: T, z: T, is256: boolean): T { } } -function SigmaZero(x: T, is256: boolean) { - return is256 - ? sigma(x, SHA2Constants.SIGMA_ZERO[256], false, is256) - : sigma(x, SHA2Constants.SIGMA_ZERO[512], false, is256); +function SigmaZero(length: Length, x: T): T { + return sigma(length, x, SHA2Constants.SIGMA_ZERO[length]); } -function SigmaOne(x: T, is256: boolean) { - return is256 - ? sigma(x, SHA2Constants.SIGMA_ONE[256], false, is256) - : sigma(x, SHA2Constants.SIGMA_ONE[512], false, is256); +function SigmaOne(length: Length, x: T): T { + return sigma(length, x, SHA2Constants.SIGMA_ONE[length]); } // lowercase sigma = delta to avoid confusing function names -function DeltaZero(x: T, is256: boolean): T { - return is256 - ? sigma(x, SHA2Constants.DELTA_ZERO[256], true, is256) - : sigma(x, SHA2Constants.DELTA_ZERO[512], true, is256); +function DeltaZero(length: Length, x: T): T { + return sigma(length, x, SHA2Constants.DELTA_ZERO[length], true) as T; } -function DeltaOne(x: T, is256: boolean): T { - return is256 - ? sigma(x, SHA2Constants.DELTA_ONE[256], true, is256) - : sigma(x, SHA2Constants.DELTA_ONE[512], true, is256); +function DeltaOne(length: Length, x: T): T { + return sigma(length, x, SHA2Constants.DELTA_ONE[length], true) as T; } function ROTR(n: number, x: T): T { @@ -497,13 +521,13 @@ function SHR(n: number, x: T): T { } function sigmaSimple( + length: Length, u: T, bits: TupleN, - firstShifted = false, - is256: boolean + firstShifted = false ): T { let [r0, r1, r2] = bits; - if (is256) { + if (isShort(length)) { let rot0 = firstShifted ? (SHR(r0, u) as UInt32) : (ROTR(r0, u) as UInt32); let rot1 = ROTR(r1, u) as UInt32; let rot2 = ROTR(r2, u) as UInt32; @@ -517,12 +541,13 @@ function sigmaSimple( } function sigma( + length: Length, u: T, bits: TupleN, - firstShifted = false, - is256: boolean + firstShifted = false ): T { - if (u.isConstant() || !is256) return sigmaSimple(u, bits, firstShifted, is256); + if (u.isConstant() || !isShort(length)) + return sigmaSimple(length, u, bits, firstShifted); // When T is UInt64, 64-bit rotation is natively supported in the gadgets. // However, 32-bit rotation is not natively supported, thus the following: From 06afc51bbd43081f78a3e6d926e50dd2b3f744d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Wed, 18 Dec 2024 16:37:10 +0000 Subject: [PATCH 22/25] fix switch syntax --- src/lib/provable/gadgets/sha2.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/provable/gadgets/sha2.ts b/src/lib/provable/gadgets/sha2.ts index b379ce966b..a79929c303 100644 --- a/src/lib/provable/gadgets/sha2.ts +++ b/src/lib/provable/gadgets/sha2.ts @@ -243,12 +243,12 @@ const SHA2 = { padding, initialState(length: Length): T[] { switch (length) { - case 224 | 256: + case 224: + case 256: return SHA2Constants.H[length].map((x) => UInt32.from(x) as T); - case 384 | 512: + case 384: + case 512: return SHA2Constants.H[length].map((x) => UInt64.from(x) as T); - default: - throw new Error('Invalid hash length'); } }, }; From 40482eec84c69eddd6d8264be6046105bdc2ae19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Wed, 18 Dec 2024 16:51:00 +0000 Subject: [PATCH 23/25] fix aliases for sha3 vs sha2 --- src/examples/zkapps/hashing/run.ts | 12 ++++++------ src/lib/provable/crypto/hash.ts | 13 +++++++++++++ src/lib/provable/gadgets/gadgets.ts | 1 + 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/examples/zkapps/hashing/run.ts b/src/examples/zkapps/hashing/run.ts index 45e99d8714..963ca19c66 100644 --- a/src/examples/zkapps/hashing/run.ts +++ b/src/examples/zkapps/hashing/run.ts @@ -35,27 +35,27 @@ const initialState = let currentState; console.log('Initial State', initialState); -console.log(`Updating commitment from ${initialState} using SHA256 ...`); +console.log(`Updating commitment from ${initialState} using SHA3-256 ...`); txn = await Mina.transaction(feePayer, async () => { - await contract.SHA256(hashData); + await contract.SHA3_256(hashData); }); await txn.prove(); await txn.sign([feePayer.key]).send(); currentState = Mina.getAccount(contractAccount).zkapp?.appState?.[0].toString(); console.log(`Current state successfully updated to ${currentState}`); -console.log(`Updating commitment from ${initialState} using SHA384 ...`); +console.log(`Updating commitment from ${initialState} using SHA3-384 ...`); txn = await Mina.transaction(feePayer, async () => { - await contract.SHA384(hashData); + await contract.SHA3_384(hashData); }); await txn.prove(); await txn.sign([feePayer.key]).send(); currentState = Mina.getAccount(contractAccount).zkapp?.appState?.[0].toString(); console.log(`Current state successfully updated to ${currentState}`); -console.log(`Updating commitment from ${initialState} using SHA512 ...`); +console.log(`Updating commitment from ${initialState} using SHA3-512 ...`); txn = await Mina.transaction(feePayer, async () => { - await contract.SHA512(hashData); + await contract.SHA3_512(hashData); }); await txn.prove(); await txn.sign([feePayer.key]).send(); diff --git a/src/lib/provable/crypto/hash.ts b/src/lib/provable/crypto/hash.ts index ad278deb3d..2d8aca4b1d 100644 --- a/src/lib/provable/crypto/hash.ts +++ b/src/lib/provable/crypto/hash.ts @@ -49,6 +49,19 @@ const Hash = { hash: Gadgets.SHA256.hash, }, + /** + * The SHA2 hash function with an output length of 224 | 256 | 384 | 512 bits. + */ + SHA2: { + /** + * Hashes the given bytes using SHA2. + * + * This is an alias for `Gadgets.SHA2.hash(length,bytes)`.\ + * See {@link Gadgets.SHA2.hash} for details and usage examples. + */ + hash: Gadgets.SHA2.hash, + }, + /** * The SHA3 hash function with an output length of 256 bits. */ diff --git a/src/lib/provable/gadgets/gadgets.ts b/src/lib/provable/gadgets/gadgets.ts index 6d79f7dd21..6f5894666d 100644 --- a/src/lib/provable/gadgets/gadgets.ts +++ b/src/lib/provable/gadgets/gadgets.ts @@ -985,6 +985,7 @@ const Gadgets = { * * Produces an output of {@link Bytes} that conforms to the chosen bit length. * + * @param length - 224 | 256 | 384 | 512 representing the length of the hash. * @param data - {@link Bytes} representing the message to hash. * * ```ts From b885ddc4afc56742c19198cbeffe8c4d317d3a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Wed, 18 Dec 2024 16:52:40 +0000 Subject: [PATCH 24/25] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dd4c5c943..9789118c27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/b857516...HEAD) +### Added + +- Gadgets for 224, 384 and 512 bit variants of SHA2 https://github.com/o1-labs/o1js/pull/1957 + ## [2.2.0](https://github.com/o1-labs/o1js/compare/e1bac02...b857516) - 2024-12-10 ### Added From a6163d7c38bda64d97c2caafa4bce30cb031798e Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 8 Jan 2025 18:44:30 +0100 Subject: [PATCH 25/25] update bindings commit --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index e05efb9999..14ce3b8f5e 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit e05efb9999dc83107127d6758904e2512eb8582d +Subproject commit 14ce3b8f5e23dbcd9afe3d53304ee84cfc92076b