Skip to content

Commit 71d0f1a

Browse files
committed
Synopsis and make docs test pass
1 parent 04469a8 commit 71d0f1a

File tree

2 files changed

+116
-14
lines changed

2 files changed

+116
-14
lines changed

schnorr_fun/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ assert!(schnorr.verify(&verification_key, message, &signature));
5454
- Adaptor signatures
5555
- compatibility with `rust-secp256k1`'s `schnorrsig` module with `libsecp_compat` feature.
5656
- [MuSig2] implementation compatible with [secp256k1-zkp]
57+
- [FROST] implementation
5758
- Feature flags
5859
- `serde`: for serde implementations for signatures
5960
- `libsecp_compat`: for `From` implementations between `rust-secp256k1`'s Schnorr signatures.
@@ -64,3 +65,4 @@ assert!(schnorr.verify(&verification_key, message, &signature));
6465
[secp256kfun]: https://docs.rs/secp256kfun
6566
[secp256k1-zkp]: https://github.com/ElementsProject/secp256k1-zkp/pull/131
6667
[MuSig2]: https://eprint.iacr.org/2020/1261.pdf
68+
[FROST]: https://eprint.iacr.org/2020/852.pdf

schnorr_fun/src/frost.rs

+114-14
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,110 @@
1-
//! ## Description
1+
//! ## FROST multisignature scheme
22
//!
3-
//! The FROST (Flexible Round-Optimize Schnorr Threshold) multisignature scheme lets you aggregate
4-
//! multiple public keys into a single public key that requires some threshold t-of-n secret keys to
5-
//! sign a signature under the aggregate key.
3+
//! The FROST (Flexible Round-Optimize Schnorr Threshold) multisignature scheme allows you aggregate
4+
//! multiple public keys into a single public key. To sign a message under this public key, a threshold t-of-n secret keys
5+
//! must use a common set of nonces to each produce a signature share. These signature shares are then combined
6+
//! to form a signature that is valid under the aggregate key.
67
//!
7-
//! This implementation has NOT yet been made compatible with other existing implementations
8-
//! [secp256k1-zkp]: https://github.com/ElementsProject/secp256k1-zkp/pull/138
8+
//! This implementation has **not yet** been made compatible with other existing FROST implementations
9+
//! (notably [secp256k1-zkp]).
910
//!
10-
//! See MuSig in this repository, the [FROST paper] and [Security of Multi- and Threshold Signatures].
11+
//! For reference see the [FROST paper], the MuSig implementation in this repository, and also [Security of Multi- and Threshold Signatures].
1112
//!
12-
//! [FROST paper]: https://eprint.iacr.org/2020/852.pdf
13-
//! [Security of Multi- and Threshold Signatures]: https://eprint.iacr.org/2021/1375.pdf
13+
//! [secp256k1-zkp]: <https://github.com/ElementsProject/secp256k1-zkp/pull/138>
14+
//! [FROST paper]: <https://eprint.iacr.org/2020/852.pdf>
15+
//! [Security of Multi- and Threshold Signatures]: <https://eprint.iacr.org/2021/1375.pdf>
16+
//!
17+
//! ## Synopsis
18+
//!
19+
//! ```
20+
//! use schnorr_fun::{frost::{Frost, ScalarPoly}, Schnorr, Message, nonce::Deterministic, fun::marker::Public};
21+
//! use sha2::Sha256;
22+
//! // use SHA256 with deterministic nonce generation
23+
//! let frost = Frost::new(Schnorr::<Sha256, Deterministic<Sha256>>::new(
24+
//! Deterministic::<Sha256>::default(),
25+
//! ));
26+
//! // create a random secret scalar poly with two coefficients,
27+
//! // corresponding to FROST multisig with a threshold of two
28+
//! let scalar_poly = ScalarPoly::random(2, &mut rand::thread_rng());
29+
//! # let scalar_poly2 = ScalarPoly::random(2, &mut rand::thread_rng());
30+
//! # let scalar_poly3 = ScalarPoly::random(2, &mut rand::thread_rng());
31+
//! // share our public point poly, and recieve the point polys from other participants
32+
//! # let point_poly2 = scalar_poly2.to_point_poly();
33+
//! # let point_poly3 = scalar_poly3.to_point_poly();
34+
//! let point_polys = vec![scalar_poly.to_point_poly(), point_poly2, point_poly3];
35+
//! // create secret shares and proof of possession using our secret scalar poly
36+
//! let keygen = frost.new_keygen(point_polys).unwrap();
37+
//! let (shares, pop) = frost.create_shares(&keygen, scalar_poly);
38+
//! # let (shares2, pop2) = frost.create_shares(&keygen, scalar_poly2);
39+
//! # let (shares3, pop3) = frost.create_shares(&keygen, scalar_poly3);
40+
//! // send shares at index i and all proofs-of-possession to each other participant i,
41+
//! // and recieve our shares from each other participant as well as their proofs-of-possession.
42+
//! let recieved_shares = vec![shares[0].clone(), shares2[0].clone(), shares3[0].clone()];
43+
//! # let recieved_shares3 = vec![shares[2].clone(), shares2[2].clone(), shares3[2].clone()];
44+
//! let proofs_of_possession = vec![pop, pop2, pop3];
45+
//! // finish keygen by calculating our secret share of the joint FROST key
46+
//! let (secret_share, frost_key) = frost
47+
//! .finish_keygen(
48+
//! keygen.clone(),
49+
//! 0,
50+
//! recieved_shares,
51+
//! proofs_of_possession.clone(),
52+
//! )
53+
//! .unwrap();
54+
//! # let (secret_share3, _frost_key3) = frost
55+
//! # .finish_keygen(
56+
//! # keygen.clone(),
57+
//! # 2,
58+
//! # recieved_shares3,
59+
//! # proofs_of_possession.clone(),
60+
//! # )
61+
//! # .unwrap();
62+
//! // for signing we must have a unique session ID to derive nonces.
63+
//! // we should include all the information that is publicly available.
64+
//! let verification_shares_bytes: Vec<_> = frost_key
65+
//! .verification_shares
66+
//! .iter()
67+
//! .map(|share| share.to_bytes())
68+
//! .collect();
69+
//! // create a unique session ID for this signing session
70+
//! let sid = [
71+
//! frost_key.joint_public_key.to_bytes().as_slice(),
72+
//! verification_shares_bytes.concat().as_slice(),
73+
//! b"frost-very-unique-id".as_slice(),
74+
//! b"0".as_slice(),
75+
//! ]
76+
//! .concat();
77+
//! # let sid3 = [
78+
//! # frost_key.joint_public_key.to_bytes().as_slice(),
79+
//! # verification_shares_bytes.concat().as_slice(),
80+
//! # b"frost-very-unique-id".as_slice(),
81+
//! # b"2".as_slice(),
82+
//! # ]
83+
//! # .concat();
84+
//! // generate nonces for this signing session
85+
//! let nonce = frost.gen_nonce(&secret_share, &sid);
86+
//! # let nonce3 = frost.gen_nonce(&secret_share3, &sid3);
87+
//! // share your public nonce with the other signing participant(s)
88+
//! # let recieved_nonce3 = nonce3.public();
89+
//! // recieve public nonces from other participants with their index
90+
//! let nonces = vec![(0, nonce.public()), (2, recieved_nonce3)];
91+
//! # let nonces3 = vec![(0, nonce.public()), (2, recieved_nonce3)];
92+
//! // start a sign session with these nonces for this message
93+
//! let session = frost.start_sign_session(&frost_key, nonces, Message::plain("test", b"test"));
94+
//! # let session3 = frost.start_sign_session(&frost_key, nonces3, Message::plain("test", b"test"));
95+
//! // create a partial signature
96+
//! let sig = frost.sign(&frost_key, &session, 0, &secret_share, nonce);
97+
//! # let sig3 = frost.sign(&frost_key, &session3, 2, &secret_share3, nonce3);
98+
//! // recieve partial signature(s) from other participant(s) and verify
99+
//! assert!(frost.verify_signature_share(&frost_key, &session, 2, sig3));
100+
//! // combine signature
101+
//! let combined_sig = frost.combine_signature_shares(&frost_key, &session, vec![sig, sig3]);
102+
//! assert!(frost.schnorr.verify(
103+
//! &frost_key.joint_public_key,
104+
//! Message::<Public>::plain("test", b"test"),
105+
//! &combined_sig
106+
//! ));
107+
//! ```
14108
use crate::{
15109
musig::{Nonce, NonceKeyPair},
16110
Message, Schnorr, Signature, Vec,
@@ -31,7 +125,8 @@ use std::collections::BTreeMap;
31125
/// The FROST context.
32126
#[derive(Clone)]
33127
pub struct Frost<H, NG: AddTag> {
34-
schnorr: Schnorr<H, NG>,
128+
/// The instance of the Schnorr signature scheme
129+
pub schnorr: Schnorr<H, NG>,
35130
keygen_id_hash: H,
36131
}
37132

@@ -246,10 +341,15 @@ impl std::error::Error for FinishKeyGenError {}
246341
serde(crate = "serde_crate")
247342
)]
248343
pub struct FrostKey {
249-
joint_public_key: Point<EvenY>,
250-
verification_shares: Vec<Point>,
251-
threshold: u32,
344+
/// The joint public key of the FROST multisignature
345+
pub joint_public_key: Point<EvenY>,
346+
/// Everyone else's point poly evaluated at your index, used in partial signature validation.
347+
pub verification_shares: Vec<Point>,
348+
/// Number of partial signatures required to create a combined signature under this key.
349+
pub threshold: u32,
350+
/// Taproot tweak applied to this FROST key, tracks the aggregate tweak.
252351
tweak: Scalar<Public, Zero>,
352+
/// Whether the secrets need negation in order to sign for the X-Only key
253353
needs_negation: bool,
254354
}
255355

@@ -657,7 +757,7 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: NonceGen + AddTag> Frost<H, NG> {
657757
///
658758
/// ## Return value
659759
///
660-
/// Returns a combined schnorr [`schnorr_fun::signature::Signature`] for the message.
760+
/// Returns a combined schnorr [`Signature`] for the message.
661761
/// Valid against the joint public key.
662762
pub fn combine_signature_shares(
663763
&self,

0 commit comments

Comments
 (0)