From 1c80016b8d2b959f9eb77417388cb3074d30456a Mon Sep 17 00:00:00 2001 From: fcarreiro Date: Thu, 11 Apr 2024 16:41:42 +0000 Subject: [PATCH] feat: export poseidon2_permutation and add to foundation/crypto --- .../barretenberg/crypto/poseidon2/c_bind.cpp | 25 ++++++++++-- .../barretenberg/crypto/poseidon2/c_bind.hpp | 5 ++- barretenberg/exports.json | 20 +++++++++- barretenberg/scripts/exports.json | 0 .../ts/src/barretenberg/poseidon.test.ts | 6 +-- barretenberg/ts/src/barretenberg_api/index.ts | 40 +++++++++++++++---- .../src/crypto/poseidon/index.test.ts | 24 +++++++++++ .../foundation/src/crypto/poseidon/index.ts | 18 ++++++++- .../simulator/src/avm/avm_simulator.test.ts | 4 +- .../simulator/src/avm/opcodes/hashing.test.ts | 6 +-- .../simulator/src/avm/opcodes/hashing.ts | 4 +- 11 files changed, 125 insertions(+), 27 deletions(-) delete mode 100644 barretenberg/scripts/exports.json create mode 100644 yarn-project/foundation/src/crypto/poseidon/index.test.ts diff --git a/barretenberg/cpp/src/barretenberg/crypto/poseidon2/c_bind.cpp b/barretenberg/cpp/src/barretenberg/crypto/poseidon2/c_bind.cpp index 714b9456702a..da7cba0ee941 100644 --- a/barretenberg/cpp/src/barretenberg/crypto/poseidon2/c_bind.cpp +++ b/barretenberg/cpp/src/barretenberg/crypto/poseidon2/c_bind.cpp @@ -3,10 +3,11 @@ #include "barretenberg/common/serialize.hpp" #include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" #include "poseidon2.hpp" +#include "poseidon2_permutation.hpp" using namespace bb; -WASM_EXPORT void poseidon_hash(fr::vec_in_buf inputs_buffer, fr::out_buf output) +WASM_EXPORT void poseidon2_hash(fr::vec_in_buf inputs_buffer, fr::out_buf output) { std::vector to_hash; read(inputs_buffer, to_hash); @@ -14,7 +15,7 @@ WASM_EXPORT void poseidon_hash(fr::vec_in_buf inputs_buffer, fr::out_buf output) fr::serialize_to_buffer(r, output); } -WASM_EXPORT void poseidon_hashes(fr::vec_in_buf inputs_buffer, fr::out_buf output) +WASM_EXPORT void poseidon2_hashes(fr::vec_in_buf inputs_buffer, fr::out_buf output) { std::vector to_hash; read(inputs_buffer, to_hash); @@ -28,4 +29,22 @@ WASM_EXPORT void poseidon_hashes(fr::vec_in_buf inputs_buffer, fr::out_buf outpu ++count; } write(output, results); -} \ No newline at end of file +} + +WASM_EXPORT void poseidon2_permutation(fr::vec_in_buf inputs_buffer, fr::vec_out_buf output) +{ + using Permutation = crypto::Poseidon2Permutation; + + // Serialise input vector + std::vector to_permute; + read(inputs_buffer, to_permute); + + // Copy input vector into Permutation::State + Permutation::State input_state; + std::copy(to_permute.begin(), to_permute.end(), input_state.data()); + + Permutation::State results_array = Permutation::permutation(input_state); + + const std::vector results(results_array.begin(), results_array.end()); + *output = to_heap_buffer(results); +} diff --git a/barretenberg/cpp/src/barretenberg/crypto/poseidon2/c_bind.hpp b/barretenberg/cpp/src/barretenberg/crypto/poseidon2/c_bind.hpp index e113b523dd18..724ab507252d 100644 --- a/barretenberg/cpp/src/barretenberg/crypto/poseidon2/c_bind.hpp +++ b/barretenberg/cpp/src/barretenberg/crypto/poseidon2/c_bind.hpp @@ -7,6 +7,7 @@ extern "C" { using namespace bb; -WASM_EXPORT void poseidon_hash(fr::vec_in_buf inputs_buffer, fr::out_buf output); -WASM_EXPORT void poseidon_hashes(fr::vec_in_buf inputs_buffer, fr::out_buf output); +WASM_EXPORT void poseidon2_hash(fr::vec_in_buf inputs_buffer, fr::out_buf output); +WASM_EXPORT void poseidon2_hashes(fr::vec_in_buf inputs_buffer, fr::out_buf output); +WASM_EXPORT void poseidon2_permutation(fr::vec_in_buf inputs_buffer, fr::vec_out_buf output); } \ No newline at end of file diff --git a/barretenberg/exports.json b/barretenberg/exports.json index cffe8b8fd083..08ddefa86a5f 100644 --- a/barretenberg/exports.json +++ b/barretenberg/exports.json @@ -76,7 +76,7 @@ "isAsync": false }, { - "functionName": "poseidon_hash", + "functionName": "poseidon2_hash", "inArgs": [ { "name": "inputs_buffer", @@ -92,7 +92,7 @@ "isAsync": false }, { - "functionName": "poseidon_hashes", + "functionName": "poseidon2_hashes", "inArgs": [ { "name": "inputs_buffer", @@ -107,6 +107,22 @@ ], "isAsync": false }, + { + "functionName": "poseidon2_permutation", + "inArgs": [ + { + "name": "input_state", + "type": "fr::vec_in_buf" + } + ], + "outArgs": [ + { + "name": "output_state", + "type": "fr::vec_out_buf" + } + ], + "isAsync": false + }, { "functionName": "blake2s", "inArgs": [ diff --git a/barretenberg/scripts/exports.json b/barretenberg/scripts/exports.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/barretenberg/ts/src/barretenberg/poseidon.test.ts b/barretenberg/ts/src/barretenberg/poseidon.test.ts index 2647e4778ddc..dd27f70a018e 100644 --- a/barretenberg/ts/src/barretenberg/poseidon.test.ts +++ b/barretenberg/ts/src/barretenberg/poseidon.test.ts @@ -10,7 +10,7 @@ describe('poseidon sync', () => { }); it('poseidonHash', () => { - const result = api.poseidonHash([new Fr(4n), new Fr(8n)]); + const result = api.poseidon2Hash([new Fr(4n), new Fr(8n)]); expect(result).toMatchSnapshot(); }); @@ -19,7 +19,7 @@ describe('poseidon sync', () => { const fields = Array.from({ length: loops * 2 }).map(() => Fr.random()); const t = new Timer(); for (let i = 0; i < loops; ++i) { - api.poseidonHash([fields[i * 2], fields[i * 2 + 1]]); + api.poseidon2Hash([fields[i * 2], fields[i * 2 + 1]]); } const us = t.us() / loops; console.log(`Executed ${loops} hashes at an average ${us}us / hash`); @@ -31,7 +31,7 @@ describe('poseidon sync', () => { const fields = Array.from({ length: numHashesPerLoop * 2 }).map(() => Fr.random()); const t = new Timer(); for (let i = 0; i < loops; ++i) { - api.poseidonHashes(fields); + api.poseidon2Hashes(fields); } const us = t.us() / (numHashesPerLoop * loops); console.log(`Executed ${numHashesPerLoop * loops} hashes at an average ${us}us / hash`); diff --git a/barretenberg/ts/src/barretenberg_api/index.ts b/barretenberg/ts/src/barretenberg_api/index.ts index 05d6568cdb7d..967fc1b03a22 100644 --- a/barretenberg/ts/src/barretenberg_api/index.ts +++ b/barretenberg/ts/src/barretenberg_api/index.ts @@ -63,11 +63,11 @@ export class BarretenbergApi { return out[0]; } - async poseidonHash(inputsBuffer: Fr[]): Promise { + async poseidon2Hash(inputsBuffer: Fr[]): Promise { const inArgs = [inputsBuffer].map(serializeBufferable); const outTypes: OutputType[] = [Fr]; const result = await this.wasm.callWasmExport( - 'poseidon_hash', + 'poseidon2_hash', inArgs, outTypes.map(t => t.SIZE_IN_BYTES), ); @@ -75,11 +75,23 @@ export class BarretenbergApi { return out[0]; } - async poseidonHashes(inputsBuffer: Fr[]): Promise { + async poseidon2Hashes(inputsBuffer: Fr[]): Promise { const inArgs = [inputsBuffer].map(serializeBufferable); const outTypes: OutputType[] = [Fr]; const result = await this.wasm.callWasmExport( - 'poseidon_hashes', + 'poseidon2_hashes', + inArgs, + outTypes.map(t => t.SIZE_IN_BYTES), + ); + const out = result.map((r, i) => outTypes[i].fromBuffer(r)); + return out[0]; + } + + async poseidon2Permutation(inputState: Fr[]): Promise { + const inArgs = [inputState].map(serializeBufferable); + const outTypes: OutputType[] = [VectorDeserializer(Fr)]; + const result = await this.wasm.callWasmExport( + 'poseidon2_permutation', inArgs, outTypes.map(t => t.SIZE_IN_BYTES), ); @@ -607,11 +619,11 @@ export class BarretenbergApiSync { return out[0]; } - poseidonHash(inputsBuffer: Fr[]): Fr { + poseidon2Hash(inputsBuffer: Fr[]): Fr { const inArgs = [inputsBuffer].map(serializeBufferable); const outTypes: OutputType[] = [Fr]; const result = this.wasm.callWasmExport( - 'poseidon_hash', + 'poseidon2_hash', inArgs, outTypes.map(t => t.SIZE_IN_BYTES), ); @@ -619,11 +631,23 @@ export class BarretenbergApiSync { return out[0]; } - poseidonHashes(inputsBuffer: Fr[]): Fr { + poseidon2Hashes(inputsBuffer: Fr[]): Fr { const inArgs = [inputsBuffer].map(serializeBufferable); const outTypes: OutputType[] = [Fr]; const result = this.wasm.callWasmExport( - 'poseidon_hashes', + 'poseidon2_hashes', + inArgs, + outTypes.map(t => t.SIZE_IN_BYTES), + ); + const out = result.map((r, i) => outTypes[i].fromBuffer(r)); + return out[0]; + } + + poseidon2Permutation(inputState: Fr[]): Fr[] { + const inArgs = [inputState].map(serializeBufferable); + const outTypes: OutputType[] = [VectorDeserializer(Fr)]; + const result = this.wasm.callWasmExport( + 'poseidon2_permutation', inArgs, outTypes.map(t => t.SIZE_IN_BYTES), ); diff --git a/yarn-project/foundation/src/crypto/poseidon/index.test.ts b/yarn-project/foundation/src/crypto/poseidon/index.test.ts new file mode 100644 index 000000000000..6bd2354b21cd --- /dev/null +++ b/yarn-project/foundation/src/crypto/poseidon/index.test.ts @@ -0,0 +1,24 @@ +import { Fr } from '../../fields/fields.js'; +import { poseidon2Permutation } from './index.js'; + +describe('poseidon2Permutation', () => { + it('test vectors from cpp should match', () => { + const init = [0, 1, 2, 3].map(i => new Fr(i)); + expect(poseidon2Permutation(init)).toEqual([ + new Fr(0x01bd538c2ee014ed5141b29e9ae240bf8db3fe5b9a38629a9647cf8d76c01737n), + new Fr(0x239b62e7db98aa3a2a8f6a0d2fa1709e7a35959aa6c7034814d9daa90cbac662n), + new Fr(0x04cbb44c61d928ed06808456bf758cbf0c18d1e15a7b6dbc8245fa7515d5e3cbn), + new Fr(0x2e11c5cff2a22c64d01304b778d78f6998eff1ab73163a35603f54794c30847an), + ]); + }); + + it('test vectors from Noir should match', () => { + const init = [1n, 2n, 3n, 0x0a0000000000000000n].map(i => new Fr(i)); + expect(poseidon2Permutation(init)).toEqual([ + new Fr(0x0369007aa630f5dfa386641b15416ecb16fb1a6f45b1acb903cb986b221a891cn), + new Fr(0x1919fd474b4e2e0f8e0cf8ca98ef285675781cbd31aa4807435385d28e4c02a5n), + new Fr(0x0810e7e9a1c236aae4ebff7d3751d9f7346dc443d1de863977d2b81fe8c557f4n), + new Fr(0x1f4a188575e29985b6f8ad03afc1f0759488f8835aafb6e19e06160fb64d3d4an), + ]); + }); +}); diff --git a/yarn-project/foundation/src/crypto/poseidon/index.ts b/yarn-project/foundation/src/crypto/poseidon/index.ts index 8f77b5802115..723570504a45 100644 --- a/yarn-project/foundation/src/crypto/poseidon/index.ts +++ b/yarn-project/foundation/src/crypto/poseidon/index.ts @@ -1,17 +1,31 @@ import { BarretenbergSync, Fr as FrBarretenberg } from '@aztec/bb.js'; +import { strict as assert } from 'assert'; + import { Fr } from '../../fields/fields.js'; /** * Create a poseidon hash (field) from an array of input fields. * Left pads any inputs less than 32 bytes. */ -export function poseidonHash(input: Buffer[]): Fr { +export function poseidon2Hash(input: Buffer[]): Fr { return Fr.fromBuffer( Buffer.from( BarretenbergSync.getSingleton() - .poseidonHash(input.map(i => new FrBarretenberg(i))) + .poseidon2Hash(input.map(i => new FrBarretenberg(i))) .toBuffer(), ), ); } + +/** + * Runs a Poseidon2 permutation. + * @param input the input state. Expected to be of size 4. + * @returns the output state, size 4. + */ +export function poseidon2Permutation(input: Fr[]): Fr[] { + assert(input.length === 4, 'Input state must be of size 4'); + const res = BarretenbergSync.getSingleton().poseidon2Permutation(input.map(i => new FrBarretenberg(i.toBuffer()))); + assert(res.length === 4, 'Output state must be of size 4'); + return res.map(o => Fr.fromBuffer(Buffer.from(o.toBuffer()))); +} diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 135eb7b75f73..fb19d1aca511 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -2,7 +2,7 @@ import { UnencryptedL2Log } from '@aztec/circuit-types'; import { computeVarArgsHash } from '@aztec/circuits.js/hash'; import { EventSelector, FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { keccak, pedersenHash, poseidonHash, sha256 } from '@aztec/foundation/crypto'; +import { keccak, pedersenHash, poseidon2Hash, sha256 } from '@aztec/foundation/crypto'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { AvmTestContractArtifact } from '@aztec/noir-contracts.js'; @@ -138,7 +138,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { }); describe.each([ - ['poseidon_hash', poseidonHash], + ['poseidon_hash', poseidon2Hash], ['pedersen_hash', pedersenHash], ['pedersen_hash_with_index', (m: Buffer[]) => pedersenHash(m, 20)], ])('Hashes with field returned in noir contracts', (name: string, hashFunction: (data: Buffer[]) => Fr) => { diff --git a/yarn-project/simulator/src/avm/opcodes/hashing.test.ts b/yarn-project/simulator/src/avm/opcodes/hashing.test.ts index 42e6d690e48b..d8d18c94de5e 100644 --- a/yarn-project/simulator/src/avm/opcodes/hashing.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/hashing.test.ts @@ -1,4 +1,4 @@ -import { keccak, pedersenHash, poseidonHash, sha256 } from '@aztec/foundation/crypto'; +import { keccak, pedersenHash, poseidon2Hash, sha256 } from '@aztec/foundation/crypto'; import { type AvmContext } from '../avm_context.js'; import { Field, Uint32 } from '../avm_memory_types.js'; @@ -41,7 +41,7 @@ describe('Hashing Opcodes', () => { const dstOffset = 3; - const expectedHash = poseidonHash(args.map(field => field.toBuffer())); + const expectedHash = poseidon2Hash(args.map(field => field.toBuffer())); await new Poseidon2(indirect, dstOffset, messageOffset, args.length).execute(context); const result = context.machineState.memory.get(dstOffset); @@ -62,7 +62,7 @@ describe('Hashing Opcodes', () => { const dstOffset = 3; - const expectedHash = poseidonHash(args.map(field => field.toBuffer())); + const expectedHash = poseidon2Hash(args.map(field => field.toBuffer())); await new Poseidon2(indirect, dstOffset, messageOffset, args.length).execute(context); const result = context.machineState.memory.get(dstOffset); diff --git a/yarn-project/simulator/src/avm/opcodes/hashing.ts b/yarn-project/simulator/src/avm/opcodes/hashing.ts index 31c5f7ac54cb..2ddf561ea82b 100644 --- a/yarn-project/simulator/src/avm/opcodes/hashing.ts +++ b/yarn-project/simulator/src/avm/opcodes/hashing.ts @@ -1,5 +1,5 @@ import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; -import { keccak, pedersenHash, poseidonHash, sha256 } from '@aztec/foundation/crypto'; +import { keccak, pedersenHash, poseidon2Hash, sha256 } from '@aztec/foundation/crypto'; import { type AvmContext } from '../avm_context.js'; import { Field } from '../avm_memory_types.js'; @@ -43,7 +43,7 @@ export class Poseidon2 extends Instruction { // Memory pointer will be indirect const hashData = memory.getSlice(messageOffset, this.messageSize).map(word => word.toBuffer()); - const hash = poseidonHash(hashData); + const hash = poseidon2Hash(hashData); memory.set(dstOffset, new Field(hash)); memory.assert(memoryOperations);