schnorr_signatures/
aggregate.rs

1use 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
21// Returns a unbiased scalar weight to use on a signature in order to prevent malleability
22fn 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  // This should be guaranteed thanks to SecureDigest
26  debug_assert!(bytes.len() >= 32);
27
28  let mut res = F::ZERO;
29  let mut i = 0;
30
31  // Derive a scalar from enough bits of entropy that bias is < 2^128
32  // This can't be const due to its usage of a generic
33  // Also due to the usize::try_from, yet that could be replaced with an `as`
34  #[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  // We load bits in as u64s
40  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    // Shift over the already loaded bits
46    if !first {
47      for _ in 0 .. WORD_LEN_IN_BITS {
48        res += res;
49      }
50    }
51    first = false;
52
53    // Add the next 64 bits
54    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 we've exhausted this challenge, get another
58    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/// Aggregate Schnorr signature as defined in <https://eprint.iacr.org/2021/350>.
68#[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  /// Read a SchnorrAggregate from something implementing Read.
77  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  /// Write a SchnorrAggregate to something implementing Write.
91  ///
92  /// This will panic if more than 4 billion signatures were aggregated.
93  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  /// Serialize a SchnorrAggregate, returning a `Vec<u8>`.
107  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  /// Perform signature verification.
119  ///
120  /// Challenges must be properly crafted, which means being binding to the public key, nonce, and
121  /// any message. Failure to do so will let a malicious adversary to forge signatures for
122  /// different keys/messages.
123  ///
124  /// The DST used here must prevent a collision with whatever hash function produced the
125  /// challenges.
126  #[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/// A signature aggregator capable of consuming signatures in order to produce an aggregate.
150#[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  /// Create a new aggregator.
159  ///
160  /// The DST used here must prevent a collision with whatever hash function produced the
161  /// challenges.
162  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  /// Aggregate a signature.
169  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  /// Complete aggregation, returning None if none were aggregated.
175  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}