diff --git a/boxes/package.json b/boxes/package.json index 9b0417032de..f5c77e660e7 100644 --- a/boxes/package.json +++ b/boxes/package.json @@ -19,6 +19,7 @@ "@aztec/bb.js": "portal:../barretenberg/ts", "@aztec/circuit-types": "portal:../yarn-project/circuit-types", "@aztec/ethereum": "portal:../yarn-project/ethereum", + "@aztec/protocol-contracts": "portal:../yarn-project/protocol-contracts", "@aztec/types": "portal:../yarn-project/types" } } diff --git a/boxes/yarn.lock b/boxes/yarn.lock index 2b37f7139af..bdbf09c7ce4 100644 --- a/boxes/yarn.lock +++ b/boxes/yarn.lock @@ -70,6 +70,7 @@ __metadata: "@aztec/circuits.js": "workspace:^" "@aztec/ethereum": "workspace:^" "@aztec/foundation": "workspace:^" + "@aztec/protocol-contracts": "workspace:^" "@aztec/types": "workspace:^" tslib: "npm:^2.4.0" languageName: node @@ -289,6 +290,7 @@ __metadata: koa-router: "npm:^12.0.0" leveldown: "npm:^6.1.1" levelup: "npm:^5.1.1" + lodash.chunk: "npm:^4.2.0" lodash.clonedeepwith: "npm:^4.5.0" memdown: "npm:^6.1.1" pako: "npm:^2.1.0" @@ -297,6 +299,18 @@ __metadata: languageName: node linkType: soft +"@aztec/protocol-contracts@portal:../yarn-project/protocol-contracts::locator=%40aztec%2Fboxes%40workspace%3A.": + version: 0.0.0-use.local + resolution: "@aztec/protocol-contracts@portal:../yarn-project/protocol-contracts::locator=%40aztec%2Fboxes%40workspace%3A." + dependencies: + "@aztec/circuits.js": "workspace:^" + "@aztec/foundation": "workspace:^" + "@aztec/types": "workspace:^" + lodash.omit: "npm:^4.5.0" + tslib: "npm:^2.4.0" + languageName: node + linkType: soft + "@aztec/types@portal:../yarn-project/types::locator=%40aztec%2Fboxes%40workspace%3A.": version: 0.0.0-use.local resolution: "@aztec/types@portal:../yarn-project/types::locator=%40aztec%2Fboxes%40workspace%3A." @@ -9476,6 +9490,13 @@ __metadata: languageName: node linkType: hard +"lodash.omit@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.omit@npm:4.5.0" + checksum: 3808b9b6faae35177174b6ab327f1177e29c91f1e98dcbccf13a72a6767bba337306449d537a4e0d8a33d2673f10d39bc732e30c4b803274ea0c1168ea60e549 + languageName: node + linkType: hard + "lodash.times@npm:^4.3.2": version: 4.3.2 resolution: "lodash.times@npm:4.3.2" diff --git a/yarn-project/aztec.js/package.json b/yarn-project/aztec.js/package.json index c3e3c461005..7af38fa4dc6 100644 --- a/yarn-project/aztec.js/package.json +++ b/yarn-project/aztec.js/package.json @@ -9,6 +9,7 @@ "./abi": "./dest/api/abi.js", "./account": "./dest/api/account.js", "./aztec_address": "./dest/api/aztec_address.js", + "./deployment": "./dest/api/deployment.js", "./eth_address": "./dest/api/eth_address.js", "./ethereum": "./dest/api/ethereum.js", "./fields": "./dest/api/fields.js", @@ -51,6 +52,7 @@ "@aztec/circuits.js": "workspace:^", "@aztec/ethereum": "workspace:^", "@aztec/foundation": "workspace:^", + "@aztec/protocol-contracts": "workspace:^", "@aztec/types": "workspace:^", "tslib": "^2.4.0" }, diff --git a/yarn-project/aztec.js/src/account_manager/index.ts b/yarn-project/aztec.js/src/account_manager/index.ts index e48e48dc161..954ab75846a 100644 --- a/yarn-project/aztec.js/src/account_manager/index.ts +++ b/yarn-project/aztec.js/src/account_manager/index.ts @@ -7,7 +7,7 @@ import { AccountContract } from '../account/contract.js'; import { Salt } from '../account/index.js'; import { AccountInterface } from '../account/interface.js'; import { DefaultWaitOpts, DeployMethod, WaitOpts } from '../contract/index.js'; -import { ContractDeployer } from '../contract_deployer/index.js'; +import { ContractDeployer } from '../deployment/index.js'; import { waitForAccountSynch } from '../utils/account.js'; import { generatePublicKey } from '../utils/index.js'; import { AccountWalletWithPrivateKey } from '../wallet/index.js'; diff --git a/yarn-project/aztec.js/src/api/deployment.ts b/yarn-project/aztec.js/src/api/deployment.ts new file mode 100644 index 00000000000..f0dd21091cb --- /dev/null +++ b/yarn-project/aztec.js/src/api/deployment.ts @@ -0,0 +1,2 @@ +export { registerContractClass } from '../deployment/register_class.js'; +export { broadcastPrivateFunction, broadcastUnconstrainedFunction } from '../deployment/broadcast_function.js'; diff --git a/yarn-project/aztec.js/src/contract/unsafe_contract.ts b/yarn-project/aztec.js/src/contract/unsafe_contract.ts new file mode 100644 index 00000000000..d010d7121c7 --- /dev/null +++ b/yarn-project/aztec.js/src/contract/unsafe_contract.ts @@ -0,0 +1,19 @@ +import { ContractArtifact } from '@aztec/foundation/abi'; +import { ContractInstanceWithAddress } from '@aztec/types/contracts'; + +import { Wallet } from '../wallet/index.js'; +import { ContractBase } from './contract_base.js'; + +/** Unsafe constructor for ContractBase that bypasses the check that the instance is registered in the wallet. */ +export class UnsafeContract extends ContractBase { + constructor( + /** The deployed contract instance definition. */ + instance: ContractInstanceWithAddress, + /** The Application Binary Interface for the contract. */ + artifact: ContractArtifact, + /** The wallet used for interacting with this contract. */ + wallet: Wallet, + ) { + super(instance, artifact, wallet); + } +} diff --git a/yarn-project/aztec.js/src/deployment/broadcast_function.ts b/yarn-project/aztec.js/src/deployment/broadcast_function.ts new file mode 100644 index 00000000000..785cd1d2619 --- /dev/null +++ b/yarn-project/aztec.js/src/deployment/broadcast_function.ts @@ -0,0 +1,118 @@ +import { + ARTIFACT_FUNCTION_TREE_MAX_HEIGHT, + MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS, + computeArtifactFunctionTree, + computeArtifactFunctionTreeRoot, + computeArtifactMetadataHash, + computeFunctionArtifactHash, + computePrivateFunctionsTree, + getContractClassFromArtifact, +} from '@aztec/circuits.js'; +import { ContractArtifact, FunctionSelector, FunctionType, bufferAsFields } from '@aztec/foundation/abi'; +import { padArrayEnd } from '@aztec/foundation/collection'; +import { Fr } from '@aztec/foundation/fields'; + +import { ContractFunctionInteraction } from '../contract/contract_function_interaction.js'; +import { Wallet } from '../wallet/index.js'; +import { getRegistererContract } from './protocol_contracts.js'; + +/** + * Sets up a call to broadcast a private function's bytecode via the ClassRegisterer contract. + * Note that this is not required for users to call the function, but is rather a convenience to make + * this code publicly available so dapps or wallets do not need to redistribute it. + * @param wallet - Wallet to send the transaction. + * @param artifact - Contract artifact that contains the function to be broadcast. + * @param selector - Selector of the function to be broadcast. + * @returns A ContractFunctionInteraction object that can be used to send the transaction. + */ +export function broadcastPrivateFunction( + wallet: Wallet, + artifact: ContractArtifact, + selector: FunctionSelector, +): ContractFunctionInteraction { + const contractClass = getContractClassFromArtifact(artifact); + const privateFunction = contractClass.privateFunctions.find(fn => fn.selector.equals(selector)); + if (!privateFunction) { + throw new Error(`Private function with selector ${selector.toString()} not found`); + } + const privateFunctionArtifact = artifact.functions.find(fn => + FunctionSelector.fromNameAndParameters(fn).equals(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); + + // We need two sibling paths because private function information is split across two trees: + // The "private function tree" captures the selectors and verification keys, and is used in the kernel circuit for verifying the proof generated by the app circuit. + // The "artifact tree" captures function bytecode and metadata, and is used by the pxe to check that its executing the code it's supposed to be executing, but it never goes into circuits. + const privateFunctionTreePath = computePrivateFunctionsTree(contractClass.privateFunctions).getSiblingPath(0); + const artifactFunctionTreePath = computeArtifactFunctionTree(artifact, FunctionType.SECRET)!.getSiblingPath(0); + + const vkHash = privateFunction.vkHash; + const metadataHash = computeFunctionArtifactHash(privateFunctionArtifact); + const bytecode = bufferAsFields( + Buffer.from(privateFunctionArtifact.bytecode, 'hex'), + MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS, + ); + + const registerer = getRegistererContract(wallet); + return 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 }, + ); +} + +/** + * Sets up a call to broadcast an unconstrained function's bytecode via the ClassRegisterer contract. + * Note that this is not required for users to call the function, but is rather a convenience to make + * this code publicly available so dapps or wallets do not need to redistribute it. + * @param wallet - Wallet to send the transaction. + * @param artifact - Contract artifact that contains the function to be broadcast. + * @param selector - Selector of the function to be broadcast. + * @returns A ContractFunctionInteraction object that can be used to send the transaction. + */ +export function broadcastUnconstrainedFunction( + wallet: Wallet, + artifact: ContractArtifact, + selector: FunctionSelector, +): ContractFunctionInteraction { + const functionArtifactIndex = artifact.functions.findIndex( + fn => fn.functionType === FunctionType.UNCONSTRAINED && FunctionSelector.fromNameAndParameters(fn).equals(selector), + ); + if (functionArtifactIndex < 0) { + throw new Error(`Unconstrained function with selector ${selector.toString()} not found`); + } + const functionArtifact = artifact.functions[functionArtifactIndex]; + + // 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( + functionArtifactIndex, + ); + + const contractClassId = getContractClassFromArtifact(artifact).id; + const metadataHash = computeFunctionArtifactHash(functionArtifact); + const bytecode = bufferAsFields( + Buffer.from(functionArtifact.bytecode, 'hex'), + MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS, + ); + + const registerer = getRegistererContract(wallet); + return registerer.methods.broadcast_unconstrained_function( + contractClassId, + 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 }, + ); +} diff --git a/yarn-project/aztec.js/src/contract_deployer/contract_deployer.test.ts b/yarn-project/aztec.js/src/deployment/contract_deployer.test.ts similarity index 100% rename from yarn-project/aztec.js/src/contract_deployer/contract_deployer.test.ts rename to yarn-project/aztec.js/src/deployment/contract_deployer.test.ts diff --git a/yarn-project/aztec.js/src/contract_deployer/contract_deployer.ts b/yarn-project/aztec.js/src/deployment/contract_deployer.ts similarity index 100% rename from yarn-project/aztec.js/src/contract_deployer/contract_deployer.ts rename to yarn-project/aztec.js/src/deployment/contract_deployer.ts diff --git a/yarn-project/aztec.js/src/contract_deployer/index.ts b/yarn-project/aztec.js/src/deployment/index.ts similarity index 100% rename from yarn-project/aztec.js/src/contract_deployer/index.ts rename to yarn-project/aztec.js/src/deployment/index.ts diff --git a/yarn-project/aztec.js/src/deployment/protocol_contracts.ts b/yarn-project/aztec.js/src/deployment/protocol_contracts.ts new file mode 100644 index 00000000000..b14c64509f5 --- /dev/null +++ b/yarn-project/aztec.js/src/deployment/protocol_contracts.ts @@ -0,0 +1,10 @@ +import { getCanonicalClassRegisterer } from '@aztec/protocol-contracts/class-registerer'; + +import { UnsafeContract } from '../contract/unsafe_contract.js'; +import { Wallet } from '../wallet/index.js'; + +/** Returns a Contract wrapper for the class registerer. */ +export function getRegistererContract(wallet: Wallet) { + const { artifact, instance } = getCanonicalClassRegisterer(); + return new UnsafeContract(instance, artifact, wallet); +} diff --git a/yarn-project/aztec.js/src/deployment/register_class.ts b/yarn-project/aztec.js/src/deployment/register_class.ts new file mode 100644 index 00000000000..4ac0532bef5 --- /dev/null +++ b/yarn-project/aztec.js/src/deployment/register_class.ts @@ -0,0 +1,15 @@ +import { MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS, getContractClassFromArtifact } from '@aztec/circuits.js'; +import { ContractArtifact, bufferAsFields } from '@aztec/foundation/abi'; + +import { ContractFunctionInteraction } from '../contract/contract_function_interaction.js'; +import { Wallet } from '../wallet/index.js'; +import { getRegistererContract } from './protocol_contracts.js'; + +/** Sets up a call to register a contract class given its artifact. */ +export function registerContractClass(wallet: Wallet, artifact: ContractArtifact): ContractFunctionInteraction { + const { artifactHash, privateFunctionsRoot, publicBytecodeCommitment, packedBytecode } = + getContractClassFromArtifact(artifact); + const encodedBytecode = bufferAsFields(packedBytecode, MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS); + const registerer = getRegistererContract(wallet); + return registerer.methods.register(artifactHash, privateFunctionsRoot, publicBytecodeCommitment, encodedBytecode); +} diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index 90f8e8d431d..14f6c88ee6e 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -31,7 +31,7 @@ export { DeploySentTx, } from './contract/index.js'; -export { ContractDeployer } from './contract_deployer/index.js'; +export { ContractDeployer } from './deployment/index.js'; export { generatePublicKey, diff --git a/yarn-project/aztec.js/tsconfig.json b/yarn-project/aztec.js/tsconfig.json index b6f148a8e0d..1d84c77d151 100644 --- a/yarn-project/aztec.js/tsconfig.json +++ b/yarn-project/aztec.js/tsconfig.json @@ -18,6 +18,9 @@ { "path": "../foundation" }, + { + "path": "../protocol-contracts" + }, { "path": "../types" } diff --git a/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap b/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap index 9f9e8537ee8..acb681499c9 100644 --- a/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap +++ b/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap @@ -44,6 +44,8 @@ exports[`ContractClass creates a contract class from a contract compilation arti "isInternal": false } ], - "id": "0x09ad0dad993129857629f13ec2f3463d0c15615bd35266d0fb26c8793c6ee050" + "id": "0x09ad0dad993129857629f13ec2f3463d0c15615bd35266d0fb26c8793c6ee050", + "privateFunctionsRoot": "0x05fa82a96814b6294d557d507151f7ccc12f70522ec4d9d0395a90e87e8087c6", + "publicBytecodeCommitment": "0x05a06c2ee54a742daa2fa43a111c03335df68ff89fd8e7ba938ca2efb225c885" }" `; diff --git a/yarn-project/circuits.js/src/contract/artifact_hash.ts b/yarn-project/circuits.js/src/contract/artifact_hash.ts index f802ae40cf8..e51852a8aa6 100644 --- a/yarn-project/circuits.js/src/contract/artifact_hash.ts +++ b/yarn-project/circuits.js/src/contract/artifact_hash.ts @@ -40,7 +40,8 @@ export function computeArtifactHash(artifact: ContractArtifact): Fr { } 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. + // TODO(@spalladino): Should we use the sorted event selectors instead? They'd need to be unique for that. + const metadata = { name: artifact.name, events: artifact.events }; return sha256(Buffer.from(JSON.stringify(metadata), 'utf-8')); } diff --git a/yarn-project/circuits.js/src/contract/contract_class.ts b/yarn-project/circuits.js/src/contract/contract_class.ts index 7238802fe36..9b39db4d094 100644 --- a/yarn-project/circuits.js/src/contract/contract_class.ts +++ b/yarn-project/circuits.js/src/contract/contract_class.ts @@ -3,7 +3,7 @@ import { Fr } from '@aztec/foundation/fields'; import { ContractClass, ContractClassWithId } from '@aztec/types/contracts'; import { computeArtifactHash } from './artifact_hash.js'; -import { computeContractClassId } from './contract_class_id.js'; +import { ContractClassIdPreimage, computeContractClassIdWithPreimage } from './contract_class_id.js'; import { packBytecode } from './public_bytecode.js'; /** Contract artifact including its artifact hash */ @@ -12,7 +12,7 @@ type ContractArtifactWithHash = ContractArtifact & { artifactHash: Fr }; /** Creates a ContractClass from a contract compilation artifact. */ export function getContractClassFromArtifact( artifact: ContractArtifact | ContractArtifactWithHash, -): ContractClassWithId { +): ContractClassWithId & ContractClassIdPreimage { const artifactHash = 'artifactHash' in artifact ? artifact.artifactHash : computeArtifactHash(artifact); const publicFunctions: ContractClass['publicFunctions'] = artifact.functions .filter(f => f.functionType === FunctionType.OPEN) @@ -36,8 +36,7 @@ export function getContractClassFromArtifact( isInternal: f.isInternal, })), }; - const id = computeContractClassId(contractClass); - return { ...contractClass, id }; + return { ...contractClass, ...computeContractClassIdWithPreimage(contractClass) }; } /** diff --git a/yarn-project/circuits.js/src/contract/contract_class_id.ts b/yarn-project/circuits.js/src/contract/contract_class_id.ts index 08ad4c3fca5..2165225a37c 100644 --- a/yarn-project/circuits.js/src/contract/contract_class_id.ts +++ b/yarn-project/circuits.js/src/contract/contract_class_id.ts @@ -19,15 +19,29 @@ import { computePrivateFunctionsRoot } from './private_function.js'; * @returns The identifier. */ export function computeContractClassId(contractClass: ContractClass | ContractClassIdPreimage): Fr { - const { artifactHash, privateFunctionsRoot, publicBytecodeCommitment } = isContractClassIdPreimage(contractClass) - ? contractClass - : computeContractClassIdPreimage(contractClass); - return Fr.fromBuffer( + return computeContractClassIdWithPreimage(contractClass).id; +} + +/** Computes a contract class id and returns it along with its preimage. */ +export function computeContractClassIdWithPreimage( + contractClass: ContractClass | ContractClassIdPreimage, +): ContractClassIdPreimage & { id: Fr } { + const artifactHash = contractClass.artifactHash; + const privateFunctionsRoot = + 'privateFunctionsRoot' in contractClass + ? contractClass.privateFunctionsRoot + : computePrivateFunctionsRoot(contractClass.privateFunctions); + const publicBytecodeCommitment = + 'publicBytecodeCommitment' in contractClass + ? contractClass.publicBytecodeCommitment + : computePublicBytecodeCommitment(contractClass.packedBytecode); + const id = Fr.fromBuffer( pedersenHash( [artifactHash.toBuffer(), privateFunctionsRoot.toBuffer(), publicBytecodeCommitment.toBuffer()], GeneratorIndex.CONTRACT_LEAF, // TODO(@spalladino): Review all generator indices in this file ), ); + return { id, artifactHash, privateFunctionsRoot, publicBytecodeCommitment }; } /** Returns the preimage of a contract class id given a contract class. */ @@ -44,11 +58,6 @@ export type ContractClassIdPreimage = { publicBytecodeCommitment: Fr; }; -/** Returns whether the given object looks like a ContractClassIdPreimage. */ -function isContractClassIdPreimage(obj: any): obj is ContractClassIdPreimage { - return obj && obj.artifactHash && obj.privateFunctionsRoot && obj.publicBytecodeCommitment; -} - // TODO(@spalladino): Replace with actual implementation export function computePublicBytecodeCommitment(bytecode: Buffer) { return Fr.fromBufferReduce(sha256(bytecode)); diff --git a/yarn-project/cli/src/cmds/unbox.ts b/yarn-project/cli/src/cmds/unbox.ts index b1b6542b5e0..a04702d0e88 100644 --- a/yarn-project/cli/src/cmds/unbox.ts +++ b/yarn-project/cli/src/cmds/unbox.ts @@ -16,6 +16,7 @@ const resolutions: { [key: string]: string } = { '@aztec/bb.js': 'portal:.aztec-packages/barretenberg/ts', '@aztec/circuit-types': 'portal:.aztec-packages/yarn-project/circuit-types', '@aztec/ethereum': 'portal:.aztec-packages/yarn-project/ethereum', + '@aztec/protocol-contracts': 'portal:.aztec-packages/yarn-project/protocol-contracts', '@aztec/types': 'portal:.aztec-packages/yarn-project/types', }; @@ -71,6 +72,7 @@ function copyDependenciesToBox(dirName: string, destPath: string) { 'yarn-project/types', 'yarn-project/circuit-types', 'yarn-project/ethereum', + 'yarn-project/protocol-contracts', ].forEach(path => cpSync(dirName + '/../../../../' + path, destPath + '/.aztec-packages/' + path, { recursive: true, 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 5ce51a687fe..e493dece908 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 @@ -22,28 +22,14 @@ 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, - Point, - PublicKey, - computeArtifactFunctionTree, - computeArtifactFunctionTreeRoot, - computeArtifactMetadataHash, - computeFunctionArtifactHash, - computePrivateFunctionsRoot, - computePrivateFunctionsTree, - computePublicBytecodeCommitment, - computePublicKeysHash, -} from '@aztec/circuits.js'; + broadcastPrivateFunction, + broadcastUnconstrainedFunction, + registerContractClass, +} from '@aztec/aztec.js/deployment'; +import { ContractClassIdPreimage, Point, PublicKey, computePublicKeysHash } 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, - ContractInstanceDeployerContract, - StatefulTestContract, -} from '@aztec/noir-contracts'; +import { FunctionSelector, FunctionType } from '@aztec/foundation/abi'; +import { ContractInstanceDeployerContract, StatefulTestContract } from '@aztec/noir-contracts'; import { TestContract, TestContractArtifact } from '@aztec/noir-contracts/Test'; import { TokenContractArtifact } from '@aztec/noir-contracts/Token'; import { SequencerClient } from '@aztec/sequencer-client'; @@ -273,121 +259,38 @@ describe('e2e_deploy_contract', () => { // These tests look scary, but don't fret: all this hodgepodge of calls will be hidden // behind a much nicer API in the near future as part of #4080. describe('registering a contract class', () => { - let registerer: ContractClassRegistererContract; let artifact: ContractArtifact; - let contractClass: ContractClassWithId; - let registerTxHash: TxHash; - let privateFunctionsRoot: Fr; - let publicBytecodeCommitment: Fr; + let contractClass: ContractClassWithId & ContractClassIdPreimage; beforeAll(async () => { artifact = StatefulTestContract.artifact; + await registerContractClass(wallet, artifact).send().wait(); contractClass = getContractClassFromArtifact(artifact); - privateFunctionsRoot = computePrivateFunctionsRoot(contractClass.privateFunctions); - publicBytecodeCommitment = computePublicBytecodeCommitment(contractClass.packedBytecode); - registerer = await registerContract(wallet, ContractClassRegistererContract, [], { salt: 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); - }); + }, 60_000); 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!.privateFunctionsRoot.toString()).toEqual(contractClass.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); - - // We need two sibling paths because private function information is split across two trees: - // The "private function tree" captures the selectors and verification keys, and is used in the kernel circuit for verifying the proof generated by the app circuit. - // The "artifact tree" captures function bytecode and metadata, and is used by the pxe to check that its executing the code it's supposed to be executing, but it never goes into circuits. - 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(); + it('broadcasts a private function', async () => { + const selector = contractClass.privateFunctions[0].selector; + await broadcastPrivateFunction(wallet, artifact, selector).send().wait(); + // TODO(#4428): Test that these functions are captured by the node and made available when + // requesting the corresponding contract class. }, 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(); + await broadcastUnconstrainedFunction(wallet, artifact, selector).send().wait(); + // TODO(#4428): Test that these functions are captured by the node and made available when + // requesting the corresponding contract class. }, 60_000); describe('deploying a contract instance', () => { diff --git a/yarn-project/protocol-contracts/src/class-registerer/__snapshots__/index.test.ts.snap b/yarn-project/protocol-contracts/src/class-registerer/__snapshots__/index.test.ts.snap index 6292d1e68c6..1e0c5fe95d3 100644 --- a/yarn-project/protocol-contracts/src/class-registerer/__snapshots__/index.test.ts.snap +++ b/yarn-project/protocol-contracts/src/class-registerer/__snapshots__/index.test.ts.snap @@ -318,6 +318,86 @@ exports[`ClassRegisterer returns canonical protocol contract 1`] = ` }, }, ], + "privateFunctionsRoot": Fr { + "asBigInt": 14149643440615160691253002398502794418486578915412752764304101313202744681431n, + "asBuffer": { + "data": [ + 31, + 72, + 106, + 20, + 204, + 180, + 255, + 137, + 103, + 217, + 28, + 197, + 42, + 211, + 3, + 8, + 107, + 19, + 4, + 29, + 137, + 6, + 131, + 49, + 212, + 129, + 225, + 242, + 112, + 231, + 99, + 215, + ], + "type": "Buffer", + }, + }, + "publicBytecodeCommitment": Fr { + "asBigInt": 13424778679590722659186070167781649503057405647123863047387123265455936508181n, + "asBuffer": { + "data": [ + 29, + 174, + 39, + 204, + 127, + 226, + 175, + 52, + 95, + 22, + 2, + 83, + 190, + 56, + 117, + 212, + 73, + 167, + 233, + 186, + 107, + 214, + 135, + 71, + 216, + 125, + 78, + 112, + 84, + 184, + 17, + 21, + ], + "type": "Buffer", + }, + }, "publicFunctions": [], "version": 1, }, diff --git a/yarn-project/protocol-contracts/src/instance-deployer/__snapshots__/index.test.ts.snap b/yarn-project/protocol-contracts/src/instance-deployer/__snapshots__/index.test.ts.snap index 483d619e453..3ca5288c62a 100644 --- a/yarn-project/protocol-contracts/src/instance-deployer/__snapshots__/index.test.ts.snap +++ b/yarn-project/protocol-contracts/src/instance-deployer/__snapshots__/index.test.ts.snap @@ -226,6 +226,86 @@ exports[`InstanceDeployer returns canonical protocol contract 1`] = ` }, }, ], + "privateFunctionsRoot": Fr { + "asBigInt": 10175937322611504760053163396285173722489074882473725995897130952520062304283n, + "asBuffer": { + "data": [ + 22, + 127, + 96, + 93, + 15, + 118, + 143, + 145, + 206, + 97, + 129, + 56, + 23, + 230, + 12, + 1, + 170, + 115, + 219, + 197, + 231, + 133, + 116, + 128, + 74, + 71, + 30, + 51, + 115, + 5, + 128, + 27, + ], + "type": "Buffer", + }, + }, + "publicBytecodeCommitment": Fr { + "asBigInt": 13424778679590722659186070167781649503057405647123863047387123265455936508181n, + "asBuffer": { + "data": [ + 29, + 174, + 39, + 204, + 127, + 226, + 175, + 52, + 95, + 22, + 2, + 83, + 190, + 56, + 117, + 212, + 73, + 167, + 233, + 186, + 107, + 214, + 135, + 71, + 216, + 125, + 78, + 112, + 84, + 184, + 17, + 21, + ], + "type": "Buffer", + }, + }, "publicFunctions": [], "version": 1, }, diff --git a/yarn-project/protocol-contracts/src/protocol_contract.ts b/yarn-project/protocol-contracts/src/protocol_contract.ts index 970c4878f8a..4f798ed4f48 100644 --- a/yarn-project/protocol-contracts/src/protocol_contract.ts +++ b/yarn-project/protocol-contracts/src/protocol_contract.ts @@ -1,12 +1,12 @@ import { AztecAddress, getContractClassFromArtifact, getContractInstanceFromDeployParams } from '@aztec/circuits.js'; import { ContractArtifact } from '@aztec/foundation/abi'; import { Fr } from '@aztec/foundation/fields'; -import { ContractClassWithId, ContractInstance } from '@aztec/types/contracts'; +import { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/contracts'; /** Represents a canonical contract in the protocol. */ export interface ProtocolContract { /** Canonical deployed instance. */ - instance: ContractInstance; + instance: ContractInstanceWithAddress; /** Contract class of this contract. */ contractClass: ContractClassWithId; /** Complete contract artifact. */ diff --git a/yarn-project/pxe/package.json b/yarn-project/pxe/package.json index 665b47b2c1a..75ba8afa0bd 100644 --- a/yarn-project/pxe/package.json +++ b/yarn-project/pxe/package.json @@ -41,6 +41,7 @@ "@aztec/kv-store": "workspace:^", "@aztec/noir-compiler": "workspace:^", "@aztec/noir-protocol-circuits": "workspace:^", + "@aztec/protocol-contracts": "workspace:^", "@aztec/simulator": "workspace:^", "@aztec/types": "workspace:^", "koa": "^2.14.2", diff --git a/yarn-project/pxe/src/pxe_service/create_pxe_service.ts b/yarn-project/pxe/src/pxe_service/create_pxe_service.ts index 3ccad19041f..d3b21dc5299 100644 --- a/yarn-project/pxe/src/pxe_service/create_pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/create_pxe_service.ts @@ -3,6 +3,8 @@ import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { TestKeyStore } from '@aztec/key-store'; import { AztecLmdbStore } from '@aztec/kv-store/lmdb'; import { initStoreForRollup } from '@aztec/kv-store/utils'; +import { getCanonicalClassRegisterer } from '@aztec/protocol-contracts/class-registerer'; +import { getCanonicalInstanceDeployer } from '@aztec/protocol-contracts/instance-deployer'; import { join } from 'path'; @@ -43,6 +45,7 @@ export async function createPXEService( const db = new KVPxeDatabase(await initStoreForRollup(AztecLmdbStore.open(pxeDbPath), l1Contracts.rollupAddress)); const server = new PXEService(keyStore, aztecNode, db, config, logSuffix); + await server.addContracts([getCanonicalClassRegisterer(), getCanonicalInstanceDeployer()]); await server.start(); return server; diff --git a/yarn-project/pxe/tsconfig.json b/yarn-project/pxe/tsconfig.json index 06d0a5e9399..91273923e82 100644 --- a/yarn-project/pxe/tsconfig.json +++ b/yarn-project/pxe/tsconfig.json @@ -33,6 +33,9 @@ { "path": "../noir-protocol-circuits" }, + { + "path": "../protocol-contracts" + }, { "path": "../types" }, diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 0a8a36a37c3..c891a1ffdd2 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -182,6 +182,7 @@ __metadata: "@aztec/circuits.js": "workspace:^" "@aztec/ethereum": "workspace:^" "@aztec/foundation": "workspace:^" + "@aztec/protocol-contracts": "workspace:^" "@aztec/types": "workspace:^" "@jest/globals": ^29.5.0 "@types/jest": ^29.5.0 @@ -759,6 +760,7 @@ __metadata: "@aztec/noir-compiler": "workspace:^" "@aztec/noir-contracts": "workspace:^" "@aztec/noir-protocol-circuits": "workspace:^" + "@aztec/protocol-contracts": "workspace:^" "@aztec/simulator": "workspace:^" "@aztec/types": "workspace:^" "@jest/globals": ^29.5.0