mod.ts
1 // SPDX-FileCopyrightext: 2024 Mass Labs 2 // 3 // SPDX-License-Identifier: MIT 4 5 import { bytesToBigInt, bytesToNumber, toBytes } from "viem"; 6 import { type CodecValue, decode } from "./codec.ts"; 7 export * as codec from "./codec.ts"; 8 export * from "./reflection.ts"; 9 10 export function randUint64(): number { 11 return bytesToNumber(randomBytes(4)); 12 } 13 14 export function randomBytes(n: number) { 15 const b = new Uint8Array(n); 16 crypto.getRandomValues(b); 17 return b; 18 } 19 20 export function hexToBase64(hex: string) { 21 const binString = Array.from( 22 toBytes(hex), 23 (byte) => String.fromCodePoint(byte), 24 ).join(""); 25 return btoa(binString); 26 } 27 28 // This is used to get the string value from an array buffer 29 export function decodeBufferToString(buffer: Uint8Array) { 30 const textDecoder = new TextDecoder(); 31 return textDecoder.decode(buffer); 32 } 33 34 export function random256BigInt() { 35 return bytesToBigInt(randomBytes(32)); 36 } 37 38 export type Hash = Uint8Array; 39 40 export async function hash(data: BufferSource): Promise<Hash> { 41 return new Uint8Array(await crypto.subtle.digest("SHA-256", data)); 42 } 43 44 // TODO: we need a some way to denote whether the value is a hash 45 export function isHash(node: CodecValue): node is Hash { 46 return node instanceof Uint8Array && node.length === 32; 47 } 48 49 export function getWindowLocation() { 50 return typeof window == "undefined" 51 ? undefined 52 : new URL(globalThis.location.href); 53 } 54 55 type TestVector = Map< 56 string, 57 Array<Map<string, Map<string, Map<string, Map<string, CodecValue>>>>> 58 >; 59 60 export async function fetchAndDecode(filename: string): Promise<TestVector> { 61 const response = await fetch( 62 `file://${Deno.env.get("MASS_TEST_VECTORS")}/vectors/${filename}.cbor`, 63 ); 64 const bytes = await response.bytes(); 65 return decode(bytes) as TestVector; 66 } 67 68 export function extractEntriesFromHAMT( 69 hamtNode: unknown, 70 ): CodecValue { 71 if (!hamtNode || !Array.isArray(hamtNode) || hamtNode.length < 2) { 72 return new Map(); 73 } 74 75 const entries = hamtNode[1]; 76 const result = new Map<number | Uint8Array, CodecValue>(); 77 78 if (Array.isArray(entries)) { 79 for (const entry of entries) { 80 if (Array.isArray(entry) && entry.length >= 2) { 81 // Check if this is a leaf node or another HAMT node 82 if (entry.length > 2 && entry[2] !== null) { 83 // This is another HAMT node, recurse into it 84 const subEntries = extractEntriesFromHAMT(entry[2]); 85 // Merge the results 86 if (!(subEntries instanceof Map)) { 87 throw new TypeError("Expected subEntries to be a Map"); 88 } 89 for (const [subKey, subValue] of subEntries.entries()) { 90 if (typeof subKey !== "number") { 91 throw new TypeError("Expected subKey to be a number"); 92 } 93 result.set(subKey, subValue); 94 } 95 } else { 96 // This is a leaf node 97 const key = entry[0]; 98 const value = entry[1]; 99 if (key.length === 8) { 100 // Convert key (Uint8Array) to number (8-byte big endian) 101 const keyNum = new DataView( 102 key.buffer, 103 key.byteOffset, 104 key.byteLength, 105 ) 106 .getBigUint64(0, false); 107 result.set(Number(keyNum), value); 108 } else { 109 result.set(key, value); 110 } 111 } 112 } 113 } 114 } 115 return result; 116 }