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};
10
11use transcript::{Transcript, RecommendedTranscript};
12use frost::{
13 curve::Ed25519,
14 Participant, FrostError, ThresholdKeys,
15 sign::{
16 Writable, Preprocess, CachedPreprocess, SignatureShare, PreprocessMachine, SignMachine,
17 SignatureMachine, AlgorithmMachine, AlgorithmSignMachine, AlgorithmSignatureMachine,
18 },
19};
20
21use monero_oxide::{
22 io::CompressedPoint,
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 {
50 signable: SignableTransaction,
51
52 keys: ThresholdKeys<Ed25519>,
53
54 key_image_generators_and_lincombs: Vec<(EdwardsPoint, (Scalar, Scalar))>,
55 clsags: Vec<(ClsagMultisigMaskSender, AlgorithmSignMachine<Ed25519, ClsagMultisig>)>,
56
57 our_preprocess: Vec<Preprocess<Ed25519, ClsagAddendum>>,
58}
59
60pub struct TransactionSignatureMachine {
66 tx: Transaction,
67 clsags: Vec<AlgorithmSignatureMachine<Ed25519, ClsagMultisig>>,
68}
69
70impl SignableTransaction {
71 pub fn multisig(self, keys: ThresholdKeys<Ed25519>) -> Result<TransactionMachine, SendError> {
77 let mut clsags = vec![];
78
79 let mut key_image_generators_and_lincombs = vec![];
80 for input in &self.inputs {
81 let key_scalar = Scalar::ONE;
83 let key_offset = input.key_offset();
84
85 let offset =
86 keys.clone().scale(key_scalar).expect("non-zero scalar (1) was zero").offset(key_offset);
87 if offset.group_key().0 != input.key() {
88 Err(SendError::WrongPrivateKey)?;
89 }
90
91 let context = ClsagContext::new(input.decoys().clone(), input.commitment().clone())
92 .map_err(SendError::ClsagError)?;
93 let (clsag, clsag_mask_send) = ClsagMultisig::new(
94 RecommendedTranscript::new(b"Monero Multisignature Transaction"),
95 context,
96 );
97 key_image_generators_and_lincombs
98 .push((clsag.key_image_generator(), (offset.current_scalar(), offset.current_offset())));
99 clsags.push((clsag_mask_send, AlgorithmMachine::new(clsag, offset)));
100 }
101
102 Ok(TransactionMachine { signable: self, keys, key_image_generators_and_lincombs, clsags })
103 }
104}
105
106#[derive(Clone, PartialEq)]
110pub struct TransactionPreprocess(Vec<Preprocess<Ed25519, ClsagAddendum>>);
111impl Writable for TransactionPreprocess {
112 fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
113 for preprocess in &self.0 {
114 preprocess.write(writer)?;
115 }
116 Ok(())
117 }
118}
119
120impl PreprocessMachine for TransactionMachine {
121 type Preprocess = TransactionPreprocess;
122 type Signature = Transaction;
123 type SignMachine = TransactionSignMachine;
124
125 fn preprocess<R: RngCore + CryptoRng>(
126 mut self,
127 rng: &mut R,
128 ) -> (TransactionSignMachine, Self::Preprocess) {
129 let mut preprocesses = Vec::with_capacity(self.clsags.len());
131 let clsags = self
132 .clsags
133 .drain(..)
134 .map(|(clsag_mask_send, clsag)| {
135 let (clsag, preprocess) = clsag.preprocess(rng);
136 preprocesses.push(preprocess);
137 (clsag_mask_send, clsag)
138 })
139 .collect();
140 let our_preprocess = preprocesses.clone();
141
142 (
143 TransactionSignMachine {
144 signable: self.signable,
145
146 keys: self.keys,
147
148 key_image_generators_and_lincombs: self.key_image_generators_and_lincombs,
149 clsags,
150
151 our_preprocess,
152 },
153 TransactionPreprocess(preprocesses),
154 )
155 }
156}
157
158#[derive(Clone, PartialEq)]
162pub struct TransactionSignatureShare(Vec<SignatureShare<Ed25519>>);
163impl Writable for TransactionSignatureShare {
164 fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
165 for share in &self.0 {
166 share.write(writer)?;
167 }
168 Ok(())
169 }
170}
171
172impl SignMachine<Transaction> for TransactionSignMachine {
173 type Params = ();
174 type Keys = ThresholdKeys<Ed25519>;
175 type Preprocess = TransactionPreprocess;
176 type SignatureShare = TransactionSignatureShare;
177 type SignatureMachine = TransactionSignatureMachine;
178
179 fn cache(self) -> CachedPreprocess {
180 unimplemented!(
181 "Monero transactions don't support caching their preprocesses due to {}",
182 "being already bound to a specific transaction"
183 );
184 }
185
186 fn from_cache(
187 (): (),
188 _: ThresholdKeys<Ed25519>,
189 _: CachedPreprocess,
190 ) -> (Self, Self::Preprocess) {
191 unimplemented!(
192 "Monero transactions don't support caching their preprocesses due to {}",
193 "being already bound to a specific transaction"
194 );
195 }
196
197 fn read_preprocess<R: Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess> {
198 Ok(TransactionPreprocess(
199 self.clsags.iter().map(|clsag| clsag.1.read_preprocess(reader)).collect::<Result<_, _>>()?,
200 ))
201 }
202
203 fn sign(
204 self,
205 mut commitments: HashMap<Participant, Self::Preprocess>,
206 msg: &[u8],
207 ) -> Result<(TransactionSignatureMachine, Self::SignatureShare), FrostError> {
208 if !msg.is_empty() {
209 panic!("message was passed to the TransactionMachine when it generates its own");
210 }
211
212 for preprocess in commitments.values() {
213 if preprocess.0.len() != self.clsags.len() {
214 Err(FrostError::InternalError(
215 "preprocesses from another instance of the signing protocol were passed in",
216 ))?;
217 }
218 }
219
220 commitments.remove(&self.keys.params().i());
224
225 let mut included = commitments.keys().copied().collect::<Vec<_>>();
227 included.push(self.keys.params().i());
229 included.sort_unstable();
232
233 let mut key_images = vec![EdwardsPoint::identity(); self.clsags.len()];
235
236 let view = self.keys.view(included.clone()).map_err(|_| {
238 FrostError::InvalidSigningSet("couldn't form an interpolated view of the key")
239 })?;
240 let mut commitments = (0 .. self.clsags.len())
241 .map(|c| {
242 included
243 .iter()
244 .map(|l| {
245 let preprocess = if *l == self.keys.params().i() {
246 self.our_preprocess[c].clone()
247 } else {
248 commitments.get_mut(l).ok_or(FrostError::MissingParticipant(*l))?.0[c].clone()
249 };
250
251 key_images[c] += preprocess.addendum.key_image_share().0 *
254 view.interpolation_factor(*l).ok_or(FrostError::InternalError(
255 "view successfully formed with participant without an interpolation factor",
256 ))?;
257
258 Ok((*l, preprocess))
259 })
260 .collect::<Result<HashMap<_, _>, _>>()
261 })
262 .collect::<Result<Vec<_>, _>>()?;
263
264 let key_images: Vec<_> = key_images
265 .into_iter()
266 .zip(&self.key_image_generators_and_lincombs)
267 .map(|(mut key_image, (generator, (scalar, offset)))| {
268 key_image *= scalar;
269 key_image += generator * offset;
270 CompressedPoint::from(key_image.compress())
271 })
272 .collect();
273
274 for map in &mut commitments {
277 map.remove(&self.keys.params().i());
278 }
279
280 let mut clsags = Vec::with_capacity(self.clsags.len());
283 for ((key_image, clsag), commitments) in key_images.iter().zip(self.clsags).zip(commitments) {
284 clsags.push((key_image, clsag, commitments));
285 }
286 clsags.sort_by(|x, y| key_image_sort(x.0, y.0));
287 let clsags =
288 clsags.into_iter().map(|(_, clsag, commitments)| (clsag, commitments)).collect::<Vec<_>>();
289
290 let tx = self.signable.with_key_images(key_images);
292
293 let clsag_len = clsags.len();
295 let output_masks = tx.intent.sum_output_masks(&tx.key_images);
296 let mut rng = tx.intent.seeded_rng(b"multisig_pseudo_out_masks");
297 let mut sum_pseudo_outs = Scalar::ZERO;
298 let mut to_sign = Vec::with_capacity(clsag_len);
299 for (i, ((clsag_mask_send, clsag), commitments)) in clsags.into_iter().enumerate() {
300 let mut mask = Scalar::random(&mut rng);
301 if i == (clsag_len - 1) {
302 mask = output_masks - sum_pseudo_outs;
303 } else {
304 sum_pseudo_outs += mask;
305 }
306 clsag_mask_send.send(mask);
307 to_sign.push((clsag, commitments));
308 }
309
310 let tx = tx.transaction_without_signatures();
311 let msg = tx.signature_hash().expect("signing a transaction which isn't signed?");
312
313 let mut shares = Vec::with_capacity(to_sign.len());
315 let clsags = to_sign
316 .drain(..)
317 .map(|(clsag, commitments)| {
318 let (clsag, share) = clsag.sign(commitments, &msg)?;
319 shares.push(share);
320 Ok(clsag)
321 })
322 .collect::<Result<_, _>>()?;
323
324 Ok((TransactionSignatureMachine { tx, clsags }, TransactionSignatureShare(shares)))
325 }
326}
327
328impl SignatureMachine<Transaction> for TransactionSignatureMachine {
329 type SignatureShare = TransactionSignatureShare;
330
331 fn read_share<R: Read>(&self, reader: &mut R) -> io::Result<Self::SignatureShare> {
332 Ok(TransactionSignatureShare(
333 self.clsags.iter().map(|clsag| clsag.read_share(reader)).collect::<Result<_, _>>()?,
334 ))
335 }
336
337 fn complete(
338 mut self,
339 shares: HashMap<Participant, Self::SignatureShare>,
340 ) -> Result<Transaction, FrostError> {
341 for share in shares.values() {
342 if share.0.len() != self.clsags.len() {
343 Err(FrostError::InternalError(
344 "signature shares from another instance of the signing protocol were passed in",
345 ))?;
346 }
347 }
348
349 let mut tx = self.tx;
350 match tx {
351 Transaction::V2 {
352 proofs:
353 Some(RctProofs {
354 prunable: RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. },
355 ..
356 }),
357 ..
358 } => {
359 for (c, clsag) in self.clsags.drain(..).enumerate() {
360 let (clsag, pseudo_out) = clsag.complete(
361 shares.iter().map(|(l, shares)| (*l, shares.0[c].clone())).collect::<HashMap<_, _>>(),
362 )?;
363 clsags.push(clsag);
364 pseudo_outs.push(CompressedPoint::from(pseudo_out.compress()));
365 }
366 }
367 _ => unreachable!("attempted to sign a multisig TX which wasn't CLSAG"),
368 }
369 Ok(tx)
370 }
371}