@swapkit/wallet-hardware 6.0.0 → 6.0.1

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.
@@ -199,11 +199,55 @@ function tryNormalizeTrezorExtendedPublicKey(xpub: string | undefined, chain: UT
199
199
  }
200
200
  }
201
201
 
202
- function decodeOpReturnData(script: Uint8Array): string | null {
203
- if (script.length < 2 || script[0] !== 0x6a) return null;
204
- const dataLen = script[1];
205
- if (dataLen === undefined || script.length < 2 + dataLen) return null;
206
- return Buffer.from(script.slice(2, 2 + dataLen)).toString("hex");
202
+ async function decodeOpReturnData(script: Uint8Array) {
203
+ const { Script } = await import("@swapkit/utxo-signer");
204
+
205
+ try {
206
+ const [opcode, data] = Script.decode(script);
207
+ const bytes = data === 0 ? new Uint8Array() : data;
208
+ if (opcode !== "RETURN" || !(bytes instanceof Uint8Array)) return null;
209
+
210
+ // Trezor rebuilds the script from the data, so only accept scripts it reproduces byte for byte
211
+ return hex.encode(Script.encode(["RETURN", bytes])) === hex.encode(script) ? hex.encode(bytes) : null;
212
+ } catch {
213
+ return null;
214
+ }
215
+ }
216
+
217
+ async function toTrezorOpReturnOutput(script: Uint8Array, amount: bigint | undefined, chain: Chain) {
218
+ if (amount !== 0n) {
219
+ throw new SwapKitError({
220
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
221
+ info: { chain, error: "OP_RETURN output carrying value cannot be signed" },
222
+ });
223
+ }
224
+
225
+ const opReturnData = await decodeOpReturnData(script);
226
+
227
+ if (opReturnData === null) {
228
+ throw new SwapKitError({
229
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
230
+ info: { chain, error: "Malformed OP_RETURN output cannot be signed" },
231
+ });
232
+ }
233
+
234
+ return { amount: "0", op_return_data: opReturnData, script_type: "PAYTOOPRETURN" as const };
235
+ }
236
+
237
+ async function assertTransactionCarriesMemo(tx: Transaction, memo: string, chain: Chain) {
238
+ if (!memo) return;
239
+
240
+ const expected = hex.encode(new TextEncoder().encode(memo));
241
+
242
+ for (let i = 0; i < tx.outputsLength; i++) {
243
+ const script = tx.getOutput(i).script;
244
+ if (script?.[0] === 0x6a && (await decodeOpReturnData(script)) === expected) return;
245
+ }
246
+
247
+ throw new SwapKitError({
248
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
249
+ info: { chain, error: "Transaction does not carry the provided memo" },
250
+ });
207
251
  }
208
252
 
209
253
  function getScriptType(chain: Chain, derivationPath: DerivationPathArray) {
@@ -327,16 +371,9 @@ async function buildPCZTOutputsForTrezor(pczt: PCZT, address_n: number[], myAddr
327
371
  const output = pczt.getOutput(i);
328
372
  const script = output.scriptPubkey;
329
373
 
330
- if (output.value === 0n && script?.length > 0 && script[0] === 0x6a) {
331
- const opReturnData = decodeOpReturnData(script);
332
- if (opReturnData) {
333
- outputs.push({ amount: "0", op_return_data: opReturnData, script_type: "PAYTOOPRETURN" as const });
334
- continue;
335
- }
336
- throw new SwapKitError({
337
- errorKey: "wallet_trezor_failed_to_sign_transaction",
338
- info: { chain, error: "Malformed OP_RETURN output cannot be signed" },
339
- });
374
+ if (script?.[0] === 0x6a) {
375
+ outputs.push(await toTrezorOpReturnOutput(script, output.value, chain));
376
+ continue;
340
377
  }
341
378
 
342
379
  const outputAddress = await decodeOutputAddress(script);
@@ -411,7 +448,7 @@ function buildZcashTxInputsForTrezor(
411
448
  return inputs;
412
449
  }
413
450
 
414
- function buildZcashTxOutputsForTrezor({
451
+ async function buildZcashTxOutputsForTrezor({
415
452
  addressN,
416
453
  chain,
417
454
  myAddress,
@@ -430,12 +467,8 @@ function buildZcashTxOutputsForTrezor({
430
467
  const outputAddress = tx.getOutputAddress(i, network);
431
468
  const script = output.script;
432
469
 
433
- if (output.amount === 0n && script?.length > 0 && script[0] === 0x6a) {
434
- const opReturnData = decodeOpReturnData(script);
435
- if (opReturnData) {
436
- outputs.push({ amount: "0", op_return_data: opReturnData, script_type: "PAYTOOPRETURN" as const });
437
- continue;
438
- }
470
+ if (script?.[0] === 0x6a) {
471
+ outputs.push(await toTrezorOpReturnOutput(script, output.amount, chain));
439
472
  continue;
440
473
  }
441
474
 
@@ -453,12 +486,11 @@ function buildZcashTxOutputsForTrezor({
453
486
  return outputs;
454
487
  }
455
488
 
456
- function buildUtxoOutputsForTrezor(
489
+ async function buildUtxoOutputsForTrezor(
457
490
  tx: Transaction,
458
491
  network: BTCNetwork,
459
492
  address_n: number[],
460
493
  myAddress: string,
461
- memo: string,
462
494
  chain: Chain,
463
495
  scriptType: { input: string; output: string },
464
496
  toCashAddress: (addr: string) => string,
@@ -469,17 +501,12 @@ function buildUtxoOutputsForTrezor(
469
501
  const output = tx.getOutput(i);
470
502
  const outputAddress = tx.getOutputAddress(i, network);
471
503
 
472
- if (!outputAddress) {
473
- const opReturnData = output.script ? decodeOpReturnData(output.script) : null;
474
- if (opReturnData !== null || memo) {
475
- outputs.push({
476
- amount: "0",
477
- op_return_data: opReturnData ?? Buffer.from(memo).toString("hex"),
478
- script_type: "PAYTOOPRETURN",
479
- });
480
- continue;
481
- }
504
+ if (output.script?.[0] === 0x6a) {
505
+ outputs.push(await toTrezorOpReturnOutput(output.script, output.amount, chain));
506
+ continue;
507
+ }
482
508
 
509
+ if (!outputAddress) {
483
510
  throw new SwapKitError({
484
511
  errorKey: "wallet_trezor_failed_to_sign_transaction",
485
512
  info: { chain, error: "Unable to decode output address from scriptPubkey" },
@@ -637,7 +664,7 @@ async function getTrezorWallet<T extends Chain>({
637
664
  const address_n = hardenDerivationPath(derivationPath);
638
665
 
639
666
  const inputs = buildZcashTxInputsForTrezor(tx, [], address_n, hexEncode);
640
- const outputs = buildZcashTxOutputsForTrezor({
667
+ const outputs = await buildZcashTxOutputsForTrezor({
641
668
  addressN: address_n,
642
669
  chain,
643
670
  myAddress: address,
@@ -779,16 +806,17 @@ async function getTrezorWallet<T extends Chain>({
779
806
  const baseToolbox = getUtxoToolbox(chain, toolboxParams);
780
807
 
781
808
  const signTransaction = async (tx: Transaction, inputs: UTXOType[], memo = "") => {
809
+ await assertTransactionCarriesMemo(tx, memo, chain);
810
+
782
811
  const TrezorConnect = (await import("@trezor/connect-web")).default;
783
812
  const address_n = hardenDerivationPath(derivationPath);
784
813
  const network = getNetworkForChain(chain as UTXOChain);
785
814
 
786
- const outputs = buildUtxoOutputsForTrezor(
815
+ const outputs = await buildUtxoOutputsForTrezor(
787
816
  tx,
788
817
  network,
789
818
  address_n,
790
819
  address,
791
- memo,
792
820
  chain,
793
821
  resolvedScriptType,
794
822
  toCashAddress,
@@ -902,12 +930,11 @@ async function getTrezorWallet<T extends Chain>({
902
930
  });
903
931
  }
904
932
 
905
- const outputs = buildUtxoOutputsForTrezor(
933
+ const outputs = await buildUtxoOutputsForTrezor(
906
934
  tx,
907
935
  network,
908
936
  address_n,
909
937
  address,
910
- "",
911
938
  chain,
912
939
  resolvedScriptType,
913
940
  toCashAddress,
@@ -977,12 +1004,11 @@ async function getTrezorWallet<T extends Chain>({
977
1004
  });
978
1005
  }
979
1006
 
980
- const outputs = buildUtxoOutputsForTrezor(
1007
+ const outputs = await buildUtxoOutputsForTrezor(
981
1008
  tx,
982
1009
  network,
983
1010
  address_n,
984
1011
  address,
985
- "",
986
1012
  chain,
987
1013
  resolvedScriptType,
988
1014
  toCashAddress,
@@ -1020,16 +1046,17 @@ async function getTrezorWallet<T extends Chain>({
1020
1046
  }>,
1021
1047
  memo = "",
1022
1048
  ) => {
1049
+ await assertTransactionCarriesMemo(tx, memo, chain);
1050
+
1023
1051
  const TrezorConnect = (await import("@trezor/connect-web")).default;
1024
1052
  const network = getNetworkForChain(chain as UTXOChain);
1025
1053
  const baseAddressN = hardenDerivationPath(derivationPath.slice(0, 3) as unknown as DerivationPathArray);
1026
1054
 
1027
- const outputs = buildUtxoOutputsForTrezor(
1055
+ const outputs = await buildUtxoOutputsForTrezor(
1028
1056
  tx,
1029
1057
  network,
1030
1058
  baseAddressN,
1031
1059
  address,
1032
- memo,
1033
1060
  chain,
1034
1061
  resolvedScriptType,
1035
1062
  toCashAddress,