@btc-vision/transaction 1.7.31 → 1.8.0-alpha.2

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.
Files changed (64) hide show
  1. package/AUDIT/README.md +9 -0
  2. package/CHANGELOG.md +5 -0
  3. package/README.md +62 -18
  4. package/SECURITY.md +71 -0
  5. package/browser/_version.d.ts +1 -1
  6. package/browser/abi/ABICoder.d.ts +8 -0
  7. package/browser/buffer/BinaryReader.d.ts +16 -1
  8. package/browser/buffer/BinaryWriter.d.ts +11 -1
  9. package/browser/deterministic/ExtendedAddressMap.d.ts +19 -0
  10. package/browser/index.js +1201 -874
  11. package/browser/keypair/Address.d.ts +4 -1
  12. package/browser/mnemonic/Mnemonic.d.ts +1 -1
  13. package/browser/noble-curves.js +1087 -1116
  14. package/browser/noble-hashes.js +25 -25
  15. package/browser/opnet.d.ts +1 -0
  16. package/browser/transaction/browser/WalletNetworks.d.ts +3 -3
  17. package/browser/transaction/browser/types/Unisat.d.ts +2 -2
  18. package/browser/utils/lengths.d.ts +3 -1
  19. package/browser/utils/types.d.ts +3 -0
  20. package/browser/vendors.js +950 -911
  21. package/build/_version.d.ts +1 -1
  22. package/build/_version.js +1 -1
  23. package/build/abi/ABICoder.d.ts +8 -0
  24. package/build/abi/ABICoder.js +32 -0
  25. package/build/buffer/BinaryReader.d.ts +16 -1
  26. package/build/buffer/BinaryReader.js +66 -1
  27. package/build/buffer/BinaryWriter.d.ts +11 -1
  28. package/build/buffer/BinaryWriter.js +66 -1
  29. package/build/deterministic/ExtendedAddressMap.d.ts +19 -0
  30. package/build/deterministic/ExtendedAddressMap.js +87 -0
  31. package/build/keypair/Address.d.ts +4 -1
  32. package/build/keypair/Address.js +48 -13
  33. package/build/mnemonic/Mnemonic.d.ts +1 -1
  34. package/build/mnemonic/Mnemonic.js +2 -2
  35. package/build/opnet.d.ts +1 -0
  36. package/build/opnet.js +1 -0
  37. package/build/transaction/browser/WalletNetworks.d.ts +3 -3
  38. package/build/transaction/browser/WalletNetworks.js +3 -3
  39. package/build/transaction/browser/extensions/UnisatSigner.js +3 -3
  40. package/build/transaction/browser/types/Unisat.d.ts +2 -2
  41. package/build/transaction/builders/MultiSignTransaction.js +2 -2
  42. package/build/transaction/shared/TweakedTransaction.js +3 -3
  43. package/build/tsconfig.build.tsbuildinfo +1 -1
  44. package/build/utils/lengths.d.ts +3 -1
  45. package/build/utils/lengths.js +3 -1
  46. package/build/utils/types.d.ts +3 -0
  47. package/package.json +13 -13
  48. package/src/_version.ts +1 -1
  49. package/src/abi/ABICoder.ts +43 -0
  50. package/src/buffer/BinaryReader.ts +158 -2
  51. package/src/buffer/BinaryWriter.ts +143 -1
  52. package/src/deterministic/ExtendedAddressMap.ts +122 -0
  53. package/src/keypair/Address.ts +79 -14
  54. package/src/mnemonic/Mnemonic.ts +2 -2
  55. package/src/opnet.ts +1 -0
  56. package/src/transaction/browser/WalletNetworks.ts +3 -3
  57. package/src/transaction/browser/extensions/UnisatSigner.ts +3 -3
  58. package/src/transaction/browser/types/Unisat.ts +2 -2
  59. package/src/transaction/builders/MultiSignTransaction.ts +2 -2
  60. package/src/transaction/shared/TweakedTransaction.ts +3 -3
  61. package/src/utils/lengths.ts +3 -1
  62. package/src/utils/types.ts +4 -1
  63. package/test/binary-reader-writer.test.ts +457 -0
  64. package/test/derivePath.test.ts +26 -25
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@btc-vision/transaction",
3
3
  "type": "module",
4
- "version": "1.7.31",
4
+ "version": "1.8.0-alpha.2",
5
5
  "author": "BlobMaster41",
6
6
  "description": "OPNet transaction library allows you to create and sign transactions for the OPNet network.",
7
7
  "engines": {
@@ -148,32 +148,32 @@
148
148
  "prebuild": "npm run check:circular"
149
149
  },
150
150
  "devDependencies": {
151
- "@babel/core": "^7.28.5",
151
+ "@babel/core": "^7.28.6",
152
152
  "@babel/plugin-proposal-class-properties": "^7.18.6",
153
153
  "@babel/plugin-transform-runtime": "^7.28.5",
154
- "@babel/preset-env": "^7.28.5",
154
+ "@babel/preset-env": "^7.28.6",
155
155
  "@babel/preset-flow": "^7.27.1",
156
156
  "@babel/preset-react": "^7.28.5",
157
157
  "@babel/preset-typescript": "^7.28.5",
158
158
  "@rollup/plugin-typescript": "^12.3.0",
159
- "@types/node": "^24.10.0",
159
+ "@types/node": "^25.0.9",
160
160
  "@types/sha.js": "^2.4.4",
161
161
  "@vitejs/plugin-legacy": "^7.2.1",
162
- "@vitest/ui": "^4.0.8",
163
- "eslint": "^9.39.1",
162
+ "@vitest/ui": "^4.0.17",
163
+ "eslint": "^9.39.2",
164
164
  "https-browserify": "^1.0.0",
165
165
  "madge": "^8.0.0",
166
166
  "os-browserify": "^0.3.0",
167
- "prettier": "^3.6.2",
167
+ "prettier": "^3.8.0",
168
168
  "stream-browserify": "^3.0.0",
169
169
  "stream-http": "^3.2.0",
170
170
  "tslib": "^2.8.1",
171
- "typedoc": "^0.28.14",
172
- "typescript-eslint": "^8.46.3",
173
- "vite": "^7.3.0",
171
+ "typedoc": "^0.28.16",
172
+ "typescript-eslint": "^8.53.1",
173
+ "vite": "^7.3.1",
174
174
  "vite-plugin-dts": "^4.5.4",
175
- "vite-plugin-node-polyfills": "^0.24.0",
176
- "vitest": "^4.0.8"
175
+ "vite-plugin-node-polyfills": "^0.25.0",
176
+ "vitest": "^4.0.17"
177
177
  },
178
178
  "dependencies": {
179
179
  "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
@@ -183,7 +183,7 @@
183
183
  "@btc-vision/bitcoin-rpc": "^1.0.6",
184
184
  "@btc-vision/logger": "^1.0.8",
185
185
  "@btc-vision/post-quantum": "^0.5.3",
186
- "@eslint/js": "^9.39.1",
186
+ "@eslint/js": "^9.39.2",
187
187
  "@noble/curves": "^1.9.7",
188
188
  "@noble/secp256k1": "^3.0.0",
189
189
  "assert": "^2.1.0",
package/src/_version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '1.7.31';
1
+ export const version = '1.8.0-alpha.2';
@@ -4,21 +4,40 @@ import { BinaryReader } from '../buffer/BinaryReader.js';
4
4
  import { BufferHelper } from '../utils/BufferHelper.js';
5
5
 
6
6
  export enum ABIDataTypes {
7
+ // Unsigned integers
7
8
  UINT8 = 'UINT8',
8
9
  UINT16 = 'UINT16',
9
10
  UINT32 = 'UINT32',
10
11
  UINT64 = 'UINT64',
11
12
  UINT128 = 'UINT128',
12
13
  UINT256 = 'UINT256',
14
+
15
+ // Signed integers
16
+ INT8 = 'INT8',
17
+ INT16 = 'INT16',
18
+ INT32 = 'INT32',
19
+ INT64 = 'INT64',
13
20
  INT128 = 'INT128',
21
+
22
+ // Basic types
14
23
  BOOL = 'BOOL',
15
24
  ADDRESS = 'ADDRESS',
25
+ EXTENDED_ADDRESS = 'EXTENDED_ADDRESS',
16
26
  STRING = 'STRING',
17
27
  BYTES4 = 'BYTES4',
18
28
  BYTES32 = 'BYTES32',
19
29
  BYTES = 'BYTES',
30
+
31
+ // Tuples/Maps
20
32
  ADDRESS_UINT256_TUPLE = 'ADDRESS_UINT256_TUPLE',
33
+ EXTENDED_ADDRESS_UINT256_TUPLE = 'EXTENDED_ADDRESS_UINT256_TUPLE',
34
+
35
+ // Signatures
36
+ SCHNORR_SIGNATURE = 'SCHNORR_SIGNATURE',
37
+
38
+ // Arrays
21
39
  ARRAY_OF_ADDRESSES = 'ARRAY_OF_ADDRESSES',
40
+ ARRAY_OF_EXTENDED_ADDRESSES = 'ARRAY_OF_EXTENDED_ADDRESSES',
22
41
  ARRAY_OF_UINT256 = 'ARRAY_OF_UINT256',
23
42
  ARRAY_OF_UINT128 = 'ARRAY_OF_UINT128',
24
43
  ARRAY_OF_UINT64 = 'ARRAY_OF_UINT64',
@@ -68,12 +87,33 @@ export class ABICoder {
68
87
  case ABIDataTypes.UINT256:
69
88
  result.push(byteReader.readU256());
70
89
  break;
90
+ case ABIDataTypes.INT8:
91
+ result.push(byteReader.readI8());
92
+ break;
93
+ case ABIDataTypes.INT16:
94
+ result.push(byteReader.readI16());
95
+ break;
96
+ case ABIDataTypes.INT32:
97
+ result.push(byteReader.readI32());
98
+ break;
99
+ case ABIDataTypes.INT64:
100
+ result.push(byteReader.readI64());
101
+ break;
71
102
  case ABIDataTypes.INT128:
72
103
  result.push(byteReader.readI128());
73
104
  break;
105
+ case ABIDataTypes.EXTENDED_ADDRESS:
106
+ result.push(byteReader.readExtendedAddress());
107
+ break;
74
108
  case ABIDataTypes.ADDRESS_UINT256_TUPLE:
75
109
  result.push(byteReader.readAddressValueTuple());
76
110
  break;
111
+ case ABIDataTypes.EXTENDED_ADDRESS_UINT256_TUPLE:
112
+ result.push(byteReader.readExtendedAddressMapU256());
113
+ break;
114
+ case ABIDataTypes.SCHNORR_SIGNATURE:
115
+ result.push(byteReader.readSchnorrSignature());
116
+ break;
77
117
  case ABIDataTypes.BYTES:
78
118
  result.push(byteReader.readBytesWithLength());
79
119
  break;
@@ -83,6 +123,9 @@ export class ABICoder {
83
123
  case ABIDataTypes.ARRAY_OF_ADDRESSES:
84
124
  result.push(byteReader.readAddressArray());
85
125
  break;
126
+ case ABIDataTypes.ARRAY_OF_EXTENDED_ADDRESSES:
127
+ result.push(byteReader.readExtendedAddressArray());
128
+ break;
86
129
  case ABIDataTypes.ARRAY_OF_UINT256:
87
130
  result.push(byteReader.readU256Array());
88
131
  break;
@@ -1,8 +1,15 @@
1
1
  import { AddressMap } from '../deterministic/AddressMap.js';
2
+ import { ExtendedAddressMap } from '../deterministic/ExtendedAddressMap.js';
2
3
  import { Address } from '../keypair/Address.js';
3
4
  import {
4
5
  ADDRESS_BYTE_LENGTH,
6
+ EXTENDED_ADDRESS_BYTE_LENGTH,
5
7
  I128_BYTE_LENGTH,
8
+ I16_BYTE_LENGTH,
9
+ I32_BYTE_LENGTH,
10
+ I64_BYTE_LENGTH,
11
+ I8_BYTE_LENGTH,
12
+ SCHNORR_SIGNATURE_BYTE_LENGTH,
6
13
  U128_BYTE_LENGTH,
7
14
  U16_BYTE_LENGTH,
8
15
  U256_BYTE_LENGTH,
@@ -10,7 +17,20 @@ import {
10
17
  U64_BYTE_LENGTH,
11
18
  U8_BYTE_LENGTH,
12
19
  } from '../utils/lengths.js';
13
- import { BufferLike, Selector, u16, u32, u8 } from '../utils/types.js';
20
+ import { BufferLike, i16, i32, i64, i8, Selector, u16, u32, u8 } from '../utils/types.js';
21
+
22
+ /**
23
+ * Represents a Schnorr signature paired with the signer's Address.
24
+ * This class bundles a 64-byte Schnorr signature with its associated
25
+ * full Address (both MLDSA and tweaked keys), providing a complete signed data structure
26
+ * for Bitcoin Taproot-compatible signatures.
27
+ */
28
+ export interface SchnorrSignature {
29
+ /** The signer's Address containing both MLDSA and tweaked public keys */
30
+ readonly address: Address;
31
+ /** The 64-byte Schnorr signature */
32
+ readonly signature: Uint8Array;
33
+ }
14
34
 
15
35
  export class BinaryReader {
16
36
  private buffer: DataView;
@@ -42,6 +62,10 @@ export class BinaryReader {
42
62
  this.currentOffset = 0;
43
63
  }
44
64
 
65
+ public get byteLength(): number {
66
+ return this.buffer.byteLength;
67
+ }
68
+
45
69
  public length(): number {
46
70
  return this.buffer.byteLength;
47
71
  }
@@ -50,6 +74,53 @@ export class BinaryReader {
50
74
  return this.buffer.byteLength - this.currentOffset;
51
75
  }
52
76
 
77
+ // ------------------- Signed Integer Readers ------------------- //
78
+
79
+ /**
80
+ * Reads a single signed byte (i8).
81
+ */
82
+ public readI8(): i8 {
83
+ this.verifyEnd(this.currentOffset + I8_BYTE_LENGTH);
84
+ const value = this.buffer.getInt8(this.currentOffset);
85
+ this.currentOffset += I8_BYTE_LENGTH;
86
+ return value;
87
+ }
88
+
89
+ /**
90
+ * Reads a signed 16-bit integer. By default, big-endian.
91
+ * @param be - Endianness; true means big-endian (the default).
92
+ */
93
+ public readI16(be: boolean = true): i16 {
94
+ this.verifyEnd(this.currentOffset + I16_BYTE_LENGTH);
95
+ const value = this.buffer.getInt16(this.currentOffset, !be);
96
+ this.currentOffset += I16_BYTE_LENGTH;
97
+ return value;
98
+ }
99
+
100
+ /**
101
+ * Reads a signed 32-bit integer. By default, big-endian.
102
+ * @param be - Endianness; true means big-endian (the default).
103
+ */
104
+ public readI32(be: boolean = true): i32 {
105
+ this.verifyEnd(this.currentOffset + I32_BYTE_LENGTH);
106
+ const value = this.buffer.getInt32(this.currentOffset, !be);
107
+ this.currentOffset += I32_BYTE_LENGTH;
108
+ return value;
109
+ }
110
+
111
+ /**
112
+ * Reads a signed 64-bit integer. By default, big-endian.
113
+ * @param be - Endianness; true means big-endian (the default).
114
+ */
115
+ public readI64(be: boolean = true): i64 {
116
+ this.verifyEnd(this.currentOffset + I64_BYTE_LENGTH);
117
+ const value = this.buffer.getBigInt64(this.currentOffset, !be);
118
+ this.currentOffset += I64_BYTE_LENGTH;
119
+ return value;
120
+ }
121
+
122
+ // ------------------- Unsigned Integer Readers ------------------- //
123
+
53
124
  /**
54
125
  * Reads a single unsigned byte (u8).
55
126
  */
@@ -197,13 +268,62 @@ export class BinaryReader {
197
268
  }
198
269
 
199
270
  /**
200
- * Reads an address.
271
+ * Reads an address (32 bytes MLDSA key hash only).
201
272
  */
202
273
  public readAddress(): Address {
203
274
  const bytes: u8[] = Array.from(this.readBytes(ADDRESS_BYTE_LENGTH));
204
275
  return new Address(bytes);
205
276
  }
206
277
 
278
+ /**
279
+ * Reads the tweaked public key portion (32 bytes) and returns it as a raw Uint8Array.
280
+ * Use this when you only need the tweaked key without the full Address object.
281
+ */
282
+ public readTweakedPublicKey(): Uint8Array {
283
+ this.verifyEnd(this.currentOffset + ADDRESS_BYTE_LENGTH);
284
+ return this.readBytes(ADDRESS_BYTE_LENGTH);
285
+ }
286
+
287
+ /**
288
+ * Reads a full address with both MLDSA key hash and tweaked public key (64 bytes total).
289
+ * Format: [32 bytes tweakedPublicKey][32 bytes MLDSA key hash]
290
+ *
291
+ * This is the equivalent of btc-runtime's readExtendedAddress().
292
+ *
293
+ * @returns An Address instance with both keys set
294
+ */
295
+ public readExtendedAddress(): Address {
296
+ this.verifyEnd(this.currentOffset + EXTENDED_ADDRESS_BYTE_LENGTH);
297
+
298
+ // Read tweaked public key first (32 bytes)
299
+ const tweakedPublicKey: u8[] = Array.from(this.readBytes(ADDRESS_BYTE_LENGTH));
300
+
301
+ // Read MLDSA key hash (32 bytes)
302
+ const mldsaKeyHash: u8[] = Array.from(this.readBytes(ADDRESS_BYTE_LENGTH));
303
+
304
+ return new Address(mldsaKeyHash, tweakedPublicKey);
305
+ }
306
+
307
+ /**
308
+ * Reads a Schnorr signature with its associated full Address.
309
+ * Format: [64 bytes full Address][64 bytes signature]
310
+ *
311
+ * Used for deserializing signed data where both the signer's address
312
+ * and their Schnorr signature are stored together.
313
+ *
314
+ * @returns A SchnorrSignature containing the address and signature
315
+ */
316
+ public readSchnorrSignature(): SchnorrSignature {
317
+ this.verifyEnd(
318
+ this.currentOffset + EXTENDED_ADDRESS_BYTE_LENGTH + SCHNORR_SIGNATURE_BYTE_LENGTH,
319
+ );
320
+
321
+ const address = this.readExtendedAddress();
322
+ const signature = this.readBytes(SCHNORR_SIGNATURE_BYTE_LENGTH);
323
+
324
+ return { address, signature };
325
+ }
326
+
207
327
  /**
208
328
  * Reads bytes written as [u32 length][bytes].
209
329
  * @param maxLength if > 0, enforces an upper bound
@@ -329,6 +449,42 @@ export class BinaryReader {
329
449
  return result;
330
450
  }
331
451
 
452
+ /**
453
+ * Reads an array of full addresses (64 bytes each).
454
+ * Format: [u16 length][FullAddress 0][FullAddress 1]...
455
+ */
456
+ public readExtendedAddressArray(be: boolean = true): Address[] {
457
+ const length = this.readU16(be);
458
+ const result: Address[] = new Array<Address>(length);
459
+ for (let i = 0; i < length; i++) {
460
+ result[i] = this.readExtendedAddress();
461
+ }
462
+ return result;
463
+ }
464
+
465
+ /**
466
+ * Reads a map of full Address -> u256 using the tweaked key for map lookup.
467
+ * Format: [u16 length][FullAddress key][u256 value]...
468
+ *
469
+ * This is the equivalent of btc-runtime's readExtendedAddressMapU256().
470
+ */
471
+ public readExtendedAddressMapU256(be: boolean = true): ExtendedAddressMap<bigint> {
472
+ const length = this.readU16(be);
473
+ const result = new ExtendedAddressMap<bigint>();
474
+
475
+ for (let i = 0; i < length; i++) {
476
+ const address = this.readExtendedAddress();
477
+ const value = this.readU256(be);
478
+
479
+ if (result.has(address)) {
480
+ throw new Error('Duplicate tweaked address found in map');
481
+ }
482
+ result.set(address, value);
483
+ }
484
+
485
+ return result;
486
+ }
487
+
332
488
  // --------------------------------------------------- //
333
489
 
334
490
  public getOffset(): u16 {
@@ -1,9 +1,16 @@
1
1
  import { AddressMap } from '../deterministic/AddressMap.js';
2
+ import { ExtendedAddressMap } from '../deterministic/ExtendedAddressMap.js';
2
3
  import { Address } from '../keypair/Address.js';
3
4
  import { BufferHelper } from '../utils/BufferHelper.js';
4
5
  import {
5
6
  ADDRESS_BYTE_LENGTH,
7
+ EXTENDED_ADDRESS_BYTE_LENGTH,
6
8
  I128_BYTE_LENGTH,
9
+ I16_BYTE_LENGTH,
10
+ I32_BYTE_LENGTH,
11
+ I64_BYTE_LENGTH,
12
+ I8_BYTE_LENGTH,
13
+ SCHNORR_SIGNATURE_BYTE_LENGTH,
7
14
  U128_BYTE_LENGTH,
8
15
  U16_BYTE_LENGTH,
9
16
  U256_BYTE_LENGTH,
@@ -11,7 +18,7 @@ import {
11
18
  U64_BYTE_LENGTH,
12
19
  U8_BYTE_LENGTH,
13
20
  } from '../utils/lengths.js';
14
- import { Selector, u16, u32, u64, u8 } from '../utils/types.js';
21
+ import { i16, i32, i64, i8, Selector, u16, u32, u64, u8 } from '../utils/types.js';
15
22
  import { BinaryReader } from './BinaryReader.js';
16
23
 
17
24
  export class BinaryWriter {
@@ -64,6 +71,56 @@ export class BinaryWriter {
64
71
  this.currentOffset += 8;
65
72
  }
66
73
 
74
+ // ------------------- Signed Integer Writers ------------------- //
75
+
76
+ /**
77
+ * Writes a signed 8-bit integer.
78
+ */
79
+ public writeI8(value: i8): void {
80
+ if (value < -128 || value > 127) throw new Error('i8 value is out of range.');
81
+
82
+ this.allocSafe(I8_BYTE_LENGTH);
83
+ this.buffer.setInt8(this.currentOffset, value);
84
+ this.currentOffset += I8_BYTE_LENGTH;
85
+ }
86
+
87
+ /**
88
+ * Writes a signed 16-bit integer. By default big-endian (be = true).
89
+ */
90
+ public writeI16(value: i16, be: boolean = true): void {
91
+ if (value < -32768 || value > 32767) throw new Error('i16 value is out of range.');
92
+
93
+ this.allocSafe(I16_BYTE_LENGTH);
94
+ this.buffer.setInt16(this.currentOffset, value, !be);
95
+ this.currentOffset += I16_BYTE_LENGTH;
96
+ }
97
+
98
+ /**
99
+ * Writes a signed 32-bit integer. By default big-endian (be = true).
100
+ */
101
+ public writeI32(value: i32, be: boolean = true): void {
102
+ if (value < -2147483648 || value > 2147483647) throw new Error('i32 value is out of range.');
103
+
104
+ this.allocSafe(I32_BYTE_LENGTH);
105
+ this.buffer.setInt32(this.currentOffset, value, !be);
106
+ this.currentOffset += I32_BYTE_LENGTH;
107
+ }
108
+
109
+ /**
110
+ * Writes a signed 64-bit integer. By default big-endian (be = true).
111
+ */
112
+ public writeI64(value: i64, be: boolean = true): void {
113
+ if (value < -9223372036854775808n || value > 9223372036854775807n) {
114
+ throw new Error('i64 value is out of range.');
115
+ }
116
+
117
+ this.allocSafe(I64_BYTE_LENGTH);
118
+ this.buffer.setBigInt64(this.currentOffset, value, !be);
119
+ this.currentOffset += I64_BYTE_LENGTH;
120
+ }
121
+
122
+ // ---------------------------------------------------------------- //
123
+
67
124
  public writeSelector(value: Selector): void {
68
125
  this.writeU32(value, true);
69
126
  }
@@ -173,12 +230,65 @@ export class BinaryWriter {
173
230
  this.writeBytes(bytes);
174
231
  }
175
232
 
233
+ /**
234
+ * Writes an address (32 bytes MLDSA key hash only).
235
+ */
176
236
  public writeAddress(value: Address): void {
177
237
  this.verifyAddress(value);
178
238
 
179
239
  this.writeBytes(value);
180
240
  }
181
241
 
242
+ /**
243
+ * Writes the tweaked public key from an Address (32 bytes).
244
+ * @param value - The Address containing the tweaked public key
245
+ */
246
+ public writeTweakedPublicKey(value: Address): void {
247
+ const tweakedKey = value.tweakedPublicKeyToBuffer();
248
+ this.allocSafe(ADDRESS_BYTE_LENGTH);
249
+ this.writeBytes(tweakedKey);
250
+ }
251
+
252
+ /**
253
+ * Writes a full address with both tweaked public key and MLDSA key hash (64 bytes total).
254
+ * Format: [32 bytes tweakedPublicKey][32 bytes MLDSA key hash]
255
+ *
256
+ * This is the equivalent of btc-runtime's writeExtendedAddress().
257
+ *
258
+ * @param value - The Address containing both keys
259
+ */
260
+ public writeExtendedAddress(value: Address): void {
261
+ this.allocSafe(EXTENDED_ADDRESS_BYTE_LENGTH);
262
+
263
+ // Write tweaked public key first (32 bytes)
264
+ this.writeTweakedPublicKey(value);
265
+
266
+ // Write MLDSA key hash (32 bytes)
267
+ this.writeBytes(value);
268
+ }
269
+
270
+ /**
271
+ * Writes a Schnorr signature with its associated full Address.
272
+ * Format: [64 bytes full Address][64 bytes signature]
273
+ *
274
+ * Used for serializing signed data where both the signer's address
275
+ * and their Schnorr signature need to be stored together.
276
+ *
277
+ * @param address - The signer's Address (with both MLDSA and tweaked keys)
278
+ * @param signature - The 64-byte Schnorr signature
279
+ * @throws {Error} If signature is not exactly 64 bytes
280
+ */
281
+ public writeSchnorrSignature(address: Address, signature: Uint8Array): void {
282
+ if (signature.length !== SCHNORR_SIGNATURE_BYTE_LENGTH) {
283
+ throw new Error(
284
+ `Invalid Schnorr signature length: expected ${SCHNORR_SIGNATURE_BYTE_LENGTH}, got ${signature.length}`,
285
+ );
286
+ }
287
+ this.allocSafe(EXTENDED_ADDRESS_BYTE_LENGTH + SCHNORR_SIGNATURE_BYTE_LENGTH);
288
+ this.writeExtendedAddress(address);
289
+ this.writeBytes(signature);
290
+ }
291
+
182
292
  public getBuffer(clear: boolean = true): Uint8Array {
183
293
  const buf = new Uint8Array(this.buffer.byteLength);
184
294
  for (let i: u32 = 0; i < this.buffer.byteLength; i++) {
@@ -235,6 +345,23 @@ export class BinaryWriter {
235
345
  }
236
346
  }
237
347
 
348
+ /**
349
+ * Writes a map of full Address -> u256 using the tweaked key for map lookup.
350
+ * Format: [u16 length][FullAddress key][u256 value]...
351
+ *
352
+ * This is the equivalent of btc-runtime's writeExtendedAddressMapU256().
353
+ */
354
+ public writeExtendedAddressMapU256(map: ExtendedAddressMap<bigint>, be: boolean = true): void {
355
+ if (map.size > 65535) throw new Error('Map size is too large');
356
+
357
+ this.writeU16(map.size, be);
358
+
359
+ for (const [key, value] of map.entries()) {
360
+ this.writeExtendedAddress(key);
361
+ this.writeU256(value, be);
362
+ }
363
+ }
364
+
238
365
  public writeBytesWithLength(value: Uint8Array): void {
239
366
  this.writeU32(value.length);
240
367
  this.writeBytes(value);
@@ -262,6 +389,21 @@ export class BinaryWriter {
262
389
  }
263
390
  }
264
391
 
392
+ /**
393
+ * Writes an array of full addresses (64 bytes each).
394
+ * Format: [u16 length][FullAddress 0][FullAddress 1]...
395
+ */
396
+ public writeExtendedAddressArray(value: Address[]): void {
397
+ if (value.length > 65535) throw new Error('Array size is too large');
398
+
399
+ this.allocSafe(U16_BYTE_LENGTH + value.length * EXTENDED_ADDRESS_BYTE_LENGTH);
400
+ this.writeU16(value.length);
401
+
402
+ for (let i = 0; i < value.length; i++) {
403
+ this.writeExtendedAddress(value[i]);
404
+ }
405
+ }
406
+
265
407
  public writeU32Array(value: u32[], be: boolean = true): void {
266
408
  if (value.length > 65535) throw new Error('Array size is too large');
267
409
 
@@ -0,0 +1,122 @@
1
+ import { Address } from '../keypair/Address.js';
2
+ import { FastMap } from './FastMap.js';
3
+
4
+ /**
5
+ * A map implementation using Address with both MLDSA and tweaked keys.
6
+ * Uses the tweaked public key for lookup/indexing, but stores the full Address.
7
+ */
8
+ export class ExtendedAddressMap<V> {
9
+ // Store tweaked bigint -> index mapping for fast lookup
10
+ private indexMap: FastMap<bigint, number>;
11
+ // Store actual addresses and values
12
+ private _keys: Address[] = [];
13
+ private _values: V[] = [];
14
+
15
+ constructor(iterable?: ReadonlyArray<readonly [Address, V]> | null) {
16
+ this.indexMap = new FastMap();
17
+
18
+ if (iterable) {
19
+ for (const [key, value] of iterable) {
20
+ this.set(key, value);
21
+ }
22
+ }
23
+ }
24
+
25
+ public get size(): number {
26
+ return this._keys.length;
27
+ }
28
+
29
+ public set(key: Address, value: V): this {
30
+ const keyBigInt = key.tweakedToBigInt();
31
+ const existingIndex = this.indexMap.get(keyBigInt);
32
+
33
+ if (existingIndex !== undefined) {
34
+ // Update existing entry
35
+ this._values[existingIndex] = value;
36
+ } else {
37
+ // Add new entry
38
+ const newIndex = this._keys.length;
39
+ this._keys.push(key);
40
+ this._values.push(value);
41
+ this.indexMap.set(keyBigInt, newIndex);
42
+ }
43
+
44
+ return this;
45
+ }
46
+
47
+ public get(key: Address): V | undefined {
48
+ const keyBigInt = key.tweakedToBigInt();
49
+ const index = this.indexMap.get(keyBigInt);
50
+ if (index === undefined) {
51
+ return undefined;
52
+ }
53
+ return this._values[index];
54
+ }
55
+
56
+ public has(key: Address): boolean {
57
+ return this.indexMap.has(key.tweakedToBigInt());
58
+ }
59
+
60
+ public delete(key: Address): boolean {
61
+ const keyBigInt = key.tweakedToBigInt();
62
+ const index = this.indexMap.get(keyBigInt);
63
+
64
+ if (index === undefined) {
65
+ return false;
66
+ }
67
+
68
+ // Remove from arrays
69
+ this._keys.splice(index, 1);
70
+ this._values.splice(index, 1);
71
+
72
+ // Rebuild index map (indices shifted after splice)
73
+ this.indexMap.clear();
74
+ for (let i = 0; i < this._keys.length; i++) {
75
+ this.indexMap.set(this._keys[i].tweakedToBigInt(), i);
76
+ }
77
+
78
+ return true;
79
+ }
80
+
81
+ public clear(): void {
82
+ this.indexMap.clear();
83
+ this._keys = [];
84
+ this._values = [];
85
+ }
86
+
87
+ public indexOf(address: Address): number {
88
+ const index = this.indexMap.get(address.tweakedToBigInt());
89
+ return index !== undefined ? index : -1;
90
+ }
91
+
92
+ *entries(): IterableIterator<[Address, V]> {
93
+ for (let i = 0; i < this._keys.length; i++) {
94
+ yield [this._keys[i], this._values[i]];
95
+ }
96
+ }
97
+
98
+ *keys(): IterableIterator<Address> {
99
+ for (const key of this._keys) {
100
+ yield key;
101
+ }
102
+ }
103
+
104
+ *values(): IterableIterator<V> {
105
+ for (const value of this._values) {
106
+ yield value;
107
+ }
108
+ }
109
+
110
+ forEach(
111
+ callback: (value: V, key: Address, map: ExtendedAddressMap<V>) => void,
112
+ thisArg?: unknown,
113
+ ): void {
114
+ for (let i = 0; i < this._keys.length; i++) {
115
+ callback.call(thisArg, this._values[i], this._keys[i], this);
116
+ }
117
+ }
118
+
119
+ *[Symbol.iterator](): IterableIterator<[Address, V]> {
120
+ yield* this.entries();
121
+ }
122
+ }