1use core::borrow::{Borrow}; #[cfg(feature = "alloc")]
37use alloc::{collections::btree_map::{BTreeMap, Entry}};
38
39use arrayref::array_ref;
40use arrayvec::ArrayVec;
41
42use merlin::Transcript;
43
44use curve25519_dalek::constants;
45use curve25519_dalek::ristretto::{CompressedRistretto,RistrettoPoint};
46use curve25519_dalek::scalar::Scalar;
47
48use super::*;
49use crate::context::SigningTranscript;
50use crate::errors::MultiSignatureStage;
51
52
53const REWINDS: usize = 3;
59
60
61#[inline(always)]
70fn commit_public_keys<'a,I>(keys: I) -> Transcript
71where I: Iterator<Item=&'a PublicKey>
72{
73 let mut t = Transcript::new(b"MuSig-aggregate-public_key");
74 for pk in keys {
75 t.commit_point(b"pk-set", pk.as_compressed() );
76 }
77 t
78}
79
80fn compute_weighting(mut t: Transcript, pk: &PublicKey) -> Scalar {
86 t.commit_point(b"pk-choice", pk.as_compressed() );
87 t.challenge_scalar(b"")
88}
89
90pub trait AggregatePublicKey {
97 fn weighting(&self, choice: &PublicKey) -> Option<Scalar>;
99
100 fn public_key(&self) -> PublicKey;
102}
103
104impl<K,V> AggregatePublicKey for BTreeMap<K,V>
105where K: Borrow<PublicKey>+Ord
106{
107 fn weighting(&self, choice: &PublicKey) -> Option<Scalar> {
108 if !self.contains_key(choice) {
109 return None;
110 }
111 let t0 = commit_public_keys( self.keys().map(|pk| pk.borrow()) );
112 Some(compute_weighting(t0, choice))
113 }
114
115 fn public_key(&self) -> PublicKey {
116 let t0 = commit_public_keys( self.keys().map(|pk| pk.borrow()) );
117 let point = self.keys().map(|pk| {
118 let pk = pk.borrow();
119 compute_weighting(t0.clone(), pk) * pk.as_point()
120 }).sum();
121 PublicKey::from_point(point)
122 }
123}
124
125pub struct AggregatePublicKeySlice<'a,K>(&'a [K])
127where K: Borrow<PublicKey>;
128
129pub fn aggregate_public_key_from_slice<'a>(public_keys: &'a mut [PublicKey])
131 -> Option<AggregatePublicKeySlice<'a,PublicKey>>
132{
133 if public_keys.len() == 1 { return None; }
134 public_keys.sort_unstable();
135 if public_keys.windows(2).any(|x| x[0]==x[1]) { return None; }
136 Some(AggregatePublicKeySlice(public_keys))
137}
138
139pub fn aggregate_public_key_from_refs_slice<'a>(public_keys: &'a mut [&'a PublicKey])
141 -> Option<AggregatePublicKeySlice<'a,&'a PublicKey>>
142{
143 if public_keys.len() == 1 { return None; }
144 public_keys.sort_unstable();
145 if public_keys.windows(2).any(|x| x[0]==x[1]) { return None; }
146 Some(AggregatePublicKeySlice(public_keys))
147}
148
149pub fn aggregate_public_key_from_sorted_slice<'a,K>(public_keys: &'a mut [K])
151 -> Option<AggregatePublicKeySlice<'a,K>>
152where K: Borrow<PublicKey>+PartialOrd<K>
153{
154 if public_keys.len() == 1 { return None; }
155 if public_keys.windows(2).any(|x| x[0] >= x[1]) { return None; }
156 Some(AggregatePublicKeySlice(public_keys))
157}
158
159impl<'a,K> AggregatePublicKey for AggregatePublicKeySlice<'a,K>
160where K: Borrow<PublicKey>+PartialEq<K>
161{
162 fn weighting(&self, choice: &PublicKey) -> Option<Scalar> {
163 if self.0.iter().any(|pk| pk.borrow() == choice) {
164 return None;
165 }
166 let t0 = commit_public_keys( self.0.iter().map(|pk| pk.borrow()) );
167 Some(compute_weighting(t0, choice))
168 }
169
170 fn public_key(&self) -> PublicKey {
171 let t0 = commit_public_keys( self.0.iter().map(|pk| pk.borrow()) );
172 let point = self.0.iter().map(|pk| {
173 let pk = pk.borrow();
174 compute_weighting(t0.clone(), pk) * pk.as_point()
175 } ).sum();
176 PublicKey::from_point(point)
177 }
178}
179
180
181const COMMITMENT_SIZE : usize = 16;
184
185#[derive(Debug,Clone,Copy,PartialEq,Eq)]
187pub struct Commitment(pub [u8; COMMITMENT_SIZE]);
188
189impl Commitment {
190 #[allow(non_snake_case)]
191 fn for_R<I>(R: I) -> Commitment
192 where I: IntoIterator<Item=CompressedRistretto>
193 {
194 let mut t = Transcript::new(b"MuSig-commitment");
195 for R0 in R.into_iter() { t.commit_point(b"sign:R",&R0); }
196 let mut commit = [0u8; COMMITMENT_SIZE];
197 t.challenge_bytes(b"commitment",&mut commit[..]);
198 Commitment(commit)
199 }
200}
201#[derive(Debug,Clone,PartialEq,Eq)]
208struct RevealedPoints([RistrettoPoint; REWINDS]);
209
210impl RevealedPoints {
211 fn to_reveal(&self) -> Reveal {
220 let mut reveal = [0u8; 32*REWINDS];
222 for (o,i) in reveal.chunks_mut(32).zip(&self.0) {
223 o.copy_from_slice(i.compress().as_bytes());
224 }
225 Reveal(reveal)
226 }
227}
228
229pub struct Reveal(pub [u8; 32*REWINDS]);
232impl Clone for Reveal {
235 fn clone(&self) -> Reveal { Reveal(self.0) }
236}
237impl PartialEq<Reveal> for Reveal {
238 #[inline]
239 fn eq(&self, other: &Reveal) -> bool {
240 self.0[..] == other.0[..]
241 }
242}
243impl Eq for Reveal { }
244
245impl Reveal {
246 fn check_length(&self) -> SignatureResult<()> {
247 if self.0.len() % 32 == 0 { Ok(()) } else { Err(SignatureError::PointDecompressionError) }
248 }
249
250 #[allow(non_snake_case)]
251 fn iter_points<'a>(&'a self) -> impl Iterator<Item=CompressedRistretto> + 'a {
252 self.0.chunks(32).map( |R| CompressedRistretto( *array_ref![R,0,32] ) )
253 }
254
255 fn to_commitment(&self) -> SignatureResult<Commitment> {
256 self.check_length() ?;
257 Ok(Commitment::for_R( self.iter_points() ))
258 }
259
260 #[allow(clippy::wrong_self_convention)]
261 fn into_points(&self) -> SignatureResult<RevealedPoints> {
262 self.check_length() ?;
263 let a = self.iter_points().map(
264 |x| x.decompress().ok_or(SignatureError::PointDecompressionError)
265 ).collect::<SignatureResult<ArrayVec<RistrettoPoint,REWINDS>>>() ?;
266 Ok( RevealedPoints( a.into_inner().unwrap() ) )
267 }
268}
269
270
271#[allow(non_snake_case)]
272#[derive(Debug,Clone,PartialEq,Eq)]
273enum CoR {
274 Commit(Commitment), Reveal(RevealedPoints), Cosigned { s: Scalar }, Collect { reveal: RevealedPoints, s: Scalar },
278}
279
280impl CoR {
281 fn set_revealed(&mut self, reveal: Reveal) -> SignatureResult<()> {
303 let commitment = reveal.to_commitment() ?;
304 let reveal = reveal.into_points() ?;
305 match self.clone() { CoR::Collect { .. } => panic!("Internal error, set_reveal during collection phase."),
307 CoR::Cosigned { .. } => panic!("Internal error, cosigning during reveal phase."),
308 CoR::Commit(c_old) =>
309 if c_old==commitment { *self = CoR::Reveal(reveal);
311 Ok(())
312 } else {
313 let musig_stage = MultiSignatureStage::Commitment;
314 Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: false, })
315 },
316 CoR::Reveal(reveal_old) =>
317 if reveal_old == reveal { Ok(()) } else { let musig_stage = MultiSignatureStage::Reveal;
319 Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true, })
320 }, }
322 }
323
324 #[allow(non_snake_case)]
325 fn set_cosigned(&mut self, s: Scalar) -> SignatureResult<()> {
326 match self {
327 CoR::Collect { .. } => panic!("Internal error, set_cosigned during collection phase."),
328 CoR::Commit(_) => {
329 let musig_stage = MultiSignatureStage::Reveal;
330 Err(SignatureError::MuSigAbsent { musig_stage, })
331 },
332 CoR::Reveal(_) => {
333 *self = CoR::Cosigned { s };
334 Ok(())
335 },
336 CoR::Cosigned { s: s_old } =>
337 if *s_old==s { Ok(()) } else {
338 let musig_stage = MultiSignatureStage::Cosignature;
339 Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true, })
340 },
341 }
342 }
343}
344
345
346#[allow(non_snake_case)]
348pub struct MuSig<T: SigningTranscript+Clone,S> {
349 t: T,
350 Rs: BTreeMap<PublicKey,CoR>,
351 stage: S
352}
353
354impl<T: SigningTranscript+Clone,S> MuSig<T,S> {
355 pub fn public_keys(&self, require_reveal: bool) -> impl Iterator<Item=&PublicKey> {
359 self.Rs.iter().filter_map( move |(pk,cor)| match cor {
360 CoR::Commit(_) => if require_reveal { None } else { Some(pk) },
361 CoR::Reveal(_) => Some(pk),
362 CoR::Cosigned { .. } => Some(pk),
363 CoR::Collect { .. } => Some(pk),
364 } )
365 }
366
367 fn compute_public_key(&self, require_reveal: bool) -> PublicKey {
371 let t0 = commit_public_keys(self.public_keys(require_reveal));
372 let point = self.public_keys(require_reveal).map( |pk|
373 compute_weighting(t0.clone(), pk) * pk.as_point()
374 ).sum();
375 PublicKey::from_point(point)
376 }
377
378 pub fn public_key(&self) -> PublicKey
380 { self.compute_public_key(true) }
381
382 pub fn expected_public_key(&self) -> PublicKey
384 { self.compute_public_key(false) }
385
386 #[allow(non_snake_case)]
390 fn iter_Rs(&self) -> impl Iterator<Item = (&PublicKey,&RevealedPoints)> {
391 self.Rs.iter().filter_map( |(pk,cor)| match cor {
392 CoR::Commit(_) => None,
393 CoR::Reveal(reveal) => Some((pk,reveal)),
394 CoR::Cosigned { .. } => panic!("Internal error, compute_R called during cosigning phase."),
395 CoR::Collect { reveal, .. } => Some((pk,reveal)),
396 } )
397 }
398
399 #[allow(non_snake_case)]
404 fn rewinder(&self) -> impl Fn(&PublicKey) -> [Scalar; REWINDS] {
405 let mut t0 = self.t.clone();
406 for (pk,R) in self.iter_Rs() {
407 t0.commit_point(b"pk-set", pk.as_compressed() );
408 for anR in R.0.iter() {
409 t0.commit_point(b"R",& anR.compress());
410 }
411 }
412 move |pk| {
413 let mut t1 = t0.clone();
414 t1.commit_point(b"pk-choice", pk.as_compressed() );
415 let mut a = ArrayVec::<Scalar, REWINDS>::new();
416 while !a.is_full() {
417 a.push( t1.challenge_scalar(b"R") );
418 }
419 a.into_inner().unwrap()
420 }
421 }
422
423 #[allow(non_snake_case)]
428 fn compute_R<F>(&self, rewinder: F) -> CompressedRistretto
429 where F: Fn(&PublicKey) -> [Scalar; REWINDS]
430 {
431 self.iter_Rs().map( |(pk,R)|
432 R.0.iter().zip(&rewinder(pk)).map(|(y,x)| x*y).sum::<RistrettoPoint>()
433 ).sum::<RistrettoPoint>().compress()
434 }
435}
436
437
438pub trait TranscriptStages {}
441impl<K> TranscriptStages for CommitStage<K> where K: Borrow<Keypair> {}
442impl<K> TranscriptStages for RevealStage<K> where K: Borrow<Keypair> {}
443impl<T,S> MuSig<T,S>
444where T: SigningTranscript+Clone, S: TranscriptStages
445{
446 pub fn transcript(&mut self) -> &mut T { &mut self.t }
452}
453
454impl Keypair {
455 #[allow(non_snake_case)]
461 pub fn musig<'k,T>(&'k self, t: T) -> MuSig<T,CommitStage<&'k Keypair>>
462 where T: SigningTranscript+Clone {
463 MuSig::new(self,t)
464 }
465}
466
467#[allow(non_snake_case)]
469pub struct CommitStage<K: Borrow<Keypair>> {
470 keypair: K,
471 r_me: [Scalar; REWINDS],
472 R_me: Reveal,
473}
474
475impl<K,T> MuSig<T,CommitStage<K>>
476where K: Borrow<Keypair>, T: SigningTranscript+Clone
477{
478 #[allow(non_snake_case)]
485 pub fn new(keypair: K, t: T) -> MuSig<T,CommitStage<K>> {
486 let nonce = &keypair.borrow().secret.nonce;
487
488 let mut r_me = ArrayVec::<Scalar, REWINDS>::new();
489 for i in 0..REWINDS {
490 r_me.push( t.witness_scalar(b"MuSigWitness",&[nonce,&i.to_le_bytes()]) );
491 }
492 let r_me = r_me.into_inner().unwrap();
493 let B = constants::RISTRETTO_BASEPOINT_TABLE;
496 let R_me_points: ArrayVec<RistrettoPoint, REWINDS> = r_me.iter()
497 .map(|r_me_i| r_me_i * B).collect();
498 let R_me_points = RevealedPoints(R_me_points.into_inner().unwrap());
499 let R_me = R_me_points.to_reveal();
500
501 let mut Rs = BTreeMap::new();
502 Rs.insert(keypair.borrow().public, CoR::Reveal( R_me_points ));
503
504 let stage = CommitStage { keypair, r_me, R_me };
505 MuSig { t, Rs, stage, }
506 }
507
508 pub fn our_commitment(&self) -> Commitment {
510 self.stage.R_me.to_commitment().unwrap()
511 }
512
513 pub fn add_their_commitment(&mut self, them: PublicKey, theirs: Commitment)
515 -> SignatureResult<()>
516 {
517 let theirs = CoR::Commit(theirs);
518 match self.Rs.entry(them) {
519 Entry::Vacant(v) => { v.insert(theirs); },
520 Entry::Occupied(o) =>
521 if o.get() != &theirs {
522 let musig_stage = MultiSignatureStage::Commitment;
523 return Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true, });
524 },
525 }
526 Ok(())
527 }
528
529 #[allow(non_snake_case)]
531 pub fn reveal_stage(self) -> MuSig<T,RevealStage<K>> {
532 let MuSig { t, Rs, stage: CommitStage { keypair, r_me, R_me, }, } = self;
533 MuSig { t, Rs, stage: RevealStage { keypair, r_me, R_me, }, }
534 }
535}
536
537#[allow(non_snake_case)]
539pub struct RevealStage<K: Borrow<Keypair>> {
540 keypair: K,
541 r_me: [Scalar; REWINDS],
542 R_me: Reveal,
543}
544
545impl<K,T> MuSig<T,RevealStage<K>>
546where K: Borrow<Keypair>, T: SigningTranscript+Clone
547{
548 pub fn our_reveal(&self) -> &Reveal { &self.stage.R_me }
550
551 pub fn add_their_reveal(&mut self, them: PublicKey, theirs: Reveal)
557 -> SignatureResult<()>
558 {
559 match self.Rs.entry(them) {
560 Entry::Vacant(_) => {
561 let musig_stage = MultiSignatureStage::Commitment;
562 Err(SignatureError::MuSigAbsent { musig_stage, })
563 },
564 Entry::Occupied(mut o) =>
565 o.get_mut().set_revealed(theirs),
566 }
567 }
568
569 pub fn add_trusted(&mut self, them: PublicKey, theirs: Reveal)
592 -> SignatureResult<()>
593 {
594 let reveal = theirs.into_points() ?;
595 let theirs = CoR::Reveal(reveal);
596 match self.Rs.entry(them) {
597 Entry::Vacant(v) => { v.insert(theirs); },
598 Entry::Occupied(o) =>
599 if o.get() != &theirs {
600 let musig_stage = MultiSignatureStage::Reveal;
601 return Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true, });
602 },
603 }
604 Ok(())
605 }
606
607 #[allow(non_snake_case)]
609 pub fn cosign_stage(mut self) -> MuSig<T,CosignStage> {
610 self.t.proto_name(b"Schnorr-sig");
611
612 let pk = *self.public_key().as_compressed();
613 self.t.commit_point(b"sign:pk",&pk);
614
615 let rewinder = self.rewinder();
616 let rewinds = rewinder(&self.stage.keypair.borrow().public);
617 let R = self.compute_R(rewinder);
618 self.t.commit_point(b"sign:R",&R);
619
620 let t0 = commit_public_keys(self.public_keys(true));
621 let a_me = compute_weighting(t0, &self.stage.keypair.borrow().public);
622 let c = self.t.challenge_scalar(b"sign:c"); let mut s_me: Scalar = self.stage.r_me.iter().zip(&rewinds).map(|(y,x)| x*y).sum();
625 s_me += c * a_me * self.stage.keypair.borrow().secret.key;
626
627 zeroize::Zeroize::zeroize(&mut self.stage.r_me);
628
629 let MuSig { t, mut Rs, stage: RevealStage { .. }, } = self;
630 *(Rs.get_mut(&self.stage.keypair.borrow().public).expect("Rs known to contain this public; qed")) = CoR::Cosigned { s: s_me };
631 MuSig { t, Rs, stage: CosignStage { R, s_me }, }
632 }
633}
634
635#[allow(non_snake_case)]
637pub struct CosignStage {
638 R: CompressedRistretto,
640 s_me: Scalar,
642}
643
644#[derive(Debug,Clone,Copy,PartialEq,Eq)]
646pub struct Cosignature(pub [u8; 32]);
647
648impl<T: SigningTranscript+Clone> MuSig<T,CosignStage> {
649 pub fn our_cosignature(&self) -> Cosignature {
651 Cosignature(self.stage.s_me.to_bytes())
652 }
653
654 pub fn add_their_cosignature(&mut self, them: PublicKey, theirs: Cosignature)
656 -> SignatureResult<()>
657 {
658 let theirs = crate::scalar_from_canonical_bytes(theirs.0)
659 .ok_or(SignatureError::ScalarFormatError) ?;
660 match self.Rs.entry(them) {
661 Entry::Vacant(_) => {
662 let musig_stage = MultiSignatureStage::Reveal;
663 Err(SignatureError::MuSigAbsent { musig_stage, })
664 },
665 Entry::Occupied(mut o) => o.get_mut().set_cosigned(theirs)
666 }
667 }
668
669 pub fn cosigned(&self) -> impl Iterator<Item=&PublicKey> {
672 self.Rs.iter().filter_map( |(pk,cor)| match cor {
673 CoR::Commit(_) => None,
674 CoR::Reveal(_) => None,
675 CoR::Cosigned { .. } => Some(pk),
676 CoR::Collect { .. } => panic!("Collect found in Cosign phase.")
677 } )
678 }
679
680 pub fn uncosigned(&self) -> impl Iterator<Item=&PublicKey> {
683 self.Rs.iter().filter_map( |(pk,cor)| match cor {
684 CoR::Commit(_) => None,
685 CoR::Reveal(_) => Some(pk),
686 CoR::Cosigned { .. } => None,
687 CoR::Collect { .. } => panic!("Collect found in Cosign phase."),
688 } )
689 }
690
691 #[allow(non_snake_case)]
693 pub fn sign(&self) -> Option<Signature> {
694 if self.uncosigned().last().is_some() { return None; }
696 let s: Scalar = self.Rs.iter()
697 .filter_map( |(_pk,cor)| match cor {
698 CoR::Commit(_) => None,
699 CoR::Reveal(_) => panic!("Internal error, MuSig<T,CosignStage>::uncosigned broken."),
700 CoR::Cosigned { s, .. } => Some(s),
701 CoR::Collect { .. } => panic!("Collect found in Cosign phase."),
702 } ).sum();
703 Some(Signature { s, R: self.stage.R, })
704 }
705}
706
707
708#[allow(non_snake_case)]
710pub fn collect_cosignatures<T: SigningTranscript+Clone>(mut t: T) -> MuSig<T,CollectStage> {
711 t.proto_name(b"Schnorr-sig");
712 MuSig { t, Rs: BTreeMap::new(), stage: CollectStage, }
713}
714
715pub struct CollectStage;
717
718impl<T: SigningTranscript+Clone> MuSig<T,CollectStage> {
719 #[allow(non_snake_case)]
721 pub fn add(&mut self, them: PublicKey, their_reveal: Reveal, their_cosignature: Cosignature)
722 -> SignatureResult<()>
723 {
724 let reveal = their_reveal.into_points() ?;
725 let s = crate::scalar_from_canonical_bytes(their_cosignature.0)
726 .ok_or(SignatureError::ScalarFormatError) ?;
727 let cor = CoR::Collect { reveal, s };
728
729 match self.Rs.entry(them) {
730 Entry::Vacant(v) => { v.insert(cor); },
731 Entry::Occupied(o) =>
732 if o.get() != &cor {
733 let musig_stage = MultiSignatureStage::Reveal;
734 return Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true, });
735 },
736 }
737 Ok(())
738 }
739
740 #[allow(non_snake_case)]
742 pub fn signature(mut self) -> Signature {
743 let pk = *self.public_key().as_compressed();
744 self.t.commit_point(b"sign:pk",&pk);
745
746 let R = self.compute_R(self.rewinder());
747
748 let s: Scalar = self.Rs.values().map(|cor| match cor {
749 CoR::Collect { s, .. } => s,
750 _ => panic!("Reached CollectStage from another stage"),
751 }).sum();
752 Signature { s, R, }
753 }
754}
755
756
757#[cfg(test)]
758mod tests {
759 #[cfg(feature = "alloc")]
760 use alloc::vec::Vec;
761
762 use super::*;
763
764 #[test]
765 fn aggregation_btreeemap_vs_slice() {
766 let mut vec: Vec<PublicKey> = (0..16).map(|_| SecretKey::generate().to_public()).collect();
767 let btm: BTreeMap<PublicKey,()> = vec.iter().map( |x| (x.clone(),()) ).collect();
768 debug_assert_eq!(
769 btm.public_key(),
770 aggregate_public_key_from_slice(vec.as_mut_slice()).unwrap().public_key()
771 );
772 }
774
775 #[test]
776 fn multi_signature() {
777 let keypairs: Vec<Keypair> = (0..16).map(|_| Keypair::generate()).collect();
778
779 let t = signing_context(b"multi-sig").bytes(b"We are legion!");
780 let mut commits: Vec<_> = keypairs.iter().map( |k| k.musig(t.clone()) ).collect();
781 for i in 0..commits.len() {
782 let r = commits[i].our_commitment();
783 for j in commits.iter_mut() {
784 assert!( j.add_their_commitment(keypairs[i].public.clone(),r)
785 .is_ok() != (r == j.our_commitment()) );
786 }
787 }
788
789 let mut reveal_msgs: Vec<Reveal> = Vec::with_capacity(commits.len());
790 let mut reveals: Vec<_> = commits.drain(..).map( |c| c.reveal_stage() ).collect();
791 for i in 0..reveals.len() {
792 let r = reveals[i].our_reveal().clone();
793 for j in reveals.iter_mut() {
794 j.add_their_reveal(keypairs[i].public.clone(),r.clone()).unwrap();
795 }
796 reveal_msgs.push(r);
797 }
798 let pk = reveals[0].public_key();
799
800 let mut cosign_msgs: Vec<Cosignature> = Vec::with_capacity(reveals.len());
801 let mut cosigns: Vec<_> = reveals.drain(..).map( |c| { assert_eq!(pk, c.public_key()); c.cosign_stage() } ).collect();
802 for i in 0..cosigns.len() {
803 assert_eq!(pk, cosigns[i].public_key());
804 let r = cosigns[i].our_cosignature();
805 for j in cosigns.iter_mut() {
806 j.add_their_cosignature(keypairs[i].public.clone(),r).unwrap();
807 }
808 cosign_msgs.push(r);
809 assert_eq!(pk, cosigns[i].public_key());
810 }
811
812 let mut c = collect_cosignatures(t.clone());
814 for i in 0..cosigns.len() {
815 c.add(keypairs[i].public.clone(),reveal_msgs[i].clone(),cosign_msgs[i].clone()).unwrap();
816 }
817 let signature = c.signature();
818
819 assert!( pk.verify(t,&signature).is_ok() );
820 for i in 0..cosigns.len() {
821 assert_eq!(pk, cosigns[i].public_key());
822 assert_eq!(signature, cosigns[i].sign().unwrap());
823 }
824 }
825}