@btc-vision/transaction 1.7.19 → 1.7.22

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 (92) hide show
  1. package/LICENSE +190 -21
  2. package/README.md +1 -1
  3. package/browser/_version.d.ts +1 -1
  4. package/browser/generators/builders/HashCommitmentGenerator.d.ts +49 -0
  5. package/browser/index.js +1 -1
  6. package/browser/keypair/Address.d.ts +3 -1
  7. package/browser/opnet.d.ts +6 -1
  8. package/browser/signer/AddressRotation.d.ts +12 -0
  9. package/browser/transaction/TransactionFactory.d.ts +14 -0
  10. package/browser/transaction/builders/ConsolidatedInteractionTransaction.d.ts +44 -0
  11. package/browser/transaction/enums/TransactionType.d.ts +3 -1
  12. package/browser/transaction/interfaces/IConsolidatedTransactionParameters.d.ts +31 -0
  13. package/browser/transaction/interfaces/ITransactionParameters.d.ts +2 -0
  14. package/browser/transaction/offline/OfflineTransactionManager.d.ts +69 -0
  15. package/browser/transaction/offline/TransactionReconstructor.d.ts +28 -0
  16. package/browser/transaction/offline/TransactionSerializer.d.ts +50 -0
  17. package/browser/transaction/offline/TransactionStateCapture.d.ts +52 -0
  18. package/browser/transaction/offline/index.d.ts +5 -0
  19. package/browser/transaction/offline/interfaces/ISerializableState.d.ts +62 -0
  20. package/browser/transaction/offline/interfaces/ITypeSpecificData.d.ts +62 -0
  21. package/browser/transaction/offline/interfaces/index.d.ts +2 -0
  22. package/browser/transaction/shared/TweakedTransaction.d.ts +12 -1
  23. package/browser/utxo/interfaces/IUTXO.d.ts +2 -0
  24. package/build/_version.d.ts +1 -1
  25. package/build/_version.js +1 -1
  26. package/build/generators/builders/HashCommitmentGenerator.d.ts +49 -0
  27. package/build/generators/builders/HashCommitmentGenerator.js +229 -0
  28. package/build/keypair/Address.d.ts +3 -1
  29. package/build/keypair/Address.js +87 -54
  30. package/build/opnet.d.ts +6 -1
  31. package/build/opnet.js +6 -1
  32. package/build/signer/AddressRotation.d.ts +12 -0
  33. package/build/signer/AddressRotation.js +16 -0
  34. package/build/transaction/TransactionFactory.d.ts +14 -0
  35. package/build/transaction/TransactionFactory.js +36 -0
  36. package/build/transaction/builders/ConsolidatedInteractionTransaction.d.ts +44 -0
  37. package/build/transaction/builders/ConsolidatedInteractionTransaction.js +259 -0
  38. package/build/transaction/builders/TransactionBuilder.js +2 -0
  39. package/build/transaction/enums/TransactionType.d.ts +3 -1
  40. package/build/transaction/enums/TransactionType.js +2 -0
  41. package/build/transaction/interfaces/IConsolidatedTransactionParameters.d.ts +31 -0
  42. package/build/transaction/interfaces/IConsolidatedTransactionParameters.js +1 -0
  43. package/build/transaction/interfaces/ITransactionParameters.d.ts +2 -0
  44. package/build/transaction/offline/OfflineTransactionManager.d.ts +69 -0
  45. package/build/transaction/offline/OfflineTransactionManager.js +255 -0
  46. package/build/transaction/offline/TransactionReconstructor.d.ts +28 -0
  47. package/build/transaction/offline/TransactionReconstructor.js +243 -0
  48. package/build/transaction/offline/TransactionSerializer.d.ts +50 -0
  49. package/build/transaction/offline/TransactionSerializer.js +700 -0
  50. package/build/transaction/offline/TransactionStateCapture.d.ts +52 -0
  51. package/build/transaction/offline/TransactionStateCapture.js +275 -0
  52. package/build/transaction/offline/index.d.ts +5 -0
  53. package/build/transaction/offline/index.js +5 -0
  54. package/build/transaction/offline/interfaces/ISerializableState.d.ts +62 -0
  55. package/build/transaction/offline/interfaces/ISerializableState.js +2 -0
  56. package/build/transaction/offline/interfaces/ITypeSpecificData.d.ts +62 -0
  57. package/build/transaction/offline/interfaces/ITypeSpecificData.js +19 -0
  58. package/build/transaction/offline/interfaces/index.d.ts +2 -0
  59. package/build/transaction/offline/interfaces/index.js +2 -0
  60. package/build/transaction/shared/TweakedTransaction.d.ts +12 -1
  61. package/build/transaction/shared/TweakedTransaction.js +75 -8
  62. package/build/utxo/interfaces/IUTXO.d.ts +2 -0
  63. package/documentation/README.md +5 -0
  64. package/documentation/offline-transaction-signing.md +650 -0
  65. package/documentation/transaction-building.md +603 -0
  66. package/package.json +2 -2
  67. package/src/_version.ts +1 -1
  68. package/src/generators/builders/HashCommitmentGenerator.ts +495 -0
  69. package/src/keypair/Address.ts +123 -70
  70. package/src/opnet.ts +8 -1
  71. package/src/signer/AddressRotation.ts +72 -0
  72. package/src/transaction/TransactionFactory.ts +90 -0
  73. package/src/transaction/builders/CancelTransaction.ts +4 -2
  74. package/src/transaction/builders/ConsolidatedInteractionTransaction.ts +568 -0
  75. package/src/transaction/builders/CustomScriptTransaction.ts +4 -2
  76. package/src/transaction/builders/MultiSignTransaction.ts +4 -2
  77. package/src/transaction/builders/TransactionBuilder.ts +8 -2
  78. package/src/transaction/enums/TransactionType.ts +2 -0
  79. package/src/transaction/interfaces/IConsolidatedTransactionParameters.ts +78 -0
  80. package/src/transaction/interfaces/ITransactionParameters.ts +8 -0
  81. package/src/transaction/offline/OfflineTransactionManager.ts +630 -0
  82. package/src/transaction/offline/TransactionReconstructor.ts +402 -0
  83. package/src/transaction/offline/TransactionSerializer.ts +920 -0
  84. package/src/transaction/offline/TransactionStateCapture.ts +469 -0
  85. package/src/transaction/offline/index.ts +8 -0
  86. package/src/transaction/offline/interfaces/ISerializableState.ts +141 -0
  87. package/src/transaction/offline/interfaces/ITypeSpecificData.ts +172 -0
  88. package/src/transaction/offline/interfaces/index.ts +2 -0
  89. package/src/transaction/shared/TweakedTransaction.ts +156 -9
  90. package/src/utxo/interfaces/IUTXO.ts +8 -0
  91. package/test/address-rotation.test.ts +553 -0
  92. package/test/offline-transaction.test.ts +2065 -0
@@ -0,0 +1,920 @@
1
+ import { createHash } from 'crypto';
2
+ import { BinaryWriter } from '../../buffer/BinaryWriter.js';
3
+ import { BinaryReader } from '../../buffer/BinaryReader.js';
4
+ import {
5
+ ISerializableTransactionState,
6
+ PrecomputedData,
7
+ SERIALIZATION_FORMAT_VERSION,
8
+ SERIALIZATION_MAGIC_BYTE,
9
+ SerializationHeader,
10
+ SerializedBaseParams,
11
+ SerializedOutput,
12
+ SerializedSignerMapping,
13
+ SerializedUTXO,
14
+ } from './interfaces/ISerializableState.js';
15
+ import {
16
+ CancelSpecificData,
17
+ CustomScriptSpecificData,
18
+ DeploymentSpecificData,
19
+ FundingSpecificData,
20
+ InteractionSpecificData,
21
+ MultiSigSpecificData,
22
+ SerializedLoadedStorage,
23
+ SerializedScriptElement,
24
+ TypeSpecificData,
25
+ } from './interfaces/ITypeSpecificData.js';
26
+ import { TransactionType } from '../enums/TransactionType.js';
27
+ import { RawChallenge, RawChallengeVerification, } from '../../epoch/interfaces/IChallengeSolution.js';
28
+
29
+ /**
30
+ * Serializes and deserializes transaction state for offline signing.
31
+ * Uses binary format for compact size.
32
+ */
33
+ export class TransactionSerializer {
34
+ /**
35
+ * Serialize transaction state to binary format
36
+ * @param state - The transaction state to serialize
37
+ * @returns Buffer containing serialized state with checksum
38
+ */
39
+ public static serialize(state: ISerializableTransactionState): Buffer {
40
+ const writer = new BinaryWriter();
41
+
42
+ // Write header
43
+ this.writeHeader(writer, state.header);
44
+
45
+ // Write base params
46
+ this.writeBaseParams(writer, state.baseParams);
47
+
48
+ // Write UTXOs
49
+ this.writeUTXOArray(writer, state.utxos);
50
+ this.writeUTXOArray(writer, state.optionalInputs);
51
+
52
+ // Write optional outputs
53
+ this.writeOutputArray(writer, state.optionalOutputs);
54
+
55
+ // Write signer mappings
56
+ writer.writeBoolean(state.addressRotationEnabled);
57
+ this.writeSignerMappings(writer, state.signerMappings);
58
+
59
+ // Write type-specific data
60
+ this.writeTypeSpecificData(writer, state.typeSpecificData);
61
+
62
+ // Write precomputed data
63
+ this.writePrecomputedData(writer, state.precomputedData);
64
+
65
+ // Get buffer and calculate checksum
66
+ const dataBuffer = Buffer.from(writer.getBuffer());
67
+ const checksum = this.calculateChecksum(dataBuffer);
68
+
69
+ return Buffer.concat([dataBuffer, checksum]);
70
+ }
71
+
72
+ /**
73
+ * Deserialize binary format to transaction state
74
+ * @param data - Buffer containing serialized state
75
+ * @returns Deserialized transaction state
76
+ * @throws Error if checksum validation fails or format is invalid
77
+ */
78
+ public static deserialize(data: Buffer): ISerializableTransactionState {
79
+ // Verify checksum (last 32 bytes)
80
+ if (data.length < 32) {
81
+ throw new Error('Invalid serialized data: too short');
82
+ }
83
+
84
+ const checksum = data.subarray(-32);
85
+ const payload = data.subarray(0, -32);
86
+ const expectedChecksum = this.calculateChecksum(payload);
87
+
88
+ if (!checksum.equals(expectedChecksum)) {
89
+ throw new Error('Invalid checksum - data may be corrupted');
90
+ }
91
+
92
+ const reader = new BinaryReader(payload);
93
+
94
+ // Read header
95
+ const header = this.readHeader(reader);
96
+
97
+ // Verify format version
98
+ if (header.formatVersion > SERIALIZATION_FORMAT_VERSION) {
99
+ throw new Error(`Unsupported format version: ${header.formatVersion}`);
100
+ }
101
+
102
+ // Read base params
103
+ const baseParams = this.readBaseParams(reader);
104
+
105
+ // Read UTXOs
106
+ const utxos = this.readUTXOArray(reader);
107
+ const optionalInputs = this.readUTXOArray(reader);
108
+
109
+ // Read optional outputs
110
+ const optionalOutputs = this.readOutputArray(reader);
111
+
112
+ // Read signer mappings
113
+ const addressRotationEnabled = reader.readBoolean();
114
+ const signerMappings = this.readSignerMappings(reader);
115
+
116
+ // Read type-specific data
117
+ const typeSpecificData = this.readTypeSpecificData(reader, header.transactionType);
118
+
119
+ // Read precomputed data
120
+ const precomputedData = this.readPrecomputedData(reader);
121
+
122
+ return {
123
+ header,
124
+ baseParams,
125
+ utxos,
126
+ optionalInputs,
127
+ optionalOutputs,
128
+ addressRotationEnabled,
129
+ signerMappings,
130
+ typeSpecificData,
131
+ precomputedData,
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Export state as base64 string (for transport)
137
+ * @param state - Transaction state to export
138
+ * @returns Base64-encoded string
139
+ */
140
+ public static toBase64(state: ISerializableTransactionState): string {
141
+ return this.serialize(state).toString('base64');
142
+ }
143
+
144
+ /**
145
+ * Import state from base64 string
146
+ * @param base64 - Base64-encoded state
147
+ * @returns Deserialized transaction state
148
+ */
149
+ public static fromBase64(base64: string): ISerializableTransactionState {
150
+ return this.deserialize(Buffer.from(base64, 'base64'));
151
+ }
152
+
153
+ /**
154
+ * Export state as hex string
155
+ * @param state - Transaction state to export
156
+ * @returns Hex-encoded string
157
+ */
158
+ public static toHex(state: ISerializableTransactionState): string {
159
+ return this.serialize(state).toString('hex');
160
+ }
161
+
162
+ /**
163
+ * Import state from hex string
164
+ * @param hex - Hex-encoded state
165
+ * @returns Deserialized transaction state
166
+ */
167
+ public static fromHex(hex: string): ISerializableTransactionState {
168
+ return this.deserialize(Buffer.from(hex, 'hex'));
169
+ }
170
+
171
+ private static writeHeader(writer: BinaryWriter, header: SerializationHeader): void {
172
+ writer.writeU8(SERIALIZATION_MAGIC_BYTE);
173
+ writer.writeU8(header.formatVersion);
174
+ writer.writeU8(header.consensusVersion);
175
+ writer.writeU8(header.transactionType);
176
+ writer.writeU32(header.chainId);
177
+ writer.writeU64(BigInt(header.timestamp));
178
+ }
179
+
180
+ private static readHeader(reader: BinaryReader): SerializationHeader {
181
+ const magic = reader.readU8();
182
+ if (magic !== SERIALIZATION_MAGIC_BYTE) {
183
+ throw new Error(
184
+ `Invalid magic byte: expected 0x${SERIALIZATION_MAGIC_BYTE.toString(16)}, got 0x${magic.toString(16)}`,
185
+ );
186
+ }
187
+
188
+ return {
189
+ formatVersion: reader.readU8(),
190
+ consensusVersion: reader.readU8(),
191
+ transactionType: reader.readU8() as TransactionType,
192
+ chainId: reader.readU32(),
193
+ timestamp: Number(reader.readU64()),
194
+ };
195
+ }
196
+
197
+ private static writeBaseParams(writer: BinaryWriter, params: SerializedBaseParams): void {
198
+ writer.writeStringWithLength(params.from);
199
+ writer.writeBoolean(params.to !== undefined);
200
+ if (params.to !== undefined) {
201
+ writer.writeStringWithLength(params.to);
202
+ }
203
+ writer.writeU32(Math.floor(params.feeRate * 1000)); // Store as milli-sat/vB for precision
204
+ writer.writeU64(BigInt(params.priorityFee));
205
+ writer.writeU64(BigInt(params.gasSatFee));
206
+ writer.writeU8(this.networkNameToU8(params.networkName));
207
+ writer.writeU8(params.txVersion);
208
+ writer.writeBoolean(params.note !== undefined);
209
+ if (params.note !== undefined) {
210
+ writer.writeBytesWithLength(Buffer.from(params.note, 'hex'));
211
+ }
212
+ writer.writeBoolean(params.anchor);
213
+ writer.writeBoolean(params.debugFees ?? false);
214
+ }
215
+
216
+ private static readBaseParams(reader: BinaryReader): SerializedBaseParams {
217
+ const from = reader.readStringWithLength();
218
+ const hasTo = reader.readBoolean();
219
+ const to = hasTo ? reader.readStringWithLength() : undefined;
220
+ const feeRate = reader.readU32() / 1000; // Convert back from milli-sat/vB
221
+ const priorityFee = reader.readU64().toString();
222
+ const gasSatFee = reader.readU64().toString();
223
+ const networkName = this.u8ToNetworkName(reader.readU8());
224
+ const txVersion = reader.readU8();
225
+ const hasNote = reader.readBoolean();
226
+ const note = hasNote
227
+ ? Buffer.from(reader.readBytesWithLength()).toString('hex')
228
+ : undefined;
229
+ const anchor = reader.readBoolean();
230
+ const debugFees = reader.readBoolean();
231
+
232
+ return {
233
+ from,
234
+ to,
235
+ feeRate,
236
+ priorityFee,
237
+ gasSatFee,
238
+ networkName,
239
+ txVersion,
240
+ note,
241
+ anchor,
242
+ debugFees,
243
+ };
244
+ }
245
+
246
+ private static writeUTXOArray(writer: BinaryWriter, utxos: SerializedUTXO[]): void {
247
+ writer.writeU16(utxos.length);
248
+ for (const utxo of utxos) {
249
+ this.writeUTXO(writer, utxo);
250
+ }
251
+ }
252
+
253
+ private static writeUTXO(writer: BinaryWriter, utxo: SerializedUTXO): void {
254
+ // Transaction ID (32 bytes)
255
+ writer.writeBytes(Buffer.from(utxo.transactionId, 'hex'));
256
+ writer.writeU32(utxo.outputIndex);
257
+ writer.writeU64(BigInt(utxo.value));
258
+ writer.writeBytesWithLength(Buffer.from(utxo.scriptPubKeyHex, 'hex'));
259
+
260
+ // Optional address
261
+ writer.writeBoolean(utxo.scriptPubKeyAddress !== undefined);
262
+ if (utxo.scriptPubKeyAddress !== undefined) {
263
+ writer.writeStringWithLength(utxo.scriptPubKeyAddress);
264
+ }
265
+
266
+ // Optional scripts
267
+ writer.writeBoolean(utxo.redeemScript !== undefined);
268
+ if (utxo.redeemScript !== undefined) {
269
+ writer.writeBytesWithLength(Buffer.from(utxo.redeemScript, 'hex'));
270
+ }
271
+
272
+ writer.writeBoolean(utxo.witnessScript !== undefined);
273
+ if (utxo.witnessScript !== undefined) {
274
+ writer.writeBytesWithLength(Buffer.from(utxo.witnessScript, 'hex'));
275
+ }
276
+
277
+ writer.writeBoolean(utxo.nonWitnessUtxo !== undefined);
278
+ if (utxo.nonWitnessUtxo !== undefined) {
279
+ writer.writeBytesWithLength(Buffer.from(utxo.nonWitnessUtxo, 'hex'));
280
+ }
281
+ }
282
+
283
+ private static readUTXOArray(reader: BinaryReader): SerializedUTXO[] {
284
+ const count = reader.readU16();
285
+ const utxos: SerializedUTXO[] = [];
286
+ for (let i = 0; i < count; i++) {
287
+ utxos.push(this.readUTXO(reader));
288
+ }
289
+ return utxos;
290
+ }
291
+
292
+ private static readUTXO(reader: BinaryReader): SerializedUTXO {
293
+ const transactionId = Buffer.from(reader.readBytes(32)).toString('hex');
294
+ const outputIndex = reader.readU32();
295
+ const value = reader.readU64().toString();
296
+ const scriptPubKeyHex = Buffer.from(reader.readBytesWithLength()).toString('hex');
297
+
298
+ const hasAddress = reader.readBoolean();
299
+ const scriptPubKeyAddress = hasAddress ? reader.readStringWithLength() : undefined;
300
+
301
+ const hasRedeemScript = reader.readBoolean();
302
+ const redeemScript = hasRedeemScript
303
+ ? Buffer.from(reader.readBytesWithLength()).toString('hex')
304
+ : undefined;
305
+
306
+ const hasWitnessScript = reader.readBoolean();
307
+ const witnessScript = hasWitnessScript
308
+ ? Buffer.from(reader.readBytesWithLength()).toString('hex')
309
+ : undefined;
310
+
311
+ const hasNonWitnessUtxo = reader.readBoolean();
312
+ const nonWitnessUtxo = hasNonWitnessUtxo
313
+ ? Buffer.from(reader.readBytesWithLength()).toString('hex')
314
+ : undefined;
315
+
316
+ return {
317
+ transactionId,
318
+ outputIndex,
319
+ value,
320
+ scriptPubKeyHex,
321
+ scriptPubKeyAddress,
322
+ redeemScript,
323
+ witnessScript,
324
+ nonWitnessUtxo,
325
+ };
326
+ }
327
+
328
+ private static writeOutputArray(writer: BinaryWriter, outputs: SerializedOutput[]): void {
329
+ writer.writeU16(outputs.length);
330
+ for (const output of outputs) {
331
+ this.writeOutput(writer, output);
332
+ }
333
+ }
334
+
335
+ private static writeOutput(writer: BinaryWriter, output: SerializedOutput): void {
336
+ writer.writeU64(BigInt(output.value));
337
+
338
+ writer.writeBoolean(output.address !== undefined);
339
+ if (output.address !== undefined) {
340
+ writer.writeStringWithLength(output.address);
341
+ }
342
+
343
+ writer.writeBoolean(output.script !== undefined);
344
+ if (output.script !== undefined) {
345
+ writer.writeBytesWithLength(Buffer.from(output.script, 'hex'));
346
+ }
347
+
348
+ writer.writeBoolean(output.tapInternalKey !== undefined);
349
+ if (output.tapInternalKey !== undefined) {
350
+ writer.writeBytesWithLength(Buffer.from(output.tapInternalKey, 'hex'));
351
+ }
352
+ }
353
+
354
+ private static readOutputArray(reader: BinaryReader): SerializedOutput[] {
355
+ const count = reader.readU16();
356
+ const outputs: SerializedOutput[] = [];
357
+ for (let i = 0; i < count; i++) {
358
+ outputs.push(this.readOutput(reader));
359
+ }
360
+ return outputs;
361
+ }
362
+
363
+ private static readOutput(reader: BinaryReader): SerializedOutput {
364
+ const value = Number(reader.readU64());
365
+
366
+ const hasAddress = reader.readBoolean();
367
+ const address = hasAddress ? reader.readStringWithLength() : undefined;
368
+
369
+ const hasScript = reader.readBoolean();
370
+ const script = hasScript
371
+ ? Buffer.from(reader.readBytesWithLength()).toString('hex')
372
+ : undefined;
373
+
374
+ const hasTapInternalKey = reader.readBoolean();
375
+ const tapInternalKey = hasTapInternalKey
376
+ ? Buffer.from(reader.readBytesWithLength()).toString('hex')
377
+ : undefined;
378
+
379
+ return { value, address, script, tapInternalKey };
380
+ }
381
+
382
+ private static writeSignerMappings(
383
+ writer: BinaryWriter,
384
+ mappings: SerializedSignerMapping[],
385
+ ): void {
386
+ writer.writeU16(mappings.length);
387
+ for (const mapping of mappings) {
388
+ writer.writeStringWithLength(mapping.address);
389
+ writer.writeU16(mapping.inputIndices.length);
390
+ for (const idx of mapping.inputIndices) {
391
+ writer.writeU16(idx);
392
+ }
393
+ }
394
+ }
395
+
396
+ private static readSignerMappings(reader: BinaryReader): SerializedSignerMapping[] {
397
+ const count = reader.readU16();
398
+ const mappings: SerializedSignerMapping[] = [];
399
+ for (let i = 0; i < count; i++) {
400
+ const address = reader.readStringWithLength();
401
+ const indicesCount = reader.readU16();
402
+ const inputIndices: number[] = [];
403
+ for (let j = 0; j < indicesCount; j++) {
404
+ inputIndices.push(reader.readU16());
405
+ }
406
+ mappings.push({ address, inputIndices });
407
+ }
408
+ return mappings;
409
+ }
410
+
411
+ private static writeTypeSpecificData(writer: BinaryWriter, data: TypeSpecificData): void {
412
+ switch (data.type) {
413
+ case TransactionType.FUNDING:
414
+ this.writeFundingData(writer, data);
415
+ break;
416
+ case TransactionType.DEPLOYMENT:
417
+ this.writeDeploymentData(writer, data);
418
+ break;
419
+ case TransactionType.INTERACTION:
420
+ this.writeInteractionData(writer, data);
421
+ break;
422
+ case TransactionType.MULTI_SIG:
423
+ this.writeMultiSigData(writer, data);
424
+ break;
425
+ case TransactionType.CUSTOM_CODE:
426
+ this.writeCustomScriptData(writer, data);
427
+ break;
428
+ case TransactionType.CANCEL:
429
+ this.writeCancelData(writer, data);
430
+ break;
431
+ default:
432
+ throw new Error(`Unsupported transaction type: ${(data as TypeSpecificData).type}`);
433
+ }
434
+ }
435
+
436
+ private static readTypeSpecificData(
437
+ reader: BinaryReader,
438
+ type: TransactionType,
439
+ ): TypeSpecificData {
440
+ switch (type) {
441
+ case TransactionType.FUNDING:
442
+ return this.readFundingData(reader);
443
+ case TransactionType.DEPLOYMENT:
444
+ return this.readDeploymentData(reader);
445
+ case TransactionType.INTERACTION:
446
+ return this.readInteractionData(reader);
447
+ case TransactionType.MULTI_SIG:
448
+ return this.readMultiSigData(reader);
449
+ case TransactionType.CUSTOM_CODE:
450
+ return this.readCustomScriptData(reader);
451
+ case TransactionType.CANCEL:
452
+ return this.readCancelData(reader);
453
+ default:
454
+ throw new Error(`Unsupported transaction type: ${type}`);
455
+ }
456
+ }
457
+
458
+ // Funding
459
+ private static writeFundingData(writer: BinaryWriter, data: FundingSpecificData): void {
460
+ writer.writeU64(BigInt(data.amount));
461
+ writer.writeU16(data.splitInputsInto);
462
+ }
463
+
464
+ private static readFundingData(reader: BinaryReader): FundingSpecificData {
465
+ return {
466
+ type: TransactionType.FUNDING,
467
+ amount: reader.readU64().toString(),
468
+ splitInputsInto: reader.readU16(),
469
+ };
470
+ }
471
+
472
+ // Deployment
473
+ private static writeDeploymentData(writer: BinaryWriter, data: DeploymentSpecificData): void {
474
+ writer.writeBytesWithLength(Buffer.from(data.bytecode, 'hex'));
475
+ writer.writeBoolean(data.calldata !== undefined);
476
+ if (data.calldata !== undefined) {
477
+ writer.writeBytesWithLength(Buffer.from(data.calldata, 'hex'));
478
+ }
479
+ this.writeChallenge(writer, data.challenge);
480
+ writer.writeBoolean(data.revealMLDSAPublicKey ?? false);
481
+ writer.writeBoolean(data.linkMLDSAPublicKeyToAddress ?? false);
482
+ writer.writeBoolean(data.hashedPublicKey !== undefined);
483
+ if (data.hashedPublicKey !== undefined) {
484
+ writer.writeBytesWithLength(Buffer.from(data.hashedPublicKey, 'hex'));
485
+ }
486
+ }
487
+
488
+ private static readDeploymentData(reader: BinaryReader): DeploymentSpecificData {
489
+ const bytecode = Buffer.from(reader.readBytesWithLength()).toString('hex');
490
+ const hasCalldata = reader.readBoolean();
491
+ const calldata = hasCalldata
492
+ ? Buffer.from(reader.readBytesWithLength()).toString('hex')
493
+ : undefined;
494
+ const challenge = this.readChallenge(reader);
495
+ const revealMLDSAPublicKey = reader.readBoolean();
496
+ const linkMLDSAPublicKeyToAddress = reader.readBoolean();
497
+ const hasHashedPublicKey = reader.readBoolean();
498
+ const hashedPublicKey = hasHashedPublicKey
499
+ ? Buffer.from(reader.readBytesWithLength()).toString('hex')
500
+ : undefined;
501
+
502
+ return {
503
+ type: TransactionType.DEPLOYMENT,
504
+ bytecode,
505
+ calldata,
506
+ challenge,
507
+ revealMLDSAPublicKey,
508
+ linkMLDSAPublicKeyToAddress,
509
+ hashedPublicKey,
510
+ };
511
+ }
512
+
513
+ // Interaction
514
+ private static writeInteractionData(writer: BinaryWriter, data: InteractionSpecificData): void {
515
+ writer.writeBytesWithLength(Buffer.from(data.calldata, 'hex'));
516
+ writer.writeBoolean(data.contract !== undefined);
517
+ if (data.contract !== undefined) {
518
+ writer.writeStringWithLength(data.contract);
519
+ }
520
+ this.writeChallenge(writer, data.challenge);
521
+ writer.writeBoolean(data.loadedStorage !== undefined);
522
+ if (data.loadedStorage !== undefined) {
523
+ this.writeLoadedStorage(writer, data.loadedStorage);
524
+ }
525
+ writer.writeBoolean(data.isCancellation ?? false);
526
+ writer.writeBoolean(data.disableAutoRefund ?? false);
527
+ writer.writeBoolean(data.revealMLDSAPublicKey ?? false);
528
+ writer.writeBoolean(data.linkMLDSAPublicKeyToAddress ?? false);
529
+ writer.writeBoolean(data.hashedPublicKey !== undefined);
530
+ if (data.hashedPublicKey !== undefined) {
531
+ writer.writeBytesWithLength(Buffer.from(data.hashedPublicKey, 'hex'));
532
+ }
533
+ }
534
+
535
+ private static readInteractionData(reader: BinaryReader): InteractionSpecificData {
536
+ const calldata = Buffer.from(reader.readBytesWithLength()).toString('hex');
537
+ const hasContract = reader.readBoolean();
538
+ const contract = hasContract ? reader.readStringWithLength() : undefined;
539
+ const challenge = this.readChallenge(reader);
540
+ const hasLoadedStorage = reader.readBoolean();
541
+ const loadedStorage = hasLoadedStorage ? this.readLoadedStorage(reader) : undefined;
542
+ const isCancellation = reader.readBoolean();
543
+ const disableAutoRefund = reader.readBoolean();
544
+ const revealMLDSAPublicKey = reader.readBoolean();
545
+ const linkMLDSAPublicKeyToAddress = reader.readBoolean();
546
+ const hasHashedPublicKey = reader.readBoolean();
547
+ const hashedPublicKey = hasHashedPublicKey
548
+ ? Buffer.from(reader.readBytesWithLength()).toString('hex')
549
+ : undefined;
550
+
551
+ return {
552
+ type: TransactionType.INTERACTION,
553
+ calldata,
554
+ contract,
555
+ challenge,
556
+ loadedStorage,
557
+ isCancellation,
558
+ disableAutoRefund,
559
+ revealMLDSAPublicKey,
560
+ linkMLDSAPublicKeyToAddress,
561
+ hashedPublicKey,
562
+ };
563
+ }
564
+
565
+ // MultiSig
566
+ private static writeMultiSigData(writer: BinaryWriter, data: MultiSigSpecificData): void {
567
+ writer.writeU16(data.pubkeys.length);
568
+ for (const pubkey of data.pubkeys) {
569
+ writer.writeBytesWithLength(Buffer.from(pubkey, 'hex'));
570
+ }
571
+ writer.writeU8(data.minimumSignatures);
572
+ writer.writeStringWithLength(data.receiver);
573
+ writer.writeU64(BigInt(data.requestedAmount));
574
+ writer.writeStringWithLength(data.refundVault);
575
+ writer.writeU16(data.originalInputCount);
576
+ writer.writeBoolean(data.existingPsbtBase64 !== undefined);
577
+ if (data.existingPsbtBase64 !== undefined) {
578
+ writer.writeStringWithLength(data.existingPsbtBase64);
579
+ }
580
+ }
581
+
582
+ private static readMultiSigData(reader: BinaryReader): MultiSigSpecificData {
583
+ const pubkeysCount = reader.readU16();
584
+ const pubkeys: string[] = [];
585
+ for (let i = 0; i < pubkeysCount; i++) {
586
+ pubkeys.push(Buffer.from(reader.readBytesWithLength()).toString('hex'));
587
+ }
588
+ const minimumSignatures = reader.readU8();
589
+ const receiver = reader.readStringWithLength();
590
+ const requestedAmount = reader.readU64().toString();
591
+ const refundVault = reader.readStringWithLength();
592
+ const originalInputCount = reader.readU16();
593
+ const hasExistingPsbt = reader.readBoolean();
594
+ const existingPsbtBase64 = hasExistingPsbt ? reader.readStringWithLength() : undefined;
595
+
596
+ return {
597
+ type: TransactionType.MULTI_SIG,
598
+ pubkeys,
599
+ minimumSignatures,
600
+ receiver,
601
+ requestedAmount,
602
+ refundVault,
603
+ originalInputCount,
604
+ existingPsbtBase64,
605
+ };
606
+ }
607
+
608
+ // Custom Script
609
+ private static writeCustomScriptData(
610
+ writer: BinaryWriter,
611
+ data: CustomScriptSpecificData,
612
+ ): void {
613
+ writer.writeU16(data.scriptElements.length);
614
+ for (const element of data.scriptElements) {
615
+ this.writeScriptElement(writer, element);
616
+ }
617
+ writer.writeU16(data.witnesses.length);
618
+ for (const witness of data.witnesses) {
619
+ writer.writeBytesWithLength(Buffer.from(witness, 'hex'));
620
+ }
621
+ writer.writeBoolean(data.annex !== undefined);
622
+ if (data.annex !== undefined) {
623
+ writer.writeBytesWithLength(Buffer.from(data.annex, 'hex'));
624
+ }
625
+ }
626
+
627
+ private static writeScriptElement(
628
+ writer: BinaryWriter,
629
+ element: SerializedScriptElement,
630
+ ): void {
631
+ writer.writeU8(element.elementType === 'buffer' ? 0 : 1);
632
+ if (element.elementType === 'buffer') {
633
+ writer.writeBytesWithLength(Buffer.from(element.value as string, 'hex'));
634
+ } else {
635
+ writer.writeU32(element.value as number);
636
+ }
637
+ }
638
+
639
+ private static readCustomScriptData(reader: BinaryReader): CustomScriptSpecificData {
640
+ const elementsCount = reader.readU16();
641
+ const scriptElements: SerializedScriptElement[] = [];
642
+ for (let i = 0; i < elementsCount; i++) {
643
+ scriptElements.push(this.readScriptElement(reader));
644
+ }
645
+ const witnessesCount = reader.readU16();
646
+ const witnesses: string[] = [];
647
+ for (let i = 0; i < witnessesCount; i++) {
648
+ witnesses.push(Buffer.from(reader.readBytesWithLength()).toString('hex'));
649
+ }
650
+ const hasAnnex = reader.readBoolean();
651
+ const annex = hasAnnex
652
+ ? Buffer.from(reader.readBytesWithLength()).toString('hex')
653
+ : undefined;
654
+
655
+ return {
656
+ type: TransactionType.CUSTOM_CODE,
657
+ scriptElements,
658
+ witnesses,
659
+ annex,
660
+ };
661
+ }
662
+
663
+ private static readScriptElement(reader: BinaryReader): SerializedScriptElement {
664
+ const typeFlag = reader.readU8();
665
+ if (typeFlag === 0) {
666
+ return {
667
+ elementType: 'buffer',
668
+ value: Buffer.from(reader.readBytesWithLength()).toString('hex'),
669
+ };
670
+ } else {
671
+ return {
672
+ elementType: 'opcode',
673
+ value: reader.readU32(),
674
+ };
675
+ }
676
+ }
677
+
678
+ // Cancel
679
+ private static writeCancelData(writer: BinaryWriter, data: CancelSpecificData): void {
680
+ writer.writeBytesWithLength(Buffer.from(data.compiledTargetScript, 'hex'));
681
+ }
682
+
683
+ private static readCancelData(reader: BinaryReader): CancelSpecificData {
684
+ return {
685
+ type: TransactionType.CANCEL,
686
+ compiledTargetScript: Buffer.from(reader.readBytesWithLength()).toString('hex'),
687
+ };
688
+ }
689
+
690
+ private static writeChallenge(writer: BinaryWriter, challenge: RawChallenge): void {
691
+ writer.writeU64(BigInt(challenge.epochNumber));
692
+ writer.writeStringWithLength(challenge.mldsaPublicKey);
693
+ writer.writeStringWithLength(challenge.legacyPublicKey);
694
+ writer.writeBytesWithLength(Buffer.from(challenge.solution.replace('0x', ''), 'hex'));
695
+ writer.writeBytesWithLength(Buffer.from(challenge.salt.replace('0x', ''), 'hex'));
696
+ writer.writeBytesWithLength(Buffer.from(challenge.graffiti.replace('0x', ''), 'hex'));
697
+ writer.writeU8(challenge.difficulty);
698
+
699
+ // Verification
700
+ this.writeChallengeVerification(writer, challenge.verification);
701
+
702
+ // Optional submission
703
+ writer.writeBoolean(challenge.submission !== undefined);
704
+ if (challenge.submission !== undefined) {
705
+ writer.writeStringWithLength(challenge.submission.mldsaPublicKey);
706
+ writer.writeStringWithLength(challenge.submission.legacyPublicKey);
707
+ writer.writeBytesWithLength(
708
+ Buffer.from(challenge.submission.solution.replace('0x', ''), 'hex'),
709
+ );
710
+ writer.writeBoolean(challenge.submission.graffiti !== undefined);
711
+ if (challenge.submission.graffiti !== undefined) {
712
+ writer.writeBytesWithLength(
713
+ Buffer.from(challenge.submission.graffiti.replace('0x', ''), 'hex'),
714
+ );
715
+ }
716
+ writer.writeBytesWithLength(
717
+ Buffer.from(challenge.submission.signature.replace('0x', ''), 'hex'),
718
+ );
719
+ }
720
+ }
721
+
722
+ private static writeChallengeVerification(
723
+ writer: BinaryWriter,
724
+ verification: RawChallengeVerification,
725
+ ): void {
726
+ writer.writeBytesWithLength(Buffer.from(verification.epochHash.replace('0x', ''), 'hex'));
727
+ writer.writeBytesWithLength(Buffer.from(verification.epochRoot.replace('0x', ''), 'hex'));
728
+ writer.writeBytesWithLength(Buffer.from(verification.targetHash.replace('0x', ''), 'hex'));
729
+ writer.writeBytesWithLength(
730
+ Buffer.from(verification.targetChecksum.replace('0x', ''), 'hex'),
731
+ );
732
+ writer.writeU64(BigInt(verification.startBlock));
733
+ writer.writeU64(BigInt(verification.endBlock));
734
+ writer.writeU16(verification.proofs.length);
735
+ for (const proof of verification.proofs) {
736
+ writer.writeBytesWithLength(Buffer.from(proof.replace('0x', ''), 'hex'));
737
+ }
738
+ }
739
+
740
+ private static readChallenge(reader: BinaryReader): RawChallenge {
741
+ const epochNumber = reader.readU64().toString();
742
+ const mldsaPublicKey = reader.readStringWithLength();
743
+ const legacyPublicKey = reader.readStringWithLength();
744
+ const solution = '0x' + Buffer.from(reader.readBytesWithLength()).toString('hex');
745
+ const salt = '0x' + Buffer.from(reader.readBytesWithLength()).toString('hex');
746
+ const graffiti = '0x' + Buffer.from(reader.readBytesWithLength()).toString('hex');
747
+ const difficulty = reader.readU8();
748
+
749
+ const verification = this.readChallengeVerification(reader);
750
+
751
+ const hasSubmission = reader.readBoolean();
752
+ let submission;
753
+ if (hasSubmission) {
754
+ const subMldsaPublicKey = reader.readStringWithLength();
755
+ const subLegacyPublicKey = reader.readStringWithLength();
756
+ const subSolution = '0x' + Buffer.from(reader.readBytesWithLength()).toString('hex');
757
+ const hasGraffiti = reader.readBoolean();
758
+ const subGraffiti = hasGraffiti
759
+ ? '0x' + Buffer.from(reader.readBytesWithLength()).toString('hex')
760
+ : undefined;
761
+ const subSignature = '0x' + Buffer.from(reader.readBytesWithLength()).toString('hex');
762
+
763
+ submission = {
764
+ mldsaPublicKey: subMldsaPublicKey,
765
+ legacyPublicKey: subLegacyPublicKey,
766
+ solution: subSolution,
767
+ graffiti: subGraffiti,
768
+ signature: subSignature,
769
+ };
770
+ }
771
+
772
+ return {
773
+ epochNumber,
774
+ mldsaPublicKey,
775
+ legacyPublicKey,
776
+ solution,
777
+ salt,
778
+ graffiti,
779
+ difficulty,
780
+ verification,
781
+ submission,
782
+ };
783
+ }
784
+
785
+ private static readChallengeVerification(reader: BinaryReader): RawChallengeVerification {
786
+ const epochHash = '0x' + Buffer.from(reader.readBytesWithLength()).toString('hex');
787
+ const epochRoot = '0x' + Buffer.from(reader.readBytesWithLength()).toString('hex');
788
+ const targetHash = '0x' + Buffer.from(reader.readBytesWithLength()).toString('hex');
789
+ const targetChecksum = '0x' + Buffer.from(reader.readBytesWithLength()).toString('hex');
790
+ const startBlock = reader.readU64().toString();
791
+ const endBlock = reader.readU64().toString();
792
+ const proofsCount = reader.readU16();
793
+ const proofs: string[] = [];
794
+ for (let i = 0; i < proofsCount; i++) {
795
+ proofs.push('0x' + Buffer.from(reader.readBytesWithLength()).toString('hex'));
796
+ }
797
+
798
+ return {
799
+ epochHash,
800
+ epochRoot,
801
+ targetHash,
802
+ targetChecksum,
803
+ startBlock,
804
+ endBlock,
805
+ proofs,
806
+ };
807
+ }
808
+
809
+ private static writeLoadedStorage(
810
+ writer: BinaryWriter,
811
+ storage: SerializedLoadedStorage,
812
+ ): void {
813
+ const keys = Object.keys(storage);
814
+ writer.writeU16(keys.length);
815
+ for (const key of keys) {
816
+ writer.writeStringWithLength(key);
817
+ writer.writeStringArray(storage[key]);
818
+ }
819
+ }
820
+
821
+ private static readLoadedStorage(reader: BinaryReader): SerializedLoadedStorage {
822
+ const count = reader.readU16();
823
+ const storage: SerializedLoadedStorage = {};
824
+ for (let i = 0; i < count; i++) {
825
+ const key = reader.readStringWithLength();
826
+ storage[key] = reader.readStringArray();
827
+ }
828
+ return storage;
829
+ }
830
+
831
+ private static writePrecomputedData(writer: BinaryWriter, data: PrecomputedData): void {
832
+ writer.writeBoolean(data.compiledTargetScript !== undefined);
833
+ if (data.compiledTargetScript !== undefined) {
834
+ writer.writeBytesWithLength(Buffer.from(data.compiledTargetScript, 'hex'));
835
+ }
836
+
837
+ writer.writeBoolean(data.randomBytes !== undefined);
838
+ if (data.randomBytes !== undefined) {
839
+ writer.writeBytesWithLength(Buffer.from(data.randomBytes, 'hex'));
840
+ }
841
+
842
+ writer.writeBoolean(data.estimatedFees !== undefined);
843
+ if (data.estimatedFees !== undefined) {
844
+ writer.writeU64(BigInt(data.estimatedFees));
845
+ }
846
+
847
+ writer.writeBoolean(data.contractSeed !== undefined);
848
+ if (data.contractSeed !== undefined) {
849
+ writer.writeStringWithLength(data.contractSeed);
850
+ }
851
+
852
+ writer.writeBoolean(data.contractAddress !== undefined);
853
+ if (data.contractAddress !== undefined) {
854
+ writer.writeStringWithLength(data.contractAddress);
855
+ }
856
+ }
857
+
858
+ private static readPrecomputedData(reader: BinaryReader): PrecomputedData {
859
+ const hasCompiledTargetScript = reader.readBoolean();
860
+ const compiledTargetScript = hasCompiledTargetScript
861
+ ? Buffer.from(reader.readBytesWithLength()).toString('hex')
862
+ : undefined;
863
+
864
+ const hasRandomBytes = reader.readBoolean();
865
+ const randomBytes = hasRandomBytes
866
+ ? Buffer.from(reader.readBytesWithLength()).toString('hex')
867
+ : undefined;
868
+
869
+ const hasEstimatedFees = reader.readBoolean();
870
+ const estimatedFees = hasEstimatedFees ? reader.readU64().toString() : undefined;
871
+
872
+ const hasContractSeed = reader.readBoolean();
873
+ const contractSeed = hasContractSeed ? reader.readStringWithLength() : undefined;
874
+
875
+ const hasContractAddress = reader.readBoolean();
876
+ const contractAddress = hasContractAddress ? reader.readStringWithLength() : undefined;
877
+
878
+ return {
879
+ compiledTargetScript,
880
+ randomBytes,
881
+ estimatedFees,
882
+ contractSeed,
883
+ contractAddress,
884
+ };
885
+ }
886
+
887
+ /**
888
+ * Calculate double SHA256 checksum (Bitcoin standard)
889
+ */
890
+ private static calculateChecksum(data: Buffer): Buffer {
891
+ const hash1 = createHash('sha256').update(data).digest();
892
+ return createHash('sha256').update(hash1).digest();
893
+ }
894
+
895
+ private static networkNameToU8(name: 'mainnet' | 'testnet' | 'regtest'): number {
896
+ switch (name) {
897
+ case 'mainnet':
898
+ return 0;
899
+ case 'testnet':
900
+ return 1;
901
+ case 'regtest':
902
+ return 2;
903
+ default:
904
+ throw new Error(`Unknown network: ${name}`);
905
+ }
906
+ }
907
+
908
+ private static u8ToNetworkName(value: number): 'mainnet' | 'testnet' | 'regtest' {
909
+ switch (value) {
910
+ case 0:
911
+ return 'mainnet';
912
+ case 1:
913
+ return 'testnet';
914
+ case 2:
915
+ return 'regtest';
916
+ default:
917
+ throw new Error(`Unknown network value: ${value}`);
918
+ }
919
+ }
920
+ }