Skip to content

Commit

Permalink
feat: parst pczt transparent and orchard
Browse files Browse the repository at this point in the history
  • Loading branch information
soralit committed Nov 18, 2024
1 parent a622a56 commit c99b140
Show file tree
Hide file tree
Showing 11 changed files with 555 additions and 106 deletions.
3 changes: 0 additions & 3 deletions rust/apps/zcash/.rustfmt.toml

This file was deleted.

4 changes: 2 additions & 2 deletions rust/apps/zcash/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use alloc::string::{String, ToString};
use alloc::string::{String};
use thiserror;
use thiserror::Error;

Expand All @@ -14,4 +14,4 @@ pub enum ZcashError {
SigningError(String),
#[error("invalid pczt, {0}")]
InvalidPczt(String),
}
}
315 changes: 226 additions & 89 deletions rust/apps/zcash/src/pczt/check.rs
Original file line number Diff line number Diff line change
@@ -1,78 +1,213 @@
use core::iter;


use super::*;
use bitvec::{array::BitArray, order::Lsb0};

use orchard::{
keys::{EphemeralKeyBytes, FullViewingKey, NullifierDerivingKey, Scope},
note::{Memo, Note, Nullifier, RandomSeed, Rho},
note_encryption::OrchardDomain,
commitment::ExtractedNoteCommitment,
keys::{FullViewingKey, Scope},
note::{Memo, Note, Nullifier, Rho},
note_ext::{
calculate_note_commitment, calculate_nullifier, try_output_recovery_with_ovk,
ENC_CIPHERTEXT_SIZE, OUT_CIPHERTEXT_SIZE,
calculate_note_commitment, calculate_nullifier,
},
spec::{diversify_hash, extract_p, mod_r_p},
value::ValueCommitment,
Address,
};
use parse::decode_output_enc_ciphertext;
use zcash_vendor::{
orchard::{commitment::NoteCommitTrapdoor, note::NoteValue},
bip32::ChildNumber,
pasta_curves::{
arithmetic::CurveExt, group::ff::PrimeField, group::ff::PrimeFieldBits,
group::GroupEncoding, pallas, Ep,
group::{
GroupEncoding,
},
},
pczt, sinsemilla,
pczt,
zcash_primitives::legacy::keys::AccountPubKey,
};

pub fn check_pczt(pczt: &Pczt, ufvk: &UnifiedFullViewingKey) -> Result<(), ZcashError> {
let orchard = ufvk.orchard().unwrap();
check_orchard(&pczt.orchard, &orchard)
pub fn check_pczt(
seed_fingerprint: &[u8; 32],
ufvk: &UnifiedFullViewingKey,
pczt: &Pczt,
) -> Result<(), ZcashError> {
let xpub = ufvk.transparent().ok_or(ZcashError::InvalidDataError(
"transparent xpub is not present".to_string(),
))?;
let orchard = ufvk.orchard().ok_or(ZcashError::InvalidDataError(
"orchard fvk is not present".to_string(),
))?;
check_orchard(&seed_fingerprint, &orchard, &pczt.orchard)?;
check_transparent(&seed_fingerprint, &xpub, &pczt.transparent)?;
Ok(())
}

fn check_orchard(bundle: &pczt::orchard::Bundle, fvk: &FullViewingKey) -> Result<(), ZcashError> {
bundle.actions.iter().try_for_each(|action| {
//check spend nullifier
let spend = &action.spend;

let nullifier = spend.nullifier;
let rho = spend.rho.ok_or(ZcashError::InvalidPczt(
"spend.rho is not present".to_string(),
))?;
let rseed = spend.rseed.ok_or(ZcashError::InvalidPczt(
"spend.rseed is not present".to_string(),
))?;
let value = spend.value.ok_or(ZcashError::InvalidPczt(
"spend.value is not present".to_string(),
))?;
let nk = fvk.nk();

let recipient = spend.recipient.ok_or(ZcashError::InvalidPczt(
"spend.recipient is not present".to_string(),
))?;

let note_commitment = calculate_note_commitment(&recipient, value, &rho, &rseed);

let derived_nullifier = calculate_nullifier(&nk, &rho, &rseed, note_commitment);

if nullifier != derived_nullifier {
return Err(ZcashError::InvalidPczt(
"orchard action nullifier wrong".to_string(),
));
}
fn check_transparent(
seed_fingerprint: &[u8; 32],
xpub: &AccountPubKey,
bundle: &pczt::transparent::Bundle,
) -> Result<(), ZcashError> {
bundle.inputs.iter().try_for_each(|input| {
check_transparent_input(seed_fingerprint, xpub, input)?;
Ok(())
})?;
bundle.outputs.iter().try_for_each(|output| {
check_transparent_output(seed_fingerprint, xpub, output)?;
Ok(())
})?;
Ok(())
}

//check output cmx
let output = &action.output;
fn check_transparent_input(
seed_fingerprint: &[u8; 32],
xpub: &AccountPubKey,
input: &pczt::transparent::Input,
) -> Result<(), ZcashError> {
match input.bip32_derivation.get(&input.script_pubkey) {
Some(bip32_derivation) => {
if seed_fingerprint == &bip32_derivation.seed_fingerprint {
//verify public key
let xpub = xpub.to_inner();
let target = bip32_derivation
.derivation_path
.iter()
.try_fold(xpub, |acc, n| {
acc.derive_child(ChildNumber::from(*n)).map_err(|_| {
ZcashError::InvalidPczt(
"transparent input bip32 derivation path invalid".to_string(),
)
})
})?;
if target.public_key().serialize().to_vec() != input.script_pubkey {
return Err(ZcashError::InvalidPczt(
"transparent input script pubkey mismatch".to_string(),
));
}
Ok(())
} else {
//not my input, pass
Ok(())
}
}
//not my input, pass
None => Ok(()),
}
}

// let rho =
fn check_transparent_output(
seed_fingerprint: &[u8; 32],
xpub: &AccountPubKey,
output: &pczt::transparent::Output,
) -> Result<(), ZcashError> {
match output.bip32_derivation.get(&output.script_pubkey) {
Some(bip32_derivation) => {
if seed_fingerprint == &bip32_derivation.seed_fingerprint {
//verify public key
let xpub = xpub.to_inner();
let target = bip32_derivation
.derivation_path
.iter()
.try_fold(xpub, |acc, n| {
acc.derive_child(ChildNumber::from(*n)).map_err(|_| {
ZcashError::InvalidPczt(
"transparent output bip32 derivation path invalid".to_string(),
)
})
})?;
if target.public_key().serialize().to_vec() != output.script_pubkey {
return Err(ZcashError::InvalidPczt(
"transparent output script pubkey mismatch".to_string(),
));
}
Ok(())
} else {
//not my output, pass
Ok(())
}
}
//not my output, pass
None => Ok(()),
}
}

fn check_orchard(
seed_fingerprint: &[u8; 32],
fvk: &FullViewingKey,
bundle: &pczt::orchard::Bundle,
) -> Result<(), ZcashError> {
bundle.actions.iter().try_for_each(|action| {
check_action(seed_fingerprint, fvk, action)?;
Ok(())
})?;
Ok(())
}

fn check_action(
seed_fingerprint: &[u8; 32],
fvk: &FullViewingKey,
action: &pczt::orchard::Action,
) -> Result<(), ZcashError> {
check_action_spend(seed_fingerprint, fvk, &action.spend)?;
check_action_output(fvk, action)
}

// check spend nullifier
fn check_action_spend(
seed_fingerprint: &[u8; 32],
fvk: &FullViewingKey,
spend: &pczt::orchard::Spend,
) -> Result<(), ZcashError> {
if let Some(zip32_derivation) = spend.zip32_derivation.as_ref() {
if zip32_derivation.seed_fingerprint == *seed_fingerprint {
let nullifier = spend.nullifier;
let rho = spend.rho.ok_or(ZcashError::InvalidPczt(
"spend.rho is not present".to_string(),
))?;
let rseed = spend.rseed.ok_or(ZcashError::InvalidPczt(
"spend.rseed is not present".to_string(),
))?;
let value = spend.value.ok_or(ZcashError::InvalidPczt(
"spend.value is not present".to_string(),
))?;
let nk = fvk.nk();

let recipient = spend.recipient.ok_or(ZcashError::InvalidPczt(
"spend.recipient is not present".to_string(),
))?;

let note_commitment = calculate_note_commitment(&recipient, value, &rho, &rseed);

let derived_nullifier = calculate_nullifier(&nk, &rho, &rseed, note_commitment);

if nullifier != derived_nullifier {
return Err(ZcashError::InvalidPczt(
"orchard action nullifier wrong".to_string(),
));
}
}
}
Ok(())
}

//check output cmx
fn check_action_output(
fvk: &FullViewingKey,
action: &pczt::orchard::Action,
) -> Result<(), ZcashError> {
let result = decode_action_output(fvk, action)?;
if let Some((note, _address, _memo, _)) = result {
let node_commitment = note.commitment();
let cmx: ExtractedNoteCommitment = node_commitment.into();
if cmx.to_bytes() != action.output.cmx {
return Err(ZcashError::InvalidPczt(
"orchard action cmx wrong".to_string(),
));
}
}
Ok(())
}

pub fn decode_action_output(
fvk: &FullViewingKey,
action: &pczt::orchard::Action,
) -> Result<Option<(Note, Address, Memo)>, ZcashError> {
) -> Result<Option<(Note, Address, Memo, bool)>, ZcashError> {
let nullifier = Nullifier::from_bytes(&action.spend.nullifier).unwrap();

let rho = Rho::from_nf_old(nullifier);
Expand All @@ -87,49 +222,50 @@ pub fn decode_action_output(

let out_ciphertext = action.output.out_ciphertext.clone().try_into().unwrap();

decode_output_enc_ciphertext(fvk, &rho, &epk, &cmx, &cv, &enc_ciphertext, &out_ciphertext)
}
let external_ovk = fvk.to_ovk(Scope::External);

pub fn decode_output_enc_ciphertext(
fvk: &FullViewingKey,
rho: &Rho,
epk: &[u8; 32],
cmx: &[u8; 32],
cv: &[u8; 32],
enc_ciphertext: &[u8; ENC_CIPHERTEXT_SIZE],
out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
) -> Result<Option<(Note, Address, Memo)>, ZcashError> {
let ovk = fvk.to_ovk(Scope::External);

let domain = OrchardDomain::new(rho.clone());

let ephemeral_key = EphemeralKeyBytes::from(epk.clone());

let cv = ValueCommitment::from_bytes(cv).unwrap();

let result = try_output_recovery_with_ovk(
&domain,
&ovk,
&ephemeral_key,
cmx,
let internal_ovk = fvk.to_ovk(Scope::Internal);

if let Some((note, address, memo)) = decode_output_enc_ciphertext(
&external_ovk,
&rho,
&epk,
&cmx,
&cv,
enc_ciphertext,
out_ciphertext,
);
&enc_ciphertext,
&out_ciphertext,
)? {
return Ok(Some((note, address, memo, true)));
}

if let Some((note, address, memo)) = decode_output_enc_ciphertext(
&internal_ovk,
&rho,
&epk,
&cmx,
&cv,
&enc_ciphertext,
&out_ciphertext,
)? {
return Ok(Some((note, address, memo, false)));
}

Ok(result)
Ok(None)
}

#[cfg(test)]
mod tests {
use alloc::vec;
use keystore::algorithms::zcash::derive_ufvk;
use pczt::common::HARDENED_MASK;
use zcash_vendor::{pczt::{
common::{Global, Zip32Derivation},
orchard::{self, Action},
sapling, transparent, Pczt, Version, V5_TX_VERSION, V5_VERSION_GROUP_ID,
}, zcash_protocol::consensus::MAIN_NETWORK};
use zcash_vendor::{
pczt::{
common::{Global, Zip32Derivation},
orchard::{self, Action},
sapling, transparent, Pczt, Version, V5_TX_VERSION, V5_VERSION_GROUP_ID,
},
zcash_protocol::consensus::MAIN_NETWORK,
};

use super::*;

Expand All @@ -139,15 +275,13 @@ mod tests {
#[test]
fn test_decode_output_enc_ciphertext() {
{
let seed = hex::decode("5d741f330207d529ff7af106616bbffa15c1cf3bf9778830e003a17787926c0bd77261f91a4a3e2e52b8f96f35bdadc94187bef0f53c449a3802b4e7332cfedd").unwrap();

let fingerprint =
hex::decode("a833c2361e2d72d8fef1ec19071a6433b5f3c0b8aafb82ce2930b2349ad985c5")
.unwrap();

let ufvk = derive_ufvk(&seed).unwrap();
let ufvk = "uview10zf3gnxd08cne6g7ryh6lln79duzsayg0qxktvyc3l6uutfk0agmyclm5g82h5z0lqv4c2gzp0eu0qc0nxzurxhj4ympwn3gj5c3dc9g7ca4eh3q09fw9kka7qplzq0wnauekf45w9vs4g22khtq57sc8k6j6s70kz0rtqlyat6zsjkcqfrlm9quje8vzszs8y9mjvduf7j2vx329hk2v956g6svnhqswxfp3n760mw233w7ffgsja2szdhy5954hsfldalf28wvav0tctxwkmkgrk43tq2p7sqchzc6";

let fvk = UnifiedFullViewingKey::decode(&MAIN_NETWORK, &ufvk).unwrap().orchard().unwrap().clone();
let unified_fvk = UnifiedFullViewingKey::decode(&MAIN_NETWORK, ufvk).unwrap();

let pczt = Pczt {
version: Version::V0,
Expand Down Expand Up @@ -255,9 +389,12 @@ mod tests {
},
};

let result = decode_action_output(&fvk, &pczt.orchard.actions[0]).unwrap();
let fingerprint = fingerprint.try_into().unwrap();

let result = check_pczt(&fingerprint, &unified_fvk, &pczt);

println!("{:?}", result);
assert_eq!(false, result.is_ok());
}
}
//TODO: add test for happy path
}
Loading

0 comments on commit c99b140

Please sign in to comment.