diff --git a/avm-transpiler/src/opcodes.rs b/avm-transpiler/src/opcodes.rs index c09bc4b5cb51..38d26fa31f1c 100644 --- a/avm-transpiler/src/opcodes.rs +++ b/avm-transpiler/src/opcodes.rs @@ -70,7 +70,7 @@ pub enum AvmOpcode { REVERT, // Gadgets KECCAK, - POSEIDON, + POSEIDON2, SHA256, // temp - may be removed, but alot of contracts rely on it PEDERSEN, // temp - may be removed, but alot of contracts rely on it } @@ -160,7 +160,7 @@ impl AvmOpcode { // Gadgets AvmOpcode::KECCAK => "KECCAK", - AvmOpcode::POSEIDON => "POSEIDON", + AvmOpcode::POSEIDON2 => "POSEIDON2", AvmOpcode::SHA256 => "SHA256 ", AvmOpcode::PEDERSEN => "PEDERSEN", } diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 9a1c5d9ae916..34c3388b9246 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -295,9 +295,6 @@ fn handle_foreign_call( "avmOpcodeKeccak256" | "avmOpcodeSha256" => { handle_2_field_hash_instruction(avm_instrs, function, destinations, inputs) } - "avmOpcodePoseidon" => { - handle_single_field_hash_instruction(avm_instrs, function, destinations, inputs) - } "avmOpcodeGetContractInstance" => { handle_get_contract_instance(avm_instrs, destinations, inputs) } @@ -707,61 +704,6 @@ fn handle_2_field_hash_instruction( }); } -/// A single field hash instruction includes hash functions that emit a single field element -/// directly onto the stack. -/// -/// This includes (snark friendly functions): -/// - poseidon2 -/// -/// Pedersen is not implemented this way as the black box function representation has the correct api. -/// As the Poseidon BBF only deals with a single permutation, it is not quite suitable for our current avm -/// representation. -fn handle_single_field_hash_instruction( - avm_instrs: &mut Vec, - function: &str, - destinations: &[ValueOrArray], - inputs: &[ValueOrArray], -) { - // handle field returns differently - let message_offset_maybe = inputs[0]; - let (message_offset, message_size) = match message_offset_maybe { - ValueOrArray::HeapArray(HeapArray { pointer, size }) => (pointer.0, size), - _ => panic!("Poseidon address inputs destination should be a single value"), - }; - - assert!(destinations.len() == 1); - let dest_offset_maybe = destinations[0]; - let dest_offset = match dest_offset_maybe { - ValueOrArray::MemoryAddress(dest_offset) => dest_offset.0, - _ => panic!("Poseidon address destination should be a single value"), - }; - - let opcode = match function { - "avmOpcodePoseidon" => AvmOpcode::POSEIDON, - _ => panic!( - "Transpiler doesn't know how to process ForeignCall function {:?}", - function - ), - }; - - avm_instrs.push(AvmInstruction { - opcode, - indirect: Some(FIRST_OPERAND_INDIRECT), - operands: vec![ - AvmOperand::U32 { - value: dest_offset as u32, - }, - AvmOperand::U32 { - value: message_offset as u32, - }, - AvmOperand::U32 { - value: message_size as u32, - }, - ], - ..Default::default() - }); -} - /// Getter Instructions are instructions that take NO inputs, and return information /// from the current execution context. /// @@ -933,10 +875,34 @@ fn handle_black_box_function(avm_instrs: &mut Vec, operation: &B ..Default::default() }); } - _ => panic!( - "Transpiler doesn't know how to process BlackBoxOp {:?}", - operation - ), + BlackBoxOp::Poseidon2Permutation { + message, + output, + len: _, // we don't use this. + } => { + // We'd love to validate the input size, but it's not known at compile time. + assert_eq!( + output.size, 4, + "Poseidon2Permutation output size must be 4!" + ); + let input_state_offset = message.pointer.0; + let output_state_offset = output.pointer.0; + + avm_instrs.push(AvmInstruction { + opcode: AvmOpcode::POSEIDON2, + indirect: Some(ZEROTH_OPERAND_INDIRECT | FIRST_OPERAND_INDIRECT), + operands: vec![ + AvmOperand::U32 { + value: input_state_offset as u32, + }, + AvmOperand::U32 { + value: output_state_offset as u32, + }, + ], + ..Default::default() + }); + } + _ => panic!("Transpiler doesn't know how to process {:?}", operation), } } /// Emit a storage write opcode diff --git a/noir-projects/aztec-nr/aztec/src/avm/hash.nr b/noir-projects/aztec-nr/aztec/src/avm/hash.nr index a8cd3b9c6843..3035cef0ac87 100644 --- a/noir-projects/aztec-nr/aztec/src/avm/hash.nr +++ b/noir-projects/aztec-nr/aztec/src/avm/hash.nr @@ -1,8 +1,5 @@ #[oracle(avmOpcodeKeccak256)] pub fn keccak256(input: [Field; N]) -> [Field; 2] {} -#[oracle(avmOpcodePoseidon)] -pub fn poseidon(input: [Field; N]) -> Field {} - #[oracle(avmOpcodeSha256)] pub fn sha256(input: [Field; N]) -> [Field; 2] {} diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index 22b28eaae184..b7efa77cbbe3 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -32,7 +32,7 @@ contract AvmTest { use dep::compressed_string::CompressedString; // avm lib - use dep::aztec::avm::hash::{keccak256, poseidon, sha256}; + use dep::aztec::avm::hash::{keccak256, sha256}; #[aztec(storage)] struct Storage { @@ -145,28 +145,28 @@ contract AvmTest { * Hashing functions ************************************************************************/ #[aztec(public-vm)] - fn keccak_hash(data: [Field; 3]) -> pub [Field; 2] { + fn keccak_hash(data: [Field; 10]) -> pub [Field; 2] { keccak256(data) } #[aztec(public-vm)] - fn poseidon_hash(data: [Field; 3]) -> pub Field { - poseidon(data) + fn poseidon2_hash(data: [Field; 10]) -> pub Field { + dep::std::hash::poseidon2::Poseidon2::hash(data, data.len()) } #[aztec(public-vm)] - fn sha256_hash(data: [Field; 3]) -> pub [Field; 2] { + fn sha256_hash(data: [Field; 10]) -> pub [Field; 2] { sha256(data) } #[aztec(public-vm)] - fn pedersen_hash(data: [Field; 3]) -> pub Field { + fn pedersen_hash(data: [Field; 10]) -> pub Field { dep::std::hash::pedersen_hash(data) } #[aztec(public-vm)] - fn pedersen_hash_with_index(data: [Field; 3]) -> pub Field { - dep::std::hash::pedersen_hash_with_separator(data, 20) + fn pedersen_hash_with_index(data: [Field; 10]) -> pub Field { + dep::std::hash::pedersen_hash_with_separator(data, /*index=*/ 20) } /************************************************************************ diff --git a/yarn-project/simulator/src/avm/avm_gas.ts b/yarn-project/simulator/src/avm/avm_gas.ts index c6923d35484b..5a80d70baf97 100644 --- a/yarn-project/simulator/src/avm/avm_gas.ts +++ b/yarn-project/simulator/src/avm/avm_gas.ts @@ -127,7 +127,7 @@ export const GasCosts: Record = { [Opcode.REVERT]: TemporaryDefaultGasCost, // Gadgets [Opcode.KECCAK]: TemporaryDefaultGasCost, - [Opcode.POSEIDON]: TemporaryDefaultGasCost, + [Opcode.POSEIDON2]: TemporaryDefaultGasCost, [Opcode.SHA256]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost, [Opcode.PEDERSEN]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost,t }; diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 480f823d2100..cd1d28652770 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -125,7 +125,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { ['keccak_hash', keccak], ])('Hashes with 2 fields returned in noir contracts', (name: string, hashFunction: (data: Buffer) => Buffer) => { it(`Should execute contract function that performs ${name} hash`, async () => { - const calldata = [new Fr(1), new Fr(2), new Fr(3)]; + const calldata = [...Array(10)].map(_ => Fr.random()); const hash = hashFunction(Buffer.concat(calldata.map(f => f.toBuffer()))); const context = initContext({ env: initExecutionEnvironment({ calldata }) }); @@ -138,12 +138,12 @@ describe('AVM simulator: transpiled Noir contracts', () => { }); describe.each([ - ['poseidon_hash', poseidon2Hash], + ['poseidon2_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) => { it(`Should execute contract function that performs ${name} hash`, async () => { - const calldata = [new Fr(1), new Fr(2), new Fr(3)]; + const calldata = [...Array(10)].map(_ => Fr.random()); const hash = hashFunction(calldata.map(f => f.toBuffer())); const context = initContext({ env: initExecutionEnvironment({ calldata }) }); diff --git a/yarn-project/simulator/src/avm/opcodes/hashing.test.ts b/yarn-project/simulator/src/avm/opcodes/hashing.test.ts index d8d18c94de5e..05b9b749288f 100644 --- a/yarn-project/simulator/src/avm/opcodes/hashing.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/hashing.test.ts @@ -1,4 +1,5 @@ -import { keccak, pedersenHash, poseidon2Hash, sha256 } from '@aztec/foundation/crypto'; +import { Fr } from '@aztec/circuits.js'; +import { keccak, pedersenHash, sha256 } from '@aztec/foundation/crypto'; import { type AvmContext } from '../avm_context.js'; import { Field, Uint32 } from '../avm_memory_types.js'; @@ -18,16 +19,10 @@ describe('Hashing Opcodes', () => { const buf = Buffer.from([ Poseidon2.opcode, // opcode 1, // indirect - ...Buffer.from('12345678', 'hex'), // dstOffset - ...Buffer.from('23456789', 'hex'), // messageOffset - ...Buffer.from('3456789a', 'hex'), // hashSize + ...Buffer.from('12345678', 'hex'), // inputStateOffset + ...Buffer.from('23456789', 'hex'), // outputStateOffset ]); - const inst = new Poseidon2( - /*indirect=*/ 1, - /*dstOffset=*/ 0x12345678, - /*messageOffset=*/ 0x23456789, - /*hashSize=*/ 0x3456789a, - ); + const inst = new Poseidon2(/*indirect=*/ 1, /*dstOffset=*/ 0x12345678, /*messageOffset=*/ 0x23456789); expect(Poseidon2.deserialize(buf)).toEqual(inst); expect(inst.serialize()).toEqual(buf); @@ -35,38 +30,41 @@ describe('Hashing Opcodes', () => { it('Should hash correctly - direct', async () => { const indirect = 0; - const args = [new Field(1n), new Field(2n), new Field(3n)]; - const messageOffset = 0; - context.machineState.memory.setSlice(messageOffset, args); - - const dstOffset = 3; - - 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); - expect(result).toEqual(new Field(expectedHash)); + const inputState = [new Field(1n), new Field(2n), new Field(3n), new Field(4n)]; + const inputStateOffset = 0; + const outputStateOffset = 0; + context.machineState.memory.setSlice(inputStateOffset, inputState); + + await new Poseidon2(indirect, inputStateOffset, outputStateOffset).execute(context); + + const result = context.machineState.memory.getSlice(outputStateOffset, 4); + expect(result).toEqual([ + new Field(0x224785a48a72c75e2cbb698143e71d5d41bd89a2b9a7185871e39a54ce5785b1n), + new Field(0x225bb800db22c4f4b09ace45cb484d42b0dd7dfe8708ee26aacde6f2c1fb2cb8n), + new Field(0x1180f4260e60b4264c987b503075ea8374b53ed06c5145f8c21c2aadb5087d21n), + new Field(0x16c877b5b9c04d873218804ccbf65d0eeb12db447f66c9ca26fec380055df7e9n), + ]); }); it('Should hash correctly - indirect', async () => { - const args = [new Field(1n), new Field(2n), new Field(3n)]; - const indirect = new Addressing([ - /*dstOffset=*/ AddressingMode.DIRECT, - /*messageOffset*/ AddressingMode.INDIRECT, - ]).toWire(); - const messageOffset = 0; - const realLocation = 4; - - context.machineState.memory.set(messageOffset, new Uint32(realLocation)); - context.machineState.memory.setSlice(realLocation, args); - - const dstOffset = 3; - - 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); - expect(result).toEqual(new Field(expectedHash)); + const indirect = new Addressing([AddressingMode.INDIRECT, AddressingMode.INDIRECT]).toWire(); + const inputState = [new Field(1n), new Field(2n), new Field(3n), new Field(4n)]; + const inputStateOffset = 0; + const inputStateOffsetReal = 10; + const outputStateOffset = 0; + const outputStateOffsetReal = 10; + context.machineState.memory.set(inputStateOffset, new Uint32(inputStateOffsetReal)); + context.machineState.memory.setSlice(inputStateOffsetReal, inputState); + + await new Poseidon2(indirect, inputStateOffset, outputStateOffset).execute(context); + + const result = context.machineState.memory.getSlice(outputStateOffsetReal, 4); + expect(result).toEqual([ + new Field(0x224785a48a72c75e2cbb698143e71d5d41bd89a2b9a7185871e39a54ce5785b1n), + new Field(0x225bb800db22c4f4b09ace45cb484d42b0dd7dfe8708ee26aacde6f2c1fb2cb8n), + new Field(0x1180f4260e60b4264c987b503075ea8374b53ed06c5145f8c21c2aadb5087d21n), + new Field(0x16c877b5b9c04d873218804ccbf65d0eeb12db447f66c9ca26fec380055df7e9n), + ]); }); }); diff --git a/yarn-project/simulator/src/avm/opcodes/hashing.ts b/yarn-project/simulator/src/avm/opcodes/hashing.ts index 2ddf561ea82b..68020ca92b3d 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, poseidon2Hash, sha256 } from '@aztec/foundation/crypto'; +import { keccak, pedersenHash, poseidon2Permutation, sha256 } from '@aztec/foundation/crypto'; import { type AvmContext } from '../avm_context.js'; import { Field } from '../avm_memory_types.js'; @@ -9,7 +9,8 @@ import { Instruction } from './instruction.js'; export class Poseidon2 extends Instruction { static type: string = 'POSEIDON2'; - static readonly opcode: Opcode = Opcode.POSEIDON; + static readonly opcode: Opcode = Opcode.POSEIDON2; + static readonly stateSize = 4; // Informs (de)serialization. See Instruction.deserialize. static readonly wireFormat: OperandType[] = [ @@ -17,34 +18,28 @@ export class Poseidon2 extends Instruction { OperandType.UINT8, OperandType.UINT32, OperandType.UINT32, - OperandType.UINT32, ]; - constructor( - private indirect: number, - private dstOffset: number, - private messageOffset: number, - private messageSize: number, - ) { + constructor(private indirect: number, private inputStateOffset: number, private outputStateOffset: number) { super(); } public async execute(context: AvmContext): Promise { - const memoryOperations = { reads: this.messageSize, writes: 1, indirect: this.indirect }; + const memoryOperations = { reads: Poseidon2.stateSize, writes: Poseidon2.stateSize, indirect: this.indirect }; const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - // We hash a set of field elements - const [dstOffset, messageOffset] = Addressing.fromWire(this.indirect).resolve( - [this.dstOffset, this.messageOffset], + const [inputOffset, outputOffset] = Addressing.fromWire(this.indirect).resolve( + [this.inputStateOffset, this.outputStateOffset], memory, ); - // Memory pointer will be indirect - const hashData = memory.getSlice(messageOffset, this.messageSize).map(word => word.toBuffer()); - - const hash = poseidon2Hash(hashData); - memory.set(dstOffset, new Field(hash)); + const inputState = memory.getSlice(inputOffset, Poseidon2.stateSize).map(word => word.toFr()); + const outputState = poseidon2Permutation(inputState); + memory.setSlice( + outputOffset, + outputState.map(word => new Field(word)), + ); memory.assert(memoryOperations); context.machineState.incrementPc(); diff --git a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts index 3957f5965b02..c2e092e1a5b9 100644 --- a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts +++ b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts @@ -75,7 +75,7 @@ export enum Opcode { REVERT, // Gadgets KECCAK, - POSEIDON, + POSEIDON2, SHA256, // temp - may be removed, but alot of contracts rely on it PEDERSEN, // temp - may be removed, but alot of contracts rely on it }