Skip to content

Commit

Permalink
feat(avm): poseidon2_permutation as black box (#5707)
Browse files Browse the repository at this point in the history
  • Loading branch information
fcarreiro authored Apr 12, 2024
1 parent 2522294 commit 5526b36
Show file tree
Hide file tree
Showing 11 changed files with 99 additions and 141 deletions.
4 changes: 2 additions & 2 deletions avm-transpiler/src/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -160,7 +160,7 @@ impl AvmOpcode {

// Gadgets
AvmOpcode::KECCAK => "KECCAK",
AvmOpcode::POSEIDON => "POSEIDON",
AvmOpcode::POSEIDON2 => "POSEIDON2",
AvmOpcode::SHA256 => "SHA256 ",
AvmOpcode::PEDERSEN => "PEDERSEN",
}
Expand Down
90 changes: 28 additions & 62 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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<AvmInstruction>,
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.
///
Expand Down Expand Up @@ -933,10 +875,34 @@ fn handle_black_box_function(avm_instrs: &mut Vec<AvmInstruction>, 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
Expand Down
3 changes: 0 additions & 3 deletions noir-projects/aztec-nr/aztec/src/avm/hash.nr
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
#[oracle(avmOpcodeKeccak256)]
pub fn keccak256<N>(input: [Field; N]) -> [Field; 2] {}

#[oracle(avmOpcodePoseidon)]
pub fn poseidon<N>(input: [Field; N]) -> Field {}

#[oracle(avmOpcodeSha256)]
pub fn sha256<N>(input: [Field; N]) -> [Field; 2] {}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}

/************************************************************************
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/foundation/src/crypto/poseidon/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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));
const init = [0, 1, 2, 3];
expect(poseidon2Permutation(init)).toEqual([
new Fr(0x01bd538c2ee014ed5141b29e9ae240bf8db3fe5b9a38629a9647cf8d76c01737n),
new Fr(0x239b62e7db98aa3a2a8f6a0d2fa1709e7a35959aa6c7034814d9daa90cbac662n),
Expand All @@ -13,7 +13,7 @@ describe('poseidon2Permutation', () => {
});

it('test vectors from Noir should match', () => {
const init = [1n, 2n, 3n, 0x0a0000000000000000n].map(i => new Fr(i));
const init = [1n, 2n, 3n, 0x0a0000000000000000n];
expect(poseidon2Permutation(init)).toEqual([
new Fr(0x0369007aa630f5dfa386641b15416ecb16fb1a6f45b1acb903cb986b221a891cn),
new Fr(0x1919fd474b4e2e0f8e0cf8ca98ef285675781cbd31aa4807435385d28e4c02a5n),
Expand Down
7 changes: 5 additions & 2 deletions yarn-project/foundation/src/crypto/poseidon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ export function poseidon2Hash(input: Fieldable[], _index = 0): Fr {
* @param input the input state. Expected to be of size 4.
* @returns the output state, size 4.
*/
export function poseidon2Permutation(input: Fr[]): Fr[] {
export function poseidon2Permutation(input: Fieldable[]): Fr[] {
const inputFields = serializeToFields(input);
// We'd like this assertion but it's not possible to use it in the browser.
// assert(input.length === 4, 'Input state must be of size 4');
const res = BarretenbergSync.getSingleton().poseidon2Permutation(input.map(i => new FrBarretenberg(i.toBuffer())));
const res = BarretenbergSync.getSingleton().poseidon2Permutation(
inputFields.map(i => new FrBarretenberg(i.toBuffer())),
);
// We'd like this assertion but it's not possible to use it in the browser.
// assert(res.length === 4, 'Output state must be of size 4');
return res.map(o => Fr.fromBuffer(Buffer.from(o.toBuffer())));
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/simulator/src/avm/avm_gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const GasCosts: Record<Opcode, Gas | typeof DynamicGasCost> = {
[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
};
Expand Down
6 changes: 3 additions & 3 deletions yarn-project/simulator/src/avm/avm_simulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,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 }) });
Expand All @@ -139,12 +139,12 @@ describe('AVM simulator: transpiled Noir contracts', () => {
});

describe.each([
['poseidon_hash', poseidon2Hash],
['poseidon2_hash', poseidon2Hash],
['pedersen_hash', pedersenHash],
['pedersen_hash_with_index', (m: Fieldable[]) => pedersenHash(m, 20)],
])('Hashes with field returned in noir contracts', (name: string, hashFunction: (data: Fieldable[]) => 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);

const context = initContext({ env: initExecutionEnvironment({ calldata }) });
Expand Down
75 changes: 36 additions & 39 deletions yarn-project/simulator/src/avm/opcodes/hashing.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { keccak, pedersenHash, poseidon2Hash, sha256 } from '@aztec/foundation/crypto';
import { keccak, pedersenHash, sha256 } from '@aztec/foundation/crypto';

import { type AvmContext } from '../avm_context.js';
import { Field, Uint32 } from '../avm_memory_types.js';
Expand All @@ -18,55 +18,52 @@ 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);
});

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);
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);
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),
]);
});
});

Expand Down
Loading

0 comments on commit 5526b36

Please sign in to comment.