@aztec/wallet-sdk 0.0.1-commit.f2ce05ee → 0.0.1-commit.f5a9928
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/README.md +157 -0
- package/dest/base-wallet/base_wallet.d.ts +87 -42
- package/dest/base-wallet/base_wallet.d.ts.map +1 -1
- package/dest/base-wallet/base_wallet.js +315 -110
- package/dest/base-wallet/get_gas_limits.d.ts +36 -0
- package/dest/base-wallet/get_gas_limits.d.ts.map +1 -0
- package/dest/base-wallet/get_gas_limits.js +55 -0
- package/dest/base-wallet/index.d.ts +3 -2
- package/dest/base-wallet/index.d.ts.map +1 -1
- package/dest/base-wallet/index.js +1 -0
- package/dest/base-wallet/utils.d.ts +8 -4
- package/dest/base-wallet/utils.d.ts.map +1 -1
- package/dest/base-wallet/utils.js +15 -6
- package/dest/crypto.d.ts +39 -1
- package/dest/crypto.d.ts.map +1 -1
- package/dest/crypto.js +88 -0
- package/dest/extension/handlers/background_connection_handler.d.ts +12 -2
- package/dest/extension/handlers/background_connection_handler.d.ts.map +1 -1
- package/dest/extension/handlers/background_connection_handler.js +44 -8
- package/dest/extension/handlers/content_script_connection_handler.d.ts +2 -1
- package/dest/extension/handlers/content_script_connection_handler.d.ts.map +1 -1
- package/dest/extension/handlers/content_script_connection_handler.js +19 -0
- package/dest/extension/handlers/internal_message_types.d.ts +3 -1
- package/dest/extension/handlers/internal_message_types.d.ts.map +1 -1
- package/dest/extension/handlers/internal_message_types.js +3 -1
- package/dest/extension/provider/extension_wallet.d.ts +27 -6
- package/dest/extension/provider/extension_wallet.d.ts.map +1 -1
- package/dest/extension/provider/extension_wallet.js +89 -11
- package/dest/extension/provider/index.d.ts +2 -2
- package/dest/extension/provider/index.d.ts.map +1 -1
- package/dest/iframe/handlers/iframe_connection_handler.d.ts +122 -0
- package/dest/iframe/handlers/iframe_connection_handler.d.ts.map +1 -0
- package/dest/iframe/handlers/iframe_connection_handler.js +239 -0
- package/dest/iframe/handlers/index.d.ts +2 -0
- package/dest/iframe/handlers/index.d.ts.map +1 -0
- package/dest/iframe/handlers/index.js +1 -0
- package/dest/iframe/provider/iframe_discovery.d.ts +25 -0
- package/dest/iframe/provider/iframe_discovery.d.ts.map +1 -0
- package/dest/iframe/provider/iframe_discovery.js +167 -0
- package/dest/iframe/provider/iframe_provider.d.ts +65 -0
- package/dest/iframe/provider/iframe_provider.d.ts.map +1 -0
- package/dest/iframe/provider/iframe_provider.js +257 -0
- package/dest/iframe/provider/iframe_wallet.d.ts +85 -0
- package/dest/iframe/provider/iframe_wallet.d.ts.map +1 -0
- package/dest/iframe/provider/iframe_wallet.js +269 -0
- package/dest/iframe/provider/index.d.ts +4 -0
- package/dest/iframe/provider/index.d.ts.map +1 -0
- package/dest/iframe/provider/index.js +3 -0
- package/dest/manager/types.d.ts +6 -5
- package/dest/manager/types.d.ts.map +1 -1
- package/dest/manager/wallet_manager.d.ts +1 -1
- package/dest/manager/wallet_manager.d.ts.map +1 -1
- package/dest/manager/wallet_manager.js +48 -18
- package/dest/types.d.ts +64 -2
- package/dest/types.d.ts.map +1 -1
- package/dest/types.js +29 -0
- package/package.json +12 -8
- package/src/base-wallet/base_wallet.ts +391 -171
- package/src/base-wallet/get_gas_limits.ts +88 -0
- package/src/base-wallet/index.ts +7 -1
- package/src/base-wallet/utils.ts +24 -5
- package/src/crypto.ts +104 -0
- package/src/extension/handlers/background_connection_handler.ts +42 -9
- package/src/extension/handlers/content_script_connection_handler.ts +18 -0
- package/src/extension/handlers/internal_message_types.ts +2 -0
- package/src/extension/provider/extension_wallet.ts +106 -17
- package/src/extension/provider/index.ts +1 -1
- package/src/iframe/handlers/iframe_connection_handler.ts +341 -0
- package/src/iframe/handlers/index.ts +7 -0
- package/src/iframe/provider/iframe_discovery.ts +185 -0
- package/src/iframe/provider/iframe_provider.ts +331 -0
- package/src/iframe/provider/iframe_wallet.ts +323 -0
- package/src/iframe/provider/index.ts +3 -0
- package/src/manager/types.ts +5 -4
- package/src/manager/wallet_manager.ts +55 -23
- package/src/types.ts +72 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { MAX_PROCESSABLE_L2_GAS, MAX_TX_DA_GAS } from '@aztec/constants';
|
|
2
|
+
import { Gas, type GasUsed } from '@aztec/stdlib/gas';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns suggested total and teardown gas limits for a simulated tx, clamped to the network's per-tx
|
|
6
|
+
* admission limits.
|
|
7
|
+
*
|
|
8
|
+
* The network only admits transactions that declare up to `maxTxGasLimits` per dimension (the
|
|
9
|
+
* node-advertised `txsLimits.gas`). Wallets pass the value read from their own node info, but since node info
|
|
10
|
+
* is remote input it is defensively clamped here to the per-tx protocol maxima so a value above them is never
|
|
11
|
+
* honored. If the simulated usage already exceeds the resulting admission limits the tx can never be included,
|
|
12
|
+
* so this throws a descriptive error instead of returning a limit the node would reject. Otherwise it pads the
|
|
13
|
+
* usage and clamps each dimension to the admission limit.
|
|
14
|
+
* @param gasUsed - The gas actually consumed during simulation.
|
|
15
|
+
* @param maxTxGasLimits - The maximum gas a single tx may declare on this network (the node-advertised `txsLimits.gas`).
|
|
16
|
+
* @param pad - Fraction to pad the suggested gas limits by (as a decimal, e.g. 0.1 for 10%). The effective
|
|
17
|
+
* padding shrinks to zero as usage approaches the network limit, since the network will not admit a higher
|
|
18
|
+
* declared limit regardless of the buffer.
|
|
19
|
+
*/
|
|
20
|
+
export function getGasLimits(
|
|
21
|
+
gasUsed: GasUsed,
|
|
22
|
+
maxTxGasLimits: Gas,
|
|
23
|
+
pad = 0.1,
|
|
24
|
+
): {
|
|
25
|
+
/**
|
|
26
|
+
* Gas limit for the tx, excluding teardown gas
|
|
27
|
+
*/
|
|
28
|
+
gasLimits: Gas;
|
|
29
|
+
/**
|
|
30
|
+
* Gas limit for the teardown phase
|
|
31
|
+
*/
|
|
32
|
+
teardownGasLimits: Gas;
|
|
33
|
+
} {
|
|
34
|
+
const { totalGas, teardownGas } = gasUsed;
|
|
35
|
+
|
|
36
|
+
// `maxTxGasLimits` is the node-advertised admission limit. Node info is remote input, so we defensively
|
|
37
|
+
// clamp to the per-tx protocol maxima so a value above them can never be honored.
|
|
38
|
+
const maxLimits = new Gas(
|
|
39
|
+
Math.min(maxTxGasLimits.daGas, MAX_TX_DA_GAS),
|
|
40
|
+
Math.min(maxTxGasLimits.l2Gas, MAX_PROCESSABLE_L2_GAS),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// The simulated usage must fit within the admission limits, otherwise the tx can never be included.
|
|
44
|
+
if (totalGas.daGas > maxLimits.daGas) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Transaction consumes ${totalGas.daGas} DA gas but the network only admits transactions declaring up to ${maxLimits.daGas} DA gas`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (totalGas.l2Gas > maxLimits.l2Gas) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Transaction consumes ${totalGas.l2Gas} L2 gas but the network only admits transactions declaring up to ${maxLimits.l2Gas} L2 gas`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Pad the limits by the buffer, then cap each dimension at the admission limit so the buffer cannot push a
|
|
56
|
+
// declared limit past what inbound validation accepts. Teardown is part of the total, so clamping it to the
|
|
57
|
+
// admission limit is safe.
|
|
58
|
+
return {
|
|
59
|
+
gasLimits: padGas(totalGas, pad, maxLimits),
|
|
60
|
+
teardownGasLimits: padGas(teardownGas, pad, maxLimits),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Pads each gas dimension, capping it at the network admission limit. */
|
|
65
|
+
function padGas(gas: Gas, pad: number, cap: Gas): Gas {
|
|
66
|
+
const padded = gas.mul(1 + pad);
|
|
67
|
+
return new Gas(Math.min(padded.daGas, cap.daGas), Math.min(padded.l2Gas, cap.l2Gas));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validates that caller-declared gas limits do not exceed the network's per-tx admission limits, throwing a
|
|
72
|
+
* descriptive error per dimension when they do. The node's inbound validation checks declared
|
|
73
|
+
* `gasSettings.gasLimits`, so we mirror that here to surface the rejection locally before the tx is sent.
|
|
74
|
+
* @param gasLimits - The gas limits the transaction will declare.
|
|
75
|
+
* @param maxTxGasLimits - The maximum gas a single tx may declare on this network (the node-advertised `txsLimits.gas`).
|
|
76
|
+
*/
|
|
77
|
+
export function assertGasLimitsWithinNetworkLimits(gasLimits: Gas, maxTxGasLimits: Gas): void {
|
|
78
|
+
if (gasLimits.daGas > maxTxGasLimits.daGas) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Declared DA gas limit (${gasLimits.daGas}) exceeds the maximum this network allows per tx (${maxTxGasLimits.daGas})`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (gasLimits.l2Gas > maxTxGasLimits.l2Gas) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Declared L2 gas limit (${gasLimits.l2Gas}) exceeds the maximum this network allows per tx (${maxTxGasLimits.l2Gas})`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/base-wallet/index.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
BaseWallet,
|
|
3
|
+
type CompleteFeeOptionsConfig,
|
|
4
|
+
type FeeOptions,
|
|
5
|
+
type SimulateViaEntrypointOptions,
|
|
6
|
+
} from './base_wallet.js';
|
|
2
7
|
export { simulateViaNode, buildMergedSimulationResult, extractOptimizablePublicStaticCalls } from './utils.js';
|
|
8
|
+
export { getGasLimits, assertGasLimitsWithinNetworkLimits } from './get_gas_limits.js';
|
package/src/base-wallet/utils.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { AztecNode } from '@aztec/aztec.js/node';
|
|
2
|
+
import { TxSimulationResultWithAppOffset } from '@aztec/aztec.js/wallet';
|
|
2
3
|
import { MAX_ENQUEUED_CALLS_PER_CALL } from '@aztec/constants';
|
|
3
4
|
import type { ChainInfo } from '@aztec/entrypoints/interfaces';
|
|
4
5
|
import { makeTuple } from '@aztec/foundation/array';
|
|
5
6
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
6
7
|
import type { Tuple } from '@aztec/foundation/serialize';
|
|
8
|
+
import type { ContractNameResolver } from '@aztec/pxe/client/lazy';
|
|
9
|
+
import { displayDebugLogs } from '@aztec/pxe/client/lazy';
|
|
7
10
|
import { generateSimulatedProvingResult } from '@aztec/pxe/simulator';
|
|
8
11
|
import { type FunctionCall, FunctionSelector } from '@aztec/stdlib/abi';
|
|
9
12
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
@@ -22,6 +25,7 @@ import {
|
|
|
22
25
|
PrivateCallExecutionResult,
|
|
23
26
|
PrivateExecutionResult,
|
|
24
27
|
PublicSimulationOutput,
|
|
28
|
+
type SimulationOverrides,
|
|
25
29
|
Tx,
|
|
26
30
|
TxContext,
|
|
27
31
|
TxSimulationResult,
|
|
@@ -62,6 +66,8 @@ export function extractOptimizablePublicStaticCalls(payload: ExecutionPayload):
|
|
|
62
66
|
* @param gasSettings - Gas settings for the transaction.
|
|
63
67
|
* @param blockHeader - Block header to use as anchor.
|
|
64
68
|
* @param skipFeeEnforcement - Whether to skip fee enforcement during simulation.
|
|
69
|
+
* @param getContractName - Resolver for contract names (used for debug log display).
|
|
70
|
+
* @param overrides - Optional pre-simulation overrides applied to the ephemeral fork and contract DB.
|
|
65
71
|
* @returns TxSimulationResult with public return values.
|
|
66
72
|
*/
|
|
67
73
|
async function simulateBatchViaNode(
|
|
@@ -72,6 +78,8 @@ async function simulateBatchViaNode(
|
|
|
72
78
|
gasSettings: GasSettings,
|
|
73
79
|
blockHeader: BlockHeader,
|
|
74
80
|
skipFeeEnforcement: boolean,
|
|
81
|
+
getContractName: ContractNameResolver,
|
|
82
|
+
overrides?: SimulationOverrides,
|
|
75
83
|
): Promise<TxSimulationResult> {
|
|
76
84
|
const txContext = new TxContext(chainInfo.chainId, chainInfo.version, gasSettings);
|
|
77
85
|
|
|
@@ -126,6 +134,7 @@ async function simulateBatchViaNode(
|
|
|
126
134
|
const provingResult = await generateSimulatedProvingResult(
|
|
127
135
|
privateResult,
|
|
128
136
|
(_contractAddress: AztecAddress, _functionSelector: FunctionSelector) => Promise.resolve(''),
|
|
137
|
+
node,
|
|
129
138
|
1, // minRevertibleSideEffectCounter
|
|
130
139
|
);
|
|
131
140
|
|
|
@@ -138,12 +147,15 @@ async function simulateBatchViaNode(
|
|
|
138
147
|
publicFunctionCalldata: publicFunctionCalldata,
|
|
139
148
|
});
|
|
140
149
|
|
|
141
|
-
const publicOutput = await node.simulatePublicCalls(tx, skipFeeEnforcement);
|
|
150
|
+
const publicOutput = await node.simulatePublicCalls(tx, skipFeeEnforcement, overrides);
|
|
142
151
|
|
|
143
152
|
if (publicOutput.revertReason) {
|
|
144
153
|
throw publicOutput.revertReason;
|
|
145
154
|
}
|
|
146
155
|
|
|
156
|
+
// Display debug logs from the public simulation.
|
|
157
|
+
await displayDebugLogs(publicOutput.debugLogs, getContractName);
|
|
158
|
+
|
|
147
159
|
return new TxSimulationResult(privateResult, provingResult.publicInputs, publicOutput, undefined);
|
|
148
160
|
}
|
|
149
161
|
|
|
@@ -158,6 +170,8 @@ async function simulateBatchViaNode(
|
|
|
158
170
|
* @param gasSettings - Gas settings for the transaction.
|
|
159
171
|
* @param blockHeader - Block header to use as anchor.
|
|
160
172
|
* @param skipFeeEnforcement - Whether to skip fee enforcement during simulation.
|
|
173
|
+
* @param getContractName - Resolver for contract names (used for debug log display).
|
|
174
|
+
* @param overrides - Optional pre-simulation overrides applied to the ephemeral fork and contract DB.
|
|
161
175
|
* @returns Array of TxSimulationResult, one per batch.
|
|
162
176
|
*/
|
|
163
177
|
export async function simulateViaNode(
|
|
@@ -168,6 +182,8 @@ export async function simulateViaNode(
|
|
|
168
182
|
gasSettings: GasSettings,
|
|
169
183
|
blockHeader: BlockHeader,
|
|
170
184
|
skipFeeEnforcement: boolean = true,
|
|
185
|
+
getContractName: ContractNameResolver,
|
|
186
|
+
overrides?: SimulationOverrides,
|
|
171
187
|
): Promise<TxSimulationResult[]> {
|
|
172
188
|
const batches: FunctionCall[][] = [];
|
|
173
189
|
|
|
@@ -186,6 +202,8 @@ export async function simulateViaNode(
|
|
|
186
202
|
gasSettings,
|
|
187
203
|
blockHeader,
|
|
188
204
|
skipFeeEnforcement,
|
|
205
|
+
getContractName,
|
|
206
|
+
overrides,
|
|
189
207
|
);
|
|
190
208
|
results.push(result);
|
|
191
209
|
}
|
|
@@ -205,13 +223,13 @@ export async function simulateViaNode(
|
|
|
205
223
|
*/
|
|
206
224
|
export function buildMergedSimulationResult(
|
|
207
225
|
optimizedResults: TxSimulationResult[],
|
|
208
|
-
normalResult:
|
|
209
|
-
):
|
|
226
|
+
normalResult: TxSimulationResultWithAppOffset | null,
|
|
227
|
+
): TxSimulationResultWithAppOffset {
|
|
210
228
|
const optimizedReturnValues = optimizedResults.flatMap(r => r.publicOutput?.publicReturnValues ?? []);
|
|
211
229
|
const normalReturnValues = normalResult?.publicOutput?.publicReturnValues ?? [];
|
|
212
230
|
const allReturnValues = [...optimizedReturnValues, ...normalReturnValues];
|
|
213
231
|
|
|
214
|
-
const baseResult = normalResult ?? optimizedResults[0];
|
|
232
|
+
const baseResult: TxSimulationResult = normalResult ?? optimizedResults[0];
|
|
215
233
|
|
|
216
234
|
const mergedPublicOutput: PublicSimulationOutput | undefined = baseResult.publicOutput
|
|
217
235
|
? {
|
|
@@ -220,10 +238,11 @@ export function buildMergedSimulationResult(
|
|
|
220
238
|
}
|
|
221
239
|
: undefined;
|
|
222
240
|
|
|
223
|
-
|
|
241
|
+
const merged = new TxSimulationResult(
|
|
224
242
|
baseResult.privateExecutionResult,
|
|
225
243
|
baseResult.publicInputs,
|
|
226
244
|
mergedPublicOutput,
|
|
227
245
|
normalResult?.stats,
|
|
228
246
|
);
|
|
247
|
+
return TxSimulationResultWithAppOffset.fromResultAndOffset(merged, normalResult?.appCallOffset ?? 0);
|
|
229
248
|
}
|
package/src/crypto.ts
CHANGED
|
@@ -497,3 +497,107 @@ export function hashToEmoji(hash: string, count: number = DEFAULT_EMOJI_GRID_SIZ
|
|
|
497
497
|
}
|
|
498
498
|
return emojis.join('');
|
|
499
499
|
}
|
|
500
|
+
|
|
501
|
+
// ─── Passphrase-based encryption (PBKDF2 + AES-256-GCM) ───────────────────
|
|
502
|
+
|
|
503
|
+
/** Default PBKDF2 iteration count. High to compensate for short PINs (~1-2s on modern hardware). */
|
|
504
|
+
const DEFAULT_PBKDF2_ITERATIONS = 2_000_000;
|
|
505
|
+
const PBKDF2_SALT_BYTES = 16;
|
|
506
|
+
const PBKDF2_IV_BYTES = 12;
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Derives an AES-256-GCM key from a passphrase using PBKDF2-SHA256.
|
|
510
|
+
*
|
|
511
|
+
* @param passphrase - The user-provided passphrase or PIN
|
|
512
|
+
* @param salt - Random salt bytes
|
|
513
|
+
* @param iterations - PBKDF2 iteration count (default: 2,000,000)
|
|
514
|
+
* @returns An AES-256-GCM CryptoKey
|
|
515
|
+
*/
|
|
516
|
+
export async function deriveKeyFromPassphrase(
|
|
517
|
+
passphrase: string,
|
|
518
|
+
salt: Uint8Array,
|
|
519
|
+
iterations: number = DEFAULT_PBKDF2_ITERATIONS,
|
|
520
|
+
): Promise<CryptoKey> {
|
|
521
|
+
const keyMaterial = await crypto.subtle.importKey('raw', new TextEncoder().encode(passphrase), 'PBKDF2', false, [
|
|
522
|
+
'deriveKey',
|
|
523
|
+
]);
|
|
524
|
+
return crypto.subtle.deriveKey(
|
|
525
|
+
{ name: 'PBKDF2', salt: salt as BufferSource, iterations, hash: 'SHA-256' },
|
|
526
|
+
keyMaterial,
|
|
527
|
+
{ name: 'AES-GCM', length: 256 },
|
|
528
|
+
false,
|
|
529
|
+
['encrypt', 'decrypt'],
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Encrypts arbitrary bytes with a passphrase using PBKDF2 + AES-256-GCM.
|
|
535
|
+
*
|
|
536
|
+
* Output layout: `[salt (16)] [iv (12)] [ciphertext (...)]`
|
|
537
|
+
*
|
|
538
|
+
* @param plaintext - Data to encrypt
|
|
539
|
+
* @param passphrase - User passphrase or PIN
|
|
540
|
+
* @param iterations - PBKDF2 iteration count (default: 2,000,000)
|
|
541
|
+
* @returns A Uint8Array containing salt + iv + ciphertext
|
|
542
|
+
*/
|
|
543
|
+
export async function encryptWithPassphrase(
|
|
544
|
+
plaintext: Uint8Array,
|
|
545
|
+
passphrase: string,
|
|
546
|
+
iterations: number = DEFAULT_PBKDF2_ITERATIONS,
|
|
547
|
+
): Promise<Uint8Array> {
|
|
548
|
+
const salt = crypto.getRandomValues(new Uint8Array(PBKDF2_SALT_BYTES));
|
|
549
|
+
const iv = crypto.getRandomValues(new Uint8Array(PBKDF2_IV_BYTES));
|
|
550
|
+
const key = await deriveKeyFromPassphrase(passphrase, salt, iterations);
|
|
551
|
+
const ciphertext = new Uint8Array(
|
|
552
|
+
await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plaintext as BufferSource),
|
|
553
|
+
);
|
|
554
|
+
const result = new Uint8Array(PBKDF2_SALT_BYTES + PBKDF2_IV_BYTES + ciphertext.length);
|
|
555
|
+
result.set(salt, 0);
|
|
556
|
+
result.set(iv, PBKDF2_SALT_BYTES);
|
|
557
|
+
result.set(ciphertext, PBKDF2_SALT_BYTES + PBKDF2_IV_BYTES);
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Decrypts data produced by {@link encryptWithPassphrase}.
|
|
563
|
+
*
|
|
564
|
+
* @param data - The encrypted blob (salt + iv + ciphertext)
|
|
565
|
+
* @param passphrase - The passphrase used during encryption
|
|
566
|
+
* @param iterations - PBKDF2 iteration count (must match encryption)
|
|
567
|
+
* @returns The decrypted plaintext bytes
|
|
568
|
+
* @throws On wrong passphrase (AES-GCM auth tag mismatch)
|
|
569
|
+
*/
|
|
570
|
+
export async function decryptWithPassphrase(
|
|
571
|
+
data: Uint8Array,
|
|
572
|
+
passphrase: string,
|
|
573
|
+
iterations: number = DEFAULT_PBKDF2_ITERATIONS,
|
|
574
|
+
): Promise<Uint8Array> {
|
|
575
|
+
const salt = data.slice(0, PBKDF2_SALT_BYTES);
|
|
576
|
+
const iv = data.slice(PBKDF2_SALT_BYTES, PBKDF2_SALT_BYTES + PBKDF2_IV_BYTES);
|
|
577
|
+
const ciphertext = data.slice(PBKDF2_SALT_BYTES + PBKDF2_IV_BYTES);
|
|
578
|
+
const key = await deriveKeyFromPassphrase(passphrase, salt, iterations);
|
|
579
|
+
return new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext as BufferSource));
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Converts a Uint8Array to a base64 string.
|
|
584
|
+
*/
|
|
585
|
+
export function uint8ToBase64(bytes: Uint8Array): string {
|
|
586
|
+
let binary = '';
|
|
587
|
+
for (const b of bytes) {
|
|
588
|
+
binary += String.fromCharCode(b);
|
|
589
|
+
}
|
|
590
|
+
return btoa(binary);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Converts a base64 string to a Uint8Array.
|
|
595
|
+
*/
|
|
596
|
+
export function base64ToUint8(b64: string): Uint8Array {
|
|
597
|
+
const binary = atob(b64);
|
|
598
|
+
const bytes = new Uint8Array(binary.length);
|
|
599
|
+
for (let i = 0; i < binary.length; i++) {
|
|
600
|
+
bytes[i] = binary.charCodeAt(i);
|
|
601
|
+
}
|
|
602
|
+
return bytes;
|
|
603
|
+
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type WalletMessage,
|
|
18
18
|
WalletMessageType,
|
|
19
19
|
type WalletResponse,
|
|
20
|
+
type WalletSdkLogger,
|
|
20
21
|
} from '../../types.js';
|
|
21
22
|
import {
|
|
22
23
|
type BackgroundMessage,
|
|
@@ -131,6 +132,8 @@ export interface BackgroundConnectionConfig {
|
|
|
131
132
|
walletVersion: string;
|
|
132
133
|
/** Optional wallet icon URL. */
|
|
133
134
|
walletIcon?: string;
|
|
135
|
+
/** Logger used for diagnostics. */
|
|
136
|
+
logger: WalletSdkLogger;
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
/**
|
|
@@ -149,6 +152,7 @@ export interface BackgroundConnectionConfig {
|
|
|
149
152
|
* walletId: 'my-wallet',
|
|
150
153
|
* walletName: 'My Wallet',
|
|
151
154
|
* walletVersion: '1.0.0',
|
|
155
|
+
* logger: console,
|
|
152
156
|
* },
|
|
153
157
|
* {
|
|
154
158
|
* sendToTab: (tabId, message) => browser.tabs.sendMessage(tabId, message),
|
|
@@ -167,12 +171,15 @@ export interface BackgroundConnectionConfig {
|
|
|
167
171
|
export class BackgroundConnectionHandler {
|
|
168
172
|
private pendingDiscoveries = new Map<string, PendingDiscovery>();
|
|
169
173
|
private activeSessions = new Map<string, ActiveSession>();
|
|
174
|
+
private log: WalletSdkLogger;
|
|
170
175
|
|
|
171
176
|
constructor(
|
|
172
177
|
private config: BackgroundConnectionConfig,
|
|
173
178
|
private transport: BackgroundTransport,
|
|
174
179
|
private callbacks: BackgroundConnectionCallbacks = {},
|
|
175
|
-
) {
|
|
180
|
+
) {
|
|
181
|
+
this.log = config.logger;
|
|
182
|
+
}
|
|
176
183
|
|
|
177
184
|
initialize(): void {
|
|
178
185
|
this.transport.addContentListener(this.handleMessage);
|
|
@@ -198,8 +205,8 @@ export class BackgroundConnectionHandler {
|
|
|
198
205
|
break;
|
|
199
206
|
case InternalMessageType.KEY_EXCHANGE_REQUEST:
|
|
200
207
|
if (sessionId) {
|
|
201
|
-
this.handleKeyExchangeRequest(sessionId, content as KeyExchangeRequest).catch(
|
|
202
|
-
|
|
208
|
+
this.handleKeyExchangeRequest(sessionId, content as KeyExchangeRequest).catch(err => {
|
|
209
|
+
this.log.warn('Key exchange failed — session will not be established', { sessionId, err });
|
|
203
210
|
});
|
|
204
211
|
}
|
|
205
212
|
break;
|
|
@@ -213,9 +220,31 @@ export class BackgroundConnectionHandler {
|
|
|
213
220
|
void this.handleEncryptedMessage(sessionId, content as EncryptedPayload);
|
|
214
221
|
}
|
|
215
222
|
break;
|
|
223
|
+
case InternalMessageType.PING:
|
|
224
|
+
if (sessionId) {
|
|
225
|
+
this.handlePing(sessionId);
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
216
228
|
}
|
|
217
229
|
};
|
|
218
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Reply to a dApp PING with a PONG. Used as a liveness probe so the dApp can
|
|
233
|
+
* tell the difference between a slow request and a dead extension.
|
|
234
|
+
* @param sessionId - The session that sent the PING.
|
|
235
|
+
*/
|
|
236
|
+
private handlePing(sessionId: string): void {
|
|
237
|
+
const session = this.activeSessions.get(sessionId);
|
|
238
|
+
if (!session) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
this.transport.sendToTab(session.tabId, {
|
|
242
|
+
origin: MessageOrigin.BACKGROUND,
|
|
243
|
+
type: InternalMessageType.PONG,
|
|
244
|
+
sessionId,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
219
248
|
getWalletInfo(): WalletInfo {
|
|
220
249
|
return {
|
|
221
250
|
id: this.config.walletId,
|
|
@@ -315,8 +344,8 @@ export class BackgroundConnectionHandler {
|
|
|
315
344
|
});
|
|
316
345
|
|
|
317
346
|
this.callbacks.onSessionEstablished?.(session);
|
|
318
|
-
} catch {
|
|
319
|
-
|
|
347
|
+
} catch (err) {
|
|
348
|
+
this.log.warn('Key exchange failed — session will not be established', { sessionId, err });
|
|
320
349
|
}
|
|
321
350
|
}
|
|
322
351
|
|
|
@@ -329,8 +358,8 @@ export class BackgroundConnectionHandler {
|
|
|
329
358
|
try {
|
|
330
359
|
const message = await decrypt<WalletMessage>(session.sharedKey, encrypted);
|
|
331
360
|
this.callbacks.onWalletMessage?.(session, message);
|
|
332
|
-
} catch {
|
|
333
|
-
|
|
361
|
+
} catch (err) {
|
|
362
|
+
this.log.warn('Failed to decrypt incoming wallet message', { sessionId, err });
|
|
334
363
|
}
|
|
335
364
|
}
|
|
336
365
|
|
|
@@ -348,8 +377,12 @@ export class BackgroundConnectionHandler {
|
|
|
348
377
|
sessionId,
|
|
349
378
|
content: encrypted,
|
|
350
379
|
});
|
|
351
|
-
} catch {
|
|
352
|
-
|
|
380
|
+
} catch (err) {
|
|
381
|
+
this.log.error('Failed to encrypt wallet response — response will not be sent', {
|
|
382
|
+
sessionId,
|
|
383
|
+
messageId: response.messageId,
|
|
384
|
+
err,
|
|
385
|
+
});
|
|
353
386
|
}
|
|
354
387
|
}
|
|
355
388
|
|
|
@@ -139,9 +139,20 @@ export class ContentScriptConnectionHandler {
|
|
|
139
139
|
case InternalMessageType.SESSION_DISCONNECTED:
|
|
140
140
|
this.handleSessionDisconnected(sessionId);
|
|
141
141
|
break;
|
|
142
|
+
case InternalMessageType.PONG:
|
|
143
|
+
this.handlePong(sessionId);
|
|
144
|
+
break;
|
|
142
145
|
}
|
|
143
146
|
};
|
|
144
147
|
|
|
148
|
+
private handlePong(sessionId: string): void {
|
|
149
|
+
const connection = this.ports.get(sessionId);
|
|
150
|
+
if (!connection) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
connection.port.postMessage({ type: WalletMessageType.PONG });
|
|
154
|
+
}
|
|
155
|
+
|
|
145
156
|
private handleDiscoveryRequest(request: DiscoveryRequest): void {
|
|
146
157
|
this.transport.sendToBackground({
|
|
147
158
|
origin: MessageOrigin.CONTENT_SCRIPT,
|
|
@@ -178,6 +189,13 @@ export class ContentScriptConnectionHandler {
|
|
|
178
189
|
content: data,
|
|
179
190
|
});
|
|
180
191
|
break;
|
|
192
|
+
case WalletMessageType.PING:
|
|
193
|
+
this.transport.sendToBackground({
|
|
194
|
+
origin: MessageOrigin.CONTENT_SCRIPT,
|
|
195
|
+
type: InternalMessageType.PING,
|
|
196
|
+
sessionId,
|
|
197
|
+
});
|
|
198
|
+
break;
|
|
181
199
|
default:
|
|
182
200
|
this.transport.sendToBackground({
|
|
183
201
|
origin: MessageOrigin.CONTENT_SCRIPT,
|
|
@@ -9,11 +9,13 @@ export const InternalMessageType = {
|
|
|
9
9
|
KEY_EXCHANGE_REQUEST: 'key-exchange-request',
|
|
10
10
|
SECURE_MESSAGE: 'secure-message',
|
|
11
11
|
DISCONNECT_REQUEST: 'disconnect-request',
|
|
12
|
+
PING: 'ping',
|
|
12
13
|
// Background → Content script
|
|
13
14
|
DISCOVERY_APPROVED: 'discovery-approved',
|
|
14
15
|
KEY_EXCHANGE_RESPONSE: 'key-exchange-response',
|
|
15
16
|
SECURE_RESPONSE: 'secure-response',
|
|
16
17
|
SESSION_DISCONNECTED: 'session-disconnected',
|
|
18
|
+
PONG: 'pong',
|
|
17
19
|
} as const;
|
|
18
20
|
|
|
19
21
|
/**
|