Skip to content

Commit

Permalink
Add artifact and instances separately to pxe
Browse files Browse the repository at this point in the history
  • Loading branch information
spalladino committed Jan 24, 2024
1 parent 8bde91d commit da3e6bb
Show file tree
Hide file tree
Showing 25 changed files with 472 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ export class ContractClassStore {

getContractClass(id: Fr): ContractClassWithId | undefined {
const contractClass = this.#contractClasses.get(id.toString());
if (!contractClass) {
return undefined;
}
return { ...SerializableContractClass.fromBuffer(contractClass), id };
return contractClass && SerializableContractClass.fromBuffer(contractClass).withId(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ export class ContractInstanceStore {

getContractInstance(address: AztecAddress): ContractInstanceWithAddress | undefined {
const contractInstance = this.#contractInstances.get(address.toString());
if (!contractInstance) {
return undefined;
}
return { ...SerializableContractInstance.fromBuffer(contractInstance), address };
return contractInstance && SerializableContractInstance.fromBuffer(contractInstance).withAddress(address);
}
}
1 change: 1 addition & 0 deletions yarn-project/circuits.js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"tslib": "^2.4.0"
},
"devDependencies": {
"@aztec/noir-contracts": "workspace:^",
"@jest/globals": "^29.5.0",
"@types/jest": "^29.5.0",
"@types/lodash.chunk": "^4.2.7",
Expand Down
16 changes: 11 additions & 5 deletions yarn-project/circuits.js/src/abis/merkle_tree_calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ import { pedersenHash } from '@aztec/foundation/crypto';
*/
export class MerkleTreeCalculator {
private zeroHashes: Buffer[];

constructor(private height: number, zeroLeaf = Buffer.alloc(32)) {
private hasher: (left: Buffer, right: Buffer) => Buffer;

constructor(
private height: number,
zeroLeaf = Buffer.alloc(32),
hasher = (left: Buffer, right: Buffer) => pedersenHash([left, right]),
) {
this.hasher = hasher;
this.zeroHashes = Array.from({ length: height }).reduce(
(acc: Buffer[], _, i) => [...acc, pedersenHash([acc[i], acc[i]])],
(acc: Buffer[], _, i) => [...acc, this.hasher(acc[i], acc[i])],
[zeroLeaf],
);
}
Expand All @@ -26,7 +32,7 @@ export class MerkleTreeCalculator {
for (let j = 0; j < leaves.length / 2; ++j) {
const l = leaves[j * 2];
const r = leaves[j * 2 + 1] || this.zeroHashes[i];
newLeaves[j] = pedersenHash([l, r]);
newLeaves[j] = this.hasher(l, r);
}
result = result.concat(new Array(numLeaves - leaves.length).fill(this.zeroHashes[i]), newLeaves);
leaves = newLeaves;
Expand All @@ -47,7 +53,7 @@ export class MerkleTreeCalculator {
for (; j < leaves.length / 2; ++j) {
const l = leaves[j * 2];
const r = leaves[j * 2 + 1] || this.zeroHashes[i];
leaves[j] = pedersenHash([l, r]);
leaves[j] = this.hasher(l, r);
}
leaves = leaves.slice(0, j);
}
Expand Down

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions yarn-project/circuits.js/src/contract/artifact_hash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BenchmarkingContractArtifact } from '@aztec/noir-contracts/Benchmarking';

import { getArtifactHash } from './artifact_hash.js';

describe('ArtifactHash', () => {
it('calculates the artifact hash', () => {
expect(getArtifactHash(BenchmarkingContractArtifact).toString()).toMatchInlineSnapshot(
`"0x1cd31b12181cf7516720f4675ffea13c8c538dc4875232776adb8bbe8364ed5c"`,
);
});
});
67 changes: 67 additions & 0 deletions yarn-project/circuits.js/src/contract/artifact_hash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ContractArtifact, FunctionArtifact, FunctionSelector, FunctionType } from '@aztec/foundation/abi';
import { sha256 } from '@aztec/foundation/crypto';
import { Fr } from '@aztec/foundation/fields';
import { numToUInt8 } from '@aztec/foundation/serialize';

import { MerkleTreeCalculator } from '../abis/merkle_tree_calculator.js';

const VERSION = 1;

/**
* Returns the artifact hash of a given compiled contract artifact.
*
* ```
* private_functions_artifact_leaves = artifact.private_functions.map fn =>
* sha256(fn.selector, fn.metadata_hash, sha256(fn.bytecode))
* private_functions_artifact_tree_root = merkleize(private_functions_artifact_leaves)
*
* unconstrained_functions_artifact_leaves = artifact.unconstrained_functions.map fn =>
* sha256(fn.selector, fn.metadata_hash, sha256(fn.bytecode))
* unconstrained_functions_artifact_tree_root = merkleize(unconstrained_functions_artifact_leaves)
*
* version = 1
* artifact_hash = sha256(
* version,
* private_functions_artifact_tree_root,
* unconstrained_functions_artifact_tree_root,
* artifact_metadata,
* )
* ```
* @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);
const preimage = [numToUInt8(VERSION), privateFunctionRoot, unconstrainedFunctionRoot, metadataHash];
return Fr.fromBufferReduce(sha256(Buffer.concat(preimage)));
}

function getArtifactMetadataHash(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 };

function getFunctionRoot(artifact: ContractArtifact, fnType: FunctionType) {
const leaves = getFunctionLeaves(artifact, fnType);
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);
}

function getFunctionLeaves(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);
}

function getFunctionArtifactHash(fn: FunctionArtifactWithSelector): Buffer {
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]));
}
35 changes: 8 additions & 27 deletions yarn-project/circuits.js/src/contract/contract_class.test.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,15 @@
import { Fr } from '@aztec/foundation/fields';
import { ContractClass } from '@aztec/types/contracts';
import { toFriendlyJSON } from '@aztec/foundation/serialize';
import { BenchmarkingContractArtifact } from '@aztec/noir-contracts/Benchmarking';

import { FunctionSelector, getContractClassId } from '../index.js';
import { createContractClassFromArtifact } from './contract_class.js';

describe('ContractClass', () => {
describe('getContractClassId', () => {
it('calculates the contract class id', () => {
const contractClass: ContractClass = {
version: 1,
artifactHash: Fr.fromString('0x1234'),
packedBytecode: Buffer.from('123456789012345678901234567890', 'hex'),
privateFunctions: [
{
selector: FunctionSelector.fromString('0x12345678'),
vkHash: Fr.fromString('0x1234'),
isInternal: false,
},
],
publicFunctions: [
{
selector: FunctionSelector.fromString('0x12345678'),
bytecode: Buffer.from('123456789012345678901234567890', 'hex'),
isInternal: false,
},
],
};

expect(getContractClassId(contractClass).toString()).toMatchInlineSnapshot(
`"0x1b436781f84669144ec383d6ea5f49b05ccba5c6221ebeb86085443c2a859202"`,
);
it('creates a contract class from a contract compilation artifact', () => {
const contractClass = createContractClassFromArtifact({
...BenchmarkingContractArtifact,
artifactHash: Fr.fromString('0x1234'),
});
expect(toFriendlyJSON(contractClass)).toMatchSnapshot();
});
});
107 changes: 37 additions & 70 deletions yarn-project/circuits.js/src/contract/contract_class.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,45 @@
import { pedersenHash, sha256 } from '@aztec/foundation/crypto';
import { ContractArtifact, FunctionSelector, FunctionType } from '@aztec/foundation/abi';
import { pedersenHash } from '@aztec/foundation/crypto';
import { Fr } from '@aztec/foundation/fields';
import { numToUInt8 } from '@aztec/foundation/serialize';
import { ContractClass, PrivateFunction, PublicFunction } from '@aztec/types/contracts';
import { ContractClass } from '@aztec/types/contracts';

import { MerkleTreeCalculator } from '../abis/merkle_tree_calculator.js';
import { FUNCTION_TREE_HEIGHT, GeneratorIndex } from '../constants.gen.js';
import chunk from 'lodash.chunk';

/**
* Returns the id of a contract class computed as its hash.
*
* ```
* version = 1
* private_function_leaves = private_functions.map(fn => pedersen([fn.function_selector as Field, fn.vk_hash], GENERATOR__FUNCTION_LEAF))
* private_functions_root = merkleize(private_function_leaves)
* bytecode_commitment = calculate_commitment(packed_bytecode)
* contract_class_id = pedersen([version, artifact_hash, private_functions_root, bytecode_commitment], GENERATOR__CLASS_IDENTIFIER)
* ```
* @param contractClass - Contract class.
* @returns The identifier.
*/
export function getContractClassId(contractClass: ContractClass): Fr {
const privateFunctionsRoot = getPrivateFunctionsRoot(contractClass.privateFunctions);
const publicFunctionsRoot = getPublicFunctionsRoot(contractClass.publicFunctions); // This should be removed once we drop public functions as first class citizens in the protocol
const bytecodeCommitment = getBytecodeCommitment(contractClass.packedBytecode);
return Fr.fromBuffer(
pedersenHash(
[
numToUInt8(contractClass.version),
contractClass.artifactHash.toBuffer(),
privateFunctionsRoot.toBuffer(),
publicFunctionsRoot.toBuffer(),
bytecodeCommitment.toBuffer(),
],
GeneratorIndex.CONTRACT_LEAF, // TODO(@spalladino): Review all generator indices in this file
),
);
}

// TODO(@spalladino): Replace with actual implementation
function getBytecodeCommitment(bytecode: Buffer) {
return Fr.fromBufferReduce(sha256(bytecode));
}
import { GeneratorIndex } from '../constants.gen.js';

// Memoize the merkle tree calculators to avoid re-computing the zero-hash for each level in each call
let privateFunctionTreeCalculator: MerkleTreeCalculator | undefined;
let publicFunctionTreeCalculator: MerkleTreeCalculator | undefined;
/** Contract artifact including its artifact hash */
type ContractArtifactWithHash = ContractArtifact & { artifactHash: Fr };

const PRIVATE_FUNCTION_SIZE = 2;
const PUBLIC_FUNCTION_SIZE = 2;

function getPrivateFunctionsRoot(fns: PrivateFunction[]): Fr {
const privateFunctionLeaves = fns.map(fn =>
pedersenHash(
[fn.selector, fn.vkHash].map(x => x.toBuffer()),
GeneratorIndex.FUNCTION_LEAF,
),
);
if (!privateFunctionTreeCalculator) {
const functionTreeZeroLeaf = pedersenHash(new Array(PRIVATE_FUNCTION_SIZE).fill(Buffer.alloc(32)));
privateFunctionTreeCalculator = new MerkleTreeCalculator(FUNCTION_TREE_HEIGHT, functionTreeZeroLeaf);
}
return Fr.fromBuffer(privateFunctionTreeCalculator.computeTreeRoot(privateFunctionLeaves));
/**
* Creates a ContractClass from a contract compilation artifact with its artifact hash.
*/
export function createContractClassFromArtifact(artifact: ContractArtifactWithHash): ContractClass {
return {
version: 1,
artifactHash: artifact.artifactHash,
publicFunctions: artifact.functions
.filter(f => f.functionType === FunctionType.OPEN)
.map(f => ({
selector: FunctionSelector.fromNameAndParameters(f.name, f.parameters),
bytecode: Buffer.from(f.bytecode, 'base64'),
isInternal: f.isInternal,
})),
privateFunctions: artifact.functions
.filter(f => f.functionType === FunctionType.SECRET)
.map(f => ({
selector: FunctionSelector.fromNameAndParameters(f.name, f.parameters),
vkHash: getVerificationKeyHash(Buffer.from(f.verificationKey!, 'base64')),
isInternal: f.isInternal,
})),
packedBytecode: Buffer.alloc(0),
};
}

function getPublicFunctionsRoot(fns: PublicFunction[]): Fr {
const publicFunctionLeaves = fns.map(fn =>
pedersenHash(
[fn.selector, getBytecodeCommitment(fn.bytecode)].map(x => x.toBuffer()),
GeneratorIndex.FUNCTION_LEAF,
),
);
if (!publicFunctionTreeCalculator) {
const functionTreeZeroLeaf = pedersenHash(new Array(PUBLIC_FUNCTION_SIZE).fill(Buffer.alloc(32)));
publicFunctionTreeCalculator = new MerkleTreeCalculator(FUNCTION_TREE_HEIGHT, functionTreeZeroLeaf);
}
return Fr.fromBuffer(publicFunctionTreeCalculator.computeTreeRoot(publicFunctionLeaves));
/**
* Calculates the hash of a verification key.
* TODO(@spalladino) Check this is the correct calculation of vkhash
* */
function getVerificationKeyHash(vk: Buffer) {
const chunks = chunk(vk, 32).map(nums => Buffer.from(nums));
return Fr.fromBuffer(pedersenHash(chunks, GeneratorIndex.VK));
}
34 changes: 34 additions & 0 deletions yarn-project/circuits.js/src/contract/contract_class_id.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Fr } from '@aztec/foundation/fields';
import { ContractClass } from '@aztec/types/contracts';

import { FunctionSelector, getContractClassId } from '../index.js';

describe('ContractClass', () => {
describe('getContractClassId', () => {
it('calculates the contract class id', () => {
const contractClass: ContractClass = {
version: 1,
artifactHash: Fr.fromString('0x1234'),
packedBytecode: Buffer.from('123456789012345678901234567890', 'hex'),
privateFunctions: [
{
selector: FunctionSelector.fromString('0x12345678'),
vkHash: Fr.fromString('0x1234'),
isInternal: false,
},
],
publicFunctions: [
{
selector: FunctionSelector.fromString('0x12345678'),
bytecode: Buffer.from('123456789012345678901234567890', 'hex'),
isInternal: false,
},
],
};

expect(getContractClassId(contractClass).toString()).toMatchInlineSnapshot(
`"0x1b436781f84669144ec383d6ea5f49b05ccba5c6221ebeb86085443c2a859202"`,
);
});
});
});
Loading

0 comments on commit da3e6bb

Please sign in to comment.