1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3
4use std::io::{self, Read};
5
6use rand_core::{RngCore, CryptoRng};
7
8use zeroize::Zeroizing;
9
10use transcript::{Transcript, MerlinTranscript};
11
12use group::{ff::PrimeField, GroupEncoding};
13use ciphersuite::{Ciphersuite, Ristretto};
14use schnorr::SchnorrSignature;
15
16use ::frost::{
17 Participant, ThresholdKeys, ThresholdView, FrostError,
18 algorithm::{Hram, Algorithm, Schnorr},
19};
20
21pub mod frost {
23 pub use ::frost::*;
24}
25
26use schnorrkel::{PublicKey, Signature, context::SigningTranscript, signing_context};
27
28type RistrettoPoint = <Ristretto as Ciphersuite>::G;
29type Scalar = <Ristretto as Ciphersuite>::F;
30
31#[cfg(test)]
32mod tests;
33
34#[derive(Clone)]
35struct SchnorrkelHram;
36impl Hram<Ristretto> for SchnorrkelHram {
37 #[allow(non_snake_case)]
38 fn hram(R: &RistrettoPoint, A: &RistrettoPoint, m: &[u8]) -> Scalar {
39 let ctx_len =
40 usize::try_from(u32::from_le_bytes(m[0 .. 4].try_into().expect("malformed message")))
41 .unwrap();
42
43 let mut t = signing_context(&m[4 .. (4 + ctx_len)]).bytes(&m[(4 + ctx_len) ..]);
44 t.proto_name(b"Schnorr-sig");
45 let convert =
46 |point: &RistrettoPoint| PublicKey::from_bytes(&point.to_bytes()).unwrap().into_compressed();
47 t.commit_point(b"sign:pk", &convert(A));
48 t.commit_point(b"sign:R", &convert(R));
49 Scalar::from_repr(t.challenge_scalar(b"sign:c").to_bytes()).unwrap()
50 }
51}
52
53#[derive(Clone)]
55pub struct Schnorrkel {
56 context: &'static [u8],
57 schnorr: Schnorr<Ristretto, MerlinTranscript, SchnorrkelHram>,
58 msg: Option<Vec<u8>>,
59}
60
61impl Schnorrkel {
62 pub fn new(context: &'static [u8]) -> Schnorrkel {
66 Schnorrkel {
67 context,
68 schnorr: Schnorr::new(MerlinTranscript::new(b"FROST Schnorrkel")),
69 msg: None,
70 }
71 }
72}
73
74impl Algorithm<Ristretto> for Schnorrkel {
75 type Transcript = MerlinTranscript;
76 type Addendum = ();
77 type Signature = Signature;
78
79 fn transcript(&mut self) -> &mut Self::Transcript {
80 self.schnorr.transcript()
81 }
82
83 fn nonces(&self) -> Vec<Vec<<Ristretto as Ciphersuite>::G>> {
84 self.schnorr.nonces()
85 }
86
87 fn preprocess_addendum<R: RngCore + CryptoRng>(
88 &mut self,
89 _: &mut R,
90 _: &ThresholdKeys<Ristretto>,
91 ) {
92 }
93
94 fn read_addendum<R: Read>(&self, _: &mut R) -> io::Result<Self::Addendum> {
95 Ok(())
96 }
97
98 fn process_addendum(
99 &mut self,
100 _: &ThresholdView<Ristretto>,
101 _: Participant,
102 (): (),
103 ) -> Result<(), FrostError> {
104 Ok(())
105 }
106
107 fn sign_share(
108 &mut self,
109 params: &ThresholdView<Ristretto>,
110 nonce_sums: &[Vec<RistrettoPoint>],
111 nonces: Vec<Zeroizing<Scalar>>,
112 msg: &[u8],
113 ) -> Scalar {
114 self.msg = Some(msg.to_vec());
115 self.schnorr.sign_share(
116 params,
117 nonce_sums,
118 nonces,
119 &[
120 &u32::try_from(self.context.len()).expect("context exceeded 2^32 bytes").to_le_bytes(),
121 self.context,
122 msg,
123 ]
124 .concat(),
125 )
126 }
127
128 #[must_use]
129 fn verify(
130 &self,
131 group_key: RistrettoPoint,
132 nonces: &[Vec<RistrettoPoint>],
133 sum: Scalar,
134 ) -> Option<Self::Signature> {
135 let mut sig = (SchnorrSignature::<Ristretto> { R: nonces[0][0], s: sum }).serialize();
136 sig[63] |= 1 << 7;
137 Some(Signature::from_bytes(&sig).unwrap()).filter(|sig| {
138 PublicKey::from_bytes(&group_key.to_bytes())
139 .unwrap()
140 .verify(&mut signing_context(self.context).bytes(self.msg.as_ref().unwrap()), sig)
141 .is_ok()
142 })
143 }
144
145 fn verify_share(
146 &self,
147 verification_share: RistrettoPoint,
148 nonces: &[Vec<RistrettoPoint>],
149 share: Scalar,
150 ) -> Result<Vec<(Scalar, RistrettoPoint)>, ()> {
151 self.schnorr.verify_share(verification_share, nonces, share)
152 }
153}