1use std_shims::{
2 vec::Vec,
3 io::{self, Read},
4 collections::HashMap,
5};
6
7use rand_core::{RngCore, CryptoRng};
8
9use curve25519_dalek::{traits::Identity, Scalar, EdwardsPoint};
10use dalek_ff_group as dfg;
11
12use transcript::{Transcript, RecommendedTranscript};
13use frost::{
14 curve::Ed25519,
15 Participant, FrostError, ThresholdKeys,
16 sign::{
17 Preprocess, CachedPreprocess, SignatureShare, PreprocessMachine, SignMachine, SignatureMachine,
18 AlgorithmMachine, AlgorithmSignMachine, AlgorithmSignatureMachine,
19 },
20};
21
22use monero_oxide::{
23 ringct::{
24 clsag::{ClsagContext, ClsagMultisigMaskSender, ClsagAddendum, ClsagMultisig},
25 RctPrunable, RctProofs,
26 },
27 transaction::Transaction,
28};
29use crate::send::{SendError, SignableTransaction, key_image_sort};
30
31pub struct TransactionMachine {
33 signable: SignableTransaction,
34
35 keys: ThresholdKeys<Ed25519>,
36
37 key_image_generators_and_lincombs: Vec<(EdwardsPoint, (Scalar, Scalar))>,
39 clsags: Vec<(ClsagMultisigMaskSender, AlgorithmMachine<Ed25519, ClsagMultisig>)>,
40}
41
42pub struct TransactionSignMachine {
46 signable: SignableTransaction,
47
48 keys: ThresholdKeys<Ed25519>,
49
50 key_image_generators_and_lincombs: Vec<(EdwardsPoint, (Scalar, Scalar))>,
51 clsags: Vec<(ClsagMultisigMaskSender, AlgorithmSignMachine<Ed25519, ClsagMultisig>)>,
52
53 our_preprocess: Vec<Preprocess<Ed25519, ClsagAddendum>>,
54}
55
56pub struct TransactionSignatureMachine {
58 tx: Transaction,
59 clsags: Vec<AlgorithmSignatureMachine<Ed25519, ClsagMultisig>>,
60}
61
62impl SignableTransaction {
63 pub fn multisig(self, keys: ThresholdKeys<Ed25519>) -> Result<TransactionMachine, SendError> {
69 let mut clsags = vec![];
70
71 let mut key_image_generators_and_lincombs = vec![];
72 for input in &self.inputs {
73 let key_scalar = Scalar::ONE;
75 let key_offset = input.key_offset();
76
77 let offset = keys
78 .clone()
79 .scale(dfg::Scalar(key_scalar))
80 .expect("non-zero scalar (1) was zero")
81 .offset(dfg::Scalar(key_offset));
82 if offset.group_key().0 != input.key() {
83 Err(SendError::WrongPrivateKey)?;
84 }
85
86 let context = ClsagContext::new(input.decoys().clone(), input.commitment().clone())
87 .map_err(SendError::ClsagError)?;
88 let (clsag, clsag_mask_send) = ClsagMultisig::new(
89 RecommendedTranscript::new(b"Monero Multisignature Transaction"),
90 context,
91 );
92 key_image_generators_and_lincombs.push((
93 clsag.key_image_generator(),
94 (offset.current_scalar().0, offset.current_offset().0),
95 ));
96 clsags.push((clsag_mask_send, AlgorithmMachine::new(clsag, offset)));
97 }
98
99 Ok(TransactionMachine { signable: self, keys, key_image_generators_and_lincombs, clsags })
100 }
101}
102
103impl PreprocessMachine for TransactionMachine {
104 type Preprocess = Vec<Preprocess<Ed25519, ClsagAddendum>>;
105 type Signature = Transaction;
106 type SignMachine = TransactionSignMachine;
107
108 fn preprocess<R: RngCore + CryptoRng>(
109 mut self,
110 rng: &mut R,
111 ) -> (TransactionSignMachine, Self::Preprocess) {
112 let mut preprocesses = Vec::with_capacity(self.clsags.len());
114 let clsags = self
115 .clsags
116 .drain(..)
117 .map(|(clsag_mask_send, clsag)| {
118 let (clsag, preprocess) = clsag.preprocess(rng);
119 preprocesses.push(preprocess);
120 (clsag_mask_send, clsag)
121 })
122 .collect();
123 let our_preprocess = preprocesses.clone();
124
125 (
126 TransactionSignMachine {
127 signable: self.signable,
128
129 keys: self.keys,
130
131 key_image_generators_and_lincombs: self.key_image_generators_and_lincombs,
132 clsags,
133
134 our_preprocess,
135 },
136 preprocesses,
137 )
138 }
139}
140
141impl SignMachine<Transaction> for TransactionSignMachine {
142 type Params = ();
143 type Keys = ThresholdKeys<Ed25519>;
144 type Preprocess = Vec<Preprocess<Ed25519, ClsagAddendum>>;
145 type SignatureShare = Vec<SignatureShare<Ed25519>>;
146 type SignatureMachine = TransactionSignatureMachine;
147
148 fn cache(self) -> CachedPreprocess {
149 unimplemented!(
150 "Monero transactions don't support caching their preprocesses due to {}",
151 "being already bound to a specific transaction"
152 );
153 }
154
155 fn from_cache(
156 (): (),
157 _: ThresholdKeys<Ed25519>,
158 _: CachedPreprocess,
159 ) -> (Self, Self::Preprocess) {
160 unimplemented!(
161 "Monero transactions don't support caching their preprocesses due to {}",
162 "being already bound to a specific transaction"
163 );
164 }
165
166 fn read_preprocess<R: Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess> {
167 self.clsags.iter().map(|clsag| clsag.1.read_preprocess(reader)).collect()
168 }
169
170 fn sign(
171 self,
172 mut commitments: HashMap<Participant, Self::Preprocess>,
173 msg: &[u8],
174 ) -> Result<(TransactionSignatureMachine, Self::SignatureShare), FrostError> {
175 if !msg.is_empty() {
176 panic!("message was passed to the TransactionMachine when it generates its own");
177 }
178
179 commitments.remove(&self.keys.params().i());
183
184 let mut included = commitments.keys().copied().collect::<Vec<_>>();
186 included.push(self.keys.params().i());
188 included.sort_unstable();
191
192 let mut key_images = vec![EdwardsPoint::identity(); self.clsags.len()];
194
195 let view = self.keys.view(included.clone()).map_err(|_| {
197 FrostError::InvalidSigningSet("couldn't form an interpolated view of the key")
198 })?;
199 let mut commitments = (0 .. self.clsags.len())
200 .map(|c| {
201 included
202 .iter()
203 .map(|l| {
204 let preprocess = if *l == self.keys.params().i() {
205 self.our_preprocess[c].clone()
206 } else {
207 commitments.get_mut(l).ok_or(FrostError::MissingParticipant(*l))?[c].clone()
208 };
209
210 key_images[c] += preprocess.addendum.key_image_share().0 *
213 view
214 .interpolation_factor(*l)
215 .ok_or(FrostError::InternalError(
216 "view successfully formed with participant without an interpolation factor",
217 ))?
218 .0;
219
220 Ok((*l, preprocess))
221 })
222 .collect::<Result<HashMap<_, _>, _>>()
223 })
224 .collect::<Result<Vec<_>, _>>()?;
225
226 for (key_image, (generator, (scalar, offset))) in
227 key_images.iter_mut().zip(&self.key_image_generators_and_lincombs)
228 {
229 *key_image *= scalar;
230 *key_image += generator * offset;
231 }
232
233 for map in &mut commitments {
236 map.remove(&self.keys.params().i());
237 }
238
239 let mut clsags = Vec::with_capacity(self.clsags.len());
242 for ((key_image, clsag), commitments) in key_images.iter().zip(self.clsags).zip(commitments) {
243 clsags.push((key_image, clsag, commitments));
244 }
245 clsags.sort_by(|x, y| key_image_sort(x.0, y.0));
246 let clsags =
247 clsags.into_iter().map(|(_, clsag, commitments)| (clsag, commitments)).collect::<Vec<_>>();
248
249 let tx = self.signable.with_key_images(key_images);
251
252 let clsag_len = clsags.len();
254 let output_masks = tx.intent.sum_output_masks(&tx.key_images);
255 let mut rng = tx.intent.seeded_rng(b"multisig_pseudo_out_masks");
256 let mut sum_pseudo_outs = Scalar::ZERO;
257 let mut to_sign = Vec::with_capacity(clsag_len);
258 for (i, ((clsag_mask_send, clsag), commitments)) in clsags.into_iter().enumerate() {
259 let mut mask = Scalar::random(&mut rng);
260 if i == (clsag_len - 1) {
261 mask = output_masks - sum_pseudo_outs;
262 } else {
263 sum_pseudo_outs += mask;
264 }
265 clsag_mask_send.send(mask);
266 to_sign.push((clsag, commitments));
267 }
268
269 let tx = tx.transaction_without_signatures();
270 let msg = tx.signature_hash().expect("signing a transaction which isn't signed?");
271
272 let mut shares = Vec::with_capacity(to_sign.len());
274 let clsags = to_sign
275 .drain(..)
276 .map(|(clsag, commitments)| {
277 let (clsag, share) = clsag.sign(commitments, &msg)?;
278 shares.push(share);
279 Ok(clsag)
280 })
281 .collect::<Result<_, _>>()?;
282
283 Ok((TransactionSignatureMachine { tx, clsags }, shares))
284 }
285}
286
287impl SignatureMachine<Transaction> for TransactionSignatureMachine {
288 type SignatureShare = Vec<SignatureShare<Ed25519>>;
289
290 fn read_share<R: Read>(&self, reader: &mut R) -> io::Result<Self::SignatureShare> {
291 self.clsags.iter().map(|clsag| clsag.read_share(reader)).collect()
292 }
293
294 fn complete(
295 mut self,
296 shares: HashMap<Participant, Self::SignatureShare>,
297 ) -> Result<Transaction, FrostError> {
298 let mut tx = self.tx;
299 match tx {
300 Transaction::V2 {
301 proofs:
302 Some(RctProofs {
303 prunable: RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. },
304 ..
305 }),
306 ..
307 } => {
308 for (c, clsag) in self.clsags.drain(..).enumerate() {
309 let (clsag, pseudo_out) = clsag.complete(
310 shares.iter().map(|(l, shares)| (*l, shares[c].clone())).collect::<HashMap<_, _>>(),
311 )?;
312 clsags.push(clsag);
313 pseudo_outs.push(pseudo_out);
314 }
315 }
316 _ => unreachable!("attempted to sign a multisig TX which wasn't CLSAG"),
317 }
318 Ok(tx)
319 }
320}