use aiken/bytearray.{from_string} use aiken/hash.{Hash, Sha2_256, sha2_256} use aiken/list use aiken/option.{choice, is_none} /// Variant of MerkleTree with only hash but without actual value pub type MerkleTree { Empty Leaf { hash: Hash } Node { hash: Hash, left: MerkleTree, right: MerkleTree, } } pub type Proof = List pub type ProofItem { Left { hash: Hash } Right { hash: Hash } } // Function returning a hash of a given Merkle Tree element pub fn root_hash(self: MerkleTree) -> Hash { when self is { Empty -> #"" Leaf { hash } -> hash Node { hash, .. } -> hash } } /// Function atests whether two Merkle Tress are equal, this is the case when their root hashes match. pub fn is_equal(left: MerkleTree, right: MerkleTree) -> Bool { root_hash(left) == root_hash(right) } /// Function returns a total numbers of leaves in the tree. pub fn size(self: MerkleTree) -> Int { when self is { Empty -> 0 Leaf { .. } -> 1 Node { left, right, .. } -> size(left) + size(right) } } fn combine_hash( left: Hash, right: Hash, ) -> Hash { sha2_256(bytearray.concat(left, right)) } /// Function that returns whether merkle tree has any elements pub fn is_empty(self: MerkleTree) -> Bool { when self is { Empty -> True _ -> False } } fn do_proof( self: MerkleTree, item_hash: Hash, proof: Proof, serialise_fn: fn(a) -> ByteArray, ) -> Option { when self is { Empty -> None Leaf { hash } -> if hash == item_hash { Some(proof) } else { None } Node { left, right, .. } -> { let rh = root_hash(right) let lh = root_hash(left) let go_left: Option = do_proof( left, item_hash, list.push(proof, Right { hash: rh }), serialise_fn, ) let go_right: Option = do_proof( right, item_hash, list.push(proof, Left { hash: lh }), serialise_fn, ) choice([go_left, go_right]) } } } /// Construct a membership 'Proof' from an element and a 'MerkleTree'. Returns /// 'None' if the element isn't a member of the tree to begin with. /// Note function will return Some([]) in case root of the tree is also it's only one and only element pub fn get_proof( self: MerkleTree, item: a, serialise_fn: fn(a) -> ByteArray, ) -> Option { let empty: Proof = [] do_proof(self, sha2_256(serialise_fn(item)), empty, serialise_fn) } fn do_from_list( items: List, len: Int, serialise_fn: fn(a) -> ByteArray, ) -> MerkleTree { when items is { [] -> Empty [item] -> { let hashed_item = sha2_256(serialise_fn(item)) Leaf { hash: hashed_item } } all -> { let cutoff: Int = len / 2 let left = all |> list.take(cutoff) |> do_from_list(cutoff, serialise_fn) let right = all |> list.drop(cutoff) |> do_from_list(len - cutoff, serialise_fn) let hash = combine_hash(root_hash(left), root_hash(right)) Node { hash, left, right } } } } /// Construct a 'MerkleTree' from a list of elements. /// Note that, while this operation is doable on-chain, it is expensive and /// preferably done off-chain. pub fn from_list( items: List, serialise_fn: fn(a) -> ByteArray, ) -> MerkleTree { do_from_list(items, list.length(items), serialise_fn) } fn do_from_hashes_list( items: List>, len: Int, ) -> MerkleTree { when items is { [] -> Empty [hashed_item] -> Leaf { hash: hashed_item } all -> { let cutoff: Int = len / 2 let left = all |> list.take(cutoff) |> do_from_hashes_list(cutoff) let right = all |> list.drop(cutoff) |> do_from_hashes_list(len - cutoff) let hash = combine_hash(root_hash(left), root_hash(right)) Node { hash, left, right } } } } /// Construct a 'MerkleTree' from a list of hashes. /// Note that, while this operation is doable on-chain, it is expensive and /// preferably done off-chain. pub fn from_hashes_list(items: List>) -> MerkleTree { do_from_hashes_list(items, list.length(items)) } // Check whether a hashed element is part of a 'MerkleTree' using only its root hash // and a 'Proof'. The proof is guaranteed to be in log(n) of the size of the // tree, which is why we are interested in such data-structure in the first // place. pub fn member_from_hash( item_hash: Hash, root_hash: Hash, proof: Proof, serialise_fn: fn(a) -> ByteArray, ) -> Bool { when proof is { [] -> root_hash == item_hash [head, ..tail] -> when head is { Left { hash: l } -> member_from_hash( combine_hash(l, item_hash), root_hash, tail, serialise_fn, ) Right { hash: r } -> member_from_hash( combine_hash(item_hash, r), root_hash, tail, serialise_fn, ) } } } // Check whether an element is part of a 'MerkleTree' using only its root hash // and a 'Proof'. pub fn member( item: a, root_hash: Hash, proof: Proof, serialise_fn: fn(a) -> ByteArray, ) -> Bool { let item_hash = sha2_256(serialise_fn(item)) member_from_hash(item_hash, root_hash, proof, serialise_fn) } pub fn member_from_tree( tree: MerkleTree, item: a, serialise_fn: fn(a) -> ByteArray, ) -> Bool { let proof: Option = get_proof(tree, item, serialise_fn) let rh = root_hash(tree) when proof is { Some(p) -> member(item, rh, p, serialise_fn) None -> False } } // needed only for tests fn create_string_item_serialise_fn() -> fn(String) -> ByteArray { fn(x: String) { from_string(x) } } test from_hashes_list_5() { let dog = @"dog" let cat = @"cat" let mouse = @"mouse" let horse = @"horse" let serialise_fn = create_string_item_serialise_fn() let items = [dog, cat, mouse, horse] let hashes_items = list.map(items, fn(item) { sha2_256(serialise_fn(item)) }) let mt = from_hashes_list(hashes_items) let left_node_hash = sha2_256( bytearray.concat(sha2_256(serialise_fn(dog)), sha2_256(serialise_fn(cat))), ) let right_node_hash = sha2_256( bytearray.concat( sha2_256(serialise_fn(mouse)), sha2_256(serialise_fn(horse)), ), ) let root_hash = sha2_256(bytearray.concat(left_node_hash, right_node_hash)) Node { hash: root_hash, left: Node { hash: left_node_hash, left: Leaf { hash: sha2_256(serialise_fn(dog)) }, right: Leaf { hash: sha2_256(serialise_fn(cat)) }, }, right: Node { hash: right_node_hash, left: Leaf { hash: sha2_256(serialise_fn(mouse)) }, right: Leaf { hash: sha2_256(serialise_fn(horse)) }, }, } == mt }