use mohan::{
ser,
dalek::{
ristretto::{
RistrettoPoint,
CompressedRistretto
},
scalar::Scalar,
constants::RISTRETTO_BASEPOINT_COMPRESSED,
traits::Identity
}
};
use crate::{
ZeiError,
core::{
AssetId,
OpenAssetOut,
AssetOut
},
proofs::{
bulletproofs::{
RangeProof,
BulletproofGens
},
EqualityProof,
BatchEqualityProof
},
PedersenGens
};
use bacteria::Transcript;
pub struct XProofs {
pub range_proof: RangeProof,
pub equality_proof: EqualityProof,
pub asset_proof: BatchEqualityProof
}
impl Default for XProofs {
fn default() -> XProofs {
XProofs {
range_proof: RangeProof::default(),
equality_proof: EqualityProof::default(),
asset_proof: BatchEqualityProof::default(),
}
}
}
impl XProofs {
pub fn proove(
inputs: &Vec<OpenAssetOut>,
outputs: &Vec<OpenAssetOut>,
) -> Result<XProofs, ZeiError> {
let mut proof = XProofs::default();
proof.proove_internal(inputs, outputs)?;
Ok(proof)
}
pub fn proove_internal(
&mut self,
inputs: &Vec<OpenAssetOut>,
outputs: &Vec<OpenAssetOut>,
) -> Result<(), ZeiError> {
self.internal_step_one(inputs, outputs)?;
self.internal_step_two(inputs, outputs)?;
self.internal_step_three(inputs, outputs)?;
Ok(())
}
fn internal_step_one(
&mut self,
inputs: &Vec<OpenAssetOut>,
outputs: &Vec<OpenAssetOut>,
) -> Result<(), ZeiError> {
let amount_comms_input = inputs.into_iter().fold(
RistrettoPoint::identity(),
|r, i| r + i.blinded.value.decompress().unwrap()
);
let amount_comms_output = outputs.into_iter().fold(
RistrettoPoint::identity(),
|r, i| r + i.blinded.value.decompress().unwrap()
);
let amount_blinds_input = inputs.into_iter().fold(
Scalar::zero(),
|r, i| r + i.value_blind
);
let amount_blinds_output = outputs.into_iter().fold(
Scalar::zero(),
|r, i| r + i.value_blind
);
let eq_proof = EqualityProof::proove(
&amount_comms_input,
&amount_blinds_input,
&amount_comms_output,
&amount_blinds_output
)?;
self.equality_proof = eq_proof;
Ok(())
}
fn internal_step_two(
&mut self,
inputs: &Vec<OpenAssetOut>,
outputs: &Vec<OpenAssetOut>,
) -> Result<(), ZeiError> {
let mut input_asset_comms = Vec::new();
let mut input_asset_blinds = Vec::new();
for i in inputs.iter() {
input_asset_comms.push(i.blinded.flavor.decompress().unwrap());
input_asset_blinds.push(i.flavor_blind);
}
let mut output_asset_comms = Vec::new();
let mut output_asset_blinds = Vec::new();
for i in outputs.iter() {
output_asset_comms.push(i.blinded.flavor.decompress().unwrap());
output_asset_blinds.push(i.flavor_blind);
}
let batcheq_proof = BatchEqualityProof::proove(
&input_asset_comms,
&input_asset_blinds,
&output_asset_comms,
&output_asset_blinds
)?;
self.asset_proof = batcheq_proof;
Ok(())
}
fn internal_step_three(
&mut self,
inputs: &Vec<OpenAssetOut>,
outputs: &Vec<OpenAssetOut>,
) -> Result<(), ZeiError> {
let pc_gens = PedersenGens::default();
let bp_gens = BulletproofGens::new(64, 32);
if outputs.len() > 32 {
return Err(ZeiError::OutputOverflowError);
}
let mut values = outputs.into_iter().map(
|i| i.value
).collect::<Vec<u64>>();
let mut blindings = outputs.into_iter().map(
|i| i.value_blind
).collect::<Vec<Scalar>>();
let mut transcript = Transcript::new(b"zei_asset_xproofs");
if !blindings.len().is_power_of_two() {
let gap = blindings.len().next_power_of_two() - blindings.len();
for i in 0..gap {
values.push(0u64);
blindings.push(Scalar::zero());
}
}
let (range_proof, value_commitments) = RangeProof::prove_multiple(
&bp_gens,
&pc_gens,
&mut transcript,
&values,
&blindings,
64, ).unwrap();
self.range_proof = range_proof;
Ok(())
}
pub fn verify(
&self,
inputs: &Vec<AssetOut>,
outputs: &Vec<AssetOut>
) -> Result<(), ZeiError> {
self.verify_step_one(inputs, outputs)?;
self.verify_step_two(inputs, outputs)?;
self.verify_step_three(inputs, outputs)?;
Ok(())
}
pub fn verify_step_one(
&self,
inputs: &Vec<AssetOut>,
outputs: &Vec<AssetOut>
) -> Result<(), ZeiError> {
let amount_comms_input = inputs.into_iter().fold(
RistrettoPoint::identity(),
|r, i| r + i.value.decompress().unwrap()
);
let amount_comms_output = outputs.into_iter().fold(
RistrettoPoint::identity(),
|r, i| r + i.value.decompress().unwrap()
);
self.equality_proof.verify(&amount_comms_input, &amount_comms_output)
}
pub fn verify_step_two(
&self,
inputs: &Vec<AssetOut>,
outputs: &Vec<AssetOut>
) -> Result<(), ZeiError> {
let mut input_asset_comms = inputs.into_iter().map(
|i| i.flavor.decompress().unwrap()
).collect::<Vec<RistrettoPoint>>();
let mut output_asset_comms = outputs.into_iter().map(
|i| i.flavor.decompress().unwrap()
).collect::<Vec<RistrettoPoint>>();
self.asset_proof.verify(&input_asset_comms, &output_asset_comms)
}
fn verify_step_three(
&self,
inputs: &Vec<AssetOut>,
outputs: &Vec<AssetOut>,
) -> Result<(), ZeiError> {
let mut transcript = Transcript::new(b"zei_asset_xproofs");
let pc_gens = PedersenGens::default();
let bp_gens = BulletproofGens::new(64, 32);
let mut values = outputs.into_iter().map(
|i| i.value
).collect::<Vec<CompressedRistretto>>();
if !values.len().is_power_of_two() {
let gap = values.len().next_power_of_two() - values.len();
for i in 0..gap {
values.push(CompressedRistretto::identity());
}
}
self.range_proof.verify_multiple(&bp_gens, &pc_gens, &mut transcript, &values, 64)
}
}
impl ser::Writeable for XProofs {
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.range_proof.write(writer)?;
self.equality_proof.write(writer)?;
self.asset_proof.write(writer)?;
Ok(())
}
}
impl ser::Readable for XProofs {
fn read(reader: &mut dyn ser::Reader) -> Result<XProofs, ser::Error> {
Ok(XProofs {
range_proof: RangeProof::read(reader)?,
equality_proof: EqualityProof::read(reader)?,
asset_proof: BatchEqualityProof::read(reader)?,
})
}
}
#[cfg(test)]
mod test {
use super::*;
use mohan::fast_merkle_root;
use std::iter::FromIterator;
use mohan::{
mohan_rand,
hash::H256
};
use crate::Lockbox;
fn satoshi(q: u64) -> OpenAssetOut {
let mut transcript = Transcript::new(b"satoshi_land");
let gens = PedersenGens::default();
let mut rng = transcript
.build_rng()
.finalize(&mut mohan_rand());
let blind_share = Scalar::random(&mut rng);
let blind_share_value: H256 = fast_merkle_root(vec![H256::from(blind_share.to_bytes()), H256::zero()]);
let blind_share_asset: H256 = fast_merkle_root(vec![H256::from(blind_share.to_bytes()), H256::from_vec(&vec![1])]);
let flav_id = AssetId::from_inner(H256::from_vec(b"satoshi"));
let pc_gens = PedersenGens::default();
return OpenAssetOut {
blinded: AssetOut {
flavor: pc_gens.switch_commit(&flav_id.into_scalar(), &blind_share_asset.into_scalar()).compress(),
value: pc_gens.switch_commit(&q.into(), &blind_share_value.into_scalar()).compress(),
vessel: Lockbox::default()
},
value: q,
value_blind: gens.blind_switch(&q.into(), &blind_share_value.into_scalar()),
flavor_id: flav_id,
flavor_blind: gens.blind_switch(&flav_id.into_scalar(), &blind_share_asset.into_scalar()),
};
}
fn turing(q: u64) -> OpenAssetOut {
let mut transcript = Transcript::new(b"turing_land");
let mut rng = transcript
.build_rng()
.finalize(&mut rand::thread_rng());
let blind_share = Scalar::random(&mut rng);
let blind_share_value: H256 = fast_merkle_root(vec![H256::from(blind_share.to_bytes()), H256::zero()]);
let blind_share_asset: H256 = fast_merkle_root(vec![H256::from(blind_share.to_bytes()), H256::from_vec(&vec![1])]);
let flav_id = AssetId::from_inner(H256::from_vec(b"turing"));
let pc_gens = PedersenGens::default();
return OpenAssetOut {
blinded: AssetOut {
flavor: pc_gens.commit(&flav_id.into_scalar(), &blind_share_asset.into_scalar()).compress(),
value: pc_gens.commit(&q.into(), &blind_share_value.into_scalar()).compress(),
vessel: Lockbox::default()
},
value: q,
value_blind: blind_share_value.into_scalar(),
flavor_id: flav_id,
flavor_blind: blind_share_asset.into_scalar(),
};
}
#[test]
fn test_xproof_simple() {
let sat = satoshi(1);
let tsat = satoshi(2);
let ghost_sat = satoshi(1);
let p = XProofs::proove(&vec![tsat.clone()], &vec![sat.clone(), ghost_sat.clone()]).unwrap();
assert!(p.verify(&vec![tsat.blinded], &vec![sat.blinded, ghost_sat.blinded]).is_ok());
}
#[test]
fn test_xproof_simple_excess_out() {
let sat = satoshi(11);
let tsat = satoshi(2);
let ghost_sat = satoshi(1);
let p = XProofs::proove(&vec![tsat.clone()], &vec![sat.clone()]).unwrap();
assert!(p.verify(&vec![tsat.blinded.clone()], &vec![sat.blinded.clone(), ghost_sat.blinded.clone()]).is_err());
}
#[test]
fn test_xproof_simple_excess_out2() {
let sat = satoshi(1);
let tsat = satoshi(21);
let ghost_sat = satoshi(1);
let p = XProofs::proove(&vec![tsat.clone()], &vec![sat.clone()]).unwrap();
assert!(p.verify(&vec![tsat.blinded.clone()], &vec![sat.blinded.clone(), ghost_sat.blinded.clone()]).is_err());
}
#[test]
fn test_xproof_simple_asset_mismatch() {
let sat = satoshi(1);
let tsat = satoshi(2);
let ghost_sat = turing(2);
let p = XProofs::proove(&vec![tsat.clone()], &vec![sat.clone(), ghost_sat.clone()]).unwrap();
assert!(p.verify(&vec![tsat.blinded], &vec![sat.blinded, ghost_sat.blinded]).is_err());
}
#[test]
fn test_xproof_simple_asset_value_mismatch() {
let sat = satoshi(10);
let tsat = satoshi(2);
let ghost_sat = turing(2);
let p = XProofs::proove(&vec![tsat.clone()], &vec![sat.clone(), ghost_sat.clone()]).unwrap();
assert!(p.verify(&vec![tsat.blinded], &vec![sat.blinded, ghost_sat.blinded]).is_err());
}
#[test]
fn test_xproof_12in_4out() {
let mut inputs = Vec::new();
let mut outputs = Vec::new();
for i in 0..12 {
inputs.push(satoshi(1));
}
for i in 0..4 {
outputs.push(satoshi(3));
}
let p = XProofs::proove(&inputs, &outputs).unwrap();
let in_blinds = inputs.into_iter().map(|i| i.blinded).collect::<Vec<AssetOut>>();
let out_blinds = outputs.into_iter().map(|i| i.blinded).collect::<Vec<AssetOut>>();
assert!(p.verify(&in_blinds, &out_blinds).is_ok());
}
}