diff --git a/acvm-repo/acvm_js/src/black_box_solvers.rs b/acvm-repo/acvm_js/src/black_box_solvers.rs new file mode 100644 index 00000000000..cc3edc3de04 --- /dev/null +++ b/acvm-repo/acvm_js/src/black_box_solvers.rs @@ -0,0 +1,100 @@ +use js_sys::JsString; +use wasm_bindgen::prelude::*; + +use crate::js_witness_map::{field_element_to_js_string, js_value_to_field_element}; +use acvm::FieldElement; + +/// Performs a bitwise AND operation between `lhs` and `rhs` +#[wasm_bindgen] +pub fn and(lhs: JsString, rhs: JsString) -> JsString { + let lhs = js_value_to_field_element(lhs.into()).unwrap(); + let rhs = js_value_to_field_element(rhs.into()).unwrap(); + let result = lhs.and(&rhs, FieldElement::max_num_bits()); + field_element_to_js_string(&result) +} + +/// Performs a bitwise XOR operation between `lhs` and `rhs` +#[wasm_bindgen] +pub fn xor(lhs: JsString, rhs: JsString) -> JsString { + let lhs = js_value_to_field_element(lhs.into()).unwrap(); + let rhs = js_value_to_field_element(rhs.into()).unwrap(); + let result = lhs.xor(&rhs, FieldElement::max_num_bits()); + field_element_to_js_string(&result) +} + +/// Calculates the SHA256 hash of the input bytes +#[wasm_bindgen] +pub fn sha256(inputs: &[u8]) -> Vec { + acvm::blackbox_solver::sha256(inputs).unwrap().into() +} + +/// Calculates the Blake2s256 hash of the input bytes +#[wasm_bindgen] +pub fn blake2s256(inputs: &[u8]) -> Vec { + acvm::blackbox_solver::blake2s(inputs).unwrap().into() +} + +/// Calculates the Keccak256 hash of the input bytes +#[wasm_bindgen] +pub fn keccak256(inputs: &[u8]) -> Vec { + acvm::blackbox_solver::keccak256(inputs).unwrap().into() +} + +/// Calculates the Blake2s256 hash of the input bytes and represents these as a single field element. +// #[wasm_bindgen] +// pub fn hash_to_field_128_security(inputs: Vec) -> JsString { +// let input_bytes: Vec = inputs +// .into_iter() +// .flat_map(|field_string| { +// let field_element = js_value_to_field_element(field_string.into()).unwrap(); +// witness_assignment.fetch_nearest_bytes(FieldElement::max_num_bits()); +// }) +// .collect(); +// field_element_to_js_string( +// &acvm::blackbox_solver::hash_to_field_128_security(&input_bytes).unwrap(), +// ) +// } + +/// Verifies a ECDSA signature over the secp256k1 curve. +#[wasm_bindgen] +pub fn ecdsa_secp256k1_verify( + hashed_msg: &[u8], + public_key_x_bytes: &[u8], + public_key_y_bytes: &[u8], + signature: &[u8], +) -> bool { + let public_key_x_bytes: &[u8; 32] = public_key_x_bytes.try_into().unwrap(); + let public_key_y_bytes: &[u8; 32] = public_key_y_bytes.try_into().unwrap(); + let signature: &[u8; 64] = signature.try_into().unwrap(); + + acvm::blackbox_solver::ecdsa_secp256k1_verify( + hashed_msg, + public_key_x_bytes, + public_key_y_bytes, + signature, + ) + .unwrap() + .into() +} + +/// Verifies a ECDSA signature over the secp256r1 curve. +#[wasm_bindgen] +pub fn ecdsa_secp256r1_verify( + hashed_msg: &[u8], + public_key_x_bytes: &[u8], + public_key_y_bytes: &[u8], + signature: &[u8], +) -> bool { + let public_key_x_bytes: &[u8; 32] = public_key_x_bytes.try_into().unwrap(); + let public_key_y_bytes: &[u8; 32] = public_key_y_bytes.try_into().unwrap(); + let signature: &[u8; 64] = signature.try_into().unwrap(); + + acvm::blackbox_solver::ecdsa_secp256r1_verify( + hashed_msg, + public_key_x_bytes, + public_key_y_bytes, + signature, + ) + .unwrap() + .into() +} diff --git a/acvm-repo/acvm_js/src/lib.rs b/acvm-repo/acvm_js/src/lib.rs index 6914c0de540..f882d0fe896 100644 --- a/acvm-repo/acvm_js/src/lib.rs +++ b/acvm-repo/acvm_js/src/lib.rs @@ -15,7 +15,9 @@ cfg_if::cfg_if! { mod logging; mod public_witness; mod js_execution_error; + mod black_box_solvers; + pub use black_box_solvers::{and, xor, sha256, blake2s256, keccak256, ecdsa_secp256k1_verify, ecdsa_secp256r1_verify}; pub use build_info::build_info; pub use compression::{compress_witness, decompress_witness}; pub use execute::{execute_circuit, execute_circuit_with_black_box_solver, create_black_box_solver}; diff --git a/acvm-repo/acvm_js/test/browser/black_box_solvers.test.ts b/acvm-repo/acvm_js/test/browser/black_box_solvers.test.ts new file mode 100644 index 00000000000..22ec365a0e3 --- /dev/null +++ b/acvm-repo/acvm_js/test/browser/black_box_solvers.test.ts @@ -0,0 +1,131 @@ +import { expect } from "@esm-bundle/chai"; +import initACVM, { + and, + blake2s256, + ecdsa_secp256k1_verify, + ecdsa_secp256r1_verify, + initLogLevel, + keccak256, + sha256, + xor, +} from "@noir-lang/acvm_js"; + +beforeEach(async () => { + await initACVM(); + + initLogLevel("INFO"); +}); + +it("successfully calculates the bitwise AND of two fields", async () => { + const { and_test_cases } = await import("../shared/black_box_solvers"); + + for (const testCase of and_test_cases) { + const [[lhs, rhs], expectedResult] = testCase; + expect(and(lhs, rhs)).to.be.eq(expectedResult); + } +}); + +it("successfully calculates the bitwise XOR of two fields", async () => { + const { xor_test_cases } = await import("../shared/black_box_solvers"); + + for (const testCase of xor_test_cases) { + const [[lhs, rhs], expectedResult] = testCase; + expect(xor(lhs, rhs)).to.be.eq(expectedResult); + } +}); + +it("successfully calculates the sha256 hash", async () => { + const { sha256_test_cases } = await import("../shared/black_box_solvers"); + + for (const testCase of sha256_test_cases) { + const [preimage, expectedResult] = testCase; + const hash = sha256(preimage); + hash.forEach((value, index) => + expect(value).to.be.eq(expectedResult.at(index)), + ); + } +}); + +it("successfully calculates the blake2s256 hash", async () => { + const { blake2s256_test_cases } = await import("../shared/black_box_solvers"); + + for (const testCase of blake2s256_test_cases) { + const [preimage, expectedResult] = testCase; + const hash = blake2s256(preimage); + hash.forEach((value, index) => + expect(value).to.be.eq(expectedResult.at(index)), + ); + } +}); + +it("successfully calculates the keccak256 hash", async () => { + const { keccak256_test_cases } = await import("../shared/black_box_solvers"); + + for (const testCase of keccak256_test_cases) { + const [preimage, expectedResult] = testCase; + const hash = keccak256(preimage); + hash.forEach((value, index) => + expect(value).to.be.eq(expectedResult.at(index)), + ); + } +}); + +// it("successfully calculates the hash_to_field_128_security field", async () => { +// const { hash_to_field_128_security_test_cases } = await import( +// "../shared/black_box_solvers" +// ); + +// for (const testCase of hash_to_field_128_security_test_cases) { +// const [preimage, expectedResult] = testCase; +// const hashField = hash_to_field_128_security(preimage); +// expect(hashField).to.be.eq(expectedResult); +// } +// }); + +it("successfully verifies secp256k1 ECDSA signatures", async () => { + const { ecdsa_secp256k1_test_cases } = await import( + "../shared/black_box_solvers" + ); + + for (const testCase of ecdsa_secp256k1_test_cases) { + const [[hashed_msg, pubkey_x, pubkey_y, signature], expectedResult] = + testCase; + + expect(hashed_msg.length).to.be.eq(32); + expect(pubkey_x.length).to.be.eq(32); + expect(pubkey_y.length).to.be.eq(32); + expect(signature.length).to.be.eq(64); + + const result = ecdsa_secp256k1_verify( + hashed_msg, + pubkey_x, + pubkey_y, + signature, + ); + expect(result).to.be.eq(expectedResult); + } +}); + +it("successfully verifies secp256r1 ECDSA signatures", async () => { + const { ecdsa_secp256r1_test_cases } = await import( + "../shared/black_box_solvers" + ); + + for (const testCase of ecdsa_secp256r1_test_cases) { + const [[hashed_msg, pubkey_x, pubkey_y, signature], expectedResult] = + testCase; + + expect(hashed_msg.length).to.be.eq(32); + expect(pubkey_x.length).to.be.eq(32); + expect(pubkey_y.length).to.be.eq(32); + expect(signature.length).to.be.eq(64); + + const result = ecdsa_secp256r1_verify( + hashed_msg, + pubkey_x, + pubkey_y, + signature, + ); + expect(result).to.be.eq(expectedResult); + } +}); diff --git a/acvm-repo/acvm_js/test/node/black_box_solvers.test.ts b/acvm-repo/acvm_js/test/node/black_box_solvers.test.ts new file mode 100644 index 00000000000..04322c19921 --- /dev/null +++ b/acvm-repo/acvm_js/test/node/black_box_solvers.test.ts @@ -0,0 +1,124 @@ +import { expect } from "chai"; +import { + and, + blake2s256, + ecdsa_secp256k1_verify, + ecdsa_secp256r1_verify, + keccak256, + sha256, + xor, +} from "@noir-lang/acvm_js"; + +it("successfully calculates the bitwise AND of two fields", async () => { + const { and_test_cases } = await import("../shared/black_box_solvers"); + + for (const testCase of and_test_cases) { + const [[lhs, rhs], expectedResult] = testCase; + expect(and(lhs, rhs)).to.be.eq(expectedResult); + } +}); + +it("successfully calculates the bitwise XOR of two fields", async () => { + const { xor_test_cases } = await import("../shared/black_box_solvers"); + + for (const testCase of xor_test_cases) { + const [[lhs, rhs], expectedResult] = testCase; + expect(xor(lhs, rhs)).to.be.eq(expectedResult); + } +}); + +it("successfully calculates the sha256 hash", async () => { + const { sha256_test_cases } = await import("../shared/black_box_solvers"); + + for (const testCase of sha256_test_cases) { + const [preimage, expectedResult] = testCase; + const hash = sha256(preimage); + hash.forEach((value, index) => + expect(value).to.be.eq(expectedResult.at(index)), + ); + } +}); + +it("successfully calculates the blake2s256 hash", async () => { + const { blake2s256_test_cases } = await import("../shared/black_box_solvers"); + + for (const testCase of blake2s256_test_cases) { + const [preimage, expectedResult] = testCase; + const hash = blake2s256(preimage); + hash.forEach((value, index) => + expect(value).to.be.eq(expectedResult.at(index)), + ); + } +}); + +it("successfully calculates the keccak256 hash", async () => { + const { keccak256_test_cases } = await import("../shared/black_box_solvers"); + + for (const testCase of keccak256_test_cases) { + const [preimage, expectedResult] = testCase; + const hash = keccak256(preimage); + hash.forEach((value, index) => + expect(value).to.be.eq(expectedResult.at(index)), + ); + } +}); + +// it("successfully calculates the hash_to_field_128_security field", async () => { +// const { hash_to_field_128_security_test_cases } = await import( +// "../shared/black_box_solvers" +// ); + +// for (const testCase of hash_to_field_128_security_test_cases) { +// const [preimage, expectedResult] = testCase; +// const hashField = hash_to_field_128_security(preimage); +// expect(hashField).to.be.eq(expectedResult); +// } +// }); + +it("successfully verifies secp256k1 ECDSA signatures", async () => { + const { ecdsa_secp256k1_test_cases } = await import( + "../shared/black_box_solvers" + ); + + for (const testCase of ecdsa_secp256k1_test_cases) { + const [[hashed_msg, pubkey_x, pubkey_y, signature], expectedResult] = + testCase; + + expect(hashed_msg.length).to.be.eq(32); + expect(pubkey_x.length).to.be.eq(32); + expect(pubkey_y.length).to.be.eq(32); + expect(signature.length).to.be.eq(64); + + const result = ecdsa_secp256k1_verify( + hashed_msg, + pubkey_x, + pubkey_y, + signature, + ); + expect(result).to.be.eq(expectedResult); + } +}); + +it("successfully verifies secp256r1 ECDSA signatures", async () => { + const { ecdsa_secp256r1_test_cases } = await import( + "../shared/black_box_solvers" + ); + + for (const testCase of ecdsa_secp256r1_test_cases) { + const [[hashed_msg, pubkey_x, pubkey_y, signature], expectedResult] = + testCase; + + expect(hashed_msg.length).to.be.eq(32); + expect(pubkey_x.length).to.be.eq(32); + expect(pubkey_y.length).to.be.eq(32); + expect(signature.length).to.be.eq(64); + + const result = ecdsa_secp256r1_verify( + hashed_msg, + pubkey_x, + pubkey_y, + signature, + ); + expect(result).to.be.eq(expectedResult); + } +}); diff --git a/acvm-repo/acvm_js/test/shared/black_box_solvers.ts b/acvm-repo/acvm_js/test/shared/black_box_solvers.ts new file mode 100644 index 00000000000..b7f3ed4b207 --- /dev/null +++ b/acvm-repo/acvm_js/test/shared/black_box_solvers.ts @@ -0,0 +1,178 @@ +export const and_test_cases: [[string, string], string][] = [ + [ + [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002", + ], + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + [ + [ + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000002", + ], + "0x0000000000000000000000000000000000000000000000000000000000000002", + ], +]; + +export const xor_test_cases: [[string, string], string][] = [ + [ + [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002", + ], + "0x0000000000000000000000000000000000000000000000000000000000000003", + ], + [ + [ + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000002", + ], + "0x0000000000000000000000000000000000000000000000000000000000000001", + ], +]; + +// https://www.di-mgt.com.au/sha_testvectors.html +export const sha256_test_cases: [Uint8Array, Uint8Array][] = [ + [ + // "abc" + Uint8Array.from([0x61, 0x62, 0x63]), + Uint8Array.from([ + 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, + 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad, + ]), + ], +]; + +// https://www.rfc-editor.org/rfc/rfc7693.html#appendix-B +export const blake2s256_test_cases: [Uint8Array, Uint8Array][] = [ + [ + // "abc" + Uint8Array.from([0x61, 0x62, 0x63]), + Uint8Array.from([ + 0x50, 0x8c, 0x5e, 0x8c, 0x32, 0x7c, 0x14, 0xe2, 0xe1, 0xa7, 0x2b, 0xa3, + 0x4e, 0xeb, 0x45, 0x2f, 0x37, 0x45, 0x8b, 0x20, 0x9e, 0xd6, 0x3a, 0x29, + 0x4d, 0x99, 0x9b, 0x4c, 0x86, 0x67, 0x59, 0x82, + ]), + ], +]; + +export const keccak256_test_cases: [Uint8Array, Uint8Array][] = [ + [ + Uint8Array.from([0xbd]), + Uint8Array.from([ + 0x5a, 0x50, 0x2f, 0x9f, 0xca, 0x46, 0x7b, 0x26, 0x6d, 0x5b, 0x78, 0x33, + 0x65, 0x19, 0x37, 0xe8, 0x05, 0x27, 0x0c, 0xa3, 0xf3, 0xaf, 0x1c, 0x0d, + 0xd2, 0x46, 0x2d, 0xca, 0x4b, 0x3b, 0x1a, 0xbf, + ]), + ], +]; + +// export const hash_to_field_128_security_test_cases: [string[], string][] = [ +// [ +// ["0x0000000000000000000000000000000000000000000000000000000000000001"], +// "0x25cebc29ded2fa515a937e2b5f674e3026c012e5b57f8a48d7dce6b7d274f9d9", +// ], +// ]; + +export const ecdsa_secp256k1_test_cases: [ + [Uint8Array, Uint8Array, Uint8Array, Uint8Array], + boolean, +][] = [ + [ + [ + // hashed message + Uint8Array.from([ + 0x3a, 0x73, 0xf4, 0x12, 0x3a, 0x5c, 0xd2, 0x12, 0x1f, 0x21, 0xcd, 0x7e, + 0x8d, 0x35, 0x88, 0x35, 0x47, 0x69, 0x49, 0xd0, 0x35, 0xd9, 0xc2, 0xda, + 0x68, 0x06, 0xb4, 0x63, 0x3a, 0xc8, 0xc1, 0xe2, + ]), + // pubkey x + Uint8Array.from([ + 0xa0, 0x43, 0x4d, 0x9e, 0x47, 0xf3, 0xc8, 0x62, 0x35, 0x47, 0x7c, 0x7b, + 0x1a, 0xe6, 0xae, 0x5d, 0x34, 0x42, 0xd4, 0x9b, 0x19, 0x43, 0xc2, 0xb7, + 0x52, 0xa6, 0x8e, 0x2a, 0x47, 0xe2, 0x47, 0xc7, + ]), + // pubkey y + Uint8Array.from([ + 0x89, 0x3a, 0xba, 0x42, 0x54, 0x19, 0xbc, 0x27, 0xa3, 0xb6, 0xc7, 0xe6, + 0x93, 0xa2, 0x4c, 0x69, 0x6f, 0x79, 0x4c, 0x2e, 0xd8, 0x77, 0xa1, 0x59, + 0x3c, 0xbe, 0xe5, 0x3b, 0x03, 0x73, 0x68, 0xd7, + ]), + // signature + Uint8Array.from([ + 0xe5, 0x08, 0x1c, 0x80, 0xab, 0x42, 0x7d, 0xc3, 0x70, 0x34, 0x6f, 0x4a, + 0x0e, 0x31, 0xaa, 0x2b, 0xad, 0x8d, 0x97, 0x98, 0xc3, 0x80, 0x61, 0xdb, + 0x9a, 0xe5, 0x5a, 0x4e, 0x8d, 0xf4, 0x54, 0xfd, 0x28, 0x11, 0x98, 0x94, + 0x34, 0x4e, 0x71, 0xb7, 0x87, 0x70, 0xcc, 0x93, 0x1d, 0x61, 0xf4, 0x80, + 0xec, 0xbb, 0x0b, 0x89, 0xd6, 0xeb, 0x69, 0x69, 0x01, 0x61, 0xe4, 0x9a, + 0x71, 0x5f, 0xcd, 0x55, + ]), + ], + true, + ], + [ + [ + // hashed message + Uint8Array.from(Array(32).fill(0)), + // pubkey x + Uint8Array.from(Array(32).fill(0)), + // pubkey y + Uint8Array.from(Array(32).fill(0)), + // signature + Uint8Array.from(Array(64).fill(0)), + ], + false, + ], +]; + +export const ecdsa_secp256r1_test_cases: [ + [Uint8Array, Uint8Array, Uint8Array, Uint8Array], + boolean, +][] = [ + [ + [ + // hashed message + Uint8Array.from([ + 84, 112, 91, 163, 186, 175, 219, 223, 186, 140, 95, 154, 112, 247, 168, + 155, 238, 152, 217, 6, 181, 62, 49, 7, 77, 167, 186, 236, 220, 13, 169, + 173, + ]), + // pubkey x + Uint8Array.from([ + 85, 15, 71, 16, 3, 243, 223, 151, 195, 223, 80, 106, 199, 151, 246, 114, + 31, 177, 161, 251, 123, 143, 111, 131, 210, 36, 73, 138, 101, 200, 142, + 36, + ]), + // pubkey y + Uint8Array.from([ + 19, 96, 147, 215, 1, 46, 80, 154, 115, 113, 92, 189, 11, 0, 163, 204, + 15, 244, 181, 192, 27, 63, 250, 25, 106, 177, 251, 50, 112, 54, 184, + 230, + ]), + // signature + Uint8Array.from([ + 44, 112, 168, 208, 132, 182, 43, 252, 92, 224, 54, 65, 202, 249, 247, + 42, 212, 218, 140, 129, 191, 230, 236, 148, 135, 187, 94, 27, 239, 98, + 161, 50, 24, 173, 158, 226, 158, 175, 53, 31, 220, 80, 241, 82, 12, 66, + 94, 155, 144, 138, 7, 39, 139, 67, 176, 236, 123, 135, 39, 120, 193, 78, + 7, 132, + ]), + ], + true, + ], + [ + [ + // hashed message + Uint8Array.from(Array(32).fill(0)), + // pubkey x + Uint8Array.from(Array(32).fill(0)), + // pubkey y + Uint8Array.from(Array(32).fill(0)), + // signature + Uint8Array.from(Array(64).fill(0)), + ], + false, + ], +];