Skip to content

Commit

Permalink
feat(avm-simulator): keccakf1600 opcode (#7545)
Browse files Browse the repository at this point in the history
  • Loading branch information
fcarreiro authored Jul 22, 2024
1 parent 55fb93d commit b81c503
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 9 deletions.
6 changes: 6 additions & 0 deletions avm-transpiler/src/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ pub enum AvmOpcode {
MSM,
// Conversions
TORADIXLE,
// Other
SHA256COMPRESSION,
KECCAKF1600,
}

impl AvmOpcode {
Expand Down Expand Up @@ -169,6 +172,9 @@ impl AvmOpcode {
AvmOpcode::MSM => "MSM",
// Conversions
AvmOpcode::TORADIXLE => "TORADIXLE",
// Other
AvmOpcode::SHA256COMPRESSION => "SHA256COMPRESSION",
AvmOpcode::KECCAKF1600 => "KECCAKF1600",
}
}
}
17 changes: 17 additions & 0 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,23 @@ fn handle_black_box_function(avm_instrs: &mut Vec<AvmInstruction>, operation: &B
..Default::default()
});
}
BlackBoxOp::Keccakf1600 { message, output } => {
let message_offset = message.pointer.0;
let message_size_offset = message.size.0;
let dest_offset = output.pointer.0;
assert_eq!(output.size, 25, "Keccakf1600 output size must be 25!");

avm_instrs.push(AvmInstruction {
opcode: AvmOpcode::KECCAKF1600,
indirect: Some(ZEROTH_OPERAND_INDIRECT | FIRST_OPERAND_INDIRECT),
operands: vec![
AvmOperand::U32 { value: dest_offset as u32 },
AvmOperand::U32 { value: message_offset as u32 },
AvmOperand::U32 { value: message_size_offset as u32 },
],
..Default::default()
});
}
BlackBoxOp::ToRadix { input, radix, output } => {
let num_limbs = output.size as u32;
let input_offset = input.0 as u32;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ contract AvmTest {
std::hash::keccak256(data, data.len() as u32)
}

#[aztec(public)]
fn keccak_f1600(data: [u64; 25]) -> [u64; 25] {
std::hash::keccak::keccakf1600(data)
}

#[aztec(public)]
fn poseidon2_hash(data: [Field; 10]) -> Field {
std::hash::poseidon2::Poseidon2::hash(data, data.len())
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/simulator/src/avm/avm_gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ const BaseGasCosts: Record<Opcode, Gas> = {
[Opcode.MSM]: DefaultBaseGasCost,
// Conversions
[Opcode.TORADIXLE]: DefaultBaseGasCost,
// Other
[Opcode.SHA256COMPRESSION]: DefaultBaseGasCost,
[Opcode.KECCAKF1600]: DefaultBaseGasCost,
};

/** Returns the fixed base gas cost for a given opcode. */
Expand Down
10 changes: 8 additions & 2 deletions yarn-project/simulator/src/avm/avm_simulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Grumpkin } from '@aztec/circuits.js/barretenberg';
import { computeVarArgsHash } from '@aztec/circuits.js/hash';
import { FunctionSelector } from '@aztec/foundation/abi';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { keccak256, pedersenHash, poseidon2Hash, sha256 } from '@aztec/foundation/crypto';
import { keccak256, keccakf1600, pedersenHash, poseidon2Hash, sha256 } from '@aztec/foundation/crypto';
import { Fq, Fr } from '@aztec/foundation/fields';
import { type Fieldable } from '@aztec/foundation/serialize';

Expand All @@ -13,7 +13,7 @@ import { mock } from 'jest-mock-extended';
import { type PublicSideEffectTraceInterface } from '../public/side_effect_trace_interface.js';
import { type AvmContext } from './avm_context.js';
import { type AvmExecutionEnvironment } from './avm_execution_environment.js';
import { type MemoryValue, TypeTag, type Uint8 } from './avm_memory_types.js';
import { type MemoryValue, TypeTag, type Uint8, type Uint64 } from './avm_memory_types.js';
import { AvmSimulator } from './avm_simulator.js';
import { isAvmBytecode, markBytecodeAsAvm } from './bytecode_utils.js';
import {
Expand All @@ -27,6 +27,7 @@ import {
initPersistableStateManager,
randomMemoryBytes,
randomMemoryFields,
randomMemoryUint64s,
} from './fixtures/index.js';
import { type HostStorage } from './journal/host_storage.js';
import { type AvmPersistableStateManager } from './journal/journal.js';
Expand Down Expand Up @@ -219,6 +220,7 @@ describe('AVM simulator: transpiled Noir contracts', () => {
describe.each([
['sha256_hash', /*input=*/ randomMemoryBytes(10), /*output=*/ sha256FromMemoryBytes],
['keccak_hash', /*input=*/ randomMemoryBytes(10), /*output=*/ keccak256FromMemoryBytes],
['keccak_f1600', /*input=*/ randomMemoryUint64s(25), /*output=*/ keccakF1600FromMemoryUint64s],
['poseidon2_hash', /*input=*/ randomMemoryFields(10), /*output=*/ poseidon2FromMemoryFields],
['pedersen_hash', /*input=*/ randomMemoryFields(10), /*output=*/ pedersenFromMemoryFields],
['pedersen_hash_with_index', /*input=*/ randomMemoryFields(10), /*output=*/ indexedPedersenFromMemoryFields],
Expand Down Expand Up @@ -883,6 +885,10 @@ function keccak256FromMemoryBytes(bytes: Uint8[]): Fr[] {
return [...keccak256(Buffer.concat(bytes.map(b => b.toBuffer())))].map(b => new Fr(b));
}

function keccakF1600FromMemoryUint64s(mem: Uint64[]): Fr[] {
return [...keccakf1600(mem.map(u => u.toBigInt()))].map(b => new Fr(b));
}

function poseidon2FromMemoryFields(fields: Fieldable[]): Fr[] {
return [poseidon2Hash(fields)];
}
Expand Down
6 changes: 5 additions & 1 deletion yarn-project/simulator/src/avm/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { type PublicSideEffectTraceInterface } from '../../public/side_effect_tr
import { AvmContext } from '../avm_context.js';
import { AvmContextInputs, AvmExecutionEnvironment } from '../avm_execution_environment.js';
import { AvmMachineState } from '../avm_machine_state.js';
import { Field, Uint8 } from '../avm_memory_types.js';
import { Field, Uint8, Uint64 } from '../avm_memory_types.js';
import { HostStorage } from '../journal/host_storage.js';
import { AvmPersistableStateManager } from '../journal/journal.js';
import { NullifierManager } from '../journal/nullifiers.js';
Expand Down Expand Up @@ -135,6 +135,10 @@ export function randomMemoryBytes(length: number): Uint8[] {
return [...Array(length)].map(_ => new Uint8(Math.floor(Math.random() * 255)));
}

export function randomMemoryUint64s(length: number): Uint64[] {
return [...Array(length)].map(_ => new Uint64(Math.floor(Math.random() * 255)));
}

export function randomMemoryFields(length: number): Field[] {
return [...Array(length)].map(_ => new Field(Fr.random()));
}
Expand Down
43 changes: 40 additions & 3 deletions yarn-project/simulator/src/avm/opcodes/hashing.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { keccak256, pedersenHash, sha256 } from '@aztec/foundation/crypto';
import { keccak256, keccakf1600, pedersenHash, sha256 } from '@aztec/foundation/crypto';

import { type AvmContext } from '../avm_context.js';
import { Field, type Uint8, Uint32 } from '../avm_memory_types.js';
import { Field, type Uint8, Uint32, Uint64 } from '../avm_memory_types.js';
import { initContext, randomMemoryBytes, randomMemoryFields } from '../fixtures/index.js';
import { Addressing, AddressingMode } from './addressing_mode.js';
import { Keccak, Pedersen, Poseidon2, Sha256 } from './hashing.js';
import { Keccak, KeccakF1600, Pedersen, Poseidon2, Sha256 } from './hashing.js';

describe('Hashing Opcodes', () => {
let context: AvmContext;
Expand Down Expand Up @@ -136,6 +136,43 @@ describe('Hashing Opcodes', () => {
});
});

describe('Keccakf1600', () => {
it('Should (de)serialize correctly', () => {
const buf = Buffer.from([
KeccakF1600.opcode, // opcode
1, // indirect
...Buffer.from('12345678', 'hex'), // dstOffset
...Buffer.from('23456789', 'hex'), // messageOffset
...Buffer.from('3456789a', 'hex'), // messageSizeOffset
]);
const inst = new KeccakF1600(
/*indirect=*/ 1,
/*dstOffset=*/ 0x12345678,
/*messageOffset=*/ 0x23456789,
/*messageSizeOffset=*/ 0x3456789a,
);

expect(KeccakF1600.deserialize(buf)).toEqual(inst);
expect(inst.serialize()).toEqual(buf);
});

it('Should permute correctly - direct', async () => {
const rawArgs = [...Array(25)].map(_ => 0n);
const args = rawArgs.map(a => new Uint64(a));
const indirect = 0;
const messageOffset = 0;
const messageSizeOffset = 50;
const dstOffset = 100;
context.machineState.memory.set(messageSizeOffset, new Uint32(args.length));
context.machineState.memory.setSlice(messageOffset, args);

await new KeccakF1600(indirect, dstOffset, messageOffset, messageSizeOffset).execute(context);

const result = context.machineState.memory.getSliceAs<Uint64>(dstOffset, 25);
expect(result).toEqual(keccakf1600(rawArgs).map(a => new Uint64(a)));
});
});

describe('Sha256', () => {
it('Should (de)serialize correctly', () => {
const buf = Buffer.from([
Expand Down
55 changes: 53 additions & 2 deletions yarn-project/simulator/src/avm/opcodes/hashing.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { keccak256, pedersenHash, poseidon2Permutation, sha256 } from '@aztec/foundation/crypto';
import { keccak256, keccakf1600, pedersenHash, poseidon2Permutation, sha256 } from '@aztec/foundation/crypto';

import { strict as assert } from 'assert';

import { type AvmContext } from '../avm_context.js';
import { Field, TypeTag, Uint8 } from '../avm_memory_types.js';
import { Field, TypeTag, Uint8, Uint64 } from '../avm_memory_types.js';
import { Opcode, OperandType } from '../serialization/instruction_serialization.js';
import { Addressing } from './addressing_mode.js';
import { Instruction } from './instruction.js';
Expand Down Expand Up @@ -94,6 +96,55 @@ export class Keccak extends Instruction {
}
}

export class KeccakF1600 extends Instruction {
static type: string = 'KECCAKF1600';
static readonly opcode: Opcode = Opcode.KECCAKF1600;

// 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 stateOffset: number,
// This is here for compatibility with the CPP side. Should be removed in both.
private stateSizeOffset: number,
) {
super();
}

// pub fn keccakf1600(input: [u64; 25]) -> [u64; 25]
public async execute(context: AvmContext): Promise<void> {
const memory = context.machineState.memory.track(this.type);
const [dstOffset, stateOffset, stateSizeOffset] = Addressing.fromWire(this.indirect).resolve(
[this.dstOffset, this.stateOffset, this.stateSizeOffset],
memory,
);
memory.checkTag(TypeTag.UINT32, stateSizeOffset);
const stateSize = memory.get(stateSizeOffset).toNumber();
assert(stateSize === 25, 'Invalid state size for keccakf1600');
const memoryOperations = { reads: stateSize + 1, writes: 25, indirect: this.indirect };
context.machineState.consumeGas(this.gasCost(memoryOperations));

memory.checkTagsRange(TypeTag.UINT64, stateOffset, stateSize);

const stateData = memory.getSlice(stateOffset, stateSize).map(word => word.toBigInt());
const updatedState = keccakf1600(stateData);

const res = updatedState.map(word => new Uint64(word));
memory.setSlice(dstOffset, res);

memory.assert(memoryOperations);
context.machineState.incrementPc();
}
}

export class Sha256 extends Instruction {
static type: string = 'SHA256';
static readonly opcode: Opcode = Opcode.SHA256;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DAGasLeft, L2GasLeft } from '../opcodes/context_getters.js';
import { EcAdd } from '../opcodes/ec_add.js';
import { Keccak, Pedersen, Poseidon2, Sha256 } from '../opcodes/hashing.js';
import { Keccak, KeccakF1600, Pedersen, Poseidon2, Sha256 } from '../opcodes/hashing.js';
import type { Instruction } from '../opcodes/index.js';
import {
Add,
Expand Down Expand Up @@ -148,6 +148,9 @@ const INSTRUCTION_SET = () =>
[MultiScalarMul.opcode, MultiScalarMul],
// Conversions
[ToRadixLE.opcode, ToRadixLE],
// Future Gadgets -- pending changes in noir
// SHA256COMPRESSION,
[KeccakF1600.opcode, KeccakF1600],
]);

interface Serializable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ export enum Opcode {
MSM,
// Conversion
TORADIXLE,
// Future Gadgets -- pending changes in noir
SHA256COMPRESSION,
KECCAKF1600, // Here for when we eventually support this
}

// Possible types for an instruction's operand in its wire format. (Keep in sync with CPP code.
Expand Down

0 comments on commit b81c503

Please sign in to comment.