Browse Source

Merkle Tree name refactors

master
Pratyush Mishra 5 years ago
parent
commit
2a86b59138
3 changed files with 63 additions and 72 deletions
  1. +3
    -3
      crypto-primitives/src/lib.rs
  2. +29
    -42
      crypto-primitives/src/merkle_tree/constraints.rs
  3. +31
    -27
      crypto-primitives/src/merkle_tree/mod.rs

+ 3
- 3
crypto-primitives/src/lib.rs

@ -6,7 +6,7 @@ extern crate derivative;
pub mod commitment; pub mod commitment;
pub mod crh; pub mod crh;
pub mod mht;
pub mod merkle_tree;
pub mod nizk; pub mod nizk;
pub mod prf; pub mod prf;
pub mod signature; pub mod signature;
@ -14,7 +14,7 @@ pub mod signature;
pub use self::{ pub use self::{
commitment::CommitmentScheme, commitment::CommitmentScheme,
crh::FixedLengthCRH, crh::FixedLengthCRH,
mht::{HashMembershipProof, MerkleHashTree},
merkle_tree::{MerkleTreePath, MerkleHashTree},
nizk::NIZK, nizk::NIZK,
prf::PRF, prf::PRF,
signature::SignatureScheme, signature::SignatureScheme,
@ -24,7 +24,7 @@ pub use self::{
pub use self::{ pub use self::{
commitment::CommitmentGadget, commitment::CommitmentGadget,
crh::FixedLengthCRHGadget, crh::FixedLengthCRHGadget,
mht::constraints::{MerklePath, MerklePathVerifierGadget},
merkle_tree::constraints::MerkleTreePathGadget,
nizk::NIZKVerifierGadget, nizk::NIZKVerifierGadget,
prf::PRFGadget, prf::PRFGadget,
signature::SigRandomizePkGadget, signature::SigRandomizePkGadget,

crypto-primitives/src/mht/constraints.rs → crypto-primitives/src/merkle_tree/constraints.rs

@ -3,63 +3,51 @@ use r1cs_core::{ConstraintSystem, SynthesisError};
use r1cs_std::prelude::*; use r1cs_std::prelude::*;
use r1cs_std::boolean::AllocatedBit; use r1cs_std::boolean::AllocatedBit;
use crate::mht::*;
use crate::merkle_tree::*;
use crate::crh::{FixedLengthCRH, FixedLengthCRHGadget}; use crate::crh::{FixedLengthCRH, FixedLengthCRHGadget};
use std::{borrow::Borrow, marker::PhantomData};
use std::borrow::Borrow;
pub struct MerklePath<P, HGadget, ConstraintF>
pub struct MerkleTreePathGadget<P, HGadget, ConstraintF>
where where
P: MHTParameters,
P: MerkleTreeConfig,
HGadget: FixedLengthCRHGadget<P::H, ConstraintF>, HGadget: FixedLengthCRHGadget<P::H, ConstraintF>,
ConstraintF: Field, ConstraintF: Field,
{ {
path: Vec<(HGadget::OutputGadget, HGadget::OutputGadget)>, path: Vec<(HGadget::OutputGadget, HGadget::OutputGadget)>,
} }
pub struct MerklePathVerifierGadget<P, CRHGadget, ConstraintF>
impl<P, CRHGadget, ConstraintF> MerkleTreePathGadget<P, CRHGadget, ConstraintF>
where where
P: MHTParameters,
ConstraintF: Field,
CRHGadget: FixedLengthCRHGadget<P::H, ConstraintF>,
{
_p: PhantomData<CRHGadget>,
_p2: PhantomData<P>,
_f: PhantomData<ConstraintF>,
}
impl<P, CRHGadget, ConstraintF> MerklePathVerifierGadget<P, CRHGadget, ConstraintF>
where
P: MHTParameters,
P: MerkleTreeConfig,
ConstraintF: Field, ConstraintF: Field,
CRHGadget: FixedLengthCRHGadget<P::H, ConstraintF>, CRHGadget: FixedLengthCRHGadget<P::H, ConstraintF>,
{ {
pub fn check_membership<CS: ConstraintSystem<ConstraintF>>( pub fn check_membership<CS: ConstraintSystem<ConstraintF>>(
&self,
cs: CS, cs: CS,
parameters: &CRHGadget::ParametersGadget, parameters: &CRHGadget::ParametersGadget,
root: &CRHGadget::OutputGadget, root: &CRHGadget::OutputGadget,
leaf: impl ToBytesGadget<ConstraintF>, leaf: impl ToBytesGadget<ConstraintF>,
witness: &MerklePath<P, CRHGadget, ConstraintF>,
) -> Result<(), SynthesisError> { ) -> Result<(), SynthesisError> {
Self::conditionally_check_membership(
self.conditionally_check_membership(
cs, cs,
parameters, parameters,
root, root,
leaf, leaf,
witness,
&Boolean::Constant(true), &Boolean::Constant(true),
) )
} }
pub fn conditionally_check_membership<CS: ConstraintSystem<ConstraintF>>( pub fn conditionally_check_membership<CS: ConstraintSystem<ConstraintF>>(
&self,
mut cs: CS, mut cs: CS,
parameters: &CRHGadget::ParametersGadget, parameters: &CRHGadget::ParametersGadget,
root: &CRHGadget::OutputGadget, root: &CRHGadget::OutputGadget,
leaf: impl ToBytesGadget<ConstraintF>, leaf: impl ToBytesGadget<ConstraintF>,
witness: &MerklePath<P, CRHGadget, ConstraintF>,
should_enforce: &Boolean, should_enforce: &Boolean,
) -> Result<(), SynthesisError> { ) -> Result<(), SynthesisError> {
assert_eq!(witness.path.len(), P::HEIGHT - 1);
assert_eq!(self.path.len(), P::HEIGHT - 1);
// Check that the hash of the given leaf matches the leaf hash in the membership // Check that the hash of the given leaf matches the leaf hash in the membership
// proof. // proof.
let leaf_bits = leaf.to_bytes(&mut cs.ns(|| "leaf_to_bytes"))?; let leaf_bits = leaf.to_bytes(&mut cs.ns(|| "leaf_to_bytes"))?;
@ -71,21 +59,21 @@ where
// Check if leaf is one of the bottom-most siblings. // Check if leaf is one of the bottom-most siblings.
let leaf_is_left = AllocatedBit::alloc(&mut cs.ns(|| "leaf_is_left"), || { let leaf_is_left = AllocatedBit::alloc(&mut cs.ns(|| "leaf_is_left"), || {
Ok(leaf_hash == witness.path[0].0)
Ok(leaf_hash == self.path[0].0)
})? })?
.into(); .into();
CRHGadget::OutputGadget::conditional_enforce_equal_or( CRHGadget::OutputGadget::conditional_enforce_equal_or(
&mut cs.ns(|| "check_leaf_is_left"), &mut cs.ns(|| "check_leaf_is_left"),
&leaf_is_left, &leaf_is_left,
&leaf_hash, &leaf_hash,
&witness.path[0].0,
&witness.path[0].1,
&self.path[0].0,
&self.path[0].1,
should_enforce, should_enforce,
)?; )?;
// Check levels between leaf level and root. // Check levels between leaf level and root.
let mut previous_hash = leaf_hash; let mut previous_hash = leaf_hash;
for (i, &(ref left_hash, ref right_hash)) in witness.path.iter().enumerate() {
for (i, &(ref left_hash, ref right_hash)) in self.path.iter().enumerate() {
// Check if the previous_hash matches the correct current hash. // Check if the previous_hash matches the correct current hash.
let previous_is_left = let previous_is_left =
AllocatedBit::alloc(&mut cs.ns(|| format!("previous_is_left_{}", i)), || { AllocatedBit::alloc(&mut cs.ns(|| format!("previous_is_left_{}", i)), || {
@ -138,10 +126,10 @@ where
HG::check_evaluation_gadget(cs, parameters, &bytes) HG::check_evaluation_gadget(cs, parameters, &bytes)
} }
impl<P, HGadget, ConstraintF> AllocGadget<HashMembershipProof<P>, ConstraintF>
for MerklePath<P, HGadget, ConstraintF>
impl<P, HGadget, ConstraintF> AllocGadget<MerkleTreePath<P>, ConstraintF>
for MerkleTreePathGadget<P, HGadget, ConstraintF>
where where
P: MHTParameters,
P: MerkleTreeConfig,
HGadget: FixedLengthCRHGadget<P::H, ConstraintF>, HGadget: FixedLengthCRHGadget<P::H, ConstraintF>,
ConstraintF: Field, ConstraintF: Field,
{ {
@ -151,7 +139,7 @@ where
) -> Result<Self, SynthesisError> ) -> Result<Self, SynthesisError>
where where
F: FnOnce() -> Result<T, SynthesisError>, F: FnOnce() -> Result<T, SynthesisError>,
T: Borrow<HashMembershipProof<P>>,
T: Borrow<MerkleTreePath<P>>,
{ {
let mut path = Vec::new(); let mut path = Vec::new();
for (i, &(ref l, ref r)) in value_gen()?.borrow().path.iter().enumerate() { for (i, &(ref l, ref r)) in value_gen()?.borrow().path.iter().enumerate() {
@ -165,7 +153,7 @@ where
})?; })?;
path.push((l_hash, r_hash)); path.push((l_hash, r_hash));
} }
Ok(MerklePath {
Ok(MerkleTreePathGadget {
path, path,
}) })
} }
@ -176,7 +164,7 @@ where
) -> Result<Self, SynthesisError> ) -> Result<Self, SynthesisError>
where where
F: FnOnce() -> Result<T, SynthesisError>, F: FnOnce() -> Result<T, SynthesisError>,
T: Borrow<HashMembershipProof<P>>,
T: Borrow<MerkleTreePath<P>>,
{ {
let mut path = Vec::new(); let mut path = Vec::new();
for (i, &(ref l, ref r)) in value_gen()?.borrow().path.iter().enumerate() { for (i, &(ref l, ref r)) in value_gen()?.borrow().path.iter().enumerate() {
@ -191,7 +179,7 @@ where
path.push((l_hash, r_hash)); path.push((l_hash, r_hash));
} }
Ok(MerklePath {
Ok(MerkleTreePathGadget {
path, path,
}) })
} }
@ -208,7 +196,7 @@ mod test {
FixedLengthCRH, FixedLengthCRH,
FixedLengthCRHGadget, FixedLengthCRHGadget,
}, },
mht::*,
merkle_tree::*,
}; };
use algebra::{ use algebra::{
curves::jubjub::JubJubAffine as JubJub, curves::jubjub::JubJubAffine as JubJub,
@ -234,20 +222,20 @@ mod test {
type H = PedersenCRH<JubJub, Window4x256>; type H = PedersenCRH<JubJub, Window4x256>;
type HG = PedersenCRHGadget<JubJub, Fq, JubJubGadget>; type HG = PedersenCRHGadget<JubJub, Fq, JubJubGadget>;
struct JubJubMHTParams;
struct JubJubMerkleTreeParams;
impl MHTParameters for JubJubMHTParams {
impl MerkleTreeConfig for JubJubMerkleTreeParams {
const HEIGHT: usize = 32; const HEIGHT: usize = 32;
type H = H; type H = H;
} }
type JubJubMHT = MerkleHashTree<JubJubMHTParams>;
type JubJubMerkleTree = MerkleHashTree<JubJubMerkleTreeParams>;
fn generate_merkle_tree(leaves: &[[u8; 30]], use_bad_root: bool) -> () { fn generate_merkle_tree(leaves: &[[u8; 30]], use_bad_root: bool) -> () {
let mut rng = XorShiftRng::seed_from_u64(9174123u64); let mut rng = XorShiftRng::seed_from_u64(9174123u64);
let crh_parameters = Rc::new(H::setup(&mut rng).unwrap()); let crh_parameters = Rc::new(H::setup(&mut rng).unwrap());
let tree = JubJubMHT::new(crh_parameters.clone(), leaves).unwrap();
let tree = JubJubMerkleTree::new(crh_parameters.clone(), leaves).unwrap();
let root = tree.root(); let root = tree.root();
let mut satisfied = true; let mut satisfied = true;
for (i, leaf) in leaves.iter().enumerate() { for (i, leaf) in leaves.iter().enumerate() {
@ -290,7 +278,7 @@ mod test {
println!("constraints from leaf: {}", constraints_from_leaf); println!("constraints from leaf: {}", constraints_from_leaf);
// Allocate Merkle Tree Path // Allocate Merkle Tree Path
let cw = MerklePath::<_, HG, _>::alloc(&mut cs.ns(|| format!("new_witness_{}", i)), || {
let cw = MerkleTreePathGadget::<_, HG, _>::alloc(&mut cs.ns(|| format!("new_witness_{}", i)), || {
Ok(proof) Ok(proof)
}) })
.unwrap(); .unwrap();
@ -301,12 +289,11 @@ mod test {
- constraints_from_leaf; - constraints_from_leaf;
println!("constraints from path: {}", constraints_from_path); println!("constraints from path: {}", constraints_from_path);
let leaf_g: &[UInt8] = leaf_g.as_slice(); let leaf_g: &[UInt8] = leaf_g.as_slice();
MerklePathVerifierGadget::<_, HG, _>::check_membership(
cw.check_membership(
&mut cs.ns(|| format!("new_witness_check_{}", i)), &mut cs.ns(|| format!("new_witness_check_{}", i)),
&crh_parameters, &crh_parameters,
&root, &root,
&leaf_g, &leaf_g,
&cw,
) )
.unwrap(); .unwrap();
if !cs.is_satisfied() { if !cs.is_satisfied() {
@ -330,7 +317,7 @@ mod test {
} }
#[test] #[test]
fn mht_gadget_test() {
fn good_root_test() {
let mut leaves = Vec::new(); let mut leaves = Vec::new();
for i in 0..4u8 { for i in 0..4u8 {
let input = [i ; 30]; let input = [i ; 30];

crypto-primitives/src/mht/mod.rs → crypto-primitives/src/merkle_tree/mod.rs

@ -7,7 +7,7 @@ use std::{fmt, rc::Rc};
#[cfg(feature = "r1cs")] #[cfg(feature = "r1cs")]
pub mod constraints; pub mod constraints;
pub trait MHTParameters {
pub trait MerkleTreeConfig {
const HEIGHT: usize; const HEIGHT: usize;
type H: FixedLengthCRH; type H: FixedLengthCRH;
} }
@ -16,14 +16,18 @@ pub trait MHTParameters {
/// Our path `is_left_child()` if the boolean in `path` is true. /// Our path `is_left_child()` if the boolean in `path` is true.
#[derive(Derivative)] #[derive(Derivative)]
#[derivative( #[derivative(
Clone(bound = "P: MHTParameters"),
Debug(bound = "P: MHTParameters, <P::H as FixedLengthCRH>::Output: fmt::Debug")
Clone(bound = "P: MerkleTreeConfig"),
Debug(bound = "P: MerkleTreeConfig, <P::H as FixedLengthCRH>::Output: fmt::Debug")
)] )]
pub struct HashMembershipProof<P: MHTParameters> {
pub struct MerkleTreePath<P: MerkleTreeConfig> {
pub(crate) path: Vec<(<P::H as FixedLengthCRH>::Output, <P::H as FixedLengthCRH>::Output)>, pub(crate) path: Vec<(<P::H as FixedLengthCRH>::Output, <P::H as FixedLengthCRH>::Output)>,
} }
impl<P: MHTParameters> Default for HashMembershipProof<P> {
pub type MerkleTreeParams<P> = <<P as MerkleTreeConfig>::H as FixedLengthCRH>::Parameters;
pub type MerkleTreeDigest<P> = <<P as MerkleTreeConfig>::H as FixedLengthCRH>::Output;
impl<P: MerkleTreeConfig> Default for MerkleTreePath<P> {
fn default() -> Self { fn default() -> Self {
let mut path = Vec::with_capacity(P::HEIGHT as usize); let mut path = Vec::with_capacity(P::HEIGHT as usize);
for _i in 1..P::HEIGHT as usize { for _i in 1..P::HEIGHT as usize {
@ -35,7 +39,7 @@ impl Default for HashMembershipProof

{

} }
} }
impl<P: MHTParameters> HashMembershipProof<P> {
impl<P: MerkleTreeConfig> MerkleTreePath<P> {
pub fn verify<L: ToBytes>( pub fn verify<L: ToBytes>(
&self, &self,
parameters: &<P::H as FixedLengthCRH>::Parameters, parameters: &<P::H as FixedLengthCRH>::Parameters,
@ -76,14 +80,14 @@ impl HashMembershipProof

{

} }
} }
pub struct MerkleHashTree<P: MHTParameters> {
pub struct MerkleHashTree<P: MerkleTreeConfig> {
tree: Vec<<P::H as FixedLengthCRH>::Output>, tree: Vec<<P::H as FixedLengthCRH>::Output>,
padding_tree: Vec<(<P::H as FixedLengthCRH>::Output, <P::H as FixedLengthCRH>::Output)>, padding_tree: Vec<(<P::H as FixedLengthCRH>::Output, <P::H as FixedLengthCRH>::Output)>,
parameters: Rc<<P::H as FixedLengthCRH>::Parameters>, parameters: Rc<<P::H as FixedLengthCRH>::Parameters>,
root: Option<<P::H as FixedLengthCRH>::Output>, root: Option<<P::H as FixedLengthCRH>::Output>,
} }
impl<P: MHTParameters> MerkleHashTree<P> {
impl<P: MerkleTreeConfig> MerkleHashTree<P> {
pub const HEIGHT: u8 = P::HEIGHT as u8; pub const HEIGHT: u8 = P::HEIGHT as u8;
pub fn blank(parameters: Rc<<P::H as FixedLengthCRH>::Parameters>) -> Self { pub fn blank(parameters: Rc<<P::H as FixedLengthCRH>::Parameters>) -> Self {
@ -96,7 +100,7 @@ impl MerkleHashTree

{

} }
pub fn new<L: ToBytes>(parameters: Rc<<P::H as FixedLengthCRH>::Parameters>, leaves: &[L]) -> Result<Self, Error> { pub fn new<L: ToBytes>(parameters: Rc<<P::H as FixedLengthCRH>::Parameters>, leaves: &[L]) -> Result<Self, Error> {
let new_time = start_timer!(|| "MHT::New");
let new_time = start_timer!(|| "MerkleTree::New");
let last_level_size = leaves.len().next_power_of_two(); let last_level_size = leaves.len().next_power_of_two();
let tree_size = 2 * last_level_size - 1; let tree_size = 2 * last_level_size - 1;
@ -177,8 +181,8 @@ impl MerkleHashTree

{

&self, &self,
index: usize, index: usize,
leaf: &L, leaf: &L,
) -> Result<HashMembershipProof<P>, Error> {
let prove_time = start_timer!(|| "MHT::GenProof");
) -> Result<MerkleTreePath<P>, Error> {
let prove_time = start_timer!(|| "MerkleTree::GenProof");
let mut path = Vec::new(); let mut path = Vec::new();
let mut buffer = [0u8; 128]; let mut buffer = [0u8; 128];
@ -189,7 +193,7 @@ impl MerkleHashTree

{

// Check that the given index corresponds to the correct leaf. // Check that the given index corresponds to the correct leaf.
if leaf_hash != self.tree[tree_index] { if leaf_hash != self.tree[tree_index] {
Err(MHTError::IncorrectLeafIndex(tree_index))?
Err(MerkleTreeError::IncorrectLeafIndex(tree_index))?
} }
// Iterate from the leaf up to the root, storing all intermediate hash values. // Iterate from the leaf up to the root, storing all intermediate hash values.
@ -219,9 +223,9 @@ impl MerkleHashTree

{

} }
end_timer!(prove_time); end_timer!(prove_time);
if path.len() != (Self::HEIGHT - 1) as usize { if path.len() != (Self::HEIGHT - 1) as usize {
Err(MHTError::IncorrectPathLength(path.len()))?
Err(MerkleTreeError::IncorrectPathLength(path.len()))?
} else { } else {
Ok(HashMembershipProof {
Ok(MerkleTreePath {
path, path,
}) })
} }
@ -229,22 +233,22 @@ impl MerkleHashTree

{

} }
#[derive(Debug)] #[derive(Debug)]
pub enum MHTError {
pub enum MerkleTreeError {
IncorrectLeafIndex(usize), IncorrectLeafIndex(usize),
IncorrectPathLength(usize), IncorrectPathLength(usize),
} }
impl std::fmt::Display for MHTError {
impl std::fmt::Display for MerkleTreeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let msg = match self { let msg = match self {
MHTError::IncorrectLeafIndex(index) => format!("incorrect leaf index: {}", index),
MHTError::IncorrectPathLength(len) => format!("incorrect path length: {}", len),
MerkleTreeError::IncorrectLeafIndex(index) => format!("incorrect leaf index: {}", index),
MerkleTreeError::IncorrectPathLength(len) => format!("incorrect path length: {}", len),
}; };
write!(f, "{}", msg) write!(f, "{}", msg)
} }
} }
impl std::error::Error for MHTError {
impl std::error::Error for MerkleTreeError {
#[inline] #[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None None
@ -357,7 +361,7 @@ pub(crate) fn hash_empty(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{crh::{pedersen::*, *}, mht::*};
use crate::{crh::{pedersen::*, *}, merkle_tree::*};
use algebra::curves::jubjub::JubJubAffine as JubJub; use algebra::curves::jubjub::JubJubAffine as JubJub;
use rand::SeedableRng; use rand::SeedableRng;
use rand_xorshift::XorShiftRng; use rand_xorshift::XorShiftRng;
@ -372,20 +376,20 @@ mod test {
type H = PedersenCRH<JubJub, Window4x256>; type H = PedersenCRH<JubJub, Window4x256>;
struct JubJubMHTParams;
struct JubJubMerkleTreeParams;
impl MHTParameters for JubJubMHTParams {
impl MerkleTreeConfig for JubJubMerkleTreeParams {
const HEIGHT: usize = 32; const HEIGHT: usize = 32;
type H = H; type H = H;
} }
type JubJubMHT = MerkleHashTree<JubJubMHTParams>;
type JubJubMerkleTree = MerkleHashTree<JubJubMerkleTreeParams>;
fn generate_merkle_tree<L: ToBytes + Clone + Eq>(leaves: &[L]) -> () { fn generate_merkle_tree<L: ToBytes + Clone + Eq>(leaves: &[L]) -> () {
let mut rng = XorShiftRng::seed_from_u64(9174123u64); let mut rng = XorShiftRng::seed_from_u64(9174123u64);
let crh_parameters = Rc::new(H::setup(&mut rng).unwrap()); let crh_parameters = Rc::new(H::setup(&mut rng).unwrap());
let tree = JubJubMHT::new(crh_parameters.clone(), &leaves).unwrap();
let tree = JubJubMerkleTree::new(crh_parameters.clone(), &leaves).unwrap();
let root = tree.root(); let root = tree.root();
for (i, leaf) in leaves.iter().enumerate() { for (i, leaf) in leaves.iter().enumerate() {
let proof = tree.generate_proof(i, &leaf).unwrap(); let proof = tree.generate_proof(i, &leaf).unwrap();
@ -394,7 +398,7 @@ mod test {
} }
#[test] #[test]
fn mht_test() {
fn good_root_test() {
let mut leaves = Vec::new(); let mut leaves = Vec::new();
for i in 0..4u8 { for i in 0..4u8 {
leaves.push([i, i, i, i, i, i, i, i]); leaves.push([i, i, i, i, i, i, i, i]);
@ -412,7 +416,7 @@ mod test {
let mut rng = XorShiftRng::seed_from_u64(13423423u64); let mut rng = XorShiftRng::seed_from_u64(13423423u64);
let crh_parameters = Rc::new(H::setup(&mut rng).unwrap()); let crh_parameters = Rc::new(H::setup(&mut rng).unwrap());
let tree = JubJubMHT::new(crh_parameters.clone(), &leaves).unwrap();
let tree = JubJubMerkleTree::new(crh_parameters.clone(), &leaves).unwrap();
let root = JubJub::zero(); let root = JubJub::zero();
for (i, leaf) in leaves.iter().enumerate() { for (i, leaf) in leaves.iter().enumerate() {
let proof = tree.generate_proof(i, &leaf).unwrap(); let proof = tree.generate_proof(i, &leaf).unwrap();
@ -422,7 +426,7 @@ mod test {
#[should_panic] #[should_panic]
#[test] #[test]
fn bad_root_mht_test() {
fn bad_root_test() {
let mut leaves = Vec::new(); let mut leaves = Vec::new();
for i in 0..4u8 { for i in 0..4u8 {
leaves.push([i, i, i, i, i, i, i, i]); leaves.push([i, i, i, i, i, i, i, i]);

Loading…
Cancel
Save