From f264c5421424bf58d983fe104aaf7c7126259e01 Mon Sep 17 00:00:00 2001 From: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Date: Wed, 6 Sep 2023 10:16:03 +0100 Subject: [PATCH] feat: initial `is_valid` eip1271 style wallet + minimal test changes (#1935) Fixes #1913. --- yarn-project/acir-simulator/src/acvm/acvm.ts | 1 + .../acir-simulator/src/client/db_oracle.ts | 7 + .../src/client/private_execution.ts | 3 + .../src/aztec_rpc_server/aztec_rpc_server.ts | 5 + .../aztec-rpc/src/database/database.ts | 14 ++ .../aztec-rpc/src/database/memory_db.ts | 20 +++ .../aztec-rpc/src/simulator_oracle/index.ts | 6 + ...schnorr_auth_witness_account_contract.json | 104 ++++++++++++ .../contract/auth_witness_account_contract.ts | 35 ++++ .../aztec.js/src/account/contract/index.ts | 1 + .../auth_witness_account_entrypoint.ts | 106 ++++++++++++ .../aztec.js/src/account/entrypoint/index.ts | 1 + .../aztec.js/src/aztec_rpc_client/wallet.ts | 55 ++++++- .../src/e2e_account_contracts.test.ts | 71 ++++++-- .../src/e2e_lending_contract.test.ts | 153 +++++++++++++----- .../native_token_contract/src/main.nr | 24 ++- .../Nargo.toml | 8 + .../src/auth_oracle.nr | 28 ++++ .../src/main.nr | 41 +++++ .../src/util.nr | 22 +++ .../noir-contracts/src/scripts/copy_output.ts | 1 + .../types/src/interfaces/aztec_rpc.ts | 7 + 22 files changed, 658 insertions(+), 55 deletions(-) create mode 100644 yarn-project/aztec.js/src/abis/schnorr_auth_witness_account_contract.json create mode 100644 yarn-project/aztec.js/src/account/contract/auth_witness_account_contract.ts create mode 100644 yarn-project/aztec.js/src/account/entrypoint/auth_witness_account_entrypoint.ts create mode 100644 yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/Nargo.toml create mode 100644 yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/auth_oracle.nr create mode 100644 yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/main.nr create mode 100644 yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/util.nr diff --git a/yarn-project/acir-simulator/src/acvm/acvm.ts b/yarn-project/acir-simulator/src/acvm/acvm.ts index 076842c1a6d..505d0e039d4 100644 --- a/yarn-project/acir-simulator/src/acvm/acvm.ts +++ b/yarn-project/acir-simulator/src/acvm/acvm.ts @@ -34,6 +34,7 @@ export const ONE_ACVM_FIELD: ACVMField = `0x${'00'.repeat(Fr.SIZE_IN_BYTES - 1)} type ORACLE_NAMES = | 'computeSelector' | 'packArguments' + | 'getAuthWitness' | 'getSecretKey' | 'getNote' | 'getNotes' diff --git a/yarn-project/acir-simulator/src/client/db_oracle.ts b/yarn-project/acir-simulator/src/client/db_oracle.ts index 5fa48c5044f..b77eff0e0da 100644 --- a/yarn-project/acir-simulator/src/client/db_oracle.ts +++ b/yarn-project/acir-simulator/src/client/db_oracle.ts @@ -88,6 +88,13 @@ export interface DBOracle extends CommitmentsDB { */ getCompleteAddress(address: AztecAddress): Promise; + /** + * Retrieve the auth witness for a given message hash. + * @param message_hash - The message hash. + * @returns A Promise that resolves to an array of field elements representing the auth witness. + */ + getAuthWitness(message_hash: Fr): Promise; + /** * Retrieve the secret key associated with a specific public key. * The function only allows access to the secret keys of the transaction creator, diff --git a/yarn-project/acir-simulator/src/client/private_execution.ts b/yarn-project/acir-simulator/src/client/private_execution.ts index 29efd94472b..bc470ab2813 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.ts @@ -80,6 +80,9 @@ export class PrivateFunctionExecution { packArguments: async args => { return toACVMField(await this.context.packedArgsCache.pack(args.map(fromACVMField))); }, + getAuthWitness: async ([messageHash]) => { + return (await this.context.db.getAuthWitness(fromACVMField(messageHash))).map(toACVMField); + }, getSecretKey: ([ownerX], [ownerY]) => this.context.getSecretKey(this.contractAddress, ownerX, ownerY), getPublicKey: async ([acvmAddress]) => { const address = frToAztecAddress(fromACVMField(acvmAddress)); diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts index a0b2284afbe..5a6034db556 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts @@ -76,6 +76,11 @@ export class AztecRPCServer implements AztecRPC { this.clientInfo = `${name.split('/')[name.split('/').length - 1]}@${version}`; } + public async addAuthWitness(messageHash: Fr, witness: Fr[]) { + await this.db.addAuthWitness(messageHash, witness); + return Promise.resolve(); + } + /** * Starts the Aztec RPC server by beginning the synchronisation process between the Aztec node and the database. * diff --git a/yarn-project/aztec-rpc/src/database/database.ts b/yarn-project/aztec-rpc/src/database/database.ts index 76ed1166e69..0809cf7e0e2 100644 --- a/yarn-project/aztec-rpc/src/database/database.ts +++ b/yarn-project/aztec-rpc/src/database/database.ts @@ -10,6 +10,20 @@ import { NoteSpendingInfoDao } from './note_spending_info_dao.js'; * addresses, storage slots, and nullifiers. */ export interface Database extends ContractDatabase { + /** + * Add a auth witness to the database. + * @param messageHash - The message hash. + * @param witness - An array of field elements representing the auth witness. + */ + addAuthWitness(messageHash: Fr, witness: Fr[]): Promise; + + /** + * Fetching the auth witness for a given message hash. + * @param messageHash - The message hash. + * @returns A Promise that resolves to an array of field elements representing the auth witness. + */ + getAuthWitness(messageHash: Fr): Promise; + /** * Get auxiliary transaction data based on contract address and storage slot. * It searches for matching NoteSpendingInfoDao objects in the MemoryDB's noteSpendingInfoTable diff --git a/yarn-project/aztec-rpc/src/database/memory_db.ts b/yarn-project/aztec-rpc/src/database/memory_db.ts index 6704ab0977c..208096fd106 100644 --- a/yarn-project/aztec-rpc/src/database/memory_db.ts +++ b/yarn-project/aztec-rpc/src/database/memory_db.ts @@ -19,11 +19,31 @@ export class MemoryDB extends MemoryContractDatabase implements Database { private treeRoots: Record | undefined; private globalVariablesHash: Fr | undefined; private addresses: CompleteAddress[] = []; + private authWitnesses: Record = {}; constructor(logSuffix?: string) { super(createDebugLogger(logSuffix ? 'aztec:memory_db_' + logSuffix : 'aztec:memory_db')); } + /** + * Add a auth witness to the database. + * @param messageHash - The message hash. + * @param witness - An array of field elements representing the auth witness. + */ + public addAuthWitness(messageHash: Fr, witness: Fr[]): Promise { + this.authWitnesses[messageHash.toString()] = witness; + return Promise.resolve(); + } + + /** + * Fetching the auth witness for a given message hash. + * @param messageHash - The message hash. + * @returns A Promise that resolves to an array of field elements representing the auth witness. + */ + public getAuthWitness(messageHash: Fr): Promise { + return Promise.resolve(this.authWitnesses[messageHash.toString()]); + } + public addNoteSpendingInfo(noteSpendingInfoDao: NoteSpendingInfoDao) { this.noteSpendingInfoTable.push(noteSpendingInfoDao); return Promise.resolve(); diff --git a/yarn-project/aztec-rpc/src/simulator_oracle/index.ts b/yarn-project/aztec-rpc/src/simulator_oracle/index.ts index 42a33e58c3b..08f613978e0 100644 --- a/yarn-project/aztec-rpc/src/simulator_oracle/index.ts +++ b/yarn-project/aztec-rpc/src/simulator_oracle/index.ts @@ -46,6 +46,12 @@ export class SimulatorOracle implements DBOracle { return completeAddress; } + async getAuthWitness(messageHash: Fr): Promise { + const witness = await this.db.getAuthWitness(messageHash); + if (!witness) throw new Error(`Unknown auth witness for message hash ${messageHash.toString()}`); + return witness; + } + async getNotes(contractAddress: AztecAddress, storageSlot: Fr) { const noteDaos = await this.db.getNoteSpendingInfo(contractAddress, storageSlot); return noteDaos.map(({ contractAddress, storageSlot, nonce, notePreimage, siloedNullifier, index }) => ({ diff --git a/yarn-project/aztec.js/src/abis/schnorr_auth_witness_account_contract.json b/yarn-project/aztec.js/src/abis/schnorr_auth_witness_account_contract.json new file mode 100644 index 00000000000..a048caeb4d7 --- /dev/null +++ b/yarn-project/aztec.js/src/abis/schnorr_auth_witness_account_contract.json @@ -0,0 +1,104 @@ +{ + "name": "SchnorrAuthWitnessAccount", + "functions": [ + { + "name": "_inner_is_valid", + "functionType": "secret", + "isInternal": false, + "parameters": [ + { + "name": "address", + "type": { + "kind": "field" + }, + "visibility": "private" + } + ], + "returnTypes": [], + "bytecode": "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", + "verificationKey": "0000000200000800000000740000000f00000003515f3109623eb3c25aa5b16a1a79fd558bac7a7ce62c4560a8c537c77ce80dd339128d1d37b6582ee9e6df9567efb64313471dfa18f520f9ce53161b50dbf7731bc5f900000003515f322bc4cce83a486a92c92fd59bd84e0f92595baa639fc2ed86b00ffa0dfded2a092a669a3bdb7a273a015eda494457cc7ed5236f26cee330c290d45a33b9daa94800000003515f332729426c008c085a81bd34d8ef12dd31e80130339ef99d50013a89e4558eee6d0fa4ffe2ee7b7b62eb92608b2251ac31396a718f9b34978888789042b790a30100000003515f342be6b6824a913eb7a57b03cb1ee7bfb4de02f2f65fe8a4e97baa7766ddb353a82a8a25c49dc63778cd9fe96173f12a2bc77f3682f4c4448f98f1df82c75234a100000003515f351f85760d6ab567465aadc2f180af9eae3800e6958fec96aef53fd8a7b195d7c000c6267a0dd5cfc22b3fe804f53e266069c0e36f51885baec1e7e67650c62e170000000c515f41524954484d455449430d9d0f8ece2aa12012fa21e6e5c859e97bd5704e5c122064a66051294bc5e04213f61f54a0ebdf6fee4d4a6ecf693478191de0c2899bcd8e86a636c8d3eff43400000003515f43224a99d02c86336737c8dd5b746c40d2be6aead8393889a76a18d664029096e90f7fe81adcc92a74350eada9622ac453f49ebac24a066a1f83b394df54dfa0130000000c515f46495845445f42415345060e8a013ed289c2f9fd7473b04f6594b138ddb4b4cf6b901622a14088f04b8d2c83ff74fce56e3d5573b99c7b26d85d5046ce0c6559506acb7a675e7713eb3a00000007515f4c4f4749430721a91cb8da4b917e054f72147e1760cfe0ef3d45090ac0f4961d84ec1996961a25e787b26bd8b50b1a99450f77a424a83513c2b33af268cd253b0587ff50c700000003515f4d05dbd8623b8652511e1eb38d38887a69eceb082f807514f09e127237c5213b401b9325b48c6c225968002318095f89d0ef9cf629b2b7f0172e03bc39aacf6ed800000007515f52414e474504b57a3805e41df328f5ca9aefa40fad5917391543b7b65c6476e60b8f72e9ad07c92f3b3e11c8feae96dedc4b14a6226ef3201244f37cfc1ee5b96781f48d2b000000075349474d415f3125001d1954a18571eaa007144c5a567bb0d2be4def08a8be918b8c05e3b27d312c59ed41e09e144eab5de77ca89a2fd783be702a47c951d3112e3de02ce6e47c000000075349474d415f3223994e6a23618e60fa01c449a7ab88378709197e186d48d604bfb6931ffb15ad11c5ec7a0700570f80088fd5198ab5d5c227f2ad2a455a6edeec024156bb7beb000000075349474d415f3300cda5845f23468a13275d18bddae27c6bb189cf9aa95b6a03a0cb6688c7e8d829639b45cf8607c525cc400b55ebf90205f2f378626dc3406cc59b2d1b474fba000000075349474d415f342d299e7928496ea2d37f10b43afd6a80c90a33b483090d18069ffa275eedb2fc2f82121e8de43dc036d99b478b6227ceef34248939987a19011f065d8b5cef5c0000000010000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f" + }, + { + "name": "constructor", + "functionType": "secret", + "isInternal": false, + "parameters": [], + "returnTypes": [], + "bytecode": "H4sIAAAAAAAA/9Xc12/aUBzFcZIm6d4le+8dG9tg05Wme++9Ehro3un/X44CEsprTh6+V7LAL+h8GPYdv8u/XC5XyW23tvrRXj86Gs+b5507zrsaz1tb83y18RgFxTiulgrVMAo3gkJWSZMgTirFNEzDJE02C2kUVdM4LWWVrBRkYRxVw1qSRbVgu3W3vFawy7aXOXsgOXshOfsgOfshOQcgOQchOYcgOYchOUcgOUchOccgOcchOScgOSchOacgOachOWcgOWchOecgOechORcgORchOZcgOZchOVcgOQNIzhCSswDJGUFyxpCcCSRnEZKzBMmZQnJmkJzlPcrZviNnsLsWthnNZyHmdqP5HMS8z2g+DzF3GM0XIOZOo/kixNxlNK9CzN1G8yWIucdoXoOYe43myxBzn9F8BWLuN5qvQswDRvM1iHnQaL4OMQ8ZzTcg5mGj+SbEPGI034KYR43m2xDzmNF8B2IeN5rvQswTRvM9iHnSaL4PMU8ZzQ8g5mmj+SHEPGM0P4KYZ43mxxDznNH8BGKeN5qfQswLRvMziHnRaH4OMS8ZzS8g5mWj+SXEvGI0v4KYA6P5NcQcGs1vIOaC0fwWYo6M5ncQc2w0v4eYE6N5HWIuGs0bEHPJaK5AzKnR/AFizozmTYi5bDRXIeb9RnMNYj5gNH+EmA8azZ8g5kNG82eI+bDR/AViPmI0f4WYjxrN3yDmY0bzd4j5uNH8A2I+YTT/hJhPGs2/IOZTRvNviPm00fwHYj5jNP+FmPNG85bRnG+8TvN/+rQnSnuEtGdGe0g0HtT4SOMF9Z/Vn1T/Sv0N3X91P9L1Wdcr/X71fdbnm295L8uNR+2F094w7ZXS3qHmXhrttVirH6rFV226arVVu6xaXtW2qtZTtY+qBVRtnGrFVDulWiLV1qjWRLUXqkXQ2rzWqrV2q7VMre1prUtrP1oL0dqA5so1d6y51PX6obk2zT1pLkZzExqra+yqsZzGNurrq++rvqD6Ruor6N6pe4murbrW6Len7+JWi/s/E/s4lihRAAA=", + "verificationKey": "0000000200000800000000740000000f00000003515f3109623eb3c25aa5b16a1a79fd558bac7a7ce62c4560a8c537c77ce80dd339128d1d37b6582ee9e6df9567efb64313471dfa18f520f9ce53161b50dbf7731bc5f900000003515f322bc4cce83a486a92c92fd59bd84e0f92595baa639fc2ed86b00ffa0dfded2a092a669a3bdb7a273a015eda494457cc7ed5236f26cee330c290d45a33b9daa94800000003515f332729426c008c085a81bd34d8ef12dd31e80130339ef99d50013a89e4558eee6d0fa4ffe2ee7b7b62eb92608b2251ac31396a718f9b34978888789042b790a30100000003515f342be6b6824a913eb7a57b03cb1ee7bfb4de02f2f65fe8a4e97baa7766ddb353a82a8a25c49dc63778cd9fe96173f12a2bc77f3682f4c4448f98f1df82c75234a100000003515f351f85760d6ab567465aadc2f180af9eae3800e6958fec96aef53fd8a7b195d7c000c6267a0dd5cfc22b3fe804f53e266069c0e36f51885baec1e7e67650c62e170000000c515f41524954484d455449430d9d0f8ece2aa12012fa21e6e5c859e97bd5704e5c122064a66051294bc5e04213f61f54a0ebdf6fee4d4a6ecf693478191de0c2899bcd8e86a636c8d3eff43400000003515f43224a99d02c86336737c8dd5b746c40d2be6aead8393889a76a18d664029096e90f7fe81adcc92a74350eada9622ac453f49ebac24a066a1f83b394df54dfa0130000000c515f46495845445f42415345060e8a013ed289c2f9fd7473b04f6594b138ddb4b4cf6b901622a14088f04b8d2c83ff74fce56e3d5573b99c7b26d85d5046ce0c6559506acb7a675e7713eb3a00000007515f4c4f4749430721a91cb8da4b917e054f72147e1760cfe0ef3d45090ac0f4961d84ec1996961a25e787b26bd8b50b1a99450f77a424a83513c2b33af268cd253b0587ff50c700000003515f4d05dbd8623b8652511e1eb38d38887a69eceb082f807514f09e127237c5213b401b9325b48c6c225968002318095f89d0ef9cf629b2b7f0172e03bc39aacf6ed800000007515f52414e474504b57a3805e41df328f5ca9aefa40fad5917391543b7b65c6476e60b8f72e9ad07c92f3b3e11c8feae96dedc4b14a6226ef3201244f37cfc1ee5b96781f48d2b000000075349474d415f3125001d1954a18571eaa007144c5a567bb0d2be4def08a8be918b8c05e3b27d312c59ed41e09e144eab5de77ca89a2fd783be702a47c951d3112e3de02ce6e47c000000075349474d415f3223994e6a23618e60fa01c449a7ab88378709197e186d48d604bfb6931ffb15ad11c5ec7a0700570f80088fd5198ab5d5c227f2ad2a455a6edeec024156bb7beb000000075349474d415f3300cda5845f23468a13275d18bddae27c6bb189cf9aa95b6a03a0cb6688c7e8d829639b45cf8607c525cc400b55ebf90205f2f378626dc3406cc59b2d1b474fba000000075349474d415f342d299e7928496ea2d37f10b43afd6a80c90a33b483090d18069ffa275eedb2fc2f82121e8de43dc036d99b478b6227ceef34248939987a19011f065d8b5cef5c0000000010000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f" + }, + { + "name": "entrypoint", + "functionType": "secret", + "isInternal": false, + "parameters": [ + { + "name": "payload", + "type": { + "kind": "struct", + "path": "aztec::entrypoint::EntrypointPayload", + "fields": [ + { + "name": "flattened_args_hashes", + "type": { + "kind": "array", + "length": 4, + "type": { + "kind": "field" + } + } + }, + { + "name": "flattened_selectors", + "type": { + "kind": "array", + "length": 4, + "type": { + "kind": "field" + } + } + }, + { + "name": "flattened_targets", + "type": { + "kind": "array", + "length": 4, + "type": { + "kind": "field" + } + } + }, + { + "name": "nonce", + "type": { + "kind": "field" + } + } + ] + }, + "visibility": "public" + } + ], + "returnTypes": [], + "bytecode": "H4sIAAAAAAAA/+1dB5gUxfPt3bsj55yDBAmKO3cHd+SkBAEJShAQheMOQaIEBTGhKIqKKIgkAQmKGDCgCIpZVFAx55xzzunfvVv9u9phTGzVOPW/me+r780sXNd71TU9bzY2zVDqtXQV3yI6ojrSYd8eZ7iOi8F+RuLPFPy5qqajuo4aOmqiv7P/XktHbR11dNSFf4+if6+no76OBjoaquQtAtgFMCvWJjs7Pycz38lyRscy247JbR3Lbj2mTa6T67TObT02MzcrKz83Ozen7Zi2ObG2TnZWvlPQum1WQSyxNUJjxVLcOHk2FsKziRCeTYXwPFgIz2ZCeDYXwrOFEJ4thfA8RAjPQ4XwbCWE52FCeMaE8HSE8MwUwjNLCM9sITxbE/I03Mw9TgMYz9zX/K4S9zYGawDWBKwFWBuwDmBdwHqA9QEbADYEPAiwEWBjwCaATQEPBmwG2BywBWBLwEMADwVsBXgYYAzQAcwEzALMBmyNxmujI0clb9RzmKtk9FpbITzbCeHZXgjPDkJ4dhTCs5MQnp2F8OwihGdXITy7CeHZXQjPw4XwPEIIzx6K3vNWgPGM3zPeLxewLWA7wPaAHQA7AnYC7AzYBbArYDfA7oCHAx4B2EMVes6eOnpB3Qy3ssDNLz9usLeOI3Wkofn7qzmNpbY5venGiqUjnn0A+wJnm6OfjqN09NcxQMdAHYN0HK3jGB2DdQzRMVTHMB3H6hiuY4SOkTqO0zFKx/E6TtAxWscYHXk6xurI11GgY5yOE3WM1zFBx0k6JuqYpGOyjik6puqYpuNkHdN1zNAxU8csHafoOFXHbB1zdJymY66O03WcoeNMHWfpOFvHPB3n6DhXx3wd5+k4X8cCHRfouFDHQh0XQQ0uRnXKiyawpEq8NoW3Ymi/C2Asxc2+lkY5ZgbwVwgV0lMS5UxT++tNR4/Zf7ev9ZWDOqSTcs7Owrns5j7fuqB9m78EcDLbuPwZXWfOOHHo+BmT86dPj6BR7MjdPUa2qoupwhnuQqIq/mSUKo5ypaFcZiulCitbHP1bCVoe8dUd8yiB8pZAeUvS5s2yGt36SyL9JdC+5VGalkdcfymXfpu3NMpbhjaveSLlf1ctrL8M0mzzl0U8yjHoL+vSb/OWQ3nL0+Y1TyD9z1Fg/eWRfpu/AuJRkZZHXH8Fl36btyLKW4k2rzFRqrKH/kpIv81fGfGoQssjrr+yS7/NWwXlrUqbN/6EYTUP/VWRfpu/GuJRnUF/NZd+m7c6yluDNq8xz3FX6tZfA+m3+WsiHrUY9Nd06bd5a6G8tWnzmpuGuPt266+N9Nv8dRCPugz667j027x1Ud56tHmNGY7fZbj110P6bf76iEcDWh5x/fVd+m3eBihvQ9q85kYgfhfl1t8Q6bf5D0I8GtHyiOs/yKXf5m2E8jamzWtuguKv4rj1N0b6bf4miEdTWh5x/U1c+m3epijvwbR5zQ1g/NUqt/6DkX6bvxni0ZxBfzOXfpu3OcrbgjavufmNvyrn1t8C6bf5WyIehzDob+nSb/MegvIeSpvX3PjHX3106z8U6bf5WyEehzHob+XSb/MehvLGSPM68eEcD/0xpN/mdxCPTFIeCf2OS7/Nm4nyZtHqj6fM9tCfhfTb/NmIR2tSHgn92S79Nm9rlLcNrf54ihwP/W2Qfps/B/HIJeWR0J/j0m/z5qK8bWn1x6e5nYf+tki/zd8O8WhPyiOhv51Lv83bHuXtQKs/3nIdPfR3QPpt/o6IRydSHgn9HV36bd5OKG9nWv2t8VhYf2ek363V8OhKyiOhv4tLv83bFeXtRqs/fpp399DfDem3+bsjHoeT8kjo7+7Sb/MejvIeQas/vuT08NB/BNJv8/dAPHqS8kjo7+HSb/P2RHl70eqPLzG9PfT3Qvpt/t6Ix5GkPBL6e7v027xHorx9aPXHl/m+Hvr7IP02f1/Eox8pj4T+vi79Nm8/lPcoWv3x+//+HvqPQvpt/v6IxwBSHgn9/V36bd4BKO9AWv3x+/9BHvoHIv02/yDE42hSHgn9g1z6bd6jUd5jaPXH7/8He+g/Bum3+QcjHkNIeST0D3bpt3mHoLxDafXH7/+HeegfivTb/MMQj2NJeST0D3Ppt3mPRXmH0+qP3/+P8NA/HOm3+UcgHiNJeST0j3Dpt3lHorzH0eqP3/+P8tB/HNJv849CPI4n5ZHQP8ql3+Y9HuU9gTRvZvw2f7SH/hOQfpt/NOIxhpRHQv9ol36bdwzKm0erP37/P9ZDfx7Sb/OPRTzySXkk9I916bd581HeAlr98fv/cR76C5B+m38c4nEiKY+E/nEu/TbviSjveFr98fv/CR76xyP9Nv8ExOMkUh4J/RNc+m3ek1DeibT64/f/kzz0T0T6bf5JiMdkUh4J/ZNc+m3eySjvFFr98fv/qR76pyD9Nv9UxGMaKY+E/qku/TbvNJT3ZFr98fv/6R76T0b6bf7piMcMUh4J/dNd+m3eGSjvTFr98fv/WR76ZyL9Nv8sxOMUUh4J/bNc+m3eU1DeU2n1x+//Z3voPxXpt/lnIx5zSHkk9M926bd556C8p9Hqj9//z/XQfxrSb/PPRTxOJ+WR0D/Xpd/mPR3lPYNWf9xynemh/wyk3+Y/E/E4i5RHQv+ZLv0271ko79m0+uMWa56H/rORfpt/HuJxDimPhP55Lv027zko77m0+uM2d76H/nORfpt/PuJxHimPhP75Lv0273ko7/m0+uOWe4GH/vORfpt/AeJxASmPhP4FLv027wUo74W0+uMWe6GH/guRfpt/IeJxEYP+hS79Nu9FKO/FtPrjtzmXeOi/GOm3+S9BPBaR8kjov8Sl3+ZdhPJeSpo3K37/v9hD/6VIv82/GPG4jJRHQv9il36b9zKU93Ja/fH7/yUe+i9H+m3+JYjHUlIeCf1LXPpt3qUo7xW0+uP3/8s89F+B9Nv8yxCPK0l5JPQvc+m3ea9EeZfT6o/f/6/w0L8c6bf5VyAeK0l5JPSvcOm3eVeivKto9cfv/1d76F+F9Nv8qxGPq0h5JPSvdum3ea9CedfQ6o/f/6/10L8G6bf51yIe60h5JPSvdem3edehvFfT6o/f/6/30H810m/zr0c8NpDySOhf79Jv825AeTfS6o/f/2/y0L8R6bf5NyEe15DySOjf5NJv816D8l5Lqz9+/7/ZQ/+1SL/NvxnxuI6UR0L/Zpd+m/c6lHcLrf74/f/1Hvq3IP02//WIxw2kPBL6r3fpt3lvQHlvpNUfv/+/yUP/jUi/zX8T4rGVlEdC/00u/TbvVpT3Zlr98fv/Wzz034z02/y3IB63kvJI6L/Fpd/mvRXlvY1Wf/z+f5uH/tuQfpt/G+JxOymPhP5tLv027+0o7x20+uP3/9s99N+B9Nv82xGPO0l5JPRvd+m3ee9EeXfQ6o/f/+/00L8D6bf5dyIed5HySOjf6dJv896F8t5Nqz9+/7/LQ//dSL/NvwvxuIeUR0L/Lpd+m/celPde0rzZ8fv/+zz034v02/z3IR73k/JI6L/Ppd/mvR/lfYBWf/z+/0EP/Q8g/Tb/g4jHQ6Q8EvofdOm3eR9CeR8mzmvG2O2h/2Gk3+bfjXg8QssjPg+PevB4BPGw+R9FPB4j5ZEdfz5kjwePxxAPm38P4rGXlkespIuH2SKu4y5ofy/i8jgpl0SPPIFyWV42T2n07/g7Ip6g5eFEUE47rj3G/P4p11KCuJYVxLWCIK6VBXGtJohrTUFc6wjiWl8Q14MEcW0iiGszQVxbCuLaShBXRxDXbEFccwRxbSeIa0dBXLsI4tpdENcegrj2FsS1ryCu/QVxHSSI62BBXIcJ4jpCENdRgriOFsR1rCCu4wRxnSCI6yRBXKcK4jpdENdZgrjOFsR1riCuZwriOk8Q1/mCuC4QxHWhIK6XCOK6WBDXJYK4LhPEdYUgrqsFcV0riOt6QVw3CeK6WRDX6wVxvUkQ11sEcd0miOt2QVx3CuK6SxDX+wRxffA/5loSPfYQesz++2702JMuPeaxfbD/KHos6tKLf10O57W/Ivckesz+Lpod17zn1v3eYLICRV2DR4jHp/w5yQYwziUq8eUB5ksMzI2a+SC9+UC/uREyHyo3H243NxrmA9bmg97GyJsPG5sPPRujbD54az4AvFYlPoR6tUoYPfOBTPPBUGOkzIcTzYckjVExH9TbohJG4AYokqlTFLiUUIXHi1zHl7qOF7uOL3MdX+46XuI6Xuo6vsJ1vMx1fKXreLnreIXreKXreJXreLXr+CrX8RrX8VrX8TrX8dWu4/Wu4w2u442u402u42tcx9e6jje7jq9zHW9xHV/vOr5BFf5wn93sid0FMJbalnTOpPoTvZcQjrUwne5c/qv6HSjP/AKzxZxFRGOZubiUsH4XBb5+8aGdxamPlQmancsI63dxkOuX/T+ezuWpjRVDmp0lhPW7JKj1y0zi6Sw98LFiLs3OFYT1WxTA+rUp2I+ns+zAxsr10OxcSVi/S4NWv1xPns7yfz9Wzp9odlYQ1m9xkOqX86c8nZX/bqzMv9DsrCKs32VBqV/OX/J0Vv/zsfL+RrNzFWH9Lg9C/XL+lqez5p+NFfsHmp21hPVb8l/XL/aPeDrr/n6s1v9Qs3M1Yf2W/pf1y/7HPJ31fzlWdsG/0OxsIKzfFf9V/XL+FU9n45+PlfsvNTubCOu37D+oX9uCf83TucZ7rNgBaHauJazflX7XL3ZAPJ3N+4/lHKBm5zrC+i33s35jD5insyV5rKwUNDvXE9ZvhU/1yyxIiadzg6J7LhE/Z5dq/Vb6VL9YaptD+DybczFh/VYJqR/h80TOIsL6rRZSP8LnOZzFhPW7Skj9CO/TncsJ67dGSP0I7zOdpYT1WyukfoT3Sc4ywvqtE1I/Qp/vLCes39VC6kfoU52VhPVbL6R+hD7LWU1Yvw1C6kfoE5w1hPXbKKR+hNc5Zx1h/TYJqR/hOu2sJ6zfNULqR7jOOBsJ63etkPoRnicOYc84XPVzv+cz1Xm+8cDH2u/9G2nK482viv59qjfSjRXDfPGPHtg3+0bV/r1bjEGTcuVx17GcYnxjMdck3cQw7lZF1/xcurfSz9FfLgKx1LakmsZS3NJU4YnitdHkyYwRju24H+DjnXgTv93sL5vgXxspCRhVhYtPMcAIqq1ZoP5AfxdBGEFj/IH+xuv/RP5knJLoMfv35RAXRVeTGMOCGmNdMO07zM0E7lKF7zi/BeVQaBJw7lSvsjcrugXwFsVzQlK7lFuVPJdyq+JxKbeh/dClpDjmrVBQ6nG3qWC7FKN7G/0csbqUbYrfpVAvXCkssJyu5D9zPPZ3tO5Aj/0bx9NN7T9XbsfTTf294/EaJ3Q8f779z/Hcjoppju9Q+zseqs84eZ1EqV79byfkdYfiOQGpF6HblT8LfKo8tyt5Lm+74nF5d6L90OWlOOZ2KCj1uDtUsF2e0b2Dfo5YXd4OVaRdnuNBV5zLs98Ucxd6LHR5NGP64vJ2qmSXd5eS5fJ2EvK6S/Gc3NSL0E7lzwKfKs+7lTyXd7ficXn4Z4ZDl5fimHerwifjKce9RwXb5Rnd99DPEavLu0cVaZeX6UFXnMu7F/A+9Fjo8mjG9MXl3auSXd59SpbLu5eQ132K5+SmXoTuVf4s8KnyvF/Jc3n3Kx6X9wDaD11eimPeDwWlHvdBFWyXZ3Q/SD9HrC7vQVWkXV6WB11xLs9+y+7D6LHQ5dGM6YvLe0glu7yHlSyX9xAhr4cVz8lNvQg9pPxZ4FPluVvJc3m7FY/LewTthy4vxTF3Q0Gpx31UBdvlGd2P0s8Rq8t7VBVpl5ftQVecy3sMcA96LHR5NGP64vIeU8kub4+S5fIeI+S1R/Gc3NSL0GPKnwU+VZ57lTyXt1fxuDz8AzWhy0txzL1QUOpxn1DBdnlG9xP0c8Tq8p5QRdrltfagK87l2V+u2oceC10ezZi+uLwnVbLL26dkubwnCXntUzwnN/Ui9KTyZ4FPledTSp7Le0rxuLyn0X7o8lIc8ykoKPW4z6hguzyj+xn6OWJ1ec+oIu3y2njQFefyngV8Dj0WujyaMX1xec+qZJf3nJLl8p4l5PWc4jm5qRehZ5U/C3yqPJ9X8lze84rH5b2A9kOXl+KYz0NBqcd9UQXb5RndL9LPEavLe1EVaZeX40FXnMt7CfBl9Fjo8mjG9MXlvaSSXd7LSpbLe4mQ18uK5+SmXoReUv4s8KnyfEXJc3mvKB6X9yraD11eimO+AgWlHvc1FWyXZ3S/Rj9HrC7vNVWkXV6uB11xLu91wDfQY6HLoxnTF5f3ukp2eW8oWS7vdUJebyiek5t6EXpd+bPAp8rzTSXP5b2peFzeW2g/dHkpjvkmFJR63LdVsF2e0f02/Ryxury3VZF2eW096Ipzee8AvoseC10ezZi+uLx3VLLLe1fJcnnvEPJ6V/Gc3NSL0DvKnwU+VZ7vKXku7z3F4/LeR/uhy0txzPegoNTjfqCC7fKM7g/o54jV5X2girTLG+1BV5zL+xDwI/RY6PJoxvTF5X2okl3eR0qWy/uQkNdHiufkpl6EPlT+LPCp8vxYyXN5Hysel/cJ2g9dXopjfgwFpR73UxVsl2d0f0o/R6wu71NVpF3eGA+64lzeZ4Cfo8dCl0czpi8u7zOV7PI+V7Jc3meEvD5XPCc39SL0mfJngU+V5xdKnsv7QvG4vC/RfujyUhzzCygo9bhfqWC7PKP7K/o5YnV5X6ki7fLyPOiKc3lfA36DHgtdHs2Yvri8r1Wyy/tGyXJ5XxPy+kbxnNzUi9DXyp8FPlWe3yp5Lu9bxePyvkP7octLccxvoaDU436vgu3yjO7v6eeI1eV9r4q0yxvrQVecy/sB8Ef0WOjyaMb0xeX9oJJd3o9Klsv7gZDXj4rn5KZehH5Q/izwqfL8SclzeT8pHpf3M9oPXV6KY/4EBaUe9xcVbJdndP9CP0esLu8XVaRdXr4HXXEu71fA39BjocujGdMXl/erSnZ5vylZLu9XQl6/KZ6Tm3oR+lX5s8CnyvN3Jc/l/a54XN4faD90eSmO+TsUlLxQkWC7vN9V8kwRjcvq8nBNYyluAl1egQddcS4vAgWOot4LXR7NmL64PDOB2OVFI7JcXoRwYY5GeE5u6kUoEvFngU+VZ1pEnstLI6wt5puODkKXl+KYZpLSI/TjZgTc5RndGcJcXkaRdnkOpRP7z1xeMShw8dDlyXR5xVwur7gwl1eMcGEuHuE5uakXoWJCXF4JgS6vBJPLKxm6PNpJKsng8koF3OUZ3aWEubxSRdvlOR50xbm80lDgMqHLk+nySrtcXhlhLq804cJcJsJzclMvQqWFuLyyAl1eWSaXVy50ebSTVI7B5ZUPuMszussLc3nli7bLy/SgK87lVYACVwxdnkyXV8Hl8ioKc3kVCBfmihGek5t6EaogxOVVEujyKjG5vMqhy6OdpMoMLq9KwF2e0V1FmMurUrRdXpYHXXEuryoUuFro8mS6vKoul1dNmMurSrgwV4vwnNzUi1BVIS6vukCXV53J5dUIXR7tJNVgcHk1A+7yjO6awlxezaLt8rI96IpzebWgwLVDlyfT5dVyubzawlxeLcKFuXaE5+SmXoRqCXF5dQS6vDpMLq9u6PJoJ6kug8urF3CXZ3TXE+by6hVtl9fag644l1cfCtwgdHkyXV59l8trIMzl1SdcmBtEeE5u6kWovhCX11Cgy2vI5PIOCl0e7SQdxODyGgXc5RndjYS5vEZF2+W18aArzuU1hgI3CV2eTJfX2OXymghzeY0JF+YmEZ6Tm3oRaizE5TUV6PKaMrm8g0OXRztJBzO4vGYBd3lGdzNhLq9Z0XZ5OR50xbm85lDgFqHLk+nymrtcXgthLq854cLcIsJzclMvQs2FuLyWAl1eSyaXd0jo8mgn6RAGl3dowF2e0X2oMJd3aNF2ebkedMW5vFZQ4MNClyfT5bVyubzDhLm8VoQL82ERnpObehFqJcTlxQS6vBiTy3NCl0c7SQ6Dy8sMuMszujOFubzMou3y2nrQFefysqDA2aHLk+nyslwuL1uYy8siXJizIzwnN/UilCXE5bUW6PJaM7m8NqHLo52kNgwuLyfgLs/ozhHm8nKKtssb7UFXnMvLhQK3DV2eTJeX63J5bYW5vFzChblthOfkpl6EcoW4vHYCXV47JpfXPnR5tJPUnsHldQi4yzO6OwhzeR2Ktssb40FXnMvrCAXuFLo8mS6vo8vldRLm8joSLsydIjwnN/Ui1FGIy+ss0OV1ZnJ5XUKXRzxJDC6va8BdntHdVZjL61q0XV6eB11xLq8bFLh76PJkurxuLpfXXZjL60a4MHeP8Jzc1ItQNyEu73CBLu9wJpd3ROjyaCfpCAaX1yPgLs/o7iHM5fUo2i5vrAddcS6vJxS4V+jyZLq8ni6X10uYy+tJuDD3ivCc3NSLUE8hLq+3QJfXm8nlHRm6PNpJOpLB5fUJuMszuvsIc3l9irbLy/egK87l9YUC9wtdnkyX19fl8voJc3l9CRfmfhGek5t6EeorxOUdJdDlHcXk8vqHLo92kvozuLwBAXd5RvcAYS5vQNF2eQUedMW5vIFQ4EGhy5Pp8ga6XN4gYS5vIOHCPCjCc3JTL0IDiZ1Iho4+On7X0RfQjt9PRwkdRwH2BxwAOBBwEODRgMcADgYcAjgUcBjgsYDDAUcAjgQ8DnAU4PGAJwCOBhwDmAc4FjAfsABwHOCJgOMBJwCeBDgRcBLgZMApgFMBpwGeDDgdcAbgTMBZgKcAngo4G3AO4GmAcwFPBzwD8EzAswDPBpwHeA7guYDzAc8DPB9wAeAFgBcCLgS8CLCBSmw3wPH1gFsArwPcDHgt4DWAmwA3Am4AXA94NeA6wLWAawCvAlwNuApwJeAKwOWAVwIuA7wCcCngEsDLAS8DXAx4KeAiwEsAj3atxNRm6GjCdcxwMxcku267z+2LAcvrOEb/58EubVGXtlT5RAnrdAyxafTjzrGeol2v7TYkvHOknaQhDHeOQwN+52h0D2W4c/ynd2Sx1DbSk4uTZy0hPKsp+sUqgsYcpg+O1TFcxwgdI3Ucp2OUjuN1nKBjtI4xOvJ0jNWRr6NAxzgdJ+oYr2OCjpN0TNQxScdkHVN0TNUxTcfJOqbrmKFjpo5ZOk7RcaqO2Trm6DhNx1wdp+s4Q8eZOs7ScbaOeTrO0XGujvk6ztNxvo4FOi7QcaGOhTou0nGxjkt0LNJxqY7FOi7TcbmOJTqW6rhCxzJ0npUHNHep7sW7pNr/jrekSl7czSblTtbcYJRAOpRLr70rL0aaNztmcmWo5M19UeriUU/DtTLs542eOHHAyeNnjZ6R32Pm5LwZ46dMxm2d4RomzUOe+/F0VIrisJ+BHrN/VxxhxM2/C2Cq15ShER5TQM3zSoLraH5BYvPL+F0ZoV9LzbY8NH60k7ScwfitCLjxM7pXMBg/hTbOmsZS3PwyqMMjPLUlnrdMxrGTXjJYCQVehXrv37xk8Ifaf64iKvklgz/U379k4DVO+JLBn2//e8nATOBvqvAlg1WR/ZOmEeceTnj1X0m4MK+K+LNwpspztUD3tJrJPV0VuifaSbqKwT2tCbh7MrrXCHNPawS6pxGhe0pyT2uhwOtC9yTTPa11uad1PrinEYRX/7WEC/M6Ie7paoHu6Wom97Q+dE+0k7SewT1tCLh7Mro3CHNPGwS6p5Ghe0pyTxuhwJtC9yTTPW10uadNPrinkYRX/42EC/MmIe7pGoHu6Rom93Rt6J5oJ+laBve0OeDuyejeLMw9bRbonk4I3VOSe7oOCrwldE8y3dN1Lve0xQf3dALh1f86woV5ixD3dL1A93Q9k3u6IXRPtJN0A4N7ujHg7snovlGYe7pRoHsaHbqnJPd0ExR4a+ieZLqnm1zuaasP7mk04dX/JsKFeasQ93SzQPd0M5N7uiV0T7STdAuDe7o14O7J6L5VmHu6VaB7GhO6pyT3dBsUeFvonmS6p9tc7mmbD+5pDOHV/zbChXmbEPd0u0D3dDuTe7ojdE+0k3QHg3vaHnD3ZHRvF+aetgt0T8tC95Tknu6EAu8I3ZNM93Snyz3t8ME9LSO8+t9JuDDvYDq5o676UX6zQqpj7YwE+4JuvoVnZ4Tege5Op51ran7m22w4dD+S7k+Px1LbHML5cR5hnutYalv8G5w45npPwHv8WKYe3yukxwnnx9kb8B6vxtTjTwa8x/OYenyfkB4nnB9nX8B7fAfMtaIdl4XrFkFctwrius1HrhRfn8uxNj0T8PP0OKY1+VkhazLh/DjPBnyuRzHN9Qs+zXWA7nMdSs1mPsyTZvYVD3MvYL46fBXgOsBNgKV03KX374a5xN/qeRz8n1GAxwNuAdwKuA2woo5dev8ej7EWwf+5FHAx4GWAlwMuASyj4169fx8ay07oLvg/eYBjAfMBCwDHAZ4IOB5wAuBJgBMBJwFOBpwCOBVwGuDJgNMBZwDOBJwFeArgqYCzAecAngY4F/B0wDMAzwQ8C/BswHmA5wCeCzgf8DzA8wEXAF4AeCHgQsCLAC8GvATwXsClgFcAttBxv95/AM2NfS5zGPyfuwDvB6yk40G9/1DE+xUxpejXqPqK9ryy28PoIHxlMcUx60NBqcfdHQn2K4tG924GQ+vXK3aUJxcnz9pCeFZX9ItVBI35iD54VMdjOvbo2KvjcR1P6HhSxz4dT+l4WsczOp7V8ZyO53W8oONFHS/peFnHKzpe1fGajtd1vKHjTR1v6Xhbxzs63tXxno73dXyg40MdH+n4WMcnOj7V8ZmOz3V8oeNLHV/p+FrHNzq+1fGdju91/KDjRx0/6fhZxy86ftXxm7m46PjDCNUrcERHVEeajnQdGTqK6SiOVufwa9zDr3FXAfgad3x9iqW2sX6Ne4movLeUYc6xFDfMtyTqrND4pTimmSRTUOpxS0Xpmp9Ld6ko+RyxmpNShCeUXwb1sQhPbYnnzbe3lJWGgzLowfAtZTRj+vKWMjOB+C1lZaL8byl7LEJ39S9NuDCXifqzcKbKs6xA91SWyT2VC90T7SSVY3BP5QPunozu8sLcU3mB7mlP6J6S3FMFOKgYuieZ7qmCyz1V9ME97SF0TxUIF+aKQtxTJYHuqRKTe6ocuifaSarM4J6qBNw9Gd1VhLmnKgLd097QPSW5p6pwUC10TzLdU1WXe6rmg3vaS+ieqhIuzNWEuKfqAt1TdSb3VCN0T7STVIPBPdUMuHsyumsKc081BbqnfaF7SnJPteCgduieZLqnWi73VNsH97SP0D3VIlyYawtxT3UEuqc6TO6pbuieaCepLoN7qhdw92R01xPmnuoJdE9Phe4pyT3Vh4MGoXuS6Z7qu9xTAx/c01OE7qk+4cLcQIh7aijQPTVkck8Hhe6JdpIOYnBPjQLunozuRsLcUyOB7unp0D0luafGcNAkdE8y3VNjl3tq4oN7eprQPTUmXJibCHFPTQW6p6ZM7ung0D3RTtLBDO6pWcDdk9HdTJh7aibQPRVnWmCJ580399QcDlqE7kmme2ruck8tfHBPxQmv/s0JF+YWTCd31FU/ym9WSHWsltFgX9DNt/C0jNI70JcC/vW/5ttsOHS/LOSrJgnnx3k54F81WZupx18LeI8/ytTjrwvpccL5cV4PeI9XZ+rxtwLe488w9fjbQnqccH6ctwPe48Y/towW1jLIXGsL4tpAENcmPnKl+Bp3jrXpvYCfp48zrcnvC1mTCefHeT/gc/0E01x/JORr3Cnvoyg1m/nAX+Nu7gXiX4seha9aB6wWLfwa90P0/qEwl/hbPR+Hv30C8EnA2vC3DQCbRAu/xr2V3j/MY6w/4G8V/N8IYBQwDTA9Wvg17mYhddBYdkJbwf95BsZ8FvA5wOcBXwB8EfAlwJcBXwF8FfA1wNcB3wB8E/AtwLcB3wF8F/A9wPcBPwD8EPAjwI8BPwH8FPAzwM8BvwD8EvArwK8BvwH8FvA7wO8BfwD8EfAnwJ8BfwH8FfA3wN8BY1DnDMBi0cKvcc/U+1lobuxzmY/A3x4C/zczWvg17tl6vzW6hpuNev1oc+DPW8ZcYzl+vQKKOcdS3DDfHFTr8BXQFMc0k5QTpR83l/CJdi7duUzG226cNY2luPn1Cijh775SvgLqMI69Xy68OLWFg3bowfAVUJoxfXkF1EzgLlX4Cmg7dFK6i0eVe2eE7urflnBhbhf1Z+FMlWd7ge6pPZN76hC6J9pJ6sDgnjoG3D0Z3R19etoyltrmtGTi6peDSmWRLUje8jzosjgojjkkGiuGF7tOcND5AN1YNw/NbjfWTf29G/Ma5/+VGwtyQ1gn1ylaODHm2DRFV5W8UTs7yvdTdSa8aHShW3QKbD27oHpy9EO7KL1z/CRY7xfYz40a3Z0YdH8a8PdJGN2dGXR/xvQ6TarrhvtCTrlu4B5PtX6fB7R+rs0h7G+HsGecz4W8TtiJ8FrTlbCXzRgc15auUZ41jHKuOZ66NT9RTK27W5Snx6l5dhfC83AhPI8g5Gl+ptJcE+wzWKanzHyZWpg86cr75pwov/NnNYqltrG8XELNMZup38gbrgchUeaGYpusHtHgc+xJzVHKlbCXkJW7N6FDk3oi9RZwIh0pZWXuQ0c0U2pD9RHQUH2lNFQ/OqJZUhuqn4CGOoqSo18vxTegGyvppfj+0cL98KX4FMdsAAWlHncA4TNdXLoHRMnnyLeXtxsonksMNc86QnjWUPSLlcEysD9Q99ogHUfrOEbHYB1DdAzVMUzHsTqG6xihYyTqy/KA5mVt92JXUu3/EnlJlbwYmk3KS9/m2akSSIdy6bUv4xejzZtncmWo5M29iHfxqKfhWhP28ydPm5k/M3/AzDETx+f1mDk5b8b4KZO7j544ETeDTWKbIs1DpPvxdFSQ4rCfgR6zf1cc4Z++fyDVlXhAlOdSSs3zOIKrj9/ffHdclH4FMtuo0C7RTtIoBrt0fMDtktF9PINdUmjjrGksxc0vW3c01zMHtDx9++a7E+BgNHow/NwHzZi+fO7DTCD+5rvR0f2TUr9L5WjCq/8JhAvzaCGvW4wR6J7GMLmnvNA90U5SHoN7Ghtw92R0jxXmnsYKdE/HhO4pyT3lw0FB6J5kuqd8l3sq8ME9HUN49c8nXJgLhLincQLd0zgm93Ri6J5oJ+lEBvc0PuDuyegeL8w9jRfongaH7inJPU2Ag5NC9yTTPU1wuaeTfHBPgwmv/hMIF+aThLiniQLd00Qm9zQpdE+0kzSJwT1NDrh7MronC3NPkwW6p2ND95TknqbAwdTQPcl0T1Nc7mmqD+7pWMKr/xTChXmqEPc0TaB7msbknk4O3RPtJJ3M4J6mB9w9Gd3Thbmn6QLd0/DQPSW5pxlwMDN0TzLd0wyXe5rpg3saTnj1n0G4MM8U4p5mCXRPs5jc0ymhe6KdpFMY3NOpAXdPRvepwtzTqQLd04jQPSW5p9lwMCd0TzLd02yXe5rjg3saQXj1n024MM9hOrmjrvpRfjYw1bFOiwb7gt5Aj3FalN6BfhPw7481n2Lm0P2tkN90JJwf51vmuY6ltsU/uc8x1z8EvMcHMfX4j0J6nHB+nB8D3uM1mHr8l4D3+EimHv9VSI8Tzo/za8B7fCrMtaIdl4XrTEFc5/jIleI3yDnO9z8C3vtDmNY58w0slLrtRr3OEc6PQ62Zeq6HMs11mk9zHaB7R4dSs5kP/Bvkxl+b34AeDVgAeFK08DfI5+r902Eu8Tc+DYH/MxRwGOBUwJmAc6KFv0F+ht4/M6qSNuoeOYvpORpqnmcL4TkvSt9/9vnCs6A3zgacB2iemD1H75/L3CvzhczBeUJ4ns/YK/OhN84DPB/1ygK9fwFzr1woZA4WCuF5EWOvXAi9sRDwItQrF+v9S5h7ZZGQObhUCM/FjL2yCHrjUsDFqFcu0/uXM/fKEiFzsFQIzysYe2UJ9MZSwCtQryzT+1cy98pyIXOwQgjPlYy9shx6YwXgStQrq/T+auZeuUrIHKwRwnMtY69cBb2xBnAt6pV1ev9q5l5ZL2QONgjhuZGxV9ZDb2wA3Ih6ZZPev4a5V64VMgebhfC8jrFXroXe2Ax4HeqVLXr/euZeuUHIHNwohOdNjL1yA/TGjYA3oV7ZqvdvZu6VW4TMwa1CeN7G2Cu3QG/cCngb6pVtev925l65Q8gcbBfC807GXrkDemM74J2oV3bo/Z3MvXKXkDm4WwjPXYy9chf0xt2Au1Cv3KP372XulfuEzMH9Qng+wNgr90Fv3A/4AOqVB/X+Q8y98rCQOdgthOcjjL3yMPTGbsBHUK88qvcfY+6VPULmYK8Qno8z9soe6I29gI+jXnlC7z/J3Cv7hMzBUwxzYEu7D2r+FKD5dben9f4zzLV/Vkjtn2Os/bNQ8+dQ7Z/X+y8w1/5FIbV/ibH2L0LNX0K1f1nvv8Jc+1eF1P41xtq/CjV/DdX+db3/BnPt3xRS+7cYa/8m1PwtVPu39f47zLV/V0jt32Os/btQ8/dQ7d/X+x8w1/5DIbX/iLH2H0LNP0K1/1jvf8Jc+0+F1P4zxtp/CjX/DNX+c73/BXPtvxRS+68Ya/8l1PwrVPuv9f43zLX/Vkjtv2Os/bdQ8+9Q7b/X+z8w1/5HIbX/ibH2P0LNf0K1/1nv/8Jc+1+F1P43xtr/CjX/DdX+d73/B3PtzRNLEmofSeOrvamBqXkkrbD2Ub2flsZb+3Qhtc9grH061DwD1b6Y3i/OXPsSQmpfkrH2JaDmJVHtS+n90sy1LyOk9mUZa18Gal4W1b6c3i/PXPsKQmpfUQjPSkJ4VhbCs4oQnlWF8KwmhGd1ITxrCOFZUwjPWkJ41hbCs44QnnWF8KwnhGd9ITwbCOHZUAjPg4TwbCSEZ2MhPJsI4dlUCM+DhfBsJoRncyE8Wwjh2VIIz0MYnjPrDOOdAa+NjASsAM+dVQSsBFgZ8Bz4fwsALwa8DHAZ4CrAdYCbALcAbgXcBrgD8B7ABwEfBXwC8GnA5wFfBnwd8G3A9wE/Bvwc8GvA7wF/BvwdMAo6iwGWAiwHWAWwKmA1wOqANQBrAtYCrA1YB7AuYD3A+oANABsCHgTYCLAxYBPApoAHAzYDbA7YArAl4CH2WMeher9VWuH3/NmnSQdCLeYCHmp7Qcdhet/8sopfv7DUUNH2vt2ctML98BeWUhyzIRSUetzMNMIXZZl0Z6aRz5Fvv1xEeXJx8qwrhGdNRb9YGSwD+1m6KbJ1tNbRRkeOjlwdbXW009FeRwcdHXV0Qg1UHtB8oMW92JVU+/+SUkmVvBiaTcovJGWoxAt/Vody6bW/9lSMNm+eyZWhkjf3It7Fo56Ga03Yz588bWb+zPwBM8dMHJ/XY+bkvBnjp0zuPnriRNwMNoltijQPke7H01FBisN+BnrM/l1xhBG3ii6Aqa7EmWk8l1Jqnp0Jrj5+/yBl5zT6FSjOL7RLxJPEYJe6BtwuGd1dGeySQhtnTWMpbn7ZutZMz2sQz5tvP0jZDYreHRU//EFKmjF9+UFKM4H4Bym7p+2fNI04d2vCq383woW5u5AnLQ8X6J4OZ3JPR4TuiXaSjmBwTz0C7p6M7h7C3FMPge6pTeiektxTTyh6r9A9yXRPPV3uqZcP7qkN4dW/J+HC3EuIe+ot0D31ZnJPR4buiXaSjmRwT30C7p6M7j7C3FMfge4pJ3RPSe6pLxS9X+ieZLqnvi731M8H95RDePXvS7gw9xPino4S6J6OYnJP/UP3RDtJ/Rnc04CAuyeje4Aw9zRAoHtqH7qnJPc0EIo+KHRPMt3TQJd7GuSDe2pPePUfSLgwDxLino4W6J6OZnJPx4TuiXaSjmFwT4MD7p6M7sHC3NNgge6pQ+iektzTECj60NA9yXRPQ1zuaagP7qkD4dV/COHCPFSIexom0D0NY3JPx4buiXaSjmVwT8MD7p6M7uHC3NNwge6pY+iektzTCCj6yNA9yXRPI1zuaaQP7qkj4dV/BOHCPJLp5I666kf52cBUxzouLdgX9IZ6jOPS6B1oRgbtXFPzM59i5tBdLMOfHo+ltjmE8+MUY57rWGpb/JP7HHNdMuA9ns3U46WE9Djh/DilAt7jNZl6vGzAe7wTU4+XE9LjhPPjlAt4jw+CuVa047JwHSqI60gfuaZ6Xprzh+N8rxjw3s9lWucqCVnnCOfHqRTwuW7LNNdVfZrrAN07OpSazXyYJ6LsqwjGX5uvJOwO2AuwH2ApHaP0/vEwl/gbn3Lh/7QFbAc4CHAo4EjAijpO0PujXc/CUvfIGCGvcOUJ4Tk2jb7/bAuMgd7IAxwLaJ6Yzdf7Bcy9Mk7IHJwohOd4xl4ZB71xIuB41CsT9P5JzL0yUcgcTBLCczJjr0yE3pgEOBn1yhS9P5W5V6YJmYOThfCcztgr06A3Tgacjnplht6fydwrs4TMwSlCeJ7K2CuzoDdOATwV9cpsvT+HuVdOEzIHc4XwPJ2xV06D3pgLeDrqlTP0/pnMvXKWkDk4WwjPeYy9chb0xtmA81CvnKP3z2XulflC5uA8ITzPZ+yV+dAb5wGej3plgd6/gLlXLhQyBwuF8LyIsVcuhN5YCHgR6pWL9f4lzL2ySMgcXCqE52LGXlkEvXEp4GLUK5fp/cuZe2WJkDlYKoTnFYy9sgR6YyngFahXlun9K5l7ZbmQOVghhOdKxl5ZDr2xAnAl6pVVen81c69cJWQO1gjhuZaxV66C3lgDuBb1yjq9fzVzr6wXMgcbhPDcyNgr66E3NgBuRL2ySe9fw9wr1wqZg81CeF7H2CvXQm9sBrwO9coWvX89c6/cIGQObhTC8ybGXrkBeuNGwJtQr2zV+zcz98otQubgVoY5sO/DugVqfiug+XW32/T+Nuba3y6k9ncw1v52qPkdqPbb9f6dzLXfIaT2OxlrvwNqvhPV/i69fzdz7XcJqf09jLXfBTW/B9X+Xr1/H3Pt7xdS+wcYa38/1PwBVPsH9f5DzLV/WEjtdzPW/mGo+W5U+0f0/qPMtX9MSO33MNb+Maj5HlT7vXr/cebaPyGk9k8y1v4JqPmTqPb79P5TzLV/Wkjtn2Gs/dNQ82dQ7Z/V+88x1/55IbV/gbH2z0PNX0C1f1Hvv8Rc+5eF1P4Vxtq/DDV/BdX+Vb3/GnPtXxdS+zcYa/861PwNVPs39f5bzLV/W0jt32Gs/dtQ83dQ7d/V++8x1/59IbX/gLH270PNP0C1/1Dvf8Rc+4+F1P4Txtp/DDX/BNX+U73/GXPtPxdS+y8Ya/851PwLVPsv9f5XzLX/WkjtvxHC81shPL8TwvN7ITx/EMLzRyE8fxLC82chPH8RwvNXITx/E8LzdyE8/xDC03wZiQSeESE8o0J4pgnhmS6EZ4YQnsWE8CwuhGcJITxLCuFZSgjP0kJ4lhHCs6wQnuUIedrnzDrDeCfAc2WdAL8G/AbwW8DvAPMBJwBOAZwBOBvwDMBzABcAXgx4GeAywFWA6wA3AW4B3Ap4G+B2wLsA7wV8EPARwL2A+wCfBXwR8FXANwHfBfwQ8FPALwG/B/wB8EfAnwB/BvwF8FfA3wB/B/wD0HhhgxHAKGAaYDpgBmAxwOKAJQBLApYCLA1YBrAsYDnAljrK6/0K6YXf82efJs0CbqMAy8PfVNJRUe9XSleKs98rpx/wWDHXWE4a4vdXnGOpbU5l4nPUblVQrcNfgkpxTDNJVdLpx62aTtf8XLqrppPPEesvQVUlPKHSlD+/BHValKe2Kc6bwzj2frnw4lQNeq466r3wl6BoxvTll6DMBO6CROa4Ojop3cWjyo1PolSv/tUIF+bqQu4Wagh0TzWY3FPN0D3RTlJNBvdUK+DuyeiuxeSeqLmar+Dn4OqXg0plkS1I3vI86LI4KI45JBorhhe72tAXdQ7QjXXz0Ox2Y93U37sxr3H+X7mxIDeEdXK10wsnxhybpuiqkjdqZ0f5u5J1CC8adekWnQJbz7qonhz9UD2d3jlWD9bvpu3nRo3u2gy6awT89+KM7joMumsy/bZOquuG+0JOuW7gHk+1frUCWj/X5hD2t0PYM04tIb/tVJvwWlOP7k6X5SbPnGv10nnWMMq55njq9rA0et31hTxr1EAIz4ZCeB5EyFMvk/EbMHvfZnrKzJephcmD/w1vRPmdP6tRLLWN5eUSao4VmfqNvOEaERJlbii2yWqUHnyOjak5SrkSNhGycjcldGhST6SmAk6kg6WszM3oiGZKbahmAhqquZSGakFHNEtqQ7UQ0FAtpTTUIUIuzYcK4dlKCM/DhPCMCeHpCOGZKYRnlhCe2UJ4thbCs40QnjlCeOYK4dlWCM92Qni2F8KzgxCeHYXw7CSEZ2dintQ3cLv1gL2iDC8KZgRbd0utuSeD7gY+vWEiVZ5dCJ8CJpxrp4GAvjmSoW+6BnydMLr7MujuJkD3UQy6uwdcd6ZecJowvEmmUcDPb/MmnsYMuhsLuS4cTnhdIJxrp7GAvjmYoW+OCPg6YXQ3Z9DdQ4Dulgy6ewq5r+klhGdvITyPFMKzjxCefYXw7CeE51FCePZn4hl18YyltsW/foBK8wAhmqOEmgcK0ZxGqHmQEM3phJqPFqI5g1DzMUI0FyPUPFiI5p6EmocI0XwI4YfDhgrRfCih5mFCNLci1HysEM2HEWoeLkRzjFDzCCGaHULNI4VoziTUfJwQzVmEmkcJ0ZxNqPl4IZpbE2o+QYjmNoSaRwvRnEOoeYwQzbmEmvOEaG5LqHmsEM3tCDXnC9HcnlBzgRDNHQg1jxOiuSOh5hOFaO5EqHm8EM2dCTVPEKK5C6Hmk4Ro7kqoeaIQzd0INU8Sork7oebJQjQfTqh5ihDNRxBqnipEcw9CzdOkvF5FqPlkIZp7EWqeLkRzb0LNM4RoPpJQ80whmvsQap4lRHNfQs2nCNHcj1DzqUI0H0WoebYQzf0JNc8Rorm4otN8mhDNJQg1zxWiuSSh5tOFaC5FqPkMIZpLE2o+U4jmMoSazxKiuSyh5rOFaC5HqHmeEM3lCTWfI0RzBULN5wrRXJFQ83whmisRaj5PiObKhJrPF6K5CqHmBUI0VyXUfAGh5qowjv3hMPOZKPMZIfOZGfMZEnM/aO6PzP2C8c/GTxp/ZfyGuf6a65FZn816Zc5f089mfqvC/zFbNR3VddTQUVNHLR21ddTRUVdHPR31dTTQ0VDHQTrawd+az8+Zz5OZz1eZzxuZz9+Yz6OYz2eYzyuY9++b97Ob93eb9zub9/+a98Oa94ea90ua9w+a99OZ95eZ91uZ9x+Z9+OY96eY92uY9y+Y1/PN69vm9V7z+qd5PdC8PmZeLzKvn5jXE8zz6+b5ZvP8q3k+0jw/Z56vMs/fmOczzP29ud8193/mfsjcHxi/bPyj8VPGX5jrrbn+mPXYrE/mfDX9a+bTbv8HMBGafT+nBAA=", + "verificationKey": "0000000200000800000000740000000f00000003515f3109623eb3c25aa5b16a1a79fd558bac7a7ce62c4560a8c537c77ce80dd339128d1d37b6582ee9e6df9567efb64313471dfa18f520f9ce53161b50dbf7731bc5f900000003515f322bc4cce83a486a92c92fd59bd84e0f92595baa639fc2ed86b00ffa0dfded2a092a669a3bdb7a273a015eda494457cc7ed5236f26cee330c290d45a33b9daa94800000003515f332729426c008c085a81bd34d8ef12dd31e80130339ef99d50013a89e4558eee6d0fa4ffe2ee7b7b62eb92608b2251ac31396a718f9b34978888789042b790a30100000003515f342be6b6824a913eb7a57b03cb1ee7bfb4de02f2f65fe8a4e97baa7766ddb353a82a8a25c49dc63778cd9fe96173f12a2bc77f3682f4c4448f98f1df82c75234a100000003515f351f85760d6ab567465aadc2f180af9eae3800e6958fec96aef53fd8a7b195d7c000c6267a0dd5cfc22b3fe804f53e266069c0e36f51885baec1e7e67650c62e170000000c515f41524954484d455449430d9d0f8ece2aa12012fa21e6e5c859e97bd5704e5c122064a66051294bc5e04213f61f54a0ebdf6fee4d4a6ecf693478191de0c2899bcd8e86a636c8d3eff43400000003515f43224a99d02c86336737c8dd5b746c40d2be6aead8393889a76a18d664029096e90f7fe81adcc92a74350eada9622ac453f49ebac24a066a1f83b394df54dfa0130000000c515f46495845445f42415345060e8a013ed289c2f9fd7473b04f6594b138ddb4b4cf6b901622a14088f04b8d2c83ff74fce56e3d5573b99c7b26d85d5046ce0c6559506acb7a675e7713eb3a00000007515f4c4f4749430721a91cb8da4b917e054f72147e1760cfe0ef3d45090ac0f4961d84ec1996961a25e787b26bd8b50b1a99450f77a424a83513c2b33af268cd253b0587ff50c700000003515f4d05dbd8623b8652511e1eb38d38887a69eceb082f807514f09e127237c5213b401b9325b48c6c225968002318095f89d0ef9cf629b2b7f0172e03bc39aacf6ed800000007515f52414e474504b57a3805e41df328f5ca9aefa40fad5917391543b7b65c6476e60b8f72e9ad07c92f3b3e11c8feae96dedc4b14a6226ef3201244f37cfc1ee5b96781f48d2b000000075349474d415f3125001d1954a18571eaa007144c5a567bb0d2be4def08a8be918b8c05e3b27d312c59ed41e09e144eab5de77ca89a2fd783be702a47c951d3112e3de02ce6e47c000000075349474d415f3223994e6a23618e60fa01c449a7ab88378709197e186d48d604bfb6931ffb15ad11c5ec7a0700570f80088fd5198ab5d5c227f2ad2a455a6edeec024156bb7beb000000075349474d415f3300cda5845f23468a13275d18bddae27c6bb189cf9aa95b6a03a0cb6688c7e8d829639b45cf8607c525cc400b55ebf90205f2f378626dc3406cc59b2d1b474fba000000075349474d415f342d299e7928496ea2d37f10b43afd6a80c90a33b483090d18069ffa275eedb2fc2f82121e8de43dc036d99b478b6227ceef34248939987a19011f065d8b5cef5c0000000010000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f" + }, + { + "name": "is_valid", + "functionType": "secret", + "isInternal": false, + "parameters": [ + { + "name": "message_hash", + "type": { + "kind": "field" + }, + "visibility": "private" + } + ], + "returnTypes": [], + "bytecode": "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", + "verificationKey": "0000000200000800000000740000000f00000003515f3109623eb3c25aa5b16a1a79fd558bac7a7ce62c4560a8c537c77ce80dd339128d1d37b6582ee9e6df9567efb64313471dfa18f520f9ce53161b50dbf7731bc5f900000003515f322bc4cce83a486a92c92fd59bd84e0f92595baa639fc2ed86b00ffa0dfded2a092a669a3bdb7a273a015eda494457cc7ed5236f26cee330c290d45a33b9daa94800000003515f332729426c008c085a81bd34d8ef12dd31e80130339ef99d50013a89e4558eee6d0fa4ffe2ee7b7b62eb92608b2251ac31396a718f9b34978888789042b790a30100000003515f342be6b6824a913eb7a57b03cb1ee7bfb4de02f2f65fe8a4e97baa7766ddb353a82a8a25c49dc63778cd9fe96173f12a2bc77f3682f4c4448f98f1df82c75234a100000003515f351f85760d6ab567465aadc2f180af9eae3800e6958fec96aef53fd8a7b195d7c000c6267a0dd5cfc22b3fe804f53e266069c0e36f51885baec1e7e67650c62e170000000c515f41524954484d455449430d9d0f8ece2aa12012fa21e6e5c859e97bd5704e5c122064a66051294bc5e04213f61f54a0ebdf6fee4d4a6ecf693478191de0c2899bcd8e86a636c8d3eff43400000003515f43224a99d02c86336737c8dd5b746c40d2be6aead8393889a76a18d664029096e90f7fe81adcc92a74350eada9622ac453f49ebac24a066a1f83b394df54dfa0130000000c515f46495845445f42415345060e8a013ed289c2f9fd7473b04f6594b138ddb4b4cf6b901622a14088f04b8d2c83ff74fce56e3d5573b99c7b26d85d5046ce0c6559506acb7a675e7713eb3a00000007515f4c4f4749430721a91cb8da4b917e054f72147e1760cfe0ef3d45090ac0f4961d84ec1996961a25e787b26bd8b50b1a99450f77a424a83513c2b33af268cd253b0587ff50c700000003515f4d05dbd8623b8652511e1eb38d38887a69eceb082f807514f09e127237c5213b401b9325b48c6c225968002318095f89d0ef9cf629b2b7f0172e03bc39aacf6ed800000007515f52414e474504b57a3805e41df328f5ca9aefa40fad5917391543b7b65c6476e60b8f72e9ad07c92f3b3e11c8feae96dedc4b14a6226ef3201244f37cfc1ee5b96781f48d2b000000075349474d415f3125001d1954a18571eaa007144c5a567bb0d2be4def08a8be918b8c05e3b27d312c59ed41e09e144eab5de77ca89a2fd783be702a47c951d3112e3de02ce6e47c000000075349474d415f3223994e6a23618e60fa01c449a7ab88378709197e186d48d604bfb6931ffb15ad11c5ec7a0700570f80088fd5198ab5d5c227f2ad2a455a6edeec024156bb7beb000000075349474d415f3300cda5845f23468a13275d18bddae27c6bb189cf9aa95b6a03a0cb6688c7e8d829639b45cf8607c525cc400b55ebf90205f2f378626dc3406cc59b2d1b474fba000000075349474d415f342d299e7928496ea2d37f10b43afd6a80c90a33b483090d18069ffa275eedb2fc2f82121e8de43dc036d99b478b6227ceef34248939987a19011f065d8b5cef5c0000000010000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f" + } + ] +} diff --git a/yarn-project/aztec.js/src/account/contract/auth_witness_account_contract.ts b/yarn-project/aztec.js/src/account/contract/auth_witness_account_contract.ts new file mode 100644 index 00000000000..134c58082b8 --- /dev/null +++ b/yarn-project/aztec.js/src/account/contract/auth_witness_account_contract.ts @@ -0,0 +1,35 @@ +import { Schnorr } from '@aztec/circuits.js/barretenberg'; +import { ContractAbi } from '@aztec/foundation/abi'; +import { CompleteAddress, NodeInfo, PrivateKey } from '@aztec/types'; + +import AuthWitnessAccountContractAbi from '../../abis/schnorr_auth_witness_account_contract.json' assert { type: 'json' }; +import { AuthWitnessAccountEntrypoint } from '../entrypoint/auth_witness_account_entrypoint.js'; +import { AccountContract } from './index.js'; + +/** + * Account contract that authenticates transactions using Schnorr signatures verified against + * the note encryption key, relying on a single private key for both encryption and authentication. + * Extended to pull verification data from the oracle instead of passed as arguments. + */ +export class AuthWitnessAccountContract implements AccountContract { + constructor(private encryptionPrivateKey: PrivateKey) {} + + public getDeploymentArgs() { + return Promise.resolve([]); + } + + public async getEntrypoint({ address, partialAddress }: CompleteAddress, { chainId, version }: NodeInfo) { + return new AuthWitnessAccountEntrypoint( + address, + partialAddress, + this.encryptionPrivateKey, + await Schnorr.new(), + chainId, + version, + ); + } + + public getContractAbi(): ContractAbi { + return AuthWitnessAccountContractAbi as unknown as ContractAbi; + } +} diff --git a/yarn-project/aztec.js/src/account/contract/index.ts b/yarn-project/aztec.js/src/account/contract/index.ts index 5f34e55c34b..31a661a0f26 100644 --- a/yarn-project/aztec.js/src/account/contract/index.ts +++ b/yarn-project/aztec.js/src/account/contract/index.ts @@ -6,6 +6,7 @@ import { Entrypoint } from '../index.js'; export * from './ecdsa_account_contract.js'; export * from './schnorr_account_contract.js'; export * from './single_key_account_contract.js'; +export * from './auth_witness_account_contract.js'; // docs:start:account-contract-interface /** diff --git a/yarn-project/aztec.js/src/account/entrypoint/auth_witness_account_entrypoint.ts b/yarn-project/aztec.js/src/account/entrypoint/auth_witness_account_entrypoint.ts new file mode 100644 index 00000000000..c4a6f4915f2 --- /dev/null +++ b/yarn-project/aztec.js/src/account/entrypoint/auth_witness_account_entrypoint.ts @@ -0,0 +1,106 @@ +import { AztecAddress, Fr, FunctionData, PartialAddress, PrivateKey, TxContext } from '@aztec/circuits.js'; +import { Signer } from '@aztec/circuits.js/barretenberg'; +import { ContractAbi, FunctionAbi, encodeArguments } from '@aztec/foundation/abi'; +import { FunctionCall, PackedArguments, TxExecutionRequest } from '@aztec/types'; + +import SchnorrAuthWitnessAccountContractAbi from '../../abis/schnorr_auth_witness_account_contract.json' assert { type: 'json' }; +import { generatePublicKey } from '../../index.js'; +import { DEFAULT_CHAIN_ID, DEFAULT_VERSION } from '../../utils/defaults.js'; +import { buildPayload, hashPayload } from './entrypoint_payload.js'; +import { CreateTxRequestOpts, Entrypoint } from './index.js'; + +/** + * Account contract implementation that uses a single key for signing and encryption. This public key is not + * stored in the contract, but rather verified against the contract address. Note that this approach is not + * secure and should not be used in real use cases. + * The entrypoint is extended to support signing and creating eip1271-like witnesses. + */ +export class AuthWitnessAccountEntrypoint implements Entrypoint { + constructor( + private address: AztecAddress, + private partialAddress: PartialAddress, + private privateKey: PrivateKey, + private signer: Signer, + private chainId: number = DEFAULT_CHAIN_ID, + private version: number = DEFAULT_VERSION, + ) {} + + /** + * Sign a message hash with the private key. + * @param message - The message hash to sign. + * @returns The signature as a Buffer. + */ + public sign(message: Buffer): Buffer { + return this.signer.constructSignature(message, this.privateKey).toBuffer(); + } + + /** + * Creates an AuthWitness witness for the given message. In this case, witness is the public key, the signature + * and the partial address, to be used for verification. + * @param message - The message hash to sign. + * @returns [publicKey, signature, partialAddress] as Fr[]. + */ + async createAuthWitness(message: Buffer): Promise { + const signature = this.sign(message); + const publicKey = await generatePublicKey(this.privateKey); + + const sigFr: Fr[] = []; + for (let i = 0; i < 64; i++) { + sigFr.push(new Fr(signature[i])); + } + + return [...publicKey.toFields(), ...sigFr, this.partialAddress]; + } + + /** + * Returns the transaction request and the auth witness for the given function calls. + * Returning the witness here as a nonce is generated in the buildPayload action. + * @param executions - The function calls to execute + * @param opts - The options + * @returns The TxRequest, the auth witness to insert in db and the message signed + */ + async createTxExecutionRequestWithWitness( + executions: FunctionCall[], + opts: CreateTxRequestOpts = {}, + ): Promise<{ + /** The transaction request */ + txRequest: TxExecutionRequest; + /** The auth witness */ + witness: Fr[]; + /** The message signed */ + message: Buffer; + }> { + if (opts.origin && !opts.origin.equals(this.address)) { + throw new Error(`Sender ${opts.origin.toString()} does not match account address ${this.address.toString()}`); + } + + const { payload, packedArguments: callsPackedArguments } = await buildPayload(executions); + const message = await hashPayload(payload); + const witness = await this.createAuthWitness(message); + + const args = [payload]; + const abi = this.getEntrypointAbi(); + const packedArgs = await PackedArguments.fromArgs(encodeArguments(abi, args)); + const txRequest = TxExecutionRequest.from({ + argsHash: packedArgs.hash, + origin: this.address, + functionData: FunctionData.fromAbi(abi), + txContext: TxContext.empty(this.chainId, this.version), + packedArguments: [...callsPackedArguments, packedArgs], + }); + + return { txRequest, message, witness }; + } + + createTxExecutionRequest(executions: FunctionCall[], _opts: CreateTxRequestOpts = {}): Promise { + throw new Error(`Not implemented, use createTxExecutionRequestWithWitness instead`); + } + + private getEntrypointAbi(): FunctionAbi { + const abi = (SchnorrAuthWitnessAccountContractAbi as any as ContractAbi).functions.find( + f => f.name === 'entrypoint', + ); + if (!abi) throw new Error(`Entrypoint abi for account contract not found`); + return abi; + } +} diff --git a/yarn-project/aztec.js/src/account/entrypoint/index.ts b/yarn-project/aztec.js/src/account/entrypoint/index.ts index 0b0335b28e1..bd38032d428 100644 --- a/yarn-project/aztec.js/src/account/entrypoint/index.ts +++ b/yarn-project/aztec.js/src/account/entrypoint/index.ts @@ -6,6 +6,7 @@ export * from './entrypoint_payload.js'; export * from './entrypoint_utils.js'; export * from './single_key_account_entrypoint.js'; export * from './stored_key_account_entrypoint.js'; +export * from './auth_witness_account_entrypoint.js'; /** Options for creating a tx request out of a set of function calls. */ export type CreateTxRequestOpts = { diff --git a/yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts b/yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts index c9450bbf887..4734b0b0457 100644 --- a/yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts +++ b/yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts @@ -15,7 +15,7 @@ import { TxReceipt, } from '@aztec/types'; -import { CreateTxRequestOpts, Entrypoint } from '../account/entrypoint/index.js'; +import { AuthWitnessAccountEntrypoint, CreateTxRequestOpts, Entrypoint } from '../account/entrypoint/index.js'; import { CompleteAddress } from '../index.js'; /** @@ -94,6 +94,9 @@ export abstract class BaseWallet implements Wallet { getSyncStatus(): Promise { return this.rpc.getSyncStatus(); } + addAuthWitness(messageHash: Fr, witness: Fr[]) { + return this.rpc.addAuthWitness(messageHash, witness); + } } /** @@ -108,6 +111,56 @@ export class EntrypointWallet extends BaseWallet { } } +/** + * A wallet implementation supporting auth witnesses. + * This wallet inserts eip1271-like witnesses into the RPC, which are then fetched using an oracle + * to provide authentication data to the contract during execution. + */ +export class AuthWitnessEntrypointWallet extends BaseWallet { + constructor(rpc: AztecRPC, protected accountImpl: AuthWitnessAccountEntrypoint) { + super(rpc); + } + + /** + * Create a transaction request and add the auth witness to the RPC. + * Note: When used in simulations, the witness that is inserted could be used later by attacker with + * access to the RPC. + * Meaning that if you were to use someone elses rpc with db you could send these transactions. + * For simulations it would be desirable to bypass such that no data is generated. + * + * @param executions - The function calls to execute. + * @param opts - The options. + * @returns - The TxRequest + */ + async createTxExecutionRequest( + executions: FunctionCall[], + opts: CreateTxRequestOpts = {}, + ): Promise { + const { txRequest, message, witness } = await this.accountImpl.createTxExecutionRequestWithWitness( + executions, + opts, + ); + await this.rpc.addAuthWitness(Fr.fromBuffer(message), witness); + return txRequest; + } + + sign(messageHash: Buffer): Promise { + return Promise.resolve(this.accountImpl.sign(messageHash)); + } + + /** + * Signs the `messageHash` and adds the witness to the RPC. + * This is useful for signing messages that are not directly part of the transaction payload, such as + * approvals . + * @param messageHash - The message hash to sign + */ + async signAndAddAuthWitness(messageHash: Buffer): Promise { + const witness = await this.accountImpl.createAuthWitness(messageHash); + await this.rpc.addAuthWitness(Fr.fromBuffer(messageHash), witness); + return Promise.resolve(); + } +} + /** * A wallet implementation that forwards authentication requests to a provided account. */ diff --git a/yarn-project/end-to-end/src/e2e_account_contracts.test.ts b/yarn-project/end-to-end/src/e2e_account_contracts.test.ts index 69f5630c5e9..e0e179cae19 100644 --- a/yarn-project/end-to-end/src/e2e_account_contracts.test.ts +++ b/yarn-project/end-to-end/src/e2e_account_contracts.test.ts @@ -2,19 +2,31 @@ import { AztecRPCServer } from '@aztec/aztec-rpc'; import { Account, AccountContract, + AuthWitnessAccountContract, + AuthWitnessAccountEntrypoint, + AuthWitnessEntrypointWallet, + AztecRPC, EcdsaAccountContract, Fr, SchnorrAccountContract, SingleKeyAccountContract, Wallet, } from '@aztec/aztec.js'; -import { PrivateKey } from '@aztec/circuits.js'; +import { CompleteAddress, PrivateKey } from '@aztec/circuits.js'; import { toBigInt } from '@aztec/foundation/serialize'; import { ChildContract } from '@aztec/noir-contracts/types'; import { setup } from './fixtures/utils.js'; -function itShouldBehaveLikeAnAccountContract(getAccountContract: (encryptionKey: PrivateKey) => AccountContract) { +function itShouldBehaveLikeAnAccountContract( + getAccountContract: (encryptionKey: PrivateKey) => AccountContract, + walletSetup: ( + rpc: AztecRPC, + encryptionPrivateKey: PrivateKey, + accountContract: AccountContract, + address?: CompleteAddress, + ) => Promise<{ account: Account; wallet: Wallet }>, +) { describe(`behaves like an account contract`, () => { let context: Awaited>; let child: ChildContract; @@ -23,10 +35,14 @@ function itShouldBehaveLikeAnAccountContract(getAccountContract: (encryptionKey: let encryptionPrivateKey: PrivateKey; beforeEach(async () => { - context = await setup(); + context = await setup(0); encryptionPrivateKey = PrivateKey.random(); - account = new Account(context.aztecRpcServer, encryptionPrivateKey, getAccountContract(encryptionPrivateKey)); - wallet = await account.deploy().then(tx => tx.getWallet()); + + ({ account, wallet } = await walletSetup( + context.aztecRpcServer, + encryptionPrivateKey, + getAccountContract(encryptionPrivateKey), + )); child = await ChildContract.deploy(wallet).send().deployed(); }, 60_000); @@ -54,12 +70,12 @@ function itShouldBehaveLikeAnAccountContract(getAccountContract: (encryptionKey: it('fails to call a function using an invalid signature', async () => { const accountAddress = await account.getCompleteAddress(); - const invalidWallet = await new Account( + const { wallet: invalidWallet } = await walletSetup( context.aztecRpcServer, encryptionPrivateKey, getAccountContract(PrivateKey.random()), accountAddress, - ).getWallet(); + ); const childWithInvalidWallet = await ChildContract.at(child.address, invalidWallet); await expect(childWithInvalidWallet.methods.value(42).simulate()).rejects.toThrowError( /Cannot satisfy constraint.*/, @@ -69,15 +85,50 @@ function itShouldBehaveLikeAnAccountContract(getAccountContract: (encryptionKey: } describe('e2e_account_contracts', () => { + const base = async ( + rpc: AztecRPC, + encryptionPrivateKey: PrivateKey, + accountContract: AccountContract, + address?: CompleteAddress, + ) => { + const account = new Account(rpc, encryptionPrivateKey, accountContract, address); + const wallet = !address ? await account.deploy().then(tx => tx.getWallet()) : await account.getWallet(); + return { account, wallet }; + }; + describe('schnorr single-key account', () => { - itShouldBehaveLikeAnAccountContract((encryptionKey: PrivateKey) => new SingleKeyAccountContract(encryptionKey)); + itShouldBehaveLikeAnAccountContract( + (encryptionKey: PrivateKey) => new SingleKeyAccountContract(encryptionKey), + base, + ); }); describe('schnorr multi-key account', () => { - itShouldBehaveLikeAnAccountContract(() => new SchnorrAccountContract(PrivateKey.random())); + itShouldBehaveLikeAnAccountContract(() => new SchnorrAccountContract(PrivateKey.random()), base); }); describe('ecdsa stored-key account', () => { - itShouldBehaveLikeAnAccountContract(() => new EcdsaAccountContract(PrivateKey.random())); + itShouldBehaveLikeAnAccountContract(() => new EcdsaAccountContract(PrivateKey.random()), base); + }); + + describe('eip single-key account', () => { + itShouldBehaveLikeAnAccountContract( + (encryptionKey: PrivateKey) => new AuthWitnessAccountContract(encryptionKey), + async ( + rpc: AztecRPC, + encryptionPrivateKey: PrivateKey, + accountContract: AccountContract, + address?: CompleteAddress, + ) => { + const account = new Account(rpc, encryptionPrivateKey, accountContract, address); + if (!address) { + const tx = await account.deploy(); + await tx.wait(); + } + const entryPoint = (await account.getEntrypoint()) as unknown as AuthWitnessAccountEntrypoint; + const wallet = new AuthWitnessEntrypointWallet(rpc, entryPoint); + return { account, wallet }; + }, + ); }); }); diff --git a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts index 27a303e5d9f..499b7497ac3 100644 --- a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts @@ -1,8 +1,17 @@ import { AztecNodeService } from '@aztec/aztec-node'; import { AztecRPCServer } from '@aztec/aztec-rpc'; -import { AztecAddress, CheatCodes, Fr, Wallet, computeMessageSecretHash } from '@aztec/aztec.js'; -import { CircuitsWasm, CompleteAddress } from '@aztec/circuits.js'; -import { pedersenPlookupCommitInputs } from '@aztec/circuits.js/barretenberg'; +import { + Account, + AuthWitnessAccountContract, + AuthWitnessAccountEntrypoint, + AuthWitnessEntrypointWallet, + AztecAddress, + CheatCodes, + Fr, + computeMessageSecretHash, +} from '@aztec/aztec.js'; +import { CircuitsWasm, CompleteAddress, FunctionSelector, GeneratorIndex, PrivateKey } from '@aztec/circuits.js'; +import { pedersenPlookupCommitInputs, pedersenPlookupCompressWithHashIndex } from '@aztec/circuits.js/barretenberg'; import { DebugLogger } from '@aztec/foundation/log'; import { LendingContract, NativeTokenContract, PriceFeedContract } from '@aztec/noir-contracts/types'; import { AztecRPC, TxStatus } from '@aztec/types'; @@ -12,7 +21,7 @@ import { setup } from './fixtures/utils.js'; describe('e2e_lending_contract', () => { let aztecNode: AztecNodeService | undefined; let aztecRpcServer: AztecRPC; - let wallet: Wallet; + let wallet: AuthWitnessEntrypointWallet; let accounts: CompleteAddress[]; let logger: DebugLogger; @@ -71,7 +80,17 @@ describe('e2e_lending_contract', () => { }; beforeEach(async () => { - ({ aztecNode, aztecRpcServer, wallet, accounts, logger, cheatCodes: cc } = await setup()); + ({ aztecNode, aztecRpcServer, logger, cheatCodes: cc } = await setup(0)); + + const privateKey = PrivateKey.random(); + const account = new Account(aztecRpcServer, privateKey, new AuthWitnessAccountContract(privateKey)); + const entryPoint = (await account.getEntrypoint()) as unknown as AuthWitnessAccountEntrypoint; + + const deployTx = await account.deploy(); + await deployTx.wait({ interval: 0.1 }); + + wallet = new AuthWitnessEntrypointWallet(aztecRpcServer, entryPoint); + accounts = await wallet.getAccounts(); }, 100_000); afterEach(async () => { @@ -81,12 +100,20 @@ describe('e2e_lending_contract', () => { } }); + const hashPayload = async (payload: Fr[]) => { + return pedersenPlookupCompressWithHashIndex( + await CircuitsWasm.get(), + payload.map(fr => fr.toBuffer()), + GeneratorIndex.SIGNATURE_PAYLOAD, + ); + }; + // Fetch a storage snapshot from the contract that we can use to compare between transitions. const getStorageSnapshot = async ( lendingContract: LendingContract, collateralAsset: NativeTokenContract, stableCoin: NativeTokenContract, - account: Account, + account: LendingAccount, ) => { logger('Fetching storage snapshot 📸 '); const accountKey = await account.key(); @@ -115,7 +142,7 @@ describe('e2e_lending_contract', () => { // Convenience struct to hold an account's address and secret that can easily be passed around. // Contains utilities to compute the "key" for private holdings in the public state. - class Account { + class LendingAccount { public readonly address: AztecAddress; public readonly secret: Fr; @@ -173,7 +200,7 @@ describe('e2e_lending_contract', () => { private key: Fr = Fr.ZERO; - constructor(private cc: CheatCodes, private account: Account, private rate: bigint) {} + constructor(private cc: CheatCodes, private account: LendingAccount, private rate: bigint) {} async prepare() { this.key = await this.account.key(); @@ -258,12 +285,13 @@ describe('e2e_lending_contract', () => { } it('Full lending run-through', async () => { - const recipientIdx = 0; + // Gotta use the actual auth witness account here and not the standard wallet. + const recipientFull = accounts[0]; + const recipient = recipientFull.address; - const recipient = accounts[recipientIdx].address; const { lendingContract, priceFeedContract, collateralAsset, stableCoin } = await deployContracts(recipient); - const account = new Account(recipient, new Fr(42)); + const lendingAccount = new LendingAccount(recipient, new Fr(42)); const storageSnapshots: { [key: string]: { [key: string]: Fr } } = {}; @@ -277,7 +305,7 @@ describe('e2e_lending_contract', () => { { // Minting some collateral in public so we got it at hand. - const tx = collateralAsset.methods.owner_mint_pub(account.address, 10000n).send({ origin: recipient }); + const tx = collateralAsset.methods.owner_mint_pub(lendingAccount.address, 10000n).send({ origin: recipient }); const receipt = await tx.wait(); expect(receipt.status).toBe(TxStatus.MINED); @@ -304,10 +332,10 @@ describe('e2e_lending_contract', () => { // Also specified in `noir-contracts/src/contracts/lending_contract/src/main.nr` const rate = 1268391679n; - const lendingSim = new LendingSimulator(cc, account, rate); + const lendingSim = new LendingSimulator(cc, lendingAccount, rate); await lendingSim.prepare(); // To handle initial mint (we use these funds to refund privately without shielding first). - lendingSim.mintStable(await account.key(), 10000n); + lendingSim.mintStable(await lendingAccount.key(), 10000n); { // Initialize the contract values, setting the interest accumulator to 1e9 and the last updated timestamp to now. @@ -317,15 +345,28 @@ describe('e2e_lending_contract', () => { .send({ origin: recipient }); const receipt = await tx.wait(); expect(receipt.status).toBe(TxStatus.MINED); - storageSnapshots['initial'] = await getStorageSnapshot(lendingContract, collateralAsset, stableCoin, account); + storageSnapshots['initial'] = await getStorageSnapshot( + lendingContract, + collateralAsset, + stableCoin, + lendingAccount, + ); lendingSim.check(storageSnapshots['initial']); } { const depositAmount = 420n; + + const messageHash = await hashPayload([ + FunctionSelector.fromSignature('unshieldTokens(Field,Field,Field)').toField(), + recipientFull.address.toField(), + lendingContract.address.toField(), + new Fr(depositAmount), + ]); + await wallet.signAndAddAuthWitness(messageHash); await lendingSim.progressTime(10); - lendingSim.deposit(await account.key(), depositAmount); + lendingSim.deposit(await lendingAccount.key(), depositAmount); // Make a private deposit of funds into own account. // This should: @@ -334,7 +375,7 @@ describe('e2e_lending_contract', () => { // - increase the private collateral. logger('Depositing 🥸 : 💰 -> 🏦'); const tx = lendingContract.methods - .deposit_private(account.secret, account.address, 0n, depositAmount, collateralAsset.address) + .deposit_private(lendingAccount.secret, lendingAccount.address, 0n, depositAmount, collateralAsset.address) .send({ origin: recipient }); const receipt = await tx.wait(); expect(receipt.status).toBe(TxStatus.MINED); @@ -342,14 +383,22 @@ describe('e2e_lending_contract', () => { lendingContract, collateralAsset, stableCoin, - account, + lendingAccount, ); lendingSim.check(storageSnapshots['private_deposit']); } { - const depositAmount = 420n; + const depositAmount = 421n; + const messageHash = await hashPayload([ + FunctionSelector.fromSignature('unshieldTokens(Field,Field,Field)').toField(), + recipientFull.address.toField(), + lendingContract.address.toField(), + new Fr(depositAmount), + ]); + await wallet.signAndAddAuthWitness(messageHash); + await lendingSim.progressTime(10); lendingSim.deposit(recipient.toField(), depositAmount); // Make a private deposit of funds into another account, in this case, a public account. @@ -359,7 +408,7 @@ describe('e2e_lending_contract', () => { // - increase the public collateral. logger('Depositing 🥸 on behalf of recipient: 💰 -> 🏦'); const tx = lendingContract.methods - .deposit_private(0n, account.address, recipient.toField(), depositAmount, collateralAsset.address) + .deposit_private(0n, lendingAccount.address, recipient.toField(), depositAmount, collateralAsset.address) .send({ origin: recipient }); const receipt = await tx.wait(); expect(receipt.status).toBe(TxStatus.MINED); @@ -367,7 +416,7 @@ describe('e2e_lending_contract', () => { lendingContract, collateralAsset, stableCoin, - account, + lendingAccount, ); lendingSim.check(storageSnapshots['private_deposit_on_behalf']); @@ -386,7 +435,7 @@ describe('e2e_lending_contract', () => { logger('Depositing: 💰 -> 🏦'); const tx = lendingContract.methods - .deposit_public(account.address, depositAmount, collateralAsset.address) + .deposit_public(lendingAccount.address, depositAmount, collateralAsset.address) .send({ origin: recipient }); const receipt = await tx.wait(); expect(receipt.status).toBe(TxStatus.MINED); @@ -394,7 +443,7 @@ describe('e2e_lending_contract', () => { lendingContract, collateralAsset, stableCoin, - account, + lendingAccount, ); lendingSim.check(storageSnapshots['public_deposit']); } @@ -402,7 +451,7 @@ describe('e2e_lending_contract', () => { { const borrowAmount = 69n; await lendingSim.progressTime(10); - lendingSim.borrow(await account.key(), account.address.toField(), borrowAmount); + lendingSim.borrow(await lendingAccount.key(), lendingAccount.address.toField(), borrowAmount); // Make a private borrow using the private account // This should: @@ -412,7 +461,7 @@ describe('e2e_lending_contract', () => { logger('Borrow 🥸 : 🏦 -> 🍌'); const tx = lendingContract.methods - .borrow_private(account.secret, account.address, borrowAmount) + .borrow_private(lendingAccount.secret, lendingAccount.address, borrowAmount) .send({ origin: recipient }); const receipt = await tx.wait(); expect(receipt.status).toBe(TxStatus.MINED); @@ -420,7 +469,7 @@ describe('e2e_lending_contract', () => { lendingContract, collateralAsset, stableCoin, - account, + lendingAccount, ); lendingSim.check(storageSnapshots['private_borrow']); @@ -429,7 +478,7 @@ describe('e2e_lending_contract', () => { { const borrowAmount = 69n; await lendingSim.progressTime(10); - lendingSim.borrow(recipient.toField(), account.address.toField(), borrowAmount); + lendingSim.borrow(recipient.toField(), lendingAccount.address.toField(), borrowAmount); // Make a public borrow using the private account // This should: @@ -438,14 +487,16 @@ describe('e2e_lending_contract', () => { // - increase the public debt. logger('Borrow: 🏦 -> 🍌'); - const tx = lendingContract.methods.borrow_public(account.address, borrowAmount).send({ origin: recipient }); + const tx = lendingContract.methods + .borrow_public(lendingAccount.address, borrowAmount) + .send({ origin: recipient }); const receipt = await tx.wait(); expect(receipt.status).toBe(TxStatus.MINED); storageSnapshots['public_borrow'] = await getStorageSnapshot( lendingContract, collateralAsset, stableCoin, - account, + lendingAccount, ); lendingSim.check(storageSnapshots['public_borrow']); @@ -453,8 +504,16 @@ describe('e2e_lending_contract', () => { { const repayAmount = 20n; + const messageHash = await hashPayload([ + FunctionSelector.fromSignature('unshieldTokens(Field,Field,Field)').toField(), + recipientFull.address.toField(), + lendingContract.address.toField(), + new Fr(repayAmount), + ]); + await wallet.signAndAddAuthWitness(messageHash); + await lendingSim.progressTime(10); - lendingSim.repay(await account.key(), await account.key(), repayAmount); + lendingSim.repay(await lendingAccount.key(), await lendingAccount.key(), repayAmount); // Make a private repay of the debt in the private account // This should: @@ -464,7 +523,7 @@ describe('e2e_lending_contract', () => { logger('Repay 🥸 : 🍌 -> 🏦'); const tx = lendingContract.methods - .repay_private(account.secret, account.address, 0n, repayAmount, stableCoin.address) + .repay_private(lendingAccount.secret, lendingAccount.address, 0n, repayAmount, stableCoin.address) .send({ origin: recipient }); const receipt = await tx.wait(); expect(receipt.status).toBe(TxStatus.MINED); @@ -472,16 +531,24 @@ describe('e2e_lending_contract', () => { lendingContract, collateralAsset, stableCoin, - account, + lendingAccount, ); lendingSim.check(storageSnapshots['private_repay']); } { - const repayAmount = 20n; + const repayAmount = 21n; + const messageHash = await hashPayload([ + FunctionSelector.fromSignature('unshieldTokens(Field,Field,Field)').toField(), + recipientFull.address.toField(), + lendingContract.address.toField(), + new Fr(repayAmount), + ]); + await wallet.signAndAddAuthWitness(messageHash); + await lendingSim.progressTime(10); - lendingSim.repay(await account.key(), account.address.toField(), repayAmount); + lendingSim.repay(await lendingAccount.key(), lendingAccount.address.toField(), repayAmount); // Make a private repay of the debt in the public account // This should: @@ -491,7 +558,7 @@ describe('e2e_lending_contract', () => { logger('Repay 🥸 on behalf of public: 🍌 -> 🏦'); const tx = lendingContract.methods - .repay_private(0n, account.address, recipient.toField(), repayAmount, stableCoin.address) + .repay_private(0n, lendingAccount.address, recipient.toField(), repayAmount, stableCoin.address) .send({ origin: recipient }); const receipt = await tx.wait(); expect(receipt.status).toBe(TxStatus.MINED); @@ -499,7 +566,7 @@ describe('e2e_lending_contract', () => { lendingContract, collateralAsset, stableCoin, - account, + lendingAccount, ); lendingSim.check(storageSnapshots['private_repay_on_behalf']); @@ -508,7 +575,7 @@ describe('e2e_lending_contract', () => { { const repayAmount = 20n; await lendingSim.progressTime(10); - lendingSim.repay(account.address.toField(), account.address.toField(), repayAmount); + lendingSim.repay(lendingAccount.address.toField(), lendingAccount.address.toField(), repayAmount); // Make a public repay of the debt in the public account // This should: @@ -526,7 +593,7 @@ describe('e2e_lending_contract', () => { lendingContract, collateralAsset, stableCoin, - account, + lendingAccount, ); lendingSim.check(storageSnapshots['public_repay']); @@ -562,7 +629,7 @@ describe('e2e_lending_contract', () => { lendingContract, collateralAsset, stableCoin, - account, + lendingAccount, ); lendingSim.check(storageSnapshots['public_withdraw']); @@ -571,7 +638,7 @@ describe('e2e_lending_contract', () => { { const withdrawAmount = 42n; await lendingSim.progressTime(10); - lendingSim.withdraw(await account.key(), withdrawAmount); + lendingSim.withdraw(await lendingAccount.key(), withdrawAmount); // Withdraw funds from the private account // This should: @@ -581,7 +648,7 @@ describe('e2e_lending_contract', () => { logger('Withdraw 🥸 : 🏦 -> 💰'); const tx = lendingContract.methods - .withdraw_private(account.secret, account.address, withdrawAmount) + .withdraw_private(lendingAccount.secret, lendingAccount.address, withdrawAmount) .send({ origin: recipient }); const receipt = await tx.wait(); expect(receipt.status).toBe(TxStatus.MINED); @@ -589,7 +656,7 @@ describe('e2e_lending_contract', () => { lendingContract, collateralAsset, stableCoin, - account, + lendingAccount, ); lendingSim.check(storageSnapshots['private_withdraw']); @@ -612,7 +679,7 @@ describe('e2e_lending_contract', () => { lendingContract, collateralAsset, stableCoin, - account, + lendingAccount, ); expect(storageSnapshots['private_withdraw']).toEqual(storageSnapshots['attempted_internal_deposit']); } diff --git a/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr index da46929f836..97f064e5ec8 100644 --- a/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr @@ -13,6 +13,8 @@ contract NativeToken { utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNoteMethods}, }; + use dep::std; + use dep::aztec::constants_gen::GENERATOR_INDEX__SIGNATURE_PAYLOAD; use dep::non_native::{ hash::{get_mint_content_hash, get_withdraw_content_hash}, @@ -299,7 +301,27 @@ contract NativeToken { ) { let storage = Storage::init(Option::some(&mut context), Option::none()); - // Remove user balance + // If `from != sender` then we use the is_valid function to check that the message is approved. + if (from != context.msg_sender()) { + // Compute the message hash, should follow eip-712 more here. + // @todo @lherskind, probably need a separate generator index and address of the + // @todo @lherskind Currently this can be used multiple times since it is not nullified. + // We can do a simple nullifier to handle that in here. Spends only 32 bytes onchain. + // @todo @LHerskind Is to be solved as part of https://github.com/AztecProtocol/aztec-packages/issues/1743 + let message_field: Field = std::hash::pedersen_with_separator([ + compute_selector("unshieldTokens(Field,Field,Field)"), + from, + to, + amount + ], + GENERATOR_INDEX__SIGNATURE_PAYLOAD + )[0]; + let is_valid_selector = compute_selector("is_valid(Field)"); + let _callStackItem0 = context.call_private_function(from, is_valid_selector, [message_field]); + assert(_callStackItem0[0] == is_valid_selector); + } + + // Reduce user balance let sender_balance = storage.balances.at(from); decrement(sender_balance, amount, from); diff --git a/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/Nargo.toml b/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/Nargo.toml new file mode 100644 index 00000000000..922b93267d0 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "schnorr_auth_witness_account_contract" +authors = [""] +compiler_version = "0.1" +type = "contract" + +[dependencies] +aztec = { path = "../../../../noir-libs/noir-aztec" } diff --git a/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/auth_oracle.nr b/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/auth_oracle.nr new file mode 100644 index 00000000000..bff7d071b23 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/auth_oracle.nr @@ -0,0 +1,28 @@ +use dep::aztec::types::point::Point; + +#[oracle(getAuthWitness)] +fn get_auth_witness_oracle(_message_hash: Field) -> [Field; 67] {} + +struct AuthWitness { + owner: Point, + signature: [u8; 64], + partial_address: Field, +} + +impl AuthWitness { + fn deserialize(values: [Field; 67]) -> Self { + let mut signature = [0; 64]; + for i in 0..64 { + signature[i] = values[i + 2] as u8; + } + Self { + owner: Point::new(values[0], values[1]), + signature, + partial_address: values[66], + } + } +} + +unconstrained fn get_auth_witness(message_hash: Field) -> AuthWitness { + AuthWitness::deserialize(get_auth_witness_oracle(message_hash)) +} \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/main.nr new file mode 100644 index 00000000000..749c1b04276 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/main.nr @@ -0,0 +1,41 @@ +mod util; +mod auth_oracle; + +contract SchnorrAuthWitnessAccount { + use dep::std::hash::pedersen_with_separator; + use dep::aztec::entrypoint::EntrypointPayload; + use dep::aztec::constants_gen::GENERATOR_INDEX__SIGNATURE_PAYLOAD; + use crate::util::recover_address; + use crate::auth_oracle::get_auth_witness; + + #[aztec(private)] + fn constructor() {} + + #[aztec(private)] + fn entrypoint( + payload: pub EntrypointPayload, + ) { + let message_hash: Field = pedersen_with_separator( + payload.serialize(), + GENERATOR_INDEX__SIGNATURE_PAYLOAD + )[0]; + _inner_is_valid(message_hash, context.this_address()); + payload.execute_calls(&mut context); + } + + #[aztec(private)] + fn is_valid( + message_hash: Field + ) -> Field { + _inner_is_valid(message_hash, context.this_address()); + 0xe86ab4ff + } + + fn _inner_is_valid( + message_hash: Field, + address: Field, + ) { + let witness = get_auth_witness(message_hash); + assert(recover_address(message_hash, witness) == address); + } +} \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/util.nr b/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/util.nr new file mode 100644 index 00000000000..7445008aa2b --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/util.nr @@ -0,0 +1,22 @@ +mod auth_oracle; + +use dep::std::{schnorr::verify_signature, hash::pedersen_with_separator}; +use dep::aztec::constants_gen::GENERATOR_INDEX__CONTRACT_ADDRESS; +use crate::auth_oracle::{AuthWitness}; + +fn recover_address( + message_hash: Field, + witness: AuthWitness, +) -> Field { + let message_bytes_slice = message_hash.to_be_bytes(32); + let mut message_bytes: [u8; 32] = [0; 32]; + for i in 0..32 { + message_bytes[i] = message_bytes_slice[i]; + } + + let verification = verify_signature(witness.owner.x, witness.owner.y, witness.signature, message_bytes); + assert(verification == true); + + let reproduced_address = pedersen_with_separator([witness.owner.x, witness.owner.y, witness.partial_address], GENERATOR_INDEX__CONTRACT_ADDRESS)[0]; + reproduced_address +} \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/scripts/copy_output.ts b/yarn-project/noir-contracts/src/scripts/copy_output.ts index 605ffd04ec2..69846b59908 100644 --- a/yarn-project/noir-contracts/src/scripts/copy_output.ts +++ b/yarn-project/noir-contracts/src/scripts/copy_output.ts @@ -20,6 +20,7 @@ const PROJECT_CONTRACTS = [ { name: 'SchnorrSingleKeyAccount', target: '../aztec.js/src/abis/', exclude: [] }, { name: 'SchnorrAccount', target: '../aztec.js/src/abis/', exclude: [] }, { name: 'EcdsaAccount', target: '../aztec.js/src/abis/', exclude: [] }, + { name: 'SchnorrAuthWitnessAccount', target: '../aztec.js/src/abis/', exclude: [] }, ]; const INTERFACE_CONTRACTS = ['private_token', 'private_token_airdrop', 'test']; diff --git a/yarn-project/types/src/interfaces/aztec_rpc.ts b/yarn-project/types/src/interfaces/aztec_rpc.ts index 5eded121bfe..b67ba94f11c 100644 --- a/yarn-project/types/src/interfaces/aztec_rpc.ts +++ b/yarn-project/types/src/interfaces/aztec_rpc.ts @@ -68,6 +68,13 @@ export type SyncStatus = { * as well as storage and view functions for smart contracts. */ export interface AztecRPC { + /** + * Insert a witness for a given message hash. + * @param messageHash - The message hash to insert witness at + * @param witness - The witness to insert + */ + addAuthWitness(messageHash: Fr, witness: Fr[]): Promise; + /** * Registers an account in the Aztec RPC server. *