use crate::traits;
use crate::types::{ViewingError, ViewingMemoOpening};
use arbitrary::{Arbitrary, Unstructured};
use arbitrary_wrappers::{ArbitraryMerkleTree, ArbitraryNullifier};
use commit::{Commitment, Committable};
use jf_cap::{
keys::{ViewerKeyPair, ViewerPubKey},
mint::MintNote,
proof::UniversalParam,
structs::{AssetCode, AssetDefinition, Nullifier, RecordCommitment},
transfer::TransferNote,
MerkleCommitment, MerkleFrontier, MerkleTree, TransactionNote,
};
use jf_primitives::merkle_tree::FilledMTBuilder;
use lazy_static::lazy_static;
use rand_chacha::{rand_core::SeedableRng, ChaChaRng};
use serde::{Deserialize, Serialize};
use snafu::Snafu;
use std::collections::{HashMap, HashSet};
use std::fmt::{Debug, Display};
use std::hash::Hash;
use std::ops::Index;
pub type NullifierSet = HashSet<ArbitraryNullifier>;
impl traits::NullifierSet for NullifierSet {
type Proof = ();
fn multi_insert(&mut self, nullifiers: &[(Nullifier, Self::Proof)]) -> Result<(), Self::Proof> {
for (n, _) in nullifiers {
self.insert((*n).into());
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, strum_macros::Display)]
pub enum TransactionKind {
Mint,
Freeze,
Unfreeze,
Send,
Receive,
Unknown,
}
impl traits::TransactionKind for TransactionKind {
fn send() -> Self {
Self::Send
}
fn receive() -> Self {
Self::Receive
}
fn mint() -> Self {
Self::Mint
}
fn freeze() -> Self {
Self::Freeze
}
fn unfreeze() -> Self {
Self::Unfreeze
}
fn unknown() -> Self {
Self::Unknown
}
}
pub type Transaction = TransactionNote;
impl traits::Transaction for TransactionNote {
type NullifierSet = NullifierSet;
type Hash = Commitment<Self>;
type Kind = TransactionKind;
fn cap(note: TransactionNote, _proofs: Vec<()>) -> Self {
note
}
fn open_viewing_memo(
&self,
assets: &HashMap<AssetCode, AssetDefinition>,
keys: &HashMap<ViewerPubKey, ViewerKeyPair>,
) -> Result<ViewingMemoOpening, ViewingError> {
match self {
Self::Transfer(xfr) => open_xfr_viewing_memo(assets, keys, xfr),
Self::Mint(mint) => open_mint_viewing_memo(keys, mint),
Self::Freeze(_) => Err(ViewingError::NoViewingMemos),
}
}
fn proven_nullifiers(&self) -> Vec<(Nullifier, ())> {
TransactionNote::nullifiers(self)
.into_iter()
.map(|n| (n, ()))
.collect()
}
fn output_commitments(&self) -> Vec<RecordCommitment> {
TransactionNote::output_commitments(self)
}
fn hash(&self) -> Self::Hash {
self.commit()
}
fn kind(&self) -> Self::Kind {
match self {
Self::Transfer(..) => TransactionKind::Send,
Self::Mint(..) => TransactionKind::Mint,
Self::Freeze(..) => TransactionKind::Freeze,
}
}
fn set_proofs(&mut self, _proofs: Vec<()>) {
}
}
pub fn open_xfr_viewing_memo(
assets: &HashMap<AssetCode, AssetDefinition>,
keys: &HashMap<ViewerPubKey, ViewerKeyPair>,
xfr: &TransferNote,
) -> Result<ViewingMemoOpening, ViewingError> {
for asset in assets.values() {
let viewing_key = &keys[asset.policy_ref().viewer_pub_key()];
if let Ok((inputs, outputs)) = viewing_key.open_transfer_viewing_memo(asset, xfr) {
return Ok(ViewingMemoOpening {
asset: asset.clone(),
inputs,
outputs,
});
}
}
Err(ViewingError::UnviewableAsset)
}
pub fn open_mint_viewing_memo(
keys: &HashMap<ViewerPubKey, ViewerKeyPair>,
mint: &MintNote,
) -> Result<ViewingMemoOpening, ViewingError> {
keys.get(mint.mint_asset_def.policy_ref().viewer_pub_key())
.ok_or(ViewingError::UnviewableAsset)
.map(|viewing_key| {
let output = viewing_key.open_mint_viewing_memo(mint).unwrap();
ViewingMemoOpening {
asset: mint.mint_asset_def.clone(),
inputs: vec![],
outputs: vec![output],
}
})
}
#[derive(Clone, Debug, Serialize, Deserialize, Snafu)]
pub enum ValidationError {
Failed { msg: String },
}
impl traits::ValidationError for ValidationError {
fn new(msg: impl Display) -> Self {
Self::Failed {
msg: msg.to_string(),
}
}
fn is_bad_nullifier_proof(&self) -> bool {
false
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Block {
transactions: Vec<Transaction>,
index: u64,
}
impl traits::Block for Block {
type Transaction = Transaction;
type Error = ValidationError;
fn add_transaction(&mut self, txn: Transaction) -> Result<(), Self::Error> {
self.transactions.push(txn);
Ok(())
}
fn txns(&self) -> Vec<Transaction> {
self.transactions.clone()
}
}
impl Block {
pub fn index(&self) -> u64 {
self.index
}
pub fn len(&self) -> usize {
self.transactions.len()
}
pub fn is_empty(&self) -> bool {
self.transactions.is_empty()
}
pub fn iter(&self) -> impl '_ + Iterator<Item = &Transaction> {
self.transactions.iter()
}
}
impl Index<usize> for Block {
type Output = Transaction;
fn index(&self, index: usize) -> &Transaction {
&self.transactions[index]
}
}
impl<'a> IntoIterator for &'a Block {
type Item = &'a Transaction;
type IntoIter = std::slice::Iter<'a, Transaction>;
fn into_iter(self) -> Self::IntoIter {
self.transactions.iter()
}
}
impl IntoIterator for Block {
type Item = Transaction;
type IntoIter = std::vec::IntoIter<Transaction>;
fn into_iter(self) -> Self::IntoIter {
self.transactions.into_iter()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Validator<const H: u8> {
pub block_height: u64,
pub records_commitment: MerkleCommitment,
pub records_frontier: MerkleFrontier,
}
impl<const H: u8> Default for Validator<H> {
fn default() -> Self {
let records = MerkleTree::new(H).unwrap();
Self {
block_height: 0,
records_commitment: records.commitment(),
records_frontier: records.frontier(),
}
}
}
impl<'a, const H: u8> Arbitrary<'a> for Validator<H> {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let records = ArbitraryMerkleTree::arbitrary(u)?.0;
Ok(Self {
block_height: u.arbitrary()?,
records_commitment: records.commitment(),
records_frontier: records.frontier(),
})
}
}
impl<const H: u8> traits::Validator for Validator<H> {
type StateCommitment = u64;
type Block = Block;
type Proof = ();
fn block_height(&self) -> u64 {
self.block_height
}
fn commit(&self) -> Self::StateCommitment {
self.block_height
}
fn next_block(&self) -> Self::Block {
Block {
transactions: vec![],
index: self.block_height,
}
}
fn validate_and_apply(
&mut self,
block: Self::Block,
_proof: Self::Proof,
) -> Result<(Vec<u64>, MerkleTree), ValidationError> {
if block.index != self.block_height {
return Err(ValidationError::Failed {
msg: format!(
"incorrect block index (expected {}, got {})",
self.block_height, block.index
),
});
}
let mut uids = vec![];
let mut uid = self.records_commitment.num_leaves;
let mut builder =
FilledMTBuilder::from_frontier(&self.records_commitment, &self.records_frontier)
.ok_or_else(|| ValidationError::Failed {
msg: "failed to restore Merkle tree from frontier".to_string(),
})?;
for txn in block.transactions {
for comm in txn.output_commitments() {
builder.push(comm.to_field_element());
uids.push(uid);
uid += 1;
}
}
let records = builder.build();
self.block_height += 1;
self.records_commitment = records.commitment();
self.records_frontier = records.frontier();
Ok((uids, records))
}
}
#[cfg(any(test, feature = "testing", feature = "secure-srs"))]
#[derive(Clone, Copy, Debug)]
pub struct LedgerWithHeight<const H: u8>;
#[cfg(any(test, feature = "testing", feature = "secure-srs"))]
lazy_static! {
static ref CAP_UNIVERSAL_PARAM: UniversalParam = jf_cap::proof::universal_setup_for_staging(
2u64.pow(17) as usize,
&mut ChaChaRng::from_seed([0u8; 32])
)
.unwrap();
}
#[cfg(any(test, feature = "testing", feature = "secure-srs"))]
impl<const H: u8> traits::Ledger for LedgerWithHeight<H> {
type Validator = Validator<H>;
fn name() -> String {
String::from("Minimal CAP Ledger")
}
fn merkle_height() -> u8 {
H
}
fn record_root_history() -> usize {
1
}
fn srs() -> &'static UniversalParam {
&CAP_UNIVERSAL_PARAM
}
}
#[cfg(any(test, feature = "testing"))]
pub const DEFAULT_MERKLE_HEIGHT: u8 = 5;
#[cfg(any(test, feature = "testing"))]
pub type Ledger = LedgerWithHeight<DEFAULT_MERKLE_HEIGHT>;
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::{
Block as _, Ledger as _, NullifierSet as _, Transaction as _, Validator as _,
};
use jf_cap::{
freeze::{FreezeNote, FreezeNoteInput},
keys::{FreezerKeyPair, UserKeyPair},
structs::{AssetPolicy, FeeInput, FreezeFlag, RecordCommitment, RecordOpening, TxnFeeInfo},
transfer::TransferNoteInput,
AccMemberWitness, MerkleTree,
};
use rand_chacha::{rand_core::SeedableRng, ChaChaRng};
#[test]
fn test_nullifier_set() {
let mut rng = ChaChaRng::from_seed([42u8; 32]);
let mut s = NullifierSet::default();
assert_eq!(s.len(), 0);
let n = Nullifier::random_for_test(&mut rng);
s.multi_insert(&[(n, ())]).unwrap();
assert_eq!(s.len(), 1);
assert!(s.contains(&n.into()));
}
#[test]
fn test_transaction() {
let mut rng = ChaChaRng::from_seed([42u8; 32]);
let key = UserKeyPair::generate(&mut rng);
let freezer_key = FreezerKeyPair::generate(&mut rng);
let viewer_key = ViewerKeyPair::generate(&mut rng);
let srs = Ledger::srs();
let xfr_proving_key =
jf_cap::proof::transfer::preprocess(srs, 2, 2, Ledger::merkle_height())
.unwrap()
.0;
let mint_proving_key = jf_cap::proof::mint::preprocess(srs, Ledger::merkle_height())
.unwrap()
.0;
let freeze_proving_key = jf_cap::proof::freeze::preprocess(srs, 2, Ledger::merkle_height())
.unwrap()
.0;
let mut records = MerkleTree::new(Ledger::merkle_height()).unwrap();
let fee_ro = RecordOpening::new(
&mut rng,
1u8.into(),
AssetDefinition::native(),
key.pub_key(),
FreezeFlag::Unfrozen,
);
let fee_comm = RecordCommitment::from(&fee_ro);
records.push(fee_comm.to_field_element());
let fee_nullifier = key.nullify(
AssetDefinition::native().policy_ref().freezer_pub_key(),
0,
&fee_comm,
);
let (asset_code, seed) = AssetCode::random(&mut rng);
let policy = AssetPolicy::default()
.set_freezer_pub_key(freezer_key.pub_key())
.set_viewer_pub_key(viewer_key.pub_key())
.reveal_user_address()
.unwrap()
.reveal_amount()
.unwrap();
let asset_def = AssetDefinition::new(asset_code, policy).unwrap();
let asset_ro = RecordOpening::new(
&mut rng,
1u8.into(),
asset_def.clone(),
key.pub_key(),
FreezeFlag::Unfrozen,
);
let asset_comm = RecordCommitment::from(&asset_ro);
records.push(asset_comm.to_field_element());
let asset_nullifier = key.nullify(&freezer_key.pub_key(), 1, &asset_comm);
let fee_input = FeeInput {
ro: fee_ro,
acc_member_witness: AccMemberWitness::lookup_from_tree(&records, 0)
.expect_ok()
.unwrap()
.1,
owner_keypair: &key,
};
let mint_ro = RecordOpening::new(
&mut rng,
1u8.into(),
asset_def.clone(),
key.pub_key(),
FreezeFlag::Unfrozen,
);
let mint_comm = RecordCommitment::from(&mint_ro);
let fee_info = TxnFeeInfo::new(&mut rng, fee_input.clone(), 1u8.into())
.unwrap()
.0;
let mint_note =
MintNote::generate(&mut rng, mint_ro, seed, &[], fee_info, &mint_proving_key)
.unwrap()
.0;
let mint = TransactionNote::Mint(Box::new(mint_note.clone()));
let xfr_inputs = vec![TransferNoteInput {
ro: asset_ro.clone(),
acc_member_witness: AccMemberWitness::lookup_from_tree(&records, 1)
.expect_ok()
.unwrap()
.1,
owner_keypair: &key,
cred: None,
}];
let xfr_ro = RecordOpening::new(
&mut rng,
1u8.into(),
asset_def.clone(),
key.pub_key(),
FreezeFlag::Unfrozen,
);
let xfr_comm = RecordCommitment::from(&xfr_ro);
let fee_info = TxnFeeInfo::new(&mut rng, fee_input.clone(), 1u8.into())
.unwrap()
.0;
let xfr_note = TransferNote::generate_non_native(
&mut rng,
xfr_inputs,
&[xfr_ro],
fee_info,
2u64.pow(jf_cap::constants::MAX_TIMESTAMP_LEN as u32) - 1,
&xfr_proving_key,
vec![],
)
.unwrap()
.0;
let xfr = TransactionNote::Transfer(Box::new(xfr_note.clone()));
let freeze_inputs = vec![FreezeNoteInput {
ro: asset_ro,
acc_member_witness: AccMemberWitness::lookup_from_tree(&records, 1)
.expect_ok()
.unwrap()
.1,
keypair: &freezer_key,
}];
let fee_info = TxnFeeInfo::new(&mut rng, fee_input.clone(), 1u8.into())
.unwrap()
.0;
let freeze_note =
FreezeNote::generate(&mut rng, freeze_inputs, fee_info, &freeze_proving_key)
.unwrap()
.0;
let freeze = TransactionNote::Freeze(Box::new(freeze_note.clone()));
assert_eq!(mint.kind(), TransactionKind::Mint);
assert_eq!(mint.output_len(), 2);
assert_eq!(
mint.output_commitments(),
vec![mint_note.chg_comm, mint_comm]
);
assert_eq!(mint.input_nullifiers(), vec![fee_nullifier]);
assert_eq!(xfr.kind(), TransactionKind::Send);
assert_eq!(xfr.output_len(), 2);
assert_eq!(xfr.output_commitments(), xfr_note.output_commitments);
assert_eq!(xfr.output_commitments()[1], xfr_comm);
assert_eq!(xfr.input_nullifiers(), vec![fee_nullifier, asset_nullifier]);
assert_eq!(freeze.kind(), TransactionKind::Freeze);
assert_eq!(freeze.output_len(), 2);
assert_eq!(freeze.output_commitments(), freeze_note.output_commitments);
assert_eq!(
freeze.input_nullifiers(),
vec![
fee_nullifier,
key.nullify(&freezer_key.pub_key(), 1, &asset_comm)
]
);
assert_ne!(
traits::Transaction::hash(&mint),
traits::Transaction::hash(&xfr)
);
assert_ne!(
traits::Transaction::hash(&xfr),
traits::Transaction::hash(&freeze)
);
assert_ne!(
traits::Transaction::hash(&freeze),
traits::Transaction::hash(&mint)
);
let viewable_assets = vec![(asset_code, asset_def.clone())].into_iter().collect();
let viewing_keys = vec![(viewer_key.pub_key(), viewer_key)]
.into_iter()
.collect();
let mint_memo = mint
.open_viewing_memo(&viewable_assets, &viewing_keys)
.unwrap();
assert_eq!(mint_memo.asset, asset_def);
assert_eq!(mint_memo.inputs, vec![]);
assert_eq!(mint_memo.outputs.len(), 1);
assert_eq!(mint_memo.outputs[0].asset_code, asset_code);
assert_eq!(mint_memo.outputs[0].user_address, Some(key.address()));
assert_eq!(mint_memo.outputs[0].amount, Some(1u8.into()));
let xfr_memo = xfr
.open_viewing_memo(&viewable_assets, &viewing_keys)
.unwrap();
assert_eq!(xfr_memo.asset, asset_def);
assert_eq!(xfr_memo.inputs.len(), 1);
assert_eq!(xfr_memo.inputs[0].asset_code, asset_code);
assert_eq!(xfr_memo.inputs[0].user_address, Some(key.address()));
assert_eq!(xfr_memo.inputs[0].amount, Some(1u8.into()));
assert_eq!(xfr_memo.outputs.len(), 1);
assert_eq!(xfr_memo.outputs[0].asset_code, asset_code);
assert_eq!(xfr_memo.outputs[0].user_address, Some(key.address()));
assert_eq!(xfr_memo.outputs[0].amount, Some(1u8.into()));
assert_eq!(
freeze.open_viewing_memo(&viewable_assets, &viewing_keys),
Err(ViewingError::NoViewingMemos)
);
}
#[test]
fn test_validator() {
let mut validator = Validator::<DEFAULT_MERKLE_HEIGHT>::default();
assert_eq!(validator.block_height(), 0);
let initial_commit = validator.commit();
let mut rng = ChaChaRng::from_seed([42u8; 32]);
let key = UserKeyPair::generate(&mut rng);
let srs = Ledger::srs();
let mint_proving_key = jf_cap::proof::mint::preprocess(srs, Ledger::merkle_height())
.unwrap()
.0;
let mut records = MerkleTree::new(Ledger::merkle_height()).unwrap();
let fee_ro = RecordOpening::new(
&mut rng,
1u8.into(),
AssetDefinition::native(),
key.pub_key(),
FreezeFlag::Unfrozen,
);
let fee_comm = RecordCommitment::from(&fee_ro);
records.push(fee_comm.to_field_element());
let fee_input = FeeInput {
ro: fee_ro,
acc_member_witness: AccMemberWitness::lookup_from_tree(&records, 0)
.expect_ok()
.unwrap()
.1,
owner_keypair: &key,
};
let (asset_code, seed) = AssetCode::random(&mut rng);
let asset_def = AssetDefinition::new(asset_code, AssetPolicy::default()).unwrap();
let mint_ro = RecordOpening::new(
&mut rng,
1u8.into(),
asset_def,
key.pub_key(),
FreezeFlag::Unfrozen,
);
let fee_info = TxnFeeInfo::new(&mut rng, fee_input.clone(), 1u8.into())
.unwrap()
.0;
let mint_note =
MintNote::generate(&mut rng, mint_ro, seed, &[], fee_info, &mint_proving_key)
.unwrap()
.0;
let mint = TransactionNote::Mint(Box::new(mint_note.clone()));
let mut block = validator.next_block();
block.add_transaction(mint.clone()).unwrap();
let (uids, records) = validator.validate_and_apply(block, ()).unwrap();
assert_eq!(uids, vec![0, 1]);
assert_eq!(records.num_leaves(), 2);
assert_eq!(
records.get_leaf(0).expect_ok().unwrap().1.leaf.0,
mint_note.chg_comm.to_field_element()
);
assert_eq!(
records.get_leaf(1).expect_ok().unwrap().1.leaf.0,
mint_note.mint_comm.to_field_element()
);
assert_eq!(validator.block_height(), 1);
assert_ne!(validator.commit(), initial_commit);
let mut block = validator.next_block();
block.add_transaction(mint).unwrap();
let (uids, records) = validator.validate_and_apply(block, ()).unwrap();
assert_eq!(uids, vec![2, 3]);
assert_eq!(records.num_leaves(), 4);
assert_eq!(
records.get_leaf(2).expect_ok().unwrap().1.leaf.0,
mint_note.chg_comm.to_field_element()
);
assert_eq!(
records.get_leaf(3).expect_ok().unwrap().1.leaf.0,
mint_note.mint_comm.to_field_element()
);
}
}