From cf17917dc58cb02623298896d31cd5a0ee3ba398 Mon Sep 17 00:00:00 2001 From: Michael Elliot Date: Mon, 11 Dec 2023 18:06:05 +0800 Subject: [PATCH] feat: improve dynamic handling of input param --- README.md | 13 +-- crates/noir-sha2/Nargo.toml | 2 +- crates/noir-sha2/src/lib.nr | 215 +++++++++++++++++++++++++++++++----- example/Nargo.toml | 2 +- example/src/main.nr | 2 +- 5 files changed, 196 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 5b52d94..c4da29b 100644 --- a/README.md +++ b/README.md @@ -10,25 +10,24 @@ In your `Nargo.toml` file, add the following dependency: ```toml [dependencies] -sha2 = { tag = "v0.0.1", git = "https://github.com/michaelelliot/noir-sha2", directory = "crates/noir-sha2" } +sha2 = { tag = "v0.0.2", git = "https://github.com/michaelelliot/noir-sha2", directory = "crates/noir-sha2" } ``` Then use it in your Noir project like this: ```rust -use dep::sha2::{sha2}; +use dep::sha2::{sha256}; fn main(input: [u8; 64], input_len: u16, hash: pub [u8; 32]) { - // Generate SHA-2 hash digest of input - let compare_hash = sha2(input, input_len); + // Generate sha256 hash of input + let compare_hash = sha256(input, input_len); assert(hash == compare_hash); } ``` -*NOTE:* The `input` parameter must be a `u8` byte array with a length that's a multiple of 64, such as 64, 128, 192, or 256 etc. -The rest of the byte array can be zero-padded (`0x00`) as shown in the example below, with the `input_len` parameter specifying the number of initial bytes from the `input` to be used for calculating the digest. +As shown in the example below, the `input` parameter is a `u8` byte array that's zero-padded (`0x00`), with the `input_len` parameter specifying the number of initial bytes to use from `input` to calculate the hash. -Here's an example unit test for the `main` entrypoint above: +Here's an example test for the `main` entrypoint above: ```rust #[test] diff --git a/crates/noir-sha2/Nargo.toml b/crates/noir-sha2/Nargo.toml index 86584d6..5056c10 100644 --- a/crates/noir-sha2/Nargo.toml +++ b/crates/noir-sha2/Nargo.toml @@ -2,5 +2,5 @@ name = "sha2" type = "lib" authors = ["@michaelelliot"] -compiler_version = "0.10.5" +compiler_version = ">=0.20.0" license = "MIT" diff --git a/crates/noir-sha2/src/lib.nr b/crates/noir-sha2/src/lib.nr index 5b105cf..ae5ffd7 100644 --- a/crates/noir-sha2/src/lib.nr +++ b/crates/noir-sha2/src/lib.nr @@ -1,4 +1,118 @@ -pub fn sha2(input: [u8; M], input_len: u16) -> [u8; 32] { +pub fn sha256(input: [u8; M], input_len: u16) -> [u8; 32] { + if input.len() as u16 > 63 { + sha256_gte_64(input, input_len) + } else { + sha256_lt_64(input, input_len) + } +} + +pub fn sha256_lt_64(input: [u8; M], input_len: u16) -> [u8; 32] { + let k: [u32; 64] = [ + 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 + ]; + let mut h0: u32 = 0x6A09E667; + let mut h1: u32 = 0xBB67AE85; + let mut h2: u32 = 0x3C6EF372; + let mut h3: u32 = 0xA54FF53A; + let mut h4: u32 = 0x510E527F; + let mut h5: u32 = 0x9B05688C; + let mut h6: u32 = 0x1F83D9AB; + let mut h7: u32 = 0x5BE0CD19; + + let mut msg: [u8; 64] = [0; 64]; + for i in 0..M { msg[i] = input[i]; } + msg[input_len] = 0x80; + + let msg_length_in_bits_as_be_bytes: [u8; 8] = u64_to_u8(input_len as u64 * 8); + let mut rem: u16 = fast_div_mod_rem(input_len + 1, 64); + rem = if rem <= 56 { + 56 as u16 - rem + } else { + 56 as u16 + (64 as u16 - rem) + }; + let msg_padding_end: u16 = input_len + 1 + rem; + for i in 0..8 { + msg[msg_padding_end+i as u16] = msg_length_in_bits_as_be_bytes[i]; + } + + let num_chunks: u16 = fast_div_quot(msg_padding_end, 64) + 1; + let num_chunks_max: u16 = (M/64) as u16; + + for chunk_idx in 0..num_chunks_max { + if chunk_idx < num_chunks { + let start_idx = chunk_idx * 64; + let mut chunk: [u8; 64] = [0; 64]; + for j in 0..64 { + chunk[j] = msg[start_idx as Field + j as Field]; + } + let mut w: [u32; 64] = [0; 64]; + for i in 0..16 { + w[i] = bytes_to_u32([ + chunk[ i * 4], + chunk[(i * 4) + 1], + chunk[(i * 4) + 2], + chunk[(i * 4) + 3]]); + } + for i in 16..64 { + w[i] = wrapping_add_4(sigma1(w[i - 2]), w[i - 7], sigma0(w[i - 15]), w[i - 16]); + } + + let (mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h) = (h0, h1, h2, h3, h4, h5, h6, h7); + for t in 0..64 { + let temp1: u32 = wrapping_add_5(h, big_sigma1(e), ch(e, f, g), k[t], w[t]); + let temp2: u32 = wrapping_add(big_sigma0(a), maj(a, b, c)); + h = g; + g = f; + f = e; + e = wrapping_add(d, temp1); + d = c; + c = b; + b = a; + a = wrapping_add(temp1, temp2); + } + h0 = wrapping_add(h0, a); + h1 = wrapping_add(h1, b); + h2 = wrapping_add(h2, c); + h3 = wrapping_add(h3, d); + h4 = wrapping_add(h4, e); + h5 = wrapping_add(h5, f); + h6 = wrapping_add(h6, g); + h7 = wrapping_add(h7, h); + } + } + + let h0_bytes = u32_to_u8(h0); + let h1_bytes = u32_to_u8(h1); + let h2_bytes = u32_to_u8(h2); + let h3_bytes = u32_to_u8(h3); + let h4_bytes = u32_to_u8(h4); + let h5_bytes = u32_to_u8(h5); + let h6_bytes = u32_to_u8(h6); + let h7_bytes = u32_to_u8(h7); + + let mut result: [u8; 32] = [0; 32]; + for i in 0..4 { result[i] = h0_bytes[i]; } + for i in 0..4 { result[4+i] = h1_bytes[i]; } + for i in 0..4 { result[8+i] = h2_bytes[i]; } + for i in 0..4 { result[12+i] = h3_bytes[i]; } + for i in 0..4 { result[16+i] = h4_bytes[i]; } + for i in 0..4 { result[20+i] = h5_bytes[i]; } + for i in 0..4 { result[24+i] = h6_bytes[i]; } + for i in 0..4 { result[28+i] = h7_bytes[i]; } + + result +} + +pub fn sha256_gte_64(input: [u8; M], input_len: u16) -> [u8; 32] { + assert(input.len() as u16 >= 64, "Size of input must be a at least 64 bytes"); + let k: [u32; 64] = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, @@ -33,11 +147,12 @@ pub fn sha2(input: [u8; M], input_len: u16) -> [u8; 32] { for i in 0..8 { msg[msg_padding_end+i as u16] = msg_length_in_bits_as_be_bytes[i]; } - - let num_chunks: u16 = (msg_padding_end / 64) + 1; - let num_chunks_max: Field = (M/64); + + let num_chunks: u16 = fast_div_quot(msg_padding_end, 64) + 1; + let num_chunks_max: u16 = (M/64) as u16; + for chunk_idx in 0..num_chunks_max { - if chunk_idx as u16 < num_chunks as u16 { + if chunk_idx < num_chunks { let start_idx = chunk_idx * 64; let mut chunk: [u8; 64] = [0; 64]; for j in 0..64 { @@ -52,33 +167,33 @@ pub fn sha2(input: [u8; M], input_len: u16) -> [u8; 32] { chunk[(i * 4) + 3]]); } for i in 16..64 { - w[i] = sigma1(w[i - 2]) + w[i - 7] + sigma0(w[i - 15]) + w[i - 16]; + w[i] = wrapping_add_4(sigma1(w[i - 2]), w[i - 7], sigma0(w[i - 15]), w[i - 16]); } - + let (mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h) = (h0, h1, h2, h3, h4, h5, h6, h7); for t in 0..64 { - let temp1: u32 = h + big_sigma1(e) + ch(e, f, g) + k[t] + w[t]; - let temp2: u32 = big_sigma0(a) + maj(a, b, c); + let temp1: u32 = wrapping_add_5(h, big_sigma1(e), ch(e, f, g), k[t], w[t]); + let temp2: u32 = wrapping_add(big_sigma0(a), maj(a, b, c)); h = g; g = f; f = e; - e = d + temp1; + e = wrapping_add(d, temp1); d = c; c = b; b = a; - a = temp1 + temp2; + a = wrapping_add(temp1, temp2); } - h0 += a; - h1 += b; - h2 += c; - h3 += d; - h4 += e; - h5 += f; - h6 += g; - h7 += h; + h0 = wrapping_add(h0, a); + h1 = wrapping_add(h1, b); + h2 = wrapping_add(h2, c); + h3 = wrapping_add(h3, d); + h4 = wrapping_add(h4, e); + h5 = wrapping_add(h5, f); + h6 = wrapping_add(h6, g); + h7 = wrapping_add(h7, h); } } - + let h0_bytes = u32_to_u8(h0); let h1_bytes = u32_to_u8(h1); let h2_bytes = u32_to_u8(h2); @@ -111,6 +226,15 @@ fn fast_div_mod_rem(dividend: u16, divisor: u16) -> u16 { } } +fn fast_div_quot(dividend: u16, divisor: u16) -> u16 { + if dividend == divisor { 0 } + else if dividend < divisor { 0 } + else { + let quot: u16 = dividend / divisor; + quot + } +} + fn bytes_to_u32(bytes: [u8; 4]) -> u32 { bytes[0] as u32 << 24 | bytes[1] as u32 << 16 | bytes[2] as u32 << 8 | bytes[3] as u32 } @@ -147,20 +271,38 @@ fn big_sigma1(x: u32) -> u32 { right_rotate(x, 6) ^ right_rotate(x, 11) ^ right_rotate(x, 25) } -fn right_rotate(n: u32, d: u32) -> u32 { - (n >> d) | (n << (32 - d)) & 0xFFFFFFFF +fn right_rotate(a: u32, b: u32) -> u32 { + wrapping_add(a >> b, a << (32 - b)) +} + +fn maj(x: u32, y: u32, z: u32) -> u32 { + (x & y) ^ (x & z) ^ (y & z) +} + +fn ch(x: u32, y: u32, z: u32) -> u32 { + (x & y) ^ ((!x) & z) +} + +#[builtin(from_field)] +fn from_field(_x: Field) -> T {} + +#[builtin(as_field)] +fn as_field(_x: T) -> Field {} + +fn wrapping_add(a: T, b: T) -> T { + from_field(as_field(a) + as_field(b)) } -fn maj(a: u32, b: u32, c: u32) -> u32 { - (a & b) ^ (a & c) ^ (b & c) +fn wrapping_add_4(a: T, b: T, c: T, d: T) -> T { + from_field(as_field(a) + as_field(b) + as_field(c) + as_field(d)) } -fn ch(a: u32, b: u32, c: u32) -> u32 { - (a & b) ^ ((!a) & c) +fn wrapping_add_5(a: T, b: T, c: T, d: T, e: T) -> T { + from_field(as_field(a) + as_field(b) + as_field(c) + as_field(d) + as_field(e)) } #[test] -fn test_sha2() { +fn test_sha256_gte_64() { // Hal Finney was a cypherpunk pioneer let test_msg: [u8; 64] = [ 0x48, 0x61, 0x6c, 0x20, 0x46, 0x69, 0x6e, 0x6e, @@ -174,7 +316,24 @@ fn test_sha2() { let test_hash: [u8; 32] = [ 0xd4, 0x5d, 0xa9, 0xcf, 0x6f, 0xc0, 0xe0, 0x56, 0x7e, 0xa2, 0x7d, 0xf1, 0xbb, 0x17, 0xfb, 0x0a, 0x28, 0x10, 0x29, 0xca, 0x23, 0x17, 0x4f, 0xd0, 0x64, 0xad, 0xc4, 0x9a, 0x98, 0x7f, 0xd9, 0xff]; + let hash = sha256(test_msg, 35); + assert(hash == test_hash); +} - let hash = sha2(test_msg, 35); +#[test] +fn test_sha256_lt_64() { + // Hal Finney was a cypherpunk pioneer + let test_msg: [u8; 56] = [ + 0x48, 0x61, 0x6c, 0x20, 0x46, 0x69, 0x6e, 0x6e, + 0x65, 0x79, 0x20, 0x77, 0x61, 0x73, 0x20, 0x61, + 0x20, 0x63, 0x79, 0x70, 0x68, 0x65, 0x72, 0x70, + 0x75, 0x6e, 0x6b, 0x20, 0x70, 0x69, 0x6f, 0x6e, + 0x65, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let test_hash: [u8; 32] = [ + 0xd4, 0x5d, 0xa9, 0xcf, 0x6f, 0xc0, 0xe0, 0x56, 0x7e, 0xa2, 0x7d, 0xf1, 0xbb, 0x17, 0xfb, 0x0a, + 0x28, 0x10, 0x29, 0xca, 0x23, 0x17, 0x4f, 0xd0, 0x64, 0xad, 0xc4, 0x9a, 0x98, 0x7f, 0xd9, 0xff]; + let hash = sha256(test_msg, 35); assert(hash == test_hash); } diff --git a/example/Nargo.toml b/example/Nargo.toml index a950bb0..c34bc21 100644 --- a/example/Nargo.toml +++ b/example/Nargo.toml @@ -2,7 +2,7 @@ name = "sha2_example" type = "bin" authors = ["@michaelelliot"] -compiler_version = "0.10.5" +compiler_version = ">=0.20.0" [dependencies] sha2 = { path = "../crates/noir-sha2" } diff --git a/example/src/main.nr b/example/src/main.nr index 06a0e2c..ebfa33a 100644 --- a/example/src/main.nr +++ b/example/src/main.nr @@ -3,7 +3,7 @@ fn main( input_len: u16, hash: pub [u8; 32] ) { - let compare_hash = dep::sha2::sha2(input, input_len); + let compare_hash = dep::sha2::sha256(input, input_len); assert(hash == compare_hash); }