From e5fb4a42e13176b4a33c2c1c1cee1937d80f7f8b Mon Sep 17 00:00:00 2001 From: soralit Date: Thu, 31 Oct 2024 17:37:44 +0800 Subject: [PATCH] feat: impl signer for pczt --- rust/apps/zcash/src/errors.rs | 2 + rust/apps/zcash/src/pczt.rs | 59 ++++++----- rust/keystore/src/algorithms/utils.rs | 2 +- rust/keystore/src/algorithms/zcash/mod.rs | 30 +++++- rust/rust_c/src/common/src/errors.rs | 4 +- rust/zcash_vendor/src/pczt/pczt_ext.rs | 118 +++++++++++++++++----- 6 files changed, 157 insertions(+), 58 deletions(-) diff --git a/rust/apps/zcash/src/errors.rs b/rust/apps/zcash/src/errors.rs index dc421618c..340c62f33 100644 --- a/rust/apps/zcash/src/errors.rs +++ b/rust/apps/zcash/src/errors.rs @@ -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), } \ No newline at end of file diff --git a/rust/apps/zcash/src/pczt.rs b/rust/apps/zcash/src/pczt.rs index d240b05ee..a6a39121b 100644 --- a/rust/apps/zcash/src/pczt.rs +++ b/rust/apps/zcash/src/pczt.rs @@ -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; - fn verify(&self) -> Result; - fn hash(&self) -> Result>; +struct SeedSigner { + seed: [u8; 32], } -fn verify_transparent_part(pczt: &Pczt, xpub: &Xpub) -> Result { - Ok(true) -} - -fn verify_orchard_part(pczt: &Pczt, fvk: &FullViewingKey) -> Result { - Ok(true) -} - -impl PcztTrait for Pczt { - fn sign(&self) -> Result { - Ok(self.clone()) +impl PcztSigner for SeedSigner { + type Error = ZcashError; + fn sign_transparent(&self, hash: &[u8], path: String) -> Result { + 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 { - Ok(true) + fn sign_sapling( + &self, + hash: &[u8], + alpha: [u8; 32], + path: String, + ) -> Result { + // we don't support sapling yet + Err(ZcashError::SigningError( + "sapling not supported".to_string(), + )) } - fn hash(&self) -> Result> { - let version = self.global.tx_version; - Ok(vec![]) + fn sign_orchard( + &self, + hash: &[u8], + alpha: [u8; 32], + path: String, + ) -> Result { + sign_message_orchard(&self.seed, alpha, hash, &path) + .map(|signature| ZcashSignature::from(signature)) + .map_err(|e| ZcashError::SigningError(e.to_string())) } } diff --git a/rust/keystore/src/algorithms/utils.rs b/rust/keystore/src/algorithms/utils.rs index 82627b2f5..28bea7cc0 100644 --- a/rust/keystore/src/algorithms/utils.rs +++ b/rust/keystore/src/algorithms/utils.rs @@ -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); diff --git a/rust/keystore/src/algorithms/zcash/mod.rs b/rust/keystore/src/algorithms/zcash/mod.rs index 6c394b9e3..11f84ec69 100644 --- a/rust/keystore/src/algorithms/zcash/mod.rs +++ b/rust/keystore/src/algorithms/zcash/mod.rs @@ -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}, @@ -15,6 +18,8 @@ use zcash_vendor::{ use crate::errors::{KeystoreError, Result}; +use super::utils::normalize_path; + pub fn derive_ufvk(seed: &[u8]) -> Result { let usk = UnifiedSpendingKey::from_seed(&MAIN_NETWORK, &seed, AccountId::ZERO) .map_err(|e| KeystoreError::DerivationError(e.to_string()))?; @@ -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() @@ -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; diff --git a/rust/rust_c/src/common/src/errors.rs b/rust/rust_c/src/common/src/errors.rs index cf47a5803..7d2c9e1e5 100644 --- a/rust/rust_c/src/common/src/errors.rs +++ b/rust/rust_c/src/common/src/errors.rs @@ -200,6 +200,7 @@ pub enum ErrorCodes { // Zcash ZcashGenerateAddressError = 1500, + ZcashSigningError, } impl ErrorCodes { @@ -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, } } } diff --git a/rust/zcash_vendor/src/pczt/pczt_ext.rs b/rust/zcash_vendor/src/pczt/pczt_ext.rs index 9abdca6ac..8ff75ee25 100644 --- a/rust/zcash_vendor/src/pczt/pczt_ext.rs +++ b/rust/zcash_vendor/src/pczt/pczt_ext.rs @@ -72,10 +72,21 @@ struct TransparentDigests { pub type ZcashSignature = [u8; 64]; -pub trait PcztSeedSigner { - fn sign_transparent(&self, hash: Hash, path: String) -> Option; - fn sign_sapling(&self, hash: Hash, path: String) -> Option; - fn sign_orchard(&self, hash: Hash, path: String) -> Option; +pub trait PcztSigner { + type Error; + fn sign_transparent(&self, hash: &[u8], path: String) -> Result; + fn sign_sapling( + &self, + hash: &[u8], + alpha: [u8; 32], + path: String, + ) -> Result; + fn sign_orchard( + &self, + hash: &[u8], + alpha: [u8; 32], + path: String, + ) -> Result; } impl Pczt { @@ -333,35 +344,88 @@ impl Pczt { } } - pub fn sign(&self, signer: &T) -> Self { + pub fn sign(&self, signer: &T) -> Result { 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: 0xc2d6_d0b4, + 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, + }, + }; } }