Skip to content

Commit

Permalink
feat: Verify function against contract class id in private kernel (#4337
Browse files Browse the repository at this point in the history
)

Updates the private kernel circuit to verify that the function being
executed belongs to the contract class id in the address preimage.

- Introduces `ContractClassId` as a type in noir-protocol-circuits
- Updates contract class computation to remove public functions root
since they'll be eventually dropped
- Updates `PrivateCallData` input to private kernel to include preimages
for address and contract class
- Introduces `WrappedFieldLike` type for codegen so all wrapped field
types can be converted from fields
- Introduces an immutable simple `MerkleTree` type in circuits.js so we
don't pass raw `Fr[]`s around
- Sets `vk_hash` to zero in ts-land since it's mocked to zero in
noir-land
- Replaces pxe `contract_tree` with a `private_functions_tree`
- Updates noir protocol circuits fixture data and builder
- Introduces `WrappedFieldLike` to encoder to easily pass structs with
just an `inner` field from aztecjs

Pending updating yellow paper with new kernel circuit structs (coming up
in another PR).

Fixes #4056
  • Loading branch information
spalladino authored Feb 1, 2024
1 parent 8e0d37d commit e1d832d
Show file tree
Hide file tree
Showing 63 changed files with 2,117 additions and 1,202 deletions.
9 changes: 9 additions & 0 deletions yarn-project/acir-simulator/src/client/db_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ export class ContractNotFoundError extends Error {
}
}

/**
* Error thrown when a contract class is not found in the database.
*/
export class ContractClassNotFoundError extends Error {
constructor(contractClassId: string) {
super(`DB has no contract class with id ${contractClassId}`);
}
}

/**
* The database oracle interface.
*/
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/aztec-nr/aztec/src/context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ use dep::protocol_types::{
NUM_FIELDS_PER_SHA256,
RETURN_VALUES_LENGTH,
},
contract_class::ContractClassId,
contrakt::{
deployment_data::ContractDeploymentData,
storage_read::StorageRead,
Expand Down Expand Up @@ -348,7 +349,7 @@ impl PrivateContext {
y: reader.read()
},
initialization_hash : reader.read(),
contract_class_id : reader.read(),
contract_class_id : ContractClassId::from_field(reader.read()),
contract_address_salt : reader.read(),
portal_contract_address : EthAddress::from_field(reader.read()),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use dep::protocol_types::{
new_contract_data::NewContractData as ContractLeafPreimage,
},
address::{AztecAddress, EthAddress},
contract_class::ContractClassId,
grumpkin_point::GrumpkinPoint,
};
use dep::std::merkle::compute_merkle_root;
Expand All @@ -23,7 +24,7 @@ use crate::{
pub fn prove_contract_inclusion(
public_key: GrumpkinPoint,
contract_address_salt: Field,
contract_class_id: Field,
contract_class_id: ContractClassId,
initialization_hash: Field,
portal_contract_address: EthAddress,
block_number: u32, // The block at which we'll prove that the public value exists
Expand Down
1 change: 1 addition & 0 deletions yarn-project/aztec.js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export {
CheatCodes,
AztecAddressLike,
FunctionSelectorLike,
WrappedFieldLike,
isContractDeployed,
EthCheatCodes,
computeAuthWitMessageHash,
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/aztec.js/src/utils/abi_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ export type AztecAddressLike = { /** Wrapped address */ address: FieldLike } | A

/** Any type that can be converted into an FunctionSelector Aztec.nr struct. */
export type FunctionSelectorLike = FieldLike | FunctionSelector;

/** Any type that can be converted into a struct with a single `inner` field. */
export type WrappedFieldLike = { /** Wrapped value */ inner: FieldLike } | FieldLike;
3 changes: 2 additions & 1 deletion yarn-project/circuits.js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"./utils": "./dest/utils/index.js",
"./types": "./dest/types/index.js",
"./constants": "./dest/constants.gen.js",
"./contract": "./dest/contract/index.js"
"./contract": "./dest/contract/index.js",
"./merkle": "./dest/merkle/index.js"
},
"typedocOptions": {
"entryPoints": [
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/circuits.js/src/abis/abis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
PRIVATE_CIRCUIT_PUBLIC_INPUTS_HASH_INPUT_LENGTH,
PUBLIC_CIRCUIT_PUBLIC_INPUTS_HASH_INPUT_LENGTH,
} from '../constants.gen.js';
import { MerkleTreeCalculator } from '../merkle/merkle_tree_calculator.js';
import {
CallContext,
ContractDeploymentData,
Expand All @@ -32,7 +33,6 @@ import {
TxRequest,
VerificationKey,
} from '../structs/index.js';
import { MerkleTreeCalculator } from './merkle_tree_calculator.js';

/**
* Computes a hash of a transaction request.
Expand Down Expand Up @@ -136,7 +136,7 @@ export function computeFunctionTree(fnLeaves: Fr[]) {
const leaves = fnLeaves.map(fr => fr.toBuffer());
return getFunctionTreeRootCalculator()
.computeTree(leaves)
.map(b => Fr.fromBuffer(b));
.nodes.map(b => Fr.fromBuffer(b));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,25 @@ exports[`ContractClass creates a contract class from a contract compilation arti
"selector": {
"value": 2432309179
},
"vkHash": "0x038021824fbd98bb0e388b0efe18f72e9350f7456481714539ba583de37113ce",
"vkHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"isInternal": false
},
{
"selector": {
"value": 283286945
},
"vkHash": "0x038021824fbd98bb0e388b0efe18f72e9350f7456481714539ba583de37113ce",
"vkHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"isInternal": false
},
{
"selector": {
"value": 332459554
},
"vkHash": "0x038021824fbd98bb0e388b0efe18f72e9350f7456481714539ba583de37113ce",
"vkHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"isInternal": false
}
],
"packedBytecode": "0x",
"id": "0x0710cd0d58fbc487f87fb17855d50ecdc46d3df58b724044f1a35eee815becf5"
"id": "0x034c098fd12d17ec1ecb116e91d01ddc76748569790835f91c866f3e8ec8466a"
}"
`;
Binary file not shown.
2 changes: 1 addition & 1 deletion yarn-project/circuits.js/src/contract/artifact_hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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';
import { MerkleTreeCalculator } from '../merkle/merkle_tree_calculator.js';

const VERSION = 1;

Expand Down
20 changes: 17 additions & 3 deletions yarn-project/circuits.js/src/contract/contract_address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import { AztecAddress, GeneratorIndex, PublicKey } from '../index.js';
* ```
* @param instance - A contract instance for which to calculate the deployment address.
*/
export function computeContractAddressFromInstance(instance: ContractInstance): AztecAddress {
export function computeContractAddressFromInstance(
instance:
| ContractInstance
| ({ contractClassId: Fr; saltedInitializationHash: Fr } & Pick<ContractInstance, 'publicKeysHash'>),
): AztecAddress {
const partialAddress = computePartialAddress(instance);
const publicKeyHash = instance.publicKeysHash;
return computeContractAddressFromPartial({ partialAddress, publicKeyHash });
Expand Down Expand Up @@ -97,6 +101,16 @@ export function computePublicKeysHash(publicKey: PublicKey | undefined): Fr {
export function computeInitializationHash(initFn: FunctionAbi, args: any[]): Fr {
const selector = FunctionSelector.fromNameAndParameters(initFn.name, initFn.parameters);
const flatArgs = encodeArguments(initFn, args);
const argsHash = computeVarArgsHash(flatArgs);
return Fr.fromBuffer(pedersenHash([selector.toBuffer(), argsHash.toBuffer()], GeneratorIndex.CONSTRUCTOR));
return computeInitializationHashFromEncodedArgs(selector, flatArgs);
}

/**
* Computes the initialization hash for an instance given its constructor function selector and encoded arguments.
* @param initFn - Constructor function selector.
* @param args - Encoded arguments.
* @returns The hash.
*/
export function computeInitializationHashFromEncodedArgs(initFn: FunctionSelector, encodedArgs: Fr[]): Fr {
const argsHash = computeVarArgsHash(encodedArgs);
return Fr.fromBuffer(pedersenHash([initFn.toBuffer(), argsHash.toBuffer()], GeneratorIndex.CONSTRUCTOR));
}
13 changes: 7 additions & 6 deletions yarn-project/circuits.js/src/contract/contract_class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { Fr } from '@aztec/foundation/fields';
import { ContractClass, ContractClassWithId } from '@aztec/types/contracts';

import { getArtifactHash } from './artifact_hash.js';
import { getContractClassId } from './contract_class_id.js';
import { hashVKStr } from './contract_tree/index.js';
import { computeContractClassId } from './contract_class_id.js';

/** Contract artifact including its artifact hash */
type ContractArtifactWithHash = ContractArtifact & { artifactHash: Fr };
Expand Down Expand Up @@ -33,13 +32,15 @@ export function getContractClassFromArtifact(
})),
packedBytecode: Buffer.alloc(0),
};
const id = getContractClassId(contractClass);
const id = computeContractClassId(contractClass);
return { ...contractClass, id };
}

/**
* Calculates the hash of a verification key.
* */
function getVerificationKeyHash(verificationKeyInBase64: string) {
return Fr.fromBuffer(hashVKStr(verificationKeyInBase64));
* Returns zero for consistency with Noir.
*/
function getVerificationKeyHash(_verificationKeyInBase64: string) {
// return Fr.fromBuffer(hashVKStr(verificationKeyInBase64));
return Fr.ZERO;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Fr } from '@aztec/foundation/fields';
import { ContractClass } from '@aztec/types/contracts';

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

describe('ContractClass', () => {
describe('getContractClassId', () => {
Expand All @@ -26,8 +26,8 @@ describe('ContractClass', () => {
],
};

expect(getContractClassId(contractClass).toString()).toMatchInlineSnapshot(
`"0x1b436781f84669144ec383d6ea5f49b05ccba5c6221ebeb86085443c2a859202"`,
expect(computeContractClassId(contractClass).toString()).toMatchInlineSnapshot(
`"0x2f4c56801b35e01081aeb1b2bd07eba0f8d55de625ec1e957347eedaea1669bb"`,
);
});
});
Expand Down
70 changes: 20 additions & 50 deletions yarn-project/circuits.js/src/contract/contract_class_id.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { pedersenHash, sha256 } 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 { GeneratorIndex } from '../constants.gen.js';
import { computePrivateFunctionsRoot } from './private_function.js';

/**
* Returns the id of a contract class computed as its hash.
Expand All @@ -19,60 +18,31 @@ import { FUNCTION_TREE_HEIGHT, GeneratorIndex } from '../constants.gen.js';
* @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);
export function computeContractClassId(contractClass: ContractClass): Fr {
const { privateFunctionsRoot, publicBytecodeCommitment } = computeContractClassIdPreimage(contractClass);
return Fr.fromBuffer(
pedersenHash(
[
numToUInt8(contractClass.version),
contractClass.artifactHash.toBuffer(),
privateFunctionsRoot.toBuffer(),
publicFunctionsRoot.toBuffer(),
bytecodeCommitment.toBuffer(),
],
[contractClass.artifactHash.toBuffer(), privateFunctionsRoot.toBuffer(), publicBytecodeCommitment.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));
/** Returns the preimage of a contract class id given a contract class. */
export function computeContractClassIdPreimage(contractClass: ContractClass): ContractClassIdPreimage {
const privateFunctionsRoot = computePrivateFunctionsRoot(contractClass.privateFunctions);
const publicBytecodeCommitment = computeBytecodeCommitment(contractClass.packedBytecode);
return { artifactHash: contractClass.artifactHash, privateFunctionsRoot, publicBytecodeCommitment };
}

// 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;
/** Preimage of a contract class id. */
export type ContractClassIdPreimage = {
artifactHash: Fr;
privateFunctionsRoot: Fr;
publicBytecodeCommitment: 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));
}

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));
// TODO(@spalladino): Replace with actual implementation
function computeBytecodeCommitment(bytecode: Buffer) {
return Fr.fromBufferReduce(sha256(bytecode));
}
4 changes: 2 additions & 2 deletions yarn-project/circuits.js/src/contract/contract_instance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ContractArtifact } from '@aztec/foundation/abi';
import { ContractInstance, ContractInstanceWithAddress } from '@aztec/types/contracts';

import { EthAddress, Fr, PublicKey, getContractClassFromArtifact, getContractClassId } from '../index.js';
import { EthAddress, Fr, PublicKey, computeContractClassId, getContractClassFromArtifact } from '../index.js';
import {
computeContractAddressFromInstance,
computeInitializationHash,
Expand Down Expand Up @@ -34,7 +34,7 @@ export function getContractInstanceFromDeployParams(
}

const contractClass = getContractClassFromArtifact(artifact);
const contractClassId = getContractClassId(contractClass);
const contractClassId = computeContractClassId(contractClass);
const initializationHash = computeInitializationHash(constructorArtifact, args);
const publicKeysHash = computePublicKeysHash(publicKey);

Expand Down
1 change: 1 addition & 0 deletions yarn-project/circuits.js/src/contract/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './contract_class_id.js';
export * from './contract_class.js';
export * from './artifact_hash.js';
export * from './contract_address.js';
export * from './private_function.js';
34 changes: 34 additions & 0 deletions yarn-project/circuits.js/src/contract/private_function.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Fr } from '@aztec/foundation/fields';
import { PrivateFunction } from '@aztec/types/contracts';

import { fr, makeSelector } from '../tests/factories.js';
import { computePrivateFunctionsRoot, computePrivateFunctionsTree } from './private_function.js';

describe('PrivateFunction', () => {
const privateFunctions: PrivateFunction[] = [
{ selector: makeSelector(1), vkHash: fr(2), isInternal: false },
{ selector: makeSelector(3), vkHash: fr(4), isInternal: false },
];

it('computes merkle tree', () => {
const tree = computePrivateFunctionsTree(privateFunctions);
expect(tree.nodes.map(node => node.toString())).toMatchSnapshot();
});

it('computes merkle tree root', () => {
const root = computePrivateFunctionsRoot(privateFunctions);
expect(root.toString()).toMatchSnapshot();
});

it('tree and root methods agree', () => {
const tree = computePrivateFunctionsTree(privateFunctions);
const root = computePrivateFunctionsRoot(privateFunctions);
expect(Fr.fromBuffer(tree.root).equals(root)).toBe(true);
});

it('sorts functions before computing tree', () => {
const root = computePrivateFunctionsRoot(privateFunctions);
const rootReversed = computePrivateFunctionsRoot([...privateFunctions].reverse());
expect(root.equals(rootReversed)).toBe(true);
});
});
Loading

0 comments on commit e1d832d

Please sign in to comment.