Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(avm): poseidon2_permutation as black box #5707

Merged
merged 1 commit into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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),
Copy link
Member

@Maddiaa0 Maddiaa0 Apr 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

much nicer than a constant for both, thanks!

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
Loading