/ packages / utils / mod.ts
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  }