Skip to main content

midnight_base_crypto/
signatures.rs

1// This file is part of midnight-ledger.
2// Copyright (C) 2025 Midnight Foundation
3// SPDX-License-Identifier: Apache-2.0
4// Licensed under the Apache License, Version 2.0 (the "License");
5// You may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7// http://www.apache.org/licenses/LICENSE-2.0
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//! Signature scheme for use primarily outside of proofs
15//!
16//! Schnorr over secp256k1, conforming to BIP340.
17use crate::BinaryHashRepr;
18use k256::schnorr;
19#[cfg(feature = "proptest")]
20use proptest::arbitrary::Arbitrary;
21use rand::distributions::{Distribution, Standard};
22use rand::rngs::OsRng;
23use rand::{CryptoRng, Rng};
24use serde::{Deserialize, Serialize};
25use serialize::{Deserializable, Serializable, Tagged, VecExt, tag_enforcement_test};
26#[cfg(feature = "proptest")]
27use serialize::{NoStrategy, simple_arbitrary};
28use signature::{RandomizedSigner, Verifier};
29use std::borrow::Cow;
30use std::cmp::Ordering;
31use std::fmt::{self, Debug, Formatter};
32use std::hash::Hash;
33use std::io::{self, Read, Write};
34#[cfg(feature = "proptest")]
35use std::marker::PhantomData;
36
37macro_rules! derive_via_to_bytes {
38    ($ty:ty) => {
39        impl Hash for $ty {
40            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
41                state.write(&self.0.to_bytes()[..]);
42            }
43        }
44
45        impl PartialOrd for $ty {
46            fn partial_cmp(&self, other: &$ty) -> Option<Ordering> {
47                Some(self.cmp(other))
48            }
49        }
50
51        impl Ord for $ty {
52            fn cmp(&self, other: &$ty) -> Ordering {
53                let left = self.0.to_bytes();
54                let right = other.0.to_bytes();
55                left.cmp(&right)
56            }
57        }
58    };
59}
60
61#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
62/// A verifying public key
63pub struct VerifyingKey(schnorr::VerifyingKey);
64derive_via_to_bytes!(VerifyingKey);
65
66impl Default for VerifyingKey {
67    fn default() -> Self {
68        // Manually sampled, we want a stand-in without an rng sometimes.
69        VerifyingKey(
70            schnorr::VerifyingKey::from_bytes(&[
71                43, 59, 242, 191, 89, 80, 243, 46, 116, 47, 12, 103, 140, 35, 90, 207, 180, 68,
72                188, 10, 108, 126, 200, 195, 239, 14, 120, 114, 89, 188, 199, 38,
73            ])
74            .expect("static verifier key should be valid"),
75        )
76    }
77}
78
79impl Debug for VerifyingKey {
80    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
81        write!(formatter, "<signature verifying key>")
82    }
83}
84
85impl BinaryHashRepr for VerifyingKey {
86    fn binary_repr<W: crate::MemWrite<u8>>(&self, writer: &mut W) {
87        writer.write(&self.0.to_bytes());
88    }
89
90    fn binary_len(&self) -> usize {
91        self.0.to_bytes().len()
92    }
93}
94
95#[cfg(feature = "proptest")]
96simple_arbitrary!(VerifyingKey);
97#[cfg(feature = "proptest")]
98serialize::randomised_serialization_test!(VerifyingKey);
99
100impl Distribution<VerifyingKey> for Standard {
101    fn sample<R: Rng + ?Sized>(&self, _rng: &mut R) -> VerifyingKey {
102        SigningKey::sample(OsRng).verifying_key()
103    }
104}
105
106impl Tagged for VerifyingKey {
107    fn tag() -> Cow<'static, str> {
108        Cow::Borrowed("signature-verifying-key[v1]")
109    }
110    fn tag_unique_factor() -> String {
111        "signature-verifying-key[v1]".into()
112    }
113}
114tag_enforcement_test!(VerifyingKey);
115
116impl Serializable for VerifyingKey {
117    fn serialize(&self, writer: &mut impl Write) -> io::Result<()> {
118        writer.write_all(&self.0.to_bytes())
119    }
120
121    fn serialized_size(&self) -> usize {
122        // Key size is 32 (k256::Secp256k1::FieldBytesSize). Accessing this
123        // would require an additional import for the trait.
124        // Note that this is *field* size, because BIP340 encodes curve points as a field
125        32
126    }
127}
128
129impl Deserializable for VerifyingKey {
130    fn deserialize(reader: &mut impl Read, _recursion_depth: u32) -> io::Result<Self> {
131        let mut bytes = [0u8; 32];
132        reader.read_exact(&mut bytes)?;
133        Ok(VerifyingKey(
134            schnorr::VerifyingKey::from_bytes(&bytes).map_err(|_| {
135                io::Error::new(
136                    io::ErrorKind::InvalidData,
137                    "Malformed Schnorr verifying key",
138                )
139            })?,
140        ))
141    }
142}
143
144impl VerifyingKey {
145    /// Verifies if a signature is correct
146    pub fn verify(&self, msg: &[u8], signature: &Signature) -> bool {
147        matches!(self.0.verify(msg, &signature.0), Ok(()))
148    }
149}
150
151#[derive(Clone)]
152/// A signing secret key
153pub struct SigningKey(schnorr::SigningKey);
154
155impl Tagged for SigningKey {
156    fn tag() -> Cow<'static, str> {
157        Cow::Borrowed("signing-key[v1]")
158    }
159    fn tag_unique_factor() -> String {
160        "signing-key[v1]".into()
161    }
162}
163tag_enforcement_test!(SigningKey);
164
165impl SigningKey {
166    /// Samples a new secret key from secure randomness
167    pub fn sample<R: Rng + CryptoRng>(mut rng: R) -> Self {
168        SigningKey(schnorr::SigningKey::random(&mut rng))
169    }
170
171    /// Returns the corresponding verifying public key
172    pub fn verifying_key(&self) -> VerifyingKey {
173        VerifyingKey(*self.0.verifying_key())
174    }
175
176    /// Signs a message
177    pub fn sign<R: Rng + CryptoRng>(&self, rng: &mut R, msg: &[u8]) -> Signature {
178        Signature(self.0.sign_with_rng(rng, msg))
179    }
180
181    /// Parse signing key from big endian-encoded bytes
182    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
183        let signing_key = schnorr::SigningKey::from_bytes(bytes)?;
184        Ok(SigningKey(signing_key))
185    }
186}
187
188impl Debug for SigningKey {
189    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
190        write!(f, "<secret key>")
191    }
192}
193
194impl Serializable for SigningKey {
195    fn serialize(&self, writer: &mut impl Write) -> io::Result<()> {
196        writer.write_all(&self.0.to_bytes())
197    }
198
199    fn serialized_size(&self) -> usize {
200        // Key size is 32 (k256::Secp256k1::FieldBytesSize). Accessing this
201        // would require an additional import for the trait.
202        32
203    }
204}
205
206impl Deserializable for SigningKey {
207    fn deserialize(reader: &mut impl Read, _recursion_depth: u32) -> io::Result<Self> {
208        let mut bytes = [0u8; 32];
209        reader.read_exact(&mut bytes)?;
210        Ok(SigningKey(
211            schnorr::SigningKey::from_bytes(&bytes).map_err(|_| {
212                io::Error::new(io::ErrorKind::InvalidData, "Malformed Schnorr signing key")
213            })?,
214        ))
215    }
216}
217
218#[derive(Clone, Debug, PartialEq, Eq)]
219/// A Schnorr signature
220pub struct Signature(schnorr::Signature);
221derive_via_to_bytes!(Signature);
222
223impl Default for Signature {
224    fn default() -> Signature {
225        // Manually sampled, we want a stand-in without an rng sometimes.
226        Signature(
227            schnorr::Signature::try_from(
228                &[
229                    20, 137, 89, 240, 159, 41, 72, 199, 212, 53, 117, 4, 235, 179, 101, 207, 210,
230                    224, 132, 10, 131, 224, 89, 19, 152, 194, 235, 130, 162, 57, 186, 40, 103, 85,
231                    94, 192, 157, 17, 70, 102, 209, 27, 62, 153, 67, 246, 158, 17, 124, 18, 63,
232                    245, 208, 254, 72, 95, 157, 235, 180, 156, 164, 66, 143, 251,
233                ][..],
234            )
235            .expect("static signature should be valid"),
236        )
237    }
238}
239
240#[cfg(feature = "proptest")]
241simple_arbitrary!(Signature);
242#[cfg(feature = "proptest")]
243serialize::randomised_serialization_test!(Signature);
244
245impl Distribution<Signature> for Standard {
246    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Signature {
247        let signing_key = SigningKey::sample(OsRng);
248        let mut message = Vec::with_bounded_capacity(32);
249        rng.fill_bytes(&mut message);
250        signing_key.sign(&mut OsRng, &message)
251    }
252}
253
254impl Tagged for Signature {
255    fn tag() -> Cow<'static, str> {
256        Cow::Borrowed("signature[v1]")
257    }
258    fn tag_unique_factor() -> String {
259        "signature[v1]".into()
260    }
261}
262tag_enforcement_test!(Signature);
263
264impl Serializable for Signature {
265    fn serialize(&self, writer: &mut impl Write) -> io::Result<()> {
266        writer.write_all(&self.0.to_bytes())
267    }
268
269    fn serialized_size(&self) -> usize {
270        schnorr::Signature::BYTE_SIZE
271    }
272}
273
274impl Deserializable for Signature {
275    fn deserialize(reader: &mut impl Read, _recursion_depth: u32) -> io::Result<Self> {
276        let mut bytes = [0u8; 64];
277        reader.read_exact(&mut bytes)?;
278        Ok(Signature(
279            schnorr::Signature::try_from(&bytes[..]).map_err(|_| {
280                io::Error::new(io::ErrorKind::InvalidData, "Malformed Schnorr signature")
281            })?,
282        ))
283    }
284}