Skip to main content

rings_core/ecc/
keys.rs

1//! Account-signing keys and DID derivation.
2//!
3//! This module is deliberately scoped to account identity and session
4//! verification:
5//!
6//! - [`SigningSecretKey`] owns account-signing secret material.
7//! - [`VerificationPublicKey`] owns the explicit public key needed by
8//!   non-recoverable signature schemes.
9//! - [`AccountVerifier`] is the session-facing verifier. Recoverable schemes
10//!   can use a DID plus algorithm; non-recoverable schemes carry a public key.
11//!
12//! ElGamal encryption keys live in [`crate::ecc::elgamal`], where they can be
13//! parameterized by any finite cyclic group. Keeping encryption keys out of this
14//! module avoids implying that account identity keys and message-encryption
15//! keys are the same cryptographic object.
16//!
17//! DID derivation uses domain-separated public-key transcripts:
18//! `algorithm || 0x00 || raw_public_key`. This prevents equal raw bytes under
19//! different algorithms from resolving to the same account DID.
20
21use std::cell::RefCell;
22
23use rand::RngCore;
24use rand::SeedableRng;
25use rand_hc::Hc128Rng;
26use serde::Deserialize;
27use serde::Serialize;
28
29use super::keccak256;
30use super::signers;
31use super::PublicKey;
32use super::PublicKeyAddress;
33use super::SecretKey;
34use crate::dht::Did;
35use crate::error::Error;
36use crate::error::Result;
37
38thread_local! {
39    static KEY_RNG: RefCell<Hc128Rng> = RefCell::new(Hc128Rng::from_entropy());
40}
41
42fn public_key_transcript(algorithm: &str, raw_bytes: &[u8]) -> Vec<u8> {
43    let mut out = algorithm.as_bytes().to_vec();
44    out.push(0);
45    out.extend_from_slice(raw_bytes);
46    out
47}
48
49fn domain_separated_address(algorithm: &str, raw_bytes: &[u8]) -> PublicKeyAddress {
50    PublicKeyAddress::from_slice(&keccak256(&public_key_transcript(algorithm, raw_bytes))[12..])
51}
52
53/// Signature algorithm used by an account signing key.
54#[derive(Deserialize, Serialize, Debug, Clone, Copy, Eq, PartialEq)]
55pub enum SignatureAlgorithm {
56    /// secp256k1 ECDSA.
57    Secp256k1,
58    /// Ethereum EIP-191 personal-sign over secp256k1.
59    Eip191,
60    /// Bitcoin BIP-137 personal-sign over secp256k1.
61    Bip137,
62    /// secp256r1 ECDSA.
63    Secp256r1,
64    /// Ed25519.
65    Ed25519,
66    /// BLS12-381 signatures with G1 public keys and G2 signatures.
67    Bls12381,
68}
69
70/// Public key used for signature verification and account addressing.
71#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
72pub enum VerificationPublicKey {
73    /// secp256k1 ECDSA public key.
74    Secp256k1(PublicKey<33>),
75    /// Ethereum EIP-191 recoverable secp256k1 public key.
76    Eip191(PublicKey<33>),
77    /// Bitcoin BIP-137 recoverable secp256k1 public key.
78    Bip137(PublicKey<33>),
79    /// secp256r1 ECDSA public key.
80    Secp256r1(PublicKey<33>),
81    /// Ed25519 public key.
82    Ed25519(PublicKey<33>),
83    /// BLS12-381 G1 public key.
84    Bls12381(PublicKey<48>),
85}
86
87/// Public verifier for an account signature.
88///
89/// Recoverable signature schemes can use DID/address plus algorithm, while
90/// non-recoverable schemes must carry the explicit public key.
91#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
92pub enum AccountVerifier {
93    /// Recoverable signature schemes can identify the account by DID/address.
94    Recoverable {
95        /// Recoverable signature algorithm.
96        algorithm: SignatureAlgorithm,
97        /// Account DID.
98        did: Did,
99    },
100    /// Non-recoverable schemes must carry the public key.
101    PublicKey(VerificationPublicKey),
102}
103
104/// Ed25519 signing seed.
105#[derive(Deserialize, Serialize, Debug, Clone, Copy, Eq, PartialEq)]
106pub struct Ed25519SecretKey([u8; 32]);
107
108/// Secret key used for account signatures.
109#[derive(Deserialize, Serialize, Debug, Clone, Copy, Eq, PartialEq)]
110pub enum SigningSecretKey {
111    /// secp256k1 ECDSA signing key.
112    Secp256k1(SecretKey),
113    /// EIP-191 signing key.
114    Eip191(SecretKey),
115    /// BIP-137 signing key.
116    Bip137(SecretKey),
117    /// secp256r1 signing key.
118    Secp256r1(SecretKey),
119    /// Ed25519 signing key.
120    Ed25519(Ed25519SecretKey),
121    /// BLS12-381 signing key.
122    Bls12381(SecretKey),
123}
124
125impl SignatureAlgorithm {
126    /// Stable lower-case algorithm name.
127    pub fn as_str(self) -> &'static str {
128        match self {
129            Self::Secp256k1 => "secp256k1",
130            Self::Eip191 => "eip191",
131            Self::Bip137 => "bip137",
132            Self::Secp256r1 => "secp256r1",
133            Self::Ed25519 => "ed25519",
134            Self::Bls12381 => "bls12-381",
135        }
136    }
137
138    /// Whether the public key can be recovered from a message signature.
139    pub fn is_recoverable(self) -> bool {
140        matches!(self, Self::Secp256k1 | Self::Eip191 | Self::Bip137)
141    }
142}
143
144impl VerificationPublicKey {
145    /// Signature algorithm for this public key.
146    pub fn algorithm(&self) -> SignatureAlgorithm {
147        match self {
148            Self::Secp256k1(_) => SignatureAlgorithm::Secp256k1,
149            Self::Eip191(_) => SignatureAlgorithm::Eip191,
150            Self::Bip137(_) => SignatureAlgorithm::Bip137,
151            Self::Secp256r1(_) => SignatureAlgorithm::Secp256r1,
152            Self::Ed25519(_) => SignatureAlgorithm::Ed25519,
153            Self::Bls12381(_) => SignatureAlgorithm::Bls12381,
154        }
155    }
156
157    /// Verify a signature for this explicit public key.
158    pub fn verify(&self, msg: &[u8], sig: impl AsRef<[u8]>) -> bool {
159        match self {
160            Self::Secp256k1(pk) => signers::secp256k1::verify(msg, &pk.address(), sig.as_ref()),
161            Self::Eip191(pk) => signers::eip191::verify(msg, &pk.address(), sig.as_ref()),
162            Self::Bip137(pk) => signers::bip137::verify(msg, &pk.address(), sig.as_ref()),
163            Self::Secp256r1(pk) => signers::secp256r1::verify(msg, &pk.address(), sig.as_ref(), pk),
164            Self::Ed25519(pk) => signers::ed25519::verify(msg, &pk.address(), sig.as_ref(), pk),
165            Self::Bls12381(pk) => {
166                let Ok(sig_data) = sig.as_ref().try_into() else {
167                    return false;
168                };
169                signers::bls::verify(&[msg], &signers::bls::Signature(sig_data), &[*pk])
170                    .unwrap_or(false)
171            }
172        }
173    }
174
175    /// Raw public key bytes.
176    pub fn raw_bytes(&self) -> &[u8] {
177        match self {
178            Self::Secp256k1(pk)
179            | Self::Eip191(pk)
180            | Self::Bip137(pk)
181            | Self::Secp256r1(pk)
182            | Self::Ed25519(pk) => &pk.0,
183            Self::Bls12381(pk) => &pk.0,
184        }
185    }
186
187    /// Domain-separated transcript bytes used to derive non-recoverable DIDs.
188    pub fn transcript_bytes(&self) -> Vec<u8> {
189        public_key_transcript(self.algorithm().as_str(), self.raw_bytes())
190    }
191
192    fn domain_separated_address(&self) -> PublicKeyAddress {
193        domain_separated_address(self.algorithm().as_str(), self.raw_bytes())
194    }
195
196    /// DID represented by this verification key.
197    pub fn did(&self) -> Did {
198        match self {
199            Self::Secp256k1(pk) | Self::Eip191(pk) | Self::Bip137(pk) => pk.address().into(),
200            Self::Secp256r1(_) | Self::Ed25519(_) | Self::Bls12381(_) => {
201                self.domain_separated_address().into()
202            }
203        }
204    }
205}
206
207impl AccountVerifier {
208    /// Parse a legacy session account pair into an account verifier.
209    pub fn from_account_parts(account_entity: &str, account_type: &str) -> Result<Self> {
210        match account_type {
211            "secp256k1" => Ok(Self::Recoverable {
212                algorithm: SignatureAlgorithm::Secp256k1,
213                did: account_entity.parse()?,
214            }),
215            "eip191" => Ok(Self::Recoverable {
216                algorithm: SignatureAlgorithm::Eip191,
217                did: account_entity.parse()?,
218            }),
219            "bip137" => Ok(Self::Recoverable {
220                algorithm: SignatureAlgorithm::Bip137,
221                did: account_entity.parse()?,
222            }),
223            "secp256r1" => {
224                let public_key = PublicKey::from_hex_string(account_entity)?;
225                let verifying_key = public_key.ct_try_into_secp256r1_pubkey();
226                if !bool::from(verifying_key.is_some()) || verifying_key.unwrap().is_err() {
227                    return Err(Error::InvalidPublicKey);
228                }
229                Ok(Self::PublicKey(VerificationPublicKey::Secp256r1(
230                    public_key,
231                )))
232            }
233            "ed25519" => Ok(Self::PublicKey(VerificationPublicKey::Ed25519(
234                PublicKey::try_from_b58t(account_entity)?,
235            ))),
236            "bls12-381" | "bls12381" => Ok(Self::PublicKey(VerificationPublicKey::Bls12381(
237                public_key_from_b58m_exact(account_entity)?,
238            ))),
239            _ => Err(Error::UnknownAccount),
240        }
241    }
242
243    /// Signature algorithm for this account verifier.
244    pub fn algorithm(&self) -> SignatureAlgorithm {
245        match self {
246            Self::Recoverable { algorithm, .. } => *algorithm,
247            Self::PublicKey(public_key) => public_key.algorithm(),
248        }
249    }
250
251    /// Verify an account signature.
252    pub fn verify(&self, msg: &[u8], sig: impl AsRef<[u8]>) -> bool {
253        match self {
254            Self::Recoverable {
255                algorithm: SignatureAlgorithm::Secp256k1,
256                did,
257            } => signers::secp256k1::verify(msg, &(*did).into(), sig.as_ref()),
258            Self::Recoverable {
259                algorithm: SignatureAlgorithm::Eip191,
260                did,
261            } => signers::eip191::verify(msg, &(*did).into(), sig.as_ref()),
262            Self::Recoverable {
263                algorithm: SignatureAlgorithm::Bip137,
264                did,
265            } => signers::bip137::verify(msg, &(*did).into(), sig.as_ref()),
266            Self::Recoverable { .. } => false,
267            Self::PublicKey(public_key) => public_key.verify(msg, sig.as_ref()),
268        }
269    }
270
271    /// Recover or return the explicit public verification key for this verifier.
272    pub fn verification_key_from_signature(
273        &self,
274        msg: &[u8],
275        sig: impl AsRef<[u8]>,
276    ) -> Result<VerificationPublicKey> {
277        match self {
278            Self::Recoverable {
279                algorithm: SignatureAlgorithm::Secp256k1,
280                ..
281            } => Ok(VerificationPublicKey::Secp256k1(
282                signers::secp256k1::recover(msg, sig.as_ref())?,
283            )),
284            Self::Recoverable {
285                algorithm: SignatureAlgorithm::Eip191,
286                ..
287            } => Ok(VerificationPublicKey::Eip191(signers::eip191::recover(
288                msg,
289                sig.as_ref(),
290            )?)),
291            Self::Recoverable {
292                algorithm: SignatureAlgorithm::Bip137,
293                ..
294            } => Ok(VerificationPublicKey::Bip137(signers::bip137::recover(
295                msg,
296                sig.as_ref(),
297            )?)),
298            Self::Recoverable { .. } => Err(Error::UnknownAccount),
299            Self::PublicKey(public_key) => Ok(public_key.clone()),
300        }
301    }
302}
303
304impl AccountVerifier {
305    /// DID represented by this verifier.
306    pub fn did(&self) -> Did {
307        match self {
308            Self::Recoverable { did, .. } => *did,
309            Self::PublicKey(public_key) => public_key.did(),
310        }
311    }
312}
313
314impl Ed25519SecretKey {
315    /// Generate a random Ed25519 signing seed from an explicit RNG.
316    pub fn random_with_rng(rng: &mut impl RngCore) -> Self {
317        let mut seed = [0u8; 32];
318        rng.fill_bytes(&mut seed);
319        Self(seed)
320    }
321
322    /// Generate a random Ed25519 signing seed.
323    pub fn random() -> Self {
324        with_key_rng(Self::random_with_rng)
325    }
326
327    /// Build an Ed25519 signing seed from exact bytes.
328    pub fn from_bytes(seed: [u8; 32]) -> Self {
329        Self(seed)
330    }
331
332    /// Return the raw Ed25519 signing seed.
333    pub fn to_bytes(self) -> [u8; 32] {
334        self.0
335    }
336
337    /// Borrow the raw Ed25519 signing seed.
338    pub fn as_bytes(&self) -> &[u8; 32] {
339        &self.0
340    }
341
342    /// Derive the Ed25519 public verification key.
343    pub fn public_key(&self) -> Result<PublicKey<33>> {
344        signers::ed25519::public_key(&self.0)
345    }
346
347    /// Sign raw message bytes with this Ed25519 seed.
348    pub fn sign_raw(&self, msg: &[u8]) -> Result<[u8; 64]> {
349        signers::ed25519::sign(&self.0, msg)
350    }
351}
352
353impl SigningSecretKey {
354    /// Sign raw message bytes using this key's algorithm.
355    pub fn sign_raw(&self, msg: &[u8]) -> Result<Vec<u8>> {
356        Ok(match self {
357            Self::Secp256k1(sk) => signers::secp256k1::sign_raw(*sk, msg).to_vec(),
358            Self::Eip191(sk) => signers::eip191::sign_raw(*sk, msg).to_vec(),
359            Self::Bip137(sk) => {
360                let signature = sk.sign_hash(&signers::bip137::magic_hash(msg));
361                let mut out = Vec::with_capacity(65);
362                out.push(signature[64] + 27);
363                out.extend_from_slice(&signature[..64]);
364                out
365            }
366            Self::Secp256r1(sk) => {
367                signers::secp256r1::sign(*sk, &signers::secp256r1::hash(msg))?.to_vec()
368            }
369            Self::Ed25519(sk) => sk.sign_raw(msg)?.to_vec(),
370            Self::Bls12381(sk) => signers::bls::sign(*sk, msg)?.0.to_vec(),
371        })
372    }
373
374    /// Generate an Ed25519 signing secret key.
375    pub fn random_ed25519_with_rng(rng: &mut impl RngCore) -> Self {
376        Self::Ed25519(Ed25519SecretKey::random_with_rng(rng))
377    }
378
379    /// Generate an Ed25519 signing secret key.
380    pub fn random_ed25519() -> Self {
381        Self::Ed25519(Ed25519SecretKey::random())
382    }
383
384    /// Generate a BLS12-381 signing secret key.
385    pub fn random_bls12381() -> Result<Self> {
386        signers::bls::random_sk().map(Self::Bls12381)
387    }
388
389    /// Build a BLS12-381 signing key after validating that the scalar is usable.
390    pub fn try_bls12381(secret_key: SecretKey) -> Result<Self> {
391        signers::bls::public_key(&secret_key)?;
392        Ok(Self::Bls12381(secret_key))
393    }
394}
395
396impl SigningSecretKey {
397    /// Stable lower-case algorithm name.
398    pub fn algorithm(&self) -> &'static str {
399        match self {
400            Self::Secp256k1(_) => SignatureAlgorithm::Secp256k1.as_str(),
401            Self::Eip191(_) => SignatureAlgorithm::Eip191.as_str(),
402            Self::Bip137(_) => SignatureAlgorithm::Bip137.as_str(),
403            Self::Secp256r1(_) => SignatureAlgorithm::Secp256r1.as_str(),
404            Self::Ed25519(_) => SignatureAlgorithm::Ed25519.as_str(),
405            Self::Bls12381(_) => SignatureAlgorithm::Bls12381.as_str(),
406        }
407    }
408
409    /// Public verification key corresponding to this signing secret.
410    pub fn public_key(&self) -> Result<VerificationPublicKey> {
411        Ok(match self {
412            Self::Secp256k1(sk) => VerificationPublicKey::Secp256k1(sk.pubkey()),
413            Self::Eip191(sk) => VerificationPublicKey::Eip191(sk.pubkey()),
414            Self::Bip137(sk) => VerificationPublicKey::Bip137(sk.pubkey()),
415            Self::Secp256r1(sk) => VerificationPublicKey::Secp256r1(secp256r1_public_key(*sk)?),
416            Self::Ed25519(sk) => VerificationPublicKey::Ed25519(sk.public_key()?),
417            Self::Bls12381(sk) => VerificationPublicKey::Bls12381(signers::bls::public_key(sk)?),
418        })
419    }
420}
421
422fn secp256r1_public_key(secret_key: SecretKey) -> Result<PublicKey<33>> {
423    let sk_bytes: elliptic_curve::FieldBytes<p256::NistP256> = secret_key.into();
424    let signing_key = ecdsa::SigningKey::<p256::NistP256>::from_bytes(&sk_bytes)?;
425    let encoded = signing_key.verifying_key().to_encoded_point(false);
426    let uncompressed = encoded
427        .as_bytes()
428        .get(1..)
429        .ok_or(Error::PublicKeyBadFormat)?;
430    PublicKey::from_u8(uncompressed)
431}
432
433fn with_key_rng<R>(f: impl FnOnce(&mut Hc128Rng) -> R) -> R {
434    KEY_RNG.with(|rng| {
435        let mut rng = rng.borrow_mut();
436        f(&mut rng)
437    })
438}
439
440fn public_key_from_b58m_exact<const SIZE: usize>(value: &str) -> Result<PublicKey<SIZE>> {
441    let bytes = base58_monero::decode_check(value).map_err(|_| Error::PublicKeyBadFormat)?;
442    PublicKey::from_exact_u8(&bytes)
443}
444
445#[cfg(test)]
446mod tests {
447    use rand::SeedableRng;
448    use rand_hc::Hc128Rng;
449
450    use super::*;
451
452    #[test]
453    fn recoverable_account_verifier_verifies_and_recovers_key() {
454        let secret =
455            SecretKey::try_from("65860affb4b570dba06db294aa7c676f68e04a5bf2721243ad3cbc05a79c68c0")
456                .unwrap();
457        let did: Did = secret.address().into();
458        let reference = AccountVerifier::Recoverable {
459            algorithm: SignatureAlgorithm::Secp256k1,
460            did,
461        };
462        let msg = b"session proof";
463        let sig = secret.sign_raw(msg);
464
465        assert_eq!(reference.did(), did);
466        assert!(reference.verify(msg, sig));
467        assert_eq!(
468            reference.verification_key_from_signature(msg, sig).unwrap(),
469            VerificationPublicKey::Secp256k1(secret.pubkey())
470        );
471    }
472
473    #[test]
474    fn bip137_signing_secret_signs_and_recovers_key() {
475        let secret = SigningSecretKey::Bip137(
476            SecretKey::try_from("65860affb4b570dba06db294aa7c676f68e04a5bf2721243ad3cbc05a79c68c0")
477                .unwrap(),
478        );
479        let did = secret.public_key().unwrap().did();
480        let reference = AccountVerifier::Recoverable {
481            algorithm: SignatureAlgorithm::Bip137,
482            did,
483        };
484        let msg = b"bitcoin session proof";
485        let sig = secret.sign_raw(msg).unwrap();
486
487        assert_eq!(secret.algorithm(), "bip137");
488        assert!(reference.verify(msg, &sig));
489        assert_eq!(
490            reference
491                .verification_key_from_signature(msg, &sig)
492                .unwrap(),
493            secret.public_key().unwrap()
494        );
495    }
496
497    #[test]
498    fn bls_signing_secret_signs_and_verifies_key() {
499        let secret = SigningSecretKey::random_bls12381().unwrap();
500        let public_key = secret.public_key().unwrap();
501        let did = public_key.did();
502        let reference = AccountVerifier::PublicKey(public_key.clone());
503        let msg = b"bls session proof";
504        let sig = secret.sign_raw(msg).unwrap();
505
506        assert_eq!(secret.algorithm(), "bls12-381");
507        assert_eq!(reference.did(), did);
508        assert!(reference.verify(msg, &sig));
509        assert_eq!(
510            reference
511                .verification_key_from_signature(msg, &sig)
512                .unwrap(),
513            public_key
514        );
515
516        let VerificationPublicKey::Bls12381(pk) = public_key else {
517            unreachable!("random_bls12381 returns a BLS verification key");
518        };
519        let encoded = base58_monero::encode_check(&pk.0).unwrap();
520        assert_eq!(
521            AccountVerifier::from_account_parts(&encoded, "bls12-381").unwrap(),
522            AccountVerifier::PublicKey(VerificationPublicKey::Bls12381(pk))
523        );
524    }
525
526    #[test]
527    fn ed25519_signing_secret_signs_and_verifies_key() {
528        let secret = SigningSecretKey::random_ed25519();
529        let public_key = secret.public_key().unwrap();
530        let did = public_key.did();
531        let reference = AccountVerifier::PublicKey(public_key.clone());
532        let msg = b"ed25519 session proof";
533        let sig = secret.sign_raw(msg).unwrap();
534
535        assert_eq!(secret.algorithm(), "ed25519");
536        assert_eq!(reference.did(), did);
537        assert!(reference.verify(msg, &sig));
538        assert_eq!(
539            reference
540                .verification_key_from_signature(msg, &sig)
541                .unwrap(),
542            public_key
543        );
544
545        let VerificationPublicKey::Ed25519(pk) = public_key else {
546            unreachable!("random_ed25519 returns an Ed25519 verification key");
547        };
548        assert_eq!(
549            AccountVerifier::from_account_parts(&pk.to_base58_string().unwrap(), "ed25519")
550                .unwrap(),
551            AccountVerifier::PublicKey(VerificationPublicKey::Ed25519(pk))
552        );
553    }
554
555    #[test]
556    fn ed25519_random_with_rng_is_reproducible_for_same_seed() {
557        let mut rng_a = Hc128Rng::seed_from_u64(42);
558        let mut rng_b = Hc128Rng::seed_from_u64(42);
559
560        assert_eq!(
561            SigningSecretKey::random_ed25519_with_rng(&mut rng_a),
562            SigningSecretKey::random_ed25519_with_rng(&mut rng_b)
563        );
564    }
565
566    #[test]
567    fn explicit_verification_keys_domain_separate_dids() {
568        let pk = SecretKey::random().pubkey();
569        let secp = VerificationPublicKey::Secp256k1(pk);
570        let ed = VerificationPublicKey::Ed25519(pk);
571
572        let mut expected_transcript = b"ed25519\0".to_vec();
573        expected_transcript.extend_from_slice(&pk.0);
574
575        assert_eq!(ed.transcript_bytes(), expected_transcript);
576        assert_ne!(secp.did(), ed.did());
577        assert_eq!(ed.did(), AccountVerifier::PublicKey(ed.clone()).did());
578    }
579
580    #[test]
581    fn secp256r1_secret_derives_p256_public_key() {
582        let secret = SigningSecretKey::Secp256r1(
583            SecretKey::try_from("2544acda37415a476d42312969926dc48e529867036cec71922d4177ea9c1038")
584                .unwrap(),
585        );
586        let expected = PublicKey::<33>::from_hex_string(
587            "17a6afd392fcbe4ac9270a599a9c5732c4f838ce35ea2234d389d8f0c367f3f5dcab906352e27289002c7f2c96039ddce7c1b5aad8b87ba94984d4c8b4f95702",
588        )
589        .unwrap();
590
591        assert_eq!(
592            secret.public_key().unwrap(),
593            VerificationPublicKey::Secp256r1(expected)
594        );
595    }
596}