Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Emit single functions from class registerer #4429

Merged
merged 2 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ library Constants {
uint256 internal constant NULLIFIER_TREE_HEIGHT = 20;
uint256 internal constant L1_TO_L2_MSG_TREE_HEIGHT = 16;
uint256 internal constant ROLLUP_VK_TREE_HEIGHT = 8;
uint256 internal constant ARTIFACT_FUNCTION_TREE_MAX_HEIGHT = 5;
uint256 internal constant CONTRACT_SUBTREE_HEIGHT = 0;
uint256 internal constant CONTRACT_SUBTREE_SIBLING_PATH_LENGTH = 16;
uint256 internal constant NOTE_HASH_SUBTREE_HEIGHT = 6;
Expand All @@ -67,8 +68,14 @@ library Constants {
uint256 internal constant ARGS_HASH_CHUNK_LENGTH = 32;
uint256 internal constant ARGS_HASH_CHUNK_COUNT = 32;
uint256 internal constant MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 1000;
uint256 internal constant CONTRACT_CLASS_REGISTERED_MAGIC_VALUE =
uint256 internal constant MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS = 500;
uint256 internal constant MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS = 500;
uint256 internal constant REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE =
0x6999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f8;
uint256 internal constant REGISTERER_PRIVATE_FUNCTION_BROADCASTED_MAGIC_VALUE =
0x1b70e95fde0b70adc30496b90a327af6a5e383e028e7a43211a07bcd;
uint256 internal constant REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE =
0xe7af816635466f128568edb04c9fa024f6c87fb9010fdbffa68b3d99;
uint256 internal constant L1_TO_L2_MESSAGE_LENGTH = 8;
uint256 internal constant L1_TO_L2_MESSAGE_ORACLE_CALL_LENGTH = 25;
uint256 internal constant MAX_NOTE_FIELDS_LENGTH = 20;
Expand Down
11 changes: 7 additions & 4 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import {
UnencryptedL2Log,
} from '@aztec/circuit-types';
import {
CONTRACT_CLASS_REGISTERED_MAGIC_VALUE,
ContractClassRegisteredEvent,
FunctionSelector,
NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE,
} from '@aztec/circuits.js';
import { createEthereumChain } from '@aztec/ethereum';
import { AztecAddress } from '@aztec/foundation/aztec-address';
Expand Down Expand Up @@ -70,10 +70,13 @@ export class Archiver implements ArchiveSource {
*/
private lastLoggedL1BlockNumber = 0n;

// TODO(@spalladino): Calculate this on the fly somewhere else!
// TODO(@spalladino): Calculate this on the fly somewhere else.
// Today this is printed in the logs for end-to-end test at
// end-to-end/src/e2e_deploy_contract.test.ts -t 'registering a new contract class'
// as "Added contract ContractClassRegisterer ADDRESS"
/** Address of the ClassRegisterer contract with a salt=1 */
private classRegistererAddress = AztecAddress.fromString(
'0x1c9f737a5ab5a7bb5ea970ba40737d44dc22fbcbe19fd8171429f2c2c433afb5',
'0x29c0cd0000951bba8af520ad5513cc53d9f0413c5a24a72a4ba8c17894c0bef9',
);

/**
Expand Down Expand Up @@ -335,7 +338,7 @@ export class Archiver implements ArchiveSource {
try {
if (
!log.contractAddress.equals(this.classRegistererAddress) ||
toBigIntBE(log.data.subarray(0, 32)) !== CONTRACT_CLASS_REGISTERED_MAGIC_VALUE
toBigIntBE(log.data.subarray(0, 32)) !== REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE
) {
continue;
}
Expand Down
10 changes: 9 additions & 1 deletion yarn-project/circuits.js/src/constants.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const PUBLIC_DATA_TREE_HEIGHT = 40;
export const NULLIFIER_TREE_HEIGHT = 20;
export const L1_TO_L2_MSG_TREE_HEIGHT = 16;
export const ROLLUP_VK_TREE_HEIGHT = 8;
export const ARTIFACT_FUNCTION_TREE_MAX_HEIGHT = 5;
export const CONTRACT_SUBTREE_HEIGHT = 0;
export const CONTRACT_SUBTREE_SIBLING_PATH_LENGTH = 16;
export const NOTE_HASH_SUBTREE_HEIGHT = 6;
Expand All @@ -53,7 +54,14 @@ export const NUM_FIELDS_PER_SHA256 = 2;
export const ARGS_HASH_CHUNK_LENGTH = 32;
export const ARGS_HASH_CHUNK_COUNT = 32;
export const MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 1000;
export const CONTRACT_CLASS_REGISTERED_MAGIC_VALUE = 0x6999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f8n;
export const MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS = 500;
export const MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS = 500;
export const REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE =
0x6999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f8n;
export const REGISTERER_PRIVATE_FUNCTION_BROADCASTED_MAGIC_VALUE =
0x1b70e95fde0b70adc30496b90a327af6a5e383e028e7a43211a07bcdn;
export const REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE =
0xe7af816635466f128568edb04c9fa024f6c87fb9010fdbffa68b3d99n;
export const L1_TO_L2_MESSAGE_LENGTH = 8;
export const L1_TO_L2_MESSAGE_ORACLE_CALL_LENGTH = 25;
export const MAX_NOTE_FIELDS_LENGTH = 20;
Expand Down
6 changes: 3 additions & 3 deletions yarn-project/circuits.js/src/contract/artifact_hash.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { getSampleContractArtifact } from '../tests/fixtures.js';
import { getArtifactHash } from './artifact_hash.js';
import { computeArtifactHash } from './artifact_hash.js';

describe('ArtifactHash', () => {
it('calculates the artifact hash', () => {
const artifact = getSampleContractArtifact();
expect(getArtifactHash(artifact).toString()).toMatchInlineSnapshot(
`"0x1cd31b12181cf7516720f4675ffea13c8c538dc4875232776adb8bbe8364ed5c"`,
expect(computeArtifactHash(artifact).toString()).toMatchInlineSnapshot(
`"0x242a46b1aa0ed341fe71f1068a1289cdbb01fbef14e2250783333cc0607db940"`,
);
});
});
36 changes: 23 additions & 13 deletions yarn-project/circuits.js/src/contract/artifact_hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { sha256 } from '@aztec/foundation/crypto';
import { Fr } from '@aztec/foundation/fields';
import { numToUInt8 } from '@aztec/foundation/serialize';

import { MerkleTree } from '../merkle/merkle_tree.js';
import { MerkleTreeCalculator } from '../merkle/merkle_tree_calculator.js';

const VERSION = 1;
Expand All @@ -29,39 +30,48 @@ const VERSION = 1;
* ```
* @param artifact - Artifact to calculate the hash for.
*/
export function getArtifactHash(artifact: ContractArtifact): Fr {
const privateFunctionRoot = getFunctionRoot(artifact, FunctionType.SECRET);
const unconstrainedFunctionRoot = getFunctionRoot(artifact, FunctionType.OPEN);
const metadataHash = getArtifactMetadataHash(artifact);
export function computeArtifactHash(artifact: ContractArtifact): Fr {
const privateFunctionRoot = computeArtifactFunctionTreeRoot(artifact, FunctionType.SECRET);
const unconstrainedFunctionRoot = computeArtifactFunctionTreeRoot(artifact, FunctionType.UNCONSTRAINED);
const metadataHash = computeArtifactMetadataHash(artifact);
const preimage = [numToUInt8(VERSION), privateFunctionRoot, unconstrainedFunctionRoot, metadataHash];
// TODO(@spalladino) Reducing sha256 to a field may have security implications. Validate this with crypto team.
return Fr.fromBufferReduce(sha256(Buffer.concat(preimage)));
}

function getArtifactMetadataHash(artifact: ContractArtifact) {
export function computeArtifactMetadataHash(artifact: ContractArtifact) {
const metadata = { name: artifact.name, events: artifact.events }; // TODO(@spalladino): Should we use the sorted event selectors instead? They'd need to be unique for that.
return sha256(Buffer.from(JSON.stringify(metadata), 'utf-8'));
}

type FunctionArtifactWithSelector = FunctionArtifact & { selector: FunctionSelector };
export function computeArtifactFunctionTreeRoot(artifact: ContractArtifact, fnType: FunctionType) {
return computeArtifactFunctionTree(artifact, fnType)?.root ?? Fr.ZERO.toBuffer();
}

function getFunctionRoot(artifact: ContractArtifact, fnType: FunctionType) {
const leaves = getFunctionLeaves(artifact, fnType);
export function computeArtifactFunctionTree(artifact: ContractArtifact, fnType: FunctionType): MerkleTree | undefined {
const leaves = computeFunctionLeaves(artifact, fnType);
// TODO(@spalladino) Consider implementing a null-object for empty trees
if (leaves.length === 0) {
return undefined;
}
const height = Math.ceil(Math.log2(leaves.length));
const calculator = new MerkleTreeCalculator(height, Buffer.alloc(32), (l, r) => sha256(Buffer.concat([l, r])));
return calculator.computeTreeRoot(leaves);
return calculator.computeTree(leaves);
}

function getFunctionLeaves(artifact: ContractArtifact, fnType: FunctionType) {
function computeFunctionLeaves(artifact: ContractArtifact, fnType: FunctionType) {
return artifact.functions
.filter(f => f.functionType === fnType)
.map(f => ({ ...f, selector: FunctionSelector.fromNameAndParameters(f.name, f.parameters) }))
.sort((a, b) => a.selector.value - b.selector.value)
.map(getFunctionArtifactHash);
.map(computeFunctionArtifactHash);
}

function getFunctionArtifactHash(fn: FunctionArtifactWithSelector): Buffer {
export function computeFunctionArtifactHash(fn: FunctionArtifact & { selector?: FunctionSelector }): Buffer {
const selector =
(fn as { selector: FunctionSelector }).selector ?? FunctionSelector.fromNameAndParameters(fn.name, fn.parameters);
const bytecodeHash = sha256(Buffer.from(fn.bytecode, 'hex'));
const metadata = JSON.stringify(fn.returnTypes);
const metadataHash = sha256(Buffer.from(metadata, 'utf8'));
return sha256(Buffer.concat([numToUInt8(VERSION), fn.selector.toBuffer(), metadataHash, bytecodeHash]));
return sha256(Buffer.concat([numToUInt8(VERSION), selector.toBuffer(), metadataHash, bytecodeHash]));
}
4 changes: 2 additions & 2 deletions yarn-project/circuits.js/src/contract/contract_class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ContractArtifact, FunctionSelector, FunctionType } from '@aztec/foundat
import { Fr } from '@aztec/foundation/fields';
import { ContractClass, ContractClassWithId } from '@aztec/types/contracts';

import { getArtifactHash } from './artifact_hash.js';
import { computeArtifactHash } from './artifact_hash.js';
import { computeContractClassId } from './contract_class_id.js';
import { packBytecode } from './public_bytecode.js';

Expand All @@ -13,7 +13,7 @@ type ContractArtifactWithHash = ContractArtifact & { artifactHash: Fr };
export function getContractClassFromArtifact(
artifact: ContractArtifact | ContractArtifactWithHash,
): ContractClassWithId {
const artifactHash = (artifact as ContractArtifactWithHash).artifactHash ?? getArtifactHash(artifact);
const artifactHash = (artifact as ContractArtifactWithHash).artifactHash ?? computeArtifactHash(artifact);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked chatgpt if there was a cleaner way to do this, and FWIW:

const artifactHash = 'artifactHash' in artifact
    ? artifact.artifactHash
    : computeArtifactHash(artifact);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks! I'll switch to this style.

const publicFunctions: ContractClass['publicFunctions'] = artifact.functions
.filter(f => f.functionType === FunctionType.OPEN)
.map(f => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { bufferFromFields } from '@aztec/foundation/abi';
import { toBigIntBE } from '@aztec/foundation/bigint-buffer';
import { Fr } from '@aztec/foundation/fields';
import { BufferReader } from '@aztec/foundation/serialize';
import { ContractClassPublic } from '@aztec/types/contracts';

import chunk from 'lodash.chunk';

import { CONTRACT_CLASS_REGISTERED_MAGIC_VALUE } from '../constants.gen.js';
import { REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE } from '../constants.gen.js';
import { computeContractClassId, computePublicBytecodeCommitment } from './contract_class_id.js';
import { packedBytecodeFromFields, unpackBytecode } from './public_bytecode.js';
import { unpackBytecode } from './public_bytecode.js';

/** Event emitted from the ContractClassRegisterer. */
export class ContractClassRegisteredEvent {
Expand All @@ -20,20 +21,20 @@ export class ContractClassRegisteredEvent {
) {}

static isContractClassRegisteredEvent(log: Buffer) {
return toBigIntBE(log.subarray(0, 32)) == CONTRACT_CLASS_REGISTERED_MAGIC_VALUE;
return toBigIntBE(log.subarray(0, 32)) == REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE;
}

static fromLogData(log: Buffer) {
if (!this.isContractClassRegisteredEvent(log)) {
const magicValue = CONTRACT_CLASS_REGISTERED_MAGIC_VALUE.toString(16);
const magicValue = REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE.toString(16);
throw new Error(`Log data for ContractClassRegisteredEvent is not prefixed with magic value 0x${magicValue}`);
}
const reader = new BufferReader(log.subarray(32));
const contractClassId = reader.readObject(Fr);
const version = reader.readObject(Fr).toNumber();
const artifactHash = reader.readObject(Fr);
const privateFunctionsRoot = reader.readObject(Fr);
const packedPublicBytecode = packedBytecodeFromFields(
const packedPublicBytecode = bufferFromFields(
chunk(reader.readToEnd(), Fr.SIZE_IN_BYTES).map(Buffer.from).map(Fr.fromBuffer),
);

Expand Down
14 changes: 1 addition & 13 deletions yarn-project/circuits.js/src/contract/public_bytecode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ContractArtifact } from '@aztec/foundation/abi';

import { getSampleContractArtifact } from '../tests/fixtures.js';
import { getContractClassFromArtifact } from './contract_class.js';
import { packBytecode, packedBytecodeAsFields, packedBytecodeFromFields, unpackBytecode } from './public_bytecode.js';
import { packBytecode, unpackBytecode } from './public_bytecode.js';

describe('PublicBytecode', () => {
let artifact: ContractArtifact;
Expand All @@ -16,16 +16,4 @@ describe('PublicBytecode', () => {
const unpackedBytecode = unpackBytecode(packedBytecode);
expect(unpackedBytecode).toEqual(publicFunctions);
});

it('converts small packed bytecode back and forth from fields', () => {
const packedBytecode = Buffer.from('1234567890abcdef'.repeat(10), 'hex');
const fields = packedBytecodeAsFields(packedBytecode);
expect(packedBytecodeFromFields(fields).toString('hex')).toEqual(packedBytecode.toString('hex'));
});

it('converts real packed bytecode back and forth from fields', () => {
const { packedBytecode } = getContractClassFromArtifact(artifact);
const fields = packedBytecodeAsFields(packedBytecode);
expect(packedBytecodeFromFields(fields).toString('hex')).toEqual(packedBytecode.toString('hex'));
});
});
41 changes: 1 addition & 40 deletions yarn-project/circuits.js/src/contract/public_bytecode.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { FunctionSelector } from '@aztec/foundation/abi';
import { Fr } from '@aztec/foundation/fields';
import {
BufferReader,
numToInt32BE,
Expand All @@ -8,9 +7,7 @@ import {
} from '@aztec/foundation/serialize';
import { ContractClass } from '@aztec/types/contracts';

import chunk from 'lodash.chunk';

import { FUNCTION_SELECTOR_NUM_BYTES, MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS } from '../constants.gen.js';
import { FUNCTION_SELECTOR_NUM_BYTES } from '../constants.gen.js';

/**
* Packs together a set of public functions for a contract class.
Expand All @@ -36,39 +33,3 @@ export function unpackBytecode(buffer: Buffer): ContractClass['publicFunctions']
}),
});
}

/**
* Formats packed bytecode as an array of fields. Splits the input into 31-byte chunks, and stores each
* of them into a field, omitting the field's first byte, then adds zero-fields at the end until the max length.
* @param packedBytecode - Packed bytecode for a contract.
* @returns A field with the total length in bytes, followed by an array of fields such that their concatenation is equal to the input buffer.
* @remarks This function is more generic than just for packed bytecode, perhaps it could be moved elsewhere.
*/
export function packedBytecodeAsFields(packedBytecode: Buffer): Fr[] {
const encoded = [
new Fr(packedBytecode.length),
...chunk(packedBytecode, Fr.SIZE_IN_BYTES - 1).map(c => {
const fieldBytes = Buffer.alloc(32);
Buffer.from(c).copy(fieldBytes, 1);
return Fr.fromBuffer(fieldBytes);
}),
];
if (encoded.length > MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS) {
throw new Error(
`Packed bytecode exceeds maximum size: got ${encoded.length} but max is ${MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS}`,
);
}
// Fun fact: we cannot use padArrayEnd here since typescript cannot deal with a Tuple this big
return [...encoded, ...Array(MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS - encoded.length).fill(Fr.ZERO)];
}

/**
* Recovers packed bytecode from an array of fields.
* @param fields - An output from packedBytecodeAsFields.
* @returns The packed bytecode.
* @remarks This function is more generic than just for packed bytecode, perhaps it could be moved elsewhere.
*/
export function packedBytecodeFromFields(fields: Fr[]): Buffer {
const [length, ...payload] = fields;
return Buffer.concat(payload.map(f => f.toBuffer().subarray(1))).subarray(0, length.toNumber());
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class MerkleTreeCalculator {

computeTree(leaves: Buffer[] = []): MerkleTree {
if (leaves.length === 0) {
// TODO(#4425): We should be returning a number of nodes that matches the tree height.
return new MerkleTree(this.height, [this.zeroHashes[this.zeroHashes.length - 1]]);
}

Expand Down
Loading
Loading