@did-btcr2/method 0.48.0 → 0.50.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.
@@ -29,25 +29,30 @@ export class Identifier {
29
29
  * @returns {string} The new did:btcr2 identifier.
30
30
  */
31
31
  static encode(genesisBytes, options) {
32
- // Deconstruct the options
33
- const { idType, version = 1, network } = options;
34
- // If idType is not a valid value per above, raise invalidDid error.
32
+ // Deconstruct the options, defaulting version to 1 and network to "bitcoin" (matching DidBtcr2.create).
33
+ const { idType, version = 1, network = 'bitcoin' } = options;
34
+ // idType MUST be "KEY" or "EXTERNAL".
35
35
  if (!(idType in IdentifierTypes)) {
36
36
  throw new IdentifierError('Expected "idType" to be "KEY" or "EXTERNAL"', INVALID_DID, { idType });
37
37
  }
38
- // 2. If version is greater than 1, raise invalidDid error.
39
- if (isNaN(version) || version > 1) {
38
+ // The only valid version_number is 1 (which encodes a btcr2_version of 0). Any other value would
39
+ // overflow or corrupt the version nibble, so reject everything except exactly 1. This also rejects
40
+ // NaN and non-number inputs, which are never strictly equal to 1.
41
+ if (version !== 1) {
40
42
  throw new IdentifierError('Expected "version" to be 1', INVALID_DID, { version });
41
43
  }
42
- // 3. If network is not a valid value (bitcoin|signet|regtest|testnet3|testnet4|number), raise invalidDid error.
43
- if (typeof network === 'string' && !(network in BitcoinNetworkNames)) {
44
- throw new IdentifierError('Invalid "network" name', INVALID_DID, { network });
44
+ // network MUST be a known network name. This encoder does not mint custom/numeric networks: the
45
+ // public surface (DidCreateOptions.network) is a string, and a numeric nibble >= 5 would overflow
46
+ // the 4-bit network field and corrupt the version nibble. Custom networks remain decode-only.
47
+ if (typeof network !== 'string') {
48
+ throw new IdentifierError('Expected "network" to be a known network name', INVALID_DID, { network });
45
49
  }
46
- // 4. If network is a number and is outside the range of 1-8, raise invalidDid error.
47
- if (typeof network === 'number' && (network < 0 || network > 8)) {
48
- throw new IdentifierError('Invalid "network" number', INVALID_DID, { network });
50
+ const networkValue = BitcoinNetworkNames[network];
51
+ if (networkValue === undefined) {
52
+ throw new IdentifierError('Invalid "network" name', INVALID_DID, { network });
49
53
  }
50
- // 5. If idType is “key” and genesisBytes is not a valid compressed secp256k1 public key, raise invalidDid error.
54
+ // genesisBytes MUST match the identifier type: a valid compressed secp256k1 public key for KEY,
55
+ // or a 32-byte hash for EXTERNAL. An EXTERNAL DID minted with any other length is unresolvable.
51
56
  if (idType === 'KEY') {
52
57
  try {
53
58
  new CompressedSecp256k1PublicKey(genesisBytes);
@@ -56,47 +61,15 @@ export class Identifier {
56
61
  throw new IdentifierError('Expected "genesisBytes" to be a valid compressed secp256k1 public key', INVALID_DID, { genesisBytes });
57
62
  }
58
63
  }
59
- // 6. Map idType to hrp from the following:
60
- // 6.1 “key” - “k”
61
- // 6.2 “external” - “x”
62
- const hrp = idType === 'KEY' ? 'k' : 'x';
63
- // 7. Create an empty nibbles numeric array.
64
- const nibbles = [];
65
- // 8. Set fCount equal to (version - 1) / 15, rounded down.
66
- const fCount = Math.floor((version - 1) / 15);
67
- // 9. Append hexadecimal F (decimal 15) to nibbles fCount times.
68
- for (let i = 0; i < fCount; i++) {
69
- nibbles.push(15);
70
- }
71
- // 10. Append (version - 1) mod 15 to nibbles.
72
- nibbles.push((version - 1) % 15);
73
- // 11. If network is a string, append the numeric value from the following map to nibbles:
74
- // "bitcoin" - 0
75
- // "signet" - 1
76
- // "regtest" - 2
77
- // "testnet3" - 3
78
- // "testnet4" - 4
79
- // "mutinynet" - 5
80
- if (typeof network === 'string') {
81
- nibbles.push(BitcoinNetworkNames[network]);
82
- }
83
- else if (typeof network === 'number') {
84
- // 12. If network is a number, append network + 11 to nibbles.
85
- nibbles.push(network + 11);
64
+ else if (genesisBytes.length !== 32) {
65
+ throw new IdentifierError('Expected "genesisBytes" to be a 32-byte hash for EXTERNAL identifiers', INVALID_DID, { genesisBytes });
86
66
  }
87
- // 13. If the number of entries in nibbles is odd, append 0.
88
- if (nibbles.length % 2 !== 0) {
89
- nibbles.push(0);
90
- }
91
- // 14. Create a dataBytes byte array from nibbles, where index is from 0 to nibbles.length / 2 - 1 and
92
- // encodingBytes[index] = (nibbles[2 * index] << 4) | nibbles[2 * index + 1].
93
- if (fCount !== 0) {
94
- for (const index in Array.from({ length: (nibbles.length / 2) - 1 })) {
95
- throw new IdentifierError('Not implemented', 'NOT_IMPLEMENTED', { index });
96
- }
97
- }
98
- const dataBytes = new Uint8Array([(nibbles[2 * 0] << 4) | nibbles[2 * 0 + 1], ...genesisBytes]);
99
- // 18. Return identifier.
67
+ // Map idType to its human-readable part: KEY -> "k", EXTERNAL -> "x".
68
+ const hrp = idType === 'KEY' ? 'k' : 'x';
69
+ // Pack btcr2_version (high nibble, = version - 1 = 0) and network_value (low nibble) into the first
70
+ // byte, then append genesisBytes. Bech32m-encode the result.
71
+ const firstByte = ((version - 1) << 4) | networkValue;
72
+ const dataBytes = new Uint8Array([firstByte, ...genesisBytes]);
100
73
  return `did:btcr2:${bech32m.encodeFromBytes(hrp, dataBytes)}`;
101
74
  }
102
75
  /**
@@ -108,104 +81,66 @@ export class Identifier {
108
81
  * @throws {DidErrorCode.MethodNotSupported} if the method is not supported
109
82
  */
110
83
  static decode(identifier) {
111
- // 1. Split identifier into an array of components at the colon : character.
84
+ // 1. Split the identifier into scheme, method, and encoded id at the colon character.
112
85
  const components = identifier.split(':');
113
- // 2. If the length of the components array is not 3, raise invalidDid error.
86
+ // 2. There MUST be exactly three colon-separated components.
114
87
  if (components.length !== 3) {
115
88
  throw new IdentifierError(`Invalid did: ${identifier}`, INVALID_DID, { identifier });
116
89
  }
117
- // Deconstruct the components of the identifier: scheme, method, encoded
118
90
  const [scheme, method, encoded] = components;
119
- // 3. If components[0] is not did”, raise invalidDid error.
91
+ // 3. The scheme MUST be "did".
120
92
  if (scheme !== 'did') {
121
93
  throw new IdentifierError(`Invalid did: ${identifier}`, INVALID_DID, { identifier });
122
94
  }
123
- // 4. If components[1] is not btcr2”, raise methodNotSupported error.
95
+ // 4. The method MUST be "btcr2".
124
96
  if (method !== 'btcr2') {
125
97
  throw new IdentifierError(`Invalid did method: ${method}`, METHOD_NOT_SUPPORTED, { identifier });
126
98
  }
127
- // 5. Set encodedString to components[2].
99
+ // 5. The method-specific id MUST be present.
128
100
  if (!encoded) {
129
101
  throw new IdentifierError(`Invalid method-specific id: ${identifier}`, INVALID_DID, { identifier });
130
102
  }
131
- // 6. Pass encodedString to the Bech32m Decoding algorithm, retrieving hrp and dataBytes.
103
+ // 6. Bech32m-decode the id into its hrp and dataBytes.
132
104
  const { prefix: hrp, bytes: dataBytes } = bech32m.decodeToBytes(encoded);
133
- // 7. If the Bech32m decoding algorithm fails, raise invalidDid error.
105
+ // 7. The hrp MUST be "k" (KEY) or "x" (EXTERNAL).
134
106
  if (!['x', 'k'].includes(hrp)) {
135
107
  throw new IdentifierError(`Invalid hrp: ${hrp}`, INVALID_DID, { identifier });
136
108
  }
137
- if (!dataBytes) {
109
+ // 8. There MUST be at least one byte to read btcr2_version and network_value from.
110
+ if (!dataBytes || dataBytes.length < 1) {
138
111
  throw new IdentifierError(`Failed to decode id: ${encoded}`, INVALID_DID, { identifier });
139
112
  }
140
- // 8. Map hrp to idType from the following:
141
- // “k” - “key”
142
- // “x” - “external”
143
- // other - raise invalidDid error
113
+ // 9. Map hrp to idType.
144
114
  const idType = hrp === 'k' ? 'KEY' : 'EXTERNAL';
145
- // 9. Set version to 1.
146
- let version = 1;
147
- let byteIndex = 0;
148
- // 10. If at any point in the remaining steps there are not enough nibbles to complete the process,
149
- // raise invalidDid error.
150
- let nibblesConsumed = 0;
151
- // 11. Start with the first nibble (the higher nibble of the first byte) of dataBytes.
152
- let currentByte = dataBytes[byteIndex];
153
- let versionNibble = currentByte >>> 4;
154
- // 12. Add the value of the current nibble to version.
155
- while (versionNibble === 0xF) {
156
- // 13. If the value of the nibble is hexadecimal F (decimal 15), advance to the next nibble (the lower nibble of
157
- // the current byte or the higher nibble of the next byte) and return to the previous step.
158
- version += 15;
159
- if (nibblesConsumed % 2 === 0) {
160
- versionNibble = currentByte & 0x0F;
161
- }
162
- else {
163
- currentByte = dataBytes[++byteIndex];
164
- versionNibble = currentByte >>> 4;
165
- }
166
- nibblesConsumed += 1;
167
- // 14. If version is greater than 1, raise invalidDid error.
168
- if (version > 1) {
169
- throw new IdentifierError(`Invalid version: ${version}`, INVALID_DID, { identifier });
170
- }
171
- }
172
- version += versionNibble;
173
- nibblesConsumed += 1;
174
- // 15. Advance to the next nibble and set networkValue to its value.
175
- let networkValue = nibblesConsumed % 2 === 0
176
- ? dataBytes[++byteIndex] >>> 4
177
- : currentByte & 0x0F;
178
- nibblesConsumed += 1;
179
- // 16. Map networkValue to network from the following:
180
- // 0 - "bitcoin"
181
- // 1 - "signet"
182
- // 2 - "regtest"
183
- // 3 - "testnet3"
184
- // 4 - "testnet4"
185
- // 5 - "mutinynet"
186
- // 6-7 - raise invalidDid error
187
- // 8-F - networkValue - 11
188
- let network = BitcoinNetworkNames[networkValue];
189
- if (!network) {
190
- if (networkValue >= 0x8 && networkValue <= 0xF) {
191
- network = networkValue - 11;
192
- }
193
- else {
194
- throw new IdentifierError(`Invalid did: ${identifier}`, INVALID_DID, { identifier });
195
- }
196
- }
197
- // 17. If the number of nibbles consumed is odd:
198
- if (nibblesConsumed % 2 === 1) {
199
- // 17.1 Advance to the next nibble and set fillerNibble to its value.
200
- const fillerNibble = currentByte & 0x0F;
201
- // 17.2 If fillerNibble is not 0, raise invalidDid error.
202
- if (fillerNibble !== 0) {
203
- throw new IdentifierError(`Invalid did: ${identifier}`, INVALID_DID, { identifier });
204
- }
205
- }
206
- // 18. Set genesisBytes to the remaining dataBytes.
207
- const genesisBytes = dataBytes.slice(byteIndex + 1);
208
- // 19. If idType is “key” and genesisBytes is not a valid compressed secp256k1 public key, raise invalidDid error.
115
+ // 10. btcr2_version is the high nibble of the first byte and MUST be 0, which is version_number 1.
116
+ // The version-extension scheme (a leading nibble of 0xF chaining into further bytes) is reserved
117
+ // and not valid under v1, so any non-zero high nibble (0x1 through 0xF) is a malformed or forged
118
+ // identifier and is rejected here. Reading a single flat nibble (rather than looping on 0xF) is
119
+ // what makes this guard actually fire: the previous loop body never ran for a leading nibble of
120
+ // 0x1 through 0xE, silently accepting forged versions.
121
+ const btcr2Version = dataBytes[0] >>> 4;
122
+ if (btcr2Version !== 0) {
123
+ throw new IdentifierError(`Invalid btcr2_version (expected 0): ${btcr2Version}`, INVALID_DID, { identifier });
124
+ }
125
+ const version = 1;
126
+ // 11. network_value is the low nibble of the first byte. 0-5 map to named networks; 12-14 are custom
127
+ // networks (returned as the numeric values 1-3); 6-11 and 15 are reserved/out-of-range and rejected.
128
+ const networkValue = dataBytes[0] & 0x0F;
129
+ const networkName = BitcoinNetworkNames[networkValue];
130
+ let network;
131
+ if (typeof networkName === 'string') {
132
+ network = networkName;
133
+ }
134
+ else if (networkValue >= 12 && networkValue <= 14) {
135
+ network = networkValue - 11;
136
+ }
137
+ else {
138
+ throw new IdentifierError(`Invalid network: ${networkValue}`, INVALID_DID, { identifier });
139
+ }
140
+ // 12. genesisBytes is everything after the first byte.
141
+ const genesisBytes = dataBytes.slice(1);
142
+ // 13. genesisBytes MUST match the identifier type: a valid compressed secp256k1 public key for KEY,
143
+ // or a 32-byte hash for EXTERNAL.
209
144
  if (idType === 'KEY') {
210
145
  try {
211
146
  new CompressedSecp256k1PublicKey(genesisBytes);
@@ -214,7 +149,10 @@ export class Identifier {
214
149
  throw new IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, INVALID_DID, { identifier });
215
150
  }
216
151
  }
217
- // 20. Return idType, version, network, and genesisBytes.
152
+ else if (genesisBytes.length !== 32) {
153
+ throw new IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, INVALID_DID, { identifier });
154
+ }
155
+ // 14. Return idType, hrp, version, network, and genesisBytes.
218
156
  return { idType, hrp, version, network, genesisBytes };
219
157
  }
220
158
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"identifier.js","sourceRoot":"","sources":["../../../src/core/identifier.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"identifier.js","sourceRoot":"","sources":["../../../src/core/identifier.ts"],"names":[],"mappings":"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"}
@@ -138,6 +138,50 @@ export interface BeaconTxPlan {
138
138
  * non-standard and rejected at broadcast with `RPC error -26: scriptpubkey`.
139
139
  */
140
140
  export declare function opReturnScript(signalBytes: Uint8Array): Uint8Array;
141
+ /**
142
+ * Minimum value (sats) a beacon UTXO must exceed for {@link selectSpendableUtxo} to
143
+ * treat it as spendable. This is a fixed, conservative, script-kind-agnostic floor:
144
+ * an output at or below it is too small to be worth spending, so selection discards
145
+ * it in favor of a larger confirmed UTXO. Keeping the floor a constant (rather than
146
+ * deriving it from a fee estimate) keeps selection pure and fee-estimator-independent.
147
+ *
148
+ * The floor is a coarse pre-filter, not the fee-coverage boundary: whether a selected
149
+ * UTXO actually covers the transaction fee is a separate check, enforced against the
150
+ * live {@link FeeEstimator} by the builders' `value <= feeSats` guard
151
+ * ({@link SinglePartyBeacon.buildSinglePartyTx} and {@link buildAggregationBeaconTx}).
152
+ * At the default 5 sat/vB rate that fee (roughly 775 to 1200 sats across the three
153
+ * script kinds) sits above this floor, so a UTXO can clear the dust filter and still
154
+ * be rejected as insufficient; conversely, at a very low fee rate an output near the
155
+ * floor could cover the fee. The floor's job is only to skip trivially small inputs.
156
+ *
157
+ * The value is the standard Bitcoin Core P2PKH dust threshold, the largest of the
158
+ * three singleton beacon script kinds (P2PKH 546, P2TR 330, P2WPKH 294 per
159
+ * {@link DUST_LIMIT_SATS}): a UTXO above it is non-dust under any beacon address kind.
160
+ * Distinct from {@link DUST_LIMIT_SATS}, which sizes the outgoing change output by
161
+ * kind; this bounds the incoming UTXO chosen to fund the transaction.
162
+ */
163
+ export declare const SPENDABLE_DUST_LIMIT_SATS = 546;
164
+ /**
165
+ * Select the beacon UTXO to fund a signal transaction from the set of UTXOs at a
166
+ * beacon address. Pure and deterministic: the same address state always yields the
167
+ * same input, across broadcast retries and across independent resolvers.
168
+ *
169
+ * Filters to confirmed UTXOs (an unconfirmed input is reorg- and RBF-unsafe: a
170
+ * signal built on it can be orphaned or double-spent before it confirms), then drops
171
+ * dust at or below {@link SPENDABLE_DUST_LIMIT_SATS}, then picks the deepest via
172
+ * {@link byDepthThenId}. The did:btcr2 spec does not mandate a selection rule or a
173
+ * confirmation depth (only the security considerations favor deeper confirmations),
174
+ * so this is an implementation policy: prefer safety and reproducibility over
175
+ * spending the newest or largest output.
176
+ *
177
+ * @param utxos UTXOs reported at the beacon address.
178
+ * @param address Beacon address, used only to annotate thrown errors.
179
+ * @returns The confirmed, non-dust, deepest UTXO.
180
+ * @throws {BeaconError} `UNFUNDED_BEACON_ADDRESS` when no UTXOs exist at all;
181
+ * `NO_SPENDABLE_BEACON_UTXO` when UTXOs exist but none are both confirmed and above
182
+ * the dust limit (the message distinguishes all-unconfirmed from all-dust).
183
+ */
184
+ export declare function selectSpendableUtxo(utxos: Array<AddressUtxo>, address?: string): AddressUtxo;
141
185
  /**
142
186
  * Build an aggregation beacon transaction (P2TR key-path spend) ready for MuSig2 signing.
143
187
  * Returns the unsigned Transaction + prev-output metadata that an aggregation service's
@@ -1 +1 @@
1
- {"version":3,"file":"beacon.d.ts","sourceRoot":"","sources":["../../../../src/core/beacon/beacon.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"beacon.d.ts","sourceRoot":"","sources":["../../../../src/core/beacon/beacon.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"identifier.d.ts","sourceRoot":"","sources":["../../../src/core/identifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9F,OAAO,EAAE,4BAA4B,EAAkB,MAAM,oBAAoB,CAAC;AAElF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD;;;;;GAKG;AACH,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACvD,GAAG,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;CACvB;AACD;;;;;;;;;;GAUG;AACH,qBAAa,UAAU;IACrB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,QAAQ,GAAG,aAAa,EAAE,OAAO,EAAE,gBAAgB,GAAG,MAAM;IAuFxF;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa;IA8HhD;;;OAGG;IACH,MAAM,CAAC,QAAQ,IAAI;QAAE,OAAO,EAAE,oBAAoB,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE;IAYjE;;;;;OAKG;IACH,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,4BAA4B;IAW9D;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;CAQ5C"}
1
+ {"version":3,"file":"identifier.d.ts","sourceRoot":"","sources":["../../../src/core/identifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9F,OAAO,EAAE,4BAA4B,EAAkB,MAAM,oBAAoB,CAAC;AAElF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD;;;;;GAKG;AACH,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACvD,GAAG,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;CACvB;AACD;;;;;;;;;;GAUG;AACH,qBAAa,UAAU;IACrB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,QAAQ,GAAG,aAAa,EAAE,OAAO,EAAE,gBAAgB,GAAG,MAAM;IAuDxF;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa;IAsFhD;;;OAGG;IACH,MAAM,CAAC,QAAQ,IAAI;QAAE,OAAO,EAAE,oBAAoB,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE;IAYjE;;;;;OAKG;IACH,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,4BAA4B;IAW9D;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;CAQ5C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@did-btcr2/method",
3
- "version": "0.48.0",
3
+ "version": "0.50.0",
4
4
  "type": "module",
5
5
  "description": "Reference implementation for the did:btcr2 DID method written in TypeScript and JavaScript. did:btcr2 is a censorship resistant DID Method using the Bitcoin blockchain as a Verifiable Data Registry to announce changes to the DID document. This is the core method implementation for the did-btcr2-js monorepo.",
6
6
  "main": "./dist/cjs/index.js",
@@ -77,11 +77,11 @@
77
77
  "canonicalize": "^2.1.0",
78
78
  "dotenv": "^16.6.1",
79
79
  "nostr-tools": "^2.23.3",
80
- "@did-btcr2/bitcoin": "^0.8.0",
81
- "@did-btcr2/smt": "^0.3.0",
82
80
  "@did-btcr2/common": "^9.1.0",
83
81
  "@did-btcr2/keypair": "^0.13.1",
84
- "@did-btcr2/cryptosuite": "^9.0.0"
82
+ "@did-btcr2/smt": "^0.3.0",
83
+ "@did-btcr2/cryptosuite": "^9.0.0",
84
+ "@did-btcr2/bitcoin": "^0.8.0"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@eslint/js": "^9.39.4",
@@ -229,27 +229,97 @@ export function opReturnScript(signalBytes: Uint8Array): Uint8Array {
229
229
  }
230
230
 
231
231
  /**
232
- * Fetch the most recent confirmed UTXO at `bitcoinAddress` + the raw bytes of its
233
- * parent transaction (needed by PSBT inputs). Throws if unfunded.
232
+ * Minimum value (sats) a beacon UTXO must exceed for {@link selectSpendableUtxo} to
233
+ * treat it as spendable. This is a fixed, conservative, script-kind-agnostic floor:
234
+ * an output at or below it is too small to be worth spending, so selection discards
235
+ * it in favor of a larger confirmed UTXO. Keeping the floor a constant (rather than
236
+ * deriving it from a fee estimate) keeps selection pure and fee-estimator-independent.
237
+ *
238
+ * The floor is a coarse pre-filter, not the fee-coverage boundary: whether a selected
239
+ * UTXO actually covers the transaction fee is a separate check, enforced against the
240
+ * live {@link FeeEstimator} by the builders' `value <= feeSats` guard
241
+ * ({@link SinglePartyBeacon.buildSinglePartyTx} and {@link buildAggregationBeaconTx}).
242
+ * At the default 5 sat/vB rate that fee (roughly 775 to 1200 sats across the three
243
+ * script kinds) sits above this floor, so a UTXO can clear the dust filter and still
244
+ * be rejected as insufficient; conversely, at a very low fee rate an output near the
245
+ * floor could cover the fee. The floor's job is only to skip trivially small inputs.
246
+ *
247
+ * The value is the standard Bitcoin Core P2PKH dust threshold, the largest of the
248
+ * three singleton beacon script kinds (P2PKH 546, P2TR 330, P2WPKH 294 per
249
+ * {@link DUST_LIMIT_SATS}): a UTXO above it is non-dust under any beacon address kind.
250
+ * Distinct from {@link DUST_LIMIT_SATS}, which sizes the outgoing change output by
251
+ * kind; this bounds the incoming UTXO chosen to fund the transaction.
234
252
  */
235
- async function fetchSpendableUtxo(
236
- bitcoinAddress: string,
237
- bitcoin: BitcoinConnection,
238
- ): Promise<{ utxo: AddressUtxo; prevTxBytes: Uint8Array }> {
239
- const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
253
+ export const SPENDABLE_DUST_LIMIT_SATS = 546;
254
+
255
+ /**
256
+ * Deterministic ordering for spendable UTXO selection: deepest first (ascending
257
+ * block height, so the most-confirmed UTXO sorts first), tie-broken by `txid` then
258
+ * `vout`. The tie-break makes the winner independent of the order the REST API
259
+ * returns UTXOs in, so retries and independent resolvers converge on the same input.
260
+ */
261
+ function byDepthThenId(a: AddressUtxo, b: AddressUtxo): number {
262
+ if(a.status.block_height !== b.status.block_height) {
263
+ return a.status.block_height - b.status.block_height;
264
+ }
265
+ const txidOrder = a.txid.localeCompare(b.txid);
266
+ return txidOrder !== 0 ? txidOrder : a.vout - b.vout;
267
+ }
268
+
269
+ /**
270
+ * Select the beacon UTXO to fund a signal transaction from the set of UTXOs at a
271
+ * beacon address. Pure and deterministic: the same address state always yields the
272
+ * same input, across broadcast retries and across independent resolvers.
273
+ *
274
+ * Filters to confirmed UTXOs (an unconfirmed input is reorg- and RBF-unsafe: a
275
+ * signal built on it can be orphaned or double-spent before it confirms), then drops
276
+ * dust at or below {@link SPENDABLE_DUST_LIMIT_SATS}, then picks the deepest via
277
+ * {@link byDepthThenId}. The did:btcr2 spec does not mandate a selection rule or a
278
+ * confirmation depth (only the security considerations favor deeper confirmations),
279
+ * so this is an implementation policy: prefer safety and reproducibility over
280
+ * spending the newest or largest output.
281
+ *
282
+ * @param utxos UTXOs reported at the beacon address.
283
+ * @param address Beacon address, used only to annotate thrown errors.
284
+ * @returns The confirmed, non-dust, deepest UTXO.
285
+ * @throws {BeaconError} `UNFUNDED_BEACON_ADDRESS` when no UTXOs exist at all;
286
+ * `NO_SPENDABLE_BEACON_UTXO` when UTXOs exist but none are both confirmed and above
287
+ * the dust limit (the message distinguishes all-unconfirmed from all-dust).
288
+ */
289
+ export function selectSpendableUtxo(utxos: Array<AddressUtxo>, address?: string): AddressUtxo {
240
290
  if(!utxos.length) {
241
291
  throw new BeaconError(
242
292
  'No UTXOs found, please fund address!',
243
- 'UNFUNDED_BEACON_ADDRESS', { address: bitcoinAddress }
293
+ 'UNFUNDED_BEACON_ADDRESS', { address }
244
294
  );
245
295
  }
246
- const utxo = utxos.sort((a, b) => b.status.block_height - a.status.block_height).shift();
247
- if(!utxo) {
296
+ const confirmed = utxos.filter(utxo => utxo.status.confirmed === true);
297
+ const spendable = confirmed.filter(utxo => utxo.value > SPENDABLE_DUST_LIMIT_SATS);
298
+ if(!spendable.length) {
299
+ const reason = confirmed.length === 0
300
+ ? `all ${utxos.length} UTXO(s) are unconfirmed`
301
+ : `all ${confirmed.length} confirmed UTXO(s) are at or below the ${SPENDABLE_DUST_LIMIT_SATS}-sat dust limit`;
248
302
  throw new BeaconError(
249
- 'Beacon bitcoin address unfunded or utxos unconfirmed.',
250
- 'UNFUNDED_BEACON_ADDRESS', { address: bitcoinAddress }
303
+ `No spendable UTXO at beacon address: ${reason}.`,
304
+ 'NO_SPENDABLE_BEACON_UTXO',
305
+ { address, total: utxos.length, confirmed: confirmed.length, dustLimit: SPENDABLE_DUST_LIMIT_SATS }
251
306
  );
252
307
  }
308
+ return [ ...spendable ].sort(byDepthThenId)[0]!;
309
+ }
310
+
311
+ /**
312
+ * Fetch the deepest confirmed, non-dust spendable UTXO at `bitcoinAddress` plus the
313
+ * raw bytes of its parent transaction (needed by PSBT inputs). Selection is delegated
314
+ * to {@link selectSpendableUtxo}; throws {@link BeaconError} when the address is
315
+ * unfunded or has no confirmed, non-dust UTXO.
316
+ */
317
+ async function fetchSpendableUtxo(
318
+ bitcoinAddress: string,
319
+ bitcoin: BitcoinConnection,
320
+ ): Promise<{ utxo: AddressUtxo; prevTxBytes: Uint8Array }> {
321
+ const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
322
+ const utxo = selectSpendableUtxo(utxos, bitcoinAddress);
253
323
  const prevTxHex = await bitcoin.rest.transaction.getHex(utxo.txid);
254
324
  return { utxo, prevTxBytes: hexToBytes(prevTxHex) };
255
325
  }