schnorr_signatures/
aggregate.rs1use std_shims::{
2 vec::Vec,
3 io::{self, Read, Write},
4};
5
6use zeroize::Zeroize;
7
8use transcript::{Transcript, SecureDigest, DigestTranscript};
9
10use ciphersuite::{
11 group::{
12 ff::{Field, PrimeField},
13 Group, GroupEncoding,
14 },
15 Ciphersuite,
16};
17use multiexp::multiexp_vartime;
18
19use crate::SchnorrSignature;
20
21fn weight<D: Send + Clone + SecureDigest, F: PrimeField>(digest: &mut DigestTranscript<D>) -> F {
23 let mut bytes = digest.challenge(b"aggregation_weight");
24 debug_assert_eq!(bytes.len() % 8, 0);
25 debug_assert!(bytes.len() >= 32);
27
28 let mut res = F::ZERO;
29 let mut i = 0;
30
31 #[allow(non_snake_case)]
35 let BYTES: usize = usize::try_from((F::NUM_BITS + 128).div_ceil(8)).unwrap();
36
37 let mut remaining = BYTES;
38
39 const WORD_LEN_IN_BITS: usize = 64;
41 const WORD_LEN_IN_BYTES: usize = WORD_LEN_IN_BITS / 8;
42
43 let mut first = true;
44 while i < remaining {
45 if !first {
47 for _ in 0 .. WORD_LEN_IN_BITS {
48 res += res;
49 }
50 }
51 first = false;
52
53 res += F::from(u64::from_be_bytes(bytes[i .. (i + WORD_LEN_IN_BYTES)].try_into().unwrap()));
55 i += WORD_LEN_IN_BYTES;
56
57 if i == bytes.len() {
59 bytes = digest.challenge(b"aggregation_weight_continued");
60 remaining -= i;
61 i = 0;
62 }
63 }
64 res
65}
66
67#[allow(non_snake_case)]
69#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
70pub struct SchnorrAggregate<C: Ciphersuite> {
71 Rs: Vec<C::G>,
72 s: C::F,
73}
74
75impl<C: Ciphersuite> SchnorrAggregate<C> {
76 pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
78 let mut len = [0; 4];
79 reader.read_exact(&mut len)?;
80
81 #[allow(non_snake_case)]
82 let mut Rs = vec![];
83 for _ in 0 .. u32::from_le_bytes(len) {
84 Rs.push(C::read_G(reader)?);
85 }
86
87 Ok(SchnorrAggregate { Rs, s: C::read_F(reader)? })
88 }
89
90 pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
94 writer.write_all(
95 &u32::try_from(self.Rs.len())
96 .expect("more than 4 billion signatures in aggregate")
97 .to_le_bytes(),
98 )?;
99 #[allow(non_snake_case)]
100 for R in &self.Rs {
101 writer.write_all(R.to_bytes().as_ref())?;
102 }
103 writer.write_all(self.s.to_repr().as_ref())
104 }
105
106 pub fn serialize(&self) -> Vec<u8> {
108 let mut buf = vec![];
109 self.write(&mut buf).unwrap();
110 buf
111 }
112
113 #[allow(non_snake_case)]
114 pub fn Rs(&self) -> &[C::G] {
115 self.Rs.as_slice()
116 }
117
118 #[must_use]
127 pub fn verify(&self, dst: &'static [u8], keys_and_challenges: &[(C::G, C::F)]) -> bool {
128 if self.Rs.len() != keys_and_challenges.len() {
129 return false;
130 }
131
132 let mut digest = DigestTranscript::<C::H>::new(dst);
133 digest.domain_separate(b"signatures");
134 for (_, challenge) in keys_and_challenges {
135 digest.append_message(b"challenge", challenge.to_repr());
136 }
137
138 let mut pairs = Vec::with_capacity((2 * keys_and_challenges.len()) + 1);
139 for (i, (key, challenge)) in keys_and_challenges.iter().enumerate() {
140 let z = weight(&mut digest);
141 pairs.push((z, self.Rs[i]));
142 pairs.push((z * challenge, *key));
143 }
144 pairs.push((-self.s, C::generator()));
145 multiexp_vartime(&pairs).is_identity().into()
146 }
147}
148
149#[allow(non_snake_case)]
151#[derive(Clone, Debug, Zeroize)]
152pub struct SchnorrAggregator<C: Ciphersuite> {
153 digest: DigestTranscript<C::H>,
154 sigs: Vec<SchnorrSignature<C>>,
155}
156
157impl<C: Ciphersuite> SchnorrAggregator<C> {
158 pub fn new(dst: &'static [u8]) -> Self {
163 let mut res = Self { digest: DigestTranscript::<C::H>::new(dst), sigs: vec![] };
164 res.digest.domain_separate(b"signatures");
165 res
166 }
167
168 pub fn aggregate(&mut self, challenge: C::F, sig: SchnorrSignature<C>) {
170 self.digest.append_message(b"challenge", challenge.to_repr());
171 self.sigs.push(sig);
172 }
173
174 pub fn complete(mut self) -> Option<SchnorrAggregate<C>> {
176 if self.sigs.is_empty() {
177 return None;
178 }
179
180 let mut aggregate = SchnorrAggregate { Rs: Vec::with_capacity(self.sigs.len()), s: C::F::ZERO };
181 for i in 0 .. self.sigs.len() {
182 aggregate.Rs.push(self.sigs[i].R);
183 aggregate.s += self.sigs[i].s * weight::<_, C::F>(&mut self.digest);
184 }
185 Some(aggregate)
186 }
187}