From 195da66ce2e230037becdcb2e1334a43d0f26217 Mon Sep 17 00:00:00 2001 From: Tornado Contrib Date: Fri, 26 Apr 2024 13:16:00 +0000 Subject: [PATCH] Added Encrypted Note Service --- .gitignore | 3 +- package.json | 3 + src/program.ts | 214 +++++++++++++++++++++++++++++++-- src/services/encryptedNotes.ts | 160 ++++++++++++++++++++++++ src/services/graphql/index.ts | 2 +- src/services/index.ts | 1 + src/services/merkleTree.ts | 6 +- src/services/networkConfig.ts | 15 +++ src/services/parser.ts | 15 +++ src/services/utils.ts | 39 ++++-- yarn.lock | 165 ++++++++++++++++++++++++- 11 files changed, 596 insertions(+), 27 deletions(-) create mode 100644 src/services/encryptedNotes.ts diff --git a/.gitignore b/.gitignore index e785e1c..3f2b777 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules /events /trees backup-tornado-* -backup-tornadoInvoice-* \ No newline at end of file +backup-tornadoInvoice-* +backup-note-account-* \ No newline at end of file diff --git a/package.json b/package.json index 567b8e1..c45ea46 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,8 @@ "compliance": "ts-node src/cli.ts compliance", "syncEvents": "ts-node src/cli.ts syncEvents", "relayers": "ts-node src/cli.ts relayers", + "createNoteAccount": "ts-node src/cli.ts createNoteAccount", + "decryptNotes": "ts-node src/cli.ts decryptNotes", "send": "ts-node src/cli.ts send", "balance": "ts-node src/cli.ts balance", "sign": "ts-node src/cli.ts sign", @@ -50,6 +52,7 @@ "yarn.lock" ], "dependencies": { + "@metamask/eth-sig-util": "^7.0.1", "@tornado/contracts": "1.0.0", "@tornado/fixed-merkle-tree": "0.7.3", "@tornado/snarkjs": "0.1.20", diff --git a/src/program.ts b/src/program.ts index 0c7812c..f7518f0 100644 --- a/src/program.ts +++ b/src/program.ts @@ -73,10 +73,13 @@ import { fetchData, fetchDataOptions, networkConfig, + getInstanceByAddress, subdomains, Config, enabledChains, substring, + NoteAccount, + parseRecoveryKey, } from './services'; const DEFAULT_GAS_LIMIT = 600_000; @@ -109,6 +112,7 @@ export type commonProgramOptions = { graph?: string; ethGraph?: string; disableGraph?: boolean; + accountKey?: string; relayer?: string; walletWithdrawal?: boolean; torPort?: number; @@ -163,6 +167,7 @@ export async function getProgramOptions(options: commonProgramOptions): Promise< graph: options.graph || (process.env.GRAPH_URL ? parseUrl(process.env.GRAPH_URL) : undefined), ethGraph: options.ethGraph || (process.env.ETHGRAPH_URL ? parseUrl(process.env.ETHGRAPH_URL) : undefined), disableGraph: Boolean(options.disableGraph) || (process.env.DISABLE_GRAPH === 'true' ? true : undefined), + accountKey: options.accountKey || (process.env.ACCOUNT_KEY ? parseRecoveryKey(process.env.ACCOUNT_KEY) : undefined), relayer: options.relayer || (process.env.RELAYER ? parseRelayer(process.env.RELAYER) : undefined), walletWithdrawal: Boolean(options.walletWithdrawal) || (process.env.WALLET_WITHDRAWAL === 'true' ? true : undefined), @@ -851,9 +856,9 @@ export function tornadoProgram() { // If we have MERKLE_WORKER_PATH run worker at background otherwise resolve it here const depositTreeInitiator = await (async () => { if (MERKLE_WORKER_PATH) { - return () => merkleTreeService.verifyTree({ events: depositEvents }) as Promise; + return () => merkleTreeService.verifyTree(depositEvents) as Promise; } - return (await merkleTreeService.verifyTree({ events: depositEvents })) as MerkleTree; + return (await merkleTreeService.verifyTree(depositEvents)) as MerkleTree; })(); let depositTreePromise: Promise | MerkleTree; @@ -1178,9 +1183,9 @@ export function tornadoProgram() { // If we have MERKLE_WORKER_PATH run worker at background otherwise resolve it here const depositTreePromise = await (async () => { if (MERKLE_WORKER_PATH) { - return () => merkleTreeService.verifyTree({ events: depositEvents }) as Promise; + return () => merkleTreeService.verifyTree(depositEvents) as Promise; } - return (await merkleTreeService.verifyTree({ events: depositEvents })) as MerkleTree; + return (await merkleTreeService.verifyTree(depositEvents)) as MerkleTree; })(); const [withdrawalEvents] = await Promise.all([ @@ -1381,12 +1386,9 @@ export function tornadoProgram() { // If we have MERKLE_WORKER_PATH run worker at background otherwise resolve it here const depositTreePromise = await (async () => { if (MERKLE_WORKER_PATH) { - return () => - merkleTreeService.verifyTree({ events: depositEvents as DepositsEvents[] }) as Promise; + return () => merkleTreeService.verifyTree(depositEvents as DepositsEvents[]) as Promise; } - return (await merkleTreeService.verifyTree({ - events: depositEvents as DepositsEvents[], - })) as MerkleTree; + return (await merkleTreeService.verifyTree(depositEvents as DepositsEvents[])) as MerkleTree; })(); await Promise.all([ @@ -1463,6 +1465,199 @@ export function tornadoProgram() { process.exit(0); }); + program + .command('createNoteAccount') + .description( + 'Creates and save on-chain account that would store encrypted notes. \n\n' + + 'Would first lookup on on-chain records to see if the notes are stored. \n\n' + + 'Requires a valid signable wallet (mnemonic or a private key) to work (Since they would encrypt or encrypted)', + ) + .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) + .action(async (netId: string | number, cmdOptions: commonProgramOptions) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc } = options; + + const config = networkConfig[`netId${netId}`]; + + const { + echoContract, + tornadoSubgraph, + constants: { ['NOTE_ACCOUNT_BLOCK']: deployedBlock }, + } = config; + + const provider = getProgramProvider(netId, rpc, config, { + ...fetchDataOptions, + }); + + const signer = getProgramSigner({ + options, + provider, + }); + + const graphApi = getProgramGraphAPI(options, config); + + if (!signer || signer instanceof VoidSigner) { + throw new Error( + 'No wallet found, make your you have supplied a valid mnemonic or private key before using this command', + ); + } + + /** + * Find for any existing note accounts + */ + const walletPublicKey = NoteAccount.getWalletPublicKey(signer); + + const Echoer = Echoer__factory.connect(echoContract, provider); + + const newAccount = new NoteAccount({ + netId, + Echoer, + }); + + const echoService = new NodeEchoService({ + netId, + provider, + graphApi, + subgraphName: tornadoSubgraph, + Echoer, + deployedBlock, + fetchDataOptions, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); + + console.log('Getting historic note accounts would take a while\n'); + + const echoEvents = (await echoService.updateEvents()).events; + + const userEvents = echoEvents.filter(({ address }) => address === signer.address); + + const existingAccounts = userEvents.map((e) => newAccount.decryptAccountWithWallet(signer, e)); + + const accountsTable = new Table(); + + if (existingAccounts.length) { + accountsTable.push( + [{ colSpan: 2, content: `Note Accounts (${netId})`, hAlign: 'center' }], + [{ colSpan: 2, content: `Backed up by: ${signer.address}`, hAlign: 'center' }], + ['blockNumber', 'noteAccount'].map((content) => ({ content: colors.red.bold(content) })), + ...existingAccounts.map(({ blockNumber, recoveryKey }) => { + return [blockNumber, recoveryKey]; + }), + ); + + console.log(accountsTable.toString() + '\n'); + } else { + accountsTable.push( + [{ colSpan: 1, content: `New Note Account (${netId})`, hAlign: 'center' }], + ['noteAccount'].map((content) => ({ content: colors.red.bold(content) })), + [newAccount.recoveryKey], + [{ colSpan: 1, content: `Would be backed up by: ${signer.address}`, hAlign: 'center' }], + ); + + const fileName = `backup-note-account-key-0x${newAccount.recoveryKey.slice(0, 8)}.txt`; + + console.log('\n' + accountsTable.toString() + '\n'); + + console.log(`Writing backup to ${fileName}\n`); + + await writeFile(fileName, newAccount.recoveryKey + '\n'); + + console.log('Backup encrypted account on-chain to use on UI?\n'); + + await promptConfirmation(options.nonInteractive); + + const { data } = newAccount.getEncryptedAccount(walletPublicKey); + + console.log('Sending encrypted note account backup transaction through wallet\n'); + + await programSendTransaction({ + signer: signer as TornadoVoidSigner | TornadoWallet, + options, + populatedTransaction: await Echoer.echo.populateTransaction(data), + }); + } + + process.exit(0); + }); + + program + .command('decryptNotes') + .description('Fetch notes from deposit events and decrypt them. \n\n' + 'Requires a valid account key to work') + .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) + .argument( + '[accountKey]', + 'Account key generated from UI or the createNoteAccount to store encrypted notes on-chain', + parseRecoveryKey, + ) + .action(async (netId: string | number, accountKey: string | undefined, cmdOptions: commonProgramOptions) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc } = options; + if (!accountKey) { + accountKey = options.accountKey; + } + + const config = networkConfig[`netId${netId}`]; + + const { + routerContract, + echoContract, + tornadoSubgraph, + constants: { ENCRYPTED_NOTES_BLOCK }, + } = config; + + const provider = getProgramProvider(netId, rpc, config, { + ...fetchDataOptions, + }); + + const graphApi = getProgramGraphAPI(options, config); + + if (!accountKey) { + throw new Error( + 'No account key find! Please supply correct account key from either UI or find one with createNoteAccount command', + ); + } + + const Echoer = Echoer__factory.connect(echoContract, provider); + + const noteAccount = new NoteAccount({ + netId, + recoveryKey: accountKey, + Echoer, + }); + + const encryptedNotesService = new NodeEncryptedNotesService({ + netId, + provider, + graphApi, + subgraphName: tornadoSubgraph, + Router: TornadoRouter__factory.connect(routerContract, provider), + deployedBlock: ENCRYPTED_NOTES_BLOCK, + fetchDataOptions, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); + + const encryptedNoteEvents = (await encryptedNotesService.updateEvents()).events; + + const accountsTable = new Table(); + + accountsTable.push( + [{ colSpan: 2, content: `Note Accounts (${netId})`, hAlign: 'center' }], + [{ colSpan: 2, content: `Account key: ${accountKey}`, hAlign: 'center' }], + ['blockNumber', 'note'].map((content) => ({ content: colors.red.bold(content) })), + ...noteAccount.decryptNotes(encryptedNoteEvents).map(({ blockNumber, address, noteHex }) => { + const { amount, currency } = getInstanceByAddress({ netId, address }) as { amount: string; currency: string }; + + return [blockNumber, `tornado-${currency}-${amount}-${netId}-${noteHex}`]; + }), + ); + + console.log('\n' + accountsTable.toString() + '\n'); + + process.exit(0); + }); + program .command('send') .description('Send ETH or ERC20 token to address.\n\n') @@ -1736,6 +1931,7 @@ export function tornadoProgram() { cmd.option('-g, --graph ', 'The Subgraph API that CLI should interact with', parseUrl); cmd.option('-G, --eth-graph ', 'The Ethereum Mainnet Subgraph API that CLI should interact with', parseUrl); cmd.option('-d, --disable-graph', 'Disable Graph API - Does not enable Subgraph API and use only local RPC as an event source'); + cmd.option('-a, --account-key ', 'Account key generated from UI or the createNoteAccount to store encrypted notes on-chain', parseRecoveryKey); cmd.option('-R, --relayer ', 'Withdraw via relayer (Should be either .eth name or URL)', parseRelayer); cmd.option('-w, --wallet-withdrawal', 'Withdrawal via wallet (Should not be linked with deposits)'); cmd.option('-T, --tor-port ', 'Optional tor port', parseNumber); diff --git a/src/services/encryptedNotes.ts b/src/services/encryptedNotes.ts new file mode 100644 index 0000000..ebae695 --- /dev/null +++ b/src/services/encryptedNotes.ts @@ -0,0 +1,160 @@ +import { getEncryptionPublicKey, encrypt, decrypt, EthEncryptedData } from '@metamask/eth-sig-util'; +import { Echoer } from '@tornado/contracts'; +import { Wallet, computeAddress } from 'ethers'; +import { crypto, base64ToBytes, bytesToBase64, bytesToHex, hexToBytes, toFixedHex, concatBytes } from './utils'; +import { EchoEvents, EncryptedNotesEvents } from './events'; + +export interface DecryptedNotes { + blockNumber: number; + address: string; + noteHex: string; +} + +export function packEncryptedMessage({ nonce, ephemPublicKey, ciphertext }: EthEncryptedData) { + const nonceBuf = toFixedHex(bytesToHex(base64ToBytes(nonce)), 24); + const ephemPublicKeyBuf = toFixedHex(bytesToHex(base64ToBytes(ephemPublicKey)), 32); + const ciphertextBuf = bytesToHex(base64ToBytes(ciphertext)); + + const messageBuff = concatBytes(hexToBytes(nonceBuf), hexToBytes(ephemPublicKeyBuf), hexToBytes(ciphertextBuf)); + + return bytesToHex(messageBuff); +} + +export function unpackEncryptedMessage(encryptedMessage: string) { + const messageBuff = hexToBytes(encryptedMessage); + const nonceBuf = bytesToBase64(messageBuff.slice(0, 24)); + const ephemPublicKeyBuf = bytesToBase64(messageBuff.slice(24, 56)); + const ciphertextBuf = bytesToBase64(messageBuff.slice(56)); + + return { + messageBuff: bytesToHex(messageBuff), + version: 'x25519-xsalsa20-poly1305', + nonce: nonceBuf, + ephemPublicKey: ephemPublicKeyBuf, + ciphertext: ciphertextBuf, + } as EthEncryptedData & { + messageBuff: string; + }; +} + +export interface NoteAccountConstructor { + netId: string | number; + blockNumber?: number; + // hex + recoveryKey?: string; + Echoer: Echoer; +} + +export class NoteAccount { + netId: number; + blockNumber?: number; + // Dedicated 32 bytes private key only used for note encryption, backed up to an Echoer and local for future derivation + // Note that unlike the private key it shouldn't have the 0x prefix + recoveryKey: string; + // Address derived from recoveryKey, only used for frontend UI + recoveryAddress: string; + // Note encryption public key derived from recoveryKey + recoveryPublicKey: string; + Echoer: Echoer; + + constructor({ netId, blockNumber, recoveryKey, Echoer }: NoteAccountConstructor) { + if (!recoveryKey) { + recoveryKey = bytesToHex(crypto.getRandomValues(new Uint8Array(32))).slice(2); + } + + this.netId = Math.floor(Number(netId)); + this.blockNumber = blockNumber; + this.recoveryKey = recoveryKey; + this.recoveryAddress = computeAddress('0x' + recoveryKey); + this.recoveryPublicKey = getEncryptionPublicKey(recoveryKey); + this.Echoer = Echoer; + } + + /** + * Intends to mock eth_getEncryptionPublicKey behavior from MetaMask + * In order to make the recoveryKey retrival from Echoer possible from the bare private key + */ + static getWalletPublicKey(wallet: Wallet) { + let { privateKey } = wallet; + + if (privateKey.startsWith('0x')) { + privateKey = privateKey.replace('0x', ''); + } + + // Should return base64 encoded public key + return getEncryptionPublicKey(privateKey); + } + + // This function intends to provide an encrypted value of recoveryKey for an on-chain Echoer backup purpose + // Thus, the pubKey should be derived by a Wallet instance or from Web3 wallets + // pubKey: base64 encoded 32 bytes key from https://docs.metamask.io/wallet/reference/eth_getencryptionpublickey/ + getEncryptedAccount(walletPublicKey: string) { + const encryptedData = encrypt({ + publicKey: walletPublicKey, + data: this.recoveryKey, + version: 'x25519-xsalsa20-poly1305', + }); + + const data = packEncryptedMessage(encryptedData); + + return { + // Use this later to save hexPrivateKey generated with + // Buffer.from(JSON.stringify(encryptedData)).toString('hex') + // As we don't use buffer with this library we should leave UI to do the rest + encryptedData, + // Data that could be used as an echo(data) params + data, + }; + } + + /** + * Decrypt Echoer backuped note encryption account with private keys + */ + decryptAccountWithWallet(wallet: Wallet, event: EchoEvents): NoteAccount { + let { privateKey } = wallet; + + if (privateKey.startsWith('0x')) { + privateKey = privateKey.replace('0x', ''); + } + + const unpackedMessage = unpackEncryptedMessage(event.encryptedAccount); + + const recoveryKey = decrypt({ + encryptedData: unpackedMessage, + privateKey, + }); + + return new NoteAccount({ + netId: this.netId, + blockNumber: event.blockNumber, + recoveryKey, + Echoer: this.Echoer, + }); + } + + decryptNotes(events: EncryptedNotesEvents[]): DecryptedNotes[] { + const decryptedEvents = []; + + for (const event of events) { + try { + const unpackedMessage = unpackEncryptedMessage(event.encryptedNote); + + const [address, noteHex] = decrypt({ + encryptedData: unpackedMessage, + privateKey: this.recoveryKey, + }).split('-'); + + decryptedEvents.push({ + blockNumber: event.blockNumber, + address, + noteHex, + }); + } catch { + // decryption may fail for foreign notes + continue; + } + } + + return decryptedEvents; + } +} diff --git a/src/services/graphql/index.ts b/src/services/graphql/index.ts index 5a9c566..212ead5 100644 --- a/src/services/graphql/index.ts +++ b/src/services/graphql/index.ts @@ -769,7 +769,7 @@ export async function getAllGraphEchoEvents({ blockNumber: Number(e.blockNumber), logIndex: Number(logIndex), transactionHash: transactionHash, - address: e.address, + address: getAddress(e.address), encryptedAccount: e.encryptedAccount, }; }); diff --git a/src/services/index.ts b/src/services/index.ts index 764eed9..b947611 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -4,6 +4,7 @@ export * from './schemas'; export * from './batch'; export * from './data'; export * from './deposits'; +export * from './encryptedNotes'; export * from './fees'; export * from './merkleTree'; export * from './mimc'; diff --git a/src/services/merkleTree.ts b/src/services/merkleTree.ts index b186119..829670f 100644 --- a/src/services/merkleTree.ts +++ b/src/services/merkleTree.ts @@ -52,7 +52,7 @@ export class MerkleTreeService { this.merkleWorkerPath = merkleWorkerPath; } - async createTree({ events }: { events: Element[] }) { + async createTree(events: Element[]) { const { hash: hashFunction } = await mimc.getHash(); if (this.merkleWorkerPath) { @@ -113,13 +113,13 @@ export class MerkleTreeService { }); } - async verifyTree({ events }: { events: DepositsEvents[] }) { + async verifyTree(events: DepositsEvents[]) { console.log( `\nCreating deposit tree for ${this.netId} ${this.amount} ${this.currency.toUpperCase()} would take a while\n`, ); console.time('Created tree in'); - const tree = await this.createTree({ events: events.map(({ commitment }) => BigInt(commitment).toString()) }); + const tree = await this.createTree(events.map(({ commitment }) => BigInt(commitment).toString())); console.timeEnd('Created tree in'); console.log(''); diff --git a/src/services/networkConfig.ts b/src/services/networkConfig.ts index 425a969..06cdc2b 100644 --- a/src/services/networkConfig.ts +++ b/src/services/networkConfig.ts @@ -95,6 +95,21 @@ export type networkConfig = { export const blockSyncInterval = 10000; export const enabledChains = ['1', '10', '56', '100', '137', '42161', '43114', '11155111']; +export function getInstanceByAddress({ netId, address }: { netId: number | string; address: string }) { + const { tokens } = networkConfig[`netId${netId}`]; + + for (const [currency, { instanceAddress }] of Object.entries(tokens)) { + for (const [amount, instance] of Object.entries(instanceAddress)) { + if (instance === address) { + return { + amount, + currency, + }; + } + } + } +} + const theGraph = { name: 'Hosted Graph', url: 'https://api.thegraph.com', diff --git a/src/services/parser.ts b/src/services/parser.ts index af068a4..d54d65a 100644 --- a/src/services/parser.ts +++ b/src/services/parser.ts @@ -60,3 +60,18 @@ export function parseKey(value?: string): string { } return value; } + +/** + * Recovery key shouldn't have a 0x prefix (Also this is how the UI generates) + */ +export function parseRecoveryKey(value?: string): string { + if (!value) { + throw new InvalidArgumentError('Invalid Recovery Key'); + } + try { + computeAddress('0x' + value); + } catch { + throw new InvalidArgumentError('Invalid Recovery Key'); + } + return value; +} diff --git a/src/services/utils.ts b/src/services/utils.ts index a1b8411..e77199a 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -1,4 +1,5 @@ import { URL } from 'url'; +import { webcrypto } from 'crypto'; import BN from 'bn.js'; import type { BigNumberish } from 'ethers'; @@ -16,6 +17,8 @@ export const isNode = } ).browser && typeof globalThis.window === 'undefined'; +export const crypto = isNode ? webcrypto : (globalThis.crypto as typeof webcrypto); + export const chunk = (arr: T[], size: number): T[][] => [...Array(Math.ceil(arr.length / size))].map((_, i) => arr.slice(size * i, size + size * i)); @@ -35,26 +38,28 @@ export function validateUrl(url: string, protocols?: string[]) { } } +export function concatBytes(...arrays: Uint8Array[]): Uint8Array { + const totalSize = arrays.reduce((acc, e) => acc + e.length, 0); + const merged = new Uint8Array(totalSize); + + arrays.forEach((array, i, arrays) => { + const offset = arrays.slice(0, i).reduce((acc, e) => acc + e.length, 0); + merged.set(array, offset); + }); + + return merged; +} + export function bufferToBytes(b: Buffer) { return new Uint8Array(b.buffer); } export function bytesToBase64(bytes: Uint8Array) { - let binary = ''; - const len = bytes.byteLength; - for (let i = 0; i < len; ++i) { - binary += String.fromCharCode(bytes[i]); - } - return btoa(binary); + return btoa(String.fromCharCode.apply(null, Array.from(bytes))); } export function base64ToBytes(base64: string) { - const binaryString = atob(base64); - const bytes = new Uint8Array(binaryString.length); - for (let i = 0; i < binaryString.length; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - return bytes; + return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); } export function bytesToHex(bytes: Uint8Array) { @@ -66,6 +71,16 @@ export function bytesToHex(bytes: Uint8Array) { ); } +export function hexToBytes(hexString: string) { + if (hexString.slice(0, 2) === '0x') { + hexString = hexString.replace('0x', ''); + } + if (hexString.length % 2 !== 0) { + hexString = '0' + hexString; + } + return Uint8Array.from((hexString.match(/.{1,2}/g) as string[]).map((byte) => parseInt(byte, 16))); +} + // Convert BE encoded bytes (Buffer | Uint8Array) array to BigInt export function bytesToBN(bytes: Uint8Array) { return BigInt(bytesToHex(bytes)); diff --git a/yarn.lock b/yarn.lock index ef328a9..8eb1156 100644 --- a/yarn.lock +++ b/yarn.lock @@ -199,6 +199,38 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== +"@ethereumjs/common@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-3.2.0.tgz#b71df25845caf5456449163012074a55f048e0a0" + integrity sha512-pksvzI0VyLgmuEF2FA/JR/4/y6hcPq8OUail3/AvycBaW1d5VSauOZzqGvJ3RTmR4MU35lWE8KseKOsEhrFRBA== + dependencies: + "@ethereumjs/util" "^8.1.0" + crc-32 "^1.2.0" + +"@ethereumjs/rlp@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" + integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== + +"@ethereumjs/tx@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-4.2.0.tgz#5988ae15daf5a3b3c815493bc6b495e76009e853" + integrity sha512-1nc6VO4jtFd172BbSnTnDQVr9IYBFl1y4xPzZdtkrkKIncBCkdbgfdRV+MiTkJYAtTxvV12GRZLqBFT1PNK6Yw== + dependencies: + "@ethereumjs/common" "^3.2.0" + "@ethereumjs/rlp" "^4.0.1" + "@ethereumjs/util" "^8.1.0" + ethereum-cryptography "^2.0.0" + +"@ethereumjs/util@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.1.0.tgz#299df97fb6b034e0577ce9f94c7d9d1004409ed4" + integrity sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + ethereum-cryptography "^2.0.0" + micro-ftch "^0.3.1" + "@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" @@ -608,6 +640,41 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@metamask/abi-utils@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@metamask/abi-utils/-/abi-utils-2.0.2.tgz#ad394e9cb8a95ac177cad942daadd88a246c0de8" + integrity sha512-B/A1dY/w4F/t6cDHUscklO6ovb/ztFsrsTXFd8QlqSByk/vyy+QbPE3VVpmmyI/7RX+PA1AJcvBdzCIz+r9dVQ== + dependencies: + "@metamask/utils" "^8.0.0" + superstruct "^1.0.3" + +"@metamask/eth-sig-util@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-7.0.1.tgz#ad3227d6120f15f9293478de7dd9685a5c329586" + integrity sha512-59GSrMyFH2fPfu7nKeIQdZ150zxXNNhAQIUaFRUW+MGtVA4w/ONbiQobcRBLi+jQProfIyss51G8pfLPcQ0ylg== + dependencies: + "@ethereumjs/util" "^8.1.0" + "@metamask/abi-utils" "^2.0.2" + "@metamask/utils" "^8.1.0" + ethereum-cryptography "^2.1.2" + tweetnacl "^1.0.3" + tweetnacl-util "^0.15.1" + +"@metamask/utils@^8.0.0", "@metamask/utils@^8.1.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-8.4.0.tgz#f44812c96467a4e1b70b2edff6ee89a9caa4e354" + integrity sha512-dbIc3C7alOe0agCuBHM1h71UaEaEqOk2W8rAtEn8QGz4haH2Qq7MoK6i7v2guzvkJVVh79c+QCzIqphC3KvrJg== + dependencies: + "@ethereumjs/tx" "^4.2.0" + "@noble/hashes" "^1.3.1" + "@scure/base" "^1.1.3" + "@types/debug" "^4.1.7" + debug "^4.3.4" + pony-cause "^2.1.10" + semver "^7.5.4" + superstruct "^1.0.3" + uuid "^9.0.1" + "@noble/curves@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" @@ -615,11 +682,28 @@ dependencies: "@noble/hashes" "1.3.2" +"@noble/curves@1.3.0", "@noble/curves@~1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" + integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== + dependencies: + "@noble/hashes" "1.3.3" + "@noble/hashes@1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== +"@noble/hashes@1.3.3", "@noble/hashes@~1.3.2": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" + integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== + +"@noble/hashes@^1.3.1": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -771,6 +855,28 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.1.tgz#299eee74b7d87e116083ac5b1ce8dd9434668294" integrity sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew== +"@scure/base@^1.1.3", "@scure/base@~1.1.4": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d" + integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g== + +"@scure/bip32@1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.3.tgz#a9624991dc8767087c57999a5d79488f48eae6c8" + integrity sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ== + dependencies: + "@noble/curves" "~1.3.0" + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.4" + +"@scure/bip39@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.2.tgz#f3426813f4ced11a47489cbcf7294aa963966527" + integrity sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA== + dependencies: + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.4" + "@tornado/contracts@1.0.0": version "1.0.0" resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Fcontracts/-/1.0.0/contracts-1.0.0.tgz#4ee8aada3d12bca94b641fbb3d6f552ec3838cbe" @@ -845,6 +951,13 @@ resolved "https://registry.yarnpkg.com/@types/circomlibjs/-/circomlibjs-0.1.6.tgz#dba1b9cc68ae4f75da045b8b14c50f3444b31d7f" integrity sha512-yF174bPDaiKgejlZzCSqKwZaqXhlxMcVEHrAtstFohwP05OjtvHXOdxO6HQeTg8WwIdgMg7MJb1WyWZdUCGlPQ== +"@types/debug@^4.1.7": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + "@types/eslint-scope@^3.7.3": version "3.7.7" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" @@ -881,6 +994,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/ms@*": + version "0.7.34" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" + integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== + "@types/node-fetch@^2.6.11": version "2.6.11" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" @@ -1846,6 +1964,11 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +crc-32@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -2540,6 +2663,16 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz#1352270ed3b339fe25af5ceeadcf1b9c8e30768a" + integrity sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA== + dependencies: + "@noble/curves" "1.3.0" + "@noble/hashes" "1.3.3" + "@scure/bip32" "1.3.3" + "@scure/bip39" "1.2.2" + ethers@^5.5.1: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" @@ -3654,6 +3787,11 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +micro-ftch@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f" + integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg== + micromatch@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -4119,6 +4257,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pony-cause@^2.1.10: + version "2.1.11" + resolved "https://registry.yarnpkg.com/pony-cause/-/pony-cause-2.1.11.tgz#d69a20aaccdb3bdb8f74dd59e5c68d8e6772e4bd" + integrity sha512-M7LhCsdNbNgiLYiP4WjsfLUuFmCfnjdF6jKe2R9NKl4WFN+HZPGHJZ9lnLP7f9ZnKe3U9nuWD0szirmj+migUg== + possible-typed-array-names@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" @@ -4480,7 +4623,7 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.6.0: +semver@^7.5.4, semver@^7.6.0: version "7.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== @@ -4792,6 +4935,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +superstruct@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" + integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -4982,6 +5130,16 @@ tty-browserify@^0.0.1: resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw== +tweetnacl-util@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" + integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== + +tweetnacl@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -5145,6 +5303,11 @@ util@^0.12.5: is-typed-array "^1.1.3" which-typed-array "^1.1.2" +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"