@btc-vision/transaction 1.2.14 → 1.3.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.
@@ -14,13 +14,13 @@ import { BufferLike, i32, Selector, u16, u32, u8 } from '../utils/types.js';
14
14
 
15
15
  export class BinaryReader {
16
16
  private buffer: DataView;
17
-
18
17
  private currentOffset: i32 = 0;
19
18
 
20
19
  constructor(bytes: BufferLike) {
21
20
  this.buffer = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
22
21
  }
23
22
 
23
+ // Helpers for comparisons; unchanged
24
24
  public static stringCompare(a: string, b: string): number {
25
25
  return a.localeCompare(b);
26
26
  }
@@ -39,257 +39,280 @@ export class BinaryReader {
39
39
 
40
40
  public setBuffer(bytes: BufferLike): void {
41
41
  this.buffer = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
42
-
43
42
  this.currentOffset = 0;
44
43
  }
45
44
 
46
- public readAddressArray(): Address[] {
47
- const length = this.readU16();
48
- const result: Address[] = new Array<Address>(length);
45
+ /**
46
+ * Reads a single unsigned byte (u8).
47
+ */
48
+ public readU8(): u8 {
49
+ this.verifyEnd(this.currentOffset + U8_BYTE_LENGTH);
50
+ const value = this.buffer.getUint8(this.currentOffset);
51
+ this.currentOffset += U8_BYTE_LENGTH;
52
+ return value;
53
+ }
49
54
 
50
- for (let i = 0; i < length; i++) {
51
- result[i] = this.readAddress();
52
- }
55
+ /**
56
+ * Reads an unsigned 16-bit integer. By default, big-endian.
57
+ * @param be - Endianness; true means big-endian (the default).
58
+ */
59
+ public readU16(be: boolean = true): u16 {
60
+ this.verifyEnd(this.currentOffset + U16_BYTE_LENGTH);
61
+ const value = this.buffer.getUint16(this.currentOffset, !be);
62
+ this.currentOffset += U16_BYTE_LENGTH;
63
+ return value;
64
+ }
53
65
 
54
- return result;
66
+ /**
67
+ * Reads an unsigned 32-bit integer. By default, big-endian.
68
+ * @param be - Endianness; true means big-endian (the default).
69
+ */
70
+ public readU32(be: boolean = true): u32 {
71
+ this.verifyEnd(this.currentOffset + U32_BYTE_LENGTH);
72
+ const value = this.buffer.getUint32(this.currentOffset, !be);
73
+ this.currentOffset += U32_BYTE_LENGTH;
74
+ return value;
55
75
  }
56
76
 
57
- public readU256Array(): bigint[] {
58
- const length = this.readU16();
59
- const result: bigint[] = new Array<bigint>(length);
77
+ /**
78
+ * Reads an unsigned 64-bit integer. By default, big-endian.
79
+ * @param be - Endianness; true means big-endian (the default).
80
+ */
81
+ public readU64(be: boolean = true): bigint {
82
+ this.verifyEnd(this.currentOffset + U64_BYTE_LENGTH);
83
+ const value = this.buffer.getBigUint64(this.currentOffset, !be);
84
+ this.currentOffset += U64_BYTE_LENGTH;
85
+ return value;
86
+ }
60
87
 
61
- for (let i = 0; i < length; i++) {
62
- result[i] = this.readU256();
88
+ /**
89
+ * Reads a 128-bit unsigned integer. By default, read big-endian.
90
+ * @param be - Endianness; true => big-endian (default).
91
+ */
92
+ public readU128(be: boolean = true): bigint {
93
+ const raw = this.readBytes(U128_BYTE_LENGTH);
94
+ let bytes = raw;
95
+ // If data was written in little-endian, we reverse before interpreting
96
+ if (!be) {
97
+ bytes = this.reverseBytes(raw);
98
+ }
99
+ return BigInt('0x' + this.toHexString(bytes));
100
+ }
101
+
102
+ /**
103
+ * Reads a 256-bit unsigned integer. Same approach as readU128.
104
+ * @param be - Endianness; true => big-endian (default).
105
+ */
106
+ public readU256(be: boolean = true): bigint {
107
+ const raw = this.readBytes(U256_BYTE_LENGTH);
108
+ let bytes = raw;
109
+ if (!be) {
110
+ bytes = this.reverseBytes(raw);
111
+ }
112
+ return BigInt('0x' + this.toHexString(bytes));
113
+ }
114
+
115
+ /**
116
+ * Reads a 128-bit signed integer. Interpret the sign bit if big-endian.
117
+ * @param be - Endianness; true => big-endian (default).
118
+ */
119
+ public readI128(be: boolean = true): bigint {
120
+ const raw = this.readBytes(I128_BYTE_LENGTH);
121
+ let bytes = raw;
122
+ if (!be) {
123
+ bytes = this.reverseBytes(raw);
63
124
  }
64
125
 
65
- return result;
66
- }
67
-
68
- public readU128Array(): bigint[] {
69
- const length = this.readU16();
70
- const result: bigint[] = new Array<bigint>(length);
126
+ // Construct as a 128-bit two's complement
127
+ let value = BigInt('0x' + this.toHexString(bytes));
71
128
 
72
- for (let i = 0; i < length; i++) {
73
- result[i] = this.readU128();
129
+ // If the top bit is set (sign bit in big-endian), interpret negative
130
+ const signBitMask = 0x80;
131
+ if (bytes[0] & signBitMask) {
132
+ // (1 << 128)
133
+ const twoTo128 = BigInt(1) << BigInt(128);
134
+ // 2's complement
135
+ value = value - twoTo128;
74
136
  }
137
+ return value;
138
+ }
75
139
 
76
- return result;
140
+ /**
141
+ * Read a boolean (u8 != 0).
142
+ */
143
+ public readBoolean(): boolean {
144
+ return this.readU8() !== 0;
77
145
  }
78
146
 
79
- public readU64Array(): bigint[] {
80
- const length = this.readU16();
81
- const result: bigint[] = new Array<bigint>(length);
147
+ /**
148
+ * Reads 32 bits
149
+ */
150
+ public readSelector(): Selector {
151
+ return this.readU32(true);
152
+ }
82
153
 
83
- for (let i = 0; i < length; i++) {
84
- result[i] = this.readU64();
154
+ /**
155
+ * Reads a raw sequence of bytes (length must be known).
156
+ * If zeroStop = true, stops if we encounter 0x00 early.
157
+ */
158
+ public readBytes(length: u32, zeroStop: boolean = false): Uint8Array {
159
+ this.verifyEnd(this.currentOffset + length);
160
+ let bytes = new Uint8Array(length);
161
+
162
+ for (let i: u32 = 0; i < length; i++) {
163
+ const b = this.buffer.getUint8(this.currentOffset++);
164
+ if (zeroStop && b === 0) {
165
+ bytes = bytes.subarray(0, i);
166
+ break;
167
+ }
168
+ bytes[i] = b;
85
169
  }
170
+ return bytes;
171
+ }
86
172
 
87
- return result;
173
+ /**
174
+ * Reads a string of the given length in raw bytes. By default, do NOT zero-stop
175
+ * (matching how we wrote the raw bytes).
176
+ */
177
+ public readString(length: u16): string {
178
+ const textDecoder = new TextDecoder();
179
+ const bytes = this.readBytes(length, false);
180
+ return textDecoder.decode(bytes);
88
181
  }
89
182
 
90
- public readU32Array(): u32[] {
91
- const length = this.readU16();
92
- const result: u32[] = new Array<u32>(length);
183
+ /**
184
+ * Reads a string that was written as [u16 length][raw bytes].
185
+ */
186
+ public readStringWithLength(be: boolean = true): string {
187
+ const length = this.readU16(be);
188
+ return this.readString(length);
189
+ }
93
190
 
94
- for (let i = 0; i < length; i++) {
95
- result[i] = this.readU32();
191
+ /**
192
+ * Reads an address.
193
+ */
194
+ public readAddress(): Address {
195
+ const bytes: u8[] = Array.from(this.readBytes(ADDRESS_BYTE_LENGTH));
196
+ return new Address(bytes);
197
+ }
198
+
199
+ /**
200
+ * Reads bytes written as [u32 length][bytes].
201
+ * @param maxLength if > 0, enforces an upper bound
202
+ * @param be
203
+ */
204
+ public readBytesWithLength(maxLength: number = 0, be: boolean = true): Uint8Array {
205
+ const length = this.readU32(be);
206
+ if (maxLength > 0 && length > maxLength) {
207
+ throw new Error('Data length exceeds maximum length.');
96
208
  }
97
209
 
98
- return result;
210
+ return this.readBytes(length);
99
211
  }
100
212
 
101
- public readU16Array(): u16[] {
102
- const length = this.readU16();
103
- const result: u16[] = new Array<u16>(length);
213
+ // ------------------ Array readers ------------------ //
104
214
 
215
+ public readAddressArray(be: boolean = true): Address[] {
216
+ const length = this.readU16(be);
217
+ const result: Address[] = new Array<Address>(length);
105
218
  for (let i = 0; i < length; i++) {
106
- result[i] = this.readU16();
219
+ result[i] = this.readAddress();
107
220
  }
108
-
109
221
  return result;
110
222
  }
111
223
 
112
- public readU8Array(): u8[] {
113
- const length = this.readU16();
114
- const result: u8[] = new Array<u8>(length);
115
-
224
+ public readU256Array(be: boolean = true): bigint[] {
225
+ const length = this.readU16(be);
226
+ const result: bigint[] = new Array<bigint>(length);
116
227
  for (let i = 0; i < length; i++) {
117
- result[i] = this.readU8();
228
+ result[i] = this.readU256(be);
118
229
  }
119
-
120
230
  return result;
121
231
  }
122
232
 
123
- public readStringArray(): string[] {
124
- const length = this.readU16();
125
- const result: string[] = new Array<string>(length);
126
-
233
+ public readU128Array(be: boolean = true): bigint[] {
234
+ const length = this.readU16(be);
235
+ const result: bigint[] = new Array<bigint>(length);
127
236
  for (let i = 0; i < length; i++) {
128
- result[i] = this.readStringWithLength();
237
+ result[i] = this.readU128(be);
129
238
  }
130
-
131
239
  return result;
132
240
  }
133
241
 
134
- public readBytesArray(): Uint8Array[] {
135
- const length = this.readU16();
136
- const result: Uint8Array[] = new Array<Uint8Array>(length);
137
-
242
+ public readU64Array(be: boolean = true): bigint[] {
243
+ const length = this.readU16(be);
244
+ const result: bigint[] = new Array<bigint>(length);
138
245
  for (let i = 0; i < length; i++) {
139
- result[i] = this.readBytesWithLength();
246
+ result[i] = this.readU64(be);
140
247
  }
141
-
142
248
  return result;
143
249
  }
144
250
 
145
- public readBytesWithLength(maxLength: number = 0): Uint8Array {
146
- const length = this.readU32();
147
-
148
- if (maxLength > 0 && length > maxLength) {
149
- throw new Error('Data length exceeds maximum length.');
251
+ public readU32Array(be: boolean = true): u32[] {
252
+ const length = this.readU16(be);
253
+ const result: u32[] = new Array<u32>(length);
254
+ for (let i = 0; i < length; i++) {
255
+ result[i] = this.readU32(be);
150
256
  }
151
-
152
- return this.readBytes(length);
257
+ return result;
153
258
  }
154
259
 
155
- public readTuple(): bigint[] {
156
- const length = this.readU32();
157
- const result: bigint[] = new Array<bigint>(length);
158
-
260
+ public readU16Array(be: boolean = true): u16[] {
261
+ const length = this.readU16(be);
262
+ const result: u16[] = new Array<u16>(length);
159
263
  for (let i = 0; i < length; i++) {
160
- result[i] = this.readU256();
264
+ result[i] = this.readU16(be);
161
265
  }
162
-
163
266
  return result;
164
267
  }
165
268
 
166
- public readU8(): u8 {
167
- this.verifyEnd(this.currentOffset + U8_BYTE_LENGTH);
168
-
169
- const value = this.buffer.getUint8(this.currentOffset);
170
- this.currentOffset += U8_BYTE_LENGTH;
171
-
172
- return value;
173
- }
174
-
175
- public readU16(): u16 {
176
- this.verifyEnd(this.currentOffset + U16_BYTE_LENGTH);
177
-
178
- const value = this.buffer.getUint16(this.currentOffset, true);
179
- this.currentOffset += U16_BYTE_LENGTH;
180
-
181
- return value;
269
+ public readU8Array(): u8[] {
270
+ const length = this.readU16(true); // by default big-endian
271
+ const result: u8[] = new Array<u8>(length);
272
+ for (let i = 0; i < length; i++) {
273
+ result[i] = this.readU8();
274
+ }
275
+ return result;
182
276
  }
183
277
 
184
- public readU32(le: boolean = true): u32 {
185
- this.verifyEnd(this.currentOffset + U32_BYTE_LENGTH);
186
-
187
- const value = this.buffer.getUint32(this.currentOffset, le);
188
- this.currentOffset += U32_BYTE_LENGTH;
189
-
190
- return value;
278
+ public readStringArray(be: boolean = true): string[] {
279
+ const length = this.readU16(be);
280
+ const result: string[] = new Array<string>(length);
281
+ for (let i = 0; i < length; i++) {
282
+ result[i] = this.readStringWithLength(be);
283
+ }
284
+ return result;
191
285
  }
192
286
 
193
- public readU64(): bigint {
194
- this.verifyEnd(this.currentOffset + U64_BYTE_LENGTH);
195
-
196
- const value: bigint = this.buffer.getBigUint64(this.currentOffset, true);
197
- this.currentOffset += U64_BYTE_LENGTH;
198
-
199
- return value;
287
+ public readBytesArray(be: boolean = true): Uint8Array[] {
288
+ const length = this.readU16(be);
289
+ const result: Uint8Array[] = new Array<Uint8Array>(length);
290
+ for (let i = 0; i < length; i++) {
291
+ result[i] = this.readBytesWithLength(0, be);
292
+ }
293
+ return result;
200
294
  }
201
295
 
202
- public readAddressValueTuple(): AddressMap<bigint> {
203
- const length = this.readU16();
296
+ /**
297
+ * Reads [u16 length][ (address, u256) pairs ].
298
+ */
299
+ public readAddressValueTuple(be: boolean = true): AddressMap<bigint> {
300
+ const length = this.readU16(be);
204
301
  const result = new AddressMap<bigint>();
205
302
 
206
303
  for (let i = 0; i < length; i++) {
207
304
  const address = this.readAddress();
208
- const value = this.readU256();
209
-
210
- if (result.has(address)) throw new Error('Duplicate address found in map');
305
+ const value = this.readU256(be);
211
306
 
307
+ if (result.has(address)) {
308
+ throw new Error('Duplicate address found in map');
309
+ }
212
310
  result.set(address, value);
213
311
  }
214
-
215
312
  return result;
216
313
  }
217
314
 
218
- public readU128(): bigint {
219
- const next16Bytes = this.readBytes(U128_BYTE_LENGTH);
220
-
221
- return BigInt(
222
- '0x' + next16Bytes.reduce((acc, byte) => acc + byte.toString(16).padStart(2, '0'), ''),
223
- );
224
- }
225
-
226
- public readU256(): bigint {
227
- const next32Bytes = this.readBytes(U256_BYTE_LENGTH);
228
-
229
- return BigInt(
230
- '0x' + next32Bytes.reduce((acc, byte) => acc + byte.toString(16).padStart(2, '0'), ''),
231
- );
232
- }
233
-
234
- readI128() {
235
- const next16Bytes = this.readBytes(I128_BYTE_LENGTH);
236
- let value = BigInt(
237
- '0x' + next16Bytes.reduce((acc, byte) => acc + byte.toString(16).padStart(2, '0'), ''),
238
- );
239
-
240
- if (next16Bytes[0] & 0x80) {
241
- const mask = (BigInt(1) << BigInt(128)) - BigInt(1);
242
- value = (value ^ mask) + BigInt(1);
243
- value = -value;
244
- }
245
-
246
- return value;
247
- }
248
-
249
- public readBytes(length: u32, zeroStop: boolean = false): Uint8Array {
250
- let bytes: Uint8Array = new Uint8Array(length);
251
- for (let i: u32 = 0; i < length; i++) {
252
- const byte: u8 = this.readU8();
253
- if (zeroStop && byte === 0) {
254
- bytes = bytes.slice(0, i);
255
- break;
256
- }
257
-
258
- bytes[i] = byte;
259
- }
260
-
261
- return bytes;
262
- }
263
-
264
- public readString(length: u16): string {
265
- const textDecoder = new TextDecoder();
266
- const bytes = this.readBytes(length, true);
267
-
268
- return textDecoder.decode(bytes);
269
- }
270
-
271
- public readSelector(): Selector {
272
- return this.readU32(false);
273
- }
274
-
275
- public readStringWithLength(): string {
276
- const length = this.readU16();
277
-
278
- return this.readString(length);
279
- }
280
-
281
- public readBoolean(): boolean {
282
- return this.readU8() !== 0;
283
- }
284
-
285
- public readAddress(): Address {
286
- const bytes: u8[] = new Array<u8>(ADDRESS_BYTE_LENGTH);
287
- for (let i: u32 = 0; i < ADDRESS_BYTE_LENGTH; i++) {
288
- bytes[i] = this.readU8();
289
- }
290
-
291
- return new Address(bytes);
292
- }
315
+ // --------------------------------------------------- //
293
316
 
294
317
  public getOffset(): u16 {
295
318
  return this.currentOffset;
@@ -299,9 +322,32 @@ export class BinaryReader {
299
322
  this.currentOffset = offset;
300
323
  }
301
324
 
325
+ /**
326
+ * Verifies we have enough bytes in the buffer to read up to `size`.
327
+ */
302
328
  public verifyEnd(size: i32): void {
303
- if (this.currentOffset > this.buffer.byteLength) {
304
- throw new Error(`Expected to read ${size} bytes but read ${this.currentOffset} bytes`);
329
+ if (size > this.buffer.byteLength) {
330
+ throw new Error(
331
+ `Attempt to read beyond buffer length: requested up to byte offset ${size}, but buffer is only ${this.buffer.byteLength} bytes.`,
332
+ );
305
333
  }
306
334
  }
335
+
336
+ /**
337
+ * Utility: reverses a byte array in-place or returns a reversed copy.
338
+ */
339
+ private reverseBytes(bytes: Uint8Array): Uint8Array {
340
+ const out = new Uint8Array(bytes.length);
341
+ for (let i = 0; i < bytes.length; i++) {
342
+ out[i] = bytes[bytes.length - 1 - i];
343
+ }
344
+ return out;
345
+ }
346
+
347
+ /**
348
+ * Utility: turn bytes into a hex string without `0x` prefix.
349
+ */
350
+ private toHexString(bytes: Uint8Array): string {
351
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
352
+ }
307
353
  }