diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index d5f9ce6d..831cb329 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -84,9 +84,9 @@ importers: prettier: specifier: ^3.3.3 version: 3.4.2 - shielder-wasm: - specifier: link:../../crates/shielder-wasm/pkg - version: link:../../crates/shielder-wasm/pkg + shielder-sdk-crypto: + specifier: workspace:* + version: link:../shielder-sdk-crypto ts-jest: specifier: ^29.2.5 version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@22.10.5)(node-notifier@10.0.1))(typescript@5.7.2) diff --git a/ts/shielder-sdk/.prettierrc.json b/ts/shielder-sdk/.prettierrc.json deleted file mode 100644 index 36b35631..00000000 --- a/ts/shielder-sdk/.prettierrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "trailingComma": "none" -} diff --git a/ts/shielder-sdk/build.sh b/ts/shielder-sdk/build.sh index dd55333c..16bc42a6 100755 --- a/ts/shielder-sdk/build.sh +++ b/ts/shielder-sdk/build.sh @@ -10,8 +10,3 @@ echo -n "as const;" >> src/_generated/abi.ts tsc --project tsconfig.json tsc-alias -p tsconfig.json - -# bundle shielder-wasm and update imports -mkdir -p dist/crates/shielder-wasm/ -cp -r ../../crates/shielder-wasm/pkg dist/crates/shielder-wasm/ -node update-imports.mjs \ No newline at end of file diff --git a/ts/shielder-sdk/eslint.config.mjs b/ts/shielder-sdk/eslint.config.mjs deleted file mode 100644 index da4ec2e6..00000000 --- a/ts/shielder-sdk/eslint.config.mjs +++ /dev/null @@ -1,40 +0,0 @@ -import eslint from "@eslint/js"; -import tseslint from "typescript-eslint"; -import globals from "globals"; -import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; - -export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommendedTypeChecked, - eslintPluginPrettierRecommended, - { - languageOptions: { - globals: { - ...globals.jest, - ...globals.node - }, - parserOptions: { - projectService: true, - tsconfigRootDir: import.meta.dirname - } - } - }, - { - ignores: [ - "dist/", - "src/_generated/", - "eslint.config.mjs", - "update-imports.mjs" - ] - }, - { - rules: { - "@typescript-eslint/unbound-method": [ - "error", - { - ignoreStatic: true - } - ] - } - } -); diff --git a/ts/shielder-sdk/src/shielder/actions/deposit.ts b/ts/shielder-sdk/src/shielder/actions/deposit.ts index f016299b..2952a4c3 100644 --- a/ts/shielder-sdk/src/shielder/actions/deposit.ts +++ b/ts/shielder-sdk/src/shielder/actions/deposit.ts @@ -1,29 +1,34 @@ import { IContract, VersionRejectedByContract } from "@/chain/contract"; -import { Scalar, scalarToBigint } from "@/crypto/scalar"; +import { + CryptoClient, + DepositPubInputs, + Proof, + Scalar, + scalarToBigint +} from "shielder-sdk-crypto"; import { SendShielderTransaction } from "@/shielder/client"; import { Calldata } from "@/shielder/actions"; -import { rawAction } from "@/shielder/actions/utils"; +import { NoteAction } from "@/shielder/actions/utils"; import { AccountState } from "@/shielder/state"; -import { DepositReturn } from "@/wasmClient"; -import { wasmClientWorker } from "@/wasmClientWorker"; +import { idHidingNonce } from "@/utils"; export interface DepositCalldata extends Calldata { - calldata: DepositReturn; + calldata: { + pubInputs: DepositPubInputs; + proof: Proof; + }; expectedContractVersion: `0x${string}`; amount: bigint; merkleRoot: Scalar; } -export class DepositAction { - contract: IContract; - constructor(contract: IContract) { +export class DepositAction extends NoteAction { + private contract: IContract; + constructor(contract: IContract, cryptoClient: CryptoClient) { + super(cryptoClient); this.contract = contract; } - static #balanceChange(currentBalance: bigint, amount: bigint) { - return currentBalance + amount; - } - /** * Return the updated state after depositing `amount` into `stateOld`. * Does not perform the actual deposit on blockchain. @@ -31,11 +36,45 @@ export class DepositAction { * @param amount amount to deposit * @returns updated state */ - static async rawDeposit( + async rawDeposit( stateOld: AccountState, amount: bigint ): Promise { - return await rawAction(stateOld, amount, this.#balanceChange); + return await this.rawAction( + stateOld, + amount, + (currentBalance: bigint, amount: bigint) => currentBalance + amount + ); + } + + async preparePubInputs( + state: AccountState, + amount: bigint, + nonce: Scalar, + nullifierOld: Scalar, + merkleRoot: Scalar + ) { + const hId = await this.cryptoClient.hasher.poseidonHash([state.id]); + const idHiding = await this.cryptoClient.hasher.poseidonHash([hId, nonce]); + + const hNullifierOld = await this.cryptoClient.hasher.poseidonHash([ + nullifierOld + ]); + const newState = await this.rawDeposit(state, amount); + if (newState === null) { + throw new Error( + "Failed to deposit, possibly due to insufficient balance" + ); + } + const hNoteNew = newState.currentNote; + return { + hNullifierOld, + hNoteNew, + nonce, + idHiding, + merkleRoot, + value: Scalar.fromBigint(amount) + }; } /** @@ -50,9 +89,10 @@ export class DepositAction { expectedContractVersion: `0x${string}` ): Promise { const lastNodeIndex = state.currentNoteIndex!; - const [path, merkleRoot] = await wasmClientWorker.merklePathAndRoot( + const [path, merkleRoot] = await this.merklePathAndRoot( await this.contract.getMerklePath(lastNodeIndex) ); + const nonce = idHidingNonce(); if (state.currentNoteIndex === undefined) { throw new Error("currentNoteIndex must be set"); @@ -61,35 +101,48 @@ export class DepositAction { const time = Date.now(); const { nullifier: nullifierOld, trapdoor: trapdoorOld } = - await wasmClientWorker.getSecrets(state.id, state.nonce - 1n); + await this.cryptoClient.secretManager.getSecrets( + state.id, + Number(state.nonce - 1n) + ); const { nullifier: nullifierNew, trapdoor: trapdoorNew } = - await wasmClientWorker.getSecrets(state.id, state.nonce); - - const accountBalanceNew = DepositAction.#balanceChange( - state.balance, - amount - ); + await this.cryptoClient.secretManager.getSecrets( + state.id, + Number(state.nonce) + ); - const calldata = await wasmClientWorker - .proveAndVerifyDeposit({ + const proof = await this.cryptoClient.depositCircuit + .prove({ id: state.id, + nonce, nullifierOld, trapdoorOld, accountBalanceOld: Scalar.fromBigint(state.balance), - merkleRoot, path, value: Scalar.fromBigint(amount), nullifierNew, - trapdoorNew, - accountBalanceNew: Scalar.fromBigint(accountBalanceNew) + trapdoorNew }) .catch((e) => { console.error(e); throw new Error(`Failed to prove deposit: ${e}`); }); + const pubInputs = await this.preparePubInputs( + state, + amount, + nonce, + nullifierOld, + merkleRoot + ); + if (!(await this.cryptoClient.depositCircuit.verify(proof, pubInputs))) { + throw new Error("Deposit proof verification failed"); + } const provingTime = Date.now() - time; return { - calldata, + calldata: { + pubInputs, + proof + }, expectedContractVersion, provingTimeMillis: provingTime, amount, diff --git a/ts/shielder-sdk/src/shielder/actions/newAccount.ts b/ts/shielder-sdk/src/shielder/actions/newAccount.ts index ed9a4b0b..e57f5fff 100644 --- a/ts/shielder-sdk/src/shielder/actions/newAccount.ts +++ b/ts/shielder-sdk/src/shielder/actions/newAccount.ts @@ -1,28 +1,32 @@ import { IContract } from "@/chain/contract"; -import { Scalar, scalarToBigint } from "@/crypto/scalar"; +import { + CryptoClient, + NewAccountPubInputs, + Proof, + Scalar, + scalarToBigint +} from "shielder-sdk-crypto"; import { SendShielderTransaction } from "@/shielder/client"; -import { rawAction } from "@/shielder/actions/utils"; +import { NoteAction } from "@/shielder/actions/utils"; import { AccountState } from "@/shielder/state"; -import { NewAccountReturn } from "@/wasmClient"; -import { wasmClientWorker } from "@/wasmClientWorker"; export interface NewAccountCalldata { - calldata: NewAccountReturn; + calldata: { + pubInputs: NewAccountPubInputs; + proof: Proof; + }; expectedContractVersion: `0x${string}`; provingTimeMillis: number; amount: bigint; } -export class NewAccountAction { +export class NewAccountAction extends NoteAction { contract: IContract; - constructor(contract: IContract) { + constructor(contract: IContract, cryptoClient: CryptoClient) { + super(cryptoClient); this.contract = contract; } - static #balanceChange(currentBalance: bigint, amount: bigint) { - return amount; - } - /** * Return the updated state after creating a new account with an initial deposit. * Does not perform the actual account creation on blockchain. @@ -30,11 +34,28 @@ export class NewAccountAction { * @param amount initial deposit * @returns updated state */ - static async rawNewAccount( + async rawNewAccount( stateOld: AccountState, amount: bigint ): Promise { - return await rawAction(stateOld, amount, NewAccountAction.#balanceChange); + return await this.rawAction( + stateOld, + amount, + (currentBalance: bigint, amount: bigint) => amount + ); + } + + async preparePubInputs(state: AccountState, amount: bigint) { + const hId = await this.cryptoClient.hasher.poseidonHash([state.id]); + const newState = await this.rawNewAccount(state, amount); + + if (newState === null) { + throw new Error( + "Failed to create new account, possibly due to negative balance" + ); + } + const hNote = newState.currentNote; + return { hId, hNote, initialDeposit: Scalar.fromBigint(amount) }; } /** @@ -48,13 +69,14 @@ export class NewAccountAction { amount: bigint, expectedContractVersion: `0x${string}` ): Promise { - const { nullifier, trapdoor } = await wasmClientWorker.getSecrets( - state.id, - state.nonce - ); + const { nullifier, trapdoor } = + await this.cryptoClient.secretManager.getSecrets( + state.id, + Number(state.nonce) + ); const time = Date.now(); - const calldata = await wasmClientWorker - .proveAndVerifyNewAccount({ + const proof = await this.cryptoClient.newAccountCircuit + .prove({ id: state.id, nullifier, trapdoor, @@ -64,10 +86,17 @@ export class NewAccountAction { console.error(e); throw new Error(`Failed to prove new account: ${e}`); }); + const pubInputs = await this.preparePubInputs(state, amount); + if (!(await this.cryptoClient.newAccountCircuit.verify(proof, pubInputs))) { + throw new Error("New account proof verification failed"); + } const provingTime = Date.now() - time; return { expectedContractVersion, - calldata, + calldata: { + pubInputs, + proof + }, provingTimeMillis: provingTime, amount }; diff --git a/ts/shielder-sdk/src/shielder/actions/utils.ts b/ts/shielder-sdk/src/shielder/actions/utils.ts index 5c07f71a..d6855202 100644 --- a/ts/shielder-sdk/src/shielder/actions/utils.ts +++ b/ts/shielder-sdk/src/shielder/actions/utils.ts @@ -1,37 +1,71 @@ -import { Scalar } from "@/crypto/scalar"; +import { CryptoClient, Scalar } from "shielder-sdk-crypto"; import { AccountState } from "@/shielder/state"; import { noteVersion } from "@/utils"; -import { wasmClientWorker } from "@/wasmClientWorker"; -export async function rawAction( - stateOld: AccountState, - amount: bigint, - balanceChange: (currentBalance: bigint, amount: bigint) => bigint -): Promise { - const { nullifier: nullifierNew, trapdoor: trapdoorNew } = - await wasmClientWorker.getSecrets(stateOld.id, stateOld.nonce); - const balanceNew = balanceChange(stateOld.balance, amount); - if (balanceNew < 0n) { - return null; +export abstract class NoteAction { + protected cryptoClient: CryptoClient; + constructor(cryptoClient: CryptoClient) { + this.cryptoClient = cryptoClient; + } + async rawAction( + stateOld: AccountState, + amount: bigint, + balanceChange: (currentBalance: bigint, amount: bigint) => bigint + ): Promise { + const { nullifier: nullifierNew, trapdoor: trapdoorNew } = + await this.cryptoClient.secretManager.getSecrets( + stateOld.id, + Number(stateOld.nonce) + ); + const balanceNew = balanceChange(stateOld.balance, amount); + if (balanceNew < 0n) { + return null; + } + const scalarArray: Scalar[] = new Array( + await this.cryptoClient.noteTreeConfig.arity() + ).fill(Scalar.fromBigint(0n)); + scalarArray[0] = Scalar.fromBigint(balanceNew); + const hAccountBalanceNew = + await this.cryptoClient.hasher.poseidonHash(scalarArray); + const version = noteVersion(); + const noteNew = await this.cryptoClient.hasher.poseidonHash([ + version, + stateOld.id, + nullifierNew, + trapdoorNew, + hAccountBalanceNew + ]); + return { + id: stateOld.id, + nonce: stateOld.nonce + 1n, + balance: balanceNew, + currentNote: noteNew, + storageSchemaVersion: stateOld.storageSchemaVersion + }; + } + + async merklePathAndRoot( + rawPath: readonly bigint[] + ): Promise<[Uint8Array, Scalar]> { + if ( + rawPath.length != + (await this.cryptoClient.noteTreeConfig.treeHeight()) * + (await this.cryptoClient.noteTreeConfig.arity()) + + 1 + ) { + throw new Error("Wrong path length"); + } + const mappedPath = rawPath.map((x) => Scalar.fromBigint(x)); + const path = new Uint8Array( + mappedPath + .slice(0, -1) // exclude the root + .map((x) => x.bytes) // convert to bytes + .reduce( + (acc, val) => new Uint8Array([...acc, ...val]), + new Uint8Array() + ) // flatten + ); + const root = mappedPath[mappedPath.length - 1]; + return [path, root]; } - const scalarArray: Scalar[] = new Array( - await wasmClientWorker.arity() - ).fill(Scalar.fromBigint(0n)); - scalarArray[0] = Scalar.fromBigint(balanceNew); - const hAccountBalanceNew = await wasmClientWorker.poseidonHash(scalarArray); - const version = noteVersion(); - const noteNew = await wasmClientWorker.poseidonHash([ - version, - stateOld.id, - nullifierNew, - trapdoorNew, - hAccountBalanceNew - ]); - return { - id: stateOld.id, - nonce: stateOld.nonce + 1n, - balance: balanceNew, - currentNote: noteNew, - storageSchemaVersion: stateOld.storageSchemaVersion - }; } diff --git a/ts/shielder-sdk/src/shielder/actions/withdraw.ts b/ts/shielder-sdk/src/shielder/actions/withdraw.ts index d909bd5d..145d1cff 100644 --- a/ts/shielder-sdk/src/shielder/actions/withdraw.ts +++ b/ts/shielder-sdk/src/shielder/actions/withdraw.ts @@ -1,34 +1,44 @@ import { IContract } from "@/chain/contract"; -import { Scalar, scalarToBigint } from "@/crypto/scalar"; +import { + CryptoClient, + Proof, + Scalar, + scalarToBigint, + WithdrawPubInputs +} from "shielder-sdk-crypto"; import { AccountState } from "@/shielder/state"; -import { wasmClientWorker } from "@/wasmClientWorker"; -import { Address } from "viem"; +import { Address, encodePacked, hexToBigInt, keccak256 } from "viem"; import { IRelayer, VersionRejectedByRelayer } from "@/chain/relayer"; -import { rawAction } from "@/shielder/actions/utils"; -import { WithdrawReturn } from "@/crypto/circuits/withdraw"; +import { NoteAction } from "@/shielder/actions/utils"; +import { idHidingNonce } from "@/utils"; +import { contractVersion } from "@/constants"; export interface WithdrawCalldata { expectedContractVersion: `0x${string}`; - calldata: WithdrawReturn; + calldata: { + pubInputs: WithdrawPubInputs; + proof: Proof; + }; provingTimeMillis: number; amount: bigint; address: Address; merkleRoot: Scalar; } -export class WithdrawAction { +export class WithdrawAction extends NoteAction { contract: IContract; relayer: IRelayer; - constructor(contract: IContract, relayer: IRelayer) { + constructor( + contract: IContract, + relayer: IRelayer, + cryptoClient: CryptoClient + ) { + super(cryptoClient); this.contract = contract; this.relayer = relayer; } - static #balanceChange(currentBalance: bigint, amount: bigint) { - return currentBalance - amount; - } - /** * Return the updated state after withdrawing `amount` from `stateOld`. * Does not perform the actual withdrawal on blockchain. @@ -36,11 +46,75 @@ export class WithdrawAction { * @param amount amount to withdraw * @returns updated state */ - static async rawWithdraw( + async rawWithdraw( stateOld: AccountState, amount: bigint ): Promise { - return await rawAction(stateOld, amount, WithdrawAction.#balanceChange); + return await this.rawAction( + stateOld, + amount, + (currentBalance: bigint, amount: bigint) => currentBalance - amount + ); + } + + calculateCommitment( + address: Scalar, + relayerAddress: Scalar, + relayerFee: Scalar + ): Scalar { + const encodingHash = hexToBigInt( + keccak256( + encodePacked( + ["bytes3", "uint256", "uint256", "uint256"], + [ + contractVersion, + scalarToBigint(address), + scalarToBigint(relayerAddress), + scalarToBigint(relayerFee) + ] + ) + ) + ); + + // Truncating to fit in the field size, as in the contract. + const commitment = encodingHash >> 4n; + + return Scalar.fromBigint(commitment); + } + + async preparePubInputs( + state: AccountState, + amount: bigint, + nonce: Scalar, + nullifierOld: Scalar, + merkleRoot: Scalar, + commitment: Scalar + ): Promise { + const hId = await this.cryptoClient.hasher.poseidonHash([state.id]); + const idHiding = await this.cryptoClient.hasher.poseidonHash([hId, nonce]); + + const hNullifierOld = await this.cryptoClient.hasher.poseidonHash([ + nullifierOld + ]); + + const newNote = await this.rawWithdraw(state, amount); + + if (newNote === null) { + throw new Error( + "Failed to withdraw, possibly due to insufficient balance" + ); + } + + const hNoteNew = newNote.currentNote; + + return { + hNullifierOld, + hNoteNew, + idHiding, + merkleRoot, + value: Scalar.fromBigint(amount), + commitment + }; } /** @@ -61,10 +135,12 @@ export class WithdrawAction { expectedContractVersion: `0x${string}` ): Promise { const lastNodeIndex = state.currentNoteIndex!; - const [path, merkleRoot] = await wasmClientWorker.merklePathAndRoot( + const [path, merkleRoot] = await this.merklePathAndRoot( await this.contract.getMerklePath(lastNodeIndex) ); + const nonce = idHidingNonce(); + if (state.currentNoteIndex === undefined) { throw new Error("currentNoteIndex must be set"); } @@ -80,39 +156,59 @@ export class WithdrawAction { const time = Date.now(); const { nullifier: nullifierOld, trapdoor: trapdoorOld } = - await wasmClientWorker.getSecrets(state.id, state.nonce - 1n); + await this.cryptoClient.secretManager.getSecrets( + state.id, + Number(state.nonce - 1n) + ); const { nullifier: nullifierNew, trapdoor: trapdoorNew } = - await wasmClientWorker.getSecrets(state.id, state.nonce); - - const accountBalanceNew = WithdrawAction.#balanceChange( - state.balance, - amount - ); + await this.cryptoClient.secretManager.getSecrets( + state.id, + Number(state.nonce) + ); - const calldata = await wasmClientWorker - .proveAndVerifyWithdraw({ + const proof = await this.cryptoClient.withdrawCircuit + .prove({ id: state.id, + nonce, nullifierOld, trapdoorOld, accountBalanceOld: Scalar.fromBigint(state.balance), - merkleRoot, path, value: Scalar.fromBigint(amount), nullifierNew, trapdoorNew, - accountBalanceNew: Scalar.fromBigint(accountBalanceNew), - relayerAddress: Scalar.fromAddress(await this.relayer.address()), - relayerFee: Scalar.fromBigint(totalFee), - address: Scalar.fromAddress(address) + commitment: this.calculateCommitment( + Scalar.fromAddress(address), + Scalar.fromAddress(await this.relayer.address()), + Scalar.fromBigint(totalFee) + ) }) .catch((e) => { console.error(e); throw new Error(`Failed to prove withdrawal: ${e}`); }); + const pubInputs = await this.preparePubInputs( + state, + amount, + nonce, + nullifierOld, + merkleRoot, + this.calculateCommitment( + Scalar.fromAddress(address), + Scalar.fromAddress(await this.relayer.address()), + Scalar.fromBigint(totalFee) + ) + ); + if (!(await this.cryptoClient.withdrawCircuit.verify(proof, pubInputs))) { + throw new Error("Withdrawal proof verification failed"); + } const provingTime = Date.now() - time; return { expectedContractVersion, - calldata, + calldata: { + pubInputs, + proof + }, provingTimeMillis: provingTime, amount, address, diff --git a/ts/shielder-sdk/src/shielder/client.ts b/ts/shielder-sdk/src/shielder/client.ts index 3873f910..ce4e2e9b 100644 --- a/ts/shielder-sdk/src/shielder/client.ts +++ b/ts/shielder-sdk/src/shielder/client.ts @@ -28,6 +28,7 @@ import { Calldata } from "@/shielder/actions"; import { contractVersion } from "@/constants"; import { CustomError } from "ts-custom-error"; import { CryptoClient } from "shielder-sdk-crypto"; +import { StateEventsFilter } from "@/shielder/state/events"; export type ShielderOperation = "shield" | "withdraw" | "sync"; @@ -176,13 +177,19 @@ export class ShielderClient { internalStorage, cryptoClient ); - this.newAccountAction = new NewAccountAction(contract); - this.depositAction = new DepositAction(contract); - this.withdrawAction = new WithdrawAction(contract, relayer); + this.newAccountAction = new NewAccountAction(contract, cryptoClient); + this.depositAction = new DepositAction(contract, cryptoClient); + this.withdrawAction = new WithdrawAction(contract, relayer, cryptoClient); + const stateEventsFilter = new StateEventsFilter( + this.newAccountAction, + this.depositAction, + this.withdrawAction + ); this.stateSynchronizer = new StateSynchronizer( this.stateManager, contract, cryptoClient, + stateEventsFilter, callbacks.onNewTransaction ); this.relayer = relayer; diff --git a/ts/shielder-sdk/src/shielder/state/chainEvents.ts b/ts/shielder-sdk/src/shielder/state/chainEvents.ts deleted file mode 100644 index 995b2599..00000000 --- a/ts/shielder-sdk/src/shielder/state/chainEvents.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { AccountState } from "@/shielder/state"; -import { DepositAction } from "@/shielder/actions/deposit"; -import { NewAccountAction } from "@/shielder/actions/newAccount"; -import { WithdrawAction } from "@/shielder/actions/withdraw"; -import { NoteEvent } from "@/chain/contract"; -import { scalarToBigint } from "shielder-sdk-crypto"; - -export const newStateByEvent = async ( - state: AccountState, - noteEvent: NoteEvent -): Promise => { - const getNewState = async () => { - switch (noteEvent.name) { - case "NewAccountNative": - return await NewAccountAction.rawNewAccount(state, noteEvent.amount); - case "DepositNative": - return await DepositAction.rawDeposit(state, noteEvent.amount); - case "WithdrawNative": - return await WithdrawAction.rawWithdraw(state, noteEvent.amount); - } - }; - const newState = await getNewState(); - if (newState === null) { - return null; - } - return { - ...newState, - currentNoteIndex: noteEvent.newNoteIndex - }; -}; - -export const stateChangingEvents = async ( - state: AccountState, - noteEvents: NoteEvent[] -): Promise => { - const filteredEvents: NoteEvent[] = []; - for (const event of noteEvents) { - const newState = await newStateByEvent(state, event); - if (newState && scalarToBigint(newState.currentNote) == event.newNote) { - filteredEvents.push(event); - } - } - return filteredEvents; -}; diff --git a/ts/shielder-sdk/src/shielder/state/events.ts b/ts/shielder-sdk/src/shielder/state/events.ts new file mode 100644 index 00000000..b3e21ed7 --- /dev/null +++ b/ts/shielder-sdk/src/shielder/state/events.ts @@ -0,0 +1,62 @@ +import { AccountState } from "@/shielder/state"; +import { DepositAction } from "@/shielder/actions/deposit"; +import { NewAccountAction } from "@/shielder/actions/newAccount"; +import { WithdrawAction } from "@/shielder/actions/withdraw"; +import { NoteEvent } from "@/chain/contract"; +import { scalarToBigint } from "shielder-sdk-crypto"; + +export class StateEventsFilter { + newAccountAction: NewAccountAction; + depositAction: DepositAction; + withdrawAction: WithdrawAction; + + constructor( + newAccountAction: NewAccountAction, + depositAction: DepositAction, + withdrawAction: WithdrawAction + ) { + this.newAccountAction = newAccountAction; + this.depositAction = depositAction; + this.withdrawAction = withdrawAction; + } + newStateByEvent = async ( + state: AccountState, + noteEvent: NoteEvent + ): Promise => { + const getNewState = async () => { + switch (noteEvent.name) { + case "NewAccountNative": + return await this.newAccountAction.rawNewAccount( + state, + noteEvent.amount + ); + case "DepositNative": + return await this.depositAction.rawDeposit(state, noteEvent.amount); + case "WithdrawNative": + return await this.withdrawAction.rawWithdraw(state, noteEvent.amount); + } + }; + const newState = await getNewState(); + if (newState === null) { + return null; + } + return { + ...newState, + currentNoteIndex: noteEvent.newNoteIndex + }; + }; + + stateChangingEvents = async ( + state: AccountState, + noteEvents: NoteEvent[] + ): Promise => { + const filteredEvents: NoteEvent[] = []; + for (const event of noteEvents) { + const newState = await this.newStateByEvent(state, event); + if (newState && scalarToBigint(newState.currentNote) == event.newNote) { + filteredEvents.push(event); + } + } + return filteredEvents; + }; +} diff --git a/ts/shielder-sdk/src/shielder/state/sync.ts b/ts/shielder-sdk/src/shielder/state/sync.ts index a076362b..39e2cd61 100644 --- a/ts/shielder-sdk/src/shielder/state/sync.ts +++ b/ts/shielder-sdk/src/shielder/state/sync.ts @@ -7,10 +7,7 @@ import { ShielderTransaction, StateManager } from "@/shielder/state"; -import { - newStateByEvent, - stateChangingEvents -} from "@/shielder/state/chainEvents"; +import { StateEventsFilter } from "@/shielder/state/events"; import { Mutex } from "async-mutex"; import { isVersionSupported } from "@/utils"; @@ -24,17 +21,20 @@ export class StateSynchronizer { private contract: IContract; private stateManager: StateManager; private cryptoClient: CryptoClient; + private stateEventsFilter: StateEventsFilter; private syncCallback?: (shielderTransaction: ShielderTransaction) => unknown; private mutex: Mutex; constructor( stateManager: StateManager, contract: IContract, cryptoClient: CryptoClient, + stateEventsFilter: StateEventsFilter, syncCallback?: (shielderTransaction: ShielderTransaction) => unknown ) { this.stateManager = stateManager; this.contract = contract; this.cryptoClient = cryptoClient; + this.stateEventsFilter = stateEventsFilter; this.syncCallback = syncCallback; this.mutex = new Mutex(); } @@ -52,7 +52,10 @@ export class StateSynchronizer { if (!event) { break; } - const newState = await newStateByEvent(state, event); + const newState = await this.stateEventsFilter.newStateByEvent( + state, + event + ); if (!newState) { throw new Error("State is null, this should not happen"); } @@ -73,7 +76,10 @@ export class StateSynchronizer { while (true) { const event = await this.findStateTransitionEvent(state); if (!event) break; - const newState = await newStateByEvent(state, event); + const newState = await this.stateEventsFilter.newStateByEvent( + state, + event + ); if (!newState) { throw new Error("State is null, this should not happen"); } @@ -96,7 +102,7 @@ export class StateSynchronizer { } private async getNoteEventForBlock(state: AccountState, block: bigint) { - const events = await stateChangingEvents( + const events = await this.stateEventsFilter.stateChangingEvents( state, await this.contract.getNoteEventsFromBlock(block) ); diff --git a/ts/shielder-sdk/src/utils.ts b/ts/shielder-sdk/src/utils.ts index ab8267ae..a362dc37 100644 --- a/ts/shielder-sdk/src/utils.ts +++ b/ts/shielder-sdk/src/utils.ts @@ -1,4 +1,4 @@ -import { Scalar } from "@/crypto/scalar"; +import { Scalar } from "shielder-sdk-crypto"; import { contractVersion } from "@/constants"; export function flatUint8(arr: Uint8Array[]) { diff --git a/ts/shielder-sdk/update-imports.mjs b/ts/shielder-sdk/update-imports.mjs deleted file mode 100644 index 7f276980..00000000 --- a/ts/shielder-sdk/update-imports.mjs +++ /dev/null @@ -1,84 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; - -// Configuration -const basePatterns = [ - 'from "shielder-wasm/web-singlethreaded"', - 'from "shielder-wasm/web-multithreaded"' -]; -const targetPaths = [ - "crates/shielder-wasm/pkg/pkg-web-singlethreaded/shielder_wasm.js", - "crates/shielder-wasm/pkg/pkg-web-multithreaded/shielder_wasm.js" -]; - -function getRelativePath(fromPath, toPath) { - // Get the relative directory depth from the source file to the project root - const relativeToRoot = path.relative(path.dirname(fromPath), process.cwd()); - // Count how many levels up we need to go - const upLevels = relativeToRoot - .split(path.sep) - .filter((x) => x === "..").length; - // Add extra "../" based on the current file's depth - const prefix = "../".repeat(upLevels - 1); - // If the target path is in the same directory, return "./" - if (prefix === "") { - return "./" + toPath; - } - return prefix + toPath; -} - -function updateImports(filePath) { - try { - let content = fs.readFileSync(filePath, "utf8"); - let wasModified = false; - - for (let i = 0; i < basePatterns.length; i++) { - if (content.includes(basePatterns[i])) { - const relativePath = getRelativePath(filePath, targetPaths[i]); - content = content.replace( - new RegExp(basePatterns[i], "g"), - `from "${relativePath}"` - ); - wasModified = true; - } - } - if (wasModified) { - fs.writeFileSync(filePath, content); - console.log(`Updated imports in: ${filePath}`); - // Log the relative paths for verification - console.log(` Relative paths for this file:`); - targetPaths.forEach((targetPath) => { - console.log(` - ${getRelativePath(filePath, targetPath)}`); - }); - } - } catch (error) { - console.error(`Error processing ${filePath}:`, error); - } -} - -function walkDirectory(dir, fileTypes = [".js", ".jsx", ".ts", ".tsx"]) { - function walk(currentDir) { - const files = fs.readdirSync(currentDir); - - for (const file of files) { - const filePath = path.join(currentDir, file); - // Skip symlinks - if (fs.lstatSync(filePath).isSymbolicLink()) { - console.log(`Skipping symlink: ${filePath}`); - continue; - } - const stat = fs.statSync(filePath); - if (stat.isDirectory() && !file.includes("node_modules")) { - walk(filePath); - } else if (stat.isFile() && fileTypes.includes(path.extname(file))) { - updateImports(filePath); - } - } - } - - walk(dir); -} - -// Usage example -const startDir = "./dist"; -walkDirectory(startDir);