-
Notifications
You must be signed in to change notification settings - Fork 333
/
Copy pathartifact_hash.ts
111 lines (96 loc) · 5 KB
/
artifact_hash.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { type ContractArtifact, type FunctionArtifact, FunctionSelector, FunctionType } from '@aztec/foundation/abi';
import { sha256 } from '@aztec/foundation/crypto';
import { Fr, reduceFn } from '@aztec/foundation/fields';
import { createDebugLogger } from '@aztec/foundation/log';
import { numToUInt8 } from '@aztec/foundation/serialize';
import { type MerkleTree } from '../merkle/merkle_tree.js';
import { MerkleTreeCalculator } from '../merkle/merkle_tree_calculator.js';
const VERSION = 1;
// TODO(miranda): Artifact and artifact metadata hashes are currently the only SHAs not truncated by a byte.
// They are never recalculated in the circuit or L1 contract, but they are input to circuits, so perhaps modding here is preferable?
// TODO(@spalladino) Reducing sha256 to a field may have security implications. Validate this with crypto team.
const sha256Fr = reduceFn(sha256, Fr);
/**
* 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 computeArtifactHash(
artifact: ContractArtifact | { privateFunctionRoot: Fr; unconstrainedFunctionRoot: Fr; metadataHash: Fr },
): Fr {
if ('privateFunctionRoot' in artifact && 'unconstrainedFunctionRoot' in artifact && 'metadataHash' in artifact) {
const { privateFunctionRoot, unconstrainedFunctionRoot, metadataHash } = artifact;
const preimage = [privateFunctionRoot, unconstrainedFunctionRoot, metadataHash].map(x => x.toBuffer());
return sha256Fr(Buffer.concat([numToUInt8(VERSION), ...preimage]));
}
const preimage = computeArtifactHashPreimage(artifact);
const artifactHash = computeArtifactHash(computeArtifactHashPreimage(artifact));
getLogger().debug('Computed artifact hash', { artifactHash, ...preimage });
return artifactHash;
}
export function computeArtifactHashPreimage(artifact: ContractArtifact) {
const privateFunctionRoot = computeArtifactFunctionTreeRoot(artifact, FunctionType.PRIVATE);
const unconstrainedFunctionRoot = computeArtifactFunctionTreeRoot(artifact, FunctionType.UNCONSTRAINED);
const metadataHash = computeArtifactMetadataHash(artifact);
return { privateFunctionRoot, unconstrainedFunctionRoot, metadataHash };
}
export function computeArtifactMetadataHash(artifact: ContractArtifact) {
return sha256Fr(Buffer.from(JSON.stringify({ name: artifact.name, outputs: artifact.outputs }), 'utf-8'));
}
export function computeArtifactFunctionTreeRoot(artifact: ContractArtifact, fnType: FunctionType) {
const root = computeArtifactFunctionTree(artifact, fnType)?.root;
return root ? Fr.fromBuffer(root) : Fr.ZERO;
}
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), getArtifactMerkleTreeHasher());
return calculator.computeTree(leaves.map(x => x.toBuffer()));
}
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(computeFunctionArtifactHash);
}
export function computeFunctionArtifactHash(
fn:
| FunctionArtifact
| (Pick<FunctionArtifact, 'bytecode'> & { functionMetadataHash: Fr; selector: FunctionSelector }),
) {
const selector = 'selector' in fn ? fn.selector : FunctionSelector.fromNameAndParameters(fn);
const bytecodeHash = sha256Fr(fn.bytecode).toBuffer();
const metadataHash = 'functionMetadataHash' in fn ? fn.functionMetadataHash : computeFunctionMetadataHash(fn);
return sha256Fr(Buffer.concat([numToUInt8(VERSION), selector.toBuffer(), metadataHash.toBuffer(), bytecodeHash]));
}
export function computeFunctionMetadataHash(fn: FunctionArtifact) {
return sha256Fr(Buffer.from(JSON.stringify(fn.returnTypes), 'utf8'));
}
function getLogger() {
return createDebugLogger('aztec:circuits:artifact_hash');
}
export function getArtifactMerkleTreeHasher() {
return (l: Buffer, r: Buffer) => sha256Fr(Buffer.concat([l, r])).toBuffer();
}