use crate::Pczt;
pub struct Signer {
pczt: Pczt,
}
impl Signer {
pub fn new(pczt: Pczt) -> Self {
Self { pczt }
}
#[cfg(feature = "orchard")]
pub fn sign_ironwood_with<E, F>(self, f: F) -> Result<Self, E>
where
E: From<OrchardParseError>,
F: FnOnce(&Pczt, &mut orchard::pczt::Bundle, &mut u8) -> Result<(), E>,
{
let mut pczt = self.pczt;
let mut tx_modifiable = pczt.global.tx_modifiable;
let anchor_requirement =
crate::common::AnchorRequirement::for_pre_authorization(pczt.global.tx_version);
let fvk_snapshot = snapshot_spend_fvks(&pczt.ironwood);
let mut parsed = pczt
.ironwood
.clone()
.into_ironwood_parsed_preverified_for_signing(anchor_requirement)
.map_err(OrchardParseError::Parse)?;
f(&pczt, &mut parsed.bundle, &mut tx_modifiable)?;
pczt.global.tx_modifiable = tx_modifiable;
pczt.ironwood = parsed.reserialize();
restore_spend_fvks(&mut pczt.ironwood, &fvk_snapshot).map_err(E::from)?;
Ok(Self { pczt })
}
#[cfg(feature = "orchard")]
pub fn sign_orchard_with<E, F>(self, f: F) -> Result<Self, E>
where
E: From<OrchardParseError>,
F: FnOnce(&Pczt, &mut orchard::pczt::Bundle, &mut u8) -> Result<(), E>,
{
let mut pczt = self.pczt;
let mut tx_modifiable = pczt.global.tx_modifiable;
let anchor_requirement =
crate::common::AnchorRequirement::for_pre_authorization(pczt.global.tx_version);
let bundle_version = crate::orchard::orchard_bundle_version(&pczt.global)
.ok_or(OrchardParseError::UnsupportedConsensusBranchId)?;
let fvk_snapshot = snapshot_spend_fvks(&pczt.orchard);
let mut parsed = pczt
.orchard
.clone()
.into_parsed_with_version_preverified_for_signing(bundle_version, anchor_requirement)
.map_err(OrchardParseError::Parse)?;
f(&pczt, &mut parsed.bundle, &mut tx_modifiable)?;
pczt.global.tx_modifiable = tx_modifiable;
pczt.orchard = parsed.reserialize();
restore_spend_fvks(&mut pczt.orchard, &fvk_snapshot).map_err(E::from)?;
Ok(Self { pczt })
}
#[cfg(feature = "sapling")]
pub fn sign_sapling_with<E, F>(self, f: F) -> Result<Self, E>
where
E: From<crate::sapling::ParseError>,
F: FnOnce(&Pczt, &mut sapling::pczt::Bundle, &mut u8) -> Result<(), E>,
{
let mut pczt = self.pczt;
let mut tx_modifiable = pczt.global.tx_modifiable;
let anchor_requirement =
crate::common::AnchorRequirement::for_pre_authorization(pczt.global.tx_version);
let mut parsed = pczt.sapling.clone().into_parsed(anchor_requirement)?;
f(&pczt, &mut parsed.bundle, &mut tx_modifiable)?;
pczt.global.tx_modifiable = tx_modifiable;
pczt.sapling = parsed.reserialize();
Ok(Self { pczt })
}
#[cfg(feature = "transparent")]
pub fn sign_transparent_with<E, F>(self, f: F) -> Result<Self, E>
where
E: From<transparent::pczt::ParseError>,
F: FnOnce(&Pczt, &mut transparent::pczt::Bundle, &mut u8) -> Result<(), E>,
{
let mut pczt = self.pczt;
let mut tx_modifiable = pczt.global.tx_modifiable;
let mut bundle = pczt.transparent.clone().into_parsed()?;
f(&pczt, &mut bundle, &mut tx_modifiable)?;
pczt.global.tx_modifiable = tx_modifiable;
pczt.transparent = crate::transparent::Bundle::serialize_from(bundle);
Ok(Self { pczt })
}
pub fn finish(self) -> Pczt {
self.pczt
}
}
#[cfg(feature = "orchard")]
type SpendFvkSnapshot = alloc::vec::Vec<([u8; 32], Option<[u8; 96]>)>;
#[cfg(feature = "orchard")]
fn snapshot_spend_fvks(bundle: &crate::orchard::Bundle) -> SpendFvkSnapshot {
bundle
.actions()
.iter()
.map(|action| (action.spend.rk, action.spend.fvk))
.collect()
}
#[cfg(feature = "orchard")]
fn restore_spend_fvks(
bundle: &mut crate::orchard::Bundle,
snapshot: &SpendFvkSnapshot,
) -> Result<(), OrchardParseError> {
if bundle.actions.len() != snapshot.len() {
return Err(OrchardParseError::SigningClosureModifiedActions);
}
for (action, (rk, _)) in bundle.actions.iter().zip(snapshot) {
if action.spend.rk != *rk {
return Err(OrchardParseError::SigningClosureModifiedActions);
}
}
for (action, (_, fvk)) in bundle.actions.iter_mut().zip(snapshot) {
action.spend.fvk = *fvk;
}
Ok(())
}
#[cfg(feature = "orchard")]
#[derive(Debug)]
pub enum OrchardParseError {
Parse(crate::orchard::ParseError),
UnsupportedConsensusBranchId,
SigningClosureModifiedActions,
}
#[cfg(feature = "orchard")]
impl From<crate::orchard::ParseError> for OrchardParseError {
fn from(e: crate::orchard::ParseError) -> Self {
OrchardParseError::Parse(e)
}
}
#[cfg(all(test, feature = "orchard"))]
mod tests {
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use crate::orchard::{Action, Bundle, EncCiphertext, NoteVersion, Output, Spend};
use super::{OrchardParseError, restore_spend_fvks, snapshot_spend_fvks};
#[test]
fn restore_spend_fvks_preserves_duplicate_nullifiers_by_position() {
let first_fvk = Some([7u8; 96]);
let second_fvk = Some([9u8; 96]);
let mut bundle = bundle_with_duplicate_nullifier_fvks([first_fvk, second_fvk]);
let snapshot = snapshot_spend_fvks(&bundle);
bundle.actions[0].spend.fvk = None;
bundle.actions[1].spend.fvk = None;
restore_spend_fvks(&mut bundle, &snapshot).expect("actions were not modified");
assert_eq!(bundle.actions[0].spend.fvk, first_fvk);
assert_eq!(bundle.actions[1].spend.fvk, second_fvk);
}
#[test]
fn restore_spend_fvks_rejects_reordered_actions() {
let first_fvk = Some([7u8; 96]);
let second_fvk = Some([9u8; 96]);
let mut bundle = bundle_with_duplicate_nullifier_fvks([first_fvk, second_fvk]);
let snapshot = snapshot_spend_fvks(&bundle);
bundle.actions.swap(0, 1);
assert!(matches!(
restore_spend_fvks(&mut bundle, &snapshot),
Err(OrchardParseError::SigningClosureModifiedActions)
));
assert_eq!(bundle.actions[0].spend.fvk, second_fvk);
assert_eq!(bundle.actions[1].spend.fvk, first_fvk);
}
fn bundle_with_duplicate_nullifier_fvks(fvks: [Option<[u8; 96]>; 2]) -> Bundle {
Bundle {
actions: fvks
.into_iter()
.enumerate()
.map(|(i, fvk)| Action {
cv_net: Some([0; 32]),
spend: Spend {
nullifier: [3u8; 32],
rk: [10 + i as u8; 32],
spend_auth_sig: None,
recipient: None,
value: None,
rho: None,
rseed: None,
fvk,
witness: None,
alpha: None,
zip32_derivation: None,
dummy_sk: None,
proprietary: BTreeMap::new(),
},
output: Output {
cmx: Some([0; 32]),
ephemeral_key: [0; 32],
enc_ciphertext: EncCiphertext::Encrypted(Vec::new()),
out_ciphertext: Vec::new(),
recipient: None,
value: None,
rseed: None,
ock: None,
zip32_derivation: None,
user_address: Option::<String>::None,
proprietary: BTreeMap::new(),
},
rcv: None,
})
.collect(),
flags: 0,
value_sum: (0, false),
anchor: Some([0; 32]),
note_version: NoteVersion::V2,
zkproof: None,
bsk: None,
}
}
}