mina_signer/
schnorr.rs

1//! Mina Schnorr signature scheme
2//!
3//! An implementation of the singer interface for the Mina signature algorithm
4//!
5//! Details: <https://github.com/MinaProtocol/mina/blob/develop/docs/specs/signatures/description.md>
6
7extern crate alloc;
8use alloc::{boxed::Box, string::String};
9
10use crate::{BaseField, CurvePoint, Hashable, Keypair, PubKey, ScalarField, Signature, Signer};
11use ark_ec::{
12    AffineRepr, // for generator()
13    CurveGroup,
14};
15use ark_ff::{
16    BigInteger, // for is_even()
17    Field,      // for from_random_bytes()
18    PrimeField, // for from_repr()
19    Zero,
20};
21use blake2::{
22    digest::{Update, VariableOutput},
23    Blake2bVar,
24};
25use core::ops::{Add, Neg};
26use mina_hasher::{self, DomainParameter, Hasher, ROInput};
27
28/// Schnorr signer context for the Mina signature algorithm
29///
30/// For details about the signature algorithm please see the [`schnorr`](crate::schnorr) documentation
31pub struct Schnorr<H: Hashable> {
32    hasher: Box<dyn Hasher<Message<H>>>,
33    domain_param: H::D,
34}
35
36#[derive(Clone)]
37struct Message<H: Hashable> {
38    input: H,
39    pub_key_x: BaseField,
40    pub_key_y: BaseField,
41    rx: BaseField,
42}
43
44impl<H: Hashable> Hashable for Message<H> {
45    type D = H::D;
46
47    fn to_roinput(&self) -> ROInput {
48        self.input
49            .to_roinput()
50            .append_field(self.pub_key_x)
51            .append_field(self.pub_key_y)
52            .append_field(self.rx)
53    }
54
55    fn domain_string(domain_param: Self::D) -> Option<String> {
56        H::domain_string(domain_param)
57    }
58}
59
60impl<H: 'static + Hashable> Signer<H> for Schnorr<H> {
61    fn sign(&mut self, kp: &Keypair, input: &H) -> Signature {
62        let k: ScalarField = self.derive_nonce(kp, input);
63        let r: CurvePoint = CurvePoint::generator()
64            .mul_bigint(k.into_bigint())
65            .into_affine();
66        let k: ScalarField = if r.y.into_bigint().is_even() { k } else { -k };
67
68        let e: ScalarField = self.message_hash(&kp.public, r.x, input);
69        let s: ScalarField = k + e * kp.secret.scalar();
70
71        Signature::new(r.x, s)
72    }
73
74    fn verify(&mut self, sig: &Signature, public: &PubKey, input: &H) -> bool {
75        let ev: ScalarField = self.message_hash(public, sig.rx, input);
76
77        let sv = CurvePoint::generator()
78            .mul_bigint(sig.s.into_bigint())
79            .into_affine();
80        // Perform addition and infinity check in projective coordinates for performance
81        let rv = public.point().mul_bigint(ev.into_bigint()).neg().add(sv);
82
83        if rv.is_zero() {
84            return false;
85        }
86
87        let rv = rv.into_affine();
88
89        rv.y.into_bigint().is_even() && rv.x == sig.rx
90    }
91}
92
93pub(crate) fn create_legacy<H: 'static + Hashable>(domain_param: H::D) -> impl Signer<H> {
94    Schnorr::<H> {
95        hasher: Box::new(mina_hasher::create_legacy::<Message<H>>(
96            domain_param.clone(),
97        )),
98        domain_param,
99    }
100}
101
102pub(crate) fn create_kimchi<H: 'static + Hashable>(domain_param: H::D) -> impl Signer<H> {
103    Schnorr::<H> {
104        hasher: Box::new(mina_hasher::create_kimchi::<Message<H>>(
105            domain_param.clone(),
106        )),
107        domain_param,
108    }
109}
110
111impl<H: 'static + Hashable> Schnorr<H> {
112    /// This function uses a cryptographic hash function to create a uniformly and
113    /// randomly distributed nonce.  It is crucial for security that no two different
114    /// messages share the same nonce.
115    fn derive_nonce(&self, kp: &Keypair, input: &H) -> ScalarField {
116        let mut blake_hasher = Blake2bVar::new(32).unwrap();
117
118        let roi = input
119            .to_roinput()
120            .append_field(kp.public.point().x)
121            .append_field(kp.public.point().y)
122            .append_scalar(*kp.secret.scalar())
123            .append_bytes(&self.domain_param.clone().into_bytes());
124
125        blake_hasher.update(&roi.to_bytes());
126
127        let mut bytes = [0; 32];
128        blake_hasher
129            .finalize_variable(&mut bytes)
130            .expect("incorrect output size");
131        // Drop the top two bits to convert into a scalar field element
132        //   N.B. Since the order of Pallas's scalar field p is very close to 2^m
133        //   for some m, truncating only creates a tiny amount of bias that should
134        //   be insignificant and better than reduction modulo p.
135        bytes[bytes.len() - 1] &= 0b0011_1111;
136
137        ScalarField::from_random_bytes(&bytes[..]).expect("failed to create scalar from bytes")
138    }
139
140    /// This function uses a cryptographic hash function (based on a sponge construction) to
141    /// convert the message to be signed (and some other information) into a uniformly and
142    /// randomly distributed scalar field element.  It uses Mina's variant of the Poseidon
143    /// SNARK-friendly cryptographic hash function.
144    /// Details: <https://github.com/o1-labs/cryptography-rfcs/blob/httpsnapps-notary-signatures/mina/001-poseidon-sponge.md>
145    fn message_hash(&mut self, pub_key: &PubKey, rx: BaseField, input: &H) -> ScalarField {
146        let schnorr_input = Message::<H> {
147            input: input.clone(),
148            pub_key_x: pub_key.point().x,
149            pub_key_y: pub_key.point().y,
150            rx,
151        };
152
153        // Squeeze and convert from base field element to scalar field element
154        // Since the difference in modulus between the two fields is < 2^125, w.h.p., a
155        // random value from one field will fit in the other field.
156        ScalarField::from(self.hasher.hash(&schnorr_input).into_bigint())
157    }
158}