Skip to main content

near_kit/types/
key.rs

1//! Cryptographic key types for NEAR.
2
3use std::fmt::{self, Debug, Display};
4use std::str::FromStr;
5
6use bip39::Mnemonic;
7use borsh::{BorshDeserialize, BorshSerialize};
8use ed25519_dalek::{Signer as _, SigningKey, VerifyingKey};
9use k256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
10use rand::rngs::OsRng;
11use serde_with::{DeserializeFromStr, SerializeDisplay};
12use sha2::Digest;
13
14use super::hd::{derive_ed25519_slip10, parse_hd_path};
15use crate::error::{ParseKeyError, SignerError};
16
17/// Key type identifier.
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19#[repr(u8)]
20pub enum KeyType {
21    /// Ed25519 key (most common).
22    Ed25519 = 0,
23    /// Secp256k1 key (for Ethereum compatibility).
24    Secp256k1 = 1,
25}
26
27impl KeyType {
28    /// Get the string prefix for this key type.
29    pub fn as_str(&self) -> &'static str {
30        match self {
31            KeyType::Ed25519 => "ed25519",
32            KeyType::Secp256k1 => "secp256k1",
33        }
34    }
35
36    /// Get the expected public key length in bytes.
37    pub fn public_key_len(&self) -> usize {
38        match self {
39            KeyType::Ed25519 => 32,
40            KeyType::Secp256k1 => 64, // Uncompressed, no prefix — matches nearcore
41        }
42    }
43
44    /// Get the expected signature length in bytes.
45    pub fn signature_len(&self) -> usize {
46        match self {
47            KeyType::Ed25519 => 64,
48            KeyType::Secp256k1 => 65,
49        }
50    }
51}
52
53impl TryFrom<u8> for KeyType {
54    type Error = ParseKeyError;
55
56    fn try_from(value: u8) -> Result<Self, Self::Error> {
57        match value {
58            0 => Ok(KeyType::Ed25519),
59            1 => Ok(KeyType::Secp256k1),
60            _ => Err(ParseKeyError::UnknownKeyType(value.to_string())),
61        }
62    }
63}
64
65/// Ed25519 or Secp256k1 public key.
66///
67/// Stored as fixed-size arrays matching nearcore's representation:
68/// - Ed25519: 32-byte compressed point
69/// - Secp256k1: 64-byte uncompressed point (x, y coordinates, no `0x04` prefix)
70#[derive(Clone, PartialEq, Eq, Hash, SerializeDisplay, DeserializeFromStr)]
71pub enum PublicKey {
72    /// Ed25519 public key (32 bytes).
73    Ed25519([u8; 32]),
74    /// Secp256k1 public key (64 bytes, uncompressed without prefix).
75    ///
76    /// This matches nearcore's representation: raw x,y coordinates
77    /// without the `0x04` uncompressed point prefix.
78    Secp256k1([u8; 64]),
79}
80
81impl PublicKey {
82    /// Create an Ed25519 public key from raw 32 bytes.
83    pub fn ed25519_from_bytes(bytes: [u8; 32]) -> Self {
84        Self::Ed25519(bytes)
85    }
86
87    /// Create a Secp256k1 public key from raw 64-byte uncompressed coordinates.
88    ///
89    /// The 64 bytes are the raw x,y coordinates without the `0x04` prefix,
90    /// matching nearcore's format.
91    ///
92    /// Validates that the point is on the secp256k1 curve.
93    ///
94    /// # Panics
95    ///
96    /// Panics if the bytes do not represent a valid point on the secp256k1 curve.
97    pub fn secp256k1_from_bytes(bytes: [u8; 64]) -> Self {
98        // Validate the point is on the curve by constructing full uncompressed encoding
99        let mut uncompressed = [0u8; 65];
100        uncompressed[0] = 0x04;
101        uncompressed[1..].copy_from_slice(&bytes);
102        let encoded = k256::EncodedPoint::from_bytes(uncompressed.as_ref())
103            .expect("invalid secp256k1 SEC1 encoding");
104        let point = k256::AffinePoint::from_encoded_point(&encoded);
105        assert!(bool::from(point.is_some()), "invalid secp256k1 curve point");
106
107        Self::Secp256k1(bytes)
108    }
109
110    /// Create a Secp256k1 public key from a compressed 33-byte SEC1 encoding.
111    ///
112    /// The key is validated and stored internally in uncompressed (64-byte) format
113    /// matching nearcore's representation.
114    ///
115    /// # Panics
116    ///
117    /// Panics if the bytes do not represent a valid point on the secp256k1 curve.
118    pub fn secp256k1_from_compressed(bytes: [u8; 33]) -> Self {
119        let encoded = k256::EncodedPoint::from_bytes(bytes.as_ref())
120            .expect("invalid secp256k1 SEC1 encoding");
121        let point = k256::AffinePoint::from_encoded_point(&encoded);
122        let point: k256::AffinePoint = Option::from(point).expect("invalid secp256k1 curve point");
123
124        // Re-encode as uncompressed (65 bytes with 0x04 prefix)
125        let uncompressed = point.to_encoded_point(false);
126        let uncompressed_bytes: &[u8] = uncompressed.as_bytes();
127        assert_eq!(uncompressed_bytes.len(), 65);
128        assert_eq!(uncompressed_bytes[0], 0x04);
129
130        // Store without the 0x04 prefix (64 bytes)
131        let mut result = [0u8; 64];
132        result.copy_from_slice(&uncompressed_bytes[1..]);
133        Self::Secp256k1(result)
134    }
135
136    /// Create a Secp256k1 public key from an uncompressed 65-byte SEC1 encoding
137    /// (with `0x04` prefix).
138    ///
139    /// The key is validated and stored internally without the prefix (64 bytes),
140    /// matching nearcore's format.
141    ///
142    /// # Panics
143    ///
144    /// Panics if the bytes do not represent a valid point on the secp256k1 curve.
145    pub fn secp256k1_from_uncompressed(bytes: [u8; 65]) -> Self {
146        let encoded = k256::EncodedPoint::from_bytes(bytes.as_ref())
147            .expect("invalid secp256k1 SEC1 encoding");
148        let point = k256::AffinePoint::from_encoded_point(&encoded);
149        assert!(bool::from(point.is_some()), "invalid secp256k1 curve point");
150
151        // Store without the 0x04 prefix (64 bytes)
152        let mut result = [0u8; 64];
153        result.copy_from_slice(&bytes[1..]);
154        Self::Secp256k1(result)
155    }
156
157    /// Get the key type.
158    pub fn key_type(&self) -> KeyType {
159        match self {
160            Self::Ed25519(_) => KeyType::Ed25519,
161            Self::Secp256k1(_) => KeyType::Secp256k1,
162        }
163    }
164
165    /// Get the raw key bytes as a slice.
166    pub fn as_bytes(&self) -> &[u8] {
167        match self {
168            Self::Ed25519(bytes) => bytes.as_slice(),
169            Self::Secp256k1(bytes) => bytes.as_slice(),
170        }
171    }
172
173    /// Get the key data as a fixed-size array for Ed25519 keys.
174    pub fn as_ed25519_bytes(&self) -> Option<&[u8; 32]> {
175        match self {
176            Self::Ed25519(bytes) => Some(bytes),
177            _ => None,
178        }
179    }
180
181    /// Get the key data as a fixed-size array for Secp256k1 keys
182    /// (64-byte uncompressed, no prefix).
183    pub fn as_secp256k1_bytes(&self) -> Option<&[u8; 64]> {
184        match self {
185            Self::Secp256k1(bytes) => Some(bytes),
186            _ => None,
187        }
188    }
189}
190
191impl FromStr for PublicKey {
192    type Err = ParseKeyError;
193
194    fn from_str(s: &str) -> Result<Self, Self::Err> {
195        let (key_type, data_str) = s.split_once(':').ok_or(ParseKeyError::InvalidFormat)?;
196
197        let key_type = match key_type {
198            "ed25519" => KeyType::Ed25519,
199            "secp256k1" => KeyType::Secp256k1,
200            other => return Err(ParseKeyError::UnknownKeyType(other.to_string())),
201        };
202
203        let data = bs58::decode(data_str)
204            .into_vec()
205            .map_err(|e| ParseKeyError::InvalidBase58(e.to_string()))?;
206
207        if data.len() != key_type.public_key_len() {
208            return Err(ParseKeyError::InvalidLength {
209                expected: key_type.public_key_len(),
210                actual: data.len(),
211            });
212        }
213
214        match key_type {
215            KeyType::Ed25519 => {
216                let bytes: [u8; 32] = data
217                    .as_slice()
218                    .try_into()
219                    .map_err(|_| ParseKeyError::InvalidCurvePoint)?;
220                VerifyingKey::from_bytes(&bytes).map_err(|_| ParseKeyError::InvalidCurvePoint)?;
221                Ok(Self::Ed25519(bytes))
222            }
223            KeyType::Secp256k1 => {
224                // Data is 64 bytes (uncompressed, no prefix). Add 0x04 prefix for validation.
225                let mut uncompressed = [0u8; 65];
226                uncompressed[0] = 0x04;
227                uncompressed[1..].copy_from_slice(&data);
228                let encoded = k256::EncodedPoint::from_bytes(uncompressed)
229                    .map_err(|_| ParseKeyError::InvalidCurvePoint)?;
230                let point = k256::AffinePoint::from_encoded_point(&encoded);
231                if point.is_none().into() {
232                    return Err(ParseKeyError::InvalidCurvePoint);
233                }
234                let bytes: [u8; 64] = data
235                    .as_slice()
236                    .try_into()
237                    .map_err(|_| ParseKeyError::InvalidCurvePoint)?;
238                Ok(Self::Secp256k1(bytes))
239            }
240        }
241    }
242}
243
244impl TryFrom<&str> for PublicKey {
245    type Error = ParseKeyError;
246
247    fn try_from(s: &str) -> Result<Self, Self::Error> {
248        s.parse()
249    }
250}
251
252impl Display for PublicKey {
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254        write!(
255            f,
256            "{}:{}",
257            self.key_type().as_str(),
258            bs58::encode(self.as_bytes()).into_string()
259        )
260    }
261}
262
263impl Debug for PublicKey {
264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265        write!(f, "PublicKey({})", self)
266    }
267}
268
269impl BorshSerialize for PublicKey {
270    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
271        borsh::BorshSerialize::serialize(&(self.key_type() as u8), writer)?;
272        writer.write_all(self.as_bytes())?;
273        Ok(())
274    }
275}
276
277impl BorshDeserialize for PublicKey {
278    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
279        let key_type_byte = u8::deserialize_reader(reader)?;
280        let key_type = KeyType::try_from(key_type_byte)
281            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
282
283        match key_type {
284            KeyType::Ed25519 => {
285                let mut bytes = [0u8; 32];
286                reader.read_exact(&mut bytes)?;
287                VerifyingKey::from_bytes(&bytes).map_err(|_| {
288                    std::io::Error::new(
289                        std::io::ErrorKind::InvalidData,
290                        "invalid ed25519 curve point",
291                    )
292                })?;
293                Ok(Self::Ed25519(bytes))
294            }
295            KeyType::Secp256k1 => {
296                let mut bytes = [0u8; 64];
297                reader.read_exact(&mut bytes)?;
298                // Validate point is on curve
299                let mut uncompressed = [0u8; 65];
300                uncompressed[0] = 0x04;
301                uncompressed[1..].copy_from_slice(&bytes);
302                let encoded = k256::EncodedPoint::from_bytes(uncompressed).map_err(|_| {
303                    std::io::Error::new(
304                        std::io::ErrorKind::InvalidData,
305                        "invalid secp256k1 encoding",
306                    )
307                })?;
308                let point = k256::AffinePoint::from_encoded_point(&encoded);
309                if point.is_none().into() {
310                    return Err(std::io::Error::new(
311                        std::io::ErrorKind::InvalidData,
312                        "invalid secp256k1 curve point",
313                    ));
314                }
315                Ok(Self::Secp256k1(bytes))
316            }
317        }
318    }
319}
320
321/// Default BIP-32 HD derivation path for NEAR keys.
322/// NEAR uses coin type 397 per SLIP-44.
323pub const DEFAULT_HD_PATH: &str = "m/44'/397'/0'";
324
325/// Default number of words in generated seed phrases.
326pub const DEFAULT_WORD_COUNT: usize = 12;
327
328/// Ed25519 or Secp256k1 secret key.
329#[derive(Clone, SerializeDisplay, DeserializeFromStr)]
330pub enum SecretKey {
331    /// Ed25519 secret key (32-byte seed).
332    Ed25519([u8; 32]),
333    /// Secp256k1 secret key (32-byte scalar).
334    Secp256k1([u8; 32]),
335}
336
337impl SecretKey {
338    /// Generate a new random Ed25519 key pair.
339    pub fn generate_ed25519() -> Self {
340        let signing_key = SigningKey::generate(&mut OsRng);
341        Self::Ed25519(signing_key.to_bytes())
342    }
343
344    /// Create an Ed25519 secret key from raw 32 bytes.
345    pub fn ed25519_from_bytes(bytes: [u8; 32]) -> Self {
346        Self::Ed25519(bytes)
347    }
348
349    /// Generate a new random Secp256k1 key pair.
350    pub fn generate_secp256k1() -> Self {
351        let secret_key = k256::SecretKey::random(&mut OsRng);
352        let mut bytes = [0u8; 32];
353        bytes.copy_from_slice(&secret_key.to_bytes());
354        Self::Secp256k1(bytes)
355    }
356
357    /// Create a Secp256k1 secret key from raw 32 bytes.
358    ///
359    /// Validates that the bytes represent a valid secp256k1 scalar
360    /// (non-zero and less than the curve order).
361    pub fn secp256k1_from_bytes(bytes: [u8; 32]) -> Result<Self, ParseKeyError> {
362        k256::SecretKey::from_bytes((&bytes).into()).map_err(|_| ParseKeyError::InvalidScalar)?;
363        Ok(Self::Secp256k1(bytes))
364    }
365
366    /// Get the key type.
367    pub fn key_type(&self) -> KeyType {
368        match self {
369            Self::Ed25519(_) => KeyType::Ed25519,
370            Self::Secp256k1(_) => KeyType::Secp256k1,
371        }
372    }
373
374    /// Get the raw key bytes as a slice.
375    pub fn as_bytes(&self) -> &[u8] {
376        match self {
377            Self::Ed25519(bytes) => bytes.as_slice(),
378            Self::Secp256k1(bytes) => bytes.as_slice(),
379        }
380    }
381
382    /// Derive the public key.
383    pub fn public_key(&self) -> PublicKey {
384        match self {
385            Self::Ed25519(bytes) => {
386                let signing_key = SigningKey::from_bytes(bytes);
387                let verifying_key = signing_key.verifying_key();
388                PublicKey::Ed25519(verifying_key.to_bytes())
389            }
390            Self::Secp256k1(bytes) => {
391                let secret_key =
392                    k256::SecretKey::from_bytes(bytes.into()).expect("invalid secp256k1 key");
393                let public_key = secret_key.public_key();
394                // Get uncompressed encoding (65 bytes with 0x04 prefix)
395                let uncompressed = public_key.to_encoded_point(false);
396                let uncompressed_bytes: &[u8] = uncompressed.as_bytes();
397                assert_eq!(uncompressed_bytes.len(), 65);
398                // Store without the 0x04 prefix (64 bytes)
399                let mut result = [0u8; 64];
400                result.copy_from_slice(&uncompressed_bytes[1..]);
401                PublicKey::Secp256k1(result)
402            }
403        }
404    }
405
406    /// Sign a message.
407    pub fn sign(&self, message: &[u8]) -> Signature {
408        match self {
409            Self::Ed25519(bytes) => {
410                let signing_key = SigningKey::from_bytes(bytes);
411                let signature = signing_key.sign(message);
412                Signature::Ed25519(signature.to_bytes())
413            }
414            Self::Secp256k1(bytes) => {
415                let signing_key = k256::ecdsa::SigningKey::from_bytes(bytes.into())
416                    .expect("invalid secp256k1 key");
417
418                // NEAR protocol: hash message with SHA-256 before signing
419                let hash = sha2::Sha256::digest(message);
420                let (signature, recovery_id) = signing_key
421                    .sign_prehash_recoverable(&hash)
422                    .expect("secp256k1 signing failed");
423
424                // NEAR format: [r (32) | s (32) | v (1)]
425                let mut sig_bytes = [0u8; 65];
426                sig_bytes[..64].copy_from_slice(&signature.to_bytes());
427                sig_bytes[64] = recovery_id.to_byte();
428
429                Signature::Secp256k1(sig_bytes)
430            }
431        }
432    }
433
434    // ========================================================================
435    // Seed Phrase / Mnemonic Support
436    // ========================================================================
437
438    /// Derive an Ed25519 secret key from a BIP-39 seed phrase.
439    ///
440    /// Uses SLIP-10 derivation with the default NEAR HD path (`m/44'/397'/0'`).
441    ///
442    /// # Arguments
443    ///
444    /// * `phrase` - BIP-39 mnemonic phrase (12, 15, 18, 21, or 24 words)
445    ///
446    /// # Example
447    ///
448    /// ```rust
449    /// use near_kit::SecretKey;
450    ///
451    /// // Valid BIP-39 mnemonic (all zeros entropy)
452    /// let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
453    /// let secret_key = SecretKey::from_seed_phrase(phrase).unwrap();
454    /// ```
455    pub fn from_seed_phrase(phrase: impl AsRef<str>) -> Result<Self, SignerError> {
456        Self::from_seed_phrase_with_path(phrase, DEFAULT_HD_PATH)
457    }
458
459    /// Derive an Ed25519 secret key from a BIP-39 seed phrase with custom HD path.
460    ///
461    /// Uses SLIP-10 derivation for Ed25519 keys. Only hardened derivation paths
462    /// are supported (all path components must use `'` suffix).
463    ///
464    /// # Arguments
465    ///
466    /// * `phrase` - BIP-39 mnemonic phrase (12, 15, 18, 21, or 24 words)
467    /// * `hd_path` - BIP-32 derivation path (e.g., `"m/44'/397'/0'"`)
468    ///
469    /// # Example
470    ///
471    /// ```rust
472    /// use near_kit::SecretKey;
473    ///
474    /// let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
475    /// let secret_key = SecretKey::from_seed_phrase_with_path(phrase, "m/44'/397'/1'").unwrap();
476    /// ```
477    pub fn from_seed_phrase_with_path(
478        phrase: impl AsRef<str>,
479        hd_path: impl AsRef<str>,
480    ) -> Result<Self, SignerError> {
481        Self::from_seed_phrase_with_path_and_passphrase(phrase, hd_path, None)
482    }
483
484    /// Derive an Ed25519 secret key from a BIP-39 seed phrase with passphrase.
485    ///
486    /// The passphrase provides additional entropy for seed generation (BIP-39 feature).
487    /// An empty passphrase is equivalent to no passphrase.
488    ///
489    /// # Arguments
490    ///
491    /// * `phrase` - BIP-39 mnemonic phrase
492    /// * `hd_path` - BIP-32 derivation path
493    /// * `passphrase` - Optional passphrase for additional entropy
494    ///
495    /// # Example
496    ///
497    /// ```rust
498    /// use near_kit::SecretKey;
499    ///
500    /// let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
501    /// let secret_key = SecretKey::from_seed_phrase_with_path_and_passphrase(
502    ///     phrase,
503    ///     "m/44'/397'/0'",
504    ///     Some("my-passphrase")
505    /// ).unwrap();
506    /// ```
507    pub fn from_seed_phrase_with_path_and_passphrase(
508        phrase: impl AsRef<str>,
509        hd_path: impl AsRef<str>,
510        passphrase: Option<&str>,
511    ) -> Result<Self, SignerError> {
512        // Normalize and parse mnemonic
513        let normalized = phrase
514            .as_ref()
515            .trim()
516            .to_lowercase()
517            .split_whitespace()
518            .collect::<Vec<_>>()
519            .join(" ");
520
521        let mnemonic: Mnemonic = normalized
522            .parse()
523            .map_err(|_| SignerError::InvalidSeedPhrase)?;
524
525        // Convert mnemonic to seed (64 bytes)
526        let seed = mnemonic.to_seed(passphrase.unwrap_or(""));
527
528        // Parse HD path and derive via SLIP-10 (Ed25519)
529        let path = parse_hd_path(hd_path.as_ref())
530            .map_err(|e| SignerError::KeyDerivationFailed(format!("Invalid HD path: {e}")))?;
531        let derived = derive_ed25519_slip10(&seed, &path);
532
533        Ok(Self::ed25519_from_bytes(derived))
534    }
535
536    /// Generate a new random seed phrase and derive the corresponding secret key.
537    ///
538    /// Returns both the seed phrase (for backup) and the derived secret key.
539    /// Uses 12 words by default and the standard NEAR HD path.
540    ///
541    /// # Example
542    ///
543    /// ```rust
544    /// use near_kit::SecretKey;
545    ///
546    /// let (phrase, secret_key) = SecretKey::generate_with_seed_phrase().unwrap();
547    /// println!("Backup your seed phrase: {}", phrase);
548    /// ```
549    pub fn generate_with_seed_phrase() -> Result<(String, Self), SignerError> {
550        Self::generate_with_seed_phrase_custom(DEFAULT_WORD_COUNT, DEFAULT_HD_PATH, None)
551    }
552
553    /// Generate a new random seed phrase with custom word count.
554    ///
555    /// # Arguments
556    ///
557    /// * `word_count` - Number of words (12, 15, 18, 21, or 24)
558    ///
559    /// # Example
560    ///
561    /// ```rust
562    /// use near_kit::SecretKey;
563    ///
564    /// let (phrase, secret_key) = SecretKey::generate_with_seed_phrase_words(24).unwrap();
565    /// assert_eq!(phrase.split_whitespace().count(), 24);
566    /// ```
567    pub fn generate_with_seed_phrase_words(
568        word_count: usize,
569    ) -> Result<(String, Self), SignerError> {
570        Self::generate_with_seed_phrase_custom(word_count, DEFAULT_HD_PATH, None)
571    }
572
573    /// Generate a new random seed phrase with full customization.
574    ///
575    /// # Arguments
576    ///
577    /// * `word_count` - Number of words (12, 15, 18, 21, or 24)
578    /// * `hd_path` - BIP-32 derivation path
579    /// * `passphrase` - Optional passphrase for additional entropy
580    pub fn generate_with_seed_phrase_custom(
581        word_count: usize,
582        hd_path: impl AsRef<str>,
583        passphrase: Option<&str>,
584    ) -> Result<(String, Self), SignerError> {
585        let phrase = generate_seed_phrase(word_count)?;
586        let secret_key =
587            Self::from_seed_phrase_with_path_and_passphrase(&phrase, hd_path, passphrase)?;
588        Ok((phrase, secret_key))
589    }
590}
591
592impl FromStr for SecretKey {
593    type Err = ParseKeyError;
594
595    fn from_str(s: &str) -> Result<Self, Self::Err> {
596        let (key_type, data_str) = s.split_once(':').ok_or(ParseKeyError::InvalidFormat)?;
597
598        let key_type = match key_type {
599            "ed25519" => KeyType::Ed25519,
600            "secp256k1" => KeyType::Secp256k1,
601            other => return Err(ParseKeyError::UnknownKeyType(other.to_string())),
602        };
603
604        let data = bs58::decode(data_str)
605            .into_vec()
606            .map_err(|e| ParseKeyError::InvalidBase58(e.to_string()))?;
607
608        // For ed25519, the secret key might be 32 bytes (seed) or 64 bytes (expanded)
609        // For secp256k1, it must be 32 bytes
610        let valid_len = match key_type {
611            KeyType::Ed25519 => data.len() == 32 || data.len() == 64,
612            KeyType::Secp256k1 => data.len() == 32,
613        };
614        if !valid_len {
615            return Err(ParseKeyError::InvalidLength {
616                expected: 32,
617                actual: data.len(),
618            });
619        }
620
621        // Take first 32 bytes if 64-byte expanded key
622        let bytes: [u8; 32] = data[..32]
623            .try_into()
624            .map_err(|_| ParseKeyError::InvalidFormat)?;
625
626        match key_type {
627            KeyType::Ed25519 => Ok(Self::Ed25519(bytes)),
628            KeyType::Secp256k1 => {
629                // Validate secp256k1 scalar (non-zero and < curve order)
630                k256::SecretKey::from_bytes((&bytes).into())
631                    .map_err(|_| ParseKeyError::InvalidScalar)?;
632                Ok(Self::Secp256k1(bytes))
633            }
634        }
635    }
636}
637
638impl TryFrom<&str> for SecretKey {
639    type Error = ParseKeyError;
640
641    fn try_from(s: &str) -> Result<Self, Self::Error> {
642        s.parse()
643    }
644}
645
646impl Display for SecretKey {
647    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
648        write!(
649            f,
650            "{}:{}",
651            self.key_type().as_str(),
652            bs58::encode(self.as_bytes()).into_string()
653        )
654    }
655}
656
657impl Debug for SecretKey {
658    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
659        write!(f, "SecretKey({}:***)", self.key_type().as_str())
660    }
661}
662
663/// Cryptographic signature.
664#[derive(Clone, PartialEq, Eq, SerializeDisplay, DeserializeFromStr)]
665pub enum Signature {
666    /// Ed25519 signature (64 bytes).
667    Ed25519([u8; 64]),
668    /// Secp256k1 signature (65 bytes: `[r (32) | s (32) | v (1)]`).
669    Secp256k1([u8; 65]),
670}
671
672impl Signature {
673    /// Create an Ed25519 signature from raw 64 bytes.
674    pub fn ed25519_from_bytes(bytes: [u8; 64]) -> Self {
675        Self::Ed25519(bytes)
676    }
677
678    /// Create a Secp256k1 signature from raw 65 bytes.
679    ///
680    /// The format is `[r (32 bytes) | s (32 bytes) | v (1 byte recovery id)]`,
681    /// matching the NEAR protocol's secp256k1 signature format.
682    pub fn secp256k1_from_bytes(bytes: [u8; 65]) -> Self {
683        Self::Secp256k1(bytes)
684    }
685
686    /// Get the key type.
687    pub fn key_type(&self) -> KeyType {
688        match self {
689            Self::Ed25519(_) => KeyType::Ed25519,
690            Self::Secp256k1(_) => KeyType::Secp256k1,
691        }
692    }
693
694    /// Get the raw signature bytes.
695    pub fn as_bytes(&self) -> &[u8] {
696        match self {
697            Self::Ed25519(bytes) => bytes.as_slice(),
698            Self::Secp256k1(bytes) => bytes.as_slice(),
699        }
700    }
701
702    /// Verify this signature against a message and public key.
703    pub fn verify(&self, message: &[u8], public_key: &PublicKey) -> bool {
704        match (self, public_key) {
705            (Self::Ed25519(sig_bytes), PublicKey::Ed25519(pk_bytes)) => {
706                let Ok(verifying_key) = VerifyingKey::from_bytes(pk_bytes) else {
707                    return false;
708                };
709                let signature = ed25519_dalek::Signature::from_bytes(sig_bytes);
710                verifying_key.verify_strict(message, &signature).is_ok()
711            }
712            (Self::Secp256k1(sig_bytes), PublicKey::Secp256k1(pk_bytes)) => {
713                // Reconstruct the 65-byte uncompressed key with 0x04 prefix for verification
714                let mut uncompressed = [0u8; 65];
715                uncompressed[0] = 0x04;
716                uncompressed[1..].copy_from_slice(pk_bytes);
717                let Ok(verifying_key) = k256::ecdsa::VerifyingKey::from_sec1_bytes(&uncompressed)
718                else {
719                    return false;
720                };
721
722                // Validate recovery id byte is in expected range (0..=3)
723                let v = sig_bytes[64];
724                if v > 3 {
725                    return false;
726                }
727
728                let Ok(signature) = k256::ecdsa::Signature::from_bytes((&sig_bytes[..64]).into())
729                else {
730                    return false;
731                };
732
733                // NEAR protocol: verify against SHA-256 hash of the message
734                let hash = sha2::Sha256::digest(message);
735                use k256::ecdsa::signature::hazmat::PrehashVerifier;
736                verifying_key.verify_prehash(&hash, &signature).is_ok()
737            }
738            // Mismatched key types
739            _ => false,
740        }
741    }
742}
743
744impl FromStr for Signature {
745    type Err = ParseKeyError;
746
747    fn from_str(s: &str) -> Result<Self, Self::Err> {
748        let (key_type, data_str) = s.split_once(':').ok_or(ParseKeyError::InvalidFormat)?;
749
750        let key_type = match key_type {
751            "ed25519" => KeyType::Ed25519,
752            "secp256k1" => KeyType::Secp256k1,
753            other => return Err(ParseKeyError::UnknownKeyType(other.to_string())),
754        };
755
756        let data = bs58::decode(data_str)
757            .into_vec()
758            .map_err(|e| ParseKeyError::InvalidBase58(e.to_string()))?;
759
760        if data.len() != key_type.signature_len() {
761            return Err(ParseKeyError::InvalidLength {
762                expected: key_type.signature_len(),
763                actual: data.len(),
764            });
765        }
766
767        match key_type {
768            KeyType::Ed25519 => {
769                let bytes: [u8; 64] = data
770                    .as_slice()
771                    .try_into()
772                    .map_err(|_| ParseKeyError::InvalidFormat)?;
773                Ok(Self::Ed25519(bytes))
774            }
775            KeyType::Secp256k1 => {
776                let bytes: [u8; 65] = data
777                    .as_slice()
778                    .try_into()
779                    .map_err(|_| ParseKeyError::InvalidFormat)?;
780                Ok(Self::Secp256k1(bytes))
781            }
782        }
783    }
784}
785
786impl Display for Signature {
787    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788        write!(
789            f,
790            "{}:{}",
791            self.key_type().as_str(),
792            bs58::encode(self.as_bytes()).into_string()
793        )
794    }
795}
796
797impl Debug for Signature {
798    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799        write!(f, "Signature({})", self)
800    }
801}
802
803impl BorshSerialize for Signature {
804    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
805        borsh::BorshSerialize::serialize(&(self.key_type() as u8), writer)?;
806        writer.write_all(self.as_bytes())?;
807        Ok(())
808    }
809}
810
811impl BorshDeserialize for Signature {
812    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
813        let key_type_byte = u8::deserialize_reader(reader)?;
814        let key_type = KeyType::try_from(key_type_byte)
815            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
816
817        match key_type {
818            KeyType::Ed25519 => {
819                let mut bytes = [0u8; 64];
820                reader.read_exact(&mut bytes)?;
821                Ok(Self::Ed25519(bytes))
822            }
823            KeyType::Secp256k1 => {
824                let mut bytes = [0u8; 65];
825                reader.read_exact(&mut bytes)?;
826                Ok(Self::Secp256k1(bytes))
827            }
828        }
829    }
830}
831
832// ============================================================================
833// Seed Phrase Generation
834// ============================================================================
835
836/// Generate a random BIP-39 seed phrase.
837///
838/// # Arguments
839///
840/// * `word_count` - Number of words (12, 15, 18, 21, or 24)
841///
842/// # Example
843///
844/// ```rust
845/// use near_kit::generate_seed_phrase;
846///
847/// let phrase = generate_seed_phrase(12).unwrap();
848/// assert_eq!(phrase.split_whitespace().count(), 12);
849/// ```
850pub fn generate_seed_phrase(word_count: usize) -> Result<String, SignerError> {
851    use rand::RngCore;
852
853    // Word count to entropy bytes: 12->16, 15->20, 18->24, 21->28, 24->32
854    let entropy_bytes = match word_count {
855        12 => 16,
856        15 => 20,
857        18 => 24,
858        21 => 28,
859        24 => 32,
860        _ => {
861            return Err(SignerError::KeyDerivationFailed(format!(
862                "Invalid word count: {}. Must be 12, 15, 18, 21, or 24",
863                word_count
864            )));
865        }
866    };
867
868    let mut entropy = vec![0u8; entropy_bytes];
869    OsRng.fill_bytes(&mut entropy);
870
871    let mnemonic = Mnemonic::from_entropy(&entropy).map_err(|e| {
872        SignerError::KeyDerivationFailed(format!("Failed to generate mnemonic: {}", e))
873    })?;
874
875    Ok(mnemonic.to_string())
876}
877
878// ============================================================================
879// KeyPair
880// ============================================================================
881
882/// A cryptographic key pair (secret key + public key).
883///
884/// This is a convenience type that bundles a [`SecretKey`] with its derived
885/// [`PublicKey`] for situations where you need both (e.g., creating accounts,
886/// adding access keys).
887///
888/// # Example
889///
890/// ```rust
891/// use near_kit::KeyPair;
892///
893/// // Generate a random Ed25519 key pair
894/// let keypair = KeyPair::random();
895/// println!("Public key: {}", keypair.public_key);
896/// println!("Secret key: {}", keypair.secret_key);
897///
898/// // Use with account creation
899/// // near.transaction("new.alice.testnet")
900/// //     .create_account()
901/// //     .add_full_access_key(keypair.public_key)
902/// //     .send()
903/// //     .await?;
904/// ```
905#[derive(Clone)]
906pub struct KeyPair {
907    /// The secret (private) key.
908    pub secret_key: SecretKey,
909    /// The public key derived from the secret key.
910    pub public_key: PublicKey,
911}
912
913impl KeyPair {
914    /// Generate a random Ed25519 key pair.
915    ///
916    /// This is the most common key type for NEAR.
917    ///
918    /// # Example
919    ///
920    /// ```rust
921    /// use near_kit::KeyPair;
922    ///
923    /// let keypair = KeyPair::random();
924    /// ```
925    pub fn random() -> Self {
926        Self::random_ed25519()
927    }
928
929    /// Generate a random Ed25519 key pair.
930    ///
931    /// # Example
932    ///
933    /// ```rust
934    /// use near_kit::KeyPair;
935    ///
936    /// let keypair = KeyPair::random_ed25519();
937    /// assert!(keypair.public_key.to_string().starts_with("ed25519:"));
938    /// ```
939    pub fn random_ed25519() -> Self {
940        let secret_key = SecretKey::generate_ed25519();
941        let public_key = secret_key.public_key();
942        Self {
943            secret_key,
944            public_key,
945        }
946    }
947
948    /// Generate a random Secp256k1 key pair.
949    ///
950    /// # Example
951    ///
952    /// ```rust
953    /// use near_kit::KeyPair;
954    ///
955    /// let keypair = KeyPair::random_secp256k1();
956    /// assert!(keypair.public_key.to_string().starts_with("secp256k1:"));
957    /// ```
958    pub fn random_secp256k1() -> Self {
959        let secret_key = SecretKey::generate_secp256k1();
960        let public_key = secret_key.public_key();
961        Self {
962            secret_key,
963            public_key,
964        }
965    }
966
967    /// Create a key pair from an existing secret key.
968    ///
969    /// # Example
970    ///
971    /// ```rust
972    /// use near_kit::{KeyPair, SecretKey};
973    ///
974    /// let secret_key: SecretKey = "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr".parse().unwrap();
975    /// let keypair = KeyPair::from_secret_key(secret_key);
976    /// ```
977    pub fn from_secret_key(secret_key: SecretKey) -> Self {
978        let public_key = secret_key.public_key();
979        Self {
980            secret_key,
981            public_key,
982        }
983    }
984
985    /// Create a key pair from a seed phrase using the default NEAR HD path.
986    ///
987    /// # Example
988    ///
989    /// ```rust
990    /// use near_kit::KeyPair;
991    ///
992    /// let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
993    /// let keypair = KeyPair::from_seed_phrase(phrase).unwrap();
994    /// ```
995    pub fn from_seed_phrase(phrase: impl AsRef<str>) -> Result<Self, SignerError> {
996        let secret_key = SecretKey::from_seed_phrase(phrase)?;
997        Ok(Self::from_secret_key(secret_key))
998    }
999
1000    /// Generate a new random key pair with a seed phrase for backup.
1001    ///
1002    /// Returns the seed phrase (for backup) and the key pair.
1003    ///
1004    /// # Example
1005    ///
1006    /// ```rust
1007    /// use near_kit::KeyPair;
1008    ///
1009    /// let (phrase, keypair) = KeyPair::random_with_seed_phrase().unwrap();
1010    /// println!("Backup your seed phrase: {}", phrase);
1011    /// println!("Public key: {}", keypair.public_key);
1012    /// ```
1013    pub fn random_with_seed_phrase() -> Result<(String, Self), SignerError> {
1014        let (phrase, secret_key) = SecretKey::generate_with_seed_phrase()?;
1015        Ok((phrase, Self::from_secret_key(secret_key)))
1016    }
1017}
1018
1019impl std::fmt::Debug for KeyPair {
1020    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1021        f.debug_struct("KeyPair")
1022            .field("public_key", &self.public_key)
1023            .field("secret_key", &"***")
1024            .finish()
1025    }
1026}
1027
1028#[cfg(test)]
1029mod tests {
1030    use super::*;
1031
1032    #[test]
1033    fn test_generate_and_sign() {
1034        let secret = SecretKey::generate_ed25519();
1035        let public = secret.public_key();
1036        let message = b"hello world";
1037
1038        let signature = secret.sign(message);
1039        assert!(signature.verify(message, &public));
1040        assert!(!signature.verify(b"wrong message", &public));
1041    }
1042
1043    #[test]
1044    fn test_public_key_roundtrip() {
1045        let secret = SecretKey::generate_ed25519();
1046        let public = secret.public_key();
1047        let s = public.to_string();
1048        let parsed: PublicKey = s.parse().unwrap();
1049        assert_eq!(public, parsed);
1050    }
1051
1052    #[test]
1053    fn test_secret_key_roundtrip() {
1054        let secret = SecretKey::generate_ed25519();
1055        let s = secret.to_string();
1056        let parsed: SecretKey = s.parse().unwrap();
1057        assert_eq!(secret.public_key(), parsed.public_key());
1058    }
1059
1060    #[test]
1061    fn test_ed25519_secret_key_64_byte_expanded_form() {
1062        // Generate a key, get its 32-byte seed, then construct a 64-byte expanded form
1063        // (seed || public_key) which near-cli sometimes stores
1064        let secret = SecretKey::generate_ed25519();
1065        let public = secret.public_key();
1066        let seed_bytes = secret.as_bytes();
1067
1068        // Construct 64-byte expanded key: seed (32) + public key bytes (32)
1069        let mut expanded = Vec::with_capacity(64);
1070        expanded.extend_from_slice(seed_bytes);
1071        expanded.extend_from_slice(public.as_bytes());
1072        let expanded_b58 = bs58::encode(&expanded).into_string();
1073        let expanded_str = format!("ed25519:{}", expanded_b58);
1074
1075        // Parse the 64-byte form — should succeed and produce same public key
1076        let parsed: SecretKey = expanded_str.parse().unwrap();
1077        assert_eq!(parsed.public_key(), public);
1078
1079        // Re-serializing yields the 32-byte seed form
1080        let reserialized = parsed.to_string();
1081        assert_eq!(reserialized, secret.to_string());
1082    }
1083
1084    // ========================================================================
1085    // Seed Phrase Tests
1086    // ========================================================================
1087
1088    #[test]
1089    fn test_generate_seed_phrase_12_words() {
1090        let phrase = generate_seed_phrase(12).unwrap();
1091        assert_eq!(phrase.split_whitespace().count(), 12);
1092    }
1093
1094    #[test]
1095    fn test_generate_seed_phrase_24_words() {
1096        let phrase = generate_seed_phrase(24).unwrap();
1097        assert_eq!(phrase.split_whitespace().count(), 24);
1098    }
1099
1100    #[test]
1101    fn test_generate_seed_phrase_invalid_word_count() {
1102        let result = generate_seed_phrase(13);
1103        assert!(result.is_err());
1104    }
1105
1106    // Valid BIP-39 test vector (from official test vectors)
1107    const TEST_PHRASE: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
1108
1109    #[test]
1110    fn test_from_seed_phrase_known_vector() {
1111        // Test vector: known seed phrase should produce consistent key
1112        let secret_key = SecretKey::from_seed_phrase(TEST_PHRASE).unwrap();
1113
1114        // Same phrase should always produce the same key
1115        let secret_key2 = SecretKey::from_seed_phrase(TEST_PHRASE).unwrap();
1116        assert_eq!(secret_key.public_key(), secret_key2.public_key());
1117    }
1118
1119    #[test]
1120    fn test_from_seed_phrase_whitespace_normalization() {
1121        let phrase1 = TEST_PHRASE;
1122        let phrase2 = "  abandon   abandon  abandon abandon abandon abandon abandon abandon abandon abandon abandon about  ";
1123        let phrase3 = "ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABOUT";
1124
1125        let key1 = SecretKey::from_seed_phrase(phrase1).unwrap();
1126        let key2 = SecretKey::from_seed_phrase(phrase2).unwrap();
1127        let key3 = SecretKey::from_seed_phrase(phrase3).unwrap();
1128
1129        assert_eq!(key1.public_key(), key2.public_key());
1130        assert_eq!(key1.public_key(), key3.public_key());
1131    }
1132
1133    #[test]
1134    fn test_from_seed_phrase_invalid() {
1135        let result = SecretKey::from_seed_phrase("invalid words that are not a mnemonic");
1136        assert!(result.is_err());
1137    }
1138
1139    #[test]
1140    fn test_from_seed_phrase_different_paths() {
1141        let key1 = SecretKey::from_seed_phrase_with_path(TEST_PHRASE, "m/44'/397'/0'").unwrap();
1142        let key2 = SecretKey::from_seed_phrase_with_path(TEST_PHRASE, "m/44'/397'/1'").unwrap();
1143
1144        // Different paths should produce different keys
1145        assert_ne!(key1.public_key(), key2.public_key());
1146    }
1147
1148    #[test]
1149    fn test_from_seed_phrase_with_passphrase() {
1150        let key_no_pass = SecretKey::from_seed_phrase_with_path_and_passphrase(
1151            TEST_PHRASE,
1152            DEFAULT_HD_PATH,
1153            None,
1154        )
1155        .unwrap();
1156
1157        let key_with_pass = SecretKey::from_seed_phrase_with_path_and_passphrase(
1158            TEST_PHRASE,
1159            DEFAULT_HD_PATH,
1160            Some("my-password"),
1161        )
1162        .unwrap();
1163
1164        // Passphrase should produce different key
1165        assert_ne!(key_no_pass.public_key(), key_with_pass.public_key());
1166    }
1167
1168    #[test]
1169    fn test_generate_with_seed_phrase() {
1170        let (phrase, secret_key) = SecretKey::generate_with_seed_phrase().unwrap();
1171
1172        // Phrase should be 12 words
1173        assert_eq!(phrase.split_whitespace().count(), 12);
1174
1175        // Re-deriving from the phrase should produce the same key
1176        let derived = SecretKey::from_seed_phrase(&phrase).unwrap();
1177        assert_eq!(secret_key.public_key(), derived.public_key());
1178    }
1179
1180    #[test]
1181    fn test_generate_with_seed_phrase_24_words() {
1182        let (phrase, secret_key) = SecretKey::generate_with_seed_phrase_words(24).unwrap();
1183
1184        assert_eq!(phrase.split_whitespace().count(), 24);
1185
1186        let derived = SecretKey::from_seed_phrase(&phrase).unwrap();
1187        assert_eq!(secret_key.public_key(), derived.public_key());
1188    }
1189
1190    #[test]
1191    fn test_seed_phrase_key_can_sign() {
1192        let secret_key = SecretKey::from_seed_phrase(TEST_PHRASE).unwrap();
1193
1194        let message = b"test message";
1195        let signature = secret_key.sign(message);
1196        let public_key = secret_key.public_key();
1197
1198        assert!(signature.verify(message, &public_key));
1199    }
1200
1201    // ========================================================================
1202    // Curve Point Validation Tests
1203    // ========================================================================
1204
1205    #[test]
1206    fn test_secp256k1_invalid_curve_point_rejected() {
1207        // This is the invalid key from the NEAR SDK docs that was identified as not being
1208        // on the secp256k1 curve. See: https://github.com/near/near-sdk-rs/pull/1469
1209        let invalid_key = "secp256k1:qMoRgcoXai4mBPsdbHi1wfyxF9TdbPCF4qSDQTRP3TfescSRoUdSx6nmeQoN3aiwGzwMyGXAb1gUjBTv5AY8DXj";
1210        let result: Result<PublicKey, _> = invalid_key.parse();
1211        assert!(result.is_err());
1212        assert!(matches!(
1213            result.unwrap_err(),
1214            ParseKeyError::InvalidCurvePoint | ParseKeyError::InvalidLength { .. }
1215        ));
1216    }
1217
1218    #[test]
1219    fn test_secp256k1_valid_curve_point_accepted() {
1220        // Valid secp256k1 key from near-sdk-js (verified to be on the curve)
1221        // This key is 64 bytes (uncompressed without prefix) — matches our new format
1222        let valid_key = "secp256k1:5r22SrjrDvgY3wdQsnjgxkeAbU1VcM71FYvALEQWihjM3Xk4Be1CpETTqFccChQr4iJwDroSDVmgaWZv2AcXvYeL";
1223        let result: Result<PublicKey, _> = valid_key.parse();
1224        // This key is now parseable because we expect 64-byte uncompressed format
1225        assert!(result.is_ok());
1226    }
1227
1228    #[test]
1229    fn test_ed25519_valid_key_accepted() {
1230        // Valid ed25519 public key
1231        let valid_key = "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp";
1232        let result: Result<PublicKey, _> = valid_key.parse();
1233        assert!(result.is_ok());
1234    }
1235
1236    #[test]
1237    fn test_ed25519_invalid_curve_point_rejected() {
1238        // The high bit of the last byte being set with an invalid x-coordinate recovery
1239        // should produce an invalid point. Specifically, a y-coordinate that when
1240        // the x is computed results in a non-square (no valid x exists).
1241        // This specific byte sequence has been verified to fail ed25519 decompression.
1242        //
1243        // Note: ed25519_dalek may accept many byte patterns as valid curve points.
1244        // Ed25519 point decompression is very permissive - most 32-byte sequences
1245        // decode to valid points.
1246        let invalid_bytes = [
1247            0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1248            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1249            0xFF, 0xFF, 0xFF, 0x7F,
1250        ];
1251        let encoded = bs58::encode(&invalid_bytes).into_string();
1252        let invalid_key = format!("ed25519:{}", encoded);
1253        let result: Result<PublicKey, _> = invalid_key.parse();
1254        if let Err(err) = result {
1255            assert!(matches!(err, ParseKeyError::InvalidCurvePoint));
1256        } else {
1257            // If ed25519_dalek accepts this, we should skip this test case
1258            eprintln!(
1259                "Note: ed25519 point decompression accepted test bytes - validation may be too lenient"
1260            );
1261        }
1262    }
1263
1264    #[test]
1265    fn test_borsh_deserialize_validates_curve_point() {
1266        use borsh::BorshDeserialize;
1267
1268        // Test with secp256k1 since ed25519 validation is very lenient
1269        // Use invalid secp256k1 bytes (all zeros is definitely not on the curve)
1270        let mut invalid_bytes = vec![1u8]; // KeyType::Secp256k1
1271        invalid_bytes.extend_from_slice(&[0u8; 64]); // Invalid curve point (64 bytes now)
1272
1273        let result = PublicKey::try_from_slice(&invalid_bytes);
1274        assert!(result.is_err());
1275    }
1276
1277    #[test]
1278    fn test_signature_from_str_roundtrip() {
1279        let sig_str = "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5";
1280        let sig: Signature = sig_str.parse().unwrap();
1281        assert_eq!(sig.key_type(), KeyType::Ed25519);
1282        assert_eq!(sig.as_bytes().len(), 64);
1283        assert_eq!(sig.to_string(), sig_str);
1284    }
1285
1286    #[test]
1287    fn test_signature_from_str_invalid_format() {
1288        assert!("no_colon".parse::<Signature>().is_err());
1289        assert!("unknown:abc".parse::<Signature>().is_err());
1290        assert!("ed25519:invalid!!!".parse::<Signature>().is_err());
1291        assert!("ed25519:AAAA".parse::<Signature>().is_err()); // too short
1292    }
1293
1294    #[test]
1295    fn test_signature_serde_roundtrip() {
1296        let sig_str = "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5";
1297        let sig: Signature = sig_str.parse().unwrap();
1298        let json = serde_json::to_value(&sig).unwrap();
1299        assert_eq!(json.as_str().unwrap(), sig_str);
1300        let parsed: Signature = serde_json::from_value(json).unwrap();
1301        assert_eq!(sig, parsed);
1302    }
1303
1304    // ========================================================================
1305    // Secp256k1 Tests
1306    // ========================================================================
1307
1308    #[test]
1309    fn test_secp256k1_generate_and_sign_verify() {
1310        let secret = SecretKey::generate_secp256k1();
1311        let public = secret.public_key();
1312        let message = b"hello world";
1313
1314        assert_eq!(secret.key_type(), KeyType::Secp256k1);
1315        assert_eq!(public.key_type(), KeyType::Secp256k1);
1316
1317        let signature = secret.sign(message);
1318        assert_eq!(signature.key_type(), KeyType::Secp256k1);
1319        assert_eq!(signature.as_bytes().len(), 65);
1320
1321        assert!(signature.verify(message, &public));
1322        assert!(!signature.verify(b"wrong message", &public));
1323    }
1324
1325    #[test]
1326    fn test_secp256k1_public_key_is_64_bytes() {
1327        let secret = SecretKey::generate_secp256k1();
1328        let public = secret.public_key();
1329
1330        // Public key should be 64 bytes (uncompressed without prefix)
1331        let pk_bytes = public.as_secp256k1_bytes().unwrap();
1332        assert_eq!(pk_bytes.len(), 64);
1333    }
1334
1335    #[test]
1336    fn test_secp256k1_secret_key_to_public_key_derivation() {
1337        // Deterministic: same secret key bytes should always produce the same public key
1338        let bytes = [42u8; 32];
1339        let sk1 = SecretKey::secp256k1_from_bytes(bytes).unwrap();
1340        let sk2 = SecretKey::secp256k1_from_bytes(bytes).unwrap();
1341        assert_eq!(sk1.public_key(), sk2.public_key());
1342
1343        // Different secret keys produce different public keys
1344        let bytes2 = [43u8; 32];
1345        let sk3 = SecretKey::secp256k1_from_bytes(bytes2).unwrap();
1346        assert_ne!(sk1.public_key(), sk3.public_key());
1347    }
1348
1349    #[test]
1350    fn test_secp256k1_public_key_string_roundtrip() {
1351        let secret = SecretKey::generate_secp256k1();
1352        let public = secret.public_key();
1353        let s = public.to_string();
1354        assert!(s.starts_with("secp256k1:"));
1355        let parsed: PublicKey = s.parse().unwrap();
1356        assert_eq!(public, parsed);
1357    }
1358
1359    #[test]
1360    fn test_secp256k1_secret_key_string_roundtrip() {
1361        let secret = SecretKey::generate_secp256k1();
1362        let s = secret.to_string();
1363        assert!(s.starts_with("secp256k1:"));
1364        let parsed: SecretKey = s.parse().unwrap();
1365        assert_eq!(secret.public_key(), parsed.public_key());
1366    }
1367
1368    #[test]
1369    fn test_secp256k1_keypair_random() {
1370        let keypair = KeyPair::random_secp256k1();
1371        assert_eq!(keypair.public_key.key_type(), KeyType::Secp256k1);
1372        assert_eq!(keypair.secret_key.key_type(), KeyType::Secp256k1);
1373        assert!(keypair.public_key.to_string().starts_with("secp256k1:"));
1374
1375        // Sign/verify through keypair
1376        let message = b"keypair test";
1377        let signature = keypair.secret_key.sign(message);
1378        assert!(signature.verify(message, &keypair.public_key));
1379    }
1380
1381    #[test]
1382    fn test_secp256k1_cross_type_verify_fails() {
1383        // Ed25519 signature should not verify against secp256k1 key
1384        let ed_secret = SecretKey::generate_ed25519();
1385        let secp_secret = SecretKey::generate_secp256k1();
1386
1387        let message = b"cross type test";
1388        let ed_sig = ed_secret.sign(message);
1389        let secp_sig = secp_secret.sign(message);
1390
1391        assert!(!ed_sig.verify(message, &secp_secret.public_key()));
1392        assert!(!secp_sig.verify(message, &ed_secret.public_key()));
1393    }
1394
1395    #[test]
1396    fn test_secp256k1_invalid_scalar_rejected() {
1397        // Zero scalar is invalid for secp256k1
1398        let zero_bytes = [0u8; 32];
1399        let result = SecretKey::secp256k1_from_bytes(zero_bytes);
1400        assert!(result.is_err());
1401        assert!(matches!(result.unwrap_err(), ParseKeyError::InvalidScalar));
1402    }
1403
1404    #[test]
1405    fn test_secp256k1_invalid_scalar_rejected_from_str() {
1406        // Construct a secp256k1 secret key string with zero bytes (invalid scalar)
1407        let zero_bytes = [0u8; 32];
1408        let encoded = bs58::encode(&zero_bytes).into_string();
1409        let key_str = format!("secp256k1:{}", encoded);
1410        let result: Result<SecretKey, _> = key_str.parse();
1411        assert!(result.is_err());
1412        assert!(matches!(result.unwrap_err(), ParseKeyError::InvalidScalar));
1413    }
1414
1415    #[test]
1416    fn test_secp256k1_invalid_recovery_id_rejected() {
1417        let secret = SecretKey::generate_secp256k1();
1418        let public = secret.public_key();
1419        let message = b"test message";
1420        let signature = secret.sign(message);
1421
1422        // Create a tampered signature with invalid recovery id
1423        if let Signature::Secp256k1(mut sig_bytes) = signature.clone() {
1424            sig_bytes[64] = 4; // valid range is 0..=3
1425            let tampered = Signature::Secp256k1(sig_bytes);
1426            assert!(!tampered.verify(message, &public));
1427
1428            sig_bytes[64] = 255;
1429            let tampered = Signature::Secp256k1(sig_bytes);
1430            assert!(!tampered.verify(message, &public));
1431        } else {
1432            panic!("Expected Secp256k1 signature");
1433        }
1434    }
1435
1436    #[test]
1437    fn test_secp256k1_from_compressed() {
1438        let secret = SecretKey::generate_secp256k1();
1439        let public = secret.public_key();
1440
1441        // Get the uncompressed bytes and create a compressed version
1442        let pk_bytes = public.as_secp256k1_bytes().unwrap();
1443        let mut uncompressed = [0u8; 65];
1444        uncompressed[0] = 0x04;
1445        uncompressed[1..].copy_from_slice(pk_bytes);
1446        let encoded =
1447            k256::EncodedPoint::from_bytes(uncompressed.as_ref()).expect("valid encoded point");
1448        let point = k256::AffinePoint::from_encoded_point(&encoded).expect("valid point on curve");
1449        let compressed = point.to_encoded_point(true);
1450        let compressed_bytes: [u8; 33] = compressed
1451            .as_bytes()
1452            .try_into()
1453            .expect("compressed should be 33 bytes");
1454
1455        // Reconstruct from compressed form
1456        let public2 = PublicKey::secp256k1_from_compressed(compressed_bytes);
1457        assert_eq!(public, public2);
1458    }
1459
1460    #[test]
1461    fn test_ed25519_borsh_roundtrip() {
1462        use borsh::BorshDeserialize;
1463
1464        let secret = SecretKey::generate_ed25519();
1465        let public = secret.public_key();
1466        let serialized = borsh::to_vec(&public).unwrap();
1467        // Ed25519: 1 byte key type + 32 bytes key data
1468        assert_eq!(serialized.len(), 33);
1469        assert_eq!(serialized[0], 0); // Ed25519
1470        let deserialized = PublicKey::try_from_slice(&serialized).unwrap();
1471        assert_eq!(public, deserialized);
1472    }
1473
1474    #[test]
1475    fn test_secp256k1_borsh_roundtrip() {
1476        use borsh::BorshDeserialize;
1477
1478        let secret = SecretKey::generate_secp256k1();
1479        let public = secret.public_key();
1480        let serialized = borsh::to_vec(&public).unwrap();
1481        // Secp256k1: 1 byte key type + 64 bytes key data
1482        assert_eq!(serialized.len(), 65);
1483        assert_eq!(serialized[0], 1); // Secp256k1
1484        let deserialized = PublicKey::try_from_slice(&serialized).unwrap();
1485        assert_eq!(public, deserialized);
1486    }
1487
1488    #[test]
1489    fn test_signature_borsh_roundtrip() {
1490        use borsh::BorshDeserialize;
1491
1492        let secret = SecretKey::generate_ed25519();
1493        let sig = secret.sign(b"test");
1494        let serialized = borsh::to_vec(&sig).unwrap();
1495        assert_eq!(serialized.len(), 65); // 1 + 64
1496        let deserialized = Signature::try_from_slice(&serialized).unwrap();
1497        assert_eq!(sig, deserialized);
1498    }
1499
1500    #[test]
1501    fn test_enum_variants_match_expected_types() {
1502        let ed_secret = SecretKey::generate_ed25519();
1503        let ed_public = ed_secret.public_key();
1504
1505        assert!(matches!(ed_public, PublicKey::Ed25519(_)));
1506        assert!(matches!(ed_secret, SecretKey::Ed25519(_)));
1507
1508        let secp_secret = SecretKey::generate_secp256k1();
1509        let secp_public = secp_secret.public_key();
1510
1511        assert!(matches!(secp_public, PublicKey::Secp256k1(_)));
1512        assert!(matches!(secp_secret, SecretKey::Secp256k1(_)));
1513    }
1514}