Added Encrypted Note Service

This commit is contained in:
Tornado Contrib 2024-04-26 13:16:00 +00:00
parent 3f55d5ca99
commit 195da66ce2
Signed by: tornadocontrib
GPG Key ID: 60B4DF1A076C64B1
11 changed files with 596 additions and 27 deletions

3
.gitignore vendored

@ -3,4 +3,5 @@ node_modules
/events
/trees
backup-tornado-*
backup-tornadoInvoice-*
backup-tornadoInvoice-*
backup-note-account-*

@ -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",

@ -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<MerkleTree>;
return () => merkleTreeService.verifyTree(depositEvents) as Promise<MerkleTree>;
}
return (await merkleTreeService.verifyTree({ events: depositEvents })) as MerkleTree;
return (await merkleTreeService.verifyTree(depositEvents)) as MerkleTree;
})();
let depositTreePromise: Promise<MerkleTree> | 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<MerkleTree>;
return () => merkleTreeService.verifyTree(depositEvents) as Promise<MerkleTree>;
}
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<MerkleTree>;
return () => merkleTreeService.verifyTree(depositEvents as DepositsEvents[]) as Promise<MerkleTree>;
}
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('<netId>', '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('<netId>', '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 <GRAPH_URL>', 'The Subgraph API that CLI should interact with', parseUrl);
cmd.option('-G, --eth-graph <ETHGRAPH_URL>', '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>', 'Account key generated from UI or the createNoteAccount to store encrypted notes on-chain', parseRecoveryKey);
cmd.option('-R, --relayer <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 <TOR_PORT>', 'Optional tor port', parseNumber);

@ -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;
}
}

@ -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,
};
});

@ -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';

@ -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('');

@ -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',

@ -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;
}

@ -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 = <T>(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));

165
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"