diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 4c31fc92340..67e3d526332 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -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; @@ -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; diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index 206a333645e..473c3da3abd 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -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'; @@ -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', ); /** @@ -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; } diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index dffbde30431..56dd6070b8c 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -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; @@ -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; diff --git a/yarn-project/circuits.js/src/contract/artifact_hash.test.ts b/yarn-project/circuits.js/src/contract/artifact_hash.test.ts index e5584ba3904..c3b04e7feaa 100644 --- a/yarn-project/circuits.js/src/contract/artifact_hash.test.ts +++ b/yarn-project/circuits.js/src/contract/artifact_hash.test.ts @@ -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"`, ); }); }); diff --git a/yarn-project/circuits.js/src/contract/artifact_hash.ts b/yarn-project/circuits.js/src/contract/artifact_hash.ts index 88a0c122d43..f802ae40cf8 100644 --- a/yarn-project/circuits.js/src/contract/artifact_hash.ts +++ b/yarn-project/circuits.js/src/contract/artifact_hash.ts @@ -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; @@ -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])); } diff --git a/yarn-project/circuits.js/src/contract/contract_class.ts b/yarn-project/circuits.js/src/contract/contract_class.ts index 588ead02f09..dd32d40588c 100644 --- a/yarn-project/circuits.js/src/contract/contract_class.ts +++ b/yarn-project/circuits.js/src/contract/contract_class.ts @@ -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'; @@ -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); const publicFunctions: ContractClass['publicFunctions'] = artifact.functions .filter(f => f.functionType === FunctionType.OPEN) .map(f => ({ diff --git a/yarn-project/circuits.js/src/contract/contract_class_registered_event.ts b/yarn-project/circuits.js/src/contract/contract_class_registered_event.ts index 5979413657c..d0acde29dac 100644 --- a/yarn-project/circuits.js/src/contract/contract_class_registered_event.ts +++ b/yarn-project/circuits.js/src/contract/contract_class_registered_event.ts @@ -1,3 +1,4 @@ +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'; @@ -5,9 +6,9 @@ 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 { @@ -20,12 +21,12 @@ 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)); @@ -33,7 +34,7 @@ export class ContractClassRegisteredEvent { 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), ); diff --git a/yarn-project/circuits.js/src/contract/public_bytecode.test.ts b/yarn-project/circuits.js/src/contract/public_bytecode.test.ts index 374fe119b1a..e91e8b5cd9f 100644 --- a/yarn-project/circuits.js/src/contract/public_bytecode.test.ts +++ b/yarn-project/circuits.js/src/contract/public_bytecode.test.ts @@ -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; @@ -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')); - }); }); diff --git a/yarn-project/circuits.js/src/contract/public_bytecode.ts b/yarn-project/circuits.js/src/contract/public_bytecode.ts index 82b6b082884..712e5579299 100644 --- a/yarn-project/circuits.js/src/contract/public_bytecode.ts +++ b/yarn-project/circuits.js/src/contract/public_bytecode.ts @@ -1,5 +1,4 @@ import { FunctionSelector } from '@aztec/foundation/abi'; -import { Fr } from '@aztec/foundation/fields'; import { BufferReader, numToInt32BE, @@ -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. @@ -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()); -} diff --git a/yarn-project/circuits.js/src/merkle/merkle_tree_calculator.ts b/yarn-project/circuits.js/src/merkle/merkle_tree_calculator.ts index 5f2f4a2c727..388d08a5f2d 100644 --- a/yarn-project/circuits.js/src/merkle/merkle_tree_calculator.ts +++ b/yarn-project/circuits.js/src/merkle/merkle_tree_calculator.ts @@ -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]]); } diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts index 983eff7e4bb..cddb33e1dec 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts @@ -6,12 +6,14 @@ import { Contract, ContractArtifact, ContractBase, + ContractClassWithId, ContractDeployer, DebugLogger, EthAddress, Fr, PXE, SignerlessWallet, + TxHash, TxStatus, Wallet, getContractClassFromArtifact, @@ -19,11 +21,20 @@ import { isContractDeployed, } from '@aztec/aztec.js'; import { + ARTIFACT_FUNCTION_TREE_MAX_HEIGHT, + MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS, + MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS, + computeArtifactFunctionTree, + computeArtifactFunctionTreeRoot, + computeArtifactMetadataHash, + computeFunctionArtifactHash, computePrivateFunctionsRoot, + computePrivateFunctionsTree, computePublicBytecodeCommitment, - packedBytecodeAsFields, } from '@aztec/circuits.js'; import { siloNullifier } from '@aztec/circuits.js/abis'; +import { FunctionSelector, FunctionType, bufferAsFields } from '@aztec/foundation/abi'; +import { padArrayEnd } from '@aztec/foundation/collection'; import { ContractClassRegistererContract, ReaderContractArtifact, StatefulTestContract } from '@aztec/noir-contracts'; import { TestContract, TestContractArtifact } from '@aztec/noir-contracts/Test'; import { TokenContractArtifact } from '@aztec/noir-contracts/Token'; @@ -40,11 +51,11 @@ describe('e2e_deploy_contract', () => { let aztecNode: AztecNode; let teardown: () => Promise; - beforeEach(async () => { + beforeAll(async () => { ({ teardown, pxe, accounts, logger, wallet, sequencer, aztecNode } = await setup()); }, 100_000); - afterEach(() => teardown()); + afterAll(() => teardown()); /** * Milestone 1.1. @@ -252,38 +263,119 @@ describe('e2e_deploy_contract', () => { // Tests registering a new contract class on a node // All this dance will be hidden behind a nicer API in the near future! - it('registers a new contract class via the class registerer contract', async () => { - const registerer = await registerContract(wallet, ContractClassRegistererContract, [], new Fr(1)); - const contractClass = getContractClassFromArtifact(ReaderContractArtifact); - const privateFunctionsRoot = computePrivateFunctionsRoot(contractClass.privateFunctions); - const publicBytecodeCommitment = computePublicBytecodeCommitment(contractClass.packedBytecode); - - logger(`contractClass.id: ${contractClass.id}`); - logger(`contractClass.artifactHash: ${contractClass.artifactHash}`); - logger(`contractClass.privateFunctionsRoot: ${privateFunctionsRoot}`); - logger(`contractClass.publicBytecodeCommitment: ${publicBytecodeCommitment}`); - logger(`contractClass.packedBytecode.length: ${contractClass.packedBytecode.length}`); - - const tx = await registerer.methods - .register( - contractClass.artifactHash, - privateFunctionsRoot, - publicBytecodeCommitment, - packedBytecodeAsFields(contractClass.packedBytecode), - ) - .send() - .wait(); - - const logs = await pxe.getUnencryptedLogs({ txHash: tx.txHash }); - const registeredLog = logs.logs[0].log; // We need a nicer API! - expect(registeredLog.contractAddress).toEqual(registerer.address); - - const registeredClass = await aztecNode.getContractClass(contractClass.id); - expect(registeredClass).toBeDefined(); - expect(registeredClass?.artifactHash.toString()).toEqual(contractClass.artifactHash.toString()); - expect(registeredClass?.privateFunctionsRoot.toString()).toEqual(privateFunctionsRoot.toString()); - expect(registeredClass?.packedBytecode.toString('hex')).toEqual(contractClass.packedBytecode.toString('hex')); - expect(registeredClass?.publicFunctions).toEqual(contractClass.publicFunctions); + describe('registering a new contract class', () => { + let registerer: ContractClassRegistererContract; + let artifact: ContractArtifact; + let contractClass: ContractClassWithId; + let registerTxHash: TxHash; + let privateFunctionsRoot: Fr; + let publicBytecodeCommitment: Fr; + + beforeAll(async () => { + artifact = ReaderContractArtifact; + contractClass = getContractClassFromArtifact(artifact); + privateFunctionsRoot = computePrivateFunctionsRoot(contractClass.privateFunctions); + publicBytecodeCommitment = computePublicBytecodeCommitment(contractClass.packedBytecode); + registerer = await registerContract(wallet, ContractClassRegistererContract, [], new Fr(1)); + + logger(`contractClass.id: ${contractClass.id}`); + logger(`contractClass.artifactHash: ${contractClass.artifactHash}`); + logger(`contractClass.privateFunctionsRoot: ${privateFunctionsRoot}`); + logger(`contractClass.publicBytecodeCommitment: ${publicBytecodeCommitment}`); + logger(`contractClass.packedBytecode.length: ${contractClass.packedBytecode.length}`); + + // Broadcast the class public bytecode via the registerer contract + const tx = await registerer.methods + .register( + contractClass.artifactHash, + privateFunctionsRoot, + publicBytecodeCommitment, + bufferAsFields(contractClass.packedBytecode, MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS), + ) + .send() + .wait(); + registerTxHash = tx.txHash; + }); + + it('emits registered logs', async () => { + const logs = await pxe.getUnencryptedLogs({ txHash: registerTxHash }); + const registeredLog = logs.logs[0].log; // We need a nicer API! + expect(registeredLog.contractAddress).toEqual(registerer.address); + }); + + it('registers the contract class on the node', async () => { + const registeredClass = await aztecNode.getContractClass(contractClass.id); + expect(registeredClass).toBeDefined(); + expect(registeredClass!.artifactHash.toString()).toEqual(contractClass.artifactHash.toString()); + expect(registeredClass!.privateFunctionsRoot.toString()).toEqual(privateFunctionsRoot.toString()); + expect(registeredClass!.packedBytecode.toString('hex')).toEqual(contractClass.packedBytecode.toString('hex')); + expect(registeredClass!.publicFunctions).toEqual(contractClass.publicFunctions); + expect(registeredClass!.privateFunctions).toEqual([]); + }); + + it('broadcasts a private function and registers it on the node', async () => { + const privateFunction = contractClass.privateFunctions[0]; + const privateFunctionArtifact = artifact.functions.find(fn => + FunctionSelector.fromNameAndParameters(fn).equals(privateFunction.selector), + )!; + + // TODO(@spalladino): The following is computing the unconstrained root hash twice. + // Feels like we need a nicer API for returning a hash along with all its preimages, + // since it's common to provide all hash preimages to a function that verifies them. + const artifactMetadataHash = computeArtifactMetadataHash(artifact); + const unconstrainedArtifactFunctionTreeRoot = computeArtifactFunctionTreeRoot(artifact, FunctionType.OPEN); + const privateFunctionTreePath = computePrivateFunctionsTree(contractClass.privateFunctions).getSiblingPath(0); + const artifactFunctionTreePath = computeArtifactFunctionTree(artifact, FunctionType.SECRET)!.getSiblingPath(0); + + const selector = privateFunction.selector; + const metadataHash = computeFunctionArtifactHash(privateFunctionArtifact); + const bytecode = bufferAsFields( + Buffer.from(privateFunctionArtifact.bytecode, 'hex'), + MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS, + ); + const vkHash = privateFunction.vkHash; + + await registerer.methods + .broadcast_private_function( + contractClass.id, + Fr.fromBufferReduce(artifactMetadataHash), + Fr.fromBufferReduce(unconstrainedArtifactFunctionTreeRoot), + privateFunctionTreePath.map(Fr.fromBufferReduce), + padArrayEnd(artifactFunctionTreePath.map(Fr.fromBufferReduce), Fr.ZERO, ARTIFACT_FUNCTION_TREE_MAX_HEIGHT), + // eslint-disable-next-line camelcase + { selector, metadata_hash: Fr.fromBufferReduce(metadataHash), bytecode, vk_hash: vkHash }, + ) + .send() + .wait(); + }, 60_000); + + it('broadcasts an unconstrained function', async () => { + const functionArtifact = artifact.functions.find(fn => fn.functionType === FunctionType.UNCONSTRAINED)!; + + // TODO(@spalladino): Same comment as above on computing duplicated hashes. + const artifactMetadataHash = computeArtifactMetadataHash(artifact); + const privateArtifactFunctionTreeRoot = computeArtifactFunctionTreeRoot(artifact, FunctionType.SECRET); + const functionTreePath = computeArtifactFunctionTree(artifact, FunctionType.UNCONSTRAINED)!.getSiblingPath(0); + + const selector = FunctionSelector.fromNameAndParameters(functionArtifact); + const metadataHash = computeFunctionArtifactHash(functionArtifact); + const bytecode = bufferAsFields( + Buffer.from(functionArtifact.bytecode, 'hex'), + MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS, + ); + + await registerer.methods + .broadcast_unconstrained_function( + contractClass.id, + Fr.fromBufferReduce(artifactMetadataHash), + Fr.fromBufferReduce(privateArtifactFunctionTreeRoot), + padArrayEnd(functionTreePath.map(Fr.fromBufferReduce), Fr.ZERO, ARTIFACT_FUNCTION_TREE_MAX_HEIGHT), + // eslint-disable-next-line camelcase + { selector, metadata_hash: Fr.fromBufferReduce(metadataHash), bytecode }, + ) + .send() + .wait(); + }, 60_000); }); }); diff --git a/yarn-project/foundation/package.json b/yarn-project/foundation/package.json index 35d2d9480e2..766c4dcaa2a 100644 --- a/yarn-project/foundation/package.json +++ b/yarn-project/foundation/package.json @@ -75,6 +75,7 @@ "koa-router": "^12.0.0", "leveldown": "^6.1.1", "levelup": "^5.1.1", + "lodash.chunk": "^4.2.0", "lodash.clonedeepwith": "^4.5.0", "memdown": "^6.1.1", "pako": "^2.1.0", @@ -96,6 +97,7 @@ "@types/koa__cors": "^4.0.0", "@types/leveldown": "^4.0.3", "@types/levelup": "^5.1.2", + "@types/lodash.chunk": "^4.2.9", "@types/lodash.clonedeepwith": "^4.5.7", "@types/memdown": "^3.0.1", "@types/node": "^18.7.23", diff --git a/yarn-project/foundation/src/abi/abi_coder.ts b/yarn-project/foundation/src/abi/abi_coder.ts deleted file mode 100644 index a702e65153a..00000000000 --- a/yarn-project/foundation/src/abi/abi_coder.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { type ABIType } from './abi.js'; - -/** - * Get the size of an ABI type in field elements. - * @param type - The ABI type. - * @returns The size of the type in field elements. - */ -export function sizeOfType(type: ABIType): number { - switch (type.kind) { - case 'field': - case 'boolean': - case 'integer': - return 1; - case 'string': - return type.length; - case 'array': - return type.length * sizeOfType(type.type); - case 'struct': - return type.fields.reduce((sum, field) => sum + sizeOfType(field.type), 0); - default: { - const exhaustiveCheck: never = type; - throw new Error(`Unhandled abi type: ${exhaustiveCheck}`); - } - } -} diff --git a/yarn-project/foundation/src/abi/buffer.test.ts b/yarn-project/foundation/src/abi/buffer.test.ts new file mode 100644 index 00000000000..9c3329666c3 --- /dev/null +++ b/yarn-project/foundation/src/abi/buffer.test.ts @@ -0,0 +1,14 @@ +import { bufferAsFields, bufferFromFields } from './buffer.js'; + +describe('buffer', () => { + it('converts buffer back and forth from fields', () => { + const buffer = Buffer.from('1234567890abcdef'.repeat(10), 'hex'); + const fields = bufferAsFields(buffer, 20); + expect(bufferFromFields(fields).toString('hex')).toEqual(buffer.toString('hex')); + }); + + it('throws if max length is exceeded', () => { + const buffer = Buffer.from('1234567890abcdef'.repeat(10), 'hex'); + expect(() => bufferAsFields(buffer, 3)).toThrow(/exceeds maximum size/); + }); +}); diff --git a/yarn-project/foundation/src/abi/buffer.ts b/yarn-project/foundation/src/abi/buffer.ts new file mode 100644 index 00000000000..b4c45af4c46 --- /dev/null +++ b/yarn-project/foundation/src/abi/buffer.ts @@ -0,0 +1,36 @@ +import chunk from 'lodash.chunk'; + +import { Fr } from '../fields/fields.js'; + +/** + * Formats a buffer 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 input - Input to format. + * @param targetLength - Length of the target array in number of fields. + * @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, followed by enough zeroes to reach targetLength. + */ +export function bufferAsFields(input: Buffer, targetLength: number): Fr[] { + const encoded = [ + new Fr(input.length), + ...chunk(input, 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 > targetLength) { + throw new Error(`Input buffer exceeds maximum size: got ${encoded.length} but max is ${targetLength}`); + } + // Fun fact: we cannot use padArrayEnd here since typescript cannot deal with a Tuple this big + return [...encoded, ...Array(targetLength - encoded.length).fill(Fr.ZERO)]; +} + +/** + * Recovers a buffer from an array of fields. + * @param fields - An output from bufferAsFields. + * @returns The recovered buffer. + */ +export function bufferFromFields(fields: Fr[]): Buffer { + const [length, ...payload] = fields; + return Buffer.concat(payload.map(f => f.toBuffer().subarray(1))).subarray(0, length.toNumber()); +} diff --git a/yarn-project/foundation/src/abi/index.ts b/yarn-project/foundation/src/abi/index.ts index 8369df9cd6e..3fd28c16cc4 100644 --- a/yarn-project/foundation/src/abi/index.ts +++ b/yarn-project/foundation/src/abi/index.ts @@ -1,5 +1,5 @@ export * from './abi.js'; -export * from './abi_coder.js'; +export * from './buffer.js'; export * from './encoder.js'; export * from './decoder.js'; export * from './selector.js'; diff --git a/yarn-project/foundation/src/abi/selector.ts b/yarn-project/foundation/src/abi/selector.ts index 760d3bd0931..c4bbf40dbf7 100644 --- a/yarn-project/foundation/src/abi/selector.ts +++ b/yarn-project/foundation/src/abi/selector.ts @@ -154,7 +154,14 @@ export class FunctionSelector extends Selector { * @param parameters - An array of ABIParameter objects, each containing the type information of a function parameter. * @returns A Buffer containing the 4-byte selector. */ - static fromNameAndParameters(name: string, parameters: ABIParameter[]) { + static fromNameAndParameters(args: { name: string; parameters: ABIParameter[] }): FunctionSelector; + static fromNameAndParameters(name: string, parameters: ABIParameter[]): FunctionSelector; + static fromNameAndParameters( + nameOrArgs: string | { name: string; parameters: ABIParameter[] }, + maybeParameters?: ABIParameter[], + ): FunctionSelector { + const { name, parameters } = + typeof nameOrArgs === 'string' ? { name: nameOrArgs, parameters: maybeParameters! } : nameOrArgs; const signature = decodeFunctionSignature(name, parameters); const selector = this.fromSignature(signature); // If using the debug logger here it kill the typing in the `server_world_state_synchronizer` and jest tests. diff --git a/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/events.nr b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/events.nr new file mode 100644 index 00000000000..b453b9acda4 --- /dev/null +++ b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/events.nr @@ -0,0 +1,3 @@ +mod class_registered; +mod private_function_broadcasted; +mod unconstrained_function_broadcasted; diff --git a/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/events/class_registered.nr b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/events/class_registered.nr new file mode 100644 index 00000000000..7fc4812ab61 --- /dev/null +++ b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/events/class_registered.nr @@ -0,0 +1,29 @@ +use dep::aztec::protocol_types::{ + contract_class::ContractClassId, + constants::{MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS, REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE}, + traits::{Serialize} +}; + +// #[event] +struct ContractClassRegistered { + contract_class_id: ContractClassId, + version: Field, + artifact_hash: Field, + private_functions_root: Field, + packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS], +} + +impl Serialize for ContractClassRegistered { + fn serialize(self: Self) -> [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS + 5] { + let mut packed = [0; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS + 5]; + packed[0] = REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE; + packed[1] = self.contract_class_id.to_field(); + packed[2] = self.version; + packed[3] = self.artifact_hash; + packed[4] = self.private_functions_root; + for i in 0..MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS { + packed[i + 5] = self.packed_public_bytecode[i]; + } + packed + } +} diff --git a/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/events/private_function_broadcasted.nr b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/events/private_function_broadcasted.nr new file mode 100644 index 00000000000..6f1b9c07117 --- /dev/null +++ b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/events/private_function_broadcasted.nr @@ -0,0 +1,58 @@ +use dep::aztec::protocol_types; +use dep::aztec::protocol_types::{ + contract_class::ContractClassId, + abis::function_selector::FunctionSelector, + constants::{FUNCTION_TREE_HEIGHT, ARTIFACT_FUNCTION_TREE_MAX_HEIGHT, MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS, REGISTERER_PRIVATE_FUNCTION_BROADCASTED_MAGIC_VALUE}, + traits::{Serialize} +}; + +struct PrivateFunction { + selector: FunctionSelector, + metadata_hash: Field, + vk_hash: Field, + bytecode: [Field; MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS], +} + +impl Serialize for PrivateFunction { + fn serialize(self: Self) -> [Field; MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS + 3] { + let mut packed = [0; MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS + 3]; + packed[0] = self.selector.to_field(); + packed[1] = self.metadata_hash; + packed[2] = self.vk_hash; + for i in 0..MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS { + packed[i + 3] = self.bytecode[i]; + } + packed + } +} + +// #[event] +struct ClassPrivateFunctionBroadcasted { + contract_class_id: ContractClassId, + artifact_metadata_hash: Field, + unconstrained_functions_artifact_tree_root: Field, + private_function_tree_sibling_path: [Field; FUNCTION_TREE_HEIGHT], + artifact_function_tree_sibling_path: [Field; ARTIFACT_FUNCTION_TREE_MAX_HEIGHT], + function: PrivateFunction +} + +impl Serialize for ClassPrivateFunctionBroadcasted { + fn serialize(self: Self) -> [Field; MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS + 17] { + let mut packed = [0; MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS + 17]; + packed[0] = REGISTERER_PRIVATE_FUNCTION_BROADCASTED_MAGIC_VALUE; + packed[1] = self.contract_class_id.to_field(); + packed[2] = self.artifact_metadata_hash; + packed[3] = self.unconstrained_functions_artifact_tree_root; + for i in 0..FUNCTION_TREE_HEIGHT { + packed[i + 4] = self.private_function_tree_sibling_path[i]; + } + for i in 0..ARTIFACT_FUNCTION_TREE_MAX_HEIGHT { + packed[i + 4 + FUNCTION_TREE_HEIGHT] = self.private_function_tree_sibling_path[i]; + } + let packed_function = self.function.serialize(); + for i in 0..MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS + 3 { + packed[i + 4 + ARTIFACT_FUNCTION_TREE_MAX_HEIGHT + FUNCTION_TREE_HEIGHT] = packed_function[i]; + } + packed + } +} diff --git a/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/events/unconstrained_function_broadcasted.nr b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/events/unconstrained_function_broadcasted.nr new file mode 100644 index 00000000000..6ee5bb3bc26 --- /dev/null +++ b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/events/unconstrained_function_broadcasted.nr @@ -0,0 +1,52 @@ +use dep::aztec::protocol_types; +use dep::aztec::protocol_types::{ + contract_class::ContractClassId, + abis::function_selector::FunctionSelector, + constants::{ARTIFACT_FUNCTION_TREE_MAX_HEIGHT, MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS, REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE}, + traits::{Serialize} +}; + +struct UnconstrainedFunction { + selector: FunctionSelector, + metadata_hash: Field, + bytecode: [Field; MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS], +} + +impl Serialize for UnconstrainedFunction { + fn serialize(self: Self) -> [Field; MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS + 2] { + let mut packed = [0; MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS + 2]; + packed[0] = self.selector.to_field(); + packed[1] = self.metadata_hash; + for i in 0..MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS { + packed[i + 2] = self.bytecode[i]; + } + packed + } +} + +// #[event] +struct ClassUnconstrainedFunctionBroadcasted { + contract_class_id: ContractClassId, + artifact_metadata_hash: Field, + private_functions_artifact_tree_root: Field, + artifact_function_tree_sibling_path: [Field; ARTIFACT_FUNCTION_TREE_MAX_HEIGHT], + function: UnconstrainedFunction +} + +impl Serialize for ClassUnconstrainedFunctionBroadcasted { + fn serialize(self: Self) -> [Field; MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS + 11] { + let mut packed = [0; MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS + 11]; + packed[0] = REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE; + packed[1] = self.contract_class_id.to_field(); + packed[2] = self.artifact_metadata_hash; + packed[3] = self.private_functions_artifact_tree_root; + for i in 0..ARTIFACT_FUNCTION_TREE_MAX_HEIGHT { + packed[i + 4] = self.artifact_function_tree_sibling_path[i]; + } + let packed_function = self.function.serialize(); + for i in 0..MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS + 2 { + packed[i + 4 + ARTIFACT_FUNCTION_TREE_MAX_HEIGHT] = packed_function[i]; + } + packed + } +} diff --git a/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr index fb45783033a..050a5a5ccd9 100644 --- a/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr @@ -1,39 +1,21 @@ +mod events; + contract ContractClassRegisterer { use dep::std::option::Option; use dep::aztec::protocol_types::{ address::{ AztecAddress, EthAddress }, contract_class::ContractClassId, - constants::{MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS, CONTRACT_CLASS_REGISTERED_MAGIC_VALUE} + constants::{ARTIFACT_FUNCTION_TREE_MAX_HEIGHT, FUNCTION_TREE_HEIGHT, MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS, REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE}, + traits::{Serialize} }; - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/3590): Remove this once the issue is fixed - use dep::aztec::protocol_types; - use dep::aztec::log::{ emit_unencrypted_log, emit_unencrypted_log_from_private}; - #[event] - struct ContractClassRegistered { - contract_class_id: ContractClassId, - version: Field, - artifact_hash: Field, - private_functions_root: Field, - packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS], - } - - impl ContractClassRegistered { - fn serialize(self: Self) -> [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS + 5] { - let mut packed = [0; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS + 5]; - packed[0] = CONTRACT_CLASS_REGISTERED_MAGIC_VALUE; - packed[1] = self.contract_class_id.to_field(); - packed[2] = self.version; - packed[3] = self.artifact_hash; - packed[4] = self.private_functions_root; - for i in 0..MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS { - packed[i + 5] = self.packed_public_bytecode[i]; - } - packed - } - } + use crate::events::{ + class_registered::ContractClassRegistered, + private_function_broadcasted::{ClassPrivateFunctionBroadcasted, PrivateFunction}, + unconstrained_function_broadcasted::{ClassUnconstrainedFunctionBroadcasted, UnconstrainedFunction} + }; #[aztec(private)] fn constructor() {} @@ -64,4 +46,46 @@ contract ContractClassRegisterer { dep::aztec::oracle::debug_log::debug_log_array_with_prefix("ContractClassRegistered", event_payload); emit_unencrypted_log_from_private(&mut context, event_payload); } + + #[aztec(private)] + fn broadcast_private_function( + contract_class_id: ContractClassId, + artifact_metadata_hash: Field, + unconstrained_functions_artifact_tree_root: Field, + private_function_tree_sibling_path: [Field; FUNCTION_TREE_HEIGHT], + artifact_function_tree_sibling_path: [Field; ARTIFACT_FUNCTION_TREE_MAX_HEIGHT], + function_data: PrivateFunction + ) { + let event = ClassPrivateFunctionBroadcasted { + contract_class_id, + artifact_metadata_hash, + unconstrained_functions_artifact_tree_root, + private_function_tree_sibling_path, + artifact_function_tree_sibling_path, + function: function_data + }; + let event_payload = event.serialize(); + dep::aztec::oracle::debug_log::debug_log_array_with_prefix("ClassPrivateFunctionBroadcasted", event_payload); + emit_unencrypted_log_from_private(&mut context, event_payload); + } + + #[aztec(private)] + fn broadcast_unconstrained_function( + contract_class_id: ContractClassId, + artifact_metadata_hash: Field, + private_functions_artifact_tree_root: Field, + artifact_function_tree_sibling_path: [Field; ARTIFACT_FUNCTION_TREE_MAX_HEIGHT], + function_data: UnconstrainedFunction + ) { + let event = ClassUnconstrainedFunctionBroadcasted { + contract_class_id, + artifact_metadata_hash, + private_functions_artifact_tree_root, + artifact_function_tree_sibling_path, + function: function_data + }; + let event_payload = event.serialize(); + dep::aztec::oracle::debug_log::debug_log_array_with_prefix("ClassUnconstrainedFunctionBroadcasted", event_payload); + emit_unencrypted_log_from_private(&mut context, event_payload); + } } diff --git a/yarn-project/noir-contracts/contracts/reader_contract/src/main.nr b/yarn-project/noir-contracts/contracts/reader_contract/src/main.nr index 928d80b908b..7139b9332dc 100644 --- a/yarn-project/noir-contracts/contracts/reader_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/reader_contract/src/main.nr @@ -7,7 +7,6 @@ contract Reader { use dep::compressed_string::FieldCompressedString; #[aztec(private)] - fn constructor() {} #[aztec(public)] @@ -20,7 +19,7 @@ contract Reader { } #[aztec(private)] - fn check_name_private(who: AztecAddress, what: str<31>) { + fn check_name_private(who: AztecAddress, what: str<31>) { let selector = FunctionSelector::from_signature("private_get_name()"); let ret = context.call_private_function_no_args(who, selector); let name = FieldCompressedString::from_field(ret[0]); @@ -28,6 +27,11 @@ contract Reader { assert(name.is_eq(_what)); } + unconstrained fn get_name(who: AztecAddress) -> pub str<6> { + // We cannot yet call an unconstrained function from another + "Reader" + } + #[aztec(public)] fn check_symbol_public(who: AztecAddress, what: str<31>) { let selector = FunctionSelector::from_signature("public_get_symbol()"); @@ -46,6 +50,11 @@ contract Reader { assert(symbol.is_eq(_what)); } + unconstrained fn get_symbol(who: AztecAddress) -> pub str<3> { + // We cannot yet call an unconstrained function from another + "RDR" + } + #[aztec(public)] fn check_decimals_public(who: AztecAddress, what: u8) { let selector = FunctionSelector::from_signature("public_get_decimals()"); @@ -59,4 +68,9 @@ contract Reader { let ret = context.call_private_function_no_args(who, selector); assert(ret[0] as u8 == what); } + + unconstrained fn get_decimals(who: AztecAddress) -> pub u8 { + // We cannot yet call an unconstrained function from another + 18 + } } diff --git a/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr b/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr index 2fcc401cd9e..406b0842f5c 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr @@ -65,6 +65,7 @@ global PUBLIC_DATA_TREE_HEIGHT: Field = 40; global NULLIFIER_TREE_HEIGHT: Field = 20; global L1_TO_L2_MSG_TREE_HEIGHT: Field = 16; global ROLLUP_VK_TREE_HEIGHT: Field = 8; +global ARTIFACT_FUNCTION_TREE_MAX_HEIGHT = 5; // SUB-TREES RELATED CONSTANTS global CONTRACT_SUBTREE_HEIGHT: Field = 0; @@ -86,15 +87,26 @@ global MAPPING_SLOT_PEDERSEN_SEPARATOR: Field = 4; global NUM_FIELDS_PER_SHA256: Field = 2; global ARGS_HASH_CHUNK_LENGTH: u32 = 32; global ARGS_HASH_CHUNK_COUNT: u32 = 32; -// This should be around 8192 (assuming 2**15 instructions packed at 8 bytes each), + +// CONTRACT CLASS CONSTANTS +// This should be around 8192 (assuming 2**15 instructions packed at 8 bytes each), // but it's reduced to speed up build times, otherwise the ClassRegisterer takes over 5 mins to compile. // We are not using 1024 so we can squeeze in a few more args to methods that consume packed public bytecode, // such as the ClassRegisterer.register, and still land below the 32 * 32 max args limit for hashing. global MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS: Field = 1000; +// Bytecode size for private functions is per function, not for the entire contract. +// Note that private functions bytecode includes a mix of acir and brillig. +global MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS: Field = 500; +// Same for unconstrained functions: the size is per function. +global MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS: Field = 500; // Since we are not yet emitting selectors we'll use this magic value to identify events emitted by the ClassRegisterer. // This is just a stopgap until we implement proper selectors. -// This is the sha224sum of 'struct ContractClassRegistered {contract_class_id: ContractClassId, version: Field, artifact_hash: Field, private_functions_root: Field, packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS] }' -global CONTRACT_CLASS_REGISTERED_MAGIC_VALUE = 0x6999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f8; +// sha224sum 'struct ContractClassRegistered {contract_class_id: ContractClassId, version: Field, artifact_hash: Field, private_functions_root: Field, packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS] }' +global REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE = 0x6999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f8; +// sha224sum 'struct ClassPrivateFunctionBroadcasted' +global REGISTERER_PRIVATE_FUNCTION_BROADCASTED_MAGIC_VALUE = 0x1b70e95fde0b70adc30496b90a327af6a5e383e028e7a43211a07bcd; +// sha224sum 'struct ClassUnconstrainedFunctionBroadcasted' +global REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE = 0xe7af816635466f128568edb04c9fa024f6c87fb9010fdbffa68b3d99; // NOIR CONSTANTS - constants used only in yarn-packages/noir-contracts // Some are defined here because Noir doesn't yet support globals referencing other globals yet. diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index a90970194bf..6cf72b9ef25 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -44,9 +44,9 @@ import { MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, PartialAddress, PublicCallRequest, + computeArtifactHash, computeContractClassId, computeSaltedInitializationHash, - getArtifactHash, getContractClassFromArtifact, } from '@aztec/circuits.js'; import { computeCommitmentNonce, siloNullifier } from '@aztec/circuits.js/abis'; @@ -232,7 +232,7 @@ export class PXEService implements PXE { private async addArtifactsAndInstancesFromDeployedContracts(contracts: DeployedContract[]) { for (const contract of contracts) { const artifact = contract.artifact; - const artifactHash = getArtifactHash(artifact); + const artifactHash = computeArtifactHash(artifact); const contractClassId = computeContractClassId(getContractClassFromArtifact({ ...artifact, artifactHash })); await this.db.addContractArtifact(contractClassId, artifact); await this.db.addContractInstance(contract.instance); diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 472b7df6b99..432c8187aad 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -496,6 +496,7 @@ __metadata: "@types/koa__cors": ^4.0.0 "@types/leveldown": ^4.0.3 "@types/levelup": ^5.1.2 + "@types/lodash.chunk": ^4.2.9 "@types/lodash.clonedeepwith": ^4.5.7 "@types/memdown": ^3.0.1 "@types/node": ^18.7.23 @@ -521,6 +522,7 @@ __metadata: koa-router: ^12.0.0 leveldown: ^6.1.1 levelup: ^5.1.1 + lodash.chunk: ^4.2.0 lodash.clonedeepwith: ^4.5.0 memdown: ^6.1.1 pako: ^2.1.0 @@ -3387,7 +3389,7 @@ __metadata: languageName: node linkType: hard -"@types/lodash.chunk@npm:^4.2.7": +"@types/lodash.chunk@npm:^4.2.7, @types/lodash.chunk@npm:^4.2.9": version: 4.2.9 resolution: "@types/lodash.chunk@npm:4.2.9" dependencies: