2021-06-20 20:36:53 -07:00
|
|
|
use crate::commitment::{CTree, Witness};
|
2021-06-20 18:41:47 -07:00
|
|
|
use rayon::prelude::IntoParallelIterator;
|
|
|
|
use rayon::prelude::*;
|
|
|
|
use zcash_primitives::merkle_tree::Hashable;
|
|
|
|
use zcash_primitives::sapling::Node;
|
|
|
|
|
2021-06-20 20:36:53 -07:00
|
|
|
trait Builder<T, C> {
|
|
|
|
fn collect(&mut self, commitments: &[Node], context: &C) -> usize;
|
2021-06-20 18:41:47 -07:00
|
|
|
fn up(&mut self);
|
|
|
|
fn finished(&self) -> bool;
|
2021-06-20 22:07:46 -07:00
|
|
|
fn finalize(self, context: &C) -> T;
|
2021-06-20 18:41:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
struct CTreeBuilder {
|
|
|
|
left: Option<Node>,
|
|
|
|
right: Option<Node>,
|
|
|
|
prev_tree: CTree,
|
|
|
|
next_tree: CTree,
|
2021-06-20 20:36:53 -07:00
|
|
|
start: usize,
|
2021-06-24 05:08:20 -07:00
|
|
|
total_len: usize,
|
2021-06-20 18:41:47 -07:00
|
|
|
depth: usize,
|
2021-06-20 20:36:53 -07:00
|
|
|
offset: Option<Node>,
|
2021-06-20 18:41:47 -07:00
|
|
|
}
|
|
|
|
|
2021-06-20 20:36:53 -07:00
|
|
|
impl Builder<CTree, ()> for CTreeBuilder {
|
|
|
|
fn collect(&mut self, commitments: &[Node], _context: &()) -> usize {
|
2021-06-20 18:41:47 -07:00
|
|
|
assert!(self.right.is_none() || self.left.is_some()); // R can't be set without L
|
|
|
|
|
|
|
|
let offset: Option<Node>;
|
|
|
|
let m: usize;
|
|
|
|
|
|
|
|
if self.left.is_some() && self.right.is_none() {
|
|
|
|
offset = self.left;
|
|
|
|
m = commitments.len() + 1;
|
|
|
|
} else {
|
|
|
|
offset = None;
|
|
|
|
m = commitments.len();
|
|
|
|
};
|
|
|
|
|
2021-06-24 05:08:20 -07:00
|
|
|
let n =
|
|
|
|
if self.total_len > 0 {
|
|
|
|
if self.depth == 0 {
|
|
|
|
if m % 2 == 0 {
|
|
|
|
self.next_tree.left = Some(*Self::get(commitments, m - 2, &offset));
|
|
|
|
self.next_tree.right = Some(*Self::get(commitments, m - 1, &offset));
|
|
|
|
m - 2
|
|
|
|
} else {
|
|
|
|
self.next_tree.left = Some(*Self::get(commitments, m - 1, &offset));
|
|
|
|
self.next_tree.right = None;
|
|
|
|
m - 1
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if m % 2 == 0 {
|
|
|
|
self.next_tree.parents.push(None);
|
|
|
|
m
|
|
|
|
} else {
|
|
|
|
let last_node = Self::get(commitments, m - 1, &offset);
|
|
|
|
self.next_tree.parents.push(Some(*last_node));
|
|
|
|
m - 1
|
|
|
|
}
|
|
|
|
}
|
2021-06-20 18:41:47 -07:00
|
|
|
}
|
2021-06-24 05:08:20 -07:00
|
|
|
else { 0 };
|
2021-06-20 18:41:47 -07:00
|
|
|
assert_eq!(n % 2, 0);
|
|
|
|
|
2021-06-20 20:36:53 -07:00
|
|
|
self.offset = offset;
|
|
|
|
n
|
2021-06-20 18:41:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn up(&mut self) {
|
|
|
|
let h = if self.left.is_some() && self.right.is_some() {
|
2021-06-20 22:07:46 -07:00
|
|
|
Some(Node::combine(
|
|
|
|
self.depth,
|
|
|
|
&self.left.unwrap(),
|
|
|
|
&self.right.unwrap(),
|
|
|
|
))
|
2021-06-20 18:41:47 -07:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
let (l, r) = match self.prev_tree.parents.get(self.depth) {
|
|
|
|
Some(Some(p)) => (Some(*p), h),
|
|
|
|
Some(None) => (h, None),
|
|
|
|
None => (h, None),
|
|
|
|
};
|
|
|
|
|
|
|
|
self.left = l;
|
|
|
|
self.right = r;
|
2021-06-20 20:36:53 -07:00
|
|
|
|
|
|
|
assert!(self.start % 2 == 0 || self.offset.is_some());
|
|
|
|
self.start /= 2;
|
|
|
|
|
2021-06-20 18:41:47 -07:00
|
|
|
self.depth += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn finished(&self) -> bool {
|
|
|
|
self.depth >= self.prev_tree.parents.len() && self.left.is_none() && self.right.is_none()
|
|
|
|
}
|
|
|
|
|
2021-06-20 22:07:46 -07:00
|
|
|
fn finalize(self, _context: &()) -> CTree {
|
2021-06-24 05:08:20 -07:00
|
|
|
if self.total_len > 0 {
|
|
|
|
self.next_tree
|
|
|
|
} else {
|
|
|
|
self.prev_tree
|
|
|
|
}
|
2021-06-20 18:41:47 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CTreeBuilder {
|
2021-06-24 05:08:20 -07:00
|
|
|
fn new(prev_tree: CTree, len: usize) -> CTreeBuilder {
|
2021-06-20 20:36:53 -07:00
|
|
|
let start = prev_tree.get_position();
|
2021-06-20 18:41:47 -07:00
|
|
|
CTreeBuilder {
|
|
|
|
left: prev_tree.left,
|
|
|
|
right: prev_tree.right,
|
|
|
|
prev_tree,
|
|
|
|
next_tree: CTree::new(),
|
2021-06-20 20:36:53 -07:00
|
|
|
start,
|
2021-06-24 05:08:20 -07:00
|
|
|
total_len: len,
|
2021-06-20 18:41:47 -07:00
|
|
|
depth: 0,
|
2021-06-20 20:36:53 -07:00
|
|
|
offset: None,
|
2021-06-20 18:41:47 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
2021-06-20 22:07:46 -07:00
|
|
|
fn get_opt<'a>(
|
|
|
|
commitments: &'a [Node],
|
|
|
|
index: usize,
|
|
|
|
offset: &'a Option<Node>,
|
|
|
|
) -> Option<&'a Node> {
|
|
|
|
if offset.is_some() {
|
|
|
|
if index > 0 {
|
|
|
|
commitments.get(index - 1)
|
|
|
|
} else {
|
|
|
|
offset.as_ref()
|
2021-06-20 18:41:47 -07:00
|
|
|
}
|
2021-06-20 22:07:46 -07:00
|
|
|
} else {
|
|
|
|
commitments.get(index)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn get<'a>(commitments: &'a [Node], index: usize, offset: &'a Option<Node>) -> &'a Node {
|
|
|
|
Self::get_opt(commitments, index, offset).unwrap()
|
|
|
|
}
|
|
|
|
|
2021-06-24 05:08:20 -07:00
|
|
|
fn adjusted_start(&self, prev: &Option<Node>, _depth: usize) -> usize {
|
|
|
|
if prev.is_some() {
|
2021-06-20 22:07:46 -07:00
|
|
|
self.start - 1
|
|
|
|
} else {
|
|
|
|
self.start
|
|
|
|
}
|
|
|
|
}
|
2021-06-20 18:41:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn combine_level(commitments: &mut [Node], offset: Option<Node>, n: usize, depth: usize) -> usize {
|
|
|
|
assert_eq!(n % 2, 0);
|
|
|
|
|
|
|
|
let nn = n / 2;
|
|
|
|
let next_level: Vec<Node> = (0..nn)
|
|
|
|
.into_par_iter()
|
|
|
|
.map(|i| {
|
|
|
|
Node::combine(
|
|
|
|
depth,
|
2021-06-20 22:07:46 -07:00
|
|
|
CTreeBuilder::get(commitments, 2 * i, &offset),
|
|
|
|
CTreeBuilder::get(commitments, 2 * i + 1, &offset),
|
2021-06-20 18:41:47 -07:00
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
commitments[0..nn].copy_from_slice(&next_level);
|
|
|
|
nn
|
|
|
|
}
|
|
|
|
|
2021-06-20 20:36:53 -07:00
|
|
|
struct WitnessBuilder {
|
|
|
|
witness: Witness,
|
|
|
|
p: usize,
|
|
|
|
inside: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl WitnessBuilder {
|
|
|
|
fn new(tree_builder: &CTreeBuilder, prev_witness: Witness, count: usize) -> WitnessBuilder {
|
|
|
|
let position = prev_witness.position;
|
|
|
|
let inside = position >= tree_builder.start && position < tree_builder.start + count;
|
|
|
|
WitnessBuilder {
|
|
|
|
witness: prev_witness,
|
|
|
|
p: position,
|
|
|
|
inside,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Builder<Witness, CTreeBuilder> for WitnessBuilder {
|
|
|
|
fn collect(&mut self, commitments: &[Node], context: &CTreeBuilder) -> usize {
|
|
|
|
let offset = context.offset;
|
|
|
|
let depth = context.depth;
|
|
|
|
|
2021-06-20 22:07:46 -07:00
|
|
|
let tree = &mut self.witness.tree;
|
2021-06-21 07:04:45 -07:00
|
|
|
let right = if depth != 0 { context.right } else { None };
|
2021-06-20 22:07:46 -07:00
|
|
|
|
2021-06-20 20:36:53 -07:00
|
|
|
if self.inside {
|
2021-06-21 07:04:45 -07:00
|
|
|
let rp = self.p - context.adjusted_start(&offset, depth);
|
2021-06-20 20:36:53 -07:00
|
|
|
if depth == 0 {
|
|
|
|
if self.p % 2 == 1 {
|
2021-06-20 22:07:46 -07:00
|
|
|
tree.left = Some(*CTreeBuilder::get(commitments, rp - 1, &offset));
|
|
|
|
tree.right = Some(*CTreeBuilder::get(commitments, rp, &offset));
|
|
|
|
} else {
|
|
|
|
tree.left = Some(*CTreeBuilder::get(commitments, rp, &offset));
|
|
|
|
tree.right = None;
|
2021-06-20 20:36:53 -07:00
|
|
|
}
|
2021-06-20 22:07:46 -07:00
|
|
|
} else {
|
|
|
|
if self.p % 2 == 1 {
|
|
|
|
tree.parents
|
|
|
|
.push(Some(*CTreeBuilder::get(commitments, rp - 1, &offset)));
|
|
|
|
} else if self.p != 0 {
|
|
|
|
tree.parents.push(None);
|
2021-06-20 20:36:53 -07:00
|
|
|
}
|
|
|
|
}
|
2021-06-20 22:07:46 -07:00
|
|
|
}
|
|
|
|
|
2021-06-24 05:08:20 -07:00
|
|
|
// println!("D {}", depth);
|
|
|
|
// println!("O {:?}", offset.map(|r| hex::encode(r.repr)));
|
|
|
|
// println!("R {:?}", right.map(|r| hex::encode(r.repr)));
|
|
|
|
// for c in commitments.iter() {
|
|
|
|
// println!("{}", hex::encode(c.repr));
|
|
|
|
// }
|
2021-06-20 22:07:46 -07:00
|
|
|
let p1 = self.p + 1;
|
2021-06-21 07:04:45 -07:00
|
|
|
let has_p1 = p1 >= context.adjusted_start(&right, depth) && p1 < context.start + commitments.len();
|
2021-06-20 22:07:46 -07:00
|
|
|
if has_p1 {
|
2021-06-21 07:04:45 -07:00
|
|
|
let p1 = CTreeBuilder::get(commitments, p1 - context.adjusted_start(&right, depth), &right);
|
2021-06-20 22:07:46 -07:00
|
|
|
if depth == 0 {
|
|
|
|
if tree.right.is_none() {
|
|
|
|
self.witness.filled.push(*p1);
|
2021-06-20 20:36:53 -07:00
|
|
|
}
|
2021-06-20 22:07:46 -07:00
|
|
|
} else {
|
|
|
|
if depth - 1 >= tree.parents.len() || tree.parents[depth - 1].is_none() {
|
|
|
|
self.witness.filled.push(*p1);
|
2021-06-20 20:36:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
0
|
|
|
|
}
|
|
|
|
|
|
|
|
fn up(&mut self) {
|
|
|
|
self.p /= 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn finished(&self) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2021-06-20 22:07:46 -07:00
|
|
|
fn finalize(mut self, context: &CTreeBuilder) -> Witness {
|
2021-06-24 05:08:20 -07:00
|
|
|
if context.total_len == 0 {
|
2021-06-21 07:04:45 -07:00
|
|
|
self.witness.cursor = CTree::new();
|
2021-06-24 05:08:20 -07:00
|
|
|
|
|
|
|
let mut final_position = context.prev_tree.get_position() as u32;
|
|
|
|
let mut witness_position = self.witness.tree.get_position() as u32;
|
|
|
|
assert_ne!(witness_position, 0);
|
|
|
|
witness_position = witness_position - 1;
|
|
|
|
|
|
|
|
// look for first not equal bit in MSB order
|
|
|
|
final_position = final_position.reverse_bits();
|
|
|
|
witness_position = witness_position.reverse_bits();
|
|
|
|
let mut bit: i32 = 31;
|
|
|
|
// reverse bits because it is easier to do in LSB
|
|
|
|
// it should not underflow because these numbers are not equal
|
|
|
|
while bit >= 0 {
|
|
|
|
if final_position & 1 != witness_position & 1 {
|
|
|
|
break;
|
2021-06-20 22:07:46 -07:00
|
|
|
}
|
2021-06-24 05:08:20 -07:00
|
|
|
final_position >>= 1;
|
|
|
|
witness_position >>= 1;
|
|
|
|
bit -= 1;
|
|
|
|
}
|
|
|
|
// look for the first bit set in final_position after
|
|
|
|
final_position >>= 1;
|
|
|
|
bit -= 1;
|
|
|
|
while bit >= 0 {
|
|
|
|
if final_position & 1 == 1 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
final_position >>= 1;
|
|
|
|
bit -= 1;
|
|
|
|
}
|
|
|
|
if bit >= 0 {
|
|
|
|
self.witness.cursor = context.prev_tree.clone_trimmed(bit as usize)
|
2021-06-20 22:07:46 -07:00
|
|
|
}
|
2021-06-21 07:04:45 -07:00
|
|
|
}
|
2021-06-20 20:36:53 -07:00
|
|
|
self.witness
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-20 18:41:47 -07:00
|
|
|
#[allow(dead_code)]
|
2021-06-21 07:04:45 -07:00
|
|
|
pub fn advance_tree(
|
2021-06-20 22:07:46 -07:00
|
|
|
prev_tree: CTree,
|
|
|
|
prev_witnesses: &[Witness],
|
|
|
|
mut commitments: &mut [Node],
|
|
|
|
) -> (CTree, Vec<Witness>) {
|
2021-06-24 05:08:20 -07:00
|
|
|
let mut builder = CTreeBuilder::new(prev_tree, commitments.len());
|
2021-06-20 22:07:46 -07:00
|
|
|
let mut witness_builders: Vec<_> = prev_witnesses
|
|
|
|
.iter()
|
|
|
|
.map(|witness| WitnessBuilder::new(&builder, witness.clone(), commitments.len()))
|
|
|
|
.collect();
|
2021-06-20 18:41:47 -07:00
|
|
|
while !commitments.is_empty() || !builder.finished() {
|
2021-06-20 20:36:53 -07:00
|
|
|
let n = builder.collect(commitments, &());
|
|
|
|
for b in witness_builders.iter_mut() {
|
|
|
|
b.collect(commitments, &builder);
|
|
|
|
}
|
|
|
|
let nn = combine_level(commitments, builder.offset, n, builder.depth);
|
2021-06-20 18:41:47 -07:00
|
|
|
builder.up();
|
2021-06-20 20:36:53 -07:00
|
|
|
for b in witness_builders.iter_mut() {
|
|
|
|
b.up();
|
|
|
|
}
|
2021-06-24 05:08:20 -07:00
|
|
|
commitments = &mut commitments[0..nn];
|
2021-06-20 18:41:47 -07:00
|
|
|
}
|
|
|
|
|
2021-06-20 22:07:46 -07:00
|
|
|
let witnesses = witness_builders
|
|
|
|
.into_iter()
|
|
|
|
.map(|b| b.finalize(&builder))
|
|
|
|
.collect();
|
|
|
|
let tree = builder.finalize(&());
|
2021-06-20 20:36:53 -07:00
|
|
|
(tree, witnesses)
|
2021-06-20 18:41:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
2021-06-20 22:07:46 -07:00
|
|
|
#[allow(unused_imports)]
|
2021-06-20 18:41:47 -07:00
|
|
|
mod tests {
|
2021-06-20 22:07:46 -07:00
|
|
|
use crate::builder::advance_tree;
|
2021-06-20 20:36:53 -07:00
|
|
|
use crate::commitment::{CTree, Witness};
|
|
|
|
use zcash_primitives::merkle_tree::{CommitmentTree, IncrementalWitness};
|
2021-06-20 22:07:46 -07:00
|
|
|
use zcash_primitives::sapling::Node;
|
2021-06-21 17:33:13 -07:00
|
|
|
use crate::chain::DecryptedNote;
|
2021-06-24 05:08:20 -07:00
|
|
|
use crate::print::{print_witness, print_witness2, print_tree, print_ctree};
|
2021-06-20 18:41:47 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_advance_tree() {
|
2021-06-24 05:08:20 -07:00
|
|
|
for num_nodes in 1..=10 {
|
|
|
|
for num_chunks in 1..=10 {
|
|
|
|
test_advance_tree_helper(num_nodes, num_chunks, 100.0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
test_advance_tree_helper(100, 50, 1.0);
|
|
|
|
// test_advance_tree_helper(2, 10, 100.0);
|
|
|
|
// test_advance_tree_helper(1, 40, 100.0);
|
|
|
|
// test_advance_tree_helper(10, 2, 100.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_advance_tree_helper(num_nodes: usize, num_chunks: usize, witness_percent: f64) {
|
|
|
|
let witness_freq = (100.0 / witness_percent) as usize;
|
2021-06-20 22:07:46 -07:00
|
|
|
|
2021-06-20 18:41:47 -07:00
|
|
|
let mut tree1: CommitmentTree<Node> = CommitmentTree::empty();
|
|
|
|
let mut tree2 = CTree::new();
|
2021-06-20 20:36:53 -07:00
|
|
|
let mut ws: Vec<IncrementalWitness<Node>> = vec![];
|
|
|
|
let mut ws2: Vec<Witness> = vec![];
|
2021-06-24 05:08:20 -07:00
|
|
|
for i in 0..num_chunks {
|
2021-06-21 07:04:45 -07:00
|
|
|
println!("{}", i);
|
2021-06-20 22:07:46 -07:00
|
|
|
let mut nodes: Vec<_> = vec![];
|
2021-06-24 05:08:20 -07:00
|
|
|
for j in 0..num_nodes {
|
2021-06-20 18:41:47 -07:00
|
|
|
let mut bb = [0u8; 32];
|
2021-06-24 05:08:20 -07:00
|
|
|
let v = i * num_nodes + j;
|
2021-06-20 18:41:47 -07:00
|
|
|
bb[0..8].copy_from_slice(&v.to_be_bytes());
|
|
|
|
let node = Node::new(bb);
|
|
|
|
tree1.append(node).unwrap();
|
2021-06-20 20:36:53 -07:00
|
|
|
for w in ws.iter_mut() {
|
|
|
|
w.append(node).unwrap();
|
|
|
|
}
|
2021-06-21 07:04:45 -07:00
|
|
|
if v % witness_freq == 0 {
|
2021-06-24 05:08:20 -07:00
|
|
|
// if v == 0 {
|
2021-06-20 20:36:53 -07:00
|
|
|
let w = IncrementalWitness::from_tree(&tree1);
|
|
|
|
ws.push(w);
|
2021-06-21 17:33:13 -07:00
|
|
|
ws2.push(Witness::new(v, 0, None));
|
2021-06-20 20:36:53 -07:00
|
|
|
}
|
2021-06-20 22:07:46 -07:00
|
|
|
nodes.push(node);
|
|
|
|
}
|
2021-06-20 18:41:47 -07:00
|
|
|
|
2021-06-20 20:36:53 -07:00
|
|
|
let (new_tree, new_witnesses) = advance_tree(tree2, &ws2, &mut nodes);
|
|
|
|
tree2 = new_tree;
|
|
|
|
ws2 = new_witnesses;
|
2021-06-20 18:41:47 -07:00
|
|
|
}
|
2021-06-20 22:07:46 -07:00
|
|
|
|
2021-06-24 05:08:20 -07:00
|
|
|
// Push an empty block
|
|
|
|
// It will calculate the tail of the tree
|
|
|
|
// This step is required at the end of a series of chunks
|
|
|
|
let (new_tree, new_witnesses) = advance_tree(tree2, &ws2, &mut []);
|
|
|
|
tree2 = new_tree;
|
|
|
|
ws2 = new_witnesses;
|
|
|
|
|
2021-06-20 22:07:46 -07:00
|
|
|
// check final state
|
2021-06-20 18:41:47 -07:00
|
|
|
let mut bb1: Vec<u8> = vec![];
|
|
|
|
tree1.write(&mut bb1).unwrap();
|
|
|
|
|
|
|
|
let mut bb2: Vec<u8> = vec![];
|
|
|
|
tree2.write(&mut bb2).unwrap();
|
|
|
|
|
|
|
|
let equal = bb1.as_slice() == bb2.as_slice();
|
2021-06-24 05:08:20 -07:00
|
|
|
if !equal {
|
|
|
|
println!("FAILED FINAL STATE");
|
|
|
|
print_tree(&tree1);
|
|
|
|
print_ctree(&tree2);
|
|
|
|
}
|
2021-06-20 18:41:47 -07:00
|
|
|
|
2021-06-20 22:07:46 -07:00
|
|
|
println!("# witnesses = {}", ws.len());
|
|
|
|
|
|
|
|
// check witnesses
|
|
|
|
let mut failed_index: Option<usize> = None;
|
|
|
|
for (i, (w1, w2)) in ws.iter().zip(&ws2).enumerate() {
|
|
|
|
let mut bb1: Vec<u8> = vec![];
|
|
|
|
w1.write(&mut bb1).unwrap();
|
|
|
|
|
|
|
|
let mut bb2: Vec<u8> = vec![];
|
|
|
|
w2.write(&mut bb2).unwrap();
|
|
|
|
|
|
|
|
if bb1.as_slice() != bb2.as_slice() {
|
|
|
|
failed_index = Some(i);
|
2021-06-24 05:08:20 -07:00
|
|
|
println!("FAILED AT {}", i);
|
|
|
|
if let Some(ref c) = w1.cursor {
|
|
|
|
print_tree(c);
|
|
|
|
}
|
|
|
|
else { println!("NONE"); }
|
|
|
|
|
|
|
|
println!("GOOD");
|
|
|
|
print_witness(&w1);
|
|
|
|
println!("BAD");
|
|
|
|
print_witness2(&w2);
|
2021-06-20 22:07:46 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-21 17:33:13 -07:00
|
|
|
assert!(equal && failed_index.is_none());
|
2021-06-20 18:41:47 -07:00
|
|
|
}
|
2021-06-20 22:07:46 -07:00
|
|
|
}
|