-
Notifications
You must be signed in to change notification settings - Fork 311
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add artifact and instances separately to pxe
- Loading branch information
1 parent
8bde91d
commit da3e6bb
Showing
25 changed files
with
472 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap
Large diffs are not rendered by default.
Oops, something went wrong.
11 changes: 11 additions & 0 deletions
11
yarn-project/circuits.js/src/contract/artifact_hash.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"`, | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
35
yarn-project/circuits.js/src/contract/contract_class.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
107
yarn-project/circuits.js/src/contract/contract_class.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
34
yarn-project/circuits.js/src/contract/contract_class_id.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"`, | ||
); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.