2022-01-27 13:53:10 -08:00
|
|
|
//! Gadgets for implementing a Merkle tree with Sinsemilla.
|
|
|
|
|
2022-01-27 15:28:02 -08:00
|
|
|
use halo2_proofs::{
|
2021-06-04 09:53:09 -07:00
|
|
|
circuit::{Chip, Layouter},
|
|
|
|
plonk::Error,
|
|
|
|
};
|
|
|
|
use pasta_curves::arithmetic::CurveAffine;
|
|
|
|
|
|
|
|
use super::{HashDomains, SinsemillaInstructions};
|
|
|
|
|
2022-01-27 13:53:10 -08:00
|
|
|
use crate::utilities::{
|
2021-12-20 23:14:21 -08:00
|
|
|
cond_swap::CondSwapInstructions, i2lebsp, transpose_option_array, UtilitiesInstructions,
|
2021-06-04 09:53:09 -07:00
|
|
|
};
|
|
|
|
|
2022-01-28 07:38:22 -08:00
|
|
|
pub mod chip;
|
2021-06-04 09:53:09 -07:00
|
|
|
|
2021-08-19 21:21:46 -07:00
|
|
|
/// SWU hash-to-curve personalization for the Merkle CRH generator
|
|
|
|
pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH";
|
|
|
|
|
2021-06-04 09:53:09 -07:00
|
|
|
/// Instructions to check the validity of a Merkle path of a given `PATH_LENGTH`.
|
|
|
|
/// The hash function used is a Sinsemilla instance with `K`-bit words.
|
|
|
|
/// The hash function can process `MAX_WORDS` words.
|
|
|
|
pub trait MerkleInstructions<
|
|
|
|
C: CurveAffine,
|
|
|
|
const PATH_LENGTH: usize,
|
|
|
|
const K: usize,
|
|
|
|
const MAX_WORDS: usize,
|
|
|
|
>:
|
|
|
|
SinsemillaInstructions<C, K, MAX_WORDS>
|
|
|
|
+ CondSwapInstructions<C::Base>
|
|
|
|
+ UtilitiesInstructions<C::Base>
|
|
|
|
+ Chip<C::Base>
|
|
|
|
{
|
2021-06-28 02:06:39 -07:00
|
|
|
/// Compute MerkleCRH for a given `layer`. The hash that computes the root
|
|
|
|
/// is at layer 0, and the hashes that are applied to two leaves are at
|
2021-11-29 12:39:41 -08:00
|
|
|
/// layer `MERKLE_DEPTH - 1` = layer 31.
|
2021-06-04 09:53:09 -07:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
fn hash_layer(
|
|
|
|
&self,
|
|
|
|
layouter: impl Layouter<C::Base>,
|
|
|
|
Q: C,
|
2021-06-28 02:06:39 -07:00
|
|
|
l: usize,
|
2021-06-04 09:53:09 -07:00
|
|
|
left: Self::Var,
|
|
|
|
right: Self::Var,
|
|
|
|
) -> Result<Self::Var, Error>;
|
|
|
|
}
|
|
|
|
|
2022-01-27 13:53:10 -08:00
|
|
|
/// Gadget representing a Merkle path that proves a leaf exists in a Merkle tree at a
|
|
|
|
/// specific position.
|
2021-06-04 09:53:09 -07:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct MerklePath<
|
|
|
|
C: CurveAffine,
|
|
|
|
MerkleChip,
|
|
|
|
const PATH_LENGTH: usize,
|
|
|
|
const K: usize,
|
|
|
|
const MAX_WORDS: usize,
|
2022-05-05 10:42:26 -07:00
|
|
|
const PAR: usize,
|
2021-06-04 09:53:09 -07:00
|
|
|
> where
|
|
|
|
MerkleChip: MerkleInstructions<C, PATH_LENGTH, K, MAX_WORDS> + Clone,
|
|
|
|
{
|
2022-05-05 10:42:26 -07:00
|
|
|
chips: [MerkleChip; PAR],
|
2022-01-28 07:38:22 -08:00
|
|
|
domain: MerkleChip::HashDomains,
|
|
|
|
leaf_pos: Option<u32>,
|
2021-06-28 02:06:39 -07:00
|
|
|
// The Merkle path is ordered from leaves to root.
|
2022-01-28 07:38:22 -08:00
|
|
|
path: Option<[C::Base; PATH_LENGTH]>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<
|
|
|
|
C: CurveAffine,
|
|
|
|
MerkleChip,
|
|
|
|
const PATH_LENGTH: usize,
|
|
|
|
const K: usize,
|
|
|
|
const MAX_WORDS: usize,
|
2022-05-05 10:42:26 -07:00
|
|
|
const PAR: usize,
|
|
|
|
> MerklePath<C, MerkleChip, PATH_LENGTH, K, MAX_WORDS, PAR>
|
2022-01-28 07:38:22 -08:00
|
|
|
where
|
|
|
|
MerkleChip: MerkleInstructions<C, PATH_LENGTH, K, MAX_WORDS> + Clone,
|
|
|
|
{
|
|
|
|
/// Constructs a [`MerklePath`].
|
2022-05-05 10:42:26 -07:00
|
|
|
///
|
|
|
|
/// A circuit may have many more columns available than are required by a single
|
|
|
|
/// `MerkleChip`. To make better use of the available circuit area, the `MerklePath`
|
|
|
|
/// gadget will distribute its path hashing across each `MerkleChip` in `chips`, such
|
|
|
|
/// that each chip processes `ceil(PATH_LENGTH / PAR)` layers (with the last chip
|
|
|
|
/// processing fewer layers if the division is inexact).
|
2022-01-28 07:38:22 -08:00
|
|
|
pub fn construct(
|
2022-05-05 10:42:26 -07:00
|
|
|
chips: [MerkleChip; PAR],
|
2022-01-28 07:38:22 -08:00
|
|
|
domain: MerkleChip::HashDomains,
|
|
|
|
leaf_pos: Option<u32>,
|
|
|
|
path: Option<[C::Base; PATH_LENGTH]>,
|
|
|
|
) -> Self {
|
2022-05-05 10:42:26 -07:00
|
|
|
assert_ne!(PAR, 0);
|
2022-01-28 07:38:22 -08:00
|
|
|
Self {
|
2022-05-05 10:42:26 -07:00
|
|
|
chips,
|
2022-01-28 07:38:22 -08:00
|
|
|
domain,
|
|
|
|
leaf_pos,
|
|
|
|
path,
|
|
|
|
}
|
|
|
|
}
|
2021-06-04 09:53:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
impl<
|
|
|
|
C: CurveAffine,
|
|
|
|
MerkleChip,
|
|
|
|
const PATH_LENGTH: usize,
|
|
|
|
const K: usize,
|
|
|
|
const MAX_WORDS: usize,
|
2022-05-05 10:42:26 -07:00
|
|
|
const PAR: usize,
|
|
|
|
> MerklePath<C, MerkleChip, PATH_LENGTH, K, MAX_WORDS, PAR>
|
2021-06-04 09:53:09 -07:00
|
|
|
where
|
|
|
|
MerkleChip: MerkleInstructions<C, PATH_LENGTH, K, MAX_WORDS> + Clone,
|
|
|
|
{
|
|
|
|
/// Calculates the root of the tree containing the given leaf at this Merkle path.
|
2022-05-05 20:17:52 -07:00
|
|
|
///
|
|
|
|
/// Implements [Zcash Protocol Specification Section 4.9: Merkle Path Validity][merklepath].
|
|
|
|
///
|
|
|
|
/// [merklepath]: https://zips.z.cash/protocol/protocol.pdf#merklepath
|
2022-01-28 07:38:22 -08:00
|
|
|
pub fn calculate_root(
|
2021-06-04 09:53:09 -07:00
|
|
|
&self,
|
|
|
|
mut layouter: impl Layouter<C::Base>,
|
|
|
|
leaf: MerkleChip::Var,
|
|
|
|
) -> Result<MerkleChip::Var, Error> {
|
2022-05-05 10:42:26 -07:00
|
|
|
// Each chip processes `ceil(PATH_LENGTH / PAR)` layers.
|
|
|
|
let layers_per_chip = (PATH_LENGTH + PAR - 1) / PAR;
|
|
|
|
|
|
|
|
// Assign each layer to a chip.
|
|
|
|
let chips = (0..PATH_LENGTH).map(|i| self.chips[i / layers_per_chip].clone());
|
2021-06-04 09:53:09 -07:00
|
|
|
|
2021-06-28 02:06:39 -07:00
|
|
|
// The Merkle path is ordered from leaves to root, which is consistent with the
|
|
|
|
// little-endian representation of `pos` below.
|
|
|
|
let path = transpose_option_array(self.path);
|
2021-06-04 09:53:09 -07:00
|
|
|
|
|
|
|
// Get position as a PATH_LENGTH-bit bitstring (little-endian bit order).
|
|
|
|
let pos: [Option<bool>; PATH_LENGTH] = {
|
|
|
|
let pos: Option<[bool; PATH_LENGTH]> = self.leaf_pos.map(|pos| i2lebsp(pos as u64));
|
2021-06-28 02:06:39 -07:00
|
|
|
transpose_option_array(pos)
|
2021-06-04 09:53:09 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
let Q = self.domain.Q();
|
|
|
|
|
|
|
|
let mut node = leaf;
|
2021-06-28 02:06:39 -07:00
|
|
|
for (l, ((sibling, pos), chip)) in path.iter().zip(pos.iter()).zip(chips).enumerate() {
|
2021-11-29 12:39:41 -08:00
|
|
|
// `l` = MERKLE_DEPTH - layer - 1, which is the index obtained from
|
2021-06-04 09:53:09 -07:00
|
|
|
// enumerating this Merkle path (going from leaf to root).
|
|
|
|
// For example, when `layer = 31` (the first sibling on the Merkle path),
|
2021-06-28 02:06:39 -07:00
|
|
|
// we have `l` = 32 - 31 - 1 = 0.
|
2021-06-04 09:53:09 -07:00
|
|
|
// On the other hand, when `layer = 0` (the final sibling on the Merkle path),
|
2021-06-28 02:06:39 -07:00
|
|
|
// we have `l` = 32 - 0 - 1 = 31.
|
2022-05-05 20:17:52 -07:00
|
|
|
|
|
|
|
// Constrain which of (node, sibling) is (left, right) with a conditional swap
|
|
|
|
// tied to the current bit of the position.
|
2021-06-04 09:53:09 -07:00
|
|
|
let pair = {
|
|
|
|
let pair = (node, *sibling);
|
|
|
|
|
|
|
|
// Swap node and sibling if needed
|
|
|
|
chip.swap(layouter.namespace(|| "node position"), pair, *pos)?
|
|
|
|
};
|
|
|
|
|
2022-05-05 20:17:52 -07:00
|
|
|
// Compute the node in layer l from its children:
|
|
|
|
// M^l_i = MerkleCRH(l, M^{l+1}_{2i}, M^{l+1}_{2i+1})
|
2021-06-04 09:53:09 -07:00
|
|
|
node = chip.hash_layer(
|
2022-05-05 20:17:52 -07:00
|
|
|
layouter.namespace(|| format!("MerkleCRH({}, left, right)", l)),
|
2021-06-04 09:53:09 -07:00
|
|
|
Q,
|
2021-06-28 02:06:39 -07:00
|
|
|
l,
|
2021-06-04 09:53:09 -07:00
|
|
|
pair.0,
|
|
|
|
pair.1,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(node)
|
|
|
|
}
|
|
|
|
}
|
2021-06-05 01:51:21 -07:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
pub mod tests {
|
|
|
|
use super::{
|
|
|
|
chip::{MerkleChip, MerkleConfig},
|
2022-01-26 16:13:49 -08:00
|
|
|
MerklePath,
|
2021-06-05 01:51:21 -07:00
|
|
|
};
|
|
|
|
|
2022-01-27 13:53:10 -08:00
|
|
|
use crate::{
|
2022-01-27 13:15:41 -08:00
|
|
|
ecc::tests::TestFixedBases,
|
|
|
|
sinsemilla::{
|
|
|
|
chip::SinsemillaChip,
|
|
|
|
tests::{TestCommitDomain, TestHashDomain},
|
|
|
|
HashDomains,
|
|
|
|
},
|
|
|
|
utilities::{i2lebsp, lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
|
2021-06-05 01:51:21 -07:00
|
|
|
};
|
|
|
|
|
2022-01-27 13:15:41 -08:00
|
|
|
use group::ff::{Field, PrimeField, PrimeFieldBits};
|
2022-01-27 15:28:02 -08:00
|
|
|
use halo2_proofs::{
|
2021-07-08 18:56:27 -07:00
|
|
|
circuit::{Layouter, SimpleFloorPlanner},
|
2021-06-05 01:51:21 -07:00
|
|
|
dev::MockProver,
|
|
|
|
pasta::pallas,
|
2021-07-08 18:56:27 -07:00
|
|
|
plonk::{Circuit, ConstraintSystem, Error},
|
2021-06-05 01:51:21 -07:00
|
|
|
};
|
|
|
|
|
2022-01-18 06:30:55 -08:00
|
|
|
use rand::{rngs::OsRng, RngCore};
|
2022-01-27 13:15:41 -08:00
|
|
|
use std::{convert::TryInto, iter};
|
2021-06-05 01:51:21 -07:00
|
|
|
|
2022-01-26 16:13:49 -08:00
|
|
|
const MERKLE_DEPTH: usize = 32;
|
|
|
|
|
2021-07-08 18:56:27 -07:00
|
|
|
#[derive(Default)]
|
2021-06-05 01:51:21 -07:00
|
|
|
struct MyCircuit {
|
|
|
|
leaf: Option<pallas::Base>,
|
|
|
|
leaf_pos: Option<u32>,
|
2021-11-29 12:39:41 -08:00
|
|
|
merkle_path: Option<[pallas::Base; MERKLE_DEPTH]>,
|
2021-06-05 01:51:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Circuit<pallas::Base> for MyCircuit {
|
2021-08-18 23:59:39 -07:00
|
|
|
type Config = (
|
2022-01-27 13:15:41 -08:00
|
|
|
MerkleConfig<TestHashDomain, TestCommitDomain, TestFixedBases>,
|
|
|
|
MerkleConfig<TestHashDomain, TestCommitDomain, TestFixedBases>,
|
2021-08-18 23:59:39 -07:00
|
|
|
);
|
2021-07-08 18:56:27 -07:00
|
|
|
type FloorPlanner = SimpleFloorPlanner;
|
|
|
|
|
|
|
|
fn without_witnesses(&self) -> Self {
|
|
|
|
Self::default()
|
|
|
|
}
|
2021-06-05 01:51:21 -07:00
|
|
|
|
|
|
|
fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
|
|
|
|
let advices = [
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
];
|
|
|
|
|
|
|
|
// Shared fixed column for loading constants
|
2021-07-20 01:22:08 -07:00
|
|
|
let constants = meta.fixed_column();
|
|
|
|
meta.enable_constant(constants);
|
2021-06-05 01:51:21 -07:00
|
|
|
|
2021-07-23 05:25:02 -07:00
|
|
|
// NB: In the actual Action circuit, these fixed columns will be reused
|
|
|
|
// by other chips. For this test, we are creating new fixed columns.
|
|
|
|
let fixed_y_q_1 = meta.fixed_column();
|
|
|
|
let fixed_y_q_2 = meta.fixed_column();
|
|
|
|
|
2021-06-05 01:51:21 -07:00
|
|
|
// Fixed columns for the Sinsemilla generator lookup table
|
|
|
|
let lookup = (
|
2021-07-27 10:32:32 -07:00
|
|
|
meta.lookup_table_column(),
|
|
|
|
meta.lookup_table_column(),
|
|
|
|
meta.lookup_table_column(),
|
2021-06-05 01:51:21 -07:00
|
|
|
);
|
|
|
|
|
2021-07-21 07:59:08 -07:00
|
|
|
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], lookup.0);
|
|
|
|
|
|
|
|
let sinsemilla_config_1 = SinsemillaChip::configure(
|
|
|
|
meta,
|
|
|
|
advices[5..].try_into().unwrap(),
|
2021-07-21 08:47:49 -07:00
|
|
|
advices[7],
|
2021-07-23 05:25:02 -07:00
|
|
|
fixed_y_q_1,
|
2021-07-21 07:59:08 -07:00
|
|
|
lookup,
|
2021-11-30 11:31:42 -08:00
|
|
|
range_check,
|
2021-07-21 07:59:08 -07:00
|
|
|
);
|
2021-06-05 01:51:21 -07:00
|
|
|
let config1 = MerkleChip::configure(meta, sinsemilla_config_1);
|
|
|
|
|
2021-07-21 07:59:08 -07:00
|
|
|
let sinsemilla_config_2 = SinsemillaChip::configure(
|
|
|
|
meta,
|
|
|
|
advices[..5].try_into().unwrap(),
|
2021-07-21 08:47:49 -07:00
|
|
|
advices[2],
|
2021-07-23 05:25:02 -07:00
|
|
|
fixed_y_q_2,
|
2021-07-21 07:59:08 -07:00
|
|
|
lookup,
|
|
|
|
range_check,
|
|
|
|
);
|
2021-06-05 01:51:21 -07:00
|
|
|
let config2 = MerkleChip::configure(meta, sinsemilla_config_2);
|
|
|
|
|
|
|
|
(config1, config2)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn synthesize(
|
|
|
|
&self,
|
|
|
|
config: Self::Config,
|
2021-07-08 18:56:27 -07:00
|
|
|
mut layouter: impl Layouter<pallas::Base>,
|
2021-06-05 01:51:21 -07:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Load generator table (shared across both configs)
|
2022-01-27 13:15:41 -08:00
|
|
|
SinsemillaChip::<TestHashDomain, TestCommitDomain, TestFixedBases>::load(
|
2021-08-18 23:59:39 -07:00
|
|
|
config.0.sinsemilla_config.clone(),
|
|
|
|
&mut layouter,
|
|
|
|
)?;
|
2021-06-05 01:51:21 -07:00
|
|
|
|
|
|
|
// Construct Merkle chips which will be placed side-by-side in the circuit.
|
|
|
|
let chip_1 = MerkleChip::construct(config.0.clone());
|
|
|
|
let chip_2 = MerkleChip::construct(config.1.clone());
|
|
|
|
|
|
|
|
let leaf = chip_1.load_private(
|
|
|
|
layouter.namespace(|| ""),
|
2022-01-28 07:38:22 -08:00
|
|
|
config.0.cond_swap_config.a(),
|
2021-06-05 01:51:21 -07:00
|
|
|
self.leaf,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let path = MerklePath {
|
2022-05-05 10:42:26 -07:00
|
|
|
chips: [chip_1, chip_2],
|
2022-01-27 13:15:41 -08:00
|
|
|
domain: TestHashDomain,
|
2021-06-05 01:51:21 -07:00
|
|
|
leaf_pos: self.leaf_pos,
|
|
|
|
path: self.merkle_path,
|
|
|
|
};
|
|
|
|
|
|
|
|
let computed_final_root =
|
|
|
|
path.calculate_root(layouter.namespace(|| "calculate root"), leaf)?;
|
|
|
|
|
2021-07-15 03:25:22 -07:00
|
|
|
if let Some(leaf_pos) = self.leaf_pos {
|
|
|
|
// The expected final root
|
2022-01-27 13:15:41 -08:00
|
|
|
let final_root = self.merkle_path.unwrap().iter().enumerate().fold(
|
|
|
|
self.leaf.unwrap(),
|
|
|
|
|node, (l, sibling)| {
|
|
|
|
let l = l as u8;
|
|
|
|
let (left, right) = if leaf_pos & (1 << l) == 0 {
|
|
|
|
(&node, sibling)
|
|
|
|
} else {
|
|
|
|
(sibling, &node)
|
|
|
|
};
|
|
|
|
|
2022-05-08 19:54:04 -07:00
|
|
|
use crate::sinsemilla::primitives as sinsemilla;
|
2022-01-27 13:15:41 -08:00
|
|
|
let merkle_crh = sinsemilla::HashDomain::from_Q(TestHashDomain.Q().into());
|
|
|
|
|
|
|
|
merkle_crh
|
|
|
|
.hash(
|
|
|
|
iter::empty()
|
|
|
|
.chain(i2lebsp::<10>(l as u64).iter().copied())
|
|
|
|
.chain(
|
|
|
|
left.to_le_bits()
|
|
|
|
.iter()
|
2022-05-04 16:36:18 -07:00
|
|
|
.by_vals()
|
2022-01-27 13:15:41 -08:00
|
|
|
.take(pallas::Base::NUM_BITS as usize),
|
|
|
|
)
|
|
|
|
.chain(
|
|
|
|
right
|
|
|
|
.to_le_bits()
|
|
|
|
.iter()
|
2022-05-04 16:36:18 -07:00
|
|
|
.by_vals()
|
2022-01-27 13:15:41 -08:00
|
|
|
.take(pallas::Base::NUM_BITS as usize),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.unwrap_or(pallas::Base::zero())
|
|
|
|
},
|
|
|
|
);
|
2021-06-05 01:51:21 -07:00
|
|
|
|
2021-07-15 03:25:22 -07:00
|
|
|
// Check the computed final root against the expected final root.
|
2022-01-27 13:15:41 -08:00
|
|
|
assert_eq!(computed_final_root.value().unwrap(), &final_root);
|
2021-07-15 03:25:22 -07:00
|
|
|
}
|
2021-06-05 01:51:21 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn merkle_chip() {
|
2022-01-18 06:30:55 -08:00
|
|
|
let mut rng = OsRng;
|
|
|
|
|
2021-06-05 01:51:21 -07:00
|
|
|
// Choose a random leaf and position
|
2022-01-18 06:30:55 -08:00
|
|
|
let leaf = pallas::Base::random(rng);
|
|
|
|
let pos = rng.next_u32();
|
2021-06-05 01:51:21 -07:00
|
|
|
|
|
|
|
// Choose a path of random inner nodes
|
2021-11-29 12:39:41 -08:00
|
|
|
let path: Vec<_> = (0..(MERKLE_DEPTH))
|
2022-01-18 06:30:55 -08:00
|
|
|
.map(|_| pallas::Base::random(rng))
|
2021-06-05 01:51:21 -07:00
|
|
|
.collect();
|
|
|
|
|
2021-09-01 03:06:08 -07:00
|
|
|
// The root is provided as a public input in the Orchard circuit.
|
2021-06-05 01:51:21 -07:00
|
|
|
|
|
|
|
let circuit = MyCircuit {
|
|
|
|
leaf: Some(leaf),
|
|
|
|
leaf_pos: Some(pos),
|
|
|
|
merkle_path: Some(path.try_into().unwrap()),
|
|
|
|
};
|
|
|
|
|
|
|
|
let prover = MockProver::run(11, &circuit, vec![]).unwrap();
|
|
|
|
assert_eq!(prover.verify(), Ok(()))
|
|
|
|
}
|
2021-07-15 03:25:22 -07:00
|
|
|
|
|
|
|
#[cfg(feature = "dev-graph")]
|
|
|
|
#[test]
|
|
|
|
fn print_merkle_chip() {
|
|
|
|
use plotters::prelude::*;
|
|
|
|
|
|
|
|
let root = BitMapBackend::new("merkle-path-layout.png", (1024, 7680)).into_drawing_area();
|
|
|
|
root.fill(&WHITE).unwrap();
|
|
|
|
let root = root.titled("MerkleCRH Path", ("sans-serif", 60)).unwrap();
|
|
|
|
|
|
|
|
let circuit = MyCircuit::default();
|
2022-01-27 15:28:02 -08:00
|
|
|
halo2_proofs::dev::CircuitLayout::default()
|
2021-07-15 03:25:22 -07:00
|
|
|
.show_labels(false)
|
2021-07-26 05:54:27 -07:00
|
|
|
.render(11, &circuit, &root)
|
2021-07-15 03:25:22 -07:00
|
|
|
.unwrap();
|
|
|
|
}
|
2021-06-05 01:51:21 -07:00
|
|
|
}
|