use core::fmt;
use nonempty::NonEmpty;
use rand::{CryptoRng, RngCore};
use super::Action;
use crate::{
bundle::{Authorization, Authorized, EffectsOnly},
primitives::redpallas::{self, Binding, SpendAuth},
Proof,
};
impl super::Bundle {
pub fn extract_effects<V: TryFrom<i64>>(
&self,
) -> Result<Option<crate::Bundle<EffectsOnly, V>>, TxExtractorError> {
self.to_tx_data(|_| Ok(()), |_| Ok(EffectsOnly))
}
pub fn extract<V: TryFrom<i64>>(
self,
) -> Result<Option<crate::Bundle<Unbound, V>>, TxExtractorError> {
self.to_tx_data(
|action| {
action
.spend
.spend_auth_sig
.clone()
.ok_or(TxExtractorError::MissingSpendAuthSig)
},
|bundle| {
Ok(Unbound {
proof: bundle
.zkproof
.clone()
.ok_or(TxExtractorError::MissingProof)?,
bsk: bundle
.bsk
.clone()
.ok_or(TxExtractorError::MissingBindingSignatureSigningKey)?,
})
},
)
}
fn to_tx_data<A, V, E, F, G>(
&self,
action_auth: F,
bundle_auth: G,
) -> Result<Option<crate::Bundle<A, V>>, E>
where
A: Authorization,
E: From<TxExtractorError>,
F: Fn(&Action) -> Result<<A as Authorization>::SpendAuth, E>,
G: FnOnce(&Self) -> Result<A, E>,
V: TryFrom<i64>,
{
let actions = self
.actions
.iter()
.map(|action| {
let authorization = action_auth(action)?;
Ok(crate::Action::from_parts(
action.spend.nullifier,
action.spend.rk.clone(),
action.output.cmx,
action.output.encrypted_note.clone(),
action.cv_net.clone(),
authorization,
))
})
.collect::<Result<_, E>>()?;
Ok(if let Some(actions) = NonEmpty::from_vec(actions) {
let value_balance = i64::try_from(self.value_sum)
.ok()
.and_then(|v| v.try_into().ok())
.ok_or(TxExtractorError::ValueSumOutOfRange)?;
let authorization = bundle_auth(self)?;
Some(crate::Bundle::from_parts(
actions,
self.flags,
value_balance,
self.anchor,
authorization,
))
} else {
None
})
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum TxExtractorError {
MissingBindingSignatureSigningKey,
MissingProof,
MissingSpendAuthSig,
ValueSumOutOfRange,
}
impl fmt::Display for TxExtractorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TxExtractorError::MissingBindingSignatureSigningKey => {
write!(f, "`bsk` must be set for the Transaction Extractor role")
}
TxExtractorError::MissingProof => write!(
f,
"Orchard `zkproof` must be set for the Transaction Extractor role"
),
TxExtractorError::MissingSpendAuthSig => write!(
f,
"`spend_auth_sig` fields must all be set for the Transaction Extractor role"
),
TxExtractorError::ValueSumOutOfRange => {
write!(f, "value sum does not fit into a `valueBalance`")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for TxExtractorError {}
#[derive(Debug)]
pub struct Unbound {
proof: Proof,
bsk: redpallas::SigningKey<Binding>,
}
impl Authorization for Unbound {
type SpendAuth = redpallas::Signature<SpendAuth>;
}
impl<V> crate::Bundle<Unbound, V> {
pub fn apply_binding_signature<R: RngCore + CryptoRng>(
self,
sighash: [u8; 32],
rng: R,
) -> Option<crate::Bundle<Authorized, V>> {
if self
.actions()
.iter()
.all(|action| action.rk().verify(&sighash, action.authorization()).is_ok())
{
Some(self.map_authorization(
&mut (),
|_, _, a| a,
|_, Unbound { proof, bsk }| Authorized::from_parts(proof, bsk.sign(rng, &sighash)),
))
} else {
None
}
}
}