From 42c9800d71036ac03c97b7b4eb2e16fd47a5d0da Mon Sep 17 00:00:00 2001 From: benesjan Date: Fri, 11 Aug 2023 09:05:29 +0000 Subject: [PATCH] WIP --- .../src/client/function_selectors.ts | 8 +- .../circuits.js/src/abis/abis.test.ts | 22 +----- .../src/contract/contract_deployment_info.ts | 6 +- .../contract/contract_tree/contract_tree.ts | 6 +- .../src/structs/function_data.test.ts | 3 +- .../circuits.js/src/structs/function_data.ts | 43 ++++------- .../structs/function_leaf_preimage.test.ts | 3 +- .../src/structs/function_leaf_preimage.ts | 24 +----- .../src/structs/function_selector.ts | 75 +++++++++++++++++++ yarn-project/circuits.js/src/structs/index.ts | 1 + .../circuits.js/src/tests/factories.ts | 7 +- .../src/types/contract_function_dao.ts | 3 +- yarn-project/foundation/src/abi/abi_coder.ts | 39 +--------- 13 files changed, 117 insertions(+), 123 deletions(-) create mode 100644 yarn-project/circuits.js/src/structs/function_selector.ts diff --git a/yarn-project/acir-simulator/src/client/function_selectors.ts b/yarn-project/acir-simulator/src/client/function_selectors.ts index 97f988134de0..1c8e34f0a357 100644 --- a/yarn-project/acir-simulator/src/client/function_selectors.ts +++ b/yarn-project/acir-simulator/src/client/function_selectors.ts @@ -1,9 +1,5 @@ -import { FUNCTION_SELECTOR_NUM_BYTES } from '@aztec/circuits.js'; -import { computeFunctionSelector } from '@aztec/foundation/abi'; +import { FunctionSelector } from '@aztec/circuits.js'; export const computeNoteHashAndNullifierSignature = 'compute_note_hash_and_nullifier(field,field,field,array)'; -export const computeNoteHashAndNullifierSelector = computeFunctionSelector( - computeNoteHashAndNullifierSignature, - FUNCTION_SELECTOR_NUM_BYTES, -); +export const computeNoteHashAndNullifierSelector = FunctionSelector.fromSignature(computeNoteHashAndNullifierSignature); diff --git a/yarn-project/circuits.js/src/abis/abis.test.ts b/yarn-project/circuits.js/src/abis/abis.test.ts index d217490f99be..79d5b54535b0 100644 --- a/yarn-project/circuits.js/src/abis/abis.test.ts +++ b/yarn-project/circuits.js/src/abis/abis.test.ts @@ -1,6 +1,6 @@ import times from 'lodash.times'; -import { AztecAddress, Fr, FunctionData, FunctionLeafPreimage, NewContractData } from '../index.js'; +import { AztecAddress, Fr, FunctionData, FunctionLeafPreimage, FunctionSelector, NewContractData } from '../index.js'; import { makeAztecAddress, makeEthAddress, makePoint, makeTxRequest, makeVerificationKey } from '../tests/factories.js'; import { CircuitsWasm } from '../wasm/circuits_wasm.js'; import { @@ -46,34 +46,18 @@ describe('abis wasm bindings', () => { }); it('computes a function leaf', () => { - const leaf = new FunctionLeafPreimage(Buffer.from([0, 0, 0, 123]), false, true, Fr.ZERO, Fr.ZERO); + const leaf = new FunctionLeafPreimage(new FunctionSelector(Buffer.from([0, 0, 0, 123])), false, true, Fr.ZERO, Fr.ZERO); const res = computeFunctionLeaf(wasm, leaf); expect(res).toMatchSnapshot(); }); - it('compute function leaf should revert if buffer is over 4 bytes', () => { - expect(() => { - new FunctionLeafPreimage(Buffer.from([0, 0, 0, 0, 123]), false, true, Fr.ZERO, Fr.ZERO); - }).toThrow('Function selector must be 4 bytes long, got 5 bytes.'); - }); - - it('function leaf toBuffer should revert if buffer is over 4 bytes ', () => { - const initBuffer = Buffer.from([0, 0, 0, 123]); - const largerBuffer = Buffer.from([0, 0, 0, 0, 123]); - expect(() => { - const leaf = new FunctionLeafPreimage(initBuffer, false, true, Fr.ZERO, Fr.ZERO); - leaf.functionSelector = largerBuffer; - leaf.toBuffer(); - }).toThrow('Function selector must be 4 bytes long, got 5 bytes.'); - }); - it('computes function tree root', () => { const res = computeFunctionTreeRoot(wasm, [new Fr(0n), new Fr(0n), new Fr(0n), new Fr(0n)]); expect(res).toMatchSnapshot(); }); it('hashes constructor info', () => { - const functionData = new FunctionData(Buffer.alloc(4), false, true, true); + const functionData = new FunctionData(FunctionSelector.empty(), false, true, true); const argsHash = new Fr(42); const vkHash = Buffer.alloc(32); const res = hashConstructor(wasm, functionData, argsHash, vkHash); diff --git a/yarn-project/circuits.js/src/contract/contract_deployment_info.ts b/yarn-project/circuits.js/src/contract/contract_deployment_info.ts index be3389e2d736..b4115aa3ba26 100644 --- a/yarn-project/circuits.js/src/contract/contract_deployment_info.ts +++ b/yarn-project/circuits.js/src/contract/contract_deployment_info.ts @@ -5,9 +5,9 @@ import { computeVarArgsHash, hashConstructor, } from '@aztec/circuits.js/abis'; -import { ContractAbi, encodeArguments, generateFunctionSelector } from '@aztec/foundation/abi'; +import { ContractAbi, encodeArguments } from '@aztec/foundation/abi'; -import { CircuitsWasm, DeploymentInfo, Fr, FunctionData, PublicKey } from '../index.js'; +import { CircuitsWasm, DeploymentInfo, Fr, FunctionData, FunctionSelector, PublicKey } from '../index.js'; import { generateFunctionLeaves, hashVKStr, isConstructor } from './contract_tree/contract_tree.js'; /** @@ -37,7 +37,7 @@ export async function getContractDeploymentInfo( const constructorVkHash = Fr.fromBuffer(vkHash); const functions = abi.functions.map(f => ({ ...f, - selector: generateFunctionSelector(f.name, f.parameters), + selector: FunctionSelector.fromNameAndParameters(f.name, f.parameters), })); const leaves = generateFunctionLeaves(functions, wasm); const functionTreeRoot = computeFunctionTreeRoot(wasm, leaves); diff --git a/yarn-project/circuits.js/src/contract/contract_tree/contract_tree.ts b/yarn-project/circuits.js/src/contract/contract_tree/contract_tree.ts index e3e636f5b6f9..02174f839ff9 100644 --- a/yarn-project/circuits.js/src/contract/contract_tree/contract_tree.ts +++ b/yarn-project/circuits.js/src/contract/contract_tree/contract_tree.ts @@ -1,6 +1,6 @@ -import { CircuitsWasm, ContractFunctionDao, Fr, FunctionData, FunctionLeafPreimage } from '@aztec/circuits.js'; +import { CircuitsWasm, ContractFunctionDao, Fr, FunctionData, FunctionLeafPreimage, FunctionSelector } from '@aztec/circuits.js'; import { computeFunctionLeaf, hashVK } from '@aztec/circuits.js/abis'; -import { FunctionType, generateFunctionSelector } from '@aztec/foundation/abi'; +import { FunctionType } from '@aztec/foundation/abi'; /** * Computes the hash of a hex-encoded string representation of a verification key (vk). @@ -69,7 +69,7 @@ export function generateFunctionLeaves(functions: ContractFunctionDao[], wasm: C const result: Fr[] = []; for (let i = 0; i < targetFunctions.length; i++) { const f = targetFunctions[i]; - const selector = generateFunctionSelector(f.name, f.parameters); + const selector = FunctionSelector.fromNameAndParameters(f.name, f.parameters); const isInternal = f.isInternal; const isPrivate = f.functionType === FunctionType.SECRET; // All non-unconstrained functions have vks diff --git a/yarn-project/circuits.js/src/structs/function_data.test.ts b/yarn-project/circuits.js/src/structs/function_data.test.ts index bd66d31438db..72055aaa8770 100644 --- a/yarn-project/circuits.js/src/structs/function_data.test.ts +++ b/yarn-project/circuits.js/src/structs/function_data.test.ts @@ -1,11 +1,12 @@ import { expectSerializeToMatchSnapshot } from '../tests/expectSerialize.js'; import { FunctionData } from './function_data.js'; +import { FunctionSelector } from './function_selector.js'; describe('basic FunctionData serialization', () => { it(`serializes a trivial FunctionData and prints it`, async () => { // Test the data case: writing (mostly) sequential numbers await expectSerializeToMatchSnapshot( - new FunctionData(Buffer.from([0, 0, 0, 123]), false, true, true).toBuffer(), + new FunctionData(new FunctionSelector(Buffer.from([0, 0, 0, 123])), false, true, true).toBuffer(), 'abis__test_roundtrip_serialize_function_data', ); }); diff --git a/yarn-project/circuits.js/src/structs/function_data.ts b/yarn-project/circuits.js/src/structs/function_data.ts index 8411c6fcc725..c11979be0041 100644 --- a/yarn-project/circuits.js/src/structs/function_data.ts +++ b/yarn-project/circuits.js/src/structs/function_data.ts @@ -1,22 +1,21 @@ -import { FunctionAbi, FunctionType, generateFunctionSelector } from '@aztec/foundation/abi'; -import { BufferReader, deserializeUInt32, numToUInt32BE } from '@aztec/foundation/serialize'; +import { FunctionAbi, FunctionType } from '@aztec/foundation/abi'; +import { BufferReader, deserializeUInt32 } from '@aztec/foundation/serialize'; import { ContractFunctionDao } from '../index.js'; import { serializeToBuffer } from '../utils/serialize.js'; - -const FUNCTION_SELECTOR_LENGTH = 4; +import { FunctionSelector } from './function_selector.js'; /** * Function description for circuit. * @see abis/function_data.hpp */ export class FunctionData { - /** - * Function selector of the function being called. - */ - public functionSelectorBuffer: Buffer; constructor( - functionSelector: Buffer | number, + /** + * Function selector of the function being called. + * TODO: rename to functionSelector once the functionSelector getter is removed + */ + private _functionSelector: FunctionSelector, /** * Indicates whether the function is only callable by self or not. */ @@ -29,23 +28,11 @@ export class FunctionData { * Indicates whether the function is a constructor. */ public isConstructor: boolean, - ) { - if (functionSelector instanceof Buffer) { - if (functionSelector.byteLength !== FUNCTION_SELECTOR_LENGTH) { - throw new Error( - `Function selector must be ${FUNCTION_SELECTOR_LENGTH} bytes long, got ${functionSelector.byteLength} bytes.`, - ); - } - this.functionSelectorBuffer = functionSelector; - } else { - // create a new numeric buffer with 4 bytes - this.functionSelectorBuffer = numToUInt32BE(functionSelector); - } - } + ) {} static fromAbi(abi: FunctionAbi | ContractFunctionDao): FunctionData { return new FunctionData( - generateFunctionSelector(abi.name, abi.parameters), + FunctionSelector.fromNameAndParameters(abi.name, abi.parameters), abi.isInternal, abi.functionType === FunctionType.SECRET, abi.name === 'constructor', @@ -55,7 +42,7 @@ export class FunctionData { // For serialization, must match function_selector name in C++ and return as number // TODO(AD) somehow remove this cruft, probably by using a buffer selector in C++ get functionSelector(): number { - return deserializeUInt32(this.functionSelectorBuffer).elem; + return deserializeUInt32(this._functionSelector.value).elem; } /** @@ -63,7 +50,7 @@ export class FunctionData { * @returns The buffer. */ toBuffer(): Buffer { - return serializeToBuffer(this.functionSelectorBuffer, this.isInternal, this.isPrivate, this.isConstructor); + return serializeToBuffer(this._functionSelector.value, this.isInternal, this.isPrivate, this.isConstructor); } /** @@ -71,7 +58,7 @@ export class FunctionData { * @returns True if the function selector is zero. */ isEmpty() { - return this.functionSelectorBuffer.equals(Buffer.alloc(FUNCTION_SELECTOR_LENGTH, 0)); + return this._functionSelector.isEmpty(); } /** @@ -94,7 +81,7 @@ export class FunctionData { isConstructor?: boolean; }): FunctionData { return new FunctionData( - Buffer.alloc(FUNCTION_SELECTOR_LENGTH, 0), + FunctionSelector.empty(), args?.isInternal ?? false, args?.isPrivate ?? false, args?.isConstructor ?? false, @@ -109,7 +96,7 @@ export class FunctionData { static fromBuffer(buffer: Buffer | BufferReader): FunctionData { const reader = BufferReader.asReader(buffer); return new FunctionData( - reader.readBytes(FUNCTION_SELECTOR_LENGTH), + reader.readObject(FunctionSelector), reader.readBoolean(), reader.readBoolean(), reader.readBoolean(), diff --git a/yarn-project/circuits.js/src/structs/function_leaf_preimage.test.ts b/yarn-project/circuits.js/src/structs/function_leaf_preimage.test.ts index dc278841d836..03e1ea5ce9a2 100644 --- a/yarn-project/circuits.js/src/structs/function_leaf_preimage.test.ts +++ b/yarn-project/circuits.js/src/structs/function_leaf_preimage.test.ts @@ -2,12 +2,13 @@ import { Fr } from '@aztec/foundation/fields'; import { expectSerializeToMatchSnapshot } from '../tests/expectSerialize.js'; import { FunctionLeafPreimage } from './function_leaf_preimage.js'; +import { FunctionSelector } from './function_selector.js'; describe('basic FunctionLeafPreimage serialization', () => { it(`serializes a trivial Function Leaf Preimage and prints it`, async () => { // Test the data case: writing (mostly) sequential numbers await expectSerializeToMatchSnapshot( - new FunctionLeafPreimage(Buffer.from([0, 0, 0, 123]), false, true, Fr.ZERO, Fr.ZERO).toBuffer(), + new FunctionLeafPreimage(new FunctionSelector(Buffer.from([0, 0, 0, 123])), false, true, Fr.ZERO, Fr.ZERO).toBuffer(), 'abis__test_roundtrip_serialize_function_leaf_preimage', ); }); diff --git a/yarn-project/circuits.js/src/structs/function_leaf_preimage.ts b/yarn-project/circuits.js/src/structs/function_leaf_preimage.ts index b60fe507e21a..0ad87423bf6e 100644 --- a/yarn-project/circuits.js/src/structs/function_leaf_preimage.ts +++ b/yarn-project/circuits.js/src/structs/function_leaf_preimage.ts @@ -2,19 +2,18 @@ import { Fr } from '@aztec/foundation/fields'; import { BufferReader } from '@aztec/foundation/serialize'; import { serializeToBuffer } from '../utils/serialize.js'; +import { FunctionSelector } from './function_selector.js'; /** * A class representing the "preimage" of a function tree leaf. * @see abis/function_leaf_preimage.hpp */ export class FunctionLeafPreimage { - readonly FUNCTION_SELECTOR_LENGTH = 4; - constructor( /** - * Function selector `FUNCTION_SELECTOR_LENGTH` bytes long. + * Function selector. */ - public functionSelector: Buffer, + public functionSelector: FunctionSelector, /** * Indicates whether the function is only callable by self or not. */ @@ -32,20 +31,6 @@ export class FunctionLeafPreimage { */ public acirHash: Fr, ) { - this.assertFunctionSelectorLength(functionSelector); - } - - /** - * Assert the function selector buffer length matches `FUNCTION_SELECTOR_LENGTH`. - * @param functionSelector - The buffer containing the function selector. - * @throws If the function selector buffer length does not match `FUNCTION_SELECTOR_LENGTH`. - */ - private assertFunctionSelectorLength(functionSelector: Buffer) { - if (functionSelector.byteLength !== this.FUNCTION_SELECTOR_LENGTH) { - throw new Error( - `Function selector must be ${this.FUNCTION_SELECTOR_LENGTH} bytes long, got ${functionSelector.byteLength} bytes.`, - ); - } } /** @@ -53,7 +38,6 @@ export class FunctionLeafPreimage { * @returns The buffer. */ toBuffer(): Buffer { - this.assertFunctionSelectorLength(this.functionSelector); return serializeToBuffer(this.functionSelector, this.isInternal, this.isPrivate, this.vkHash, this.acirHash); } @@ -65,7 +49,7 @@ export class FunctionLeafPreimage { static fromBuffer(buffer: Buffer | BufferReader): FunctionLeafPreimage { const reader = BufferReader.asReader(buffer); return new FunctionLeafPreimage( - reader.readBytes(4), + reader.readObject(FunctionSelector), reader.readBoolean(), reader.readBoolean(), reader.readFr(), diff --git a/yarn-project/circuits.js/src/structs/function_selector.ts b/yarn-project/circuits.js/src/structs/function_selector.ts new file mode 100644 index 000000000000..ddac897f191e --- /dev/null +++ b/yarn-project/circuits.js/src/structs/function_selector.ts @@ -0,0 +1,75 @@ +import { ABIParameter } from '@aztec/foundation/abi'; +import { keccak } from '@aztec/foundation/crypto'; +import { BufferReader } from '@aztec/foundation/serialize'; + +import { FUNCTION_SELECTOR_NUM_BYTES } from '../cbind/constants.gen.js'; + +/** + * A function selector is the first 4 bytes of the hash of a function signature. + */ +export class FunctionSelector { + /** + * The size of the hash in bytes. + */ + public static SIZE = FUNCTION_SELECTOR_NUM_BYTES; + + constructor(/** buffer containing the function selector */ public value: Buffer) { + if (value.length !== FunctionSelector.SIZE) { + throw new Error(`Function selector must be ${FunctionSelector.SIZE} bytes long, got ${value.length} bytes.`); + } + } + + /** + * Checks if the function selector is empty (all bytes are 0). + * @returns True if the function selector is empty (all bytes are 0). + */ + public isEmpty(): boolean { + return this.value.equals(Buffer.alloc(FunctionSelector.SIZE)); + } + + /** + * Serialize as a buffer. + * @returns The buffer. + */ + toBuffer(): Buffer { + return this.value; + } + + /** + * Deserializes from a buffer or reader, corresponding to a write in cpp. + * @param buffer - Buffer or BufferReader to read from. + * @returns The FunctionSelector. + */ + static fromBuffer(buffer: Buffer | BufferReader): FunctionSelector { + const reader = BufferReader.asReader(buffer); + return new FunctionSelector(reader.readBytes(FunctionSelector.SIZE)); + } + + /** + * Creates a function selector from a signature. + * @param signature - Signature of the function to generate the selector for (e.g. "transfer(field,field)"). + * @returns Function selector. + */ + static fromSignature(signature: string): FunctionSelector { + return new FunctionSelector(keccak(Buffer.from(signature)).subarray(0, FunctionSelector.SIZE)); + } + + /** + * Creates a function selector for a given function name and parameters. + * @param name - The name of the function. + * @param parameters - An array of ABIParameter objects, each containing the type information of a function parameter. + * @returns A Buffer containing the 4-byte function selector. + */ + static fromNameAndParameters(name: string, parameters: ABIParameter[]) { + const signature = name === 'constructor' ? name : `${name}(${parameters.map(p => p.type.kind).join(',')})`; + return FunctionSelector.fromSignature(signature); + } + + /** + * Creates an empty function selector. + * @returns An empty function selector. + */ + static empty(): FunctionSelector { + return new FunctionSelector(Buffer.alloc(FunctionSelector.SIZE)); + } +} diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index 99fb1e993f3c..e3f9c5f21ff1 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -24,6 +24,7 @@ export * from './tx_request.js'; export * from './tx_context.js'; export * from './verification_key.js'; export * from './function_leaf_preimage.js'; +export * from './function_selector.js'; export * from './aggregation_object.js'; export * from './membership_witness.js'; export * from './read_request_membership_witness.js'; diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index e8b6f51e6343..284dba506ade 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -25,6 +25,7 @@ import { Fq, Fr, FunctionData, + FunctionSelector, G1AffineElement, HISTORIC_BLOCKS_TREE_HEIGHT, KernelCircuitPublicInputs, @@ -135,10 +136,10 @@ export function makeConstantData(seed = 1): CombinedConstantData { * @param seed - The seed to use for generating the selector. * @returns A selector. */ -export function makeSelector(seed: number): Buffer { - const buffer = Buffer.alloc(4); +export function makeSelector(seed: number): FunctionSelector { + const buffer = Buffer.alloc(FunctionSelector.SIZE); buffer.writeUInt32BE(seed, 0); - return buffer; + return new FunctionSelector(buffer); } /** diff --git a/yarn-project/circuits.js/src/types/contract_function_dao.ts b/yarn-project/circuits.js/src/types/contract_function_dao.ts index 020964755bb1..4c9490b2bfdb 100644 --- a/yarn-project/circuits.js/src/types/contract_function_dao.ts +++ b/yarn-project/circuits.js/src/types/contract_function_dao.ts @@ -1,4 +1,5 @@ import { FunctionAbi } from '@aztec/foundation/abi'; +import { FunctionSelector } from '../index.js'; /** * A contract function Data Access Object (DAO). @@ -9,5 +10,5 @@ export interface ContractFunctionDao extends FunctionAbi { /** * Unique identifier for a contract function. */ - selector: Buffer; + selector: FunctionSelector; } diff --git a/yarn-project/foundation/src/abi/abi_coder.ts b/yarn-project/foundation/src/abi/abi_coder.ts index c33e3db4cd98..6971b423757c 100644 --- a/yarn-project/foundation/src/abi/abi_coder.ts +++ b/yarn-project/foundation/src/abi/abi_coder.ts @@ -1,41 +1,4 @@ -import { ABIParameter, ABIType } from '@aztec/foundation/abi'; -import { keccak } from '@aztec/foundation/crypto'; - -/** - * Generate a function signature string for a given function name and parameters. - * The signature is used to uniquely identify functions within noir contracts. - * If the function name is 'constructor', it returns just the name, otherwise it returns the name followed by the list of parameter types. - * - * @param name - The name of the function. - * @param parameters - An array of ABIParameter objects, each containing the type information of a function parameter. - * @returns A string representing the function signature. - */ -export function computeFunctionSignature(name: string, parameters: ABIParameter[]) { - return name === 'constructor' ? name : `${name}(${parameters.map(p => p.type.kind).join(',')})`; -} - -/** - * Generate a function selector for a given function signature. - * @param signature - The signature of the function. - * @param size - Number of bytes of the return buffer. - * @returns A Buffer containing the n-byte function selector. - */ -export function computeFunctionSelector(signature: string, size: number) { - return keccak(Buffer.from(signature)).slice(0, size); -} - -/** - * Generate a function selector for a given function name and parameters. - * It is derived by taking the first 4 bytes of the Keccak-256 hash of the function signature. - * - * @param name - The name of the function. - * @param parameters - An array of ABIParameter objects, each containing the type information of a function parameter. - * @returns A Buffer containing the 4-byte function selector. - */ -export function generateFunctionSelector(name: string, parameters: ABIParameter[]) { - const signature = computeFunctionSignature(name, parameters); - return keccak(Buffer.from(signature)).slice(0, 4); -} +import { ABIType } from '@aztec/foundation/abi'; /** * Get the size of an ABI type in field elements.