Skip to content

Commit

Permalink
feat: impl signer for pczt
Browse files Browse the repository at this point in the history
  • Loading branch information
soralit committed Nov 1, 2024
1 parent 8636858 commit 515890a
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 58 deletions.
2 changes: 2 additions & 0 deletions rust/apps/zcash/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ pub enum ZcashError {
GenerateAddressError(String),
#[error("invalid zcash data: {0}")]
InvalidDataError(String),
#[error("failed to sign zcash data, {0}")]
SigningError(String),
}
59 changes: 34 additions & 25 deletions rust/apps/zcash/src/pczt.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@
use alloc::vec;
use alloc::vec::Vec;
use zcash_vendor::{orchard::keys::FullViewingKey, pczt::Pczt};
use alloc::string::{String, ToString};
use bitcoin::secp256k1::Message;
use keystore::algorithms::secp256k1::sign_message_by_seed;
use keystore::algorithms::zcash::sign_message_orchard;
use zcash_vendor::pczt::pczt_ext::{PcztSigner, ZcashSignature};

use crate::errors::Result;
use bitcoin::bip32::Xpub;
use crate::errors::ZcashError;

trait PcztTrait {
fn sign(&self) -> Result<Pczt>;
fn verify(&self) -> Result<bool>;
fn hash(&self) -> Result<Vec<u8>>;
struct SeedSigner {
seed: [u8; 32],
}

fn verify_transparent_part(pczt: &Pczt, xpub: &Xpub) -> Result<bool> {
Ok(true)
}

fn verify_orchard_part(pczt: &Pczt, fvk: &FullViewingKey) -> Result<bool> {
Ok(true)
}

impl PcztTrait for Pczt {
fn sign(&self) -> Result<Pczt> {
Ok(self.clone())
impl PcztSigner for SeedSigner {
type Error = ZcashError;
fn sign_transparent(&self, hash: &[u8], path: String) -> Result<ZcashSignature, Self::Error> {
let message = Message::from_digest_slice(hash).unwrap();
sign_message_by_seed(&self.seed, &path, &message)
.map(|(_rec_id, signature)| ZcashSignature::from(signature))
.map_err(|e| ZcashError::SigningError(e.to_string()))
}

fn verify(&self) -> Result<bool> {
Ok(true)
fn sign_sapling(
&self,
hash: &[u8],
alpha: [u8; 32],
path: String,
) -> Result<ZcashSignature, Self::Error> {
// we don't support sapling yet
Err(ZcashError::SigningError(
"sapling not supported".to_string(),
))
}

fn hash(&self) -> Result<Vec<u8>> {
let version = self.global.tx_version;
Ok(vec![])
fn sign_orchard(
&self,
hash: &[u8],
alpha: [u8; 32],
path: String,
) -> Result<ZcashSignature, Self::Error> {
sign_message_orchard(&self.seed, alpha, hash, &path)
.map(|signature| ZcashSignature::from(signature))
.map_err(|e| ZcashError::SigningError(e.to_string()))
}
}
2 changes: 1 addition & 1 deletion rust/keystore/src/algorithms/utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use alloc::string::String;

pub fn normalize_path(path: &String) -> String {
pub fn normalize_path(path: &str) -> String {
let mut p = path.to_lowercase();
if !p.starts_with('m') {
p = format!("{}{}", "m/", p);
Expand Down
30 changes: 26 additions & 4 deletions rust/keystore/src/algorithms/zcash/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use core::str::FromStr;

use alloc::{
string::{String, ToString},
vec::Vec,
};
use bitcoin::bip32::{ChildNumber, DerivationPath};
use hex;
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaCha8Rng;
use hex;
use zcash_vendor::{
orchard::keys::{SpendAuthorizingKey, SpendingKey},
pasta_curves::{group::ff::PrimeField, Fq},
Expand All @@ -15,6 +18,8 @@ use zcash_vendor::{

use crate::errors::{KeystoreError, Result};

use super::utils::normalize_path;

pub fn derive_ufvk(seed: &[u8]) -> Result<String> {
let usk = UnifiedSpendingKey::from_seed(&MAIN_NETWORK, &seed, AccountId::ZERO)
.map_err(|e| KeystoreError::DerivationError(e.to_string()))?;
Expand All @@ -29,12 +34,29 @@ pub fn calculate_seed_fingerprint(seed: &[u8]) -> Result<[u8; 32]> {
Ok(sfp.to_bytes())
}

pub fn sign_message_orchard(seed: &[u8], alpha: [u8; 32], msg: &[u8]) -> Result<[u8; 64]> {
pub fn sign_message_orchard(
seed: &[u8],
alpha: [u8; 32],
msg: &[u8],
path: &str,
) -> Result<[u8; 64]> {
let p = normalize_path(path);
let derivation_path = DerivationPath::from_str(p.as_str())
.map_err(|e| KeystoreError::InvalidDerivationPath(e.to_string()))?;

let coin_type = 133;
let account = derivation_path[2];
let account_id = match account {
ChildNumber::Normal { index } => index,
ChildNumber::Hardened { index } => index,
};
let account_id = AccountId::try_from(account_id).unwrap();

let mut alpha = alpha;
alpha.reverse();
let rng_seed = alpha.clone();
let rng = ChaCha8Rng::from_seed(rng_seed);
let osk = SpendingKey::from_zip32_seed(seed, 133, AccountId::ZERO).unwrap();
let osk = SpendingKey::from_zip32_seed(seed, coin_type, account_id).unwrap();
let osak = SpendAuthorizingKey::from(&osk);
let randm = Fq::from_repr(alpha)
.into_option()
Expand Down Expand Up @@ -75,9 +97,9 @@ mod tests {
};
use zcash_vendor::pasta_curves::group::ff::{FromUniformBytes, PrimeField};

use hex;
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaCha8Rng;
use hex;

extern crate std;
use std::println;
Expand Down
4 changes: 3 additions & 1 deletion rust/rust_c/src/common/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ pub enum ErrorCodes {

// Zcash
ZcashGenerateAddressError = 1500,
ZcashSigningError,
}

impl ErrorCodes {
Expand Down Expand Up @@ -480,7 +481,8 @@ impl From<&ZcashError> for ErrorCodes {
fn from(value: &ZcashError) -> Self {
match value {
ZcashError::GenerateAddressError(_) => Self::ZcashGenerateAddressError,
ZcashError::InvalidDataError(_) => Self::InvalidData
ZcashError::InvalidDataError(_) => Self::InvalidData,
ZcashError::SigningError(_) => Self::ZcashSigningError,
}
}
}
Expand Down
118 changes: 91 additions & 27 deletions rust/zcash_vendor/src/pczt/pczt_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,21 @@ struct TransparentDigests {

pub type ZcashSignature = [u8; 64];

pub trait PcztSeedSigner {
fn sign_transparent(&self, hash: Hash, path: String) -> Option<ZcashSignature>;
fn sign_sapling(&self, hash: Hash, path: String) -> Option<ZcashSignature>;
fn sign_orchard(&self, hash: Hash, path: String) -> Option<ZcashSignature>;
pub trait PcztSigner {
type Error;
fn sign_transparent(&self, hash: &[u8], path: String) -> Result<ZcashSignature, Self::Error>;
fn sign_sapling(
&self,
hash: &[u8],
alpha: [u8; 32],
path: String,
) -> Result<ZcashSignature, Self::Error>;
fn sign_orchard(
&self,
hash: &[u8],
alpha: [u8; 32],
path: String,
) -> Result<ZcashSignature, Self::Error>;
}

impl Pczt {
Expand Down Expand Up @@ -333,35 +344,88 @@ impl Pczt {
}
}

pub fn sign<T: PcztSeedSigner>(&self, signer: &T) -> Self {
pub fn sign<T: PcztSigner>(&self, signer: &T) -> Result<Self, T::Error> {
let mut pczt = self.clone();
pczt.transparent
.inputs
.iter_mut()
.enumerate()
.for_each(|(i, input)| {
.try_for_each(|(i, input)| {
let signature = signer.sign_transparent(
self.transparent_sig_digest(Some((input, i as u32))),
self.transparent_sig_digest(Some((input, i as u32)))
.as_bytes(),
"".to_string(),
);
if let Some(signature) = signature {
input
.signatures
.insert(input.script_pubkey.clone(), signature.to_vec());
}
});
pczt.sapling.spends.iter_mut().for_each(|spend| {
let signature = signer.sign_sapling(self.sheilded_sig_commitment(), "".to_string());
if let Some(signature) = signature {
spend.spend_auth_sig = Some(signature);
}
});
pczt.orchard.actions.iter_mut().for_each(|action| {
let signature = signer.sign_orchard(self.sheilded_sig_commitment(), "".to_string());
if let Some(signature) = signature {
action.spend.spend_auth_sig = Some(signature);
}
});
pczt
)?;
input
.signatures
.insert(input.script_pubkey.clone(), signature.to_vec());
Ok(())
})?;
pczt.sapling.spends.iter_mut().try_for_each(|spend| {
let signature = signer.sign_sapling(
self.sheilded_sig_commitment().as_bytes(),
pczt.sapling.anchor.unwrap(),
"".to_string(),
)?;
spend.spend_auth_sig = Some(signature);
Ok(())
})?;
pczt.orchard.actions.iter_mut().try_for_each(|action| {
let signature = signer.sign_orchard(
self.sheilded_sig_commitment().as_bytes(),
pczt.orchard.anchor.unwrap(),
"".to_string(),
)?;
action.spend.spend_auth_sig = Some(signature);
Ok(())
})?;
Ok(pczt)
}
}

#[cfg(test)]
mod tests {
extern crate std;
use alloc::{collections::btree_map::BTreeMap, vec};

use crate::pczt::{
self, common::Global, orchard, sapling, transparent, Version, V5_TX_VERSION,
V5_VERSION_GROUP_ID,
};

use super::*;

#[test]
fn test_pczt_hash() {
let pczt = Pczt {
version: Version::V0,
global: Global {
tx_version: V5_TX_VERSION,
version_group_id: V5_VERSION_GROUP_ID,
consensus_branch_id: 0xc8e7_1055,
lock_time: 0,
expiry_height: 0,
proprietary: BTreeMap::new(),
},
transparent: transparent::Bundle {
inputs: vec![],
outputs: vec![],
},
sapling: sapling::Bundle {
anchor: None,
spends: vec![],
outputs: vec![],
value_balance: 0,
bsk: None,
},
orchard: orchard::Bundle {
anchor: None,
actions: vec![],
flags: 0,
value_balance: 0,
zkproof: None,
bsk: None,
},
};
}
}

0 comments on commit 515890a

Please sign in to comment.