Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fcarreiro committed Apr 12, 2024
1 parent 6b91e27 commit 945ddfd
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 137 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
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
31 changes: 13 additions & 18 deletions yarn-project/simulator/src/avm/opcodes/hashing.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -9,42 +9,37 @@ 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[] = [
OperandType.UINT8,
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<void> {
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);

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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 945ddfd

Please sign in to comment.