diff --git a/src/utils/merkle_tree.ts b/src/utils/merkle_tree.ts index 5ce69305..f5c156eb 100644 --- a/src/utils/merkle_tree.ts +++ b/src/utils/merkle_tree.ts @@ -1,6 +1,6 @@ // Haskell implementation: https://github.com/input-output-hk/hydra-poc/blob/master/plutus-merkle-tree/src/Plutus/MerkleTree.hs -import { concat, equals } from "https://deno.land/std@0.148.0/bytes/mod.ts"; -import { Sha256 } from "https://deno.land/std@0.153.0/hash/sha256.ts"; +// TODO: get rid of async sha256 +import { concat, equals } from "jsr:@std/bytes"; import { toHex } from "./utils.ts"; type MerkleNode = { @@ -20,18 +20,24 @@ export class MerkleTree { root!: MerkleNode | null; /** Construct Merkle tree from data, which get hashed with sha256 */ - constructor(data: Array) { - this.root = MerkleTree.buildRecursively(data.map((d) => sha256(d))); + static async new(data: Array): Promise { + const tree = new this(); + tree.root = await MerkleTree.buildRecursively( + await Promise.all(data.map((d) => sha256(d))), + ); + return tree; } /** Construct Merkle tree from sha256 hashes */ - static fromHashes(hashes: Array): MerkleTree { - return new this(hashes); + static async fromHashes(hashes: Array): Promise { + const tree = new this(); + tree.root = await MerkleTree.buildRecursively(hashes); + return tree; } - private static buildRecursively( + private static async buildRecursively( hashes: Array, - ): MerkleNode | null { + ): Promise { if (hashes.length <= 0) return null; if (hashes.length === 1) { return { @@ -43,11 +49,11 @@ export class MerkleTree { const cutoff = Math.floor(hashes.length / 2); const [left, right] = [hashes.slice(0, cutoff), hashes.slice(cutoff)]; - const lnode = this.buildRecursively(left); - const rnode = this.buildRecursively(right); + const lnode = await this.buildRecursively(left); + const rnode = await this.buildRecursively(right); if (lnode === null || rnode === null) return null; return { - node: combineHash(lnode.node, rnode.node), + node: await combineHash(lnode.node, rnode.node), left: lnode, right: rnode, }; @@ -58,8 +64,8 @@ export class MerkleTree { return this.root.node; } - getProof(data: Uint8Array): MerkleTreeProof { - const hash = sha256(data); + async getProof(data: Uint8Array): Promise { + const hash = await sha256(data); const proof: MerkleTreeProof = []; const searchRecursively = (tree: MerkleNode | null) => { if (tree && equals(tree.node, hash)) return true; @@ -90,23 +96,23 @@ export class MerkleTree { return searchRecursively(this.root); } - static verify( + static async verify( data: Uint8Array, rootHash: Hash, proof: MerkleTreeProof, - ): boolean { - const hash = sha256(data); - const searchRecursively = ( + ): Promise { + const hash = await sha256(data); + const searchRecursively = async ( rootHash2: Hash, proof: MerkleTreeProof, - ): boolean => { + ): Promise => { if (proof.length <= 0) return equals(rootHash, rootHash2); const [h, t] = [proof[0], proof.slice(1)]; if (h.left) { - return searchRecursively(combineHash(h.left, rootHash2), t); + return searchRecursively(await combineHash(h.left, rootHash2), t); } if (h.right) { - return searchRecursively(combineHash(rootHash2, h.right), t); + return searchRecursively(await combineHash(rootHash2, h.right), t); } return false; }; @@ -129,10 +135,10 @@ export class MerkleTree { export { concat, equals }; -export function sha256(data: Uint8Array): Hash { - return new Uint8Array(new Sha256().update(data).arrayBuffer()); +export async function sha256(data: Uint8Array): Promise { + return new Uint8Array(await crypto.subtle.digest("SHA-256", data)); } -export function combineHash(hash1: Hash, hash2: Hash): Hash { - return sha256(concat(hash1, hash2)); +export function combineHash(hash1: Hash, hash2: Hash): Promise { + return sha256(concat([hash1, hash2])); } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index fa6f75ec..4050349f 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,8 +1,4 @@ -import { - decode, - decodeString, - encodeToString, -} from "https://deno.land/std@0.100.0/encoding/hex.ts"; +import { decodeHex, encodeHex } from "jsr:@std/encoding/hex"; import { Addresses, type Assets, @@ -15,16 +11,16 @@ import { import { crc8 } from "../misc/crc8.ts"; export function fromHex(hex: string): Uint8Array { - return decodeString(hex); + return decodeHex(hex); } export function toHex(bytes: Uint8Array): string { - return encodeToString(bytes); + return encodeHex(bytes); } /** Convert a Hex encoded string to a Utf-8 encoded string. */ export function toText(hex: string): string { - return new TextDecoder().decode(decode(new TextEncoder().encode(hex))); + return new TextDecoder().decode(fromHex(hex)); } /** Convert a Utf-8 encoded string to a Hex encoded string. */ diff --git a/tests/mod.test.ts b/tests/mod.test.ts index 80773cce..fda4c730 100644 --- a/tests/mod.test.ts +++ b/tests/mod.test.ts @@ -218,25 +218,25 @@ Deno.test("json datum to cbor datum", () => { assertEquals(cborDatum, Codec.encodeData(jsonDatum as DataJson)); }); -Deno.test("Basic Merkle tree", () => { +Deno.test("Basic Merkle tree", async () => { const data = [new Uint8Array([0]), new Uint8Array([1])]; - const merkleTree = new MerkleTree(data); + const merkleTree = await MerkleTree.new(data); const rootHash = merkleTree.rootHash(); - const proof = merkleTree.getProof(data[0]); - assert(MerkleTree.verify(data[0], rootHash, proof)); + const proof = await merkleTree.getProof(data[0]); + assert(await MerkleTree.verify(data[0], rootHash, proof)); assertEquals(merkleTree.size(), 3); }); -Deno.test("Merkle tree property test", () => { - fc.assert( - fc.property( +Deno.test("Merkle tree property test", async () => { + await fc.assert( + fc.asyncProperty( fc.array(fc.uint8Array(), { minLength: 1 }), - (data: Uint8Array[]) => { - const merkleTree = new MerkleTree(data); + async (data: Uint8Array[]) => { + const merkleTree = await MerkleTree.new(data); const rootHash = merkleTree.rootHash(); const index = Math.floor(Math.random() * data.length); - const proof = merkleTree.getProof(data[index]); - assert(MerkleTree.verify(data[index], rootHash, proof)); + const proof = await merkleTree.getProof(data[index]); + assert(await MerkleTree.verify(data[index], rootHash, proof)); assertEquals( merkleTree.size(), Math.max(0, data.length + (data.length - 1)),