From d8b00fa95a27603271c72c2c443b812dde78f0d1 Mon Sep 17 00:00:00 2001 From: erhant Date: Sun, 15 Dec 2024 01:18:45 +0300 Subject: [PATCH] added some merkle trees, mux docs --- book/src/SUMMARY.md | 4 ++ book/src/control-flow/multiplexing.md | 40 ++++++++++------ book/src/merkle-trees/README.md | 31 +++++++++---- book/src/merkle-trees/bmt.md | 7 +++ book/src/merkle-trees/cbmt.md | 7 +++ book/src/merkle-trees/imt.md | 10 ++++ book/src/merkle-trees/mmr.md | 7 +++ book/src/merkle-trees/smt.md | 13 +++++- bun.lockb | Bin 41987 -> 42354 bytes circuits/merkle-trees/bmt.circom | 36 +++++++++++++++ circuits/test/merkle-trees/bmt-1.circom | 6 +++ circuits/test/merkle-trees/bmt-2.circom | 6 +++ package.json | 1 + tests/merkle-trees/bmt.test.ts | 59 ++++++++++++++++++++++++ 14 files changed, 201 insertions(+), 26 deletions(-) create mode 100644 book/src/merkle-trees/bmt.md create mode 100644 book/src/merkle-trees/cbmt.md create mode 100644 book/src/merkle-trees/imt.md create mode 100644 book/src/merkle-trees/mmr.md create mode 100644 circuits/merkle-trees/bmt.circom create mode 100644 circuits/test/merkle-trees/bmt-1.circom create mode 100644 circuits/test/merkle-trees/bmt-2.circom create mode 100644 tests/merkle-trees/bmt.test.ts diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 32ffc55..6ddb126 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -24,6 +24,10 @@ - [MiMC 🚧](./hashing/mimc.md) - [Merkle Trees](./merkle-trees/README.md) - [Sparse Merkle Tree 🚧](./merkle-trees/smt.md) + - [Dense Merkle Tree 🚧](./merkle-trees/dmt.md) + - [Incremential Merkle Tree 🚧](./merkle-trees/imt.md) + - [Complete Binary Merkle Tree 🚧](./merkle-trees/cbmt.md) + - [Merkle Mountain Range 🚧](./merkle-trees/mmr.md) - [Advanced](./advanced/README.md) # Examples diff --git a/book/src/control-flow/multiplexing.md b/book/src/control-flow/multiplexing.md index b006a4a..bb091b4 100644 --- a/book/src/control-flow/multiplexing.md +++ b/book/src/control-flow/multiplexing.md @@ -6,8 +6,8 @@ Multiplexing is a technique to select one of many signals based on a control sig ```cs template Mux1() { - signal input in[2]; - signal input sel; + signal input in[2]; + signal input sel; signal output out; out <== (in[1] - in[0]) * sel + in[0]; @@ -23,13 +23,6 @@ template Mux1() { The main idea here is that `in` has number of values equal to the number of bits of `sel`, and `out = in[sel]`. -To compute this expression, on can construct the truth table and then its corresponding boolean expression. The truth table for `Mux1` is: - -| sel | out | -| --- | ------- | -| 0 | `in[0]` | -| 1 | `in[1]` | - ## `Mux2` ```cs @@ -50,12 +43,29 @@ template Mux2() { } ``` -One can actually construct a `Mux2` using three `Mux1` circuits. - -- `inA <== Mux1(in[0], in[1], sel[0])` -- `inB <== Mux1(in[2], in[3], sel[0])` -- `out <== Mux1(inA, inB, sel[1])` +`Mux2` is a 2-bit multiplexer, which means it can select one of four inputs based on two control signals. The arithmetic above may look weird, but its actually simple to derive. Just think of the `sel` cases in order (note that the bits are in little-endian): + +- `sel = [0, 0]`: You need `in[0]` here, and since `sel` multiplications will be 0, you need `in[0]` added by default. +- `sel = [1, 0]`: You need `in[1]`, but `in[0]` was added already as a constant so you need `in[1] - in[0]` in total. +- `sel = [0, 1]`: Similar to the previous case, you need `in[2]`, but `in[0]` was added already as a constant so you need `in[2] - in[0]` in total. +- `sel = [1, 1]`: You need `in[3]`, but now we have all things from the previous cases added together, which is a total: `in[0] + (in[1] - in[0]) + (in[2] - in[0])`. We need to add `in[3]` and get rid of evertyhing else here, which gives us `in[3] - in[2] - in[1] + in[0]`. + +> You can actually construct a `Mux2` using three `Mux1` circuits. +> +> ```cs +> template Mux2() { +> signal input in[4]; +> signal input sel[2]; +> signal output out; +> +> signal inA <== Mux1()([in[0], in[1]], sel[0]); +> signal inB <== Mux1()([in[2], in[3]], sel[0]); +> out <== Mux1()([inA, inB], sel[1]); +> } +> ``` +> +> In fact, this has 3 non-linear constraints while the previous implementation has 4 non-linear constraints! ## Larger Multiplexers -There are larger multiplexing circuits, e.g. [`Mux3`](https://github.com/iden3/circomlib/blob/circomlib2/circuits/mux3.circom), [`Mux4`](https://github.com/iden3/circomlib/blob/circomlib2/circuits/mux4.circom), etc. but we will not cover them here. +There are larger multiplexing circuits, e.g. [`Mux3`](https://github.com/iden3/circomlib/blob/circomlib2/circuits/mux3.circom), [`Mux4`](https://github.com/iden3/circomlib/blob/circomlib2/circuits/mux4.circom), etc. but we will not cover them here. You can construct them with a similar approach as we did for `Mux2`, i.e. by using smaller multiplexers and combining them. diff --git a/book/src/merkle-trees/README.md b/book/src/merkle-trees/README.md index e9cfdf9..b28e6f1 100644 --- a/book/src/merkle-trees/README.md +++ b/book/src/merkle-trees/README.md @@ -1,8 +1,20 @@ # Merkle Trees -If you have been in the world of crypto for a while, it is highly likely that you have heard the term [Merkle Tree](https://brilliant.org/wiki/merkle-tree/), also known as Merkle Hash Tree. A Merkle Tree is a hash-based data structure, and can serve as a cryptographic commitment scheme. +If you have been in the world of crypto for a while, it is highly likely that you have heard the term [Merkle Tree](https://brilliant.org/wiki/merkle-tree/), also known as Merkle Hash Tree. -You can commit to a set of values using a merkle tree, such as: +They play a huge role in many applications, especially in Blockchain; and there are several types of it, such as: + +- [Binary Merkle Tree](./bmt.md) +- [Complete Binary Merkle Tree](./cbmt.md) +- [Sparse Merkle Tree](./smt.md) +- [Incremential Merkle Tree](./imt.md) +- [Merkle Mountain Range](./mmr.md) + +## Merkle Tree Basics + +A Merkle Tree is a hash-based data structure, and can serve as a **cryptographic commitment scheme**. + +You can commit to a set of values using a Merkle Tree, such as: - Evaluations of a function - Coefficients of a polynomial @@ -90,16 +102,15 @@ graph BT You see, we only needed to provide 3 hashes here, although our data had 8 elements! In fact, if you have $n$ elements you only need to provide $\log_2{n}$ elements to the verifier, this is so much more efficient than the naive method of sending all the data to the verifier. -## As a Commitment Scheme - -A Merkle Root can serve as a cryptographic **commitment** to a set of data. - -- It is **hiding** because you can't find the preimage of an hash efficiently. -- It is **binding** because assuming otherwise would require you to find a hash-collision efficiently, which is known to be intractable. - -To **reveal** that some value is part of the comitted set of data at a specific point, you only need to reveal the path from that node to the root, along with the value itself, as described [above](#merkle-proof). +> A Merkle Root can serve as a cryptographic **commitment** to a set of data. +> +> - It is **hiding** because you can't find the preimage of an hash efficiently. +> - It is **binding** because assuming otherwise would require you to find a hash-collision efficiently, which is known to be intractable. +> +> To **reveal** that some value is part of the comitted set of data at a specific point, you only need to reveal the path from that node to the root, along with the value itself, as described [above](#merkle-proof). ## Further Reading - The original paper is ["A Digital Signature Based on a Conventional Encryption Function"](https://people.eecs.berkeley.edu/~raluca/cs261-f15/readings/me) by Ralph Merkle. - ["Providing Authentication and Integrity in Outsourced Databases using Merkle Hash Tree's"](https://people.eecs.berkeley.edu/~raluca/cs261-f15/readings/merkleodb.pdf) by Mykletun, Narasimha, Tsudik is another paper that is slightly easier to read. +- RareSkills Merkle Tree second-preimage attack [blog post](https://www.rareskills.io/post/merkle-tree-second-preimage-attack). diff --git a/book/src/merkle-trees/bmt.md b/book/src/merkle-trees/bmt.md new file mode 100644 index 0000000..a9c9461 --- /dev/null +++ b/book/src/merkle-trees/bmt.md @@ -0,0 +1,7 @@ +# Binary Merkle Tree + +A Binary Merkle Tree is a **dense** Merkle Tree where each leaf node is **full** (non-zero). The [example](./README.md) of the vector with elements `[m, y, v, e, c, t, o, r]` is an example of a Dense Merkle Tree for a tree of depth 3. + +## See Also + +- [PSE zk-kit: Binary Merkle Root](https://github.com/privacy-scaling-explorations/zk-kit.circom/tree/main/packages/binary-merkle-root) diff --git a/book/src/merkle-trees/cbmt.md b/book/src/merkle-trees/cbmt.md new file mode 100644 index 0000000..961abee --- /dev/null +++ b/book/src/merkle-trees/cbmt.md @@ -0,0 +1,7 @@ +# Complete Binary Merkle Tree + +A **Complete Binary Merkle Tree** is a Merkle Tree that does not necessarily have $2^n-1$ nodes, but instead it is optimized to use a few nodes as possible, doing so with only the exception of the last node possibly being empty. + +## See Also + +- [Nervos Network](https://www.nervos.org/)'s CBMT implementation: diff --git a/book/src/merkle-trees/imt.md b/book/src/merkle-trees/imt.md new file mode 100644 index 0000000..f4a3d09 --- /dev/null +++ b/book/src/merkle-trees/imt.md @@ -0,0 +1,10 @@ +# Incremential Merkle Tree + +An Incremential Merkle Tree is a tree that one can start completely empty (all-zeros) with a fixed number of nodes, and then allow addition & removal of nodes to the tree. + +## See Also + +- [PSE zk-kit: Incremental Merkle Tree](https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/imt) +- [PSE zk-kit: Lean Incremental Merkle Tree](https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/lean-imt) +- [HerodotusDev's Accumulators IMT](https://github.com/HerodotusDev/accumulators/blob/main/packages/incremental-merkle-tree/README.md) +- [RareSkills Tornado Cash Line-by-Line](https://www.rareskills.io/post/how-does-tornado-cash-work) diff --git a/book/src/merkle-trees/mmr.md b/book/src/merkle-trees/mmr.md new file mode 100644 index 0000000..beef8e2 --- /dev/null +++ b/book/src/merkle-trees/mmr.md @@ -0,0 +1,7 @@ +# Merkle Mountain Range + +TODO: !!! + +## See Also + +- [HerodotusDev's Accumulators MMR](https://github.com/HerodotusDev/accumulators/tree/main/packages/merkle-mountain-range) diff --git a/book/src/merkle-trees/smt.md b/book/src/merkle-trees/smt.md index dd80497..8fc0752 100644 --- a/book/src/merkle-trees/smt.md +++ b/book/src/merkle-trees/smt.md @@ -1,3 +1,14 @@ # Sparse Merkle Tree -In our description of the Merkle Tree, we had a binary-tree with 8 leaf nodes, and we had filled all of them with elements of a vector $\vec{v} = [m, y, v, e, c, t, o, r]$. What if we did not have data to fill the entire leaves? +What if we don't even know the size of the tree in advance? This is where **Sparse Merkle Trees** come in. It is like an [**Incremential Merkle Tree**](./imt.md) in the sense that we can update the tree by adding / removing nodes, but in the case of a Sparse Merkle Tree the size of the tree is not known ahead of time! + +A Sparse Merkle Tree allow us to create Merkle Trees where the leaf nodes are not entirely filled, and one can create either an inclusion proof or exclusion proof for an element in the tree without revealing the entire tree. + +- **Inclusion proof** shows that an element is in the tree, as we discussed while describing Merkle Trees. +- **Exclusion proof** shows that an element is NOT in the tree. This is possible with a Sparse Merkle Tree because nodes + +## See Also + +- [Tochicool's SMT](https://github.com/tochicool/sparse-merkle-trees#readme) +- [Efficient Sparse Merkle Trees](https://eprint.iacr.org/2016/683.pdf) by Rasmus Dahlberg, Tobias Pulls, and Roel Peeters. +- Iden3 (authors of Circom) has a nice publication on Merkle Trees: [Sparse Merkle Trees](https://docs.iden3.io/publications/pdfs/Merkle-Tree.pdf) by Jordi Baylina and Marta Belles. diff --git a/bun.lockb b/bun.lockb index a581a15bd2b800b14c51ddf756a5733f92c64eb4..a6ebe14f16460958b79ae349195de2bf1404e041 100755 GIT binary patch delta 6669 zcmeHLd2keE7VodgKpL1NVauKx(T%cf81l=k(D5y{?Uf}*-_q0X}ORIKk|MIK(_3PjJ z-uLx;?>oBZYdbDqJ0WjKiVxiT)uaPc${#IiA9Tgq@bc={4!=0m+5PqTduErI_r#r_ z_2CXlE6#R%jGSKI-W;3z0u;R^$z3BPsTX7^WKYPcURhPv*i_}Hty~McAm|Ny6YBlJ zkG7wWahZH1NdYN!uBNOpv8uMLIu@Lr zxejTD%P9B zFWIp=NY>wk#Lw-9lJyI{G6t0a;I)+t>$#&4aITlVvNt5RtEjAONOaJbB2d(lBH!tI z7mV3sN|9q-My`xpu0>_n9D2Yx3^Q`ZvesFR2UgblfOGFRpybXLG&yQvD&+)9QXlYx zkeucKbOuM<)y{?ru3rPr_DG|1QN84nE_oGKA-ThIURmXwpIBK|USkQ-J2-$2xqdG= zOx*oKB`F+o6F92e{@}sPL2&DC3zMV>$TVsmGd3lni|hgaksF9 z<8Ye&!f-p-&pfnc|1%(2p8&~m#6-};-G*6SjnaEzQ%-tLwC-J7$E9wEgm3MX?n{n* z*Rrl-tI1auQ_0gq6>m`|uE7-VtBOW);Cg^OxL&1BToWi>QAIg9aQzc`aQ%cjafQC8 zDk{l=>(k`H^)z+j>PPXtRADCvu74pL>?ieVJrTNU->=&dTdOp-Jf zH8Sn*WmT?%rGfcSD%c22UvAZd_WN2z3pvcH@;r3ex`nS*kub6IjMid2#rvsZH92rS zMjk&^R($EQU$!y@(|Dw2HqUGo50S%P6|ay7*DtBlUo~Z5@P<=e^hk09sLCNQeuS(! zn@XJls#r_$eN^!}IdDxTPaoB^2pLbIMBhxYli~was%w?e=Orvi-aktPKE1jPrd$~(|;N`#gmA50f!T92k-V0oGk z&D0sJnqC3Fi}om)a-=`a3CR}4~W zvnf7ORgPfAOwi1cEF}}`YP!x8t3qJo^{&iT(+6OaDA6y|l#GQpl@cQ}mCd-DVOWT- zsWVEIv%@JNI@{EarJF;E0h!7v+;J{sO6_e`6pJ2RH!Zq+uq4z;^o74wSpdeC7$rP@ z511Yorn~YP80W!<=7m_5Vk}lYW5}rT7??hR+44M?KDoIrEV64hr-AVkFjM@jruAUM z*hg`cJh7@QqUdsLwo(+O8+D`ofjnG}I8~e=Pn@ds!X}WRJBY3e!Sqz~us%SJzN$D( zp1zqbtQ3iXjf1x?$p=9Cq;3G>&`MW{*BEbQT45;za6+?>(X^6uJIU>00q(y)zI8smhXBkI0WK_=V>C3G49TUd^)VjoW#KWl-mh4~wzz-}0xcv9h^~)WxLs$mdfK7+wgkoZA5zK;Q zNA3gIvDpAeRtd1a3gB`($&;WS;Qkx=`Fzt1aKps_7naPI09?;@%-sMNw|4!h%10nM!bbrvEV=$MCMdU)cy~xoYPGkQ z+%3A-@Ey}drr;0P6%#@g86QgKxj-3YOn(2M8F%+L`r*mNI zb8RA@&V#MaGgDTcO%&3aJUa~^Wu_}&HX1R?PX7YiJjy1Ds1xjuqs>$_+J=v`$4A?# zV2qh=f)!KY7(0Chwr7k@OrfvAc8xXD%&|5xjXY!RG(F!;=6oBzz)SKGUjgC+DC-O+;}NMnoB0)ovdl?2 zbt8FCxb?kWSUVf&IyDW>Pc>fZ<+$fp6ayd2TH*I8x8sd?4#4)+UdfwFo%h}W$?f>n zR|2@WK`p?I`So-!z&dWgI)07t&cvOt=d4qJsQ~Mo0PFbctuMf(0bm{epZxGiGtaB* z4asi?wx7>`D%o%s_kz+ikCrqHq}}!57WU;SU>m@Jj{|JLczVD7&O#1*2f$%-m>kwz zfFt3LtaM-~fbUQKd!v1br9$#MjK3A}F)G~w1X7VRvSc&v`1Q^4u_x?5`wRcHPc(c4 zOCx|-AP(R-`vJUP{Q+nM761zY{)9^a1_S-+h%+c_CGPkurX6qt{7p9!z-PCl0=y*+ z0R{jAfn~sQU=^^A`ZR>yJqCA4z;>Vpm;>88D-Z^ubxJ@+vSCba}-&ILMz5b zw7kbx{StyA`pKd+CCQqUAxUc~rYSvgGpsN+ZjarwC2OMj7V6WIh9;%pK#uB~io^>v zZ$Sv1Y`Oz~>ON~qmra6lmWAW*drXss?p_d~8CY;+le^F&N@-?`Mbyxt1>P(9wXlU5 zb-K2T9W^bikxf2yVPSZramZM5=&EVcg)4*LMQT!N68`l(NqrVs#BLhA$P#HBHO9yv zmKI;V+5oGJq?Dvo3@pupm1Y}h95hZye(fpCiKD@=8j8LU(O}xMC|z8qOR&wOZ*euE z^P!~X2)z9Yn_F>?^k#E9-Ui<{TQZEZMX_wa-)0WnRU-?ymBvprP8nakI^Z}?MjV>Q z!?~uGiWV1%g|u<8CCZJdj-UIVpS~P%IQHf#AF(_oX&76{z3AlPv`FL7@vz^=p8SrA zFJ!SiH7P9#yHf~7FG`%T+)3lKa^}!Q2)3l}O!b5A8@~F1ASm^Vm5!$Gxm{8qiU_;5dELykG1~$Zm-MeE3{p8!#tG{5-7~Jm)lNR` zt-%8}(aq%+oEnC?io`*hcYmk}%77qpyTZj9+SVEzt94+UpbjXC+j>lWFG%YkohQsC zifOe(87HmLQ8!)f&wl)d*Ei%$?~5Yb5jYHjscZ?MN$zm5i4L|}B7db{%>|3tOQ+lx z**}y%b%+19CN#M2b(V!pBsAs{E ztkj_rC&&_MoEod8*A{*=YRhhKOXQ8~Tcctk^lzNxIR(>NSNomy)?*kFBFL{TT+WD~ z4Q(MYXkeTOcbjFoe%gO`mS(E0!yj#oQ{sY8pW8m;^3ELCVDDi^HJfnpDh*y?k#nNy zSX-KCqZ@7Mx~k{CmHPg+Zxue0f@$`OA*tQHi*51SfBx?N=8rQ1!24kFEv-8{`eJoj zub(g3I6AT-Chj5ZegUvLzV^U(M<*3Ovjv=&>_<)aI|3>S-}R7K`CF`VSE>>Jj?_2P z6~i;$63u?MF0Rs@{KpP@Vႀ`cXqrzFASnFu2?D%G7PrvA&sFD+tf5K@}`+yFg IN51#}57$BK761SM delta 6527 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 diff --git a/circuits/merkle-trees/bmt.circom b/circuits/merkle-trees/bmt.circom new file mode 100644 index 0000000..98e33c7 --- /dev/null +++ b/circuits/merkle-trees/bmt.circom @@ -0,0 +1,36 @@ +pragma circom 2.1.0; + +include "circomlib/circuits/poseidon.circom"; + +// Binary Merkle Tree +// +// Parameters: +// - n: depth of the tree such that number of leaves is 2^n +// +// Inputs: +// - leafs: the leaves of the tree +// +// Outputs: +// - root: the root of the tree +// +template BinaryMerkleTree(n) { + assert(n > 0); + var NUM_LEAVES = 1 << n; + var NUM_NODES = (1 << (n+1)) - 1; + signal input leafs[NUM_LEAVES]; + signal output root; + + signal nodes[NUM_NODES]; + + // compute hashes of leaves + for (var i = 0; i < NUM_LEAVES; i++) { + nodes[(NUM_NODES - 1) - i] <== Poseidon(1)([leafs[i]]); + } + + // build the tree from the leaves to the root in reverse + for (var i = NUM_NODES - NUM_LEAVES - 1; i >= 0; i--) { + nodes[i] <== Poseidon(2)([nodes[2*i + 1], nodes[2*i + 2]]); + } + + root <== nodes[0]; +} diff --git a/circuits/test/merkle-trees/bmt-1.circom b/circuits/test/merkle-trees/bmt-1.circom new file mode 100644 index 0000000..9e3aa15 --- /dev/null +++ b/circuits/test/merkle-trees/bmt-1.circom @@ -0,0 +1,6 @@ +// auto-generated by circomkit +pragma circom 2.0.0; + +include "../../merkle-trees/bmt.circom"; + +component main = BinaryMerkleTree(1); diff --git a/circuits/test/merkle-trees/bmt-2.circom b/circuits/test/merkle-trees/bmt-2.circom new file mode 100644 index 0000000..7db864b --- /dev/null +++ b/circuits/test/merkle-trees/bmt-2.circom @@ -0,0 +1,6 @@ +// auto-generated by circomkit +pragma circom 2.0.0; + +include "../../merkle-trees/bmt.circom"; + +component main = BinaryMerkleTree(2); diff --git a/package.json b/package.json index de83f8f..f3998ec 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "circomkit": "^0.3.1", "circomlib": "^2.0.5", + "poseidon-lite": "^0.3.0", "snarkjs": "^0.7.5" }, "devDependencies": { diff --git a/tests/merkle-trees/bmt.test.ts b/tests/merkle-trees/bmt.test.ts new file mode 100644 index 0000000..865bb2e --- /dev/null +++ b/tests/merkle-trees/bmt.test.ts @@ -0,0 +1,59 @@ +import { poseidon1, poseidon2 } from "poseidon-lite"; +import { circomkit } from "../common"; +import { describe, beforeAll, it } from "bun:test"; + +function binaryMerkleTree(leafs: bigint[]) { + if (leafs.length === 0) { + throw new Error("The number of leafs must be greater than 0"); + } + + const n = Math.ceil(Math.log2(leafs.length)); + if (1 << n !== leafs.length) { + throw new Error("The number of leafs must be a power of 2 " + `${1 << n} != ${leafs.length}`); + } + + const NUM_LEAVES = 1 << n; + const NUM_NODES = (1 << (n + 1)) - 1; + const nodes: bigint[] = new Array(NUM_NODES).fill(0n); + + // compute hashes of leaves + for (let i = 0; i < NUM_LEAVES; i++) { + nodes[NUM_NODES - 1 - i] = poseidon1([leafs[i]]); + } + + // build the tree from the leaves to the root in reverse + for (let i = NUM_NODES - NUM_LEAVES - 1; i >= 0; i--) { + nodes[i] = poseidon2([nodes[2 * i + 1], nodes[2 * i + 2]]); + } + + const root = nodes[0]; + return { leafs, nodes, root }; +} + +describe("binary merkle tree", () => { + it("n = 1", async () => { + const circuit = await circomkit.WitnessTester<["leafs"], ["root"]>("bmt-1", { + file: "merkle-trees/bmt", + template: "BinaryMerkleTree", + dir: "test/merkle-trees", + params: [1], + }); + + const leafs = [123n, 345n]; + const { root } = binaryMerkleTree(leafs); + await circuit.expectPass({ leafs }, { root }); + }); + + it("n = 2", async () => { + const circuit = await circomkit.WitnessTester<["leafs"], ["root"]>("bmt-2", { + file: "merkle-trees/bmt", + template: "BinaryMerkleTree", + dir: "test/merkle-trees", + params: [2], + }); + + const leafs = [123n, 345n, 678n, 981n]; + const { root } = binaryMerkleTree(leafs); + await circuit.expectPass({ leafs }, { root }); + }); +});