pczt/roles/low_level_signer/
mod.rs1use crate::Pczt;
4
5pub struct Signer {
6 pczt: Pczt,
7}
8
9impl Signer {
10 pub fn new(pczt: Pczt) -> Self {
12 Self { pczt }
13 }
14
15 #[cfg(feature = "orchard")]
28 pub fn sign_ironwood_with<E, F>(self, f: F) -> Result<Self, E>
29 where
30 E: From<OrchardParseError>,
31 F: FnOnce(&Pczt, &mut orchard::pczt::Bundle, &mut u8) -> Result<(), E>,
32 {
33 let mut pczt = self.pczt;
34
35 let mut tx_modifiable = pczt.global.tx_modifiable;
36 let anchor_requirement =
37 crate::common::AnchorRequirement::for_pre_authorization(pczt.global.tx_version);
38
39 let fvk_snapshot = snapshot_spend_fvks(&pczt.ironwood);
40 let mut parsed = pczt
41 .ironwood
42 .clone()
43 .into_ironwood_parsed_preverified_for_signing(anchor_requirement)
44 .map_err(OrchardParseError::Parse)?;
45
46 f(&pczt, &mut parsed.bundle, &mut tx_modifiable)?;
47
48 pczt.global.tx_modifiable = tx_modifiable;
49 pczt.ironwood = parsed.reserialize();
50 restore_spend_fvks(&mut pczt.ironwood, &fvk_snapshot).map_err(E::from)?;
51
52 Ok(Self { pczt })
53 }
54
55 #[cfg(feature = "orchard")]
68 pub fn sign_orchard_with<E, F>(self, f: F) -> Result<Self, E>
69 where
70 E: From<OrchardParseError>,
71 F: FnOnce(&Pczt, &mut orchard::pczt::Bundle, &mut u8) -> Result<(), E>,
72 {
73 let mut pczt = self.pczt;
74
75 let mut tx_modifiable = pczt.global.tx_modifiable;
76 let anchor_requirement =
77 crate::common::AnchorRequirement::for_pre_authorization(pczt.global.tx_version);
78
79 let bundle_version = crate::orchard::orchard_bundle_version(&pczt.global)
80 .ok_or(OrchardParseError::UnsupportedConsensusBranchId)?;
81 let fvk_snapshot = snapshot_spend_fvks(&pczt.orchard);
82 let mut parsed = pczt
83 .orchard
84 .clone()
85 .into_parsed_with_version_preverified_for_signing(bundle_version, anchor_requirement)
86 .map_err(OrchardParseError::Parse)?;
87
88 f(&pczt, &mut parsed.bundle, &mut tx_modifiable)?;
89
90 pczt.global.tx_modifiable = tx_modifiable;
91 pczt.orchard = parsed.reserialize();
92 restore_spend_fvks(&mut pczt.orchard, &fvk_snapshot).map_err(E::from)?;
93
94 Ok(Self { pczt })
95 }
96
97 #[cfg(feature = "sapling")]
99 pub fn sign_sapling_with<E, F>(self, f: F) -> Result<Self, E>
100 where
101 E: From<crate::sapling::ParseError>,
102 F: FnOnce(&Pczt, &mut sapling::pczt::Bundle, &mut u8) -> Result<(), E>,
103 {
104 let mut pczt = self.pczt;
105
106 let mut tx_modifiable = pczt.global.tx_modifiable;
107 let anchor_requirement =
108 crate::common::AnchorRequirement::for_pre_authorization(pczt.global.tx_version);
109
110 let mut parsed = pczt.sapling.clone().into_parsed(anchor_requirement)?;
111
112 f(&pczt, &mut parsed.bundle, &mut tx_modifiable)?;
113
114 pczt.global.tx_modifiable = tx_modifiable;
115 pczt.sapling = parsed.reserialize();
116
117 Ok(Self { pczt })
118 }
119
120 #[cfg(feature = "transparent")]
122 pub fn sign_transparent_with<E, F>(self, f: F) -> Result<Self, E>
123 where
124 E: From<transparent::pczt::ParseError>,
125 F: FnOnce(&Pczt, &mut transparent::pczt::Bundle, &mut u8) -> Result<(), E>,
126 {
127 let mut pczt = self.pczt;
128
129 let mut tx_modifiable = pczt.global.tx_modifiable;
130
131 let mut bundle = pczt.transparent.clone().into_parsed()?;
132
133 f(&pczt, &mut bundle, &mut tx_modifiable)?;
134
135 pczt.global.tx_modifiable = tx_modifiable;
136 pczt.transparent = crate::transparent::Bundle::serialize_from(bundle);
137
138 Ok(Self { pczt })
139 }
140
141 pub fn finish(self) -> Pczt {
143 self.pczt
144 }
145}
146
147#[cfg(feature = "orchard")]
151type SpendFvkSnapshot = alloc::vec::Vec<([u8; 32], Option<[u8; 96]>)>;
152
153#[cfg(feature = "orchard")]
156fn snapshot_spend_fvks(bundle: &crate::orchard::Bundle) -> SpendFvkSnapshot {
157 bundle
158 .actions()
159 .iter()
160 .map(|action| (action.spend.rk, action.spend.fvk))
161 .collect()
162}
163
164#[cfg(feature = "orchard")]
174fn restore_spend_fvks(
175 bundle: &mut crate::orchard::Bundle,
176 snapshot: &SpendFvkSnapshot,
177) -> Result<(), OrchardParseError> {
178 if bundle.actions.len() != snapshot.len() {
181 return Err(OrchardParseError::SigningClosureModifiedActions);
182 }
183 for (action, (rk, _)) in bundle.actions.iter().zip(snapshot) {
184 if action.spend.rk != *rk {
185 return Err(OrchardParseError::SigningClosureModifiedActions);
186 }
187 }
188 for (action, (_, fvk)) in bundle.actions.iter_mut().zip(snapshot) {
189 action.spend.fvk = *fvk;
190 }
191 Ok(())
192}
193
194#[cfg(feature = "orchard")]
197#[derive(Debug)]
198pub enum OrchardParseError {
199 Parse(crate::orchard::ParseError),
201 UnsupportedConsensusBranchId,
204 SigningClosureModifiedActions,
208}
209
210#[cfg(feature = "orchard")]
211impl From<crate::orchard::ParseError> for OrchardParseError {
212 fn from(e: crate::orchard::ParseError) -> Self {
213 OrchardParseError::Parse(e)
214 }
215}
216
217#[cfg(all(test, feature = "orchard"))]
218mod tests {
219 use alloc::collections::BTreeMap;
220 use alloc::string::String;
221 use alloc::vec::Vec;
222
223 use crate::orchard::{Action, Bundle, EncCiphertext, NoteVersion, Output, Spend};
224
225 use super::{OrchardParseError, restore_spend_fvks, snapshot_spend_fvks};
226
227 #[test]
228 fn restore_spend_fvks_preserves_duplicate_nullifiers_by_position() {
229 let first_fvk = Some([7u8; 96]);
230 let second_fvk = Some([9u8; 96]);
231 let mut bundle = bundle_with_duplicate_nullifier_fvks([first_fvk, second_fvk]);
234 let snapshot = snapshot_spend_fvks(&bundle);
235
236 bundle.actions[0].spend.fvk = None;
237 bundle.actions[1].spend.fvk = None;
238
239 restore_spend_fvks(&mut bundle, &snapshot).expect("actions were not modified");
240
241 assert_eq!(bundle.actions[0].spend.fvk, first_fvk);
242 assert_eq!(bundle.actions[1].spend.fvk, second_fvk);
243 }
244
245 #[test]
246 fn restore_spend_fvks_rejects_reordered_actions() {
247 let first_fvk = Some([7u8; 96]);
248 let second_fvk = Some([9u8; 96]);
249 let mut bundle = bundle_with_duplicate_nullifier_fvks([first_fvk, second_fvk]);
250 let snapshot = snapshot_spend_fvks(&bundle);
251
252 bundle.actions.swap(0, 1);
254
255 assert!(matches!(
256 restore_spend_fvks(&mut bundle, &snapshot),
257 Err(OrchardParseError::SigningClosureModifiedActions)
258 ));
259 assert_eq!(bundle.actions[0].spend.fvk, second_fvk);
261 assert_eq!(bundle.actions[1].spend.fvk, first_fvk);
262 }
263
264 fn bundle_with_duplicate_nullifier_fvks(fvks: [Option<[u8; 96]>; 2]) -> Bundle {
267 Bundle {
268 actions: fvks
269 .into_iter()
270 .enumerate()
271 .map(|(i, fvk)| Action {
274 cv_net: Some([0; 32]),
275 spend: Spend {
276 nullifier: [3u8; 32],
277 rk: [10 + i as u8; 32],
278 spend_auth_sig: None,
279 recipient: None,
280 value: None,
281 rho: None,
282 rseed: None,
283 fvk,
284 witness: None,
285 alpha: None,
286 zip32_derivation: None,
287 dummy_sk: None,
288 proprietary: BTreeMap::new(),
289 },
290 output: Output {
291 cmx: Some([0; 32]),
292 ephemeral_key: [0; 32],
293 enc_ciphertext: EncCiphertext::Encrypted(Vec::new()),
294 out_ciphertext: Vec::new(),
295 recipient: None,
296 value: None,
297 rseed: None,
298 ock: None,
299 zip32_derivation: None,
300 user_address: Option::<String>::None,
301 proprietary: BTreeMap::new(),
302 },
303 rcv: None,
304 })
305 .collect(),
306 flags: 0,
307 value_sum: (0, false),
308 anchor: Some([0; 32]),
309 note_version: NoteVersion::V2,
310 zkproof: None,
311 bsk: None,
312 }
313 }
314}