@kohaku-eth/tornado-cash 0.0.2-alpha.15 → 0.0.2-alpha.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -33,6 +33,15 @@ type DeriveDepositSecretParams = BaseDeriveSecretParams & {
33
33
  interface ISecretManager {
34
34
  getDepositSecrets: (params: DeriveDepositSecretParams) => Promise<Secret>;
35
35
  deriveEphemeralSigner: (params: DeriveDepositSecretParams) => Promise<`0x${string}`>;
36
+ /**
37
+ * Derives a delegator key from a classical wallet BIP-32 `path`, WITHOUT the
38
+ * `coalesceSecret` privacy transform, so it resolves to a recoverable,
39
+ * classical Ethereum address. Used for the tail-call batch delegator when the
40
+ * integrator supplies a wallet path (see `paymasterWithdrawThunk`).
41
+ */
42
+ deriveDelegatorSigner: (params: {
43
+ path: string;
44
+ }) => Promise<`0x${string}`>;
36
45
  }
37
46
  interface SecretManagerParams {
38
47
  host: Pick<Host, 'keystore'>;
@@ -352,6 +361,24 @@ declare enum DepositStrategy {
352
361
  MinFee = 1
353
362
  }
354
363
 
364
+ /**
365
+ * How the batch delegator for a tail-call paymaster withdrawal is produced.
366
+ * Only honored when `tailCalls` are present (see `paymasterWithdrawThunk`).
367
+ *
368
+ * - `deterministic` (default): recoverable. With `path`, the delegator is a
369
+ * classical wallet-path address; without `path`, an ephemeral address keyed by
370
+ * the batch's first deposit.
371
+ * - `random`: a throwaway, unrecoverable EOA — an explicit opt-out of
372
+ * recoverability. Never the default, because in the tail-call path the
373
+ * delegator holds the withdrawn funds.
374
+ */
375
+ type DelegationConfig = {
376
+ mode: 'deterministic';
377
+ path?: string;
378
+ } | {
379
+ mode: 'random';
380
+ };
381
+
355
382
  interface SerializedAuth {
356
383
  address: Address$1;
357
384
  chainId: Hex;
@@ -410,12 +437,6 @@ interface IPaymasterBroadcasterClient {
410
437
  broadcast(withdrawals: IPaymasterWithdrawalPayload[]): Promise<PaymasterBroadcastResult[]>;
411
438
  }
412
439
 
413
- type DelegationConfig = {
414
- mode: 'deterministic';
415
- path?: string;
416
- } | {
417
- mode: 'random';
418
- };
419
440
  type StringAddress = `0x${string}`;
420
441
  interface IPaymasterConfig {
421
442
  paymasterAddress: StringAddress;
package/dist/index.js CHANGED
@@ -764,9 +764,13 @@ async function SecretManager({
764
764
  const coalesced = coalesceSecret({ secret: raw, chainId, poolAddress });
765
765
  return `0x${coalesced.toString(16).padStart(64, "0")}`;
766
766
  };
767
+ const deriveDelegatorSigner = async ({ path }) => {
768
+ return Promise.resolve(keystore.deriveAt(path));
769
+ };
767
770
  return {
768
771
  getDepositSecrets: (params) => deriveSecrets(params),
769
- deriveEphemeralSigner
772
+ deriveEphemeralSigner,
773
+ deriveDelegatorSigner
770
774
  };
771
775
  }
772
776
  function tcPath({ accountIndex, secretType, depositIndex }) {
@@ -17281,9 +17281,13 @@ async function SecretManager({
17281
17281
  const coalesced = coalesceSecret({ secret: raw, chainId, poolAddress });
17282
17282
  return `0x${coalesced.toString(16).padStart(64, "0")}`;
17283
17283
  };
17284
+ const deriveDelegatorSigner = async ({ path }) => {
17285
+ return Promise.resolve(keystore.deriveAt(path));
17286
+ };
17284
17287
  return {
17285
17288
  getDepositSecrets: (params) => deriveSecrets(params),
17286
- deriveEphemeralSigner
17289
+ deriveEphemeralSigner,
17290
+ deriveDelegatorSigner
17287
17291
  };
17288
17292
  }
17289
17293
  function tcPath({ accountIndex, secretType, depositIndex }) {
@@ -24087,11 +24091,8 @@ async function getUserOperationGasPrice(client) {
24087
24091
  fast: parse(result.fast)
24088
24092
  };
24089
24093
  }
24090
- function ephemeralSenderAddress(privateKey) {
24091
- return privateKeyToAccount(privateKey).address;
24092
- }
24093
24094
  async function buildSignedTornadoUserOp({
24094
- privateKey,
24095
+ signer,
24095
24096
  chainId,
24096
24097
  paymasterAddress,
24097
24098
  paymasterData,
@@ -24101,7 +24102,7 @@ async function buildSignedTornadoUserOp({
24101
24102
  tailCalls = async () => [],
24102
24103
  nonce = 0n
24103
24104
  }) {
24104
- const owner = privateKeyToAccount(privateKey);
24105
+ const owner = signer;
24105
24106
  const calls = await tailCalls(owner.address);
24106
24107
  let callData = "0x";
24107
24108
  if (calls.length === 1) {
@@ -24174,6 +24175,12 @@ function serializeAuth(auth) {
24174
24175
  }
24175
24176
 
24176
24177
  // src/state/thunks/paymasterWithdrawThunk.ts
24178
+ var BIP32_PATH = /^m(\/\d+'?)+$/;
24179
+ function assertValidDelegatorPath(path) {
24180
+ if (!BIP32_PATH.test(path)) {
24181
+ throw new Error(`Invalid delegation path "${path}": expected a BIP-32 path like m/44'/60'/0'/0/0`);
24182
+ }
24183
+ }
24177
24184
  var paymasterWithdrawThunk = createAsyncThunk("withdraw/executePaymasterWithdrawals", async ({
24178
24185
  dataService,
24179
24186
  assetAddress,
@@ -24220,18 +24227,28 @@ var paymasterWithdrawThunk = createAsyncThunk("withdraw/executePaymasterWithdraw
24220
24227
  const relayerAddress = BigInt(paymasterAddress);
24221
24228
  const bigintChainId = await dataService.getChainId();
24222
24229
  const { recipient: originalRecipient, ...restWithoutRecipient } = rest;
24223
- const sharedPrivateKey = tailCalls ? generatePrivateKey() : null;
24230
+ const ephemeralSigner = async (deposit) => privateKeyToAccount(await secretManager.deriveEphemeralSigner({
24231
+ depositIndex: deposit.index,
24232
+ chainId: bigintChainId,
24233
+ poolAddress: deposit.pool
24234
+ }));
24235
+ const resolveBatchDelegator = async () => {
24236
+ if (delegation?.mode === "random") return privateKeyToAccount(generatePrivateKey());
24237
+ if (delegation?.mode === "deterministic" && delegation.path) {
24238
+ assertValidDelegatorPath(delegation.path);
24239
+ return privateKeyToAccount(await secretManager.deriveDelegatorSigner({ path: delegation.path }));
24240
+ }
24241
+ return ephemeralSigner(deposits[0]);
24242
+ };
24243
+ const resolveIndependentSigner = (deposit) => delegation?.mode === "deterministic" ? ephemeralSigner(deposit) : Promise.resolve(privateKeyToAccount(generatePrivateKey()));
24244
+ const sharedDelegator = tailCalls ? await resolveBatchDelegator() : null;
24224
24245
  const proofOutputs = [];
24225
24246
  const userOperations = [];
24226
24247
  for (let i = 0; i < deposits.length; i++) {
24227
24248
  const deposit = deposits[i];
24228
24249
  const isLast = i === deposits.length - 1;
24229
- const privateKey = sharedPrivateKey ?? (delegation?.mode === "deterministic" ? await secretManager.deriveEphemeralSigner({
24230
- depositIndex: deposit.index,
24231
- chainId: bigintChainId,
24232
- poolAddress: deposit.pool
24233
- }) : generatePrivateKey());
24234
- const recipient = sharedPrivateKey ? BigInt(ephemeralSenderAddress(privateKey)) : originalRecipient;
24250
+ const signer = sharedDelegator ?? await resolveIndependentSigner(deposit);
24251
+ const recipient = sharedDelegator ? BigInt(signer.address) : originalRecipient;
24235
24252
  const effectiveTailCalls = isLast ? tailCalls : void 0;
24236
24253
  const gas = { ...gasUnits, callGasLimit: effectiveTailCalls ? gasUnits.callGasLimit : 0n };
24237
24254
  const withdrawResultAction = await dispatch(
@@ -24261,7 +24278,7 @@ var paymasterWithdrawThunk = createAsyncThunk("withdraw/executePaymasterWithdraw
24261
24278
  );
24262
24279
  userOperations.push(
24263
24280
  await buildSignedTornadoUserOp({
24264
- privateKey,
24281
+ signer,
24265
24282
  chainId,
24266
24283
  paymasterAddress,
24267
24284
  paymasterData,
@@ -24269,9 +24286,9 @@ var paymasterWithdrawThunk = createAsyncThunk("withdraw/executePaymasterWithdraw
24269
24286
  maxFeePerGas,
24270
24287
  maxPriorityFeePerGas,
24271
24288
  tailCalls: effectiveTailCalls,
24272
- // When sharing one ephemeral key across multiple deposits each userOp
24273
- // needs a distinct nonce (0, 1, 2 …) on the shared sender.
24274
- nonce: sharedPrivateKey ? BigInt(i) : 0n
24289
+ // When sharing one delegator across multiple deposits each userOp needs
24290
+ // a distinct nonce (0, 1, 2 …) on the shared sender.
24291
+ nonce: sharedDelegator ? BigInt(i) : 0n
24275
24292
  })
24276
24293
  );
24277
24294
  }
@@ -16853,9 +16853,13 @@ async function SecretManager({
16853
16853
  const coalesced = coalesceSecret({ secret: raw, chainId, poolAddress });
16854
16854
  return `0x${coalesced.toString(16).padStart(64, "0")}`;
16855
16855
  };
16856
+ const deriveDelegatorSigner = async ({ path }) => {
16857
+ return Promise.resolve(keystore.deriveAt(path));
16858
+ };
16856
16859
  return {
16857
16860
  getDepositSecrets: (params) => deriveSecrets(params),
16858
- deriveEphemeralSigner
16861
+ deriveEphemeralSigner,
16862
+ deriveDelegatorSigner
16859
16863
  };
16860
16864
  }
16861
16865
  function tcPath({ accountIndex, secretType, depositIndex }) {
@@ -23313,11 +23317,8 @@ async function getUserOperationGasPrice(client) {
23313
23317
  fast: parse(result.fast)
23314
23318
  };
23315
23319
  }
23316
- function ephemeralSenderAddress(privateKey) {
23317
- return privateKeyToAccount(privateKey).address;
23318
- }
23319
23320
  async function buildSignedTornadoUserOp({
23320
- privateKey,
23321
+ signer,
23321
23322
  chainId,
23322
23323
  paymasterAddress,
23323
23324
  paymasterData,
@@ -23327,7 +23328,7 @@ async function buildSignedTornadoUserOp({
23327
23328
  tailCalls = async () => [],
23328
23329
  nonce = 0n
23329
23330
  }) {
23330
- const owner = privateKeyToAccount(privateKey);
23331
+ const owner = signer;
23331
23332
  const calls = await tailCalls(owner.address);
23332
23333
  let callData = "0x";
23333
23334
  if (calls.length === 1) {
@@ -23400,6 +23401,12 @@ function serializeAuth(auth) {
23400
23401
  }
23401
23402
 
23402
23403
  // src/state/thunks/paymasterWithdrawThunk.ts
23404
+ var BIP32_PATH = /^m(\/\d+'?)+$/;
23405
+ function assertValidDelegatorPath(path) {
23406
+ if (!BIP32_PATH.test(path)) {
23407
+ throw new Error(`Invalid delegation path "${path}": expected a BIP-32 path like m/44'/60'/0'/0/0`);
23408
+ }
23409
+ }
23403
23410
  var paymasterWithdrawThunk = createAsyncThunk("withdraw/executePaymasterWithdrawals", async ({
23404
23411
  dataService,
23405
23412
  assetAddress,
@@ -23446,18 +23453,28 @@ var paymasterWithdrawThunk = createAsyncThunk("withdraw/executePaymasterWithdraw
23446
23453
  const relayerAddress = BigInt(paymasterAddress);
23447
23454
  const bigintChainId = await dataService.getChainId();
23448
23455
  const { recipient: originalRecipient, ...restWithoutRecipient } = rest;
23449
- const sharedPrivateKey = tailCalls ? generatePrivateKey() : null;
23456
+ const ephemeralSigner = async (deposit) => privateKeyToAccount(await secretManager.deriveEphemeralSigner({
23457
+ depositIndex: deposit.index,
23458
+ chainId: bigintChainId,
23459
+ poolAddress: deposit.pool
23460
+ }));
23461
+ const resolveBatchDelegator = async () => {
23462
+ if (delegation?.mode === "random") return privateKeyToAccount(generatePrivateKey());
23463
+ if (delegation?.mode === "deterministic" && delegation.path) {
23464
+ assertValidDelegatorPath(delegation.path);
23465
+ return privateKeyToAccount(await secretManager.deriveDelegatorSigner({ path: delegation.path }));
23466
+ }
23467
+ return ephemeralSigner(deposits[0]);
23468
+ };
23469
+ const resolveIndependentSigner = (deposit) => delegation?.mode === "deterministic" ? ephemeralSigner(deposit) : Promise.resolve(privateKeyToAccount(generatePrivateKey()));
23470
+ const sharedDelegator = tailCalls ? await resolveBatchDelegator() : null;
23450
23471
  const proofOutputs = [];
23451
23472
  const userOperations = [];
23452
23473
  for (let i = 0; i < deposits.length; i++) {
23453
23474
  const deposit = deposits[i];
23454
23475
  const isLast = i === deposits.length - 1;
23455
- const privateKey = sharedPrivateKey ?? (delegation?.mode === "deterministic" ? await secretManager.deriveEphemeralSigner({
23456
- depositIndex: deposit.index,
23457
- chainId: bigintChainId,
23458
- poolAddress: deposit.pool
23459
- }) : generatePrivateKey());
23460
- const recipient = sharedPrivateKey ? BigInt(ephemeralSenderAddress(privateKey)) : originalRecipient;
23476
+ const signer = sharedDelegator ?? await resolveIndependentSigner(deposit);
23477
+ const recipient = sharedDelegator ? BigInt(signer.address) : originalRecipient;
23461
23478
  const effectiveTailCalls = isLast ? tailCalls : void 0;
23462
23479
  const gas = { ...gasUnits, callGasLimit: effectiveTailCalls ? gasUnits.callGasLimit : 0n };
23463
23480
  const withdrawResultAction = await dispatch(
@@ -23487,7 +23504,7 @@ var paymasterWithdrawThunk = createAsyncThunk("withdraw/executePaymasterWithdraw
23487
23504
  );
23488
23505
  userOperations.push(
23489
23506
  await buildSignedTornadoUserOp({
23490
- privateKey,
23507
+ signer,
23491
23508
  chainId,
23492
23509
  paymasterAddress,
23493
23510
  paymasterData,
@@ -23495,9 +23512,9 @@ var paymasterWithdrawThunk = createAsyncThunk("withdraw/executePaymasterWithdraw
23495
23512
  maxFeePerGas,
23496
23513
  maxPriorityFeePerGas,
23497
23514
  tailCalls: effectiveTailCalls,
23498
- // When sharing one ephemeral key across multiple deposits each userOp
23499
- // needs a distinct nonce (0, 1, 2 …) on the shared sender.
23500
- nonce: sharedPrivateKey ? BigInt(i) : 0n
23515
+ // When sharing one delegator across multiple deposits each userOp needs
23516
+ // a distinct nonce (0, 1, 2 …) on the shared sender.
23517
+ nonce: sharedDelegator ? BigInt(i) : 0n
23501
23518
  })
23502
23519
  );
23503
23520
  }
@@ -113,6 +113,24 @@ declare enum DepositStrategy {
113
113
  MinFee = 1
114
114
  }
115
115
 
116
+ /**
117
+ * How the batch delegator for a tail-call paymaster withdrawal is produced.
118
+ * Only honored when `tailCalls` are present (see `paymasterWithdrawThunk`).
119
+ *
120
+ * - `deterministic` (default): recoverable. With `path`, the delegator is a
121
+ * classical wallet-path address; without `path`, an ephemeral address keyed by
122
+ * the batch's first deposit.
123
+ * - `random`: a throwaway, unrecoverable EOA — an explicit opt-out of
124
+ * recoverability. Never the default, because in the tail-call path the
125
+ * delegator holds the withdrawn funds.
126
+ */
127
+ type DelegationConfig = {
128
+ mode: 'deterministic';
129
+ path?: string;
130
+ } | {
131
+ mode: 'random';
132
+ };
133
+
116
134
  interface SerializedAuth {
117
135
  address: Address$1;
118
136
  chainId: Hex;
@@ -154,12 +172,6 @@ interface IGenericPaymasterWithdrawalPayload {
154
172
  userOperation: SerializedUserOperation;
155
173
  }
156
174
 
157
- type DelegationConfig = {
158
- mode: 'deterministic';
159
- path?: string;
160
- } | {
161
- mode: 'random';
162
- };
163
175
  type StringAddress = `0x${string}`;
164
176
  interface IPaymasterConfig {
165
177
  paymasterAddress: StringAddress;
@@ -114,6 +114,24 @@ declare enum DepositStrategy {
114
114
  MinFee = 1
115
115
  }
116
116
 
117
+ /**
118
+ * How the batch delegator for a tail-call paymaster withdrawal is produced.
119
+ * Only honored when `tailCalls` are present (see `paymasterWithdrawThunk`).
120
+ *
121
+ * - `deterministic` (default): recoverable. With `path`, the delegator is a
122
+ * classical wallet-path address; without `path`, an ephemeral address keyed by
123
+ * the batch's first deposit.
124
+ * - `random`: a throwaway, unrecoverable EOA — an explicit opt-out of
125
+ * recoverability. Never the default, because in the tail-call path the
126
+ * delegator holds the withdrawn funds.
127
+ */
128
+ type DelegationConfig = {
129
+ mode: 'deterministic';
130
+ path?: string;
131
+ } | {
132
+ mode: 'random';
133
+ };
134
+
117
135
  interface SerializedAuth {
118
136
  address: Address$1;
119
137
  chainId: Hex;
@@ -155,12 +173,6 @@ interface IGenericPaymasterWithdrawalPayload {
155
173
  userOperation: SerializedUserOperation;
156
174
  }
157
175
 
158
- type DelegationConfig = {
159
- mode: 'deterministic';
160
- path?: string;
161
- } | {
162
- mode: 'random';
163
- };
164
176
  type StringAddress = `0x${string}`;
165
177
  interface IPaymasterConfig {
166
178
  paymasterAddress: StringAddress;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kohaku-eth/tornado-cash",
3
- "version": "0.0.2-alpha.15",
3
+ "version": "0.0.2-alpha.16",
4
4
  "description": "Tornado Cash Shielded Pool Kohaku Plugin",
5
5
  "type": "module",
6
6
  "private": false,
@@ -74,9 +74,9 @@
74
74
  "micro-zk-proofs": "0.3.1",
75
75
  "ox": "^0.12.0",
76
76
  "viem": "^2.51.3",
77
+ "@kohaku-eth/plugins": "0.0.1-alpha.11",
77
78
  "@kohaku-eth/mimc-tree": "0.0.3",
78
- "@kohaku-eth/provider": "0.1.0-alpha.8",
79
- "@kohaku-eth/plugins": "0.0.1-alpha.11"
79
+ "@kohaku-eth/provider": "0.1.0-alpha.8"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@types/node": "^24.12.2",