Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan committed Aug 11, 2023
1 parent 1a6168a commit 42c9800
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 123 deletions.
8 changes: 2 additions & 6 deletions yarn-project/acir-simulator/src/client/function_selectors.ts
Original file line number Diff line number Diff line change
@@ -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);
22 changes: 3 additions & 19 deletions yarn-project/circuits.js/src/abis/abis.test.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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).
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/circuits.js/src/structs/function_data.test.ts
Original file line number Diff line number Diff line change
@@ -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',
);
});
Expand Down
43 changes: 15 additions & 28 deletions yarn-project/circuits.js/src/structs/function_data.ts
Original file line number Diff line number Diff line change
@@ -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.
*/
Expand All @@ -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',
Expand All @@ -55,23 +42,23 @@ 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;
}

/**
* Serialize this as a buffer.
* @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);
}

/**
* Returns whether this instance is empty.
* @returns True if the function selector is zero.
*/
isEmpty() {
return this.functionSelectorBuffer.equals(Buffer.alloc(FUNCTION_SELECTOR_LENGTH, 0));
return this._functionSelector.isEmpty();
}

/**
Expand All @@ -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,
Expand All @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
);
});
Expand Down
24 changes: 4 additions & 20 deletions yarn-project/circuits.js/src/structs/function_leaf_preimage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -32,28 +31,13 @@ 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.`,
);
}
}

/**
* Serialize this as a buffer.
* @returns The buffer.
*/
toBuffer(): Buffer {
this.assertFunctionSelectorLength(this.functionSelector);
return serializeToBuffer(this.functionSelector, this.isInternal, this.isPrivate, this.vkHash, this.acirHash);
}

Expand All @@ -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(),
Expand Down
75 changes: 75 additions & 0 deletions yarn-project/circuits.js/src/structs/function_selector.ts
Original file line number Diff line number Diff line change
@@ -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));
}
}
1 change: 1 addition & 0 deletions yarn-project/circuits.js/src/structs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
7 changes: 4 additions & 3 deletions yarn-project/circuits.js/src/tests/factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
Fq,
Fr,
FunctionData,
FunctionSelector,
G1AffineElement,
HISTORIC_BLOCKS_TREE_HEIGHT,
KernelCircuitPublicInputs,
Expand Down Expand Up @@ -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);
}

/**
Expand Down
Loading

0 comments on commit 42c9800

Please sign in to comment.