use alloc::vec::Vec;
use blake2b_simd::Hash as Blake2bHash;
use orchard::primitives::redpallas;
use rand_core::OsRng;
use ::transparent::sighash::{SIGHASH_ANYONECANPAY, SIGHASH_NONE, SIGHASH_SINGLE};
use zcash_primitives::transaction::{
TransactionData, TxDigests, sighash::SignableInput, txid::TxIdDigester,
};
use crate::{
ExtractError, ParsedPczt, Pczt,
common::{
FLAG_HAS_SIGHASH_SINGLE, FLAG_SHIELDED_MODIFIABLE, FLAG_TRANSPARENT_INPUTS_MODIFIABLE,
FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE, Global,
},
};
pub use crate::EffectsOnly;
use crate::sighash;
pub mod batch;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SpendAuthSignature {
value_pool: orchard::ValuePool,
action_index: usize,
signature: [u8; 64],
}
impl SpendAuthSignature {
pub fn from_parts(
value_pool: orchard::ValuePool,
action_index: usize,
signature: [u8; 64],
) -> Self {
Self {
value_pool,
action_index,
signature,
}
}
pub fn value_pool(&self) -> orchard::ValuePool {
self.value_pool
}
pub fn action_index(&self) -> usize {
self.action_index
}
pub fn signature(&self) -> &[u8; 64] {
&self.signature
}
}
pub fn extract_orchard_spend_auth_signatures(pczt: &Pczt) -> Vec<SpendAuthSignature> {
fn extract_from_bundle(
signatures: &mut Vec<SpendAuthSignature>,
value_pool: orchard::ValuePool,
bundle: &crate::orchard::Bundle,
) {
for (action_index, action) in bundle.actions().iter().enumerate() {
if let Some(signature) = action.spend().spend_auth_sig() {
signatures.push(SpendAuthSignature::from_parts(
value_pool,
action_index,
*signature,
));
}
}
}
let mut signatures = Vec::new();
extract_from_bundle(&mut signatures, orchard::ValuePool::Orchard, pczt.orchard());
extract_from_bundle(
&mut signatures,
orchard::ValuePool::Ironwood,
pczt.ironwood(),
);
signatures
}
pub struct Signer {
global: Global,
transparent: transparent::pczt::Bundle,
sapling: crate::sapling::Parsed,
orchard: crate::orchard::Parsed,
ironwood: crate::orchard::Parsed,
empty_ironwood: Option<crate::orchard::Bundle>,
tx_data: TransactionData<EffectsOnly>,
txid_parts: TxDigests<Blake2bHash>,
shielded_sighash: [u8; 32],
secp: secp256k1::Secp256k1<secp256k1::All>,
}
impl Signer {
pub fn new(pczt: Pczt) -> Result<Self, Error> {
let anchor_requirement =
crate::common::AnchorRequirement::for_pre_authorization(pczt.global.tx_version);
let empty_ironwood = pczt
.ironwood
.actions
.is_empty()
.then(|| pczt.ironwood.clone());
let ParsedPczt {
global,
transparent,
sapling,
orchard,
ironwood,
tx_data,
} = pczt.extract_tx_data(
anchor_requirement,
|t| {
t.extract_effects()
.map_err(ExtractError::TransparentExtract)
},
|s| s.extract_effects().map_err(ExtractError::SaplingExtract),
|o| o.extract_effects().map_err(ExtractError::OrchardExtract),
|i| i.extract_effects().map_err(ExtractError::IronwoodExtract),
)?;
let txid_parts = tx_data.digest(TxIdDigester);
let shielded_sighash = sighash(&tx_data, &SignableInput::Shielded, &txid_parts);
Ok(Self {
global,
transparent,
sapling,
orchard,
ironwood,
empty_ironwood,
tx_data,
txid_parts,
shielded_sighash,
secp: secp256k1::Secp256k1::new(),
})
}
pub fn shielded_sighash(&self) -> [u8; 32] {
self.shielded_sighash
}
pub fn transparent_sighash(&self, index: usize) -> Result<[u8; 32], Error> {
let input = self
.transparent
.inputs()
.get(index)
.ok_or(Error::InvalidIndex)?;
input.with_signable_input(index, |signable_input| {
Ok(sighash(
&self.tx_data,
&SignableInput::Transparent(signable_input),
&self.txid_parts,
))
})
}
pub fn sign_transparent(
&mut self,
index: usize,
sk: &secp256k1::SecretKey,
) -> Result<(), Error> {
self.generate_or_append_transparent_signature(index, |input, tx_data, txid_parts, secp| {
input.sign(
index,
|input| sighash(tx_data, &SignableInput::Transparent(input), txid_parts),
sk,
secp,
)
})
}
pub fn append_transparent_signature(
&mut self,
index: usize,
signature: secp256k1::ecdsa::Signature,
) -> Result<(), Error> {
self.generate_or_append_transparent_signature(index, |input, tx_data, txid_parts, secp| {
input.append_signature(
index,
|input| sighash(tx_data, &SignableInput::Transparent(input), txid_parts),
signature,
secp,
)
})
}
fn generate_or_append_transparent_signature<F>(
&mut self,
index: usize,
f: F,
) -> Result<(), Error>
where
F: FnOnce(
&mut transparent::pczt::Input,
&TransactionData<EffectsOnly>,
&TxDigests<Blake2bHash>,
&secp256k1::Secp256k1<secp256k1::All>,
) -> Result<(), transparent::pczt::SignerError>,
{
let input = self
.transparent
.inputs_mut()
.get_mut(index)
.ok_or(Error::InvalidIndex)?;
f(input, &self.tx_data, &self.txid_parts, &self.secp).map_err(Error::TransparentSign)?;
if input.sighash_type().encode() & SIGHASH_ANYONECANPAY == 0 {
self.global.tx_modifiable &= !FLAG_TRANSPARENT_INPUTS_MODIFIABLE;
}
if (input.sighash_type().encode() & !SIGHASH_ANYONECANPAY) != SIGHASH_NONE {
self.global.tx_modifiable &= !FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE;
}
if (input.sighash_type().encode() & !SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE {
self.global.tx_modifiable |= FLAG_HAS_SIGHASH_SINGLE;
}
self.global.tx_modifiable &= !FLAG_SHIELDED_MODIFIABLE;
Ok(())
}
pub fn sign_sapling(
&mut self,
index: usize,
ask: &sapling::keys::SpendAuthorizingKey,
) -> Result<(), Error> {
self.generate_or_apply_sapling_signature(index, |spend, shielded_sighash| {
spend.sign(shielded_sighash, ask, OsRng)
})
}
pub fn apply_sapling_signature(
&mut self,
index: usize,
signature: redjubjub::Signature<redjubjub::SpendAuth>,
) -> Result<(), Error> {
self.generate_or_apply_sapling_signature(index, |spend, shielded_sighash| {
spend.apply_signature(shielded_sighash, signature)
})
}
fn generate_or_apply_sapling_signature<F>(&mut self, index: usize, f: F) -> Result<(), Error>
where
F: FnOnce(&mut sapling::pczt::Spend, [u8; 32]) -> Result<(), sapling::pczt::SignerError>,
{
let spend = self
.sapling
.bundle
.spends_mut()
.get_mut(index)
.ok_or(Error::InvalidIndex)?;
match spend.verify_nullifier(None) {
Err(
sapling::pczt::VerifyError::MissingRecipient
| sapling::pczt::VerifyError::MissingValue
| sapling::pczt::VerifyError::MissingRandomSeed,
) => Ok(()),
r => r,
}
.map_err(Error::SaplingVerify)?;
f(spend, self.shielded_sighash).map_err(Error::SaplingSign)?;
self.global.tx_modifiable &= !(FLAG_TRANSPARENT_INPUTS_MODIFIABLE
| FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE
| FLAG_SHIELDED_MODIFIABLE);
Ok(())
}
pub fn sign_orchard(
&mut self,
index: usize,
ask: &orchard::keys::SpendAuthorizingKey,
) -> Result<(), Error> {
self.generate_or_apply_orchard_signature(index, |spend, shielded_sighash| {
spend.sign(shielded_sighash, ask, OsRng)
})
}
pub fn apply_orchard_signature(
&mut self,
index: usize,
signature: redpallas::Signature<redpallas::SpendAuth>,
) -> Result<(), Error> {
self.generate_or_apply_orchard_signature(index, |action, shielded_sighash| {
action.apply_signature(shielded_sighash, signature)
})
}
pub fn apply_orchard_spend_auth_signature(
&mut self,
signature: &SpendAuthSignature,
) -> Result<(), Error> {
let spend_auth_sig =
redpallas::Signature::<redpallas::SpendAuth>::from(*signature.signature());
match signature.value_pool() {
orchard::ValuePool::Orchard => {
self.apply_orchard_signature(signature.action_index(), spend_auth_sig)
}
orchard::ValuePool::Ironwood => {
self.apply_ironwood_signature(signature.action_index(), spend_auth_sig)
}
}
}
fn generate_or_apply_orchard_signature<F>(&mut self, index: usize, f: F) -> Result<(), Error>
where
F: FnOnce(&mut orchard::pczt::Action, [u8; 32]) -> Result<(), orchard::pczt::SignerError>,
{
let action = self
.orchard
.bundle
.actions_mut()
.get_mut(index)
.ok_or(Error::InvalidIndex)?;
match action.spend().verify_nullifier(None) {
Err(
orchard::pczt::VerifyError::MissingRecipient
| orchard::pczt::VerifyError::MissingValue
| orchard::pczt::VerifyError::MissingRho
| orchard::pczt::VerifyError::MissingRandomSeed,
) => Ok(()),
r => r,
}
.map_err(Error::OrchardVerify)?;
f(action, self.shielded_sighash).map_err(Error::OrchardSign)?;
self.global.tx_modifiable &= !(FLAG_TRANSPARENT_INPUTS_MODIFIABLE
| FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE
| FLAG_SHIELDED_MODIFIABLE);
Ok(())
}
pub fn sign_ironwood(
&mut self,
index: usize,
ask: &orchard::keys::SpendAuthorizingKey,
) -> Result<(), Error> {
self.generate_or_apply_ironwood_signature(index, |spend, shielded_sighash| {
spend.sign(shielded_sighash, ask, OsRng)
})
}
pub fn apply_ironwood_signature(
&mut self,
index: usize,
signature: redpallas::Signature<redpallas::SpendAuth>,
) -> Result<(), Error> {
self.generate_or_apply_ironwood_signature(index, |action, shielded_sighash| {
action.apply_signature(shielded_sighash, signature)
})
}
fn generate_or_apply_ironwood_signature<F>(&mut self, index: usize, f: F) -> Result<(), Error>
where
F: FnOnce(&mut orchard::pczt::Action, [u8; 32]) -> Result<(), orchard::pczt::SignerError>,
{
let action = self
.ironwood
.bundle
.actions_mut()
.get_mut(index)
.ok_or(Error::InvalidIndex)?;
match action.spend().verify_nullifier(None) {
Err(
orchard::pczt::VerifyError::MissingRecipient
| orchard::pczt::VerifyError::MissingValue
| orchard::pczt::VerifyError::MissingRho
| orchard::pczt::VerifyError::MissingRandomSeed,
) => Ok(()),
r => r,
}
.map_err(Error::IronwoodVerify)?;
f(action, self.shielded_sighash).map_err(Error::IronwoodSign)?;
self.global.tx_modifiable &= !(FLAG_TRANSPARENT_INPUTS_MODIFIABLE
| FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE
| FLAG_SHIELDED_MODIFIABLE);
Ok(())
}
pub fn finish(self) -> Pczt {
let Self {
global,
transparent,
sapling,
orchard,
ironwood,
empty_ironwood,
tx_data: _,
txid_parts: _,
shielded_sighash: _,
secp: _,
} = self;
Pczt {
global,
transparent: crate::transparent::Bundle::serialize_from(transparent),
sapling: sapling.reserialize(),
orchard: orchard.reserialize(),
ironwood: empty_ironwood.unwrap_or_else(|| ironwood.reserialize()),
}
}
}
#[derive(Debug)]
pub enum Error {
Extract(crate::ExtractError),
InvalidIndex,
IronwoodSign(orchard::pczt::SignerError),
IronwoodVerify(orchard::pczt::VerifyError),
OrchardSign(orchard::pczt::SignerError),
OrchardVerify(orchard::pczt::VerifyError),
SaplingSign(sapling::pczt::SignerError),
SaplingVerify(sapling::pczt::VerifyError),
TransparentSign(transparent::pczt::SignerError),
}
impl From<crate::ExtractError> for Error {
fn from(e: crate::ExtractError) -> Self {
Error::Extract(e)
}
}
#[cfg(test)]
mod tests {
use ff::{Field, PrimeField};
use pasta_curves::pallas;
use zcash_protocol::consensus::BranchId;
use super::Signer;
use crate::{
orchard::{Action, Spend},
roles::{creator::Creator, io_finalizer::IoFinalizer, updater::Updater},
};
fn dummy_action() -> Action {
let sk = orchard::keys::SpendingKey::from_bytes([7; 32]).unwrap();
let alpha = pallas::Scalar::ONE;
let base = crate::orchard::testing::dummy_action();
Action {
spend: Spend {
alpha: Some(alpha.to_repr()),
dummy_sk: Some(*sk.to_bytes()),
..base.spend
},
rcv: Some([3; 32]),
..base
}
}
#[test]
fn io_finalizer_and_signer_succeed_with_absent_ironwood_anchor() {
let mut pczt = Creator::new(BranchId::Nu6_3.into(), 100, 133, None, None)
.unwrap()
.build()
.unwrap();
pczt.ironwood.actions.push(dummy_action());
assert!(pczt.ironwood.anchor.is_none());
let pczt = IoFinalizer::new(pczt).finalize_io().unwrap();
assert!(pczt.ironwood.anchor.is_none());
assert!(pczt.ironwood.bsk.is_some());
assert!(pczt.ironwood.actions[0].spend.dummy_sk.is_none());
assert!(pczt.ironwood.actions[0].spend.spend_auth_sig.is_some());
let signer = Signer::new(pczt).unwrap();
let pczt = signer.finish();
assert!(pczt.ironwood.anchor.is_none());
let anchor = orchard::Anchor::empty_tree();
let pczt = Updater::new(pczt)
.set_ironwood_anchor(anchor)
.unwrap()
.finish();
assert_eq!(pczt.ironwood.anchor, Some(anchor.to_bytes()));
}
}