use blake2b_simd::Hash as Blake2bHash;
use rand_core::OsRng;
use ::transparent::sighash::{SIGHASH_ANYONECANPAY, SIGHASH_NONE, SIGHASH_SINGLE};
use zcash_primitives::transaction::{
Authorization, TransactionData, TxDigests, TxVersion, sighash::SignableInput,
sighash_v5::v5_signature_hash, txid::TxIdDigester,
};
use zcash_protocol::consensus::BranchId;
#[cfg(all(
any(zcash_unstable = "nu7", zcash_unstable = "zfuture"),
feature = "zip-233"
))]
use zcash_protocol::value::Zatoshis;
use crate::{
Pczt,
common::{
FLAG_HAS_SIGHASH_SINGLE, FLAG_SHIELDED_MODIFIABLE, FLAG_TRANSPARENT_INPUTS_MODIFIABLE,
FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE, Global,
},
};
use crate::common::determine_lock_time;
const V5_TX_VERSION: u32 = 5;
const V5_VERSION_GROUP_ID: u32 = 0x26A7270A;
pub struct Signer {
global: Global,
transparent: transparent::pczt::Bundle,
sapling: sapling::pczt::Bundle,
orchard: orchard::pczt::Bundle,
tx_data: TransactionData<EffectsOnly>,
txid_parts: TxDigests<Blake2bHash>,
shielded_sighash: [u8; 32],
secp: secp256k1::Secp256k1<secp256k1::SignOnly>,
}
impl Signer {
pub fn new(pczt: Pczt) -> Result<Self, Error> {
let Pczt {
global,
transparent,
sapling,
orchard,
} = pczt;
let transparent = transparent.into_parsed().map_err(Error::TransparentParse)?;
let sapling = sapling.into_parsed().map_err(Error::SaplingParse)?;
let orchard = orchard.into_parsed().map_err(Error::OrchardParse)?;
let tx_data = pczt_to_tx_data(&global, &transparent, &sapling, &orchard)?;
let txid_parts = tx_data.digest(TxIdDigester);
match (global.tx_version, global.version_group_id) {
(V5_TX_VERSION, V5_VERSION_GROUP_ID) => Ok(()),
(version, version_group_id) => Err(Error::Global(GlobalError::UnsupportedTxVersion {
version,
version_group_id,
})),
}?;
let shielded_sighash = v5_signature_hash(&tx_data, &SignableInput::Shielded, &txid_parts)
.as_ref()
.try_into()
.expect("correct length");
Ok(Self {
global,
transparent,
sapling,
orchard,
tx_data,
txid_parts,
shielded_sighash,
secp: secp256k1::Secp256k1::signing_only(),
})
}
pub fn shielded_sighash(&self) -> [u8; 32] {
self.shielded_sighash
}
pub fn sign_transparent(
&mut self,
index: usize,
sk: &secp256k1::SecretKey,
) -> Result<(), Error> {
let input = self
.transparent
.inputs_mut()
.get_mut(index)
.ok_or(Error::InvalidIndex)?;
input
.sign(
index,
|input| {
v5_signature_hash(
&self.tx_data,
&SignableInput::Transparent(input),
&self.txid_parts,
)
.as_ref()
.try_into()
.unwrap()
},
sk,
&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> {
let spend = self
.sapling
.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)?;
spend
.sign(self.shielded_sighash, ask, OsRng)
.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> {
let action = self
.orchard
.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)?;
action
.sign(self.shielded_sighash, ask, OsRng)
.map_err(Error::OrchardSign)?;
self.global.tx_modifiable &= !(FLAG_TRANSPARENT_INPUTS_MODIFIABLE
| FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE
| FLAG_SHIELDED_MODIFIABLE);
Ok(())
}
pub fn finish(self) -> Pczt {
Pczt {
global: self.global,
transparent: crate::transparent::Bundle::serialize_from(self.transparent),
sapling: crate::sapling::Bundle::serialize_from(self.sapling),
orchard: crate::orchard::Bundle::serialize_from(self.orchard),
}
}
}
pub(crate) fn pczt_to_tx_data(
global: &Global,
transparent: &transparent::pczt::Bundle,
sapling: &sapling::pczt::Bundle,
orchard: &orchard::pczt::Bundle,
) -> Result<TransactionData<EffectsOnly>, Error> {
let version = match (global.tx_version, global.version_group_id) {
(V5_TX_VERSION, V5_VERSION_GROUP_ID) => Ok(TxVersion::V5),
(version, version_group_id) => Err(Error::Global(GlobalError::UnsupportedTxVersion {
version,
version_group_id,
})),
}?;
let consensus_branch_id = BranchId::try_from(global.consensus_branch_id)
.map_err(|_| Error::Global(GlobalError::UnknownConsensusBranchId))?;
let transparent_bundle = transparent
.extract_effects()
.map_err(Error::TransparentExtract)?;
let sapling_bundle = sapling.extract_effects().map_err(Error::SaplingExtract)?;
let orchard_bundle = orchard.extract_effects().map_err(Error::OrchardExtract)?;
Ok(TransactionData::from_parts(
version,
consensus_branch_id,
determine_lock_time(global, transparent.inputs()).ok_or(Error::IncompatibleLockTimes)?,
global.expiry_height.into(),
#[cfg(all(
any(zcash_unstable = "nu7", zcash_unstable = "zfuture"),
feature = "zip-233"
))]
Zatoshis::ZERO,
transparent_bundle,
None,
sapling_bundle,
orchard_bundle,
))
}
pub struct EffectsOnly;
impl Authorization for EffectsOnly {
type TransparentAuth = transparent::bundle::EffectsOnly;
type SaplingAuth = sapling::bundle::EffectsOnly;
type OrchardAuth = orchard::bundle::EffectsOnly;
#[cfg(zcash_unstable = "zfuture")]
type TzeAuth = core::convert::Infallible;
}
#[derive(Debug)]
pub enum Error {
Global(GlobalError),
IncompatibleLockTimes,
InvalidIndex,
OrchardExtract(orchard::pczt::TxExtractorError),
OrchardParse(orchard::pczt::ParseError),
OrchardSign(orchard::pczt::SignerError),
OrchardVerify(orchard::pczt::VerifyError),
SaplingExtract(sapling::pczt::TxExtractorError),
SaplingParse(sapling::pczt::ParseError),
SaplingSign(sapling::pczt::SignerError),
SaplingVerify(sapling::pczt::VerifyError),
TransparentExtract(transparent::pczt::TxExtractorError),
TransparentParse(transparent::pczt::ParseError),
TransparentSign(transparent::pczt::SignerError),
}
#[derive(Debug)]
pub enum GlobalError {
UnknownConsensusBranchId,
UnsupportedTxVersion { version: u32, version_group_id: u32 },
}