@effectstream/utils 0.100.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.
@@ -0,0 +1,139 @@
1
+ import { crc32 } from "@foxglove/crc";
2
+ import { CHECKSUM_ERROR_MSG, MALFORMED_ERROR_MSG } from "./utils.ts";
3
+ import { MiniCborReader } from "./minicbor.ts";
4
+ import bs58 from "bs58";
5
+ import { bech32 } from "bech32";
6
+ import { Buffer } from "node:buffer";
7
+
8
+ const CHECKSUM_ADDRESS_ERROR_MSG = CHECKSUM_ERROR_MSG("address");
9
+
10
+ export const InvalidArgumentError = Error;
11
+
12
+ /**
13
+ * Example address:
14
+ * - (mainnet icarus) Ae2tdPwUPEZFRbyhz3cpfC2CumGzNkFBN2L42rcUc2yjQpEkxDbkPodpMAi
15
+ * - (testnet byron) 37btjrVyb4KDXBNC4haBVPCrro8AQPHwvCMp3RFhhSVWwfFmZ6wwzSK6JK1hY6wHNmtrpTf1kdbva8TCneM2YsiXT7mrzT21EacHnPpz5YyUdj64na
16
+ * - (mainnet byron) 82d818584283581caf335162c1ecb0a5b95342e3d1d0c9c38a246fa976b077e81dea9d9ba101581e581c2d574aa85a82a80fd6d1c51ea115c2b03b68166230a3c383c9f89ad5001ac3de014e
17
+ */
18
+ export function verifyCardanoByronAddress(address: string): boolean {
19
+ return verifyCardanoByronAddressBytes(bs58.decode(address));
20
+ }
21
+ function verifyCardanoByronAddressBytes(data: Uint8Array): boolean {
22
+ const reader = new MiniCborReader(Buffer.from(data).toString("hex"));
23
+
24
+ reader.readStartArray();
25
+ reader.readTag();
26
+
27
+ const addressDataEncoded = reader.readByteString();
28
+
29
+ if (Number(reader.readInt()) !== crc32(addressDataEncoded)) {
30
+ throw new Error(CHECKSUM_ADDRESS_ERROR_MSG);
31
+ }
32
+
33
+ // skip the rest of the address verification and assume if the checksum matches the rest is okay
34
+
35
+ return true;
36
+ }
37
+
38
+ export enum AddressType {
39
+ BasePaymentKeyStakeKey = 0b0000,
40
+ BasePaymentScriptStakeKey = 0b0001,
41
+ BasePaymentKeyStakeScript = 0b0010,
42
+ BasePaymentScriptStakeScript = 0b0011,
43
+ PointerKey = 0b0100,
44
+ PointerScript = 0b0101,
45
+ EnterpriseKey = 0b0110,
46
+ EnterpriseScript = 0b0111,
47
+ // 0b1000 was chosen because all existing Byron addresses actually start with 0b1000,
48
+ // therefore we can re-use Byron addresses as-is
49
+ Byron = 0b1000,
50
+ RewardKey = 0b1110,
51
+ RewardScript = 0b1111,
52
+ // 1001-1101 are left for future formats
53
+ }
54
+
55
+ /**
56
+ * See: https://github.com/input-output-hk/cardano-js-sdk/blob/d1544fc9d1fb3997eb7c5b94a233789bb9e5cd9f/packages/core/src/Cardano/Address/Address.ts
57
+ */
58
+ export function verifyCardanoShelleyAddress(address: string): boolean {
59
+ const bech32data = bech32.decode(address, Number.MAX_SAFE_INTEGER / 2);
60
+ const data = bech32.fromWords(bech32data.words);
61
+
62
+ // strictly speaking, you don't have to use these prefixes
63
+ // but it's a good sanity check
64
+ // see CIP5
65
+ if (
66
+ !["addr", "addr_test", "stake", "stake_test"].includes(bech32data.prefix)
67
+ ) {
68
+ return false;
69
+ }
70
+
71
+ const type = data[0] >> 4;
72
+
73
+ switch (type) {
74
+ case AddressType.BasePaymentKeyStakeKey:
75
+ case AddressType.BasePaymentScriptStakeKey:
76
+ case AddressType.BasePaymentKeyStakeScript:
77
+ case AddressType.BasePaymentScriptStakeScript: {
78
+ return data.length === 57;
79
+ }
80
+ case AddressType.PointerKey:
81
+ case AddressType.PointerScript: {
82
+ if (data.length <= 29) {
83
+ return false;
84
+ }
85
+ let index = 29;
86
+ const dataBuffer = Buffer.from(data);
87
+ const { bytesRead: slotBytes } = variableLengthDecode(
88
+ dataBuffer.subarray(index),
89
+ );
90
+
91
+ index += slotBytes;
92
+ const { bytesRead: txIndexBytes } = variableLengthDecode(
93
+ dataBuffer.subarray(index),
94
+ );
95
+
96
+ index += txIndexBytes;
97
+ variableLengthDecode(dataBuffer.subarray(index));
98
+ return true;
99
+ }
100
+ case AddressType.EnterpriseKey:
101
+ case AddressType.EnterpriseScript: {
102
+ return data.length === 47;
103
+ }
104
+ case AddressType.RewardKey:
105
+ case AddressType.RewardScript: {
106
+ return data.length === 47;
107
+ }
108
+ case AddressType.Byron: {
109
+ return verifyCardanoByronAddressBytes(new Uint8Array(data));
110
+ }
111
+ default:
112
+ throw new Error(MALFORMED_ERROR_MSG("address"));
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Decodes a value previously encoded in a variable length array.
118
+ *
119
+ * @param array The encoded value.
120
+ * @returns the decoded value.
121
+ */
122
+ const variableLengthDecode = (
123
+ array: Buffer,
124
+ ): { value: bigint; bytesRead: number } => {
125
+ let more = true;
126
+ let value = 0n;
127
+
128
+ let bytesRead = 0;
129
+ while (more && bytesRead < array.length) {
130
+ const b = array[bytesRead];
131
+ value <<= 7n;
132
+ value |= BigInt(b & 127);
133
+
134
+ more = (b & 128) !== 0;
135
+ ++bytesRead;
136
+ }
137
+
138
+ return { bytesRead, value };
139
+ };
@@ -0,0 +1,54 @@
1
+ import bs58check from "bs58check";
2
+ import { MALFORMED_ERROR_MSG } from "./utils.ts";
3
+
4
+ const BLOCK_HASH_BYTE_LENGTH = 34;
5
+ const TX_ID_BYTE_LENGTH = 34;
6
+ const ADDRESS_BYTE_LENGTH = 36;
7
+
8
+ const MALFORMED_BLOCK_HASH_ERROR_MSG = MALFORMED_ERROR_MSG("block hash");
9
+ const MALFORMED_TX_ID_ERROR_MSG = MALFORMED_ERROR_MSG("tx id");
10
+ const MALFORMED_ADDRESS_ERROR_MSG = MALFORMED_ERROR_MSG("address");
11
+
12
+ /**
13
+ * Example address: B62qpLST3UC1rpVT6SHfB7wqW2iQgiopFAGfrcovPgLjgfpDUN2LLeg
14
+ */
15
+ export function verifyMinaAddress(address: string): boolean {
16
+ const decoded = bs58check.decode(address);
17
+ // Sanity check
18
+ if (decoded.length !== ADDRESS_BYTE_LENGTH) {
19
+ throw new Error(
20
+ `${MALFORMED_ADDRESS_ERROR_MSG}: expected byte length ${ADDRESS_BYTE_LENGTH}, got ${decoded.length}`,
21
+ );
22
+ }
23
+ return true;
24
+ }
25
+
26
+ /**
27
+ * Example block hash: 3NK9ToRM1gnzyDbafWzdde7EYemjUN2cb5isjtchcCuupty2MgbC
28
+ */
29
+ export function verifyMinaBlockHash(hash: string): boolean {
30
+ const decoded = bs58check.decode(hash);
31
+ // Sanity check
32
+ if (decoded.length !== TX_ID_BYTE_LENGTH) {
33
+ throw new Error(
34
+ `${MALFORMED_BLOCK_HASH_ERROR_MSG}: expected byte length ${BLOCK_HASH_BYTE_LENGTH}, got ${decoded.length}`,
35
+ );
36
+ }
37
+ return true;
38
+ }
39
+
40
+ /**
41
+ * See https://github.com/openmina/openmina/blob/26354df0b4165a82ba67fa9ab1f2301841f7b033/mina-p2p-messages/src/v2/hashing.rs#L89-L92
42
+ *
43
+ * Example tx hash: 5JuYVpv6njuwUjgf8WLfXhTEJGHck1pwJXt92Lpcskiwu8kBL632
44
+ */
45
+ export function verifyMinaTransactionHash(hash: string): boolean {
46
+ const decoded = bs58check.decode(hash);
47
+ // Sanity check
48
+ if (decoded.length !== TX_ID_BYTE_LENGTH) {
49
+ throw new Error(
50
+ `${MALFORMED_TX_ID_ERROR_MSG}: expected byte length ${TX_ID_BYTE_LENGTH}, got ${decoded.length}`,
51
+ );
52
+ }
53
+ return true;
54
+ }
@@ -0,0 +1,468 @@
1
+ /**
2
+ * We need CBOR to parse Cardano Byron addresses
3
+ * but CBOR parsing libraries are massive
4
+ * we want to avoid having to import megabytes JS code in the browser just for Cardano Byron address parsing
5
+ *
6
+ * As a middle ground, this file copies just the CBOR parsing code required for Byron checksum matching
7
+ * reference: https://github.com/input-output-hk/cardano-js-sdk/blob/d1544fc9d1fb3997eb7c5b94a233789bb9e5cd9f/packages/core/src/Serialization/CBOR/MiniCborReader.ts#L26
8
+ *
9
+ * This file is 3kb when compressing with gzip
10
+ */
11
+
12
+ import { Buffer } from "node:buffer";
13
+
14
+ type HexBlob = string;
15
+
16
+ export const CborContentException = Error;
17
+ export const CborInvalidOperationException = Error;
18
+
19
+ export enum CborReaderState {
20
+ Undefined = 0,
21
+ UnsignedInteger,
22
+ NegativeInteger,
23
+ ByteString,
24
+ StartIndefiniteLengthByteString,
25
+ EndIndefiniteLengthByteString,
26
+ TextString,
27
+ StartIndefiniteLengthTextString,
28
+ EndIndefiniteLengthTextString,
29
+ StartArray,
30
+ EndArray,
31
+ StartMap,
32
+ EndMap,
33
+ Tag,
34
+ SimpleValue,
35
+ HalfPrecisionFloat,
36
+ SinglePrecisionFloat,
37
+ DoublePrecisionFloat,
38
+ Null,
39
+ Boolean,
40
+ Finished,
41
+ }
42
+
43
+ export enum CborMajorType {
44
+ UnsignedInteger = 0,
45
+ NegativeInteger = 1,
46
+ ByteString = 2,
47
+ Utf8String = 3,
48
+ Array = 4,
49
+ Map = 5,
50
+ Tag = 6,
51
+ Simple = 7,
52
+ }
53
+
54
+ type StackFrame = {
55
+ type: CborMajorType | null;
56
+ frameOffset: number;
57
+ definiteLength?: number;
58
+ itemsRead: number;
59
+ currentKeyOffset: number | null;
60
+ };
61
+
62
+ export enum CborTag {
63
+ DateTimeString = 0,
64
+ UnixTimeSeconds = 1,
65
+ UnsignedBigNum = 2,
66
+ NegativeBigNum = 3,
67
+ DecimalFraction = 4,
68
+ BigFloat = 5,
69
+ Base64UrlLaterEncoding = 21,
70
+ Base64StringLaterEncoding = 22,
71
+ Base16StringLaterEncoding = 23,
72
+ EncodedCborDataItem = 24,
73
+ RationalNumber = 30,
74
+ Uri = 32,
75
+ Base64Url = 33,
76
+ Base64 = 34,
77
+ Regex = 35,
78
+ MimeMessage = 36,
79
+ Set = 258,
80
+ SelfDescribeCbor = 55_799,
81
+ }
82
+
83
+ const UNEXPECTED_END_OF_BUFFER_MSG = "Unexpected end of buffer";
84
+
85
+ export enum CborAdditionalInfo {
86
+ AdditionalFalse = 20,
87
+ AdditionalTrue = 21,
88
+ AdditionalNull = 22,
89
+ Additional8BitData = 24,
90
+ Additional16BitData = 25,
91
+ Additional32BitData = 26,
92
+ Additional64BitData = 27,
93
+ IndefiniteLength = 31,
94
+ }
95
+
96
+ export class CborInitialByte {
97
+ static readonly IndefiniteLengthBreakByte = 0xff;
98
+ static readonly AdditionalInformationMask = 0b0001_1111;
99
+
100
+ constructor(private initialByte: number) {}
101
+ CborInitialByte(
102
+ majorType: CborMajorType,
103
+ additionalInfo: CborAdditionalInfo,
104
+ ) {
105
+ this.initialByte = (majorType << 5) | additionalInfo;
106
+ }
107
+ static from(initialByte: number) {
108
+ const init = new CborInitialByte(initialByte);
109
+
110
+ return init;
111
+ }
112
+ getInitialByte(): number {
113
+ return this.initialByte;
114
+ }
115
+ getMajorType(): CborMajorType {
116
+ return this.initialByte >> 5;
117
+ }
118
+ getAdditionalInfo(): CborAdditionalInfo {
119
+ return this.initialByte & CborInitialByte.AdditionalInformationMask;
120
+ }
121
+ }
122
+
123
+ export class MiniCborReader {
124
+ readonly #data: Uint8Array;
125
+ #offset = 0;
126
+ #nestedItems: Array<StackFrame> = new Array<StackFrame>();
127
+ #isTagContext = false;
128
+ #currentFrame: StackFrame;
129
+ #cachedState = CborReaderState.Undefined;
130
+
131
+ constructor(data: HexBlob) {
132
+ this.#data = new Uint8Array(Buffer.from(data, "hex"));
133
+ this.#currentFrame = {
134
+ currentKeyOffset: null,
135
+ frameOffset: 0,
136
+ itemsRead: 0,
137
+ type: null,
138
+ };
139
+ }
140
+
141
+ readStartArray(): number | null {
142
+ const header: CborInitialByte = this.#peekInitialByte(CborMajorType.Array);
143
+
144
+ if (header.getAdditionalInfo() === CborAdditionalInfo.IndefiniteLength) {
145
+ this.#advanceBuffer(1);
146
+ this.#pushDataItem(CborMajorType.Array);
147
+ return null;
148
+ }
149
+
150
+ const buffer = this.#getRemainingBytes();
151
+ const { length, bytesRead } = MiniCborReader.#peekDefiniteLength(
152
+ header,
153
+ buffer,
154
+ );
155
+
156
+ this.#advanceBuffer(bytesRead);
157
+ this.#pushDataItem(CborMajorType.Array, length);
158
+ return length;
159
+ }
160
+
161
+ readTag(): CborTag {
162
+ const { tag, bytesRead } = this.#peekTagCore();
163
+
164
+ this.#advanceBuffer(bytesRead);
165
+ this.#isTagContext = true;
166
+ return tag;
167
+ }
168
+
169
+ readByteString(): Uint8Array {
170
+ const header = this.#peekInitialByte(CborMajorType.ByteString);
171
+
172
+ if (header.getAdditionalInfo() === CborAdditionalInfo.IndefiniteLength) {
173
+ const { val, encodingLength } = this
174
+ .#readIndefiniteLengthByteStringConcatenated(
175
+ CborMajorType.ByteString,
176
+ );
177
+
178
+ this.#advanceBuffer(encodingLength);
179
+ this.#advanceDataItemCounters();
180
+
181
+ return val;
182
+ }
183
+
184
+ const buffer = this.#getRemainingBytes();
185
+ const { length, bytesRead } = MiniCborReader.#peekDefiniteLength(
186
+ header,
187
+ buffer,
188
+ );
189
+
190
+ this.#ensureReadCapacity(bytesRead + length);
191
+ this.#advanceBuffer(bytesRead + length);
192
+ this.#advanceDataItemCounters();
193
+
194
+ return buffer.slice(bytesRead, bytesRead + length);
195
+ }
196
+
197
+ readInt(): bigint {
198
+ const value = this.#peekSignedInteger();
199
+ this.#advanceBuffer(value.bytesRead);
200
+ this.#advanceDataItemCounters();
201
+ return value.signedInt;
202
+ }
203
+
204
+ #peekSignedInteger(): { signedInt: bigint; bytesRead: number } {
205
+ const header: CborInitialByte = this.#peekInitialByte();
206
+
207
+ switch (header.getMajorType()) {
208
+ case CborMajorType.UnsignedInteger: {
209
+ const { unsignedInt: signedInt, bytesRead } = MiniCborReader
210
+ .#decodeUnsignedInteger(
211
+ header,
212
+ this.#getRemainingBytes(),
213
+ );
214
+
215
+ return { bytesRead, signedInt: BigInt(signedInt) };
216
+ }
217
+ case CborMajorType.NegativeInteger: {
218
+ const { unsignedInt, bytesRead } = MiniCborReader
219
+ .#decodeUnsignedInteger(
220
+ header,
221
+ this.#getRemainingBytes(),
222
+ );
223
+
224
+ return { bytesRead, signedInt: BigInt(-1) - unsignedInt };
225
+ }
226
+ default:
227
+ throw new CborInvalidOperationException(
228
+ `Reader type mismatch, expected ${CborMajorType.UnsignedInteger} or ${CborMajorType.NegativeInteger} but got ${header.getMajorType()}`,
229
+ );
230
+ }
231
+ }
232
+
233
+ #ensureReadCapacity(bytesToRead: number) {
234
+ if (this.#data.length - this.#offset < bytesToRead) {
235
+ throw new CborContentException(UNEXPECTED_END_OF_BUFFER_MSG);
236
+ }
237
+ }
238
+
239
+ #readIndefiniteLengthByteStringConcatenated(type: CborMajorType): {
240
+ val: Uint8Array;
241
+ encodingLength: number;
242
+ } {
243
+ const data = this.#getRemainingBytes();
244
+ let concat = Buffer.from([]);
245
+ let encodingLength = 0;
246
+
247
+ let i = 1; // skip the indefinite-length initial byte
248
+
249
+ let nextInitialByte = MiniCborReader.#peekNextInitialByte(
250
+ data.slice(i),
251
+ type,
252
+ );
253
+
254
+ while (
255
+ nextInitialByte.getInitialByte() !==
256
+ CborInitialByte.IndefiniteLengthBreakByte
257
+ ) {
258
+ const { length: chunkLength, bytesRead } = MiniCborReader
259
+ .#peekDefiniteLength(
260
+ nextInitialByte,
261
+ data.slice(i),
262
+ );
263
+ const payloadSize = bytesRead + Number(chunkLength);
264
+
265
+ concat = Buffer.concat([
266
+ concat as any, // TODO: remove `as any` once @types/node gets updated properly
267
+ data.slice(i + (payloadSize - chunkLength), i + payloadSize),
268
+ ]);
269
+
270
+ i += payloadSize;
271
+
272
+ nextInitialByte = MiniCborReader.#peekNextInitialByte(
273
+ data.slice(i),
274
+ type,
275
+ );
276
+ }
277
+
278
+ encodingLength = i + 1; // include the break byte
279
+
280
+ return { encodingLength, val: new Uint8Array(concat) };
281
+ }
282
+
283
+ #advanceDataItemCounters() {
284
+ ++this.#currentFrame.itemsRead;
285
+ this.#isTagContext = false;
286
+ }
287
+
288
+ static #peekNextInitialByte(
289
+ buffer: Uint8Array,
290
+ expectedType?: CborMajorType,
291
+ ): CborInitialByte {
292
+ MiniCborReader.ensureReadCapacityInArray(buffer, 1);
293
+ const header = CborInitialByte.from(buffer[0]);
294
+
295
+ if (
296
+ header.getInitialByte() !== CborInitialByte.IndefiniteLengthBreakByte &&
297
+ header.getMajorType() !== expectedType
298
+ ) {
299
+ throw new CborContentException(
300
+ "Indefinite length string contains invalid data item",
301
+ );
302
+ }
303
+
304
+ return header;
305
+ }
306
+
307
+ #peekTagCore(): { tag: CborTag; bytesRead: number } {
308
+ const header: CborInitialByte = this.#peekInitialByte(CborMajorType.Tag);
309
+ const { unsignedInt: result, bytesRead } = MiniCborReader
310
+ .#decodeUnsignedInteger(
311
+ header,
312
+ this.#getRemainingBytes(),
313
+ );
314
+
315
+ return { bytesRead, tag: Number(result) as CborTag };
316
+ }
317
+
318
+ static #peekDefiniteLength(
319
+ header: CborInitialByte,
320
+ data: Uint8Array,
321
+ ): { length: number; bytesRead: number } {
322
+ const { unsignedInt: length, bytesRead } = MiniCborReader
323
+ .#decodeUnsignedInteger(header, data);
324
+ return { bytesRead, length: Number(length) };
325
+ }
326
+
327
+ #peekInitialByte(expectedType?: CborMajorType): CborInitialByte {
328
+ if (
329
+ this.#currentFrame.definiteLength !== undefined &&
330
+ this.#currentFrame.definiteLength - this.#currentFrame.itemsRead === 0
331
+ ) {
332
+ throw new CborInvalidOperationException("No more data items to read");
333
+ }
334
+
335
+ if (this.#offset === this.#data.length) {
336
+ if (
337
+ this.#currentFrame.type === null &&
338
+ this.#currentFrame.definiteLength === undefined &&
339
+ this.#offset > 0
340
+ ) {
341
+ throw new CborInvalidOperationException(
342
+ "End of root-level. No more data items to read",
343
+ );
344
+ }
345
+
346
+ throw new CborContentException(UNEXPECTED_END_OF_BUFFER_MSG);
347
+ }
348
+
349
+ const nextByte = CborInitialByte.from(this.#data[this.#offset]);
350
+
351
+ switch (this.#currentFrame.type) {
352
+ case CborMajorType.ByteString:
353
+ case CborMajorType.Utf8String:
354
+ // Indefinite-length string contexts allow two possible data items:
355
+ // 1) Definite-length string chunks of the same major type OR
356
+ // 2) a break byte denoting the end of the indefinite-length string context.
357
+ if (
358
+ nextByte.getInitialByte() ===
359
+ CborInitialByte.IndefiniteLengthBreakByte ||
360
+ (nextByte.getMajorType() === this.#currentFrame.type &&
361
+ nextByte.getAdditionalInfo() !==
362
+ CborAdditionalInfo.IndefiniteLength)
363
+ ) {
364
+ break;
365
+ }
366
+
367
+ throw new CborContentException(
368
+ `Indefinite length string contains invalid data item, ${nextByte.getMajorType()}`,
369
+ );
370
+ }
371
+
372
+ if (expectedType && expectedType !== nextByte.getMajorType()) {
373
+ throw new CborInvalidOperationException(
374
+ `Major type mismatch, expected type ${expectedType} but got ${nextByte.getMajorType()}`,
375
+ );
376
+ }
377
+
378
+ return nextByte;
379
+ }
380
+
381
+ static #decodeUnsignedInteger(
382
+ header: CborInitialByte,
383
+ data: Uint8Array,
384
+ ): { unsignedInt: bigint; bytesRead: number } {
385
+ if (
386
+ (header.getInitialByte() & CborInitialByte.AdditionalInformationMask) <
387
+ CborAdditionalInfo.Additional8BitData
388
+ ) {
389
+ return { bytesRead: 1, unsignedInt: BigInt(header.getAdditionalInfo()) };
390
+ }
391
+
392
+ switch (header.getAdditionalInfo()) {
393
+ case CborAdditionalInfo.Additional8BitData: {
394
+ MiniCborReader.ensureReadCapacityInArray(data, 2);
395
+
396
+ return { bytesRead: 2, unsignedInt: BigInt(data[1]) };
397
+ }
398
+ case CborAdditionalInfo.Additional16BitData: {
399
+ MiniCborReader.ensureReadCapacityInArray(data, 3);
400
+
401
+ const buffer = Buffer.from(data.slice(1));
402
+ const val = buffer.readUInt16BE();
403
+
404
+ return { bytesRead: 3, unsignedInt: BigInt(val) };
405
+ }
406
+ case CborAdditionalInfo.Additional32BitData: {
407
+ MiniCborReader.ensureReadCapacityInArray(data, 5);
408
+
409
+ const buffer = Buffer.from(data.slice(1));
410
+ const val = buffer.readUInt32BE();
411
+
412
+ return { bytesRead: 5, unsignedInt: BigInt(val) };
413
+ }
414
+ case CborAdditionalInfo.Additional64BitData: {
415
+ MiniCborReader.ensureReadCapacityInArray(data, 9);
416
+
417
+ const buffer = Buffer.from(data.slice(1, 9));
418
+
419
+ let result = BigInt(0);
420
+
421
+ for (const element of buffer) {
422
+ result = (result << BigInt(8)) + BigInt(element);
423
+ }
424
+
425
+ return { bytesRead: 9, unsignedInt: result };
426
+ }
427
+ default:
428
+ throw new CborContentException("Invalid integer encoding");
429
+ }
430
+ }
431
+
432
+ static ensureReadCapacityInArray(data: Uint8Array, bytesToRead: number) {
433
+ if (data.length < bytesToRead) {
434
+ throw new CborContentException(UNEXPECTED_END_OF_BUFFER_MSG);
435
+ }
436
+ }
437
+
438
+ #advanceBuffer(length: number) {
439
+ if (this.#offset + length > this.#data.length) {
440
+ throw new CborContentException("Buffer offset out of bounds");
441
+ }
442
+ this.#offset += length;
443
+ this.#cachedState = CborReaderState.Undefined;
444
+ }
445
+
446
+ #pushDataItem(majorType: CborMajorType, definiteLength?: number): void {
447
+ const frame: StackFrame = {
448
+ currentKeyOffset: this.#currentFrame.currentKeyOffset,
449
+ definiteLength: this.#currentFrame.definiteLength,
450
+ frameOffset: this.#currentFrame.frameOffset,
451
+ itemsRead: this.#currentFrame.itemsRead,
452
+ type: this.#currentFrame.type,
453
+ };
454
+
455
+ this.#nestedItems.push(frame);
456
+
457
+ this.#currentFrame.type = majorType;
458
+ this.#currentFrame.definiteLength = definiteLength;
459
+ this.#currentFrame.itemsRead = 0;
460
+ this.#currentFrame.frameOffset = this.#offset;
461
+ this.#isTagContext = false;
462
+ this.#currentFrame.currentKeyOffset = null;
463
+ }
464
+
465
+ #getRemainingBytes(): Uint8Array {
466
+ return this.#data.slice(this.#offset);
467
+ }
468
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./algorand.ts";
2
+ export * from "./utils.ts";
@@ -0,0 +1,34 @@
1
+ import type { HexStringNo0x, UnknownFormat } from "../nominal.ts";
2
+
3
+ function hexStringToBytes(hexString: HexStringNo0x): number[] {
4
+ if (!/^[0-9a-fA-F]+$/.test(hexString)) {
5
+ throw new Error("Non-hex digits found in hex string");
6
+ }
7
+ const bytes: number[] = [];
8
+ if (hexString.length % 2 !== 0) {
9
+ hexString = "0" + hexString;
10
+ }
11
+ for (let c = 0; c < hexString.length; c += 2) {
12
+ const nextByte = hexString.slice(c, c + 2);
13
+ bytes.push(parseInt(nextByte, 16));
14
+ }
15
+ return bytes;
16
+ }
17
+
18
+ export function hexStringToUint8Array(hexString: UnknownFormat): Uint8Array {
19
+ return new Uint8Array(hexStringToBytes(hexString));
20
+ }
21
+
22
+ export function uint8ArrayToHexString(uint8Array: Uint8Array): HexStringNo0x {
23
+ // recall: slice(-2) takes two characters starting from the end of the string
24
+ // so this works because we <16 numbers with '0'
25
+ return Array.prototype.map.call(
26
+ uint8Array,
27
+ (byte) => ("0" + byte.toString(16)).slice(-2),
28
+ ).join("");
29
+ }
30
+
31
+ export const MALFORMED_ERROR_MSG = (str: string): string =>
32
+ `${str} is malformed`;
33
+ export const CHECKSUM_ERROR_MSG = (str: string): string =>
34
+ `${str} checksum is wrong`;