@anywalletkit/wallet-btc 0.3.1 → 0.4.0

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.cts CHANGED
@@ -324,6 +324,7 @@ interface Built {
324
324
  * ownership) before signing AND before broadcast — also for caller-supplied raw hex.
325
325
  */
326
326
  declare class WalletAccountBtc extends WalletAccountReadOnlyBtc implements IWalletAccount {
327
+ #private;
327
328
  /** UTXOs, fee rate and tip height come from the network before signing. */
328
329
  readonly needsNetwork = true;
329
330
  /** Derivation path, or `external` for an external signer. */
@@ -331,8 +332,8 @@ declare class WalletAccountBtc extends WalletAccountReadOnlyBtc implements IWall
331
332
  /** Compressed secp256k1 public key. */
332
333
  readonly publicKey: Uint8Array;
333
334
  readonly addressType: BtcAddressType;
334
- /** The signer that holds the key. */
335
- readonly signer: Signer;
335
+ /** What holds the key: `seed`, `key` (one imported key) or `external`. */
336
+ readonly signerKind: Signer['kind'];
336
337
  private _disposed;
337
338
  private _readOnly;
338
339
  /** `publicKey` is the signer's compressed secp256k1 key (33 bytes); it is needed synchronously for the address. */
@@ -346,20 +347,7 @@ declare class WalletAccountBtc extends WalletAccountReadOnlyBtc implements IWall
346
347
  sign(message: string | Uint8Array): Promise<string>;
347
348
  /** The x-only internal key and its BIP-86 tweak (no script tree) for taproot key-path spends. */
348
349
  private taprootKeys;
349
- /**
350
- * Sign every input through the signer and attach the witness-ready signature:
351
- * p2wpkh → `partialSig` (DER + SIGHASH_ALL), p2tr → `tapKeySig` (64 bytes, DEFAULT).
352
- * btc-signer only computes the sighash and assembles the witness; it never sees a key.
353
- */
354
- private signInputs;
355
350
  private ctx;
356
- private signPlan;
357
- /**
358
- * Plan → sign → measure. The plan's fee assumes 72-byte DER signatures; if the
359
- * signed vsize somehow needs more, the difference comes out of change (never the
360
- * recipient) and the tx is re-signed once.
361
- */
362
- protected signSpend(spend: BtcSpend): Promise<Built>;
363
351
  private checkMax;
364
352
  /**
365
353
  * BIP-143 (p2wpkh) commits each input's signature to that input's CLAIMED amount
package/dist/index.d.ts CHANGED
@@ -324,6 +324,7 @@ interface Built {
324
324
  * ownership) before signing AND before broadcast — also for caller-supplied raw hex.
325
325
  */
326
326
  declare class WalletAccountBtc extends WalletAccountReadOnlyBtc implements IWalletAccount {
327
+ #private;
327
328
  /** UTXOs, fee rate and tip height come from the network before signing. */
328
329
  readonly needsNetwork = true;
329
330
  /** Derivation path, or `external` for an external signer. */
@@ -331,8 +332,8 @@ declare class WalletAccountBtc extends WalletAccountReadOnlyBtc implements IWall
331
332
  /** Compressed secp256k1 public key. */
332
333
  readonly publicKey: Uint8Array;
333
334
  readonly addressType: BtcAddressType;
334
- /** The signer that holds the key. */
335
- readonly signer: Signer;
335
+ /** What holds the key: `seed`, `key` (one imported key) or `external`. */
336
+ readonly signerKind: Signer['kind'];
336
337
  private _disposed;
337
338
  private _readOnly;
338
339
  /** `publicKey` is the signer's compressed secp256k1 key (33 bytes); it is needed synchronously for the address. */
@@ -346,20 +347,7 @@ declare class WalletAccountBtc extends WalletAccountReadOnlyBtc implements IWall
346
347
  sign(message: string | Uint8Array): Promise<string>;
347
348
  /** The x-only internal key and its BIP-86 tweak (no script tree) for taproot key-path spends. */
348
349
  private taprootKeys;
349
- /**
350
- * Sign every input through the signer and attach the witness-ready signature:
351
- * p2wpkh → `partialSig` (DER + SIGHASH_ALL), p2tr → `tapKeySig` (64 bytes, DEFAULT).
352
- * btc-signer only computes the sighash and assembles the witness; it never sees a key.
353
- */
354
- private signInputs;
355
350
  private ctx;
356
- private signPlan;
357
- /**
358
- * Plan → sign → measure. The plan's fee assumes 72-byte DER signatures; if the
359
- * signed vsize somehow needs more, the difference comes out of change (never the
360
- * recipient) and the tx is re-signed once.
361
- */
362
- protected signSpend(spend: BtcSpend): Promise<Built>;
363
351
  private checkMax;
364
352
  /**
365
353
  * BIP-143 (p2wpkh) commits each input's signature to that input's CLAIMED amount
package/dist/index.js CHANGED
@@ -658,8 +658,14 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
658
658
  /** Compressed secp256k1 public key. */
659
659
  publicKey;
660
660
  addressType;
661
- /** The signer that holds the key. */
662
- signer;
661
+ /**
662
+ * The signer that holds the key, in a private field: not a property of the account, so no code
663
+ * the account is handed to (a protocol module, a logger) can reach it and sign around the
664
+ * account's checks (audit 2026-10-29 A1).
665
+ */
666
+ #signer;
667
+ /** What holds the key: `seed`, `key` (one imported key) or `external`. */
668
+ signerKind;
663
669
  _disposed = false;
664
670
  _readOnly;
665
671
  /** `publicKey` is the signer's compressed secp256k1 key (33 bytes); it is needed synchronously for the address. */
@@ -667,9 +673,10 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
667
673
  const type = config.addressType ?? "p2wpkh";
668
674
  super(addressFromPublicKey(publicKey, networkParams(config.network ?? "bitcoin"), type), config);
669
675
  this.addressType = type;
670
- this.signer = requireCurve(signer, "secp256k1");
676
+ this.#signer = requireCurve(signer, "secp256k1");
677
+ this.signerKind = this.#signer.kind;
671
678
  const needed = type === "p2tr" ? "schnorr" : "ecdsa";
672
- if (!this.signer.schemes.includes(needed)) throw new SignerError2(`A ${type} account needs a signer with the '${needed}' scheme (has: ${this.signer.schemes.join(", ")}).`);
679
+ if (!this.#signer.schemes.includes(needed)) throw new SignerError2(`A ${type} account needs a signer with the '${needed}' scheme (has: ${this.#signer.schemes.join(", ")}).`);
673
680
  this.publicKey = publicKey;
674
681
  this.path = path;
675
682
  }
@@ -689,7 +696,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
689
696
  async sign(message) {
690
697
  this.live();
691
698
  if (this.addressType === "p2tr") throw new UnsupportedAddressTypeError("BIP-137 message signatures do not exist for taproot accounts (BIP-322 is not implemented).");
692
- return signMessageWith(message, this.signer, "p2wpkh");
699
+ return signMessageWith(message, this.#signer, "p2wpkh");
693
700
  }
694
701
  /** The x-only internal key and its BIP-86 tweak (no script tree) for taproot key-path spends. */
695
702
  taprootKeys() {
@@ -701,13 +708,13 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
701
708
  * p2wpkh → `partialSig` (DER + SIGHASH_ALL), p2tr → `tapKeySig` (64 bytes, DEFAULT).
702
709
  * btc-signer only computes the sighash and assembles the witness; it never sees a key.
703
710
  */
704
- async signInputs(tx, values) {
711
+ async #signInputs(tx, values) {
705
712
  if (this.addressType === "p2tr") {
706
713
  const { tweak } = this.taprootKeys();
707
714
  const scripts = Array.from({ length: tx.inputsLength }, () => this.scriptPubKey);
708
715
  for (let i = 0; i < tx.inputsLength; i++) {
709
716
  const sighash = tx.preimageWitnessV1(i, scripts, SigHash.DEFAULT, values);
710
- const sig = await this.signer.sign(sighash, { scheme: "schnorr", context: "btc-taproot-keypath", tweak });
717
+ const sig = await this.#signer.sign(sighash, { scheme: "schnorr", context: "btc-taproot-keypath", tweak });
711
718
  tx.updateInput(i, { tapKeySig: sig }, true);
712
719
  }
713
720
  return;
@@ -715,7 +722,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
715
722
  const scriptCode = p2pkh(this.publicKey).script;
716
723
  for (let i = 0; i < tx.inputsLength; i++) {
717
724
  const sighash = tx.preimageWitnessV0(i, scriptCode, SigHash.ALL, values[i]);
718
- const rs = await this.signer.sign(sighash, { scheme: "ecdsa", context: "btc-sighash" });
725
+ const rs = await this.#signer.sign(sighash, { scheme: "ecdsa", context: "btc-sighash" });
719
726
  const der = secp256k12.Signature.fromBytes(rs, "compact").toBytes("der");
720
727
  const sig = new Uint8Array(der.length + 1);
721
728
  sig.set(der, 0);
@@ -726,7 +733,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
726
733
  ctx(intended) {
727
734
  return { network: this._network, ownAddress: this.address, intended, maxFeeRate: this.maxFeeRate };
728
735
  }
729
- async signPlan(plan, to, recipientValue, change, lockTime, rbf) {
736
+ async #signPlan(plan, to, recipientValue, change, lockTime, rbf) {
730
737
  const spend = spendInfo(this.publicKey, this._network, this.addressType);
731
738
  const tx = new Transaction2({ lockTime });
732
739
  const sequence = rbf ? RBF_SEQUENCE : FINAL_SEQUENCE;
@@ -735,7 +742,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
735
742
  }
736
743
  tx.addOutputAddress(to, recipientValue, this._network);
737
744
  if (change > 0n) tx.addOutputAddress(this.address, change, this._network);
738
- await this.signInputs(tx, plan.utxos.map((u) => u.value));
745
+ await this.#signInputs(tx, plan.utxos.map((u) => u.value));
739
746
  tx.finalize();
740
747
  return tx;
741
748
  }
@@ -744,7 +751,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
744
751
  * signed vsize somehow needs more, the difference comes out of change (never the
745
752
  * recipient) and the tx is re-signed once.
746
753
  */
747
- async signSpend(spend) {
754
+ async #signSpend(spend) {
748
755
  this.live();
749
756
  const plan = await this.planSpend(spend);
750
757
  await this.verifyPrevouts(plan.utxos);
@@ -752,7 +759,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
752
759
  const rbf = spend.rbf ?? this._config.rbf ?? true;
753
760
  let change = plan.change;
754
761
  let fee = plan.fee;
755
- let tx = await this.signPlan(plan, spend.to, plan.amount, change, lockTime, rbf);
762
+ let tx = await this.#signPlan(plan, spend.to, plan.amount, change, lockTime, rbf);
756
763
  const required = BigInt(tx.vsize) * plan.feeRate;
757
764
  if (required > fee) {
758
765
  const delta = required - fee;
@@ -765,7 +772,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
765
772
  fee = plan.fee + plan.change;
766
773
  change = 0n;
767
774
  } else fee = required;
768
- tx = await this.signPlan(plan, spend.to, plan.amount, change, lockTime, rbf);
775
+ tx = await this.#signPlan(plan, spend.to, plan.amount, change, lockTime, rbf);
769
776
  if (BigInt(tx.vsize) * plan.feeRate > fee) throw new Error("Fee shortfall after output rebalance.");
770
777
  }
771
778
  const ctx = this.ctx({ to: spend.to, value: plan.amount });
@@ -836,7 +843,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
836
843
  /** "What you sign": the fully resolved spend (signed, NOT broadcast) or a caller's raw hex. */
837
844
  async describeTransaction(tx) {
838
845
  if (typeof tx === "string") return (await this.describeRaw(tx)).desc;
839
- return (await this.signSpend(tx)).desc;
846
+ return (await this.#signSpend(tx)).desc;
840
847
  }
841
848
  /** Sign without broadcasting. Returns the raw transaction hex. Raw hex input gets the checks `sendTransaction` runs (prevouts through the client) and is returned unchanged, never re-signed. */
842
849
  async signTransaction(tx, options = {}) {
@@ -846,7 +853,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
846
853
  this.checkMax(desc.fee, feeLimit(this._config.transactionMaxFee, options), "transaction");
847
854
  return tx;
848
855
  }
849
- const { tx: built, fee } = await this.signSpend(tx);
856
+ const { tx: built, fee } = await this.#signSpend(tx);
850
857
  this.checkMax(fee, feeLimit(this._config.transactionMaxFee, options), "transaction");
851
858
  return built.hex;
852
859
  }
@@ -864,7 +871,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
864
871
  hash = parsed.id;
865
872
  fee = desc.fee;
866
873
  } else {
867
- const built = await this.signSpend(tx);
874
+ const built = await this.#signSpend(tx);
868
875
  rawHex = built.tx.hex;
869
876
  hash = built.tx.id;
870
877
  fee = built.fee;
@@ -877,7 +884,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
877
884
  if (o.token) throw new Error("Tokens are not supported on the bitcoin blockchain.");
878
885
  this.live();
879
886
  const client = this.requireClient("transfer");
880
- const built = await this.signSpend({ to: o.recipient, value: o.amount });
887
+ const built = await this.#signSpend({ to: o.recipient, value: o.amount });
881
888
  this.checkMax(built.fee, feeLimit(this._config.transferMaxFee, writeOptions), "transfer");
882
889
  return { hash: await this.broadcast(client, built.tx.hex, built.tx.id), fee: built.fee };
883
890
  }
@@ -902,7 +909,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
902
909
  }
903
910
  dispose() {
904
911
  this._disposed = true;
905
- this.signer.dispose();
912
+ this.#signer.dispose();
906
913
  }
907
914
  };
908
915