@anywalletkit/wallet-btc 0.3.2 → 0.4.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.
package/dist/index.cjs CHANGED
@@ -722,7 +722,24 @@ var purposeFor = (type) => type === "p2tr" ? 86 : 84;
722
722
 
723
723
  // src/read-only-account.ts
724
724
  var import_core8 = require("@anywalletkit/core");
725
+ function assertSpendShape(spend) {
726
+ const proto = typeof spend === "object" && spend !== null && !Array.isArray(spend) ? Object.getPrototypeOf(spend) : void 0;
727
+ if (proto === void 0 || proto !== null && Object.getPrototypeOf(proto) !== null) {
728
+ throw new UnsafeTransactionError("malformed-spend", "A spend must be one plain object ({ to, value, \u2026 }); a list or another kind of object is not signed.");
729
+ }
730
+ }
725
731
  var toBig = (v) => typeof v === "bigint" ? v : BigInt(Math.round(Number(v)));
732
+ function spendValue(v) {
733
+ if (typeof v === "bigint") return v;
734
+ if (typeof v === "number" && Number.isInteger(v)) return BigInt(v);
735
+ if (typeof v === "string" && v.length <= 300 && !/\s/.test(v)) {
736
+ try {
737
+ return BigInt(v);
738
+ } catch {
739
+ }
740
+ }
741
+ throw new UnsafeTransactionError("malformed-spend", "A spend value must be a bigint, a whole number or an integer string of at most 300 characters (sats).");
742
+ }
726
743
  var WalletAccountReadOnlyBtc = class {
727
744
  /** The address. */
728
745
  address;
@@ -788,7 +805,8 @@ var WalletAccountReadOnlyBtc = class {
788
805
  }
789
806
  /** Coin selection for a spend (see coinselect.ts). Validates the recipient and the dust floor first. */
790
807
  async planSpend(spend) {
791
- const amount = toBig(spend.value);
808
+ assertSpendShape(spend);
809
+ const amount = spendValue(spend.value);
792
810
  const recipient = decodeAddress(spend.to, this._network);
793
811
  const floor = dustLimit(recipient.kind);
794
812
  if (amount < floor) throw new Error(`The amount must be at least the dust limit of the recipient's address type (${floor} sat).`);
@@ -885,8 +903,14 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
885
903
  /** Compressed secp256k1 public key. */
886
904
  publicKey;
887
905
  addressType;
888
- /** The signer that holds the key. */
889
- signer;
906
+ /**
907
+ * The signer that holds the key, in a private field: not a property of the account, so no code
908
+ * the account is handed to (a protocol module, a logger) can reach it and sign around the
909
+ * account's checks (audit 2026-10-29 A1).
910
+ */
911
+ #signer;
912
+ /** What holds the key: `seed`, `key` (one imported key) or `external`. */
913
+ signerKind;
890
914
  _disposed = false;
891
915
  _readOnly;
892
916
  /** `publicKey` is the signer's compressed secp256k1 key (33 bytes); it is needed synchronously for the address. */
@@ -894,9 +918,10 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
894
918
  const type = config.addressType ?? "p2wpkh";
895
919
  super(addressFromPublicKey(publicKey, networkParams(config.network ?? "bitcoin"), type), config);
896
920
  this.addressType = type;
897
- this.signer = (0, import_core9.requireCurve)(signer, "secp256k1");
921
+ this.#signer = (0, import_core9.requireCurve)(signer, "secp256k1");
922
+ this.signerKind = this.#signer.kind;
898
923
  const needed = type === "p2tr" ? "schnorr" : "ecdsa";
899
- if (!this.signer.schemes.includes(needed)) throw new import_core9.SignerError(`A ${type} account needs a signer with the '${needed}' scheme (has: ${this.signer.schemes.join(", ")}).`);
924
+ if (!this.#signer.schemes.includes(needed)) throw new import_core9.SignerError(`A ${type} account needs a signer with the '${needed}' scheme (has: ${this.#signer.schemes.join(", ")}).`);
900
925
  this.publicKey = publicKey;
901
926
  this.path = path;
902
927
  }
@@ -916,7 +941,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
916
941
  async sign(message) {
917
942
  this.live();
918
943
  if (this.addressType === "p2tr") throw new UnsupportedAddressTypeError("BIP-137 message signatures do not exist for taproot accounts (BIP-322 is not implemented).");
919
- return signMessageWith(message, this.signer, "p2wpkh");
944
+ return signMessageWith(message, this.#signer, "p2wpkh");
920
945
  }
921
946
  /** The x-only internal key and its BIP-86 tweak (no script tree) for taproot key-path spends. */
922
947
  taprootKeys() {
@@ -934,7 +959,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
934
959
  const scripts = Array.from({ length: tx.inputsLength }, () => this.scriptPubKey);
935
960
  for (let i = 0; i < tx.inputsLength; i++) {
936
961
  const sighash = tx.preimageWitnessV1(i, scripts, import_btc_signer4.SigHash.DEFAULT, values);
937
- const sig = await this.signer.sign(sighash, { scheme: "schnorr", context: "btc-taproot-keypath", tweak });
962
+ const sig = await this.#signer.sign(sighash, { scheme: "schnorr", context: "btc-taproot-keypath", tweak });
938
963
  tx.updateInput(i, { tapKeySig: sig }, true);
939
964
  }
940
965
  return;
@@ -942,7 +967,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
942
967
  const scriptCode = (0, import_btc_signer4.p2pkh)(this.publicKey).script;
943
968
  for (let i = 0; i < tx.inputsLength; i++) {
944
969
  const sighash = tx.preimageWitnessV0(i, scriptCode, import_btc_signer4.SigHash.ALL, values[i]);
945
- const rs = await this.signer.sign(sighash, { scheme: "ecdsa", context: "btc-sighash" });
970
+ const rs = await this.#signer.sign(sighash, { scheme: "ecdsa", context: "btc-sighash" });
946
971
  const der = import_secp256k12.secp256k1.Signature.fromBytes(rs, "compact").toBytes("der");
947
972
  const sig = new Uint8Array(der.length + 1);
948
973
  sig.set(der, 0);
@@ -1129,7 +1154,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
1129
1154
  }
1130
1155
  dispose() {
1131
1156
  this._disposed = true;
1132
- this.signer.dispose();
1157
+ this.#signer.dispose();
1133
1158
  }
1134
1159
  };
1135
1160