hopr_crypto_types/
signing.rs

1use std::marker::PhantomData;
2
3use hopr_primitive_types::prelude::{GeneralError::ParseError, *};
4use sha2::Sha512;
5
6use crate::prelude::*;
7
8const ECDSA_SIGNATURE_SIZE: usize = 64;
9
10type RawSignature = ([u8; ECDSA_SIGNATURE_SIZE], u8);
11
12/// Trait for ECDSA signature engines.
13pub trait EcdsaEngine {
14    /// Sign the given `hash` with the private key from the `chain_keypair`.
15    fn sign_hash(hash: &Hash, chain_keypair: &ChainKeypair) -> Result<RawSignature, CryptoError>;
16    /// Verify the given `signature` against the `hash` and the `public_key`.
17    fn verify_hash(signature: &RawSignature, hash: &Hash, public_key: &PublicKey) -> Result<bool, CryptoError>;
18    /// Recover the signer public key from the `signature` and the `hash`.
19    fn recover_from_hash(signature: &RawSignature, hash: &Hash) -> Result<PublicKey, CryptoError>;
20}
21
22/// ECDSA signing engine based on the pure Rust [`k256`](https://docs.rs/k256/latest/k256/) crate.
23///
24/// This is usually slower than [`NativeEcdsaSigningEngine`], but pure Rust-based.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub struct K256EcdsaSigningEngine;
27impl EcdsaEngine for K256EcdsaSigningEngine {
28    fn sign_hash(hash: &Hash, chain_keypair: &ChainKeypair) -> Result<RawSignature, CryptoError> {
29        let key = k256::ecdsa::SigningKey::from_bytes(chain_keypair.secret().as_ref().into())
30            .map_err(|_| CryptoError::InvalidInputValue("chain_keypair"))?;
31        let (sig, rec) = key
32            .sign_prehash_recoverable(hash.as_ref())
33            .map_err(|_| CryptoError::CalculationError)?;
34
35        Ok((sig.to_bytes().into(), rec.to_byte()))
36    }
37
38    fn verify_hash(signature: &RawSignature, hash: &Hash, public_key: &PublicKey) -> Result<bool, CryptoError> {
39        use k256::ecdsa::signature::hazmat::PrehashVerifier;
40
41        let pub_key = k256::ecdsa::VerifyingKey::from_sec1_bytes(&public_key.to_uncompressed_bytes())
42            .map_err(|_| CryptoError::InvalidInputValue("public key"))?;
43
44        if let Ok(signature) = k256::ecdsa::Signature::try_from(signature.0.as_ref()) {
45            Ok(pub_key.verify_prehash(hash.as_ref(), &signature).is_ok())
46        } else {
47            Err(CryptoError::InvalidInputValue("signature"))
48        }
49    }
50
51    fn recover_from_hash(signature: &RawSignature, hash: &Hash) -> Result<PublicKey, CryptoError> {
52        let sig = k256::ecdsa::Signature::from_bytes(&signature.0.into())
53            .map_err(|_| CryptoError::InvalidInputValue("signature.sig"))?;
54
55        let recid = k256::ecdsa::RecoveryId::try_from(signature.1)
56            .map_err(|_| CryptoError::InvalidInputValue("signature.recid"))?;
57
58        let recovered_key = k256::ecdsa::VerifyingKey::recover_from_prehash(hash.as_ref(), &sig, recid)
59            .map_err(|_| CryptoError::SignatureVerification)?;
60
61        (*recovered_key.as_affine()).try_into()
62    }
63}
64
65/// ECDSA signing engine based on the fast [`secp256k1`](https://docs.rs/secp256k1/latest/secp256k1/) crate.
66///
67/// The crate uses bindings to the Bitcoin secp256k1 C library.
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub struct NativeEcdsaSigningEngine;
70
71impl EcdsaEngine for NativeEcdsaSigningEngine {
72    fn sign_hash(hash: &Hash, chain_keypair: &ChainKeypair) -> Result<RawSignature, CryptoError> {
73        let sk = secp256k1::SecretKey::from_byte_array(chain_keypair.secret().clone().into())
74            .map_err(|_| CryptoError::InvalidInputValue("chain_keypair"))?;
75
76        let sig =
77            secp256k1::global::SECP256K1.sign_ecdsa_recoverable(secp256k1::Message::from_digest(hash.into()), &sk);
78        let (recid, sig) = sig.serialize_compact();
79        Ok((sig, i32::from(recid) as u8))
80    }
81
82    fn verify_hash(signature: &RawSignature, hash: &Hash, public_key: &PublicKey) -> Result<bool, CryptoError> {
83        let pk = secp256k1::PublicKey::from_slice(&public_key.to_uncompressed_bytes())
84            .map_err(|_| CryptoError::InvalidInputValue("public key"))?;
85
86        let sig = secp256k1::ecdsa::Signature::from_compact(&signature.0)
87            .map_err(|_| CryptoError::InvalidInputValue("signature"))?;
88
89        Ok(secp256k1::global::SECP256K1
90            .verify_ecdsa(secp256k1::Message::from_digest(hash.into()), &sig, &pk)
91            .is_ok())
92    }
93
94    fn recover_from_hash(signature: &RawSignature, hash: &Hash) -> Result<PublicKey, CryptoError> {
95        let sig = secp256k1::ecdsa::RecoverableSignature::from_compact(
96            &signature.0,
97            secp256k1::ecdsa::RecoveryId::from_u8_masked(signature.1),
98        )
99        .map_err(|_| CryptoError::InvalidInputValue("signature"))?;
100
101        let pk = secp256k1::global::SECP256K1
102            .recover_ecdsa(secp256k1::Message::from_digest(hash.into()), &sig)
103            .map_err(|_| CryptoError::SignatureVerification)?;
104
105        PublicKey::try_from(pk.serialize_uncompressed().as_ref()).map_err(|_| CryptoError::CalculationError)
106    }
107}
108
109/// Represents an ECDSA signature based on the secp256k1 curve with a recoverable public key.
110/// The signature uses [Keccak256](Hash) as the hash function.
111///
112/// This signature encodes the 2-bit recovery information into the
113/// uppermost bits from MSB of the `S` value, which are never used by this ECDSA
114/// instantiation over secp256k1.
115///
116/// The instance holds the byte array consisting of `R` and `S` values with the recovery bit
117/// already embedded in `S`.
118///
119/// See [EIP-2098](https://eips.ethereum.org/EIPS/eip-2098) for details.
120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
122pub struct ChainSignature<E>(
123    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; ECDSA_SIGNATURE_SIZE],
124    PhantomData<E>,
125);
126
127impl<E: EcdsaEngine> ChainSignature<E> {
128    fn new(raw: RawSignature) -> Self {
129        let mut ret = Self(raw.0, PhantomData);
130
131        // Embed the parity recovery bit into the S value
132        let parity = raw.1 & 0x01;
133        ret.0[Self::SIZE / 2] &= 0x7f;
134        ret.0[Self::SIZE / 2] |= parity << 7;
135
136        ret
137    }
138
139    /// Returns the raw signature, without the encoded public key recovery bit and
140    /// the recovery parity bit as a separate value.
141    pub fn raw_signature(&self) -> RawSignature {
142        let mut raw_sig = self.0;
143        let parity: u8 = (raw_sig[Self::SIZE / 2] & 0x80 != 0).into();
144        raw_sig[Self::SIZE / 2] &= 0x7f;
145        (raw_sig, parity)
146    }
147
148    /// Signs the given message using the chain private key.
149    #[inline]
150    pub fn sign_message(message: &[u8], chain_keypair: &ChainKeypair) -> Self {
151        Self::sign_hash(&Hash::create(&[message]), chain_keypair)
152    }
153
154    /// Signs the given hash using the raw private key.
155    #[inline]
156    pub fn sign_hash(hash: &Hash, chain_keypair: &ChainKeypair) -> Self {
157        Self::new(
158            E::sign_hash(hash, chain_keypair).expect("signing cannot fail: keypair always contains a valid secret key"),
159        )
160    }
161
162    /// Verifies this signature against the given message and a public key object
163    #[inline]
164    pub fn verify_message(&self, message: &[u8], public_key: &PublicKey) -> crate::errors::Result<bool> {
165        self.verify_hash(&Hash::create(&[message]), public_key)
166    }
167
168    /// Verifies this signature against the given hash and a public key object
169    #[inline]
170    pub fn verify_hash(&self, hash: &Hash, public_key: &PublicKey) -> crate::errors::Result<bool> {
171        E::verify_hash(&self.raw_signature(), hash, public_key)
172    }
173
174    /// Recovers signer public key if this signature is a valid signature of the given `msg`.
175    #[inline]
176    pub fn recover_from_msg(&self, msg: &[u8]) -> crate::errors::Result<PublicKey> {
177        self.recover_from_hash(&Hash::create(&[msg]))
178    }
179
180    /// Recovers signer public key if this signature is a valid signature of the given `hash`.
181    pub fn recover_from_hash(&self, hash: &Hash) -> crate::errors::Result<PublicKey> {
182        // We saved only the parity bit in the S value, so test both x-coordinate signs.
183        let (sig, parity) = self.raw_signature();
184        for alt in [0u8, 2u8] {
185            if let Ok(pk) = E::recover_from_hash(&(sig, parity | alt), hash) {
186                return Ok(pk);
187            }
188        }
189        Err(CryptoError::CalculationError)
190    }
191}
192
193impl<E> AsRef<[u8]> for ChainSignature<E> {
194    fn as_ref(&self) -> &[u8] {
195        &self.0
196    }
197}
198
199impl<E> TryFrom<&[u8]> for ChainSignature<E> {
200    type Error = GeneralError;
201
202    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
203        Ok(Self(
204            value.try_into().map_err(|_| ParseError("Signature".into()))?,
205            PhantomData,
206        ))
207    }
208}
209
210impl<E> BytesRepresentable for ChainSignature<E> {
211    const SIZE: usize = ECDSA_SIGNATURE_SIZE;
212}
213
214#[cfg(not(feature = "rust-ecdsa"))]
215pub type Signature = ChainSignature<NativeEcdsaSigningEngine>;
216
217#[cfg(feature = "rust-ecdsa")]
218pub type Signature = ChainSignature<K256EcdsaSigningEngine>;
219
220/// Represents an EdDSA signature using the Ed25519 Edwards curve.
221#[derive(Clone, Copy, Debug, PartialEq, Eq)]
222#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
223pub struct OffchainSignature(#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; Self::SIZE]);
224
225impl OffchainSignature {
226    /// Sign the given message using the [`OffchainKeypair`].
227    pub fn sign_message(msg: &[u8], signing_keypair: &OffchainKeypair) -> Self {
228        // Expand the SK from the given keypair
229        let expanded_sk = ed25519_dalek::hazmat::ExpandedSecretKey::from(
230            &ed25519_dalek::SecretKey::try_from(signing_keypair.secret().as_ref())
231                .expect("cannot fail: OffchainKeypair always contains a valid secret key"),
232        );
233
234        // Get the verifying key from the SAME keypair, avoiding Double Public Key Signing Function Oracle Attack on
235        // Ed25519 See https://github.com/MystenLabs/ed25519-unsafe-libs for details
236        let verifying = ed25519_dalek::VerifyingKey::from(signing_keypair.public().edwards);
237
238        ed25519_dalek::hazmat::raw_sign::<Sha512>(&expanded_sk, msg, &verifying).into()
239    }
240
241    /// Verify this signature of the given message and [OffchainPublicKey].
242    pub fn verify_message(&self, msg: &[u8], public_key: &OffchainPublicKey) -> bool {
243        let sgn = ed25519_dalek::Signature::from_slice(&self.0)
244            .expect("cannot fail: OffchainSignature always contains a valid signature");
245        let pk = ed25519_dalek::VerifyingKey::from(public_key.edwards);
246        pk.verify_strict(msg, &sgn).is_ok()
247    }
248
249    /// Performs optimized signature verification of multiple signed messages and public keys.
250    pub fn verify_batch<'a, I: IntoIterator<Item = ((&'a [u8], OffchainSignature), OffchainPublicKey)>>(
251        entries: I,
252    ) -> bool {
253        let (signed_msgs, pub_keys): (Vec<(&[u8], OffchainSignature)>, Vec<ed25519_dalek::VerifyingKey>) = entries
254            .into_iter()
255            .map(|(a, b)| (a, ed25519_dalek::VerifyingKey::from(b.edwards)))
256            .unzip();
257
258        let (msgs, signatures): (Vec<&[u8]>, Vec<ed25519_dalek::Signature>) = signed_msgs
259            .into_iter()
260            .map(|(a, b)| (a, ed25519_dalek::Signature::from_bytes(&b.0)))
261            .unzip();
262
263        ed25519_dalek::verify_batch(&msgs, &signatures, &pub_keys).is_ok()
264    }
265}
266
267impl AsRef<[u8]> for OffchainSignature {
268    fn as_ref(&self) -> &[u8] {
269        &self.0
270    }
271}
272
273impl TryFrom<&[u8]> for OffchainSignature {
274    type Error = GeneralError;
275
276    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
277        Ok(ed25519_dalek::Signature::from_slice(value)
278            .map_err(|_| ParseError("OffchainSignature".into()))?
279            .into())
280    }
281}
282
283impl BytesRepresentable for OffchainSignature {
284    /// Size of the EdDSA signature using Ed25519.
285    const SIZE: usize = ed25519_dalek::Signature::BYTE_SIZE;
286}
287
288impl From<ed25519_dalek::Signature> for OffchainSignature {
289    fn from(value: ed25519_dalek::Signature) -> Self {
290        let mut ret = Self([0u8; Self::SIZE]);
291        ret.0.copy_from_slice(value.to_bytes().as_ref());
292        ret
293    }
294}
295
296impl TryFrom<([u8; 32], [u8; 32])> for OffchainSignature {
297    type Error = GeneralError;
298
299    fn try_from(value: ([u8; 32], [u8; 32])) -> std::result::Result<Self, Self::Error> {
300        Ok(ed25519_dalek::Signature::from_components(value.0, value.1).into())
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use ed25519_dalek::Signer;
307    use hex_literal::hex;
308
309    use super::*;
310    use crate::keypairs::Keypair;
311
312    const PRIVATE_KEY: [u8; 32] = hex!("e17fe86ce6e99f4806715b0c9412f8dad89334bf07f72d5834207a9d8f19d7f8");
313
314    #[test]
315    fn chain_signature_serialize() -> anyhow::Result<()> {
316        let msg = b"test000000";
317        let kp = ChainKeypair::from_secret(&PRIVATE_KEY)?;
318        let sgn = Signature::sign_message(msg, &kp);
319
320        let deserialized = Signature::try_from(sgn.as_ref())?;
321        assert_eq!(sgn, deserialized, "signatures don't match");
322
323        Ok(())
324    }
325
326    #[test]
327    fn chain_signature_engines_must_be_compatible() -> anyhow::Result<()> {
328        let msg = b"test12345";
329        let ck = ChainKeypair::random();
330        let sgn_1 = ChainSignature::<NativeEcdsaSigningEngine>::sign_message(msg, &ck);
331        let sgn_2 = ChainSignature::<K256EcdsaSigningEngine>::sign_message(msg, &ck);
332
333        let sgn_1_k256 = ChainSignature::<K256EcdsaSigningEngine>::try_from(sgn_1.as_ref())?;
334        let sgn_2_native = ChainSignature::<NativeEcdsaSigningEngine>::try_from(sgn_2.as_ref())?;
335
336        assert!(sgn_1_k256.verify_message(msg, ck.public())?);
337        assert!(sgn_2_native.verify_message(msg, ck.public())?);
338
339        Ok(())
340    }
341
342    #[test]
343    fn chain_signature_sign_and_recover() -> anyhow::Result<()> {
344        let msg = hex!("eff80b9f035b1d369c6a60f362ac7c8b8c3b61b76d151d1be535145ccaa3e83e");
345        let hash = Hash::create(&[&msg]);
346
347        let kp = ChainKeypair::from_secret(&PRIVATE_KEY)?;
348
349        let signature1 = Signature::sign_message(&msg, &kp);
350        let signature2 = Signature::sign_hash(&hash, &kp);
351
352        let pub_key1 = PublicKey::from_privkey(&PRIVATE_KEY)?;
353        let pub_key2 = signature1.recover_from_msg(&msg)?;
354        let pub_key3 = signature2.recover_from_hash(&hash)?;
355
356        assert_eq!(pub_key1, *kp.public());
357        assert_eq!(pub_key1, pub_key2, "recovered public key does not match");
358        assert_eq!(pub_key1, pub_key3, "recovered public key does not match");
359
360        assert!(
361            signature1.verify_message(&msg, &pub_key1)?,
362            "signature 1 verification failed with pub key 1"
363        );
364        assert!(
365            signature1.verify_message(&msg, &pub_key2)?,
366            "signature 1 verification failed with pub key 2"
367        );
368        assert!(
369            signature1.verify_message(&msg, &pub_key3)?,
370            "signature 1 verification failed with pub key 3"
371        );
372
373        assert!(
374            signature2.verify_hash(&hash, &pub_key1)?,
375            "signature 2 verification failed with pub key 1"
376        );
377        assert!(
378            signature2.verify_hash(&hash, &pub_key2)?,
379            "signature 2 verification failed with pub key 2"
380        );
381        assert!(
382            signature2.verify_hash(&hash, &pub_key3)?,
383            "signature 2 verification failed with pub key 3"
384        );
385
386        Ok(())
387    }
388
389    #[test]
390    fn offchain_signature_signing() -> anyhow::Result<()> {
391        let msg = b"test12345";
392        let keypair = OffchainKeypair::from_secret(&PRIVATE_KEY)?;
393
394        let key = ed25519_dalek::SecretKey::try_from(PRIVATE_KEY)?;
395        let kp = ed25519_dalek::SigningKey::from_bytes(&key);
396        let pk = ed25519_dalek::VerifyingKey::from(&kp);
397
398        let sgn = kp.sign(msg);
399        assert!(pk.verify_strict(msg, &sgn).is_ok(), "blomp");
400
401        let sgn_1 = OffchainSignature::sign_message(msg, &keypair);
402        let sgn_2 = OffchainSignature::try_from(sgn_1.as_ref())?;
403
404        assert!(
405            sgn_1.verify_message(msg, keypair.public()),
406            "cannot verify message via sig 1"
407        );
408        assert!(
409            sgn_2.verify_message(msg, keypair.public()),
410            "cannot verify message via sig 2"
411        );
412        assert_eq!(sgn_1, sgn_2, "signatures must be equal");
413
414        let keypair = OffchainKeypair::from_secret(&PRIVATE_KEY)?;
415        let sig = OffchainSignature::sign_message("my test msg".as_bytes(), &keypair);
416        assert!(sig.verify_message("my test msg".as_bytes(), keypair.public()));
417
418        Ok(())
419    }
420
421    #[test]
422    fn offchain_signature_batch_verify() -> anyhow::Result<()> {
423        let msgs = (0..100)
424            .map(|i| format!("test_msg_{i}").as_bytes().to_vec())
425            .collect::<Vec<_>>();
426
427        let tuples = (0..100)
428            .map(|i| {
429                let kp = OffchainKeypair::random();
430                let sig = OffchainSignature::sign_message(&msgs[i], &kp);
431                ((msgs[i].as_slice(), sig), *kp.public())
432            })
433            .collect::<Vec<_>>();
434
435        assert!(OffchainSignature::verify_batch(tuples));
436        Ok(())
437    }
438}