@anywalletkit/wallet-zcash 0.4.0 → 0.5.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
@@ -985,7 +985,24 @@ var import_secp256k12 = require("@noble/curves/secp256k1.js");
985
985
 
986
986
  // src/read-only-account.ts
987
987
  var import_core6 = require("@anywalletkit/core");
988
+ function assertSpendShape(spend) {
989
+ const proto = typeof spend === "object" && spend !== null && !Array.isArray(spend) ? Object.getPrototypeOf(spend) : void 0;
990
+ if (proto === void 0 || proto !== null && Object.getPrototypeOf(proto) !== null) {
991
+ 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.");
992
+ }
993
+ }
988
994
  var toBig = (v) => typeof v === "bigint" ? v : BigInt(typeof v === "string" ? v.trim() : Math.round(v));
995
+ function spendValue(v) {
996
+ if (typeof v === "bigint") return v;
997
+ if (typeof v === "number" && Number.isInteger(v)) return BigInt(v);
998
+ if (typeof v === "string" && v.length <= 300 && !/\s/.test(v)) {
999
+ try {
1000
+ return BigInt(v);
1001
+ } catch {
1002
+ }
1003
+ }
1004
+ throw new UnsafeTransactionError("malformed-spend", "A spend value must be a bigint, a whole number or an integer string of at most 300 characters (zatoshis).");
1005
+ }
989
1006
  var WalletAccountReadOnlyZcash = class {
990
1007
  /** The transparent address. */
991
1008
  address;
@@ -1030,8 +1047,9 @@ var WalletAccountReadOnlyZcash = class {
1030
1047
  }
1031
1048
  /** Coin selection for a spend (ZIP-317 fee). Validates the recipient and the dust floor first. */
1032
1049
  async planSpend(spend) {
1050
+ assertSpendShape(spend);
1033
1051
  if (this.scriptKind !== "p2pkh") throw new Error(`Cannot build spends from a ${this.scriptKind} address; only P2PKH (t1\u2026) accounts spend.`);
1034
- const amount = toBig(spend.value);
1052
+ const amount = spendValue(spend.value);
1035
1053
  const recipient = decodeAddress(spend.to, this._network);
1036
1054
  if (amount < DUST_LIMIT) throw new Error(`The amount must be at least the dust limit (${DUST_LIMIT} zatoshis).`);
1037
1055
  const utxos = await this.requireClient("plan a spend").listUnspent(this.address);
@@ -1104,15 +1122,22 @@ var WalletAccountZcash = class _WalletAccountZcash extends WalletAccountReadOnly
1104
1122
  path;
1105
1123
  /** Compressed secp256k1 public key. */
1106
1124
  publicKey;
1107
- /** The signer that holds the key. */
1108
- signer;
1125
+ /**
1126
+ * The signer that holds the key, in a private field: not a property of the account, so no code
1127
+ * the account is handed to (a protocol module, a logger) can reach it and sign around the
1128
+ * account's checks (audit 2026-10-29 A1).
1129
+ */
1130
+ #signer;
1131
+ /** What holds the key: `seed`, `key` (one imported key) or `external`. */
1132
+ signerKind;
1109
1133
  _disposed = false;
1110
1134
  _readOnly;
1111
1135
  /** `publicKey` is the signer's compressed secp256k1 key (33 bytes); it is needed synchronously for the address. */
1112
1136
  constructor(signer, publicKey, path, config = {}) {
1113
1137
  super(addressFromPublicKey(publicKey, config.network ?? "mainnet"), config);
1114
- this.signer = (0, import_core7.requireCurve)(signer, "secp256k1");
1115
- if (!this.signer.schemes.includes("ecdsa")) throw new import_core7.SignerError(`A Zcash account needs a signer with the 'ecdsa' scheme (has: ${this.signer.schemes.join(", ")}).`);
1138
+ this.#signer = (0, import_core7.requireCurve)(signer, "secp256k1");
1139
+ this.signerKind = this.#signer.kind;
1140
+ if (!this.#signer.schemes.includes("ecdsa")) throw new import_core7.SignerError(`A Zcash account needs a signer with the 'ecdsa' scheme (has: ${this.#signer.schemes.join(", ")}).`);
1116
1141
  this.publicKey = publicKey;
1117
1142
  this.path = path;
1118
1143
  }
@@ -1131,7 +1156,7 @@ var WalletAccountZcash = class _WalletAccountZcash extends WalletAccountReadOnly
1131
1156
  /** Zcash message signature (zcashd `signmessage` encoding), base64. */
1132
1157
  async sign(message) {
1133
1158
  this.live();
1134
- return signMessageWith(message, this.signer);
1159
+ return signMessageWith(message, this.#signer);
1135
1160
  }
1136
1161
  ctx(intended) {
1137
1162
  return { network: this._network, ownAddress: this.address, intended, maxFee: this.maxFee };
@@ -1163,7 +1188,7 @@ var WalletAccountZcash = class _WalletAccountZcash extends WalletAccountReadOnly
1163
1188
  async #signInputs(tx, spent) {
1164
1189
  for (let i = 0; i < tx.inputs.length; i++) {
1165
1190
  const digest = sighashAll(tx, i, spent);
1166
- const rs = await this.signer.sign(digest, { scheme: "ecdsa", context: "zcash-sighash" });
1191
+ const rs = await this.#signer.sign(digest, { scheme: "ecdsa", context: "zcash-sighash" });
1167
1192
  const der = import_secp256k12.secp256k1.Signature.fromBytes(rs, "compact").toBytes("der");
1168
1193
  tx.inputs[i].scriptSig = p2pkhScriptSig(der, this.publicKey);
1169
1194
  }
@@ -1278,7 +1303,7 @@ var WalletAccountZcash = class _WalletAccountZcash extends WalletAccountReadOnly
1278
1303
  }
1279
1304
  dispose() {
1280
1305
  this._disposed = true;
1281
- this.signer.dispose();
1306
+ this.#signer.dispose();
1282
1307
  }
1283
1308
  };
1284
1309