diff --git a/avm-transpiler/src/opcodes.rs b/avm-transpiler/src/opcodes.rs index fc177793494..23b67dc18f9 100644 --- a/avm-transpiler/src/opcodes.rs +++ b/avm-transpiler/src/opcodes.rs @@ -74,6 +74,7 @@ pub enum AvmOpcode { PEDERSEN, // temp - may be removed, but alot of contracts rely on it ECADD, MSM, + PEDERSENCOMMITMENT, // temp // Conversions TORADIXLE, // Other @@ -170,6 +171,7 @@ impl AvmOpcode { AvmOpcode::PEDERSEN => "PEDERSEN", AvmOpcode::ECADD => "ECADD", AvmOpcode::MSM => "MSM", + AvmOpcode::PEDERSENCOMMITMENT => "PEDERSENCOMMITMENT", // Conversions AvmOpcode::TORADIXLE => "TORADIXLE", // Other diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index a6d09a975fa..4109a453355 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -888,6 +888,24 @@ fn handle_black_box_function(avm_instrs: &mut Vec, operation: &B ..Default::default() }); } + // Temporary while we dont have efficient noir implementations (again) + BlackBoxOp::PedersenCommitment { inputs, domain_separator, output } => { + let input_offset = inputs.pointer.0; + let input_size_offset = inputs.size.0; + let index_offset = domain_separator.0; + let output_offset = output.pointer.0; + avm_instrs.push(AvmInstruction { + opcode: AvmOpcode::PEDERSENCOMMITMENT, + indirect: Some(ZEROTH_OPERAND_INDIRECT | FIRST_OPERAND_INDIRECT), + operands: vec![ + AvmOperand::U32 { value: input_offset as u32 }, + AvmOperand::U32 { value: output_offset as u32 }, + AvmOperand::U32 { value: input_size_offset as u32 }, + AvmOperand::U32 { value: index_offset as u32 }, + ], + ..Default::default() + }); + } _ => panic!("Transpiler doesn't know how to process {:?}", operation), } } diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_deserialization.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_deserialization.cpp index 1663491d3ab..a2d596c12b7 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_deserialization.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_deserialization.cpp @@ -166,10 +166,11 @@ const std::unordered_map> OPCODE_WIRE_FORMAT = OperandType::UINT32 } }, // dst_offset { OpCode::MSM, { OperandType::INDIRECT, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32 } }, + { OpCode::PEDERSENCOMMITMENT, + { OperandType::INDIRECT, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32 } }, // Gadget - Conversion { OpCode::TORADIXLE, { OperandType::INDIRECT, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32 } }, - // Gadgets - Unused for now { OpCode::SHA256COMPRESSION, { OperandType::INDIRECT, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32 } }, diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp index f2beb29d32f..c70213f355d 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp @@ -106,6 +106,7 @@ enum class OpCode : uint8_t { PEDERSEN, ECADD, MSM, + PEDERSENCOMMITMENT, // Conversions TORADIXLE, // Future Gadgets -- pending changes in noir 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 5bebeadf78d..4c6838558a1 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 @@ -24,7 +24,7 @@ contract AvmTest { global big_field_136_bits: Field = 0x991234567890abcdef1234567890abcdef; // Libs - use std::embedded_curve_ops::multi_scalar_mul; + use std::embedded_curve_ops::{multi_scalar_mul, EmbeddedCurvePoint}; use dep::aztec::protocol_types::constants::CONTRACT_INSTANCE_LENGTH; use dep::aztec::prelude::{Map, Deserialize}; use dep::aztec::state_vars::PublicMutable; @@ -153,6 +153,12 @@ contract AvmTest { triple_g } + #[aztec(public)] + fn pedersen_commit(x: Field, y: Field) -> EmbeddedCurvePoint { + let commitment = dep::std::hash::pedersen_commitment([x, y]); + commitment + } + /************************************************************************ * Misc ************************************************************************/ diff --git a/yarn-project/simulator/src/avm/avm_gas.ts b/yarn-project/simulator/src/avm/avm_gas.ts index 34ee3b492c5..bdb8ef86097 100644 --- a/yarn-project/simulator/src/avm/avm_gas.ts +++ b/yarn-project/simulator/src/avm/avm_gas.ts @@ -124,6 +124,7 @@ const BaseGasCosts: Record = { [Opcode.PEDERSEN]: DefaultBaseGasCost, [Opcode.ECADD]: DefaultBaseGasCost, [Opcode.MSM]: DefaultBaseGasCost, + [Opcode.PEDERSENCOMMITMENT]: DefaultBaseGasCost, // Conversions [Opcode.TORADIXLE]: DefaultBaseGasCost, // Other diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 83d4f95939b..54b42fa3f80 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -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, keccakf1600, pedersenHash, poseidon2Hash, sha256 } from '@aztec/foundation/crypto'; +import { keccak256, keccakf1600, pedersenCommit, pedersenHash, poseidon2Hash, sha256 } from '@aztec/foundation/crypto'; import { Fq, Fr } from '@aztec/foundation/fields'; import { type Fieldable } from '@aztec/foundation/serialize'; @@ -140,6 +140,22 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(results.output).toEqual([expectedResult.x, expectedResult.y, Fr.ZERO]); }); + it('pedersen commitment operations', async () => { + const calldata: Fr[] = [new Fr(100), new Fr(1)]; + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + + const bytecode = getAvmTestContractBytecode('pedersen_commit'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(false); + // This doesnt include infinites + const expectedResult = pedersenCommit([Buffer.from([100]), Buffer.from([1])]).map(f => new Fr(f)); + // TODO: Come back to the handling of infinities when we confirm how they're handled in bb + const isInf = expectedResult[0] === new Fr(0) && expectedResult[1] === new Fr(0); + expectedResult.push(new Fr(isInf)); + expect(results.output).toEqual(expectedResult); + }); + describe('U128 addition and overflows', () => { it('U128 addition', async () => { const calldata: Fr[] = [ diff --git a/yarn-project/simulator/src/avm/opcodes/commitment.test.ts b/yarn-project/simulator/src/avm/opcodes/commitment.test.ts new file mode 100644 index 00000000000..c5b7c509334 --- /dev/null +++ b/yarn-project/simulator/src/avm/opcodes/commitment.test.ts @@ -0,0 +1,93 @@ +import { pedersenCommit } from '@aztec/foundation/crypto'; + +import { type AvmContext } from '../avm_context.js'; +import { Field, Uint32 } from '../avm_memory_types.js'; +import { initContext, randomMemoryFields } from '../fixtures/index.js'; +import { Addressing, AddressingMode } from './addressing_mode.js'; +import { PedersenCommitment } from './commitment.js'; + +describe('Commitment Opcode', () => { + let context: AvmContext; + + beforeEach(async () => { + context = initContext(); + }); + + describe('Pedersen Commitment', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + PedersenCommitment.opcode, // opcode + 1, // indirect + ...Buffer.from('23456789', 'hex'), // inputOffset + ...Buffer.from('3456789a', 'hex'), // inputSizeOffset + ...Buffer.from('12345678', 'hex'), // outputOffset + ...Buffer.from('00000000', 'hex'), // genIndexOffset + ]); + const inst = new PedersenCommitment( + /*indirect=*/ 1, + /*inputOffset=*/ 0x23456789, + /*inputSizeOffset=*/ 0x3456789a, + /*outputOffset=*/ 0x12345678, + /*genIndexOffset=*/ 0, + ); + + expect(PedersenCommitment.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should commit correctly - direct', async () => { + const args = randomMemoryFields(10); + const inputOffset = 0; + const inputSizeOffset = 20; + const outputOffset = 50; + const indirect = 0; + const generatorIndexOffset = 10; + + context.machineState.memory.setSlice(inputOffset, args); + context.machineState.memory.set(inputSizeOffset, new Uint32(args.length)); + context.machineState.memory.set(generatorIndexOffset, new Uint32(0)); + + const expectedCommitment = pedersenCommit(args.map(f => f.toBuffer())).map(f => new Field(f)); + await new PedersenCommitment(indirect, inputOffset, outputOffset, inputSizeOffset, generatorIndexOffset).execute( + context, + ); + + const result = context.machineState.memory.getSlice(outputOffset, 2); + expect(result).toEqual(expectedCommitment); + // Check Inf + expect(0).toEqual(context.machineState.memory.get(outputOffset + 2).toNumber()); + }); + + it('Should commit correctly - indirect', async () => { + const args = randomMemoryFields(10); + const indirect = new Addressing([ + /*inputOffset=*/ AddressingMode.INDIRECT, + /*outputOffset*/ AddressingMode.INDIRECT, + /*inputSizeOffset=*/ AddressingMode.DIRECT, + /*generatorIndexOffset=*/ AddressingMode.DIRECT, + ]).toWire(); + const inputOffset = 0; + const inputSizeOffset = 20; + const outputOffset = 50; + const realOutputOffset = 100; + const realInputOffset = 200; + const generatorIndexOffset = 51; + + context.machineState.memory.set(outputOffset, new Uint32(realOutputOffset)); + context.machineState.memory.set(inputOffset, new Uint32(realInputOffset)); + context.machineState.memory.setSlice(realInputOffset, args); + context.machineState.memory.set(inputSizeOffset, new Uint32(args.length)); + context.machineState.memory.set(generatorIndexOffset, new Uint32(0)); + + const expectedCommitment = pedersenCommit(args.map(f => f.toBuffer())).map(f => new Field(f)); + await new PedersenCommitment(indirect, inputOffset, outputOffset, inputSizeOffset, generatorIndexOffset).execute( + context, + ); + + const result = context.machineState.memory.getSlice(realOutputOffset, 2); + expect(result).toEqual(expectedCommitment); + // Check Inf + expect(0).toEqual(context.machineState.memory.get(realOutputOffset + 2).toNumber()); + }); + }); +}); diff --git a/yarn-project/simulator/src/avm/opcodes/commitment.ts b/yarn-project/simulator/src/avm/opcodes/commitment.ts new file mode 100644 index 00000000000..ad99064a6d9 --- /dev/null +++ b/yarn-project/simulator/src/avm/opcodes/commitment.ts @@ -0,0 +1,66 @@ +import { pedersenCommit } from '@aztec/foundation/crypto'; + +import { type AvmContext } from '../avm_context.js'; +import { Field, TypeTag, Uint8 } from '../avm_memory_types.js'; +import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; +import { Addressing } from './addressing_mode.js'; +import { Instruction } from './instruction.js'; + +export class PedersenCommitment extends Instruction { + static type: string = 'PEDERSENCOMMITMENT'; + static readonly opcode: Opcode = Opcode.PEDERSENCOMMITMENT; + + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8 /* Opcode */, + OperandType.UINT8 /* Indirect */, + OperandType.UINT32 /* Input Offset*/, + OperandType.UINT32 /* Dst Offset */, + OperandType.UINT32 /* Input Size Offset */, + OperandType.UINT32 /* Generator Index Offset */, + ]; + + constructor( + private indirect: number, + private inputOffset: number, + private outputOffset: number, + private inputSizeOffset: number, + private genIndexOffset: number, + ) { + super(); + } + + public async execute(context: AvmContext): Promise { + const memory = context.machineState.memory.track(this.type); + const [inputOffset, outputOffset, inputSizeOffset, genIndexOffset] = Addressing.fromWire(this.indirect).resolve( + [this.inputOffset, this.outputOffset, this.inputSizeOffset, this.genIndexOffset], + memory, + ); + + const inputSize = memory.get(inputSizeOffset).toNumber(); + memory.checkTag(TypeTag.UINT32, inputSizeOffset); + + const inputs = memory.getSlice(inputOffset, inputSize); + memory.checkTagsRange(TypeTag.FIELD, inputOffset, inputSize); + + // Generator index not used for now since we dont utilise it in the pedersenCommit function + memory.checkTag(TypeTag.UINT32, genIndexOffset); + + const memoryOperations = { reads: inputSize + 1, writes: 3, indirect: this.indirect }; + context.machineState.consumeGas(this.gasCost(memoryOperations)); + + const inputBuffer: Buffer[] = inputs.map(input => input.toBuffer()); + // TODO: Add the generate index to the pedersenCommit function + const commitment = pedersenCommit(inputBuffer).map(f => new Field(f)); + // The function doesnt include a flag if the output point is infinity, come back to this + // for now we just check if theyre zero - until we know how bb encodes them + const isInfinity = commitment[0].equals(new Field(0)) && commitment[1].equals(new Field(0)); + + memory.set(outputOffset, commitment[0]); // Field typed + memory.set(outputOffset + 1, commitment[1]); // Field typed + memory.set(outputOffset + 2, new Uint8(isInfinity ? 1 : 0)); // U8 typed + + memory.assert(memoryOperations); + context.machineState.incrementPc(); + } +} diff --git a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts index 03a0d01f0c9..46811568427 100644 --- a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts +++ b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts @@ -1,3 +1,4 @@ +import { PedersenCommitment } from '../opcodes/commitment.js'; import { DAGasLeft, L2GasLeft } from '../opcodes/context_getters.js'; import { EcAdd } from '../opcodes/ec_add.js'; import { Keccak, KeccakF1600, Pedersen, Poseidon2, Sha256 } from '../opcodes/hashing.js'; @@ -146,6 +147,7 @@ const INSTRUCTION_SET = () => [Sha256.opcode, Sha256], [Pedersen.opcode, Pedersen], [MultiScalarMul.opcode, MultiScalarMul], + [PedersenCommitment.opcode, PedersenCommitment], // Conversions [ToRadixLE.opcode, ToRadixLE], // Future Gadgets -- pending changes in noir diff --git a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts index c449f49cd8b..013c35ce369 100644 --- a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts +++ b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts @@ -78,6 +78,7 @@ export enum Opcode { PEDERSEN, // temp - may be removed, but alot of contracts rely on it ECADD, MSM, + PEDERSENCOMMITMENT, // Conversion TORADIXLE, // Future Gadgets -- pending changes in noir