From 59c4ead588b95868823ecda74b84570035efb5d6 Mon Sep 17 00:00:00 2001 From: sklppy88 Date: Mon, 11 Nov 2024 18:14:09 +0000 Subject: [PATCH] use validation lib to enforce sorting and ordering --- .../src/contract/artifact_hash.test.ts | 22 ++++++++++++++++--- .../circuits.js/src/contract/artifact_hash.ts | 6 +---- .../circuits.js/src/tests/fixtures.ts | 2 +- yarn-project/foundation/src/abi/abi.ts | 11 +++++++++- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/yarn-project/circuits.js/src/contract/artifact_hash.test.ts b/yarn-project/circuits.js/src/contract/artifact_hash.test.ts index fcbacad077f..269da7368e9 100644 --- a/yarn-project/circuits.js/src/contract/artifact_hash.test.ts +++ b/yarn-project/circuits.js/src/contract/artifact_hash.test.ts @@ -1,6 +1,10 @@ import { type ContractArtifact } from '@aztec/foundation/abi'; +import { loadContractArtifact } from '@aztec/types/abi'; +import type { NoirCompiledContract } from '@aztec/types/noir'; -import { getTestContractArtifact } from '../tests/fixtures.js'; +import { readFileSync } from 'fs'; + +import { getPathToFixture, getTestContractArtifact } from '../tests/fixtures.js'; import { computeArtifactHash } from './artifact_hash.js'; describe('ArtifactHash', () => { @@ -17,7 +21,7 @@ describe('ArtifactHash', () => { notes: {}, }; expect(computeArtifactHash(emptyArtifact).toString()).toMatchInlineSnapshot( - `"0x0c6fd9b48570721c5d36f978d084d77cacbfd2814f1344985f40e62bea6e61be"`, + `"0x0dea64e7fa0688017f77bcb7075485485afb4a5f1f8508483398869439f82fdf"`, ); }); @@ -26,8 +30,20 @@ describe('ArtifactHash', () => { for (let i = 0; i < 1000; i++) { expect(computeArtifactHash(testArtifact).toString()).toMatchInlineSnapshot( - `"0x16b7b028aaaa06d648cc521c385644d7786d2f1ae49157b27a424b172b298162"`, + `"0x28faac60666e51e4c1f46439d154831c5cbef6b10cdae51a25aa41cb4fa50f65"`, ); } }); + + it('calculates the test contract artifact hash', () => { + const path = getPathToFixture('Test.test.json'); + const content = JSON.parse(readFileSync(path).toString()) as NoirCompiledContract; + content.outputs.structs.functions.reverse(); + + const testArtifact = loadContractArtifact(content); + + expect(computeArtifactHash(testArtifact).toString()).toMatchInlineSnapshot( + `"0x28faac60666e51e4c1f46439d154831c5cbef6b10cdae51a25aa41cb4fa50f65"`, + ); + }); }); diff --git a/yarn-project/circuits.js/src/contract/artifact_hash.ts b/yarn-project/circuits.js/src/contract/artifact_hash.ts index de8ada1782c..de23fe9594d 100644 --- a/yarn-project/circuits.js/src/contract/artifact_hash.ts +++ b/yarn-project/circuits.js/src/contract/artifact_hash.ts @@ -59,11 +59,7 @@ export function computeArtifactHashPreimage(artifact: ContractArtifact) { } export function computeArtifactMetadataHash(artifact: ContractArtifact) { - // TODO: #6021 We need to make sure the artifact is deterministic from any specific compiler run. This relates to selectors not being sorted and being - // apparently random in the order they appear after compiled w/ nargo. We can try to sort this upon loading an artifact. - // TODO: #6021: Should we use the sorted event selectors instead? They'd need to be unique for that. - // Response - The output selectors need to be sorted, because if not noir makes no guarantees on the order of outputs for some reason - return sha256Fr(Buffer.from(JSON.stringify({ name: artifact.name }), 'utf-8')); + return sha256Fr(Buffer.from(JSON.stringify({ name: artifact.name, outputs: artifact.outputs }), 'utf-8')); } export function computeArtifactFunctionTreeRoot(artifact: ContractArtifact, fnType: FunctionType) { diff --git a/yarn-project/circuits.js/src/tests/fixtures.ts b/yarn-project/circuits.js/src/tests/fixtures.ts index ee8146a79fe..280c4240bb5 100644 --- a/yarn-project/circuits.js/src/tests/fixtures.ts +++ b/yarn-project/circuits.js/src/tests/fixtures.ts @@ -48,6 +48,6 @@ export function getSampleUnconstrainedFunctionBroadcastedEventPayload(): Buffer return Buffer.from(readFileSync(path).toString(), 'hex'); } -function getPathToFixture(name: string) { +export function getPathToFixture(name: string) { return resolve(dirname(fileURLToPath(import.meta.url)), `../../fixtures/${name}`); } diff --git a/yarn-project/foundation/src/abi/abi.ts b/yarn-project/foundation/src/abi/abi.ts index b58d5cc5527..2a22565a615 100644 --- a/yarn-project/foundation/src/abi/abi.ts +++ b/yarn-project/foundation/src/abi/abi.ts @@ -358,7 +358,16 @@ export const ContractArtifactSchema: ZodFor = z.object({ aztecNrVersion: z.string().optional(), functions: z.array(FunctionArtifactSchema), outputs: z.object({ - structs: z.record(z.array(AbiTypeSchema)), + structs: z.record(z.array(AbiTypeSchema)).transform(structs => { + for (const [key, value] of Object.entries(structs)) { + // We are manually ordering events and functions in the abi by path. + // The path ordering is arbitrary, and only needed to ensure deterministic order. + if (key === 'events' || key === 'functions') { + structs[key] = (value as StructType[]).sort((a, b) => (a.path > b.path ? -1 : 1)); + } + } + return structs; + }), globals: z.record(z.array(AbiValueSchema)), }), storageLayout: z.record(z.object({ slot: schemas.Fr })),