From cc46f5b0cd709bd713a1480fcb160be49d166db7 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Fri, 1 Dec 2023 18:29:00 +0100 Subject: [PATCH 01/52] it works --- src/examples/sha256.ts | 208 ++++++++++++++++++++++++++++++++++++++ src/lib/gadgets/sha256.ts | 166 ++++++++++++++++++++++++++++++ src/lib/int.ts | 16 +-- 3 files changed, 384 insertions(+), 6 deletions(-) create mode 100644 src/examples/sha256.ts create mode 100644 src/lib/gadgets/sha256.ts diff --git a/src/examples/sha256.ts b/src/examples/sha256.ts new file mode 100644 index 0000000000..41ef423716 --- /dev/null +++ b/src/examples/sha256.ts @@ -0,0 +1,208 @@ +// https://csrc.nist.gov/pubs/fips/180-4/upd1/final + +import { UInt32, Provable } from 'o1js'; +import { bitsToBytes } from 'src/bindings/lib/binable.js'; + +export { SHA256 }; + +function processStringToMessageBlocks(s: string) { + let msgBits = s + .split('') + .map((c) => { + let binary = c.charCodeAt(0).toString(2); + return '00000000'.substr(binary.length) + binary; + }) + .join(''); + + let l = msgBits.length; + msgBits = msgBits + '1'; + console.log('msgBits:', msgBits.length); + + // calculate k in l + 1 +k = 448 mod 512 + let remainder = (448 - (l + 1)) % 512; + + let k = (remainder + 512) % 512; + console.log(k); + let padding = '0'.repeat(k); + msgBits = msgBits + padding; + let lBits = l.toString(2); + msgBits = msgBits + '0'.repeat(64 - lBits.length) + lBits; + console.log('msgBits:', msgBits.length); + let bitBlocks32 = []; + for (let i = 0; i < msgBits.length; i += 32) { + bitBlocks32.push(UInt32.from(BigInt('0b' + msgBits.substr(i, 32)))); + } + + let lengthBlocks = bitBlocks32.length; + let blocks = []; + for (let i = 0; i < lengthBlocks; i += 16) { + let block = bitBlocks32.slice(i, i + 16); + blocks.push(block); + } + return blocks; +} + +// constants §4.2.2 +const K = [ + 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, +].map((k) => UInt32.from(k)); + +// initial hash values §5.3.3 +const H = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19, +].map((h) => UInt32.from(h)); + +const SHA256 = { + hash(msg: string) { + // preprocessing §6.2 + // padding the message $5.1.1 into blocks that are a multiple of 512 + let messageBlocks = Provable.witness( + Provable.Array(Provable.Array(UInt32, 16), 6), + () => { + console.log('hashing <' + msg + '>'); + return processStringToMessageBlocks(msg); + } + ); + + const N = messageBlocks.length; + + for (let i = 0; i < N; i++) { + const M = messageBlocks[i]; + // for each message block of 16 x 32 bytes do: + const W = new Array(64); + + // prepare message block + for (let t = 0; t <= 15; t++) W[t] = M[t]; + for (let t = 16; t <= 63; t++) { + W[t] = DeltaOne(W[t - 2]) + .addMod32(W[t - 7]) + .addMod32(DeltaZero(W[t - 15]).addMod32(W[t - 16])); + } + // 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++) { + const T1 = h + .addMod32(SigmaOne(e)) + .addMod32(Ch(e, f, g)) + .addMod32(K[t]) + .addMod32(W[t]); + + const T2 = SigmaZero(a).addMod32(Maj(a, b, c)); + + h = g; + g = f; + f = e; + e = d.addMod32(T1); + d = c; + c = b; + b = a; + a = T1.addMod32(T2); + } + + // 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); + } + + Provable.asProver(() => { + let hex = ''; + for (let h = 0; h < H.length; h++) + hex = hex + ('00000000' + H[h].toBigint().toString(16)).slice(-8); + console.log('sha256 digest:', hex); + }); + }, +}; + +Provable.runAndCheck(() => + SHA256.hash( + 'testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest' + ) +); +let cs = Provable.constraintSystem(() => SHA256.hash('test')); +console.log('rows:', cs.rows); +/* function toUInt8(msg: string) { + return msg.split('').map((c) => UInt8.from(c.charCodeAt(0))); +} */ + +function Ch(x: UInt32, y: UInt32, z: UInt32) { + let xAndY = x.and(y); + let xNotAndZ = x.not().and(z); + return xAndY.xor(xNotAndZ); +} + +function Maj(x: UInt32, y: UInt32, z: UInt32) { + let xAndY = x.and(y); + let xAndZ = x.and(z); + let yAndZ = y.and(z); + + return xAndY.xor(xAndZ).xor(yAndZ); +} + +function SigmaZero(x: UInt32) { + let rotr2 = ROTR(2, x); + let rotr13 = ROTR(13, x); + let rotr22 = ROTR(22, x); + + return rotr2.xor(rotr13).xor(rotr22); +} + +function SigmaOne(x: UInt32) { + let rotr6 = ROTR(6, x); + let rotr11 = ROTR(11, x); + let rotr25 = ROTR(25, x); + + return rotr6.xor(rotr11).xor(rotr25); +} + +// lowercase sigma = delta to avoid confusing function names + +function DeltaZero(x: UInt32) { + let rotr7 = ROTR(7, x); + let rotr18 = ROTR(18, x); + let shr3 = SHR(3, x); + + return rotr7.xor(rotr18).xor(shr3); +} + +function DeltaOne(x: UInt32) { + let rotr17 = ROTR(17, x); + let rotr19 = ROTR(19, x); + let shr10 = SHR(10, x); + return rotr17.xor(rotr19).xor(shr10); +} + +function ROTR(n: number, x: UInt32) { + return x.rotate(n, 'right'); +} + +function SHR(n: number, x: UInt32) { + let val = x.rightShift(n); + return val; +} diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts new file mode 100644 index 0000000000..3693b82bb1 --- /dev/null +++ b/src/lib/gadgets/sha256.ts @@ -0,0 +1,166 @@ +// https://csrc.nist.gov/pubs/fips/180-4/upd1/final + +import { UInt32 } from '../int.js'; +import { Provable } from '../provable.js'; + +export { SHA256 }; + +// constants §4.2.2 +const K = [ + 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, +].map((k) => UInt32.from(k)); + +// initial hash values §5.3.3 +const H = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19, +].map((h) => UInt32.from(h)); + +const SHA256 = { + hash(msg: string) { + // preprocessing §6.2 + // padding the message $5.1.1 into blocks that are a multiple of 512 + let l = 8 * 8; + let k = 448 - (l + 1); + let padding = [1, ...new Array(k).fill(0)]; + + let messageBlocks = [ + // M1 + new Array(16).fill(UInt32.from(1)), + new Array(16).fill(UInt32.from(1)), + ]; + + for (let i = 0; i < messageBlocks.length; i++) { + const M = messageBlocks[i]; + // for each message block of 16 x 32 bytes do: + const W = new Array(64); + + // prepare message block + for (let t = 0; t < 16; t++) W[t] = M[t]; + for (let t = 16; t < 64; t++) { + W[t] = DeltaOne(W[t - 2]) + .addMod32(W[t - 7]) + .add(DeltaZero(W[t - 15]).add(W[t - 16])); + } + // initialize working variables + let a = H[0], + b = H[1], + c = H[2], + d = H[3], + e = H[4], + f = H[5], + g = H[6], + h = H[7]; + + // main loop + for (let t = 0; t < 64; t++) { + const T1 = h + .addMod32(SigmaOne(e)) + .addMod32(Ch(e, f, g)) + .addMod32(K[t]) + .addMod32(W[t]); + + const T2 = SigmaOne(a).addMod32(Maj(a, b, c)); + + h = g; + g = f; + f = e; + e = d.addMod32(T1); + d = c; + c = b; + b = a; + a = T1.addMod32(T2); + } + + // 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); + } + + /* + let hex = ''; + for (let h = 0; h < H.length; h++) + hex = hex + ('00000000' + H[h].toBigint().toString(16)).slice(-8); + console.log(hex); + */ + }, +}; + +let cs = Provable.constraintSystem(() => SHA256.hash('abc')); +console.log(cs); +/* function toUInt8(msg: string) { + return msg.split('').map((c) => UInt8.from(c.charCodeAt(0))); +} */ + +function Ch(x: UInt32, y: UInt32, z: UInt32) { + let xAndY = x.and(y); + let xNotAndZ = x.not().and(z); + return xAndY.xor(xNotAndZ); +} + +function Maj(x: UInt32, y: UInt32, z: UInt32) { + let xAndY = x.and(y); + let xAndZ = x.and(z); + let yAndZ = y.and(z); + + return xAndY.xor(xAndZ).xor(yAndZ); +} + +function SigmaZero(x: UInt32) { + let rotr2 = ROTR(2, x); + let rotr13 = ROTR(13, x); + let rotr22 = ROTR(22, x); + + return rotr2.xor(rotr13).xor(rotr22); +} + +function SigmaOne(x: UInt32) { + let rotr6 = ROTR(6, x); + let rotr11 = ROTR(11, x); + let rotr25 = ROTR(25, x); + + return rotr6.xor(rotr11).xor(rotr25); +} + +// lowercase sigma = delta to avoid confusing function names + +function DeltaZero(x: UInt32) { + let rotr7 = ROTR(7, x); + let rotr18 = ROTR(18, x); + let shr3 = SHR(3, x); + + return rotr7.xor(rotr18).xor(shr3); +} + +function DeltaOne(x: UInt32) { + let rotr17 = ROTR(17, x); + let rotr19 = ROTR(19, x); + let shr10 = SHR(10, x); + return rotr17.xor(rotr19).xor(shr10); +} + +function ROTR(n: number, x: UInt32) { + return x.rotate(n, 'right'); +} + +function SHR(n: number, x: UInt32) { + let val = x.rightShift(n); + return val; +} diff --git a/src/lib/int.ts b/src/lib/int.ts index 6fe765e7db..ac59f5c78e 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -703,6 +703,10 @@ class UInt32 extends CircuitValue { Gadgets.rangeCheck32(z); return new UInt32(z); } + + addMod32(y: UInt32) { + return new UInt32(Gadgets.addMod32(this.value, y.value)); + } /** * Subtraction with underflow checking. */ @@ -732,7 +736,7 @@ class UInt32 extends CircuitValue { * ``` */ xor(x: UInt32) { - return Gadgets.xor(this.value, x.value, UInt32.NUM_BITS); + return UInt32.from(Gadgets.xor(this.value, x.value, UInt32.NUM_BITS)); } /** @@ -763,7 +767,7 @@ class UInt32 extends CircuitValue { * @param a - The value to apply NOT to. */ not() { - return Gadgets.not(this.value, UInt32.NUM_BITS, false); + return UInt32.from(Gadgets.not(this.value, UInt32.NUM_BITS, false)); } /** @@ -795,7 +799,7 @@ class UInt32 extends CircuitValue { * ``` */ rotate(bits: number, direction: 'left' | 'right' = 'left') { - return Gadgets.rotate32(this.value, bits, direction); + return UInt32.from(Gadgets.rotate32(this.value, bits, direction)); } /** @@ -818,7 +822,7 @@ class UInt32 extends CircuitValue { * ``` */ leftShift(bits: number) { - return Gadgets.leftShift32(this.value, bits); + return UInt32.from(Gadgets.leftShift32(this.value, bits)); } /** @@ -841,7 +845,7 @@ class UInt32 extends CircuitValue { * ``` */ rightShift(bits: number) { - return Gadgets.rightShift64(this.value, bits); + return UInt32.from(Gadgets.rightShift64(this.value, bits)); } /** @@ -870,7 +874,7 @@ class UInt32 extends CircuitValue { * ``` */ and(x: UInt32) { - return Gadgets.and(this.value, x.value, UInt32.NUM_BITS); + return UInt32.from(Gadgets.and(this.value, x.value, UInt32.NUM_BITS)); } /** From 35a15f5b43e05b9601524a6caf822bc2b6ab799a Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Fri, 1 Dec 2023 19:38:10 +0100 Subject: [PATCH 02/52] add dummy api --- src/examples/sha256.ts | 260 ++++++++----------------------------- src/lib/gadgets/gadgets.ts | 4 + src/lib/gadgets/sha256.ts | 131 +++++++++++-------- 3 files changed, 134 insertions(+), 261 deletions(-) diff --git a/src/examples/sha256.ts b/src/examples/sha256.ts index 41ef423716..8b14e65e4a 100644 --- a/src/examples/sha256.ts +++ b/src/examples/sha256.ts @@ -1,208 +1,58 @@ -// https://csrc.nist.gov/pubs/fips/180-4/upd1/final - -import { UInt32, Provable } from 'o1js'; -import { bitsToBytes } from 'src/bindings/lib/binable.js'; - -export { SHA256 }; - -function processStringToMessageBlocks(s: string) { - let msgBits = s - .split('') - .map((c) => { - let binary = c.charCodeAt(0).toString(2); - return '00000000'.substr(binary.length) + binary; - }) - .join(''); - - let l = msgBits.length; - msgBits = msgBits + '1'; - console.log('msgBits:', msgBits.length); - - // calculate k in l + 1 +k = 448 mod 512 - let remainder = (448 - (l + 1)) % 512; - - let k = (remainder + 512) % 512; - console.log(k); - let padding = '0'.repeat(k); - msgBits = msgBits + padding; - let lBits = l.toString(2); - msgBits = msgBits + '0'.repeat(64 - lBits.length) + lBits; - console.log('msgBits:', msgBits.length); - let bitBlocks32 = []; - for (let i = 0; i < msgBits.length; i += 32) { - bitBlocks32.push(UInt32.from(BigInt('0b' + msgBits.substr(i, 32)))); - } - - let lengthBlocks = bitBlocks32.length; - let blocks = []; - for (let i = 0; i < lengthBlocks; i += 16) { - let block = bitBlocks32.slice(i, i + 16); - blocks.push(block); - } - return blocks; -} - -// constants §4.2.2 -const K = [ - 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, -].map((k) => UInt32.from(k)); - -// initial hash values §5.3.3 -const H = [ - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, - 0x1f83d9ab, 0x5be0cd19, -].map((h) => UInt32.from(h)); - -const SHA256 = { - hash(msg: string) { - // preprocessing §6.2 - // padding the message $5.1.1 into blocks that are a multiple of 512 - let messageBlocks = Provable.witness( - Provable.Array(Provable.Array(UInt32, 16), 6), - () => { - console.log('hashing <' + msg + '>'); - return processStringToMessageBlocks(msg); - } - ); - - const N = messageBlocks.length; - - for (let i = 0; i < N; i++) { - const M = messageBlocks[i]; - // for each message block of 16 x 32 bytes do: - const W = new Array(64); - - // prepare message block - for (let t = 0; t <= 15; t++) W[t] = M[t]; - for (let t = 16; t <= 63; t++) { - W[t] = DeltaOne(W[t - 2]) - .addMod32(W[t - 7]) - .addMod32(DeltaZero(W[t - 15]).addMod32(W[t - 16])); - } - // 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++) { - const T1 = h - .addMod32(SigmaOne(e)) - .addMod32(Ch(e, f, g)) - .addMod32(K[t]) - .addMod32(W[t]); - - const T2 = SigmaZero(a).addMod32(Maj(a, b, c)); - - h = g; - g = f; - f = e; - e = d.addMod32(T1); - d = c; - c = b; - b = a; - a = T1.addMod32(T2); - } - - // 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); - } - - Provable.asProver(() => { - let hex = ''; - for (let h = 0; h < H.length; h++) - hex = hex + ('00000000' + H[h].toBigint().toString(16)).slice(-8); - console.log('sha256 digest:', hex); - }); - }, +import assert from 'assert'; +import { Gadgets, Provable, UInt32 } from 'o1js'; + +const Parser = Gadgets.SHA256.processStringToMessageBlocks; + +const Hash = Gadgets.SHA256.hash; + +const run = (msg: string, expected: string) => { + let messageBlocks = Provable.witness( + Provable.Array(Provable.Array(UInt32, 16), 1), + () => Parser(msg) + ); + let digest = Hash(messageBlocks); + Provable.asProver(() => { + let y = toHex(digest); + assert(expected === y, `expected ${expected} got ${y}`); + }); }; -Provable.runAndCheck(() => - SHA256.hash( - 'testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest' - ) -); -let cs = Provable.constraintSystem(() => SHA256.hash('test')); -console.log('rows:', cs.rows); -/* function toUInt8(msg: string) { - return msg.split('').map((c) => UInt8.from(c.charCodeAt(0))); -} */ - -function Ch(x: UInt32, y: UInt32, z: UInt32) { - let xAndY = x.and(y); - let xNotAndZ = x.not().and(z); - return xAndY.xor(xNotAndZ); -} - -function Maj(x: UInt32, y: UInt32, z: UInt32) { - let xAndY = x.and(y); - let xAndZ = x.and(z); - let yAndZ = y.and(z); - - return xAndY.xor(xAndZ).xor(yAndZ); -} - -function SigmaZero(x: UInt32) { - let rotr2 = ROTR(2, x); - let rotr13 = ROTR(13, x); - let rotr22 = ROTR(22, x); - - return rotr2.xor(rotr13).xor(rotr22); -} - -function SigmaOne(x: UInt32) { - let rotr6 = ROTR(6, x); - let rotr11 = ROTR(11, x); - let rotr25 = ROTR(25, x); - - return rotr6.xor(rotr11).xor(rotr25); -} - -// lowercase sigma = delta to avoid confusing function names - -function DeltaZero(x: UInt32) { - let rotr7 = ROTR(7, x); - let rotr18 = ROTR(18, x); - let shr3 = SHR(3, x); - - return rotr7.xor(rotr18).xor(shr3); -} - -function DeltaOne(x: UInt32) { - let rotr17 = ROTR(17, x); - let rotr19 = ROTR(19, x); - let shr10 = SHR(10, x); - return rotr17.xor(rotr19).xor(shr10); -} - -function ROTR(n: number, x: UInt32) { - return x.rotate(n, 'right'); -} - -function SHR(n: number, x: UInt32) { - let val = x.rightShift(n); - return val; +Provable.runAndCheck(() => { + run( + 'duck', + '2d2370db2447ff8cf4f3accd68c85aa119a9c893effd200a9b69176e9fc5eb98' + ); + run( + 'doggo', + '8aa89c66e2c453b71400ac832a345d872c33147150267be5402552ee19b3d4ce' + ); + run( + 'frog', + '74fa5327cc0f4e947789dd5e989a61a8242986a596f170640ac90337b1da1ee4' + ); +}); + +let cs = Provable.constraintSystem(() => { + run( + 'duck', + '2d2370db2447ff8cf4f3accd68c85aa119a9c893effd200a9b69176e9fc5eb98' + ); + run( + 'doggo', + '8aa89c66e2c453b71400ac832a345d872c33147150267be5402552ee19b3d4ce' + ); + run( + 'frog', + '74fa5327cc0f4e947789dd5e989a61a8242986a596f170640ac90337b1da1ee4' + ); +}); + +console.log(cs); + +function toHex(xs: UInt32[]) { + let hex = ''; + for (let h = 0; h < xs.length; h++) + hex = hex + ('00000000' + xs[h].toBigint().toString(16)).slice(-8); + + return hex; } diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index d095184388..0e7b781f3a 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -22,6 +22,7 @@ import { import { Field } from '../core.js'; import { ForeignField, Field3 } from './foreign-field.js'; import { divMod32, addMod32 } from './arithmetic.js'; +import { SHA256 } from './sha256.js'; export { Gadgets }; @@ -668,6 +669,9 @@ const Gadgets = { * ``` * */ addMod32, + + // TODO: everything + SHA256: SHA256, }; export namespace Gadgets { diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 3693b82bb1..e78c5dfd37 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -5,72 +5,101 @@ import { Provable } from '../provable.js'; export { SHA256 }; -// constants §4.2.2 -const K = [ - 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, -].map((k) => UInt32.from(k)); - -// initial hash values §5.3.3 -const H = [ - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, - 0x1f83d9ab, 0x5be0cd19, -].map((h) => UInt32.from(h)); +function processStringToMessageBlocks(s: string) { + let msgBits = s + .split('') + .map((c) => { + let binary = c.charCodeAt(0).toString(2); + return '00000000'.substr(binary.length) + binary; + }) + .join(''); + + let l = msgBits.length; + msgBits = msgBits + '1'; + + // calculate k in l + 1 +k = 448 mod 512 + let remainder = (448 - (l + 1)) % 512; + + let k = (remainder + 512) % 512; + let padding = '0'.repeat(k); + msgBits = msgBits + padding; + let lBits = l.toString(2); + msgBits = msgBits + '0'.repeat(64 - lBits.length) + lBits; + + let bitBlocks32 = []; + for (let i = 0; i < msgBits.length; i += 32) { + bitBlocks32.push(UInt32.from(BigInt('0b' + msgBits.substr(i, 32)))); + } + + let lengthBlocks = bitBlocks32.length; + let blocks = []; + for (let i = 0; i < lengthBlocks; i += 16) { + let block = bitBlocks32.slice(i, i + 16); + blocks.push(block); + } + return blocks; +} const SHA256 = { - hash(msg: string) { - // preprocessing §6.2 + hash(data: UInt32[][]) { + // constants §4.2.2 + const K = [ + 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, + ].map((k) => UInt32.from(k)); + + // initial hash values §5.3.3 + const H = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19, + ].map((h) => UInt32.from(h)); + + // TODO: correct dynamic preprocessing §6.2 // padding the message $5.1.1 into blocks that are a multiple of 512 - let l = 8 * 8; - let k = 448 - (l + 1); - let padding = [1, ...new Array(k).fill(0)]; + let messageBlocks = data; - let messageBlocks = [ - // M1 - new Array(16).fill(UInt32.from(1)), - new Array(16).fill(UInt32.from(1)), - ]; + const N = messageBlocks.length; - for (let i = 0; i < messageBlocks.length; i++) { + for (let i = 0; i < N; i++) { const M = messageBlocks[i]; // for each message block of 16 x 32 bytes do: const W = new Array(64); // prepare message block - for (let t = 0; t < 16; t++) W[t] = M[t]; - for (let t = 16; t < 64; t++) { + for (let t = 0; t <= 15; t++) W[t] = M[t]; + for (let t = 16; t <= 63; t++) { W[t] = DeltaOne(W[t - 2]) .addMod32(W[t - 7]) - .add(DeltaZero(W[t - 15]).add(W[t - 16])); + .addMod32(DeltaZero(W[t - 15]).addMod32(W[t - 16])); } // initialize working variables - let a = H[0], - b = H[1], - c = H[2], - d = H[3], - e = H[4], - f = H[5], - g = H[6], - h = H[7]; + 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 < 64; t++) { + for (let t = 0; t <= 63; t++) { const T1 = h .addMod32(SigmaOne(e)) .addMod32(Ch(e, f, g)) .addMod32(K[t]) .addMod32(W[t]); - const T2 = SigmaOne(a).addMod32(Maj(a, b, c)); + const T2 = SigmaZero(a).addMod32(Maj(a, b, c)); h = g; g = f; @@ -94,21 +123,11 @@ const SHA256 = { H[7] = H[7].addMod32(h); } - /* - let hex = ''; - for (let h = 0; h < H.length; h++) - hex = hex + ('00000000' + H[h].toBigint().toString(16)).slice(-8); - console.log(hex); - */ + return H; }, + processStringToMessageBlocks: processStringToMessageBlocks, }; -let cs = Provable.constraintSystem(() => SHA256.hash('abc')); -console.log(cs); -/* function toUInt8(msg: string) { - return msg.split('').map((c) => UInt8.from(c.charCodeAt(0))); -} */ - function Ch(x: UInt32, y: UInt32, z: UInt32) { let xAndY = x.and(y); let xNotAndZ = x.not().and(z); From a1e8f17b36a477e49d3fa070ae16b190f2d272cf Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Fri, 1 Dec 2023 21:45:53 +0100 Subject: [PATCH 03/52] maybe some improvements --- src/lib/gadgets/sha256.ts | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index e78c5dfd37..7ff6f96d01 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -1,7 +1,8 @@ // https://csrc.nist.gov/pubs/fips/180-4/upd1/final +import { Field } from '../field.js'; import { UInt32 } from '../int.js'; -import { Provable } from '../provable.js'; +import { Gadgets } from './gadgets.js'; export { SHA256 }; @@ -72,15 +73,21 @@ const SHA256 = { for (let i = 0; i < N; i++) { const M = messageBlocks[i]; // for each message block of 16 x 32 bytes do: - const W = new Array(64); + const W: UInt32[] = []; // prepare message block for (let t = 0; t <= 15; t++) W[t] = M[t]; for (let t = 16; t <= 63; t++) { - W[t] = DeltaOne(W[t - 2]) + let temp = DeltaOne(W[t - 2]) + .value.add(W[t - 7].value) + .add(DeltaZero(W[t - 15]).value.add(W[t - 16].value)); + + W[t] = UInt32.from(Gadgets.divMod32(temp).remainder); + /* W[t] = DeltaOne(W[t - 2]) .addMod32(W[t - 7]) - .addMod32(DeltaZero(W[t - 15]).addMod32(W[t - 16])); + .addMod32(DeltaZero(W[t - 15]).addMod32(W[t - 16])); */ } + // initialize working variables let a = H[0]; let b = H[1]; @@ -93,13 +100,17 @@ const SHA256 = { // main loop for (let t = 0; t <= 63; t++) { - const T1 = h - .addMod32(SigmaOne(e)) - .addMod32(Ch(e, f, g)) - .addMod32(K[t]) - .addMod32(W[t]); - - const T2 = SigmaZero(a).addMod32(Maj(a, b, c)); + const T1 = new UInt32( + Gadgets.divMod32( + h.value + .add(SigmaOne(e).value) + .add(Ch(e, f, g).value) + .add(K[t].value) + .add(W[t].value) + ).remainder + ); + + const unreducedT2 = SigmaZero(a).value.add(Maj(a, b, c).value); h = g; g = f; @@ -108,7 +119,7 @@ const SHA256 = { d = c; c = b; b = a; - a = T1.addMod32(T2); + a = UInt32.from(Gadgets.divMod32(unreducedT2.add(T1.value)).remainder); } // new intermediate hash value From aa85082ebc85da7049927e37d5cf700038f082de Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Fri, 1 Dec 2023 21:59:44 +0100 Subject: [PATCH 04/52] slight refac --- src/examples/sha256.ts | 61 +++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/src/examples/sha256.ts b/src/examples/sha256.ts index 8b14e65e4a..ac9dce1076 100644 --- a/src/examples/sha256.ts +++ b/src/examples/sha256.ts @@ -2,9 +2,31 @@ import assert from 'assert'; import { Gadgets, Provable, UInt32 } from 'o1js'; const Parser = Gadgets.SHA256.processStringToMessageBlocks; - const Hash = Gadgets.SHA256.hash; +const testVectors = [ + { + msg: '', + expected: + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + { + msg: 'duck', + expected: + '2d2370db2447ff8cf4f3accd68c85aa119a9c893effd200a9b69176e9fc5eb98', + }, + { + msg: 'doggo', + expected: + '8aa89c66e2c453b71400ac832a345d872c33147150267be5402552ee19b3d4ce', + }, + { + msg: 'frog', + expected: + '74fa5327cc0f4e947789dd5e989a61a8242986a596f170640ac90337b1da1ee4', + }, +]; + const run = (msg: string, expected: string) => { let messageBlocks = Provable.witness( Provable.Array(Provable.Array(UInt32, 16), 1), @@ -17,34 +39,23 @@ const run = (msg: string, expected: string) => { }); }; +console.log('running plain'); +testVectors.forEach((v) => { + run(v.msg, v.expected); +}); + +console.log('run and check'); Provable.runAndCheck(() => { - run( - 'duck', - '2d2370db2447ff8cf4f3accd68c85aa119a9c893effd200a9b69176e9fc5eb98' - ); - run( - 'doggo', - '8aa89c66e2c453b71400ac832a345d872c33147150267be5402552ee19b3d4ce' - ); - run( - 'frog', - '74fa5327cc0f4e947789dd5e989a61a8242986a596f170640ac90337b1da1ee4' - ); + testVectors.forEach((v) => { + run(v.msg, v.expected); + }); }); +console.log('constraint system'); let cs = Provable.constraintSystem(() => { - run( - 'duck', - '2d2370db2447ff8cf4f3accd68c85aa119a9c893effd200a9b69176e9fc5eb98' - ); - run( - 'doggo', - '8aa89c66e2c453b71400ac832a345d872c33147150267be5402552ee19b3d4ce' - ); - run( - 'frog', - '74fa5327cc0f4e947789dd5e989a61a8242986a596f170640ac90337b1da1ee4' - ); + testVectors.forEach((v) => { + run(v.msg, v.expected); + }); }); console.log(cs); From 880a4129ac375f48b76dc8e1c4ee8b9532f9b41b Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Fri, 1 Dec 2023 22:16:21 +0100 Subject: [PATCH 05/52] more efficient hashing --- src/lib/gadgets/sha256.ts | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 7ff6f96d01..c85dc47ff3 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -78,14 +78,11 @@ const SHA256 = { // prepare message block for (let t = 0; t <= 15; t++) W[t] = M[t]; for (let t = 16; t <= 63; t++) { - let temp = DeltaOne(W[t - 2]) + let unreduced = DeltaOne(W[t - 2]) .value.add(W[t - 7].value) .add(DeltaZero(W[t - 15]).value.add(W[t - 16].value)); - W[t] = UInt32.from(Gadgets.divMod32(temp).remainder); - /* W[t] = DeltaOne(W[t - 2]) - .addMod32(W[t - 7]) - .addMod32(DeltaZero(W[t - 15]).addMod32(W[t - 16])); */ + W[t] = UInt32.from(Gadgets.divMod32(unreduced).remainder); } // initialize working variables @@ -100,26 +97,24 @@ const SHA256 = { // main loop for (let t = 0; t <= 63; t++) { - const T1 = new UInt32( - Gadgets.divMod32( - h.value - .add(SigmaOne(e).value) - .add(Ch(e, f, g).value) - .add(K[t].value) - .add(W[t].value) - ).remainder - ); + const unreducedT1 = h.value + .add(SigmaOne(e).value) + .add(Ch(e, f, g).value) + .add(K[t].value) + .add(W[t].value); const unreducedT2 = SigmaZero(a).value.add(Maj(a, b, c).value); h = g; g = f; f = e; - e = d.addMod32(T1); + e = UInt32.from(Gadgets.divMod32(d.value.add(unreducedT1)).remainder); d = c; c = b; b = a; - a = UInt32.from(Gadgets.divMod32(unreducedT2.add(T1.value)).remainder); + a = UInt32.from( + Gadgets.divMod32(unreducedT2.add(unreducedT1)).remainder + ); } // new intermediate hash value From 79068e350835575e906a1689dbc7973ba833c2b5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 21:58:20 +0100 Subject: [PATCH 06/52] faster rotation of 3 values at once --- src/lib/gadgets/sha256.ts | 82 ++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index c85dc47ff3..84b267d325 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -1,7 +1,8 @@ // https://csrc.nist.gov/pubs/fips/180-4/upd1/final - import { Field } from '../field.js'; import { UInt32 } from '../int.js'; +import { TupleN } from '../util/types.js'; +import { assert, bitSlice, exists } from './common.js'; import { Gadgets } from './gadgets.js'; export { SHA256 }; @@ -149,18 +150,12 @@ function Maj(x: UInt32, y: UInt32, z: UInt32) { } function SigmaZero(x: UInt32) { - let rotr2 = ROTR(2, x); - let rotr13 = ROTR(13, x); - let rotr22 = ROTR(22, x); - + let [rotr2, rotr13, rotr22] = ROTR3(x, [2, 13, 22]); return rotr2.xor(rotr13).xor(rotr22); } function SigmaOne(x: UInt32) { - let rotr6 = ROTR(6, x); - let rotr11 = ROTR(11, x); - let rotr25 = ROTR(25, x); - + let [rotr6, rotr11, rotr25] = ROTR3(x, [6, 11, 25]); return rotr6.xor(rotr11).xor(rotr25); } @@ -189,3 +184,72 @@ function SHR(n: number, x: UInt32) { let val = x.rightShift(n); return val; } + +function ROTR3Simple(u: UInt32, bits: TupleN): TupleN { + let [r0, r1, r2] = bits; + return [ROTR(r0, u), ROTR(r1, u), ROTR(r2, u)]; +} + +function ROTR3(u: UInt32, bits: TupleN): TupleN { + if (u.isConstant()) return ROTR3Simple(u, bits); + + 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 + rangeCheckNSmall(x0, d0); + rangeCheckNSmall(x1, d1); + rangeCheckNSmall(x2, d2); + assert(d3 <= 16, 'expected d3 <= 16'); + rangeCheckNSmall(x3, 16); // cheaper and sufficient + + // 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); + + // reassemble chunks into rotated values + + // rotr(x, r0) = x1 + x2*2^d1 + x3*2^(d1+d2) + x0*2^(d1+d2+d3) + let xRotR0 = x123.add(x0.mul(1 << (d1 + d2 + d3))).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(); + + // 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(); + + return TupleN.map([xRotR0, xRotR1, xRotR2], (x) => UInt32.from(x)); +} + +function rangeCheckNSmall(x: Field, n: number) { + assert(n <= 16, 'expected n <= 16'); + + // x < 2^16 + x.rangeCheckHelper(16).assertEquals(x); + if (n === 16) return; + + // 2^(16-n)*x < 2^16, which implies x < 2^n + let xScaled = x.mul(1 << (16 - n)).seal(); + xScaled.rangeCheckHelper(16).assertEquals(xScaled); +} From e2bcd0dfa5dd54e53fa929efe56ec803641332f4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 08:00:08 +0100 Subject: [PATCH 07/52] save some range checks --- src/lib/gadgets/sha256.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 84b267d325..61daa37ed2 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -190,6 +190,7 @@ function ROTR3Simple(u: UInt32, bits: TupleN): TupleN { return [ROTR(r0, u), ROTR(r1, u), ROTR(r2, u)]; } +// assumes that outputs are range-checked to 32 bits externally function ROTR3(u: UInt32, bits: TupleN): TupleN { if (u.isConstant()) return ROTR3Simple(u, bits); @@ -213,11 +214,12 @@ function ROTR3(u: UInt32, bits: TupleN): TupleN { }); // range check each chunk - rangeCheckNSmall(x0, d0); - rangeCheckNSmall(x1, d1); - rangeCheckNSmall(x2, d2); - assert(d3 <= 16, 'expected d3 <= 16'); - rangeCheckNSmall(x3, 16); // cheaper and sufficient + // 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 + rangeCheckNSmall(x0, 16); + rangeCheckNSmall(x1, 16); + rangeCheckNSmall(x2, 16); + rangeCheckNSmall(x3, 16); // prove x decomposition @@ -225,19 +227,23 @@ function ROTR3(u: UInt32, bits: TupleN): TupleN { 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 // rotr(x, r0) = x1 + x2*2^d1 + x3*2^(d1+d2) + x0*2^(d1+d2+d3) let 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 // 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 return TupleN.map([xRotR0, xRotR1, xRotR2], (x) => UInt32.from(x)); } From a82c63b7f805f7461237d1913035bca5c2ec4540 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 08:03:26 +0100 Subject: [PATCH 08/52] clean up a bit --- src/lib/gadgets/sha256.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 61daa37ed2..76e2a2f49e 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -216,10 +216,10 @@ function ROTR3(u: UInt32, bits: TupleN): TupleN { // 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 - rangeCheckNSmall(x0, 16); - rangeCheckNSmall(x1, 16); - rangeCheckNSmall(x2, 16); - rangeCheckNSmall(x3, 16); + rangeCheck16(x0); + rangeCheck16(x1); + rangeCheck16(x2); + rangeCheck16(x3); // prove x decomposition @@ -252,10 +252,13 @@ function rangeCheckNSmall(x: Field, n: number) { assert(n <= 16, 'expected n <= 16'); // x < 2^16 - x.rangeCheckHelper(16).assertEquals(x); + rangeCheck16(x); if (n === 16) return; // 2^(16-n)*x < 2^16, which implies x < 2^n - let xScaled = x.mul(1 << (16 - n)).seal(); - xScaled.rangeCheckHelper(16).assertEquals(xScaled); + rangeCheck16(x.mul(1 << (16 - n)).seal()); +} + +function rangeCheck16(x: Field) { + x.rangeCheckHelper(16).assertEquals(x); } From 17c7c88c4f349152ff971aebc9299ed5a944cf4b Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 08:33:29 +0100 Subject: [PATCH 09/52] support Field as input to UInt32.xor --- src/lib/int.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index ac59f5c78e..f161e83187 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -735,8 +735,10 @@ class UInt32 extends CircuitValue { * c.assertEquals(0b0110); * ``` */ - xor(x: UInt32) { - return UInt32.from(Gadgets.xor(this.value, x.value, UInt32.NUM_BITS)); + xor(x: UInt32 | Field) { + return UInt32.from( + Gadgets.xor(this.value, UInt32.from(x).value, UInt32.NUM_BITS) + ); } /** From 62db5b27f5cb718255a34f4a9f545410246e9f53 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 08:34:10 +0100 Subject: [PATCH 10/52] cover delta0/1 with fast 3-rotation --- src/lib/gadgets/sha256.ts | 47 +++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 76e2a2f49e..eec0ca3949 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -150,30 +150,21 @@ function Maj(x: UInt32, y: UInt32, z: UInt32) { } function SigmaZero(x: UInt32) { - let [rotr2, rotr13, rotr22] = ROTR3(x, [2, 13, 22]); - return rotr2.xor(rotr13).xor(rotr22); + return sigma(x, [2, 13, 22]); } function SigmaOne(x: UInt32) { - let [rotr6, rotr11, rotr25] = ROTR3(x, [6, 11, 25]); - return rotr6.xor(rotr11).xor(rotr25); + return sigma(x, [6, 11, 25]); } // lowercase sigma = delta to avoid confusing function names function DeltaZero(x: UInt32) { - let rotr7 = ROTR(7, x); - let rotr18 = ROTR(18, x); - let shr3 = SHR(3, x); - - return rotr7.xor(rotr18).xor(shr3); + return sigma(x, [3, 7, 18], true); } function DeltaOne(x: UInt32) { - let rotr17 = ROTR(17, x); - let rotr19 = ROTR(19, x); - let shr10 = SHR(10, x); - return rotr17.xor(rotr19).xor(shr10); + return sigma(x, [10, 17, 19], true); } function ROTR(n: number, x: UInt32) { @@ -185,14 +176,16 @@ function SHR(n: number, x: UInt32) { return val; } -function ROTR3Simple(u: UInt32, bits: TupleN): TupleN { +function sigmaSimple(u: UInt32, bits: TupleN, firstShifted = false) { let [r0, r1, r2] = bits; - return [ROTR(r0, u), ROTR(r1, u), ROTR(r2, u)]; + 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); } -// assumes that outputs are range-checked to 32 bits externally -function ROTR3(u: UInt32, bits: TupleN): TupleN { - if (u.isConstant()) return ROTR3Simple(u, bits); +function sigma(u: UInt32, 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; @@ -231,9 +224,19 @@ function ROTR3(u: UInt32, bits: TupleN): TupleN { // reassemble chunks into rotated values - // rotr(x, r0) = x1 + x2*2^d1 + x3*2^(d1+d2) + x0*2^(d1+d2+d3) - let 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 + 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(); @@ -245,7 +248,7 @@ function ROTR3(u: UInt32, bits: TupleN): TupleN { 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 - return TupleN.map([xRotR0, xRotR1, xRotR2], (x) => UInt32.from(x)); + return UInt32.from(xRotR0).xor(xRotR1).xor(xRotR2); } function rangeCheckNSmall(x: Field, n: number) { From c783258449997831146fff5be5c2b0e538993476 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 08:38:18 +0100 Subject: [PATCH 11/52] remove unused function --- src/lib/gadgets/sha256.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index eec0ca3949..d513b610f4 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -251,17 +251,6 @@ function sigma(u: UInt32, bits: TupleN, firstShifted = false) { return UInt32.from(xRotR0).xor(xRotR1).xor(xRotR2); } -function rangeCheckNSmall(x: Field, n: number) { - assert(n <= 16, 'expected n <= 16'); - - // x < 2^16 - rangeCheck16(x); - if (n === 16) return; - - // 2^(16-n)*x < 2^16, which implies x < 2^n - rangeCheck16(x.mul(1 << (16 - n)).seal()); -} - function rangeCheck16(x: Field) { x.rangeCheckHelper(16).assertEquals(x); } From ef874db7b8d3768ace200ba9f6f80a95e001e0a3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 09:25:14 +0100 Subject: [PATCH 12/52] express maj with fewer xors --- src/lib/gadgets/common.ts | 12 ++++++++++++ src/lib/gadgets/sha256.ts | 21 ++++++++++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 196ca64e73..4f50bb1c27 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -17,6 +17,7 @@ export { witnessSlice, witnessNextValue, divideWithRemainder, + toBigints, }; function existsOne(compute: () => bigint) { @@ -60,6 +61,17 @@ function toVars>( return Tuple.map(fields, toVar); } +/** + * Convert several Fields to bigints. + */ +function toBigints< + T extends Tuple<{ toBigInt(): bigint } | { toBigint(): bigint }> +>(...fields: T) { + return Tuple.map(fields, (x) => + 'toBigInt' in x ? x.toBigInt() : x.toBigint() + ); +} + function assert(stmt: boolean, message?: string): asserts stmt { if (!stmt) { throw Error(message ?? 'Assertion failed'); diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index d513b610f4..3370250906 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -2,7 +2,7 @@ import { Field } from '../field.js'; import { UInt32 } from '../int.js'; import { TupleN } from '../util/types.js'; -import { assert, bitSlice, exists } from './common.js'; +import { bitSlice, exists, existsOne, toBigints } from './common.js'; import { Gadgets } from './gadgets.js'; export { SHA256 }; @@ -142,11 +142,22 @@ function Ch(x: UInt32, y: UInt32, z: UInt32) { } function Maj(x: UInt32, y: UInt32, z: UInt32) { - let xAndY = x.and(y); - let xAndZ = x.and(z); - let yAndZ = y.and(z); + if (x.isConstant() && y.isConstant() && z.isConstant()) { + let [x0, y0, z0] = toBigints(x, y, z); + return UInt32.from((x0 & y0) ^ (x0 & z0) ^ (y0 & z0)); + } + + let maj = existsOne(() => { + let [x0, y0, z0] = toBigints(x, y, z); + return (x0 & y0) ^ (x0 & z0) ^ (y0 & z0); + }); - return xAndY.xor(xAndZ).xor(yAndZ); + // maj(x, y, z) = (x & y) ^ (x & z) ^ (y & z) can be alternatively expressed as + // x + y + z = 2*maj(x, y, z) + (x ^ y ^ z) + let sum = x.value.add(y.value).add(z.value).seal(); + let xor = x.xor(y).xor(z); + maj.mul(2).add(xor.value).assertEquals(sum); + return UInt32.from(maj); } function SigmaZero(x: UInt32) { From 0ba733ad28f408a9775b1c5b5e32e5a28953f524 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 09:48:59 +0100 Subject: [PATCH 13/52] speed up Ch by replacing 1 XOR with addition --- src/lib/gadgets/sha256.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 3370250906..47748b369b 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -138,7 +138,9 @@ const SHA256 = { function Ch(x: UInt32, y: UInt32, z: UInt32) { let xAndY = x.and(y); let xNotAndZ = x.not().and(z); - return xAndY.xor(xNotAndZ); + // because of the occurence of x and ~x, the bits of (x & y) and (~x & z) are disjoint + // therefore, we can use + instead of XOR which is faster in a circuit + return UInt32.from(xAndY.value.add(xNotAndZ.value).seal()); } function Maj(x: UInt32, y: UInt32, z: UInt32) { From cf486594858ae1dad91acf4d07481fed355ee425 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 10:37:30 +0100 Subject: [PATCH 14/52] simplify logic --- src/lib/gadgets/sha256.ts | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 47748b369b..b09e370218 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -136,29 +136,20 @@ const SHA256 = { }; function Ch(x: UInt32, y: UInt32, z: UInt32) { - let xAndY = x.and(y); - let xNotAndZ = x.not().and(z); - // because of the occurence of x and ~x, the bits of (x & y) and (~x & z) are disjoint - // therefore, we can use + instead of XOR which is faster in a circuit - return UInt32.from(xAndY.value.add(xNotAndZ.value).seal()); + // 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.from(ch); } function Maj(x: UInt32, y: UInt32, z: UInt32) { - if (x.isConstant() && y.isConstant() && z.isConstant()) { - let [x0, y0, z0] = toBigints(x, y, z); - return UInt32.from((x0 & y0) ^ (x0 & z0) ^ (y0 & z0)); - } - - let maj = existsOne(() => { - let [x0, y0, z0] = toBigints(x, y, z); - return (x0 & y0) ^ (x0 & z0) ^ (y0 & z0); - }); - - // maj(x, y, z) = (x & y) ^ (x & z) ^ (y & z) can be alternatively expressed as - // x + y + z = 2*maj(x, y, z) + (x ^ y ^ z) + // 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); - maj.mul(2).add(xor.value).assertEquals(sum); + let xor = x.xor(y).xor(z).value; + let maj = sum.sub(xor).div(2).seal(); return UInt32.from(maj); } From 3efe56f2e5e45df8cca2d5e3bdab1bee4e1f5954 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 10:39:19 +0100 Subject: [PATCH 15/52] let's not introduce an unused function --- src/lib/gadgets/common.ts | 12 ------------ src/lib/gadgets/sha256.ts | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 4f50bb1c27..196ca64e73 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -17,7 +17,6 @@ export { witnessSlice, witnessNextValue, divideWithRemainder, - toBigints, }; function existsOne(compute: () => bigint) { @@ -61,17 +60,6 @@ function toVars>( return Tuple.map(fields, toVar); } -/** - * Convert several Fields to bigints. - */ -function toBigints< - T extends Tuple<{ toBigInt(): bigint } | { toBigint(): bigint }> ->(...fields: T) { - return Tuple.map(fields, (x) => - 'toBigInt' in x ? x.toBigInt() : x.toBigint() - ); -} - function assert(stmt: boolean, message?: string): asserts stmt { if (!stmt) { throw Error(message ?? 'Assertion failed'); diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index b09e370218..caa43f80fa 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -2,7 +2,7 @@ import { Field } from '../field.js'; import { UInt32 } from '../int.js'; import { TupleN } from '../util/types.js'; -import { bitSlice, exists, existsOne, toBigints } from './common.js'; +import { bitSlice, exists } from './common.js'; import { Gadgets } from './gadgets.js'; export { SHA256 }; From 1b4279b32f1e825e9aae585af4f8e3f3f9c120fd Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 17:38:51 +0100 Subject: [PATCH 16/52] do smaller range checks on the quotient for add mod 32 --- src/lib/gadgets/arithmetic.ts | 13 +++++++++---- src/lib/gadgets/sha256.ts | 8 +++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/lib/gadgets/arithmetic.ts b/src/lib/gadgets/arithmetic.ts index 414ddfe814..31350bfa6a 100644 --- a/src/lib/gadgets/arithmetic.ts +++ b/src/lib/gadgets/arithmetic.ts @@ -1,12 +1,13 @@ +import { Bool } from '../bool.js'; import { provableTuple } from '../circuit_value.js'; import { Field } from '../core.js'; import { assert } from '../errors.js'; import { Provable } from '../provable.js'; -import { rangeCheck32 } from './range-check.js'; +import { rangeCheck32, rangeCheckN } from './range-check.js'; export { divMod32, addMod32 }; -function divMod32(n: Field) { +function divMod32(n: Field, quotientBits = 32) { if (n.isConstant()) { assert( n.toBigInt() < 1n << 64n, @@ -32,7 +33,11 @@ function divMod32(n: Field) { } ); - rangeCheck32(quotient); + if (quotientBits === 1) { + Bool.check(Bool.Unsafe.ofField(quotient)); + } else { + rangeCheckN(quotientBits, quotient); + } rangeCheck32(remainder); n.assertEquals(quotient.mul(1n << 32n).add(remainder)); @@ -44,5 +49,5 @@ function divMod32(n: Field) { } function addMod32(x: Field, y: Field) { - return divMod32(x.add(y)).remainder; + return divMod32(x.add(y), 1).remainder; } diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index caa43f80fa..5c389a9e52 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -83,7 +83,7 @@ const SHA256 = { .value.add(W[t - 7].value) .add(DeltaZero(W[t - 15]).value.add(W[t - 16].value)); - W[t] = UInt32.from(Gadgets.divMod32(unreduced).remainder); + W[t] = UInt32.from(Gadgets.divMod32(unreduced, 16).remainder); } // initialize working variables @@ -109,12 +109,14 @@ const SHA256 = { h = g; g = f; f = e; - e = UInt32.from(Gadgets.divMod32(d.value.add(unreducedT1)).remainder); + e = UInt32.from( + Gadgets.divMod32(d.value.add(unreducedT1), 16).remainder + ); d = c; c = b; b = a; a = UInt32.from( - Gadgets.divMod32(unreducedT2.add(unreducedT1)).remainder + Gadgets.divMod32(unreducedT2.add(unreducedT1), 16).remainder ); } From 99094d612b82e09ffa4e1491538c5bd238aec8a5 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 19 Dec 2023 12:06:14 +0100 Subject: [PATCH 17/52] start touching up SHA256 API --- src/examples/sha256.ts | 61 +++------------------------------- src/lib/gadgets/sha256.ts | 70 ++++++++++++++++++++++++++++++++++++--- src/lib/int.ts | 7 ++++ 3 files changed, 77 insertions(+), 61 deletions(-) diff --git a/src/examples/sha256.ts b/src/examples/sha256.ts index ac9dce1076..a38926d72c 100644 --- a/src/examples/sha256.ts +++ b/src/examples/sha256.ts @@ -1,65 +1,14 @@ -import assert from 'assert'; -import { Gadgets, Provable, UInt32 } from 'o1js'; +import { Bytes, Field, Gadgets, Provable, UInt32, UInt64, UInt8 } from 'o1js'; -const Parser = Gadgets.SHA256.processStringToMessageBlocks; -const Hash = Gadgets.SHA256.hash; +type FlexibleBytes = Bytes | (UInt8 | bigint | number)[] | Uint8Array; -const testVectors = [ - { - msg: '', - expected: - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - }, - { - msg: 'duck', - expected: - '2d2370db2447ff8cf4f3accd68c85aa119a9c893effd200a9b69176e9fc5eb98', - }, - { - msg: 'doggo', - expected: - '8aa89c66e2c453b71400ac832a345d872c33147150267be5402552ee19b3d4ce', - }, - { - msg: 'frog', - expected: - '74fa5327cc0f4e947789dd5e989a61a8242986a596f170640ac90337b1da1ee4', - }, -]; - -const run = (msg: string, expected: string) => { - let messageBlocks = Provable.witness( - Provable.Array(Provable.Array(UInt32, 16), 1), - () => Parser(msg) - ); - let digest = Hash(messageBlocks); - Provable.asProver(() => { - let y = toHex(digest); - assert(expected === y, `expected ${expected} got ${y}`); - }); -}; - -console.log('running plain'); -testVectors.forEach((v) => { - run(v.msg, v.expected); -}); - -console.log('run and check'); Provable.runAndCheck(() => { - testVectors.forEach((v) => { - run(v.msg, v.expected); - }); -}); + let digest = Gadgets.SHA256.hash(Bytes.fromString('Hello world!')); -console.log('constraint system'); -let cs = Provable.constraintSystem(() => { - testVectors.forEach((v) => { - run(v.msg, v.expected); + Provable.asProver(() => { + console.log(toHex(digest)); }); }); - -console.log(cs); - function toHex(xs: UInt32[]) { let hex = ''; for (let h = 0; h < xs.length; h++) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 5c389a9e52..4e2591b858 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -1,6 +1,8 @@ // https://csrc.nist.gov/pubs/fips/180-4/upd1/final import { Field } from '../field.js'; -import { UInt32 } from '../int.js'; +import { UInt32, UInt8 } from '../int.js'; +import { Bytes, FlexibleBytes, createBytes } from '../provable-types/bytes.js'; +import { Provable } from '../provable.js'; import { TupleN } from '../util/types.js'; import { bitSlice, exists } from './common.js'; import { Gadgets } from './gadgets.js'; @@ -42,8 +44,66 @@ function processStringToMessageBlocks(s: string) { return blocks; } +function padding(data: FlexibleBytes): UInt32[][] { + // pad the data with zeros to reach the desired length + let zeroPadded = Bytes.from(data); + + // now pad the data to reach the format expected by sha256 + // pad 1 bit, followed by k zero bits where k is the smallest non-negative solution to + // l + 1 + k = 448 mod 512 + // then append a 64bit block containing the length of the original message in bits + // TODO: question, most values are witnessed because we dont need to prove the padding. + // The user is incentivized to provide the correct padding because otherwise the hash will be wrong. + + let l = data.length * 8; // length in bits + let k = (448 - (l + 1)) % 512; + let totalPaddingLength = (k + 1 + 64) / 8; // total length of the padding + + let padding = Provable.witness( + Provable.Array(UInt8, totalPaddingLength), + () => { + let padding = ( + '1' + + '0'.repeat(k) + + '0'.repeat(64 - l.toString(2).length) + + l.toString(2) + ).match(/.{1,8}/g)!; + + let xs = padding.map((x) => UInt8.from(BigInt('0b' + x))); + return xs; + } + ); + + // concatenate the padding with the original padded data + let messageBlocks = zeroPadded.bytes.concat(padding); + + // split the message into 32bit chunks + let chunks: UInt32[] = []; + + for (let i = 0; i < messageBlocks.length; i += 4) { + chunks.push(concat(messageBlocks.slice(i, i + 4))); + } + + // split message blocks into 16 element sized blocks + let xs: UInt32[][] = []; + for (let i = 0; i < chunks.length; i += 16) { + xs.push(chunks.slice(i, i + 16)); + } + return xs; +} + +function concat(xs: UInt8[]) { + let target = new Field(0); + xs.forEach((x) => { + target = Gadgets.leftShift32(target, 8).add(x.value); + }); + Gadgets.rangeCheck32(target); + return new UInt32(target); +} + const SHA256 = { - hash(data: UInt32[][]) { + hash(data: FlexibleBytes) { + const message = Bytes.from(data); // constants §4.2.2 const K = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, @@ -67,13 +127,13 @@ const SHA256 = { // TODO: correct dynamic preprocessing §6.2 // padding the message $5.1.1 into blocks that are a multiple of 512 - let messageBlocks = data; + let messageBlocks = padding(data); const N = messageBlocks.length; for (let i = 0; i < N; i++) { const M = messageBlocks[i]; - // for each message block of 16 x 32 bytes do: + // for each message block of 16 x 32bit do: const W: UInt32[] = []; // prepare message block @@ -254,7 +314,7 @@ function sigma(u: UInt32, bits: TupleN, firstShifted = false) { 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 - return UInt32.from(xRotR0).xor(xRotR1).xor(xRotR2); + return UInt32.from(xRotR0).xor(new UInt32(xRotR1)).xor(new UInt32(xRotR2)); } function rangeCheck16(x: Field) { diff --git a/src/lib/int.ts b/src/lib/int.ts index 9bae3afdf1..a98ff8cf4b 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -629,6 +629,13 @@ class UInt32 extends CircuitValue { return new UInt32(Field((1n << 32n) - 1n)); } + /** + * Addition modulo 2^32. Check {@link Gadgets.addMod32} for a detailed description. + */ + addMod32(y: UInt32) { + return new UInt32(Gadgets.addMod32(this.value, y.value)); + } + /** * Integer division with remainder. * From 691ac671aed98f24a553e824ec33dccf717cca81 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 19 Dec 2023 15:07:29 +0100 Subject: [PATCH 18/52] finish API, start tests --- src/examples/sha256.ts | 37 ++++++++-- src/lib/gadgets/sha256.ts | 111 ++++++++++++---------------- src/lib/gadgets/sha256.unit-test.ts | 19 +++++ 3 files changed, 95 insertions(+), 72 deletions(-) create mode 100644 src/lib/gadgets/sha256.unit-test.ts diff --git a/src/examples/sha256.ts b/src/examples/sha256.ts index a38926d72c..f6c694952d 100644 --- a/src/examples/sha256.ts +++ b/src/examples/sha256.ts @@ -1,14 +1,35 @@ -import { Bytes, Field, Gadgets, Provable, UInt32, UInt64, UInt8 } from 'o1js'; +import { + Bytes, + Field, + Gadgets, + Provable, + UInt32, + UInt64, + UInt8, + ZkProgram, +} from 'o1js'; -type FlexibleBytes = Bytes | (UInt8 | bigint | number)[] | Uint8Array; +/* +let SHA256 = ZkProgram({ + name: 'sha256', + publicOutput: Bytes(32).provable, + methods: { + sha256: { + privateInputs: [Bytes12.provable], + method(xs: Bytes12) { + return Gadgets.SHA256.hash(xs); + }, + }, + }, +}); -Provable.runAndCheck(() => { - let digest = Gadgets.SHA256.hash(Bytes.fromString('Hello world!')); +await SHA256.compile(); */ +class BytesN extends Bytes(58) {} + +let preimage = BytesN.fromString('hello world!'); + +Gadgets.SHA256.hash(preimage); - Provable.asProver(() => { - console.log(toHex(digest)); - }); -}); function toHex(xs: UInt32[]) { let hex = ''; for (let h = 0; h < xs.length; h++) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 4e2591b858..b466993088 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -9,41 +9,6 @@ import { Gadgets } from './gadgets.js'; export { SHA256 }; -function processStringToMessageBlocks(s: string) { - let msgBits = s - .split('') - .map((c) => { - let binary = c.charCodeAt(0).toString(2); - return '00000000'.substr(binary.length) + binary; - }) - .join(''); - - let l = msgBits.length; - msgBits = msgBits + '1'; - - // calculate k in l + 1 +k = 448 mod 512 - let remainder = (448 - (l + 1)) % 512; - - let k = (remainder + 512) % 512; - let padding = '0'.repeat(k); - msgBits = msgBits + padding; - let lBits = l.toString(2); - msgBits = msgBits + '0'.repeat(64 - lBits.length) + lBits; - - let bitBlocks32 = []; - for (let i = 0; i < msgBits.length; i += 32) { - bitBlocks32.push(UInt32.from(BigInt('0b' + msgBits.substr(i, 32)))); - } - - let lengthBlocks = bitBlocks32.length; - let blocks = []; - for (let i = 0; i < lengthBlocks; i += 16) { - let block = bitBlocks32.slice(i, i + 16); - blocks.push(block); - } - return blocks; -} - function padding(data: FlexibleBytes): UInt32[][] { // pad the data with zeros to reach the desired length let zeroPadded = Bytes.from(data); @@ -63,9 +28,9 @@ function padding(data: FlexibleBytes): UInt32[][] { Provable.Array(UInt8, totalPaddingLength), () => { let padding = ( - '1' + - '0'.repeat(k) + - '0'.repeat(64 - l.toString(2).length) + + '1' + // append 1 bit + '0'.repeat(k) + // append k zero bits + '0'.repeat(64 - l.toString(2).length) + // append 64bit containing the length of the original message l.toString(2) ).match(/.{1,8}/g)!; @@ -75,23 +40,24 @@ function padding(data: FlexibleBytes): UInt32[][] { ); // concatenate the padding with the original padded data - let messageBlocks = zeroPadded.bytes.concat(padding); + let paddedMessage = zeroPadded.bytes.concat(padding); // split the message into 32bit chunks let chunks: UInt32[] = []; - for (let i = 0; i < messageBlocks.length; i += 4) { - chunks.push(concat(messageBlocks.slice(i, i + 4))); + for (let i = 0; i < paddedMessage.length; i += 4) { + chunks.push(concat(paddedMessage.slice(i, i + 4))); } - // split message blocks into 16 element sized blocks - let xs: UInt32[][] = []; + // split message into 16 element sized message blocks + let messageBlocks: UInt32[][] = []; for (let i = 0; i < chunks.length; i += 16) { - xs.push(chunks.slice(i, i + 16)); + messageBlocks.push(chunks.slice(i, i + 16)); } - return xs; + return messageBlocks; } +// concatenate bytes into a 32bit word using bit shifting function concat(xs: UInt8[]) { let target = new Field(0); xs.forEach((x) => { @@ -101,31 +67,44 @@ function concat(xs: UInt8[]) { return new UInt32(target); } +// decompose a 32bit word into 4 bytes +function decomposeToBytes(a: UInt32) { + let field = a.value; + let ys = []; + for (let i = 0; i < 4; i++) { + let { quotient, remainder } = Gadgets.divMod32(field.mul(1n << 8n)); + field = remainder; + ys.push(quotient); + } + + // UInt8.from does a rangeCheck8 + return ys.map(UInt8.from); +} + +// constants §4.2.2 +const K = [ + 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, +].map((k) => UInt32.from(k)); + const SHA256 = { hash(data: FlexibleBytes) { - const message = Bytes.from(data); - // constants §4.2.2 - const K = [ - 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, - ].map((k) => UInt32.from(k)); - // initial hash values §5.3.3 const H = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, ].map((h) => UInt32.from(h)); - // TODO: correct dynamic preprocessing §6.2 + // preprocessing §6.2 // padding the message $5.1.1 into blocks that are a multiple of 512 let messageBlocks = padding(data); @@ -192,9 +171,13 @@ const SHA256 = { H[7] = H[7].addMod32(h); } - return H; + let ys: UInt8[] = []; + H.forEach((x) => { + ys.push(...decomposeToBytes(x)); + }); + + return Bytes.from(ys); }, - processStringToMessageBlocks: processStringToMessageBlocks, }; function Ch(x: UInt32, y: UInt32, z: UInt32) { diff --git a/src/lib/gadgets/sha256.unit-test.ts b/src/lib/gadgets/sha256.unit-test.ts new file mode 100644 index 0000000000..e91f1b1411 --- /dev/null +++ b/src/lib/gadgets/sha256.unit-test.ts @@ -0,0 +1,19 @@ +import { Field } from '../field.js'; +import { ZkProgram } from '../proof_system.js'; +import { Bytes } from '../provable-types/provable-types.js'; +import { Gadgets } from './gadgets.js'; +import { sha256 as nobleSha256 } from '@noble/hashes/sha256'; +import { bytes } from './test-utils.js'; +import { equivalent } from '../testing/equivalent.js'; +import { Random, sample } from '../testing/random.js'; + +sample(Random.nat(100), 10).forEach((preimageLength) => { + let inputBytes = bytes(preimageLength); + let outputBytes = bytes(256 / 8); + + equivalent({ from: [inputBytes], to: outputBytes, verbose: true })( + (x) => nobleSha256(x), + (x) => Gadgets.SHA256.hash(x), + `sha256 preimage length ${preimageLength}` + ); +}); From dc4ddb97d442057a81b4f85328f8089e9bea72ef Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 19 Dec 2023 15:21:45 +0100 Subject: [PATCH 19/52] fix block size, add tests --- src/lib/gadgets/sha256.ts | 4 ++++ src/lib/gadgets/sha256.unit-test.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index b466993088..33eb392c56 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -22,6 +22,10 @@ function padding(data: FlexibleBytes): UInt32[][] { let l = data.length * 8; // length in bits let k = (448 - (l + 1)) % 512; + + // pad for new blog size + if (k < 0) k += 512; + let totalPaddingLength = (k + 1 + 64) / 8; // total length of the padding let padding = Provable.witness( diff --git a/src/lib/gadgets/sha256.unit-test.ts b/src/lib/gadgets/sha256.unit-test.ts index e91f1b1411..5a7b8104fc 100644 --- a/src/lib/gadgets/sha256.unit-test.ts +++ b/src/lib/gadgets/sha256.unit-test.ts @@ -7,7 +7,7 @@ import { bytes } from './test-utils.js'; import { equivalent } from '../testing/equivalent.js'; import { Random, sample } from '../testing/random.js'; -sample(Random.nat(100), 10).forEach((preimageLength) => { +sample(Random.nat(400), 25).forEach((preimageLength) => { let inputBytes = bytes(preimageLength); let outputBytes = bytes(256 / 8); From c49e07bad5e44868637aa313e8d2ec9d75332094 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 19 Dec 2023 16:05:24 +0100 Subject: [PATCH 20/52] add tests --- src/lib/gadgets/sha256.ts | 33 +++++++-------- src/lib/gadgets/sha256.unit-test.ts | 63 +++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 19 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 33eb392c56..13933a7da5 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -1,5 +1,5 @@ // https://csrc.nist.gov/pubs/fips/180-4/upd1/final -import { Field } from '../field.js'; +import { Field } from '../core.js'; import { UInt32, UInt8 } from '../int.js'; import { Bytes, FlexibleBytes, createBytes } from '../provable-types/bytes.js'; import { Provable } from '../provable.js'; @@ -85,21 +85,6 @@ function decomposeToBytes(a: UInt32) { return ys.map(UInt8.from); } -// constants §4.2.2 -const K = [ - 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, -].map((k) => UInt32.from(k)); - const SHA256 = { hash(data: FlexibleBytes) { // initial hash values §5.3.3 @@ -108,6 +93,22 @@ const SHA256 = { 0x1f83d9ab, 0x5be0cd19, ].map((h) => UInt32.from(h)); + // I dont like to have this in here but UInt32 isn't initialized if used at top level + // constants §4.2.2 + const K = [ + 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, + ].map((k) => UInt32.from(k)); + // preprocessing §6.2 // padding the message $5.1.1 into blocks that are a multiple of 512 let messageBlocks = padding(data); diff --git a/src/lib/gadgets/sha256.unit-test.ts b/src/lib/gadgets/sha256.unit-test.ts index 5a7b8104fc..c2ade3f822 100644 --- a/src/lib/gadgets/sha256.unit-test.ts +++ b/src/lib/gadgets/sha256.unit-test.ts @@ -1,13 +1,13 @@ -import { Field } from '../field.js'; import { ZkProgram } from '../proof_system.js'; import { Bytes } from '../provable-types/provable-types.js'; import { Gadgets } from './gadgets.js'; import { sha256 as nobleSha256 } from '@noble/hashes/sha256'; import { bytes } from './test-utils.js'; -import { equivalent } from '../testing/equivalent.js'; +import { equivalent, equivalentAsync } from '../testing/equivalent.js'; import { Random, sample } from '../testing/random.js'; +import { expect } from 'expect'; -sample(Random.nat(400), 25).forEach((preimageLength) => { +sample(Random.nat(400), 10).forEach((preimageLength) => { let inputBytes = bytes(preimageLength); let outputBytes = bytes(256 / 8); @@ -17,3 +17,60 @@ sample(Random.nat(400), 25).forEach((preimageLength) => { `sha256 preimage length ${preimageLength}` ); }); + +const Sha256Program = ZkProgram({ + name: `sha256`, + publicOutput: Bytes(32).provable, + methods: { + sha256: { + privateInputs: [Bytes(192).provable], + method(preImage: Bytes) { + return Gadgets.SHA256.hash(preImage); + }, + }, + }, +}); + +const RUNS = 5; + +await Sha256Program.compile(); + +await equivalentAsync( + { + from: [bytes(192)], + to: bytes(32), + }, + { runs: RUNS } +)(nobleSha256, async (x) => { + const proof = await Sha256Program.sha256(x); + await Sha256Program.verify(proof); + return proof.publicOutput; +}); + +for (let { preimage, hash } of testVectors()) { + class BytesN extends Bytes(preimage.length) {} + let actual = Gadgets.SHA256.hash(BytesN.fromString(preimage)); + expect(actual.toHex()).toEqual(hash); +} + +function testVectors() { + return [ + { + preimage: 'abc', + hash: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', + }, + { + preimage: 'a'.repeat(1000000), + hash: 'cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0', + }, + { + preimage: '', + hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + { + preimage: + 'de188941a3375d3a8a061e67576e926dc71a7fa3f0cceb97452b4d3227965f9ea8cc75076d9fb9c5417aa5cb30fc22198b34982dbb629e', + hash: '70b6ee0dd06c26d51177d5bb1de954d6d50aa9f7b771b4401415d43da40605ad', + }, + ]; +} From ea83f74350fe8a2f32aba8d6036b394b0c15a3d6 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 19 Dec 2023 16:08:43 +0100 Subject: [PATCH 21/52] add trivial example --- src/examples/sha256.ts | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/src/examples/sha256.ts b/src/examples/sha256.ts index f6c694952d..fcda4698dd 100644 --- a/src/examples/sha256.ts +++ b/src/examples/sha256.ts @@ -1,15 +1,7 @@ -import { - Bytes, - Field, - Gadgets, - Provable, - UInt32, - UInt64, - UInt8, - ZkProgram, -} from 'o1js'; +import { Bytes, Gadgets, ZkProgram } from 'o1js'; + +class Bytes12 extends Bytes(12) {} -/* let SHA256 = ZkProgram({ name: 'sha256', publicOutput: Bytes(32).provable, @@ -23,17 +15,16 @@ let SHA256 = ZkProgram({ }, }); -await SHA256.compile(); */ -class BytesN extends Bytes(58) {} - -let preimage = BytesN.fromString('hello world!'); +await SHA256.compile(); +let preimage = Bytes12.fromString('hello world!'); -Gadgets.SHA256.hash(preimage); +let proof = await SHA256.sha256(preimage); -function toHex(xs: UInt32[]) { - let hex = ''; - for (let h = 0; h < xs.length; h++) - hex = hex + ('00000000' + xs[h].toBigint().toString(16)).slice(-8); +let isValid = await SHA256.verify(proof); - return hex; -} +if ( + proof.publicOutput.toHex() !== + '7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9' +) + throw new Error('Invalid sha256 digest!'); +if (!isValid) throw new Error('Invalid proof'); From 9b61ead488fee31e80aaf174574af432606d6bce Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 19 Dec 2023 16:14:22 +0100 Subject: [PATCH 22/52] move example, dump VKs --- src/examples/{ => crypto}/sha256.ts | 10 ++++++---- tests/vk-regression/vk-regression.json | 13 +++++++++++++ tests/vk-regression/vk-regression.ts | 2 ++ 3 files changed, 21 insertions(+), 4 deletions(-) rename src/examples/{ => crypto}/sha256.ts (74%) diff --git a/src/examples/sha256.ts b/src/examples/crypto/sha256.ts similarity index 74% rename from src/examples/sha256.ts rename to src/examples/crypto/sha256.ts index fcda4698dd..1b79085058 100644 --- a/src/examples/sha256.ts +++ b/src/examples/crypto/sha256.ts @@ -1,8 +1,10 @@ import { Bytes, Gadgets, ZkProgram } from 'o1js'; +export { SHA256Program }; + class Bytes12 extends Bytes(12) {} -let SHA256 = ZkProgram({ +let SHA256Program = ZkProgram({ name: 'sha256', publicOutput: Bytes(32).provable, methods: { @@ -15,12 +17,12 @@ let SHA256 = ZkProgram({ }, }); -await SHA256.compile(); +await SHA256Program.compile(); let preimage = Bytes12.fromString('hello world!'); -let proof = await SHA256.sha256(preimage); +let proof = await SHA256Program.sha256(preimage); -let isValid = await SHA256.verify(proof); +let isValid = await SHA256Program.verify(proof); if ( proof.publicOutput.toHex() !== diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index a7ff2ecb38..74c98a7c4d 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -264,5 +264,18 @@ "data": "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", "hash": "24853426911779666983119365864639019692354940105641954611250992067926077919455" } + }, + "sha256": { + "digest": "94f9e7bfe7224549a32ebf3e2f65e5d848e9feebd4c79a5d966a084c28b41fe", + "methods": { + "sha256": { + "rows": 5957, + "digest": "ea7ff27557e7c6270c200f2feb62a3a7" + } + }, + "verificationKey": { + "data": "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", + "hash": "5242875480670941030919675131962218019722619843591732956374487945418325506772" + } } } \ No newline at end of file diff --git a/tests/vk-regression/vk-regression.ts b/tests/vk-regression/vk-regression.ts index 9c63c7e38e..bc6a644934 100644 --- a/tests/vk-regression/vk-regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -7,6 +7,7 @@ import { ecdsa, keccakAndEcdsa, } from '../../src/examples/crypto/ecdsa/ecdsa.js'; +import { SHA256Program } from '../../src/examples/crypto/sha256.js'; import { GroupCS, BitwiseCS, HashCS } from './plain-constraint-system.js'; // toggle this for quick iteration when debugging vk regressions @@ -45,6 +46,7 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ HashCS, ecdsa, keccakAndEcdsa, + SHA256Program, ]; let filePath = jsonPath ? jsonPath : './tests/vk-regression/vk-regression.json'; From 9e9a3cb70bd4ae5f75fc87c6ef94baa868375889 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 19 Dec 2023 17:11:41 +0100 Subject: [PATCH 23/52] add SHA256 to Hash namespace --- src/lib/hashes-combined.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/hashes-combined.ts b/src/lib/hashes-combined.ts index 8440a25b32..edd64f8355 100644 --- a/src/lib/hashes-combined.ts +++ b/src/lib/hashes-combined.ts @@ -1,3 +1,4 @@ +import { Gadgets } from './gadgets/gadgets.js'; import { Poseidon } from './hash.js'; import { Keccak } from './keccak.js'; import { Bytes } from './provable-types/provable-types.js'; @@ -35,6 +36,19 @@ const Hash = { */ Poseidon, + /** + * The SHA2 hash function with an output length of 256 bits. + */ + SHA2_256: { + /** + * Hashes the given bytes using SHA2-256. + * + * This is an alias for `Gadgets.SHA256.hash(bytes)`.\ + * See {@link Gadgets.SHA256.hash} for details and usage examples. + */ + hash: Gadgets.SHA256.hash, + }, + /** * The SHA3 hash function with an output length of 256 bits. */ From a6252bdb970d217a62926f863bf86275efe11478 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 20 Dec 2023 13:10:09 +0100 Subject: [PATCH 24/52] make witness constant --- src/lib/gadgets/sha256.ts | 44 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 13933a7da5..f489181b09 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -1,7 +1,7 @@ // https://csrc.nist.gov/pubs/fips/180-4/upd1/final import { Field } from '../core.js'; import { UInt32, UInt8 } from '../int.js'; -import { Bytes, FlexibleBytes, createBytes } from '../provable-types/bytes.js'; +import { Bytes, FlexibleBytes } from '../provable-types/bytes.js'; import { Provable } from '../provable.js'; import { TupleN } from '../util/types.js'; import { bitSlice, exists } from './common.js'; @@ -11,37 +11,29 @@ export { SHA256 }; function padding(data: FlexibleBytes): UInt32[][] { // pad the data with zeros to reach the desired length + // this is because we need to inherit to a static sized Bytes array. unrelated to sha256 let zeroPadded = Bytes.from(data); // now pad the data to reach the format expected by sha256 // pad 1 bit, followed by k zero bits where k is the smallest non-negative solution to // l + 1 + k = 448 mod 512 // then append a 64bit block containing the length of the original message in bits - // TODO: question, most values are witnessed because we dont need to prove the padding. - // The user is incentivized to provide the correct padding because otherwise the hash will be wrong. - let l = data.length * 8; // length in bits + let l = zeroPadded.length * 8; // length in bits let k = (448 - (l + 1)) % 512; - // pad for new blog size + // pad message exceeds 512bit and needs a new block if (k < 0) k += 512; - let totalPaddingLength = (k + 1 + 64) / 8; // total length of the padding + let paddingBits = ( + '1' + // append 1 bit + '0'.repeat(k) + // append k zero bits + '0'.repeat(64 - l.toString(2).length) + // append 64bit containing the length of the original message + l.toString(2) + ).match(/.{1,8}/g)!; // this should always be devisable by 8 - let padding = Provable.witness( - Provable.Array(UInt8, totalPaddingLength), - () => { - let padding = ( - '1' + // append 1 bit - '0'.repeat(k) + // append k zero bits - '0'.repeat(64 - l.toString(2).length) + // append 64bit containing the length of the original message - l.toString(2) - ).match(/.{1,8}/g)!; - - let xs = padding.map((x) => UInt8.from(BigInt('0b' + x))); - return xs; - } - ); + // 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 = zeroPadded.bytes.concat(padding); @@ -50,10 +42,12 @@ function padding(data: FlexibleBytes): UInt32[][] { let chunks: UInt32[] = []; for (let i = 0; i < paddedMessage.length; i += 4) { - chunks.push(concat(paddedMessage.slice(i, i + 4))); + // chunk 4 bytes into one UInt32, as expected by SHA256 + chunks.push(concatToUInt32(paddedMessage.slice(i, i + 4))); } // split message into 16 element sized message blocks + // SHA256 expects n-blocks of 512bit each, 16*32bit = 512bit let messageBlocks: UInt32[][] = []; for (let i = 0; i < chunks.length; i += 16) { messageBlocks.push(chunks.slice(i, i + 16)); @@ -62,11 +56,13 @@ function padding(data: FlexibleBytes): UInt32[][] { } // concatenate bytes into a 32bit word using bit shifting -function concat(xs: UInt8[]) { +function concatToUInt32(xs: UInt8[]) { let target = new Field(0); xs.forEach((x) => { + // for each element we shift the target by 8 bits and add the element target = Gadgets.leftShift32(target, 8).add(x.value); }); + // making sure its actually 32bit Gadgets.rangeCheck32(target); return new UInt32(target); } @@ -76,12 +72,14 @@ function decomposeToBytes(a: UInt32) { let field = a.value; let ys = []; for (let i = 0; i < 4; i++) { + // for each byte we rotate the element and get the excess bits (8 at a time) and construct a UInt8 of it let { quotient, remainder } = Gadgets.divMod32(field.mul(1n << 8n)); + // "shift" the element by 8 bit to get the next byte sequence during the next iteration field = remainder; ys.push(quotient); } - // UInt8.from does a rangeCheck8 + // UInt8.from does a rangeCheck8 for Field elements return ys.map(UInt8.from); } From 3ba3824261e3f14f8f8405e6967e627f80fd790c Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 20 Dec 2023 13:16:36 +0100 Subject: [PATCH 25/52] simplify --- src/lib/gadgets/sha256.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index f489181b09..b922fb4b73 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -121,10 +121,12 @@ const SHA256 = { // prepare message block for (let t = 0; t <= 15; t++) W[t] = M[t]; for (let t = 16; t <= 63; t++) { + // the field element is unreduced and not proven to be 32bit, 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 32bit the unreduced field element W[t] = UInt32.from(Gadgets.divMod32(unreduced, 16).remainder); } @@ -140,12 +142,14 @@ const SHA256 = { // 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(K[t].value) .add(W[t].value); + // T2 is also unreduced const unreducedT2 = SigmaZero(a).value.add(Maj(a, b, c).value); h = g; @@ -153,13 +157,13 @@ const SHA256 = { f = e; e = UInt32.from( Gadgets.divMod32(d.value.add(unreducedT1), 16).remainder - ); + ); // mod 32bit the unreduced field element d = c; c = b; b = a; a = UInt32.from( Gadgets.divMod32(unreducedT2.add(unreducedT1), 16).remainder - ); + ); // mod 32bit } // new intermediate hash value @@ -174,12 +178,15 @@ const SHA256 = { H[7] = H[7].addMod32(h); } + /* let ys: UInt8[] = []; H.forEach((x) => { ys.push(...decomposeToBytes(x)); }); + */ - return Bytes.from(ys); + // 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) => decomposeToBytes(x)).flat()); }, }; From 719742456a0be94a717191c022debaf9dbef7bb5 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 20 Dec 2023 13:25:51 +0100 Subject: [PATCH 26/52] add doc comments --- src/lib/gadgets/gadgets.ts | 20 +++++++++++++++++++- src/lib/gadgets/sha256.ts | 8 -------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 74c95cb812..18954d4080 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -798,7 +798,25 @@ const Gadgets = { * */ addMod32, - // TODO: everything + /** + * Implementation of the [SHA256 hash function.](https://en.wikipedia.org/wiki/SHA-2) Hash function with 256bit output. + * + * Applies the SHA2-256 hash function to a list of big-endian 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. + * Both input and output bytes are big-endian. + * + * @param data - Big-endian {@link Bytes} representing the message to hash. + * + * ```ts + * let preimage = Bytes.fromString("hello world"); + * let digest = Gadgets.SHA256.hash(preimage); + * ``` + * + */ SHA256: SHA256, }; diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index b922fb4b73..e428826921 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -167,7 +167,6 @@ const SHA256 = { } // new intermediate hash value - H[0] = H[0].addMod32(a); H[1] = H[1].addMod32(b); H[2] = H[2].addMod32(c); @@ -178,13 +177,6 @@ const SHA256 = { H[7] = H[7].addMod32(h); } - /* - let ys: UInt8[] = []; - H.forEach((x) => { - ys.push(...decomposeToBytes(x)); - }); - */ - // 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) => decomposeToBytes(x)).flat()); }, From 498ab345562484b9d26782fff33d464423b9d928 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 20 Dec 2023 13:31:49 +0100 Subject: [PATCH 27/52] Update sha256.ts --- src/lib/gadgets/sha256.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index e428826921..f877e3fc8e 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -2,7 +2,6 @@ import { Field } from '../core.js'; import { UInt32, UInt8 } from '../int.js'; import { Bytes, FlexibleBytes } from '../provable-types/bytes.js'; -import { Provable } from '../provable.js'; import { TupleN } from '../util/types.js'; import { bitSlice, exists } from './common.js'; import { Gadgets } from './gadgets.js'; From ebe202869378dd8be99fc5b79900fe28e66166c8 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 20 Dec 2023 13:33:40 +0100 Subject: [PATCH 28/52] undo accidental commit --- src/lib/gadgets/arithmetic.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/lib/gadgets/arithmetic.ts b/src/lib/gadgets/arithmetic.ts index 31350bfa6a..414ddfe814 100644 --- a/src/lib/gadgets/arithmetic.ts +++ b/src/lib/gadgets/arithmetic.ts @@ -1,13 +1,12 @@ -import { Bool } from '../bool.js'; import { provableTuple } from '../circuit_value.js'; import { Field } from '../core.js'; import { assert } from '../errors.js'; import { Provable } from '../provable.js'; -import { rangeCheck32, rangeCheckN } from './range-check.js'; +import { rangeCheck32 } from './range-check.js'; export { divMod32, addMod32 }; -function divMod32(n: Field, quotientBits = 32) { +function divMod32(n: Field) { if (n.isConstant()) { assert( n.toBigInt() < 1n << 64n, @@ -33,11 +32,7 @@ function divMod32(n: Field, quotientBits = 32) { } ); - if (quotientBits === 1) { - Bool.check(Bool.Unsafe.ofField(quotient)); - } else { - rangeCheckN(quotientBits, quotient); - } + rangeCheck32(quotient); rangeCheck32(remainder); n.assertEquals(quotient.mul(1n << 32n).add(remainder)); @@ -49,5 +44,5 @@ function divMod32(n: Field, quotientBits = 32) { } function addMod32(x: Field, y: Field) { - return divMod32(x.add(y), 1).remainder; + return divMod32(x.add(y)).remainder; } From 68b6d1a0176caffaadadc549fc87c8dafea8080d Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 20 Dec 2023 13:38:43 +0100 Subject: [PATCH 29/52] add commit back :X --- src/lib/gadgets/arithmetic.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/arithmetic.ts b/src/lib/gadgets/arithmetic.ts index 414ddfe814..31350bfa6a 100644 --- a/src/lib/gadgets/arithmetic.ts +++ b/src/lib/gadgets/arithmetic.ts @@ -1,12 +1,13 @@ +import { Bool } from '../bool.js'; import { provableTuple } from '../circuit_value.js'; import { Field } from '../core.js'; import { assert } from '../errors.js'; import { Provable } from '../provable.js'; -import { rangeCheck32 } from './range-check.js'; +import { rangeCheck32, rangeCheckN } from './range-check.js'; export { divMod32, addMod32 }; -function divMod32(n: Field) { +function divMod32(n: Field, quotientBits = 32) { if (n.isConstant()) { assert( n.toBigInt() < 1n << 64n, @@ -32,7 +33,11 @@ function divMod32(n: Field) { } ); - rangeCheck32(quotient); + if (quotientBits === 1) { + Bool.check(Bool.Unsafe.ofField(quotient)); + } else { + rangeCheckN(quotientBits, quotient); + } rangeCheck32(remainder); n.assertEquals(quotient.mul(1n << 32n).add(remainder)); @@ -44,5 +49,5 @@ function divMod32(n: Field) { } function addMod32(x: Field, y: Field) { - return divMod32(x.add(y)).remainder; + return divMod32(x.add(y), 1).remainder; } From 175228b65166914fc8e4712cc51dec4cf268f04d Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 20 Dec 2023 18:23:28 +0100 Subject: [PATCH 30/52] add flow comments --- src/examples/crypto/sha256.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/examples/crypto/sha256.ts b/src/examples/crypto/sha256.ts index 1b79085058..bc973499d9 100644 --- a/src/examples/crypto/sha256.ts +++ b/src/examples/crypto/sha256.ts @@ -17,13 +17,21 @@ let SHA256Program = ZkProgram({ }, }); +console.time('compile'); await SHA256Program.compile(); +console.timeEnd('compile'); + let preimage = Bytes12.fromString('hello world!'); -let proof = await SHA256Program.sha256(preimage); +console.log('sha256 rows:', SHA256Program.analyzeMethods().sha256.rows); +console.time('prove'); +let proof = await SHA256Program.sha256(preimage); +console.timeEnd('prove'); let isValid = await SHA256Program.verify(proof); +console.log('digest:', proof.publicOutput.toHex()); + if ( proof.publicOutput.toHex() !== '7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9' From 06afa39e09cb643707f3f5e4c233ad604686591a Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 20 Dec 2023 18:28:15 +0100 Subject: [PATCH 31/52] dump vks --- tests/vk-regression/vk-regression.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 329b1f1f40..74c98a7c4d 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -236,33 +236,33 @@ } }, "ecdsa-only": { - "digest": "1e6bef24a2e02b573363f3512822d401d53ec7220c8d5837cd49691c19723028", + "digest": "2113edb508f10afee42dd48aec81ac7d06805d76225b0b97300501136486bb30", "methods": { "verifySignedHash": { - "rows": 30680, - "digest": "5fe00efee7ecfb82b3ca69c8dd23d07f" + "rows": 38888, + "digest": "f75dd9e49c88eb6097a7f3abbe543467" } }, "verificationKey": { - "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAEce2RV0gBkOlsJXf/A50Yo1Y+0y0ZMB/g8wkRIs0p8RIff5piGXJPfSak+7+oCoV/CQoa0RkkegIKmjsjOyMAAgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6FGIs8TlruzZJRhz1lOLvl2FPvrUsFtz4yWTPjbT+VGsKuJFPvMuYybxq8pGyWVQN023uObDel47krlcQoH4MAggd39s50O0IaSbMgpJUWQVx+sxoXepe26SF5LQjWRDf7usrBVYTYoI9gDkVXMxLRmNnjQsKjg65fnQdhdLHyE/DR5DLlenSa0wQ3PXdv/C9LpDvkzJOLZs+/ZePd4YMI0+WuP2+6Xas4aNM+4JkNuHF5uMDcxgWID4TUy7Vdlzm3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMjvxngHnci+aYJZ6J+Lszh5zgo708vzO7fwaxC0wgd8anH3gFrbFnOg1hkmmoUEIgIwXh+ynuoZPOaoKNXNm1jOl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", - "hash": "13090090603196708539583054794074329820941412942119534377592583560995629520780" + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAHV05f+qac/ZBDmwqzaprv0J0hiho1m+s3yNkKQVSOkFyy3T9xnMBFjK62dF1KOp2k1Uvadd2KRyqwXiGN7JtQwgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09Ag9D9DKKoexOqr3N/Z3GGptvh3qvOPyxcWf475b+B/fTIwTQQC8ykkZ35HAVW3ZT6XDz0QFSmB0NJ8A+lkaTa0JF46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "10504586047480864396273137275551599454708712068910013426206550544367939284599" } }, "ecdsa": { - "digest": "334c2efc6a82e87319cadb7f3e6f9edb55f8f2eab71e0bcf2451f3d5536de5fe", + "digest": "baf90de64c1b6b5b5938a0b80a8fdb70bdbbc84a292dd7eccfdbce470c10b4c", "methods": { "sha3": { "rows": 14494, "digest": "949539824d56622702d9ac048e8111e9" }, "verifyEcdsa": { - "rows": 45178, - "digest": "0b6ce4cc658bcca79b1b1373569aa9b9" + "rows": 53386, + "digest": "5a234cff8ea48ce653cbd7efa2e1c241" } }, "verificationKey": { - "data": "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", - "hash": "18381574995221799963215366500837755447342811019610547066832598459350935665488" + "data": "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", + "hash": "24853426911779666983119365864639019692354940105641954611250992067926077919455" } }, "sha256": { From 0a60312534fd5e50efa71cbded0541ddcfae699d Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Thu, 21 Dec 2023 12:04:21 +0100 Subject: [PATCH 32/52] move example --- .../crypto/{sha256.ts => sha256/run.ts} | 19 ++----------------- src/examples/crypto/sha256/sha256.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 17 deletions(-) rename src/examples/crypto/{sha256.ts => sha256/run.ts} (63%) create mode 100644 src/examples/crypto/sha256/sha256.ts diff --git a/src/examples/crypto/sha256.ts b/src/examples/crypto/sha256/run.ts similarity index 63% rename from src/examples/crypto/sha256.ts rename to src/examples/crypto/sha256/run.ts index bc973499d9..4cb750cabc 100644 --- a/src/examples/crypto/sha256.ts +++ b/src/examples/crypto/sha256/run.ts @@ -1,21 +1,6 @@ -import { Bytes, Gadgets, ZkProgram } from 'o1js'; +import { Bytes12, SHA256Program } from './sha256.js'; -export { SHA256Program }; - -class Bytes12 extends Bytes(12) {} - -let SHA256Program = ZkProgram({ - name: 'sha256', - publicOutput: Bytes(32).provable, - methods: { - sha256: { - privateInputs: [Bytes12.provable], - method(xs: Bytes12) { - return Gadgets.SHA256.hash(xs); - }, - }, - }, -}); +console.log('sha256 rows:', SHA256Program.analyzeMethods().sha256.rows); console.time('compile'); await SHA256Program.compile(); diff --git a/src/examples/crypto/sha256/sha256.ts b/src/examples/crypto/sha256/sha256.ts new file mode 100644 index 0000000000..a85357bda6 --- /dev/null +++ b/src/examples/crypto/sha256/sha256.ts @@ -0,0 +1,18 @@ +import { Bytes, Gadgets, ZkProgram } from 'o1js'; + +export { SHA256Program, Bytes12 }; + +class Bytes12 extends Bytes(12) {} + +let SHA256Program = ZkProgram({ + name: 'sha256', + publicOutput: Bytes(32).provable, + methods: { + sha256: { + privateInputs: [Bytes12.provable], + method(xs: Bytes12) { + return Gadgets.SHA256.hash(xs); + }, + }, + }, +}); From f789d5a4c3b57f0c2378b8e7eadda1dcbc4fe516 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Thu, 21 Dec 2023 12:06:21 +0100 Subject: [PATCH 33/52] dump vks --- src/examples/crypto/sha256/run.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/examples/crypto/sha256/run.ts b/src/examples/crypto/sha256/run.ts index 4cb750cabc..b1d72e3fa6 100644 --- a/src/examples/crypto/sha256/run.ts +++ b/src/examples/crypto/sha256/run.ts @@ -1,7 +1,5 @@ import { Bytes12, SHA256Program } from './sha256.js'; -console.log('sha256 rows:', SHA256Program.analyzeMethods().sha256.rows); - console.time('compile'); await SHA256Program.compile(); console.timeEnd('compile'); From ca4c7c9f6403d7aa3657956ad47f200fb0d20793 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Thu, 21 Dec 2023 12:06:32 +0100 Subject: [PATCH 34/52] dump vks --- tests/vk-regression/vk-regression.json | 30 +++++++++++++------------- tests/vk-regression/vk-regression.ts | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 74c98a7c4d..ec705078d8 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -236,46 +236,46 @@ } }, "ecdsa-only": { - "digest": "2113edb508f10afee42dd48aec81ac7d06805d76225b0b97300501136486bb30", + "digest": "1e6bef24a2e02b573363f3512822d401d53ec7220c8d5837cd49691c19723028", "methods": { "verifySignedHash": { - "rows": 38888, - "digest": "f75dd9e49c88eb6097a7f3abbe543467" + "rows": 30680, + "digest": "5fe00efee7ecfb82b3ca69c8dd23d07f" } }, "verificationKey": { - "data": "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", - "hash": "10504586047480864396273137275551599454708712068910013426206550544367939284599" + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAEce2RV0gBkOlsJXf/A50Yo1Y+0y0ZMB/g8wkRIs0p8RIff5piGXJPfSak+7+oCoV/CQoa0RkkegIKmjsjOyMAAgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6FGIs8TlruzZJRhz1lOLvl2FPvrUsFtz4yWTPjbT+VGsKuJFPvMuYybxq8pGyWVQN023uObDel47krlcQoH4MAggd39s50O0IaSbMgpJUWQVx+sxoXepe26SF5LQjWRDf7usrBVYTYoI9gDkVXMxLRmNnjQsKjg65fnQdhdLHyE/DR5DLlenSa0wQ3PXdv/C9LpDvkzJOLZs+/ZePd4YMI0+WuP2+6Xas4aNM+4JkNuHF5uMDcxgWID4TUy7Vdlzm3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMjvxngHnci+aYJZ6J+Lszh5zgo708vzO7fwaxC0wgd8anH3gFrbFnOg1hkmmoUEIgIwXh+ynuoZPOaoKNXNm1jOl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "13090090603196708539583054794074329820941412942119534377592583560995629520780" } }, "ecdsa": { - "digest": "baf90de64c1b6b5b5938a0b80a8fdb70bdbbc84a292dd7eccfdbce470c10b4c", + "digest": "334c2efc6a82e87319cadb7f3e6f9edb55f8f2eab71e0bcf2451f3d5536de5fe", "methods": { "sha3": { "rows": 14494, "digest": "949539824d56622702d9ac048e8111e9" }, "verifyEcdsa": { - "rows": 53386, - "digest": "5a234cff8ea48ce653cbd7efa2e1c241" + "rows": 45178, + "digest": "0b6ce4cc658bcca79b1b1373569aa9b9" } }, "verificationKey": { - "data": "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", - "hash": "24853426911779666983119365864639019692354940105641954611250992067926077919455" + "data": "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", + "hash": "18381574995221799963215366500837755447342811019610547066832598459350935665488" } }, "sha256": { - "digest": "94f9e7bfe7224549a32ebf3e2f65e5d848e9feebd4c79a5d966a084c28b41fe", + "digest": "15bce9c541c0619f3b3587d7c06918e1423c5d1d4efe6ba6e0c789987e41b512", "methods": { "sha256": { - "rows": 5957, - "digest": "ea7ff27557e7c6270c200f2feb62a3a7" + "rows": 5314, + "digest": "bfad0408f0baab2e3db346a3e9997455" } }, "verificationKey": { - "data": "AACa0OPxg0UDEf5DBLZf/TtdB4TzIAMNQC467+/R1yGnL1tRXJnn0BDcLG0fGWdqFcWK1q2zKdAYfORKVOHgvKEwxtxZYv4CjM7SwlG09G52oNZmOgGCilD0ntmby4rzJTaoyCimx5ynQ/oYjslJgSM/1DTAkpW6IIdFjjkmjlOSHqRNRAMtNtl44D9lbhci4blHbDvzQnlYynwRh58jAzoDj3bCkPsByviAyFBoPhUx2M13h0/VPK1ND/69djzZgi9lQKaON74XvbnvJdSAYLQSIBbOE0yo7tS1vGTPqDqJGzc1f0+2QhZttE2UEUV4PPEGB6LUFEuQPXK8lXyXHVQTOU+omjpvKHLLs/dQZLTSvvZ3UFqxUvxjSEG9eTPo3Cyt4wXcPEi1b993ePSSxP6njj1SoPBA4BvLCCcvaxz5DegUu7nZDKkC8tuaoNbqYcayaf/8+oZ+dA+Ek1Xe08og1Hz9gB9cfPvgI9EiL2Na2cgiOF2klHHEAj3fORN8UQVzs0ioqLcQeq2tH2UMMtZS4JNcohHUah1T0jKmS3eZDqe+aMuf5qzKA5oKRF2ejRwmsxI161YlgXXpmHs+kCQGAMfWLQPS0/gbveVfQ3HLmsXF+Hvfl3J3IBdUECqVnbsExIc5Pacvo8DgeGxJObnGn5pp6P761+YUr/WuUqVyYSP4U20AHCGYSahuSFbBO6dDhRCPGRCJJgxktY+CSO+3B9mLtCe2X23FeFsUnaAFlzmsWeKR1tMyPupNsUXPFugubYZYd1EwxUDeBIOLeahcqX78yDDMOdsr0QHNG8XteyXcRXGFUSzTTKNEXdD+BOMY6FfMroKO4y3FffyrZef1PRsu/kVwaZb4UthvhBwet8DBcMa26kISlsI1ItcvsdwST5x1+iLCM4hBcFB0ly3N18uR7+MLHU0AdV13INFo8indyz1XEZCHlm+tKF3LX8Z7G0v68uRmjKRgy/S9NjKPA+M3rbj4pDU+jS4EKN++nKYxXjwdKJXu4XvbUU9ITKM7N7nZr0RbamEC3Mi3j7dV69psfJY41NUnqTvf3N6dGxdZ7zyPZ5YRyP7VMZMroxw+31ZYUyTYYQ4Kjb6mXDTbP00OzQ8/vAiYAdVmcHuS+M1etSdnWerxU4E3vC2odvcjNq2yh/VyODIwPt/UPYT1soPpv6M3XSyvpE1kVb0TmD91v6mXvfq23wSDn7ndgLx52m+CGnEGzA/OwwVQnZaFNq+sZjKjOa8ALFNcjyS8IuYEz4XYMiSy/wCl2n2AHVIc6djnQZySvECly6HHJSpWpWv8zvNfLUiWozg4ActV4QzQsd2nagCedaw1z82uWcKLraPboPGke+1dGOqg8OFiBJUBoW/bROBgw2H8WnPIhcofMwGOCrXpMGvA4LKnn3okzY3hTNfex1t7zCpxQC2YRBN0Ze8qOELHTYvOlSwG1AQqZDhC//VDGFvvQ1ZgzsmpnbyNGTGMaCKRMGI0evgxD6Ziitu5k9ogVW6dGSR9cBM0NryOxQl6JcwUXd0twqsLI0pbH7cjlGzWsylGuufx/m77GPRqnysk6r+ibZ4fDBFDumJFxxbS4AqzXLR+nNg42hJbvuxsyZnCRpkB2N9xH3VX8/Il8BX40HdEE/08l3JmL3jn8Bznqwj8z3TqBFMdRGOOq2/5DjwcjaNh9pYRn6bDl/1qVPrJkUExPECrQRymHQ3JERZjm1G+a3bhAJFeb+Ak4D8PlK2RgPcMCS7bBXWuPRh0z4FKGnhZnecNmuWMSvUFsSZcoaFKfyNanX8qMsu+KWtWEbDwwQ49NrfCmg45/WAOQxX8LKMYgUrDpSVdE/bM+JqYpq0AmOHAhoIdlOC7jVMIPI6LEAVJC1PrFQBS3HbH+u5IMQ684sJehPFtd1pjpfboDrnbgfhnjFf9HqS5bG1sR1Dh2mXGXpQ+ni+O3FvEYCEZY+UU9d9XCGwL2OZIhtMi6M6qcnrn11w2MyaZ8U6aZxVTMFvKQ2JLtwVGVnMDuSkNC+iN711acwssgbTpsgHLdVbtFR1oYcbpJHe6a9SFquG+q9qfzT15IzKpBzxn6BVXfhhFGpJRAbU0bXbjOpeceg7vaV7RykTzBoIzAe5aUVAdKNM6fzGlPw16xx7QeOW+LFlOm6HJyxYAZfbpB/BLza4ZhoqmVx+ALUXHFIztgGQK9rzm+jBwiuwuLqdD2W00cbTZcbgKTo48XD6NJ+8T4J9B3rPzht3qbgpN//TyYkfrzAercAa/HCvFeBNXl1slCj8cF/EO6iX/NnIxBkuqmXfQnGUfcFK0LZPsvd7RInaLEYTeA4ZDfChiuw+5nTmrJFOywwOYdIA+NiMfCh24dPYAAwEGb9KLEP9u7/Rp5uPi0S3tuTw67yg=", - "hash": "5242875480670941030919675131962218019722619843591732956374487945418325506772" + "data": "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", + "hash": "801809578510365639167868048464129098535493687043603299559390072759232736708" } } } \ No newline at end of file diff --git a/tests/vk-regression/vk-regression.ts b/tests/vk-regression/vk-regression.ts index bc6a644934..cccf97e4bc 100644 --- a/tests/vk-regression/vk-regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -7,7 +7,7 @@ import { ecdsa, keccakAndEcdsa, } from '../../src/examples/crypto/ecdsa/ecdsa.js'; -import { SHA256Program } from '../../src/examples/crypto/sha256.js'; +import { SHA256Program } from '../../src/examples/crypto/sha256/sha256.js'; import { GroupCS, BitwiseCS, HashCS } from './plain-constraint-system.js'; // toggle this for quick iteration when debugging vk regressions From a816814bd2277d29af6dc7a59c6a7361c4540d6c Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Thu, 21 Dec 2023 12:10:31 +0100 Subject: [PATCH 35/52] improve doc comment --- src/lib/gadgets/gadgets.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 18954d4080..fedbc8b89b 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -801,15 +801,14 @@ const Gadgets = { /** * Implementation of the [SHA256 hash function.](https://en.wikipedia.org/wiki/SHA-2) Hash function with 256bit output. * - * Applies the SHA2-256 hash function to a list of big-endian byte-sized elements. + * Applies the SHA2-256 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. - * Both input and output bytes are big-endian. * - * @param data - Big-endian {@link Bytes} representing the message to hash. + * @param data - {@link Bytes} representing the message to hash. * * ```ts * let preimage = Bytes.fromString("hello world"); From 7747e75fd102cd3dcd17be0e2bc1b5e6cd8a82cd Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Thu, 21 Dec 2023 12:46:51 +0100 Subject: [PATCH 36/52] address part of feedback --- src/lib/gadgets/sha256.ts | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index f877e3fc8e..7878d01075 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -1,7 +1,10 @@ // https://csrc.nist.gov/pubs/fips/180-4/upd1/final +import { mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../core.js'; import { UInt32, UInt8 } from '../int.js'; -import { Bytes, FlexibleBytes } from '../provable-types/bytes.js'; +import { FlexibleBytes } from '../provable-types/bytes.js'; +import { Bytes } from '../provable-types/provable-types.js'; +import { chunk } from '../util/arrays.js'; import { TupleN } from '../util/types.js'; import { bitSlice, exists } from './common.js'; import { Gadgets } from './gadgets.js'; @@ -9,20 +12,17 @@ import { Gadgets } from './gadgets.js'; export { SHA256 }; function padding(data: FlexibleBytes): UInt32[][] { - // pad the data with zeros to reach the desired length - // this is because we need to inherit to a static sized Bytes array. unrelated to sha256 - let zeroPadded = Bytes.from(data); + // 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); // now pad the data to reach the format expected by sha256 // pad 1 bit, followed by k zero bits where k is the smallest non-negative solution to // l + 1 + k = 448 mod 512 // then append a 64bit block containing the length of the original message in bits - let l = zeroPadded.length * 8; // length in bits - let k = (448 - (l + 1)) % 512; - - // pad message exceeds 512bit and needs a new block - if (k < 0) k += 512; + let l = message.length * 8; // length in bits + let k = Number(mod(448n - (BigInt(l) + 1n), 512n)); let paddingBits = ( '1' + // append 1 bit @@ -35,7 +35,7 @@ function padding(data: FlexibleBytes): UInt32[][] { let padding = paddingBits.map((x) => UInt8.from(BigInt('0b' + x))); // concatenate the padding with the original padded data - let paddedMessage = zeroPadded.bytes.concat(padding); + let paddedMessage = message.bytes.concat(padding); // split the message into 32bit chunks let chunks: UInt32[] = []; @@ -47,11 +47,7 @@ function padding(data: FlexibleBytes): UInt32[][] { // split message into 16 element sized message blocks // SHA256 expects n-blocks of 512bit each, 16*32bit = 512bit - let messageBlocks: UInt32[][] = []; - for (let i = 0; i < chunks.length; i += 16) { - messageBlocks.push(chunks.slice(i, i + 16)); - } - return messageBlocks; + return chunk(chunks, 16); } // concatenate bytes into a 32bit word using bit shifting From 198ecb830cca33c3b4048d1a96e29978e67be3ea Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Thu, 21 Dec 2023 12:56:27 +0100 Subject: [PATCH 37/52] move constants --- src/lib/gadgets/sha256.ts | 52 +++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 7878d01075..763df489ac 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -8,9 +8,32 @@ import { chunk } from '../util/arrays.js'; import { TupleN } from '../util/types.js'; import { bitSlice, exists } from './common.js'; import { Gadgets } from './gadgets.js'; +import { rangeCheck16 } from './range-check.js'; export { SHA256 }; +const SHA256Constants = { + // constants §4.2.2 + K: [ + 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, + ], + // initial hash values §5.3.3 + H: [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19, + ], +}; + function padding(data: FlexibleBytes): 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 @@ -80,32 +103,13 @@ function decomposeToBytes(a: UInt32) { const SHA256 = { hash(data: FlexibleBytes) { - // initial hash values §5.3.3 - const H = [ - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, - 0x1f83d9ab, 0x5be0cd19, - ].map((h) => UInt32.from(h)); - - // I dont like to have this in here but UInt32 isn't initialized if used at top level - // constants §4.2.2 - const K = [ - 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, - ].map((k) => UInt32.from(k)); - // preprocessing §6.2 // padding the message $5.1.1 into blocks that are a multiple of 512 let messageBlocks = padding(data); + const H = SHA256Constants.H.map((x) => UInt32.from(x)); + const K = SHA256Constants.K.map((x) => UInt32.from(x)); + const N = messageBlocks.length; for (let i = 0; i < N; i++) { @@ -296,7 +300,3 @@ function sigma(u: UInt32, bits: TupleN, firstShifted = false) { return UInt32.from(xRotR0).xor(new UInt32(xRotR1)).xor(new UInt32(xRotR2)); } - -function rangeCheck16(x: Field) { - x.rangeCheckHelper(16).assertEquals(x); -} From 98bed632f587d349845c3fa81c88ab6bda54a1b2 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Thu, 21 Dec 2023 12:56:33 +0100 Subject: [PATCH 38/52] improve tests --- src/lib/gadgets/sha256.unit-test.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/sha256.unit-test.ts b/src/lib/gadgets/sha256.unit-test.ts index c2ade3f822..f17c7ad51e 100644 --- a/src/lib/gadgets/sha256.unit-test.ts +++ b/src/lib/gadgets/sha256.unit-test.ts @@ -3,15 +3,15 @@ import { Bytes } from '../provable-types/provable-types.js'; import { Gadgets } from './gadgets.js'; import { sha256 as nobleSha256 } from '@noble/hashes/sha256'; import { bytes } from './test-utils.js'; -import { equivalent, equivalentAsync } from '../testing/equivalent.js'; +import { equivalentAsync, equivalentProvable } from '../testing/equivalent.js'; import { Random, sample } from '../testing/random.js'; import { expect } from 'expect'; -sample(Random.nat(400), 10).forEach((preimageLength) => { +sample(Random.nat(400), 5).forEach((preimageLength) => { let inputBytes = bytes(preimageLength); let outputBytes = bytes(256 / 8); - equivalent({ from: [inputBytes], to: outputBytes, verbose: true })( + equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( (x) => nobleSha256(x), (x) => Gadgets.SHA256.hash(x), `sha256 preimage length ${preimageLength}` @@ -48,8 +48,7 @@ await equivalentAsync( }); for (let { preimage, hash } of testVectors()) { - class BytesN extends Bytes(preimage.length) {} - let actual = Gadgets.SHA256.hash(BytesN.fromString(preimage)); + let actual = Gadgets.SHA256.hash(Bytes.fromString(preimage)); expect(actual.toHex()).toEqual(hash); } From e98fae941ce4d3c2ddfbaff18e049303cfdfc288 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Thu, 21 Dec 2023 13:52:40 +0100 Subject: [PATCH 39/52] move keccak helper functions --- src/lib/gadgets/common.ts | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 9da4490723..190124d802 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -2,6 +2,9 @@ import { Field, FieldConst, FieldVar, VarField } from '../field.js'; import { Tuple, TupleN } from '../util/types.js'; import { Snarky } from '../../snarky.js'; import { MlArray } from '../ml/base.js'; +import { UInt8 } from '../int.js'; +import { Provable } from '../provable.js'; +import { chunk } from '../util/arrays.js'; const MAX_BITS = 64 as const; @@ -15,6 +18,10 @@ export { assert, bitSlice, divideWithRemainder, + bytesToWord, + bytesToWords, + wordsToBytes, + wordToBytes, }; function existsOne(compute: () => bigint) { @@ -77,3 +84,46 @@ function divideWithRemainder(numerator: bigint, denominator: bigint) { const remainder = numerator - denominator * quotient; return { quotient, remainder }; } + +/** + * Convert an array of UInt8 to a Field element. Expects little endian representation. + */ +function bytesToWord(wordBytes: UInt8[]): Field { + return wordBytes.reduce((acc, byte, idx) => { + const shift = 1n << BigInt(8 * idx); + return acc.add(byte.value.mul(shift)); + }, Field.from(0)); +} + +/** + * Convert a Field element to an array of UInt8. Expects little endian representation. + * @param bytesPerWord number of bytes per word + */ +function wordToBytes(word: Field, bytesPerWord = 8): UInt8[] { + let bytes = Provable.witness(Provable.Array(UInt8, bytesPerWord), () => { + let w = word.toBigInt(); + return Array.from({ length: bytesPerWord }, (_, k) => + UInt8.from((w >> BigInt(8 * k)) & 0xffn) + ); + }); + + // check decomposition + bytesToWord(bytes).assertEquals(word); + + return bytes; +} + +/** + * Convert an array of Field elements to an array of UInt8. Expects little endian representation. + * @param bytesPerWord number of bytes per word + */ +function wordsToBytes(words: Field[], bytesPerWord = 8): UInt8[] { + return words.flatMap((w) => wordToBytes(w, bytesPerWord)); +} +/** + * Convert an array of UInt8 to an array of Field elements. Expects little endian representation. + * @param bytesPerWord number of bytes per word + */ +function bytesToWords(bytes: UInt8[], bytesPerWord = 8): Field[] { + return chunk(bytes, bytesPerWord).map(bytesToWord); +} From a1aa7c8ba559a80566bea5a929c6c233238cb4d8 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Thu, 21 Dec 2023 13:52:46 +0100 Subject: [PATCH 40/52] move helpers --- src/lib/keccak.ts | 36 +----------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index ccc8c45548..e4e2e4aec1 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -1,11 +1,10 @@ import { Field } from './field.js'; import { Gadgets } from './gadgets/gadgets.js'; import { assert } from './errors.js'; -import { Provable } from './provable.js'; -import { chunk } from './util/arrays.js'; import { FlexibleBytes } from './provable-types/bytes.js'; import { UInt8 } from './int.js'; import { Bytes } from './provable-types/provable-types.js'; +import { bytesToWords, wordsToBytes } from './gadgets/common.js'; export { Keccak }; @@ -508,39 +507,6 @@ const BytesOfBitlength = { 512: Bytes64, }; -// AUXILARY FUNCTIONS - -// Auxiliary functions to check the composition of 8 byte values (LE) into a 64-bit word and create constraints for it - -function bytesToWord(wordBytes: UInt8[]): Field { - return wordBytes.reduce((acc, byte, idx) => { - const shift = 1n << BigInt(8 * idx); - return acc.add(byte.value.mul(shift)); - }, Field.from(0)); -} - -function wordToBytes(word: Field): UInt8[] { - let bytes = Provable.witness(Provable.Array(UInt8, BYTES_PER_WORD), () => { - let w = word.toBigInt(); - return Array.from({ length: BYTES_PER_WORD }, (_, k) => - UInt8.from((w >> BigInt(8 * k)) & 0xffn) - ); - }); - - // check decomposition - bytesToWord(bytes).assertEquals(word); - - return bytes; -} - -function bytesToWords(bytes: UInt8[]): Field[] { - return chunk(bytes, BYTES_PER_WORD).map(bytesToWord); -} - -function wordsToBytes(words: Field[]): UInt8[] { - return words.flatMap(wordToBytes); -} - // xor which avoids doing anything on 0 inputs // (but doesn't range-check the other input in that case) function xor(x: Field, y: Field): Field { From 015db6b1d1ebd2b87f51b6d9442e2316826d3d29 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Thu, 21 Dec 2023 13:52:56 +0100 Subject: [PATCH 41/52] simply decomposition of variables --- src/lib/gadgets/sha256.ts | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 763df489ac..60e1cfdb8d 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -4,9 +4,10 @@ import { Field } from '../core.js'; import { UInt32, UInt8 } from '../int.js'; import { FlexibleBytes } from '../provable-types/bytes.js'; import { Bytes } from '../provable-types/provable-types.js'; +import { Provable } from '../provable.js'; import { chunk } from '../util/arrays.js'; import { TupleN } from '../util/types.js'; -import { bitSlice, exists } from './common.js'; +import { bitSlice, bytesToWord, exists, wordToBytes } from './common.js'; import { Gadgets } from './gadgets.js'; import { rangeCheck16 } from './range-check.js'; @@ -65,7 +66,10 @@ function padding(data: FlexibleBytes): UInt32[][] { for (let i = 0; i < paddedMessage.length; i += 4) { // chunk 4 bytes into one UInt32, as expected by SHA256 - chunks.push(concatToUInt32(paddedMessage.slice(i, i + 4))); + // bytesToWord expects little endian, so we reverse the bytes + chunks.push( + UInt32.from(bytesToWord(paddedMessage.slice(i, i + 4).reverse())) + ); } // split message into 16 element sized message blocks @@ -73,18 +77,6 @@ function padding(data: FlexibleBytes): UInt32[][] { return chunk(chunks, 16); } -// concatenate bytes into a 32bit word using bit shifting -function concatToUInt32(xs: UInt8[]) { - let target = new Field(0); - xs.forEach((x) => { - // for each element we shift the target by 8 bits and add the element - target = Gadgets.leftShift32(target, 8).add(x.value); - }); - // making sure its actually 32bit - Gadgets.rangeCheck32(target); - return new UInt32(target); -} - // decompose a 32bit word into 4 bytes function decomposeToBytes(a: UInt32) { let field = a.value; @@ -177,7 +169,8 @@ const SHA256 = { } // 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) => decomposeToBytes(x)).flat()); + // wordToBytes expects little endian, so we reverse the bytes + return Bytes.from(H.map((x) => wordToBytes(x.value, 4).reverse()).flat()); }, }; From a477a0caa918ff988daadf86aa838e525e22553f Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 2 Jan 2024 12:25:43 +0100 Subject: [PATCH 42/52] fix dependency hell --- src/lib/gadgets/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 190124d802..8198884be2 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -2,7 +2,7 @@ import { Field, FieldConst, FieldVar, VarField } from '../field.js'; import { Tuple, TupleN } from '../util/types.js'; import { Snarky } from '../../snarky.js'; import { MlArray } from '../ml/base.js'; -import { UInt8 } from '../int.js'; +import { UInt8 } from '../../index.js'; import { Provable } from '../provable.js'; import { chunk } from '../util/arrays.js'; From e318e3ea6b94e28d7370f6b3d046a5e91a558029 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 2 Jan 2024 13:08:13 +0100 Subject: [PATCH 43/52] add assert to export --- CHANGELOG.md | 1 + src/index.ts | 2 ++ src/lib/gadgets/common.ts | 12 +++++++++--- src/lib/gadgets/sha256.ts | 1 - 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b30d0e852..20ca16e89e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - bitwise AND via `{UInt32, UInt64}.and()` - Example for using actions to store a map data structure https://github.com/o1-labs/o1js/pull/1300 - `Provable.constraintSystem()` and `{ZkProgram,SmartContract}.analyzeMethods()` return a `summary()` method to return a summary of the constraints used by a method https://github.com/o1-labs/o1js/pull/1007 +- `assert()` asserts that a given statement is true https://github.com/o1-labs/o1js/pull/1285 ### Fixed diff --git a/src/index.ts b/src/index.ts index 243fe7dbfa..47d35c815a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,8 @@ export { Poseidon, TokenSymbol } from './lib/hash.js'; export { Keccak } from './lib/keccak.js'; export { Hash } from './lib/hashes-combined.js'; +export { assert } from './lib/gadgets/common.js'; + export * from './lib/signature.js'; export type { ProvableExtended, diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 8198884be2..27c1b49ff0 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -2,7 +2,7 @@ import { Field, FieldConst, FieldVar, VarField } from '../field.js'; import { Tuple, TupleN } from '../util/types.js'; import { Snarky } from '../../snarky.js'; import { MlArray } from '../ml/base.js'; -import { UInt8 } from '../../index.js'; +import { Bool, UInt8 } from '../../index.js'; import { Provable } from '../provable.js'; import { chunk } from '../util/arrays.js'; @@ -69,8 +69,14 @@ function toVars>( return Tuple.map(fields, toVar); } -function assert(stmt: boolean, message?: string): asserts stmt { - if (!stmt) { +/** + * Assert that a statement is true. If the statement is false, throws an error with the given message. + * Can be used in provable code. + */ +function assert(stmt: boolean | Bool, message?: string): asserts stmt { + if (stmt instanceof Bool) { + stmt.assertTrue(message ?? 'Assertion failed'); + } else if (!stmt) { throw Error(message ?? 'Assertion failed'); } } diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 60e1cfdb8d..644cfb8f45 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -4,7 +4,6 @@ import { Field } from '../core.js'; import { UInt32, UInt8 } from '../int.js'; import { FlexibleBytes } from '../provable-types/bytes.js'; import { Bytes } from '../provable-types/provable-types.js'; -import { Provable } from '../provable.js'; import { chunk } from '../util/arrays.js'; import { TupleN } from '../util/types.js'; import { bitSlice, bytesToWord, exists, wordToBytes } from './common.js'; From 93070076aed0486a122a88b9fe6c7f2fdb5ee936 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Jan 2024 12:37:13 +0100 Subject: [PATCH 44/52] fix import cycles by moving high-level dependencies out of gadgets/common, and gadgets out of int.ts --- src/lib/gadgets/bit-slices.ts | 54 +++++++++++++++++++ src/lib/gadgets/common.ts | 51 +----------------- src/lib/gadgets/sha256.ts | 3 +- src/lib/int.ts | 93 +++++++++++++++++---------------- src/lib/keccak.ts | 2 +- src/lib/provable-types/bytes.ts | 8 ++- 6 files changed, 112 insertions(+), 99 deletions(-) create mode 100644 src/lib/gadgets/bit-slices.ts diff --git a/src/lib/gadgets/bit-slices.ts b/src/lib/gadgets/bit-slices.ts new file mode 100644 index 0000000000..b6b3d7acfe --- /dev/null +++ b/src/lib/gadgets/bit-slices.ts @@ -0,0 +1,54 @@ +/** + * Gadgets for converting between field elements and bit slices of various lengths + */ +import { Field } from '../field.js'; +import { UInt8 } from '../int.js'; +import { Provable } from '../provable.js'; +import { chunk } from '../util/arrays.js'; + +export { bytesToWord, wordToBytes, wordsToBytes, bytesToWords }; + +// conversion between bytes and multi-byte words + +/** + * Convert an array of UInt8 to a Field element. Expects little endian representation. + */ +function bytesToWord(wordBytes: UInt8[]): Field { + return wordBytes.reduce((acc, byte, idx) => { + const shift = 1n << BigInt(8 * idx); + return acc.add(byte.value.mul(shift)); + }, Field.from(0)); +} + +/** + * Convert a Field element to an array of UInt8. Expects little endian representation. + * @param bytesPerWord number of bytes per word + */ +function wordToBytes(word: Field, bytesPerWord = 8): UInt8[] { + let bytes = Provable.witness(Provable.Array(UInt8, bytesPerWord), () => { + let w = word.toBigInt(); + return Array.from({ length: bytesPerWord }, (_, k) => + UInt8.from((w >> BigInt(8 * k)) & 0xffn) + ); + }); + + // check decomposition + bytesToWord(bytes).assertEquals(word); + + return bytes; +} + +/** + * Convert an array of Field elements to an array of UInt8. Expects little endian representation. + * @param bytesPerWord number of bytes per word + */ +function wordsToBytes(words: Field[], bytesPerWord = 8): UInt8[] { + return words.flatMap((w) => wordToBytes(w, bytesPerWord)); +} +/** + * Convert an array of UInt8 to an array of Field elements. Expects little endian representation. + * @param bytesPerWord number of bytes per word + */ +function bytesToWords(bytes: UInt8[], bytesPerWord = 8): Field[] { + return chunk(bytes, bytesPerWord).map(bytesToWord); +} diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 27c1b49ff0..3a1a36b5a5 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -2,9 +2,7 @@ import { Field, FieldConst, FieldVar, VarField } from '../field.js'; import { Tuple, TupleN } from '../util/types.js'; import { Snarky } from '../../snarky.js'; import { MlArray } from '../ml/base.js'; -import { Bool, UInt8 } from '../../index.js'; -import { Provable } from '../provable.js'; -import { chunk } from '../util/arrays.js'; +import { Bool } from '../bool.js'; const MAX_BITS = 64 as const; @@ -18,10 +16,6 @@ export { assert, bitSlice, divideWithRemainder, - bytesToWord, - bytesToWords, - wordsToBytes, - wordToBytes, }; function existsOne(compute: () => bigint) { @@ -90,46 +84,3 @@ function divideWithRemainder(numerator: bigint, denominator: bigint) { const remainder = numerator - denominator * quotient; return { quotient, remainder }; } - -/** - * Convert an array of UInt8 to a Field element. Expects little endian representation. - */ -function bytesToWord(wordBytes: UInt8[]): Field { - return wordBytes.reduce((acc, byte, idx) => { - const shift = 1n << BigInt(8 * idx); - return acc.add(byte.value.mul(shift)); - }, Field.from(0)); -} - -/** - * Convert a Field element to an array of UInt8. Expects little endian representation. - * @param bytesPerWord number of bytes per word - */ -function wordToBytes(word: Field, bytesPerWord = 8): UInt8[] { - let bytes = Provable.witness(Provable.Array(UInt8, bytesPerWord), () => { - let w = word.toBigInt(); - return Array.from({ length: bytesPerWord }, (_, k) => - UInt8.from((w >> BigInt(8 * k)) & 0xffn) - ); - }); - - // check decomposition - bytesToWord(bytes).assertEquals(word); - - return bytes; -} - -/** - * Convert an array of Field elements to an array of UInt8. Expects little endian representation. - * @param bytesPerWord number of bytes per word - */ -function wordsToBytes(words: Field[], bytesPerWord = 8): UInt8[] { - return words.flatMap((w) => wordToBytes(w, bytesPerWord)); -} -/** - * Convert an array of UInt8 to an array of Field elements. Expects little endian representation. - * @param bytesPerWord number of bytes per word - */ -function bytesToWords(bytes: UInt8[], bytesPerWord = 8): Field[] { - return chunk(bytes, bytesPerWord).map(bytesToWord); -} diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 644cfb8f45..8cac25b58c 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -6,7 +6,8 @@ import { FlexibleBytes } from '../provable-types/bytes.js'; import { Bytes } from '../provable-types/provable-types.js'; import { chunk } from '../util/arrays.js'; import { TupleN } from '../util/types.js'; -import { bitSlice, bytesToWord, exists, wordToBytes } from './common.js'; +import { bytesToWord, wordToBytes } from './bit-slices.js'; +import { bitSlice, exists } from './common.js'; import { Gadgets } from './gadgets.js'; import { rangeCheck16 } from './range-check.js'; diff --git a/src/lib/int.ts b/src/lib/int.ts index a98ff8cf4b..73ff036ed8 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -3,9 +3,12 @@ import { AnyConstructor, CircuitValue, Struct, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; -import { Gadgets } from './gadgets/gadgets.js'; - +import * as RangeCheck from './gadgets/range-check.js'; +import * as Bitwise from './gadgets/bitwise.js'; +import { addMod32 } from './gadgets/arithmetic.js'; +import type { Gadgets } from './gadgets/gadgets.js'; import { FieldVar, withMessage } from './field.js'; + // external API export { UInt8, UInt32, UInt64, Int64, Sign }; @@ -74,7 +77,7 @@ class UInt64 extends CircuitValue { } static check(x: UInt64) { - Gadgets.rangeCheckN(UInt64.NUM_BITS, x.value); + RangeCheck.rangeCheckN(UInt64.NUM_BITS, x.value); } static toInput(x: UInt64): HashInput { @@ -149,11 +152,11 @@ class UInt64 extends CircuitValue { () => new Field(x.toBigInt() / y_.toBigInt()) ); - Gadgets.rangeCheckN(UInt64.NUM_BITS, q); + RangeCheck.rangeCheckN(UInt64.NUM_BITS, q); // TODO: Could be a bit more efficient let r = x.sub(q.mul(y_)).seal(); - Gadgets.rangeCheckN(UInt64.NUM_BITS, r); + RangeCheck.rangeCheckN(UInt64.NUM_BITS, r); let r_ = new UInt64(r); let q_ = new UInt64(q); @@ -189,7 +192,7 @@ class UInt64 extends CircuitValue { */ mul(y: UInt64 | number) { let z = this.value.mul(UInt64.from(y).value); - Gadgets.rangeCheckN(UInt64.NUM_BITS, z); + RangeCheck.rangeCheckN(UInt64.NUM_BITS, z); return new UInt64(z); } @@ -198,7 +201,7 @@ class UInt64 extends CircuitValue { */ add(y: UInt64 | number) { let z = this.value.add(UInt64.from(y).value); - Gadgets.rangeCheckN(UInt64.NUM_BITS, z); + RangeCheck.rangeCheckN(UInt64.NUM_BITS, z); return new UInt64(z); } @@ -207,7 +210,7 @@ class UInt64 extends CircuitValue { */ sub(y: UInt64 | number) { let z = this.value.sub(UInt64.from(y).value); - Gadgets.rangeCheckN(UInt64.NUM_BITS, z); + RangeCheck.rangeCheckN(UInt64.NUM_BITS, z); return new UInt64(z); } @@ -231,7 +234,7 @@ class UInt64 extends CircuitValue { * ``` */ xor(x: UInt64) { - return new UInt64(Gadgets.xor(this.value, x.value, UInt64.NUM_BITS)); + return new UInt64(Bitwise.xor(this.value, x.value, UInt64.NUM_BITS)); } /** @@ -264,7 +267,7 @@ class UInt64 extends CircuitValue { * */ not() { - return new UInt64(Gadgets.not(this.value, UInt64.NUM_BITS, false)); + return new UInt64(Bitwise.not(this.value, UInt64.NUM_BITS, false)); } /** @@ -296,7 +299,7 @@ class UInt64 extends CircuitValue { * ``` */ rotate(bits: number, direction: 'left' | 'right' = 'left') { - return new UInt64(Gadgets.rotate64(this.value, bits, direction)); + return new UInt64(Bitwise.rotate64(this.value, bits, direction)); } /** @@ -317,7 +320,7 @@ class UInt64 extends CircuitValue { * ``` */ leftShift(bits: number) { - return new UInt64(Gadgets.leftShift64(this.value, bits)); + return new UInt64(Bitwise.leftShift64(this.value, bits)); } /** @@ -338,7 +341,7 @@ class UInt64 extends CircuitValue { * ``` */ rightShift(bits: number) { - return new UInt64(Gadgets.leftShift64(this.value, bits)); + return new UInt64(Bitwise.leftShift64(this.value, bits)); } /** @@ -367,7 +370,7 @@ class UInt64 extends CircuitValue { * ``` */ and(x: UInt64) { - return new UInt64(Gadgets.and(this.value, x.value, UInt64.NUM_BITS)); + return new UInt64(Bitwise.and(this.value, x.value, UInt64.NUM_BITS)); } /** @@ -382,9 +385,9 @@ class UInt64 extends CircuitValue { let xMinusY = this.value.sub(y.value).seal(); let yMinusX = xMinusY.neg(); - let xMinusYFits = Gadgets.isInRangeN(UInt64.NUM_BITS, xMinusY); + let xMinusYFits = RangeCheck.isInRangeN(UInt64.NUM_BITS, xMinusY); - let yMinusXFits = Gadgets.isInRangeN(UInt64.NUM_BITS, yMinusX); + let yMinusXFits = RangeCheck.isInRangeN(UInt64.NUM_BITS, yMinusX); xMinusYFits.or(yMinusXFits).assertEquals(true); // x <= y if y - x fits in 64 bits @@ -402,9 +405,9 @@ class UInt64 extends CircuitValue { let xMinusY = this.value.sub(y.value).seal(); let yMinusX = xMinusY.neg(); - let xMinusYFits = Gadgets.isInRangeN(UInt64.NUM_BITS, xMinusY); + let xMinusYFits = RangeCheck.isInRangeN(UInt64.NUM_BITS, xMinusY); - let yMinusXFits = Gadgets.isInRangeN(UInt64.NUM_BITS, yMinusX); + let yMinusXFits = RangeCheck.isInRangeN(UInt64.NUM_BITS, yMinusX); xMinusYFits.or(yMinusXFits).assertEquals(true); // x <= y if y - x fits in 64 bits @@ -435,7 +438,7 @@ class UInt64 extends CircuitValue { return; } let yMinusX = y.value.sub(this.value).seal(); - Gadgets.rangeCheckN(UInt64.NUM_BITS, yMinusX, message); + RangeCheck.rangeCheckN(UInt64.NUM_BITS, yMinusX, message); } /** @@ -583,7 +586,7 @@ class UInt32 extends CircuitValue { } static check(x: UInt32) { - Gadgets.rangeCheck32(x.value); + RangeCheck.rangeCheck32(x.value); } static toInput(x: UInt32): HashInput { return { packed: [[x.value, 32]] }; @@ -633,7 +636,7 @@ class UInt32 extends CircuitValue { * Addition modulo 2^32. Check {@link Gadgets.addMod32} for a detailed description. */ addMod32(y: UInt32) { - return new UInt32(Gadgets.addMod32(this.value, y.value)); + return new UInt32(addMod32(this.value, y.value)); } /** @@ -663,11 +666,11 @@ class UInt32 extends CircuitValue { () => new Field(x.toBigInt() / y_.toBigInt()) ); - Gadgets.rangeCheck32(q); + RangeCheck.rangeCheck32(q); // TODO: Could be a bit more efficient let r = x.sub(q.mul(y_)).seal(); - Gadgets.rangeCheck32(r); + RangeCheck.rangeCheck32(r); let r_ = new UInt32(r); let q_ = new UInt32(q); @@ -700,7 +703,7 @@ class UInt32 extends CircuitValue { */ mul(y: UInt32 | number) { let z = this.value.mul(UInt32.from(y).value); - Gadgets.rangeCheck32(z); + RangeCheck.rangeCheck32(z); return new UInt32(z); } /** @@ -708,7 +711,7 @@ class UInt32 extends CircuitValue { */ add(y: UInt32 | number) { let z = this.value.add(UInt32.from(y).value); - Gadgets.rangeCheck32(z); + RangeCheck.rangeCheck32(z); return new UInt32(z); } /** @@ -716,7 +719,7 @@ class UInt32 extends CircuitValue { */ sub(y: UInt32 | number) { let z = this.value.sub(UInt32.from(y).value); - Gadgets.rangeCheck32(z); + RangeCheck.rangeCheck32(z); return new UInt32(z); } @@ -740,7 +743,7 @@ class UInt32 extends CircuitValue { * ``` */ xor(x: UInt32) { - return new UInt32(Gadgets.xor(this.value, x.value, UInt32.NUM_BITS)); + return new UInt32(Bitwise.xor(this.value, x.value, UInt32.NUM_BITS)); } /** @@ -771,7 +774,7 @@ class UInt32 extends CircuitValue { * @param a - The value to apply NOT to. */ not() { - return new UInt32(Gadgets.not(this.value, UInt32.NUM_BITS, false)); + return new UInt32(Bitwise.not(this.value, UInt32.NUM_BITS, false)); } /** @@ -803,7 +806,7 @@ class UInt32 extends CircuitValue { * ``` */ rotate(bits: number, direction: 'left' | 'right' = 'left') { - return new UInt32(Gadgets.rotate32(this.value, bits, direction)); + return new UInt32(Bitwise.rotate32(this.value, bits, direction)); } /** @@ -826,7 +829,7 @@ class UInt32 extends CircuitValue { * ``` */ leftShift(bits: number) { - return new UInt32(Gadgets.leftShift32(this.value, bits)); + return new UInt32(Bitwise.leftShift32(this.value, bits)); } /** @@ -849,7 +852,7 @@ class UInt32 extends CircuitValue { * ``` */ rightShift(bits: number) { - return new UInt32(Gadgets.rightShift64(this.value, bits)); + return new UInt32(Bitwise.rightShift64(this.value, bits)); } /** @@ -878,7 +881,7 @@ class UInt32 extends CircuitValue { * ``` */ and(x: UInt32) { - return new UInt32(Gadgets.and(this.value, x.value, UInt32.NUM_BITS)); + return new UInt32(Bitwise.and(this.value, x.value, UInt32.NUM_BITS)); } /** @@ -892,8 +895,8 @@ class UInt32 extends CircuitValue { } else { let xMinusY = this.value.sub(y.value).seal(); let yMinusX = xMinusY.neg(); - let xMinusYFits = Gadgets.isInRangeN(UInt32.NUM_BITS, xMinusY); - let yMinusXFits = Gadgets.isInRangeN(UInt32.NUM_BITS, yMinusX); + let xMinusYFits = RangeCheck.isInRangeN(UInt32.NUM_BITS, xMinusY); + let yMinusXFits = RangeCheck.isInRangeN(UInt32.NUM_BITS, yMinusX); xMinusYFits.or(yMinusXFits).assertEquals(true); // x <= y if y - x fits in 64 bits return yMinusXFits; @@ -909,8 +912,8 @@ class UInt32 extends CircuitValue { } else { let xMinusY = this.value.sub(y.value).seal(); let yMinusX = xMinusY.neg(); - let xMinusYFits = Gadgets.isInRangeN(UInt32.NUM_BITS, xMinusY); - let yMinusXFits = Gadgets.isInRangeN(UInt32.NUM_BITS, yMinusX); + let xMinusYFits = RangeCheck.isInRangeN(UInt32.NUM_BITS, xMinusY); + let yMinusXFits = RangeCheck.isInRangeN(UInt32.NUM_BITS, yMinusX); xMinusYFits.or(yMinusXFits).assertEquals(true); // x <= y if y - x fits in 64 bits return yMinusXFits; @@ -940,7 +943,7 @@ class UInt32 extends CircuitValue { return; } let yMinusX = y.value.sub(this.value).seal(); - Gadgets.rangeCheckN(UInt32.NUM_BITS, yMinusX, message); + RangeCheck.rangeCheckN(UInt32.NUM_BITS, yMinusX, message); } /** @@ -1340,7 +1343,7 @@ class UInt8 extends Struct({ */ add(y: UInt8 | bigint | number) { let z = this.value.add(UInt8.from(y).value); - Gadgets.rangeCheck8(z); + RangeCheck.rangeCheck8(z); return UInt8.Unsafe.fromField(z); } @@ -1358,7 +1361,7 @@ class UInt8 extends Struct({ */ sub(y: UInt8 | bigint | number) { let z = this.value.sub(UInt8.from(y).value); - Gadgets.rangeCheck8(z); + RangeCheck.rangeCheck8(z); return UInt8.Unsafe.fromField(z); } @@ -1376,7 +1379,7 @@ class UInt8 extends Struct({ */ mul(y: UInt8 | bigint | number) { let z = this.value.mul(UInt8.from(y).value); - Gadgets.rangeCheck8(z); + RangeCheck.rangeCheck8(z); return UInt8.Unsafe.fromField(z); } @@ -1436,8 +1439,8 @@ class UInt8 extends Struct({ // q, r being 16 bits is enough for them to be 8 bits, // thanks to the === x check and the r < y check below - Gadgets.rangeCheck16(q); - Gadgets.rangeCheck16(r); + RangeCheck.rangeCheck16(q); + RangeCheck.rangeCheck16(r); let remainder = UInt8.Unsafe.fromField(r); let quotient = UInt8.Unsafe.fromField(q); @@ -1526,7 +1529,7 @@ class UInt8 extends Struct({ try { // x <= y <=> y - x >= 0 which is implied by y - x in [0, 2^16) let yMinusX = y_.value.sub(this.value).seal(); - Gadgets.rangeCheck16(yMinusX); + RangeCheck.rangeCheck16(yMinusX); } catch (err) { throw withMessage(err, message); } @@ -1630,7 +1633,7 @@ class UInt8 extends Struct({ */ static check(x: { value: Field } | Field) { if (x instanceof Field) x = { value: x }; - Gadgets.rangeCheck8(x.value); + RangeCheck.rangeCheck8(x.value); } static toInput(x: { value: Field }): HashInput { @@ -1674,6 +1677,6 @@ class UInt8 extends Struct({ private static checkConstant(x: Field) { if (!x.isConstant()) return; - Gadgets.rangeCheck8(x); + RangeCheck.rangeCheck8(x); } } diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index e4e2e4aec1..1d1f0e40a9 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -4,7 +4,7 @@ import { assert } from './errors.js'; import { FlexibleBytes } from './provable-types/bytes.js'; import { UInt8 } from './int.js'; import { Bytes } from './provable-types/provable-types.js'; -import { bytesToWords, wordsToBytes } from './gadgets/common.js'; +import { bytesToWords, wordsToBytes } from './gadgets/bit-slices.js'; export { Keccak }; diff --git a/src/lib/provable-types/bytes.ts b/src/lib/provable-types/bytes.ts index 992e10bd1f..811e93981c 100644 --- a/src/lib/provable-types/bytes.ts +++ b/src/lib/provable-types/bytes.ts @@ -1,11 +1,15 @@ import { provableFromClass } from '../../bindings/lib/provable-snarky.js'; -import { ProvablePureExtended } from '../circuit_value.js'; +import type { ProvablePureExtended } from '../circuit_value.js'; import { assert } from '../gadgets/common.js'; import { chunkString } from '../util/arrays.js'; import { Provable } from '../provable.js'; import { UInt8 } from '../int.js'; -export { Bytes, createBytes, FlexibleBytes }; +// external API +export { Bytes }; + +// internal API +export { createBytes, FlexibleBytes }; type FlexibleBytes = Bytes | (UInt8 | bigint | number)[] | Uint8Array; From dd7b88ab3aec7e772fdd4d1e77e1abac66777c35 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Jan 2024 12:50:30 +0100 Subject: [PATCH 45/52] move other bit slicing gadget to bit slicing file --- src/lib/gadgets/bit-slices.ts | 104 +++++++++++++++++++++++++++++- src/lib/gadgets/elliptic-curve.ts | 101 +---------------------------- 2 files changed, 106 insertions(+), 99 deletions(-) diff --git a/src/lib/gadgets/bit-slices.ts b/src/lib/gadgets/bit-slices.ts index b6b3d7acfe..0672e1aeb5 100644 --- a/src/lib/gadgets/bit-slices.ts +++ b/src/lib/gadgets/bit-slices.ts @@ -1,12 +1,17 @@ /** * Gadgets for converting between field elements and bit slices of various lengths */ +import { bigIntToBits } from '../../bindings/crypto/bigint-helpers.js'; +import { Bool } from '../bool.js'; import { Field } from '../field.js'; import { UInt8 } from '../int.js'; import { Provable } from '../provable.js'; import { chunk } from '../util/arrays.js'; +import { assert, exists } from './common.js'; +import type { Field3 } from './foreign-field.js'; +import { l } from './range-check.js'; -export { bytesToWord, wordToBytes, wordsToBytes, bytesToWords }; +export { bytesToWord, wordToBytes, wordsToBytes, bytesToWords, sliceField3 }; // conversion between bytes and multi-byte words @@ -52,3 +57,100 @@ function wordsToBytes(words: Field[], bytesPerWord = 8): UInt8[] { function bytesToWords(bytes: UInt8[], bytesPerWord = 8): Field[] { return chunk(bytes, bytesPerWord).map(bytesToWord); } + +// conversion between 3-limb foreign fields and arbitrary bit slices + +/** + * Provable method for slicing a 3x88-bit bigint into smaller bit chunks of length `chunkSize` + * + * This serves as a range check that the input is in [0, 2^maxBits) + */ +function sliceField3( + [x0, x1, x2]: Field3, + { maxBits, chunkSize }: { maxBits: number; chunkSize: number } +) { + let l_ = Number(l); + assert(maxBits <= 3 * l_, `expected max bits <= 3*${l_}, got ${maxBits}`); + + // first limb + let result0 = sliceField(x0, Math.min(l_, maxBits), chunkSize); + if (maxBits <= l_) return result0.chunks; + maxBits -= l_; + + // second limb + let result1 = sliceField(x1, Math.min(l_, maxBits), chunkSize, result0); + if (maxBits <= l_) return result0.chunks.concat(result1.chunks); + maxBits -= l_; + + // third limb + let result2 = sliceField(x2, maxBits, chunkSize, result1); + return result0.chunks.concat(result1.chunks, result2.chunks); +} + +/** + * Provable method for slicing a field element into smaller bit chunks of length `chunkSize`. + * + * This serves as a range check that the input is in [0, 2^maxBits) + * + * If `chunkSize` does not divide `maxBits`, the last chunk will be smaller. + * We return the number of free bits in the last chunk, and optionally accept such a result from a previous call, + * so that this function can be used to slice up a bigint of multiple limbs into homogeneous chunks. + * + * TODO: atm this uses expensive boolean checks for each bit. + * For larger chunks, we should use more efficient range checks. + */ +function sliceField( + x: Field, + maxBits: number, + chunkSize: number, + leftover?: { chunks: Field[]; leftoverSize: number } +) { + let bits = exists(maxBits, () => { + let bits = bigIntToBits(x.toBigInt()); + // normalize length + if (bits.length > maxBits) bits = bits.slice(0, maxBits); + if (bits.length < maxBits) + bits = bits.concat(Array(maxBits - bits.length).fill(false)); + return bits.map(BigInt); + }); + + let chunks = []; + let sum = Field.from(0n); + + // if there's a leftover chunk from a previous sliceField() call, we complete it + if (leftover !== undefined) { + let { chunks: previous, leftoverSize: size } = leftover; + let remainingChunk = Field.from(0n); + for (let i = 0; i < size; i++) { + let bit = bits[i]; + Bool.check(Bool.Unsafe.ofField(bit)); + remainingChunk = remainingChunk.add(bit.mul(1n << BigInt(i))); + } + sum = remainingChunk = remainingChunk.seal(); + let chunk = previous[previous.length - 1]; + previous[previous.length - 1] = chunk.add( + remainingChunk.mul(1n << BigInt(chunkSize - size)) + ); + } + + let i = leftover?.leftoverSize ?? 0; + for (; i < maxBits; i += chunkSize) { + // prove that chunk has `chunkSize` bits + // TODO: this inner sum should be replaced with a more efficient range check when possible + let chunk = Field.from(0n); + let size = Math.min(maxBits - i, chunkSize); // last chunk might be smaller + for (let j = 0; j < size; j++) { + let bit = bits[i + j]; + Bool.check(Bool.Unsafe.ofField(bit)); + chunk = chunk.add(bit.mul(1n << BigInt(j))); + } + chunk = chunk.seal(); + // prove that chunks add up to x + sum = sum.add(chunk.mul(1n << BigInt(i))); + chunks.push(chunk); + } + sum.assertEquals(x); + + let leftoverSize = i - maxBits; + return { chunks, leftoverSize } as const; +} diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index d2ee6eefc8..78ca4dbdff 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -3,10 +3,9 @@ import { Field } from '../field.js'; import { Provable } from '../provable.js'; import { assert, exists } from './common.js'; import { Field3, ForeignField, split, weakBound } from './foreign-field.js'; -import { l, l2, multiRangeCheck } from './range-check.js'; +import { l2, multiRangeCheck } from './range-check.js'; import { sha256 } from 'js-sha256'; import { - bigIntToBits, bigIntToBytes, bytesToBigInt, } from '../../bindings/crypto/bigint-helpers.js'; @@ -19,6 +18,7 @@ import { Bool } from '../bool.js'; import { provable } from '../circuit_value.js'; import { assertPositiveInteger } from '../../bindings/crypto/non-negative.js'; import { arrayGet, assertBoolean } from './basic.js'; +import { sliceField3 } from './bit-slices.js'; // external API export { EllipticCurve, Point, Ecdsa }; @@ -410,7 +410,7 @@ function multiScalarMul( // slice scalars let scalarChunks = scalars.map((s, i) => - slice(s, { maxBits, chunkSize: windowSizes[i] }) + sliceField3(s, { maxBits, chunkSize: windowSizes[i] }) ); ia ??= initialAggregator(Curve); @@ -625,101 +625,6 @@ function simpleMapToCurve(x: bigint, Curve: CurveAffine) { return p; } -/** - * Provable method for slicing a 3x88-bit bigint into smaller bit chunks of length `chunkSize` - * - * This serves as a range check that the input is in [0, 2^maxBits) - */ -function slice( - [x0, x1, x2]: Field3, - { maxBits, chunkSize }: { maxBits: number; chunkSize: number } -) { - let l_ = Number(l); - assert(maxBits <= 3 * l_, `expected max bits <= 3*${l_}, got ${maxBits}`); - - // first limb - let result0 = sliceField(x0, Math.min(l_, maxBits), chunkSize); - if (maxBits <= l_) return result0.chunks; - maxBits -= l_; - - // second limb - let result1 = sliceField(x1, Math.min(l_, maxBits), chunkSize, result0); - if (maxBits <= l_) return result0.chunks.concat(result1.chunks); - maxBits -= l_; - - // third limb - let result2 = sliceField(x2, maxBits, chunkSize, result1); - return result0.chunks.concat(result1.chunks, result2.chunks); -} - -/** - * Provable method for slicing a field element into smaller bit chunks of length `chunkSize`. - * - * This serves as a range check that the input is in [0, 2^maxBits) - * - * If `chunkSize` does not divide `maxBits`, the last chunk will be smaller. - * We return the number of free bits in the last chunk, and optionally accept such a result from a previous call, - * so that this function can be used to slice up a bigint of multiple limbs into homogeneous chunks. - * - * TODO: atm this uses expensive boolean checks for each bit. - * For larger chunks, we should use more efficient range checks. - */ -function sliceField( - x: Field, - maxBits: number, - chunkSize: number, - leftover?: { chunks: Field[]; leftoverSize: number } -) { - let bits = exists(maxBits, () => { - let bits = bigIntToBits(x.toBigInt()); - // normalize length - if (bits.length > maxBits) bits = bits.slice(0, maxBits); - if (bits.length < maxBits) - bits = bits.concat(Array(maxBits - bits.length).fill(false)); - return bits.map(BigInt); - }); - - let chunks = []; - let sum = Field.from(0n); - - // if there's a leftover chunk from a previous sliceField() call, we complete it - if (leftover !== undefined) { - let { chunks: previous, leftoverSize: size } = leftover; - let remainingChunk = Field.from(0n); - for (let i = 0; i < size; i++) { - let bit = bits[i]; - Bool.check(Bool.Unsafe.ofField(bit)); - remainingChunk = remainingChunk.add(bit.mul(1n << BigInt(i))); - } - sum = remainingChunk = remainingChunk.seal(); - let chunk = previous[previous.length - 1]; - previous[previous.length - 1] = chunk.add( - remainingChunk.mul(1n << BigInt(chunkSize - size)) - ); - } - - let i = leftover?.leftoverSize ?? 0; - for (; i < maxBits; i += chunkSize) { - // prove that chunk has `chunkSize` bits - // TODO: this inner sum should be replaced with a more efficient range check when possible - let chunk = Field.from(0n); - let size = Math.min(maxBits - i, chunkSize); // last chunk might be smaller - for (let j = 0; j < size; j++) { - let bit = bits[i + j]; - Bool.check(Bool.Unsafe.ofField(bit)); - chunk = chunk.add(bit.mul(1n << BigInt(j))); - } - chunk = chunk.seal(); - // prove that chunks add up to x - sum = sum.add(chunk.mul(1n << BigInt(i))); - chunks.push(chunk); - } - sum.assertEquals(x); - - let leftoverSize = i - maxBits; - return { chunks, leftoverSize } as const; -} - /** * Get value from array in O(n) constraints. * From c202d9d5eeb2c58a9711ed51c601982553b9661f Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Jan 2024 12:53:13 +0100 Subject: [PATCH 46/52] remove cyclic top-level gadgets dep from sha256.ts --- src/lib/gadgets/sha256.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 8cac25b58c..aa98f6e46e 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -6,9 +6,9 @@ import { FlexibleBytes } from '../provable-types/bytes.js'; import { Bytes } from '../provable-types/provable-types.js'; import { chunk } from '../util/arrays.js'; import { TupleN } from '../util/types.js'; +import { divMod32 } from './arithmetic.js'; import { bytesToWord, wordToBytes } from './bit-slices.js'; import { bitSlice, exists } from './common.js'; -import { Gadgets } from './gadgets.js'; import { rangeCheck16 } from './range-check.js'; export { SHA256 }; @@ -83,7 +83,7 @@ function decomposeToBytes(a: UInt32) { let ys = []; for (let i = 0; i < 4; i++) { // for each byte we rotate the element and get the excess bits (8 at a time) and construct a UInt8 of it - let { quotient, remainder } = Gadgets.divMod32(field.mul(1n << 8n)); + let { quotient, remainder } = divMod32(field.mul(1n << 8n)); // "shift" the element by 8 bit to get the next byte sequence during the next iteration field = remainder; ys.push(quotient); @@ -118,7 +118,7 @@ const SHA256 = { .add(DeltaZero(W[t - 15]).value.add(W[t - 16].value)); // mod 32bit the unreduced field element - W[t] = UInt32.from(Gadgets.divMod32(unreduced, 16).remainder); + W[t] = UInt32.from(divMod32(unreduced, 16).remainder); } // initialize working variables @@ -146,15 +146,11 @@ const SHA256 = { h = g; g = f; f = e; - e = UInt32.from( - Gadgets.divMod32(d.value.add(unreducedT1), 16).remainder - ); // mod 32bit the unreduced field element + e = UInt32.from(divMod32(d.value.add(unreducedT1), 16).remainder); // mod 32bit the unreduced field element d = c; c = b; b = a; - a = UInt32.from( - Gadgets.divMod32(unreducedT2.add(unreducedT1), 16).remainder - ); // mod 32bit + a = UInt32.from(divMod32(unreducedT2.add(unreducedT1), 16).remainder); // mod 32bit } // new intermediate hash value From 1f7ebb29f6c760aefc4441ca2f92102385bcc820 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Jan 2024 12:53:32 +0100 Subject: [PATCH 47/52] remove unused function --- src/lib/gadgets/sha256.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index aa98f6e46e..6d55273347 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -77,22 +77,6 @@ function padding(data: FlexibleBytes): UInt32[][] { return chunk(chunks, 16); } -// decompose a 32bit word into 4 bytes -function decomposeToBytes(a: UInt32) { - let field = a.value; - let ys = []; - for (let i = 0; i < 4; i++) { - // for each byte we rotate the element and get the excess bits (8 at a time) and construct a UInt8 of it - let { quotient, remainder } = divMod32(field.mul(1n << 8n)); - // "shift" the element by 8 bit to get the next byte sequence during the next iteration - field = remainder; - ys.push(quotient); - } - - // UInt8.from does a rangeCheck8 for Field elements - return ys.map(UInt8.from); -} - const SHA256 = { hash(data: FlexibleBytes) { // preprocessing §6.2 From f077283029744f63594715184a4d951bb350e609 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Jan 2024 13:02:13 +0100 Subject: [PATCH 48/52] dump vks --- tests/vk-regression/vk-regression.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index ec705078d8..7a84e7fc0f 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -266,16 +266,16 @@ } }, "sha256": { - "digest": "15bce9c541c0619f3b3587d7c06918e1423c5d1d4efe6ba6e0c789987e41b512", + "digest": "2253f1a883d78886858530b473943e685f302d489a509a9af105ccf3621584bf", "methods": { "sha256": { - "rows": 5314, - "digest": "bfad0408f0baab2e3db346a3e9997455" + "rows": 5125, + "digest": "5a7c5b22395d328f5ca9e3242669b478" } }, "verificationKey": { - "data": "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", - "hash": "801809578510365639167868048464129098535493687043603299559390072759232736708" + "data": "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", + "hash": "4802323592772073249618921425353059023939321317450660242786504849922261724135" } } } \ No newline at end of file From 6b8d48a7af93fc74c2bea224fb73cc1002e9c885 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Jan 2024 13:06:03 +0100 Subject: [PATCH 49/52] save a few constraints with seal() --- src/lib/gadgets/sha256.ts | 3 ++- tests/vk-regression/vk-regression.json | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 6d55273347..9cca97c7b4 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -122,7 +122,8 @@ const SHA256 = { .add(SigmaOne(e).value) .add(Ch(e, f, g).value) .add(K[t].value) - .add(W[t].value); + .add(W[t].value) + .seal(); // T2 is also unreduced const unreducedT2 = SigmaZero(a).value.add(Maj(a, b, c).value); diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 7a84e7fc0f..1c1dca92f3 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -266,16 +266,16 @@ } }, "sha256": { - "digest": "2253f1a883d78886858530b473943e685f302d489a509a9af105ccf3621584bf", + "digest": "38eaa6264b1e4c1c0b5787c06efd92aa7855890aeeafed91818d466f1b109ca5", "methods": { "sha256": { - "rows": 5125, - "digest": "5a7c5b22395d328f5ca9e3242669b478" + "rows": 5036, + "digest": "c3952f2bd0dbb6b16ed43add7c57c9c5" } }, "verificationKey": { - "data": "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", - "hash": "4802323592772073249618921425353059023939321317450660242786504849922261724135" + "data": "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", + "hash": "22296391645667701199385692837408020819294441951376164803693884547686842878882" } } } \ No newline at end of file From d71872dee0e387a9e7c886d6ab59d1d4f78ed878 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Jan 2024 13:08:13 +0100 Subject: [PATCH 50/52] add comments --- src/lib/gadgets/sha256.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 9cca97c7b4..73cec23804 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -272,5 +272,7 @@ function sigma(u: UInt32, bits: TupleN, firstShifted = false) { 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.from(xRotR0).xor(new UInt32(xRotR1)).xor(new UInt32(xRotR2)); } From b16d146dc0adaed1227b0c1d04a5c985beafda53 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 16 Jan 2024 09:54:24 +0300 Subject: [PATCH 51/52] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6868af6bd1..dadf1323e4 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/08ba27329...HEAD) +### Added + +- **SHA256 hash function** exposed via `Hash.SHA2_256` or `Gadgets.SHA256`. https://github.com/o1-labs/o1js/pull/1285 + ### Fixed - Fix approving of complex account update layouts https://github.com/o1-labs/o1js/pull/1364 From 23d096c923431662220a5052e54b92f6fa39c296 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 17 Jan 2024 10:55:41 +0300 Subject: [PATCH 52/52] address feedback --- src/lib/gadgets/sha256.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index 73cec23804..ed69db8325 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -48,12 +48,14 @@ function padding(data: FlexibleBytes): UInt32[][] { let l = message.length * 8; // length in bits let k = Number(mod(448n - (BigInt(l) + 1n), 512n)); + let lBinary = l.toString(2); + let paddingBits = ( '1' + // append 1 bit '0'.repeat(k) + // append k zero bits - '0'.repeat(64 - l.toString(2).length) + // append 64bit containing the length of the original message - l.toString(2) - ).match(/.{1,8}/g)!; // this should always be devisable by 8 + '0'.repeat(64 - lBinary.length) + // append 64bit 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)));