Skip to content

Commit 0e2c01a

Browse files
Merge pull request #1019 from ProvableHQ/fix/verify-function-execution
[Fix] Enable verification of function executions with multiple imports
2 parents dbe60ca + 908258d commit 0e2c01a

File tree

12 files changed

+189
-75
lines changed

12 files changed

+189
-75
lines changed

sdk/src/browser.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ExecutionJSON, FeeExecutionJSON } from "./models/execution/executionJSO
1010
import { ExecutionObject, FeeExecutionObject } from "./models/execution/executionObject";
1111
import { FinalizeJSON } from "./models/finalizeJSON";
1212
import { FunctionObject } from "./models/functionObject";
13+
import { ImportedVerifyingKeys, ImportedPrograms } from "./models/imports";
1314
import { InputJSON } from "./models/input/inputJSON";
1415
import { InputObject } from "./models/input/inputObject";
1516
import { OutputJSON } from "./models/output/outputJSON";
@@ -128,6 +129,8 @@ export {
128129
FunctionKeyPair,
129130
FunctionKeyProvider,
130131
Header,
132+
ImportedPrograms,
133+
ImportedVerifyingKeys,
131134
InputJSON,
132135
InputObject,
133136
KeySearchParams,

sdk/src/models/imports.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
interface ImportedVerifyingKeys {
2+
[key: string]: Array<[string, string]>;
3+
}
4+
5+
interface ImportedPrograms {
6+
[key: string]: string; // This allows for arbitrary keys with any type values
7+
}
8+
9+
export { ImportedVerifyingKeys, ImportedPrograms }

sdk/src/network-client.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ interface AleoNetworkClientOptions {
3333
* const publicNetworkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined, account);
3434
*/
3535
class AleoNetworkClient {
36-
host: string;
37-
headers: { [key: string]: string };
38-
account: Account | undefined;
39-
40-
constructor(host: string, options?: AleoNetworkClientOptions | undefined) {
41-
this.host = host + "/%%NETWORK%%";
36+
host: string;
37+
headers: { [key: string]: string };
38+
account: Account | undefined;
39+
readonly network: string;
40+
41+
constructor(host: string, options?: AleoNetworkClientOptions) {
42+
this.host = host + "/%%NETWORK%%";
43+
this.network = "%%NETWORK%%";
4244

4345
if (options && options.headers) {
4446
this.headers = options.headers;

sdk/src/program-manager.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Account } from "./account";
22
import { AleoNetworkClient, AleoNetworkClientOptions, ProgramImports } from "./network-client";
3-
3+
import { ImportedPrograms, ImportedVerifyingKeys } from "./models/imports";
44
import { RecordProvider, RecordSearchParams } from "./record-provider";
55

66
import {
@@ -1952,12 +1952,38 @@ class ProgramManager {
19521952
}
19531953

19541954
/**
1955-
* Verify a proof of execution from an offline execution
1955+
* Verify a proof from an offline execution. This is useful when it is desired to do offchain proving and verification.
19561956
*
1957-
* @param {executionResponse} executionResponse
1957+
* @param {executionResponse} executionResponse The response from an offline function execution (via the `programManager.run` method)
1958+
* @param {ImportedPrograms} imports The imported programs used in the execution. Specified as { "programName": "programSourceCode", ... }
1959+
* @param {ImportedVerifyingKeys} importedVerifyingKeys The verifying keys in the execution. Specified as { "programName": [["functionName", "verifyingKey"], ...], ... }
19581960
* @returns {boolean} True if the proof is valid, false otherwise
1961+
*
1962+
* @example
1963+
* /// Import the mainnet version of the sdk used to build executions.
1964+
* import { Account, ProgramManager } from "@provablehq/sdk/mainnet.js";
1965+
*
1966+
* /// Create the source for two programs.
1967+
* const program = "import add_it_up.aleo; \n\n program mul_add.aleo;\n\nfunction mul_and_add:\n input r0 as u32.public;\n input r1 as u32.private;\n mul r0 r1 into r2;\n call add_it_up.aleo/add_it r1 r2 into r3; output r3 as u32.private;\n";
1968+
* const program_import = "program add_it_up.aleo;\n\nfunction add_it:\n input r0 as u32.public;\n input r1 as u32.private;\n add r0 r1 into r2;\n output r2 as u32.private;\n";
1969+
* const programManager = new ProgramManager(undefined, undefined, undefined);
1970+
*
1971+
* /// Create a temporary account for the execution of the program
1972+
* const account = Account.fromCipherText(process.env.ciphertext, process.env.password);
1973+
* programManager.setAccount(account);
1974+
*
1975+
* /// Get the response and ensure that the program executed correctly
1976+
* const executionResponse = await programManager.run(program, "mul_and_add", ["5u32", "5u32"], true);
1977+
*
1978+
* /// Construct the imports and verifying keys
1979+
* const imports = { "add_it_up.aleo": program_import };
1980+
* const importedVerifyingKeys = { "add_it_up.aleo": [["add_it", "verifyingKey1..."]] };
1981+
*
1982+
* /// Verify the execution.
1983+
* const isValid = programManager.verifyExecution(executionResponse, imports, importedVerifyingKeys);
1984+
* assert(isValid);
19591985
*/
1960-
verifyExecution(executionResponse: ExecutionResponse): boolean {
1986+
verifyExecution(executionResponse: ExecutionResponse, imports?: ImportedPrograms, importedVerifyingKeys?: ImportedVerifyingKeys): boolean {
19611987
try {
19621988
const execution = <FunctionExecution>(
19631989
executionResponse.getExecution()
@@ -1970,6 +1996,8 @@ class ProgramManager {
19701996
verifyingKey,
19711997
program,
19721998
function_id,
1999+
imports,
2000+
importedVerifyingKeys,
19732001
);
19742002
} catch (e) {
19752003
console.warn(

sdk/tests/data/program.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Define an execution with imports, its imported programs, and the verifying keys for the functions called from the imported programs.
2+
const SPIN_VERIFYING_KEY: string = "verifier1qysqqqqqqqqqqqxrjvqqqqqqqqqtaycqqqqqqqqqnpjszqqqqqqqpm9lqyqqqqqqqrddxqqqqqqqqqqvqqqqqqqqqqqt8ksvknxqn3yc8rlnf75w0d6v7h3kad4patlmn4rzq7yny8rawe4c58yq3t64v2p4ts3g506j2juq5mzxwk3z34zrjrvkhl99xju75derjkxv64ktsdzmu3mwkj486tnndw2x8cnwj9q469yf3l7304sqzyufsu8pqdcykenav93ms3uqh9sgahmxrr74f6wr8eqyxkud0xw2jlt9v674gj0ml4zrqzf3mz0pszntjt5mtw79spqcud6ekjpqc9u28snzyg8vdt78up3q6mlq7nh7ms06xyslj7s9jcna0r7s2t9pdqyvae8kcjqcl358u88qrcu83q3zta2rf58yvqrtvqmtux8d0h3sx9r44xw4a069lz989262kja6xuqzttnujfrthkj6fr0vlvz9fdzle8kupdqea0x8pe6xju962lskf7sajl3pddxx4arqd8aafuzxczypyk0cqvjt26th3hg845vrxx9vtzpgn5vpu5j49ednwvyf4yl86ujpknqcqzq2jvn5ux8sh286tkgsqv7mqz20629zxffvee9ncdl8mdw38uhqsfdam3585jdxcuu6wxds7m8mss5yxj2tmcu6cq93hh28s9ws33ey0vz9mv9xcy3pfyekzruxtznw0625ynvc4j2mtd8yl9drcftlkqqvl4em0dxz6umvxua9cqg9sesfw6gs6fwvgtadw79mkh48kwpvevypd0f6da8scpck2xnhxhe6zcxrknntkd92wcqccdfdy2qhwne53e3fhak42p0y73re7njr8xyraq7dy4ncvfcly5j2jler3ln04dgzkrdsm7vufdrarh7xy6vpw634mv2jyv088zj9upl63gntav65gskcyez0cg86wa6hzhjjjygqzh7mndqnk8yhdh9ppud0a7tqruvdqge9a9ltkk4vjua07kq2l7xqd55zjyh7jqq96tyglk55l4a25t2sqqqqqqqqqs6zqrd";
3+
const SPEND_VERIFYING_KEY: string = "verifier1qygqqqqqqqqqqqz33cqqqqqqqqqxrrsqqqqqqqqql5rqzqqqqqqqptslqyqqqqqqqpedvqqqqqqqqqqvqqqqqqqqqqqyudfp2mjt8zj3lskjzunts69hgx2kpwryn37p62f30mwqtca442epnw2dqstdnh88ljx44p5cmjyqnfx620nl9pwsh6qsaeughffgfrpxc5qhl5wda6ekss7pt3j72ps32wlrv7mlljjq9lq5jn8yn29gztqk8yz8ehq7smprjs3gzsllqpzaq07gk9yw50te0yarl5yd796xldapdfpc4n75ccy7z2jkmfyss8zz89gz7fw7937xjzt3k3cmhrekgjzzvxmqtzd20ffae8eym3lhq2gwejgqjmw9c8edum06s6a24q2zv9w3kcz5uavcgatlskxxy285nq5uhceusavwzu8rqu0e6lj6j5kgva00aw6x0khfyn4gg3ppeqqwmw3crvtgrdc8w45flwm4d9e8r3edct8hsgven70juqd9me3jk27cgr9fz90h57r2eqdctegc3ecqfh3kr9vx8u0tjhmfp4edpd9skke5yqw9u2ep8l5mwcuhdqcthr8u98wqsng6thy7wsfm2hut3musppwexfetnc7p46ckcjx2a3zvcxf8hht9yja03ymx5sy9wuhsmvsag676w6jg5yeqw3qzah5r38zpqxk8wzm0vk52ed4qtypnup37x2uj89lftvk4hfskd9wwumxvgawsm5xfum05p7vapjzpwu79k9hr7qf0wckhmkmmdxka8zz5qkaxrjsz3eny3x0vdsn5z9zcm8n2me44a56ej7893uduwv2l3ls7ua09dgqsvuddsgn4c9gn68hplup49rtfht0we6ruwn2sqe53z9sv4zq8924w7fku98fwpujykme2tumda4sqk54july0cd8gsvdx62t7gqjrfmrt6t7ezc8cvrxw99rq72h3qm3rr87fl47d5uw55pvdpfcj7weqqvks75erlpc73d08e2xsatay4vc2ey69wz8648wzqvagsyx4vs4lmnqqqqqqqqqqqwv0uty";
4+
const MINT_VERIFYING_KEY: string = "verifier1qygqqqqqqqqqqqyjvyqqqqqqqqqfvcgqqqqqqqqqar9sqqqqqqqqq282qqqqqqqqqpjexqqqqqqqqqqvqqqqqqqqqqqtj8lxtlmlrtwaffkdwfctwu2ujt795jnnwj0jsgyc57n0lcsqkjy5u04k25gf9wzt45xwaxt9vzsqwj2ad42zuukvjklg8r8t5uxw6sslpsjdetjnx0vpqdwp7rddernk8kdr6av3cpx6e5cd7tu579ucr47qrhz003d2xpwc5rejfp60acvra8edv3qmkp29ukhzv0htt8gnnkfk6afgj2vg9ys9hjl4ww3gszkd2zl2yh8ms0tfxn546w8q20dmj5fcxuyj8vevl9r0pk3fm0y27g5r25z97gjyxrgfdperxh490qxjhcffdfxdc65hvtan5q76ghfpqg3mzzs350s0jlu5pc4k3252nq93dctnjscmy3rgxypswpvucjqxreh6x5ahwfpa7yrl24s5pq2naz6fz0u3v2zhqp3fcx0ycq9ptxtqscnf3g6u8llurjer85g5zyqpwfd599vphjna37cjehn88y36u6hl0tp9r206grcdrssggawcjfcf3vuscy90sks8s0klryzmdgxszagsqqxzly4j92q7psv9a36exzuz7ckkrslcxtnv77vq9hcurdcxq98xvdz0a8pnct7lcr9x8dprsxhdsq2j4t3805e3dztss26h8p8e6q3mzhv0549fw8dktm06tjayvnu5xag8hx9k3hsgvjknyaaawqzcj0wm5nzffuq3tr0pfh0arw5qgsc326sccyn7wz3h0nu7vcz9pu4k39syt248t7hexlapdvffpzq0we245z8l70qk8c5kmqkfwmedmf0dje4kxtc0qntwc9yxl09jhcshytl73ddhuem5lyx9ns6qvcgpspr50m76j4c4aa5wrgrqvm40jl00whc5yajujcqlwmlpdsskfw3se2ghy25pg66wm7z8zxatz25sp6e63w6qr378h0ul7v5udurmc26fkkeqynypurffzz50lmcmqv67vjpqqqqqqqqqqa59tnr";
5+
const PROGRAM: string = "import puzzle_arcade_coin_v002.aleo;\nimport puzzle_arcade_ticket_v002.aleo;\n\nprogram puzzle_spinner_v002.aleo;\n\nstruct Result:\n nonce as field;\n tickets as u64;\n\nmapping used_nonces:\n key as field.public;\n value as boolean.public;\n\nfunction spin:\n input r0 as puzzle_arcade_coin_v002.aleo/PuzzleArcadeCoin.record;\n input r1 as Result.public;\n input r2 as signature.private;\n sign.verify r2 aleo196a39wq9q8ea779cmlmff0c9pj2gl4f5e8fhjpvmufe5utuq7y8snz4h2l r1 into r3;\n assert.eq r3 true ;\n is.eq r1.tickets 1000000u64 into r4;\n is.eq r1.tickets 2000000u64 into r5;\n or r4 r5 into r6;\n is.eq r1.tickets 5000000u64 into r7;\n or r6 r7 into r8;\n is.eq r1.tickets 10000000u64 into r9;\n or r8 r9 into r10;\n assert.eq r10 true ;\n call puzzle_arcade_coin_v002.aleo/spend r0 1000000u64 into r11;\n call puzzle_arcade_ticket_v002.aleo/mint r0.owner r1.tickets into r12 r13;\n async spin r13 r1.nonce into r14;\n output r11 as puzzle_arcade_coin_v002.aleo/PuzzleArcadeCoin.record;\n output r12 as puzzle_arcade_ticket_v002.aleo/PuzzleArcadeTicket.record;\n output r14 as puzzle_spinner_v002.aleo/spin.future;\n\nfinalize spin:\n input r0 as puzzle_arcade_ticket_v002.aleo/mint.future;\n input r1 as field.public;\n get.or_use used_nonces[r1] false into r2;\n assert.eq r2 false ;\n set true into used_nonces[r1];\n await r0;\n";
6+
const IMPORT_1: string = "program puzzle_arcade_coin_v002.aleo;\n\nrecord PuzzleArcadeCoin:\n owner as address.private;\n amount as u64.private;\n\nfunction mint:\n input r0 as address.public;\n input r1 as u64.public;\n assert.eq self.caller self.signer ;\n assert.eq self.caller aleo196a39wq9q8ea779cmlmff0c9pj2gl4f5e8fhjpvmufe5utuq7y8snz4h2l ;\n cast r0 r1 into r2 as PuzzleArcadeCoin.record;\n output r2 as PuzzleArcadeCoin.record;\n\nfunction spend:\n input r0 as PuzzleArcadeCoin.record;\n input r1 as u64.public;\n gte r0.amount r1 into r2;\n assert.eq r2 true ;\n sub r0.amount r1 into r3;\n cast r0.owner r3 into r4 as PuzzleArcadeCoin.record;\n output r4 as PuzzleArcadeCoin.record;\n";
7+
const IMPORT_2: string = "program puzzle_arcade_ticket_v002.aleo;\n\nrecord PuzzleArcadeTicket:\n owner as address.private;\n amount as u64.private;\n\nmapping registry:\n key as address.public;\n value as boolean.public;\n\nfunction add_program_to_registry:\n input r0 as address.private;\n assert.eq self.caller self.signer ;\n assert.eq self.caller aleo196a39wq9q8ea779cmlmff0c9pj2gl4f5e8fhjpvmufe5utuq7y8snz4h2l ;\n async add_program_to_registry r0 into r1;\n output r1 as puzzle_arcade_ticket_v002.aleo/add_program_to_registry.future;\n\nfinalize add_program_to_registry:\n input r0 as address.public;\n set true into registry[r0];\n\nfunction mint:\n input r0 as address.public;\n input r1 as u64.public;\n cast r0 r1 into r2 as PuzzleArcadeTicket.record;\n async mint self.caller into r3;\n output r2 as PuzzleArcadeTicket.record;\n output r3 as puzzle_arcade_ticket_v002.aleo/mint.future;\n\nfinalize mint:\n input r0 as address.public;\n get.or_use registry[r0] false into r1;\n assert.eq r1 true ;\n\nfunction spend:\n input r0 as PuzzleArcadeTicket.record;\n input r1 as u64.public;\n gte r0.amount r1 into r2;\n assert.eq r2 true ;\n sub r0.amount r1 into r3;\n cast r0.owner r3 into r4 as PuzzleArcadeTicket.record;\n output r4 as PuzzleArcadeTicket.record;\n\nfunction join:\n input r0 as PuzzleArcadeTicket.record;\n input r1 as PuzzleArcadeTicket.record;\n gt r0.amount 0u64 into r2;\n assert.eq r2 true ;\n gt r1.amount 0u64 into r3;\n assert.eq r3 true ;\n add r0.amount r1.amount into r4;\n cast self.signer r4 into r5 as PuzzleArcadeTicket.record;\n output r5 as PuzzleArcadeTicket.record;\n\nfunction join3:\n input r0 as PuzzleArcadeTicket.record;\n input r1 as PuzzleArcadeTicket.record;\n input r2 as PuzzleArcadeTicket.record;\n gt r0.amount 0u64 into r3;\n assert.eq r3 true ;\n gt r1.amount 0u64 into r4;\n assert.eq r4 true ;\n gt r2.amount 0u64 into r5;\n assert.eq r5 true ;\n add r0.amount r1.amount into r6;\n add r6 r2.amount into r7;\n cast self.signer r7 into r8 as PuzzleArcadeTicket.record;\n output r8 as PuzzleArcadeTicket.record;\n\nfunction join4:\n input r0 as PuzzleArcadeTicket.record;\n input r1 as PuzzleArcadeTicket.record;\n input r2 as PuzzleArcadeTicket.record;\n input r3 as PuzzleArcadeTicket.record;\n gt r0.amount 0u64 into r4;\n assert.eq r4 true ;\n gt r1.amount 0u64 into r5;\n assert.eq r5 true ;\n gt r2.amount 0u64 into r6;\n assert.eq r6 true ;\n gt r3.amount 0u64 into r7;\n assert.eq r7 true ;\n add r0.amount r1.amount into r8;\n add r8 r2.amount into r9;\n add r9 r3.amount into r10;\n cast self.signer r10 into r11 as PuzzleArcadeTicket.record;\n output r11 as PuzzleArcadeTicket.record;\n\nfunction join5:\n input r0 as PuzzleArcadeTicket.record;\n input r1 as PuzzleArcadeTicket.record;\n input r2 as PuzzleArcadeTicket.record;\n input r3 as PuzzleArcadeTicket.record;\n input r4 as PuzzleArcadeTicket.record;\n gt r0.amount 0u64 into r5;\n assert.eq r5 true ;\n gt r1.amount 0u64 into r6;\n assert.eq r6 true ;\n gt r2.amount 0u64 into r7;\n assert.eq r7 true ;\n gt r3.amount 0u64 into r8;\n assert.eq r8 true ;\n gt r4.amount 0u64 into r9;\n assert.eq r9 true ;\n add r0.amount r1.amount into r10;\n add r10 r2.amount into r11;\n add r11 r3.amount into r12;\n add r12 r4.amount into r13;\n cast self.signer r13 into r14 as PuzzleArcadeTicket.record;\n output r14 as PuzzleArcadeTicket.record;\n";
8+
9+
export { IMPORT_1, IMPORT_2, MINT_VERIFYING_KEY, PROGRAM, SPEND_VERIFYING_KEY, SPIN_VERIFYING_KEY };

sdk/tests/network-client.test.ts

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,11 @@ async function expectThrows(f: () => Promise<any>): Promise<void> {
3535

3636
describe("NodeConnection", () => {
3737
let connection: AleoNetworkClient;
38-
let network: string;
3938
let windowFetchSpy: sinon.SinonSpy;
4039

4140
beforeEach(() => {
42-
connection = new AleoNetworkClient(
43-
"https://api.explorer.provable.com/v1",
44-
);
45-
if (
46-
connection.host === "https://api.explorer.provable.com/v1/testnet"
47-
) {
48-
network = "testnet";
49-
} else {
50-
network = "mainnet";
51-
}
52-
windowFetchSpy = sinon.spy(globalThis, "fetch");
41+
connection = new AleoNetworkClient("https://api.explorer.provable.com/v1");
42+
windowFetchSpy = sinon.spy(globalThis, 'fetch');
5343
});
5444

5545
afterEach(() => {
@@ -381,9 +371,9 @@ describe("NodeConnection", () => {
381371
});
382372
});
383373

384-
describe("Test API methods that return wasm objects", () => {
385-
it("Plaintext returned from the API should have expected properties", async () => {
386-
if (network === "testnet") {
374+
describe('Test API methods that return wasm objects', () => {
375+
it('Plaintext returned from the API should have expected properties', async () => {
376+
if (connection.network === "testnet") {
387377
// Check a struct variant of a plaintext object.
388378
let plaintext = await connection.getProgramMappingPlaintext(
389379
"credits.aleo",
@@ -414,10 +404,8 @@ describe("NodeConnection", () => {
414404

415405
it("should have correct data within the wasm object and summary object for an execution transaction", async () => {
416406
// Get the first transaction at block 24700 on testnet.
417-
if (network === "testnet") {
418-
const transaction = await connection.getTransactionObject(
419-
"at1fjy6s9md2v4rgcn3j3q4qndtfaa2zvg58a4uha0rujvrn4cumu9qfazxdd",
420-
);
407+
if (connection.network === "testnet") {
408+
const transaction = await connection.getTransactionObject("at1fjy6s9md2v4rgcn3j3q4qndtfaa2zvg58a4uha0rujvrn4cumu9qfazxdd");
421409
const transition = <Transition>transaction.transitions()[0];
422410
const summary = <TransactionObject>transaction.summary(true);
423411

@@ -527,10 +515,8 @@ describe("NodeConnection", () => {
527515

528516
it("should have correct data within the wasm object and summary object for a deployment transaction", async () => {
529517
// Get the deployment transaction for token_registry.aleo
530-
if (network === "mainnet") {
531-
const transaction = await connection.getTransactionObject(
532-
"at15mwg0jyhvpjjrfxwrlwzn8puusnmy7r3xzvpjht4e5gzgnp68q9qd0qqec",
533-
);
518+
if (connection.network === "mainnet") {
519+
const transaction = await connection.getTransactionObject("at15mwg0jyhvpjjrfxwrlwzn8puusnmy7r3xzvpjht4e5gzgnp68q9qd0qqec");
534520
const summary = <TransactionObject>transaction.summary(true);
535521
const deployment = <DeploymentObject>summary.deployment;
536522

@@ -558,8 +544,8 @@ describe("NodeConnection", () => {
558544
}
559545
});
560546

561-
it("Should give the correct JSON response when requesting multiple transactions", async () => {
562-
if (network === "testnet") {
547+
it('Should give the correct JSON response when requesting multiple transactions', async () => {
548+
if (connection.network === "testnet") {
563549
const transactions = await connection.getTransactions(27400);
564550
expect(transactions.length).equal(4);
565551
expect(transactions[0].status).equal("accepted");

0 commit comments

Comments
 (0)