use chia_protocol::Bytes32;
use dig_capsule::format::Bytes32 as CapsuleBytes32;
use dig_capsule::merkle::ProofStep;
use dig_chainsource_interface::ChainSource;
use crate::error::{EvidenceError, EvidenceResult};
use crate::evidence::Evidence;
use crate::range_inclusion::{RangeInclusionClaim, RangeInclusionEvidence};
use crate::root_anchor::{RootAnchorClaim, RootAnchorEvidence};
fn to_capsule_bytes(bytes: Bytes32) -> CapsuleBytes32 {
let mut raw = [0u8; 32];
raw.copy_from_slice(bytes.as_ref());
CapsuleBytes32(raw)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadIntegrityClaim {
pub store_id: Bytes32,
pub generation_root: Bytes32,
pub range_leaf: CapsuleBytes32,
pub range_path: Vec<ProofStep>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadIntegrityEvidence {
range: RangeInclusionEvidence,
anchor: RootAnchorEvidence,
}
impl ReadIntegrityEvidence {
pub fn range(&self) -> &RangeInclusionEvidence {
&self.range
}
pub fn anchor(&self) -> &RootAnchorEvidence {
&self.anchor
}
pub fn store_id(&self) -> Bytes32 {
self.anchor.store_id()
}
pub fn generation_root(&self) -> Bytes32 {
self.anchor.generation_root()
}
}
impl Evidence for ReadIntegrityEvidence {
type Claim = ReadIntegrityClaim;
fn gather<S: ChainSource>(claim: &Self::Claim, chain: &S) -> EvidenceResult<Self> {
let anchor = RootAnchorEvidence::gather(
&RootAnchorClaim {
store_id: claim.store_id,
generation_root: claim.generation_root,
},
chain,
)?;
let range = RangeInclusionEvidence::gather(
&RangeInclusionClaim {
leaf: claim.range_leaf,
path: claim.range_path.clone(),
generation_root: to_capsule_bytes(anchor.generation_root()),
},
chain,
)?;
Ok(Self { range, anchor })
}
fn verify(&self) -> EvidenceResult<()> {
self.range.verify()?;
self.anchor.verify()?;
if self.range.generation_root() != to_capsule_bytes(self.anchor.generation_root()) {
return Err(EvidenceError::RootMismatch);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use chia_puzzle_types::standard::StandardArgs;
use chia_wallet_sdk::test::Simulator;
use dig_capsule::merkle::MerkleTree;
use dig_chainsource_interface::MockChainSource;
use dig_chainsource_interface::{CoinRecord, SingletonLineage};
use dig_merkle::{mint_datastore, Owner};
struct Fixture {
source: MockChainSource,
store_id: Bytes32,
root: Bytes32,
leaf: CapsuleBytes32,
path: Vec<ProofStep>,
}
fn record(coin: chia_protocol::Coin, spent: Option<u32>) -> CoinRecord {
CoinRecord {
coin,
confirmed_height: Some(1),
spent_height: spent,
timestamp: None,
coinbase: false,
}
}
fn fixture() -> anyhow::Result<Fixture> {
let chunks: Vec<Vec<u8>> = (0..6u8).map(|i| vec![i; 16]).collect();
let tree = MerkleTree::build(&chunks);
let root_capsule = tree.root();
let proof = tree.prove(3).expect("index in range");
let root = Bytes32::new(root_capsule.0);
let mut sim = Simulator::new();
let owner = sim.bls(1_000_000);
let owner_ph: Bytes32 = StandardArgs::curry_tree_hash(owner.pk).into();
let built = mint_datastore(
owner.coin,
Owner::Standard(owner.pk),
root,
None,
None,
None,
None,
None,
owner_ph,
vec![],
0,
)?;
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
let minted = built.child.expect("child");
let store_id = minted.info.launcher_id;
let launcher_spend = built
.coin_spends
.iter()
.find(|s| s.coin.coin_id() == store_id)
.expect("launcher spend")
.clone();
let launcher = launcher_spend.coin;
let eve = minted.coin;
let source = MockChainSource::new()
.with_coin(store_id, record(launcher, Some(2)))
.with_coin(eve.coin_id(), record(eve, None))
.with_spend(store_id, launcher_spend)
.with_lineage(
store_id,
SingletonLineage::new(eve.coin_id(), [store_id, eve.coin_id()]),
);
Ok(Fixture {
source,
store_id,
root,
leaf: proof.leaf,
path: proof.path,
})
}
#[test]
fn a_genuine_read_gathers_and_verifies() -> anyhow::Result<()> {
let f = fixture()?;
let claim = ReadIntegrityClaim {
store_id: f.store_id,
generation_root: f.root,
range_leaf: f.leaf,
range_path: f.path.clone(),
};
let evidence = ReadIntegrityEvidence::gather(&claim, &f.source).expect("genuine read");
assert_eq!(evidence.store_id(), f.store_id);
assert_eq!(evidence.generation_root(), f.root);
assert!(evidence.verify().is_ok());
Ok(())
}
#[test]
fn a_range_under_an_unanchored_root_is_rejected() -> anyhow::Result<()> {
let f = fixture()?;
let claim = ReadIntegrityClaim {
store_id: f.store_id,
generation_root: Bytes32::new([0xAB; 32]), range_leaf: f.leaf,
range_path: f.path.clone(),
};
assert_eq!(
ReadIntegrityEvidence::gather(&claim, &f.source).unwrap_err(),
EvidenceError::RootNotCommitted
);
Ok(())
}
#[test]
fn a_tampered_range_leaf_is_rejected_even_with_a_genuine_anchor() -> anyhow::Result<()> {
let f = fixture()?;
let claim = ReadIntegrityClaim {
store_id: f.store_id,
generation_root: f.root, range_leaf: CapsuleBytes32([0xff; 32]), range_path: f.path.clone(),
};
assert_eq!(
ReadIntegrityEvidence::gather(&claim, &f.source).unwrap_err(),
EvidenceError::ProofDoesNotFold
);
Ok(())
}
}