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;
10use rand::rngs::OsRng;
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12use slipped10::{BIP32Path, Curve};
13
14use crate::error::{ParseKeyError, SignerError};
15
16/// Key type identifier.
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18#[repr(u8)]
19pub enum KeyType {
20    /// Ed25519 key (most common).
21    Ed25519 = 0,
22    /// Secp256k1 key (for Ethereum compatibility).
23    Secp256k1 = 1,
24}
25
26impl KeyType {
27    /// Get the string prefix for this key type.
28    pub fn as_str(&self) -> &'static str {
29        match self {
30            KeyType::Ed25519 => "ed25519",
31            KeyType::Secp256k1 => "secp256k1",
32        }
33    }
34
35    /// Get the expected key length in bytes.
36    pub fn key_len(&self) -> usize {
37        match self {
38            KeyType::Ed25519 => 32,
39            KeyType::Secp256k1 => 33, // Compressed
40        }
41    }
42
43    /// Get the expected signature length in bytes.
44    pub fn signature_len(&self) -> usize {
45        match self {
46            KeyType::Ed25519 => 64,
47            KeyType::Secp256k1 => 65,
48        }
49    }
50}
51
52impl TryFrom<u8> for KeyType {
53    type Error = ParseKeyError;
54
55    fn try_from(value: u8) -> Result<Self, Self::Error> {
56        match value {
57            0 => Ok(KeyType::Ed25519),
58            1 => Ok(KeyType::Secp256k1),
59            _ => Err(ParseKeyError::UnknownKeyType(value.to_string())),
60        }
61    }
62}
63
64/// Ed25519 or Secp256k1 public key.
65#[derive(Clone, PartialEq, Eq, Hash)]
66pub struct PublicKey {
67    key_type: KeyType,
68    data: Vec<u8>,
69}
70
71impl PublicKey {
72    /// Create an Ed25519 public key from raw 32 bytes.
73    pub fn ed25519_from_bytes(bytes: [u8; 32]) -> Self {
74        Self {
75            key_type: KeyType::Ed25519,
76            data: bytes.to_vec(),
77        }
78    }
79
80    /// Get the key type.
81    pub fn key_type(&self) -> KeyType {
82        self.key_type
83    }
84
85    /// Get the raw key bytes.
86    pub fn as_bytes(&self) -> &[u8] {
87        &self.data
88    }
89
90    /// Get the key data as a fixed-size array for Ed25519 keys.
91    pub fn as_ed25519_bytes(&self) -> Option<&[u8; 32]> {
92        if self.key_type == KeyType::Ed25519 && self.data.len() == 32 {
93            Some(self.data.as_slice().try_into().unwrap())
94        } else {
95            None
96        }
97    }
98}
99
100impl FromStr for PublicKey {
101    type Err = ParseKeyError;
102
103    fn from_str(s: &str) -> Result<Self, Self::Err> {
104        let (key_type, data_str) = s.split_once(':').ok_or(ParseKeyError::InvalidFormat)?;
105
106        let key_type = match key_type {
107            "ed25519" => KeyType::Ed25519,
108            "secp256k1" => KeyType::Secp256k1,
109            other => return Err(ParseKeyError::UnknownKeyType(other.to_string())),
110        };
111
112        let data = bs58::decode(data_str)
113            .into_vec()
114            .map_err(|e| ParseKeyError::InvalidBase58(e.to_string()))?;
115
116        if data.len() != key_type.key_len() {
117            return Err(ParseKeyError::InvalidLength {
118                expected: key_type.key_len(),
119                actual: data.len(),
120            });
121        }
122
123        // Validate that the key is actually on the curve
124        match key_type {
125            KeyType::Ed25519 => {
126                // Validate Ed25519 public key is a valid curve point
127                let bytes: [u8; 32] = data
128                    .as_slice()
129                    .try_into()
130                    .map_err(|_| ParseKeyError::InvalidCurvePoint)?;
131                VerifyingKey::from_bytes(&bytes).map_err(|_| ParseKeyError::InvalidCurvePoint)?;
132            }
133            KeyType::Secp256k1 => {
134                // Validate secp256k1 public key is on the curve
135                // The key is 33 bytes (compressed SEC1 format)
136                let encoded = k256::EncodedPoint::from_bytes(&data)
137                    .map_err(|_| ParseKeyError::InvalidCurvePoint)?;
138                let point = k256::AffinePoint::from_encoded_point(&encoded);
139                if point.is_none().into() {
140                    return Err(ParseKeyError::InvalidCurvePoint);
141                }
142            }
143        }
144
145        Ok(Self { key_type, data })
146    }
147}
148
149impl TryFrom<&str> for PublicKey {
150    type Error = ParseKeyError;
151
152    fn try_from(s: &str) -> Result<Self, Self::Error> {
153        s.parse()
154    }
155}
156
157impl Display for PublicKey {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        write!(
160            f,
161            "{}:{}",
162            self.key_type.as_str(),
163            bs58::encode(&self.data).into_string()
164        )
165    }
166}
167
168impl Debug for PublicKey {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        write!(f, "PublicKey({})", self)
171    }
172}
173
174impl Serialize for PublicKey {
175    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
176        s.serialize_str(&self.to_string())
177    }
178}
179
180impl<'de> Deserialize<'de> for PublicKey {
181    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
182        let s: String = serde::Deserialize::deserialize(d)?;
183        s.parse().map_err(serde::de::Error::custom)
184    }
185}
186
187impl BorshSerialize for PublicKey {
188    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
189        borsh::BorshSerialize::serialize(&(self.key_type as u8), writer)?;
190        writer.write_all(&self.data)?;
191        Ok(())
192    }
193}
194
195impl BorshDeserialize for PublicKey {
196    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
197        let key_type_byte = u8::deserialize_reader(reader)?;
198        let key_type = KeyType::try_from(key_type_byte)
199            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
200
201        let mut data = vec![0u8; key_type.key_len()];
202        reader.read_exact(&mut data)?;
203
204        // Validate that the key is actually on the curve
205        match key_type {
206            KeyType::Ed25519 => {
207                let bytes: [u8; 32] = data.as_slice().try_into().map_err(|_| {
208                    std::io::Error::new(
209                        std::io::ErrorKind::InvalidData,
210                        "invalid ed25519 key length",
211                    )
212                })?;
213                VerifyingKey::from_bytes(&bytes).map_err(|_| {
214                    std::io::Error::new(
215                        std::io::ErrorKind::InvalidData,
216                        "invalid ed25519 curve point",
217                    )
218                })?;
219            }
220            KeyType::Secp256k1 => {
221                let encoded = k256::EncodedPoint::from_bytes(&data).map_err(|_| {
222                    std::io::Error::new(
223                        std::io::ErrorKind::InvalidData,
224                        "invalid secp256k1 encoding",
225                    )
226                })?;
227                let point = k256::AffinePoint::from_encoded_point(&encoded);
228                if point.is_none().into() {
229                    return Err(std::io::Error::new(
230                        std::io::ErrorKind::InvalidData,
231                        "invalid secp256k1 curve point",
232                    ));
233                }
234            }
235        }
236
237        Ok(Self { key_type, data })
238    }
239}
240
241/// Default BIP-32 HD derivation path for NEAR keys.
242/// NEAR uses coin type 397 per SLIP-44.
243pub const DEFAULT_HD_PATH: &str = "m/44'/397'/0'";
244
245/// Default number of words in generated seed phrases.
246pub const DEFAULT_WORD_COUNT: usize = 12;
247
248/// Ed25519 or Secp256k1 secret key.
249#[derive(Clone)]
250pub struct SecretKey {
251    key_type: KeyType,
252    data: Vec<u8>,
253}
254
255impl SecretKey {
256    /// Generate a new random Ed25519 key pair.
257    pub fn generate_ed25519() -> Self {
258        let signing_key = SigningKey::generate(&mut OsRng);
259        Self {
260            key_type: KeyType::Ed25519,
261            data: signing_key.to_bytes().to_vec(),
262        }
263    }
264
265    /// Create an Ed25519 secret key from raw 32 bytes.
266    pub fn ed25519_from_bytes(bytes: [u8; 32]) -> Self {
267        Self {
268            key_type: KeyType::Ed25519,
269            data: bytes.to_vec(),
270        }
271    }
272
273    /// Get the key type.
274    pub fn key_type(&self) -> KeyType {
275        self.key_type
276    }
277
278    /// Get the raw key bytes.
279    pub fn as_bytes(&self) -> &[u8] {
280        &self.data
281    }
282
283    /// Derive the public key.
284    pub fn public_key(&self) -> PublicKey {
285        match self.key_type {
286            KeyType::Ed25519 => {
287                let bytes: [u8; 32] = self
288                    .data
289                    .as_slice()
290                    .try_into()
291                    .expect("invalid ed25519 key");
292                let signing_key = SigningKey::from_bytes(&bytes);
293                let verifying_key = signing_key.verifying_key();
294                PublicKey::ed25519_from_bytes(verifying_key.to_bytes())
295            }
296            KeyType::Secp256k1 => {
297                unimplemented!("secp256k1 not yet supported")
298            }
299        }
300    }
301
302    /// Sign a message.
303    pub fn sign(&self, message: &[u8]) -> Signature {
304        match self.key_type {
305            KeyType::Ed25519 => {
306                let bytes: [u8; 32] = self
307                    .data
308                    .as_slice()
309                    .try_into()
310                    .expect("invalid ed25519 key");
311                let signing_key = SigningKey::from_bytes(&bytes);
312                let signature = signing_key.sign(message);
313                Signature {
314                    key_type: KeyType::Ed25519,
315                    data: signature.to_bytes().to_vec(),
316                }
317            }
318            KeyType::Secp256k1 => {
319                unimplemented!("secp256k1 not yet supported")
320            }
321        }
322    }
323
324    // ========================================================================
325    // Seed Phrase / Mnemonic Support
326    // ========================================================================
327
328    /// Derive an Ed25519 secret key from a BIP-39 seed phrase.
329    ///
330    /// Uses SLIP-10 derivation with the default NEAR HD path (`m/44'/397'/0'`).
331    ///
332    /// # Arguments
333    ///
334    /// * `phrase` - BIP-39 mnemonic phrase (12, 15, 18, 21, or 24 words)
335    ///
336    /// # Example
337    ///
338    /// ```rust
339    /// use near_kit::SecretKey;
340    ///
341    /// // Valid BIP-39 mnemonic (all zeros entropy)
342    /// let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
343    /// let secret_key = SecretKey::from_seed_phrase(phrase).unwrap();
344    /// ```
345    pub fn from_seed_phrase(phrase: impl AsRef<str>) -> Result<Self, SignerError> {
346        Self::from_seed_phrase_with_path(phrase, DEFAULT_HD_PATH)
347    }
348
349    /// Derive an Ed25519 secret key from a BIP-39 seed phrase with custom HD path.
350    ///
351    /// Uses SLIP-10 derivation for Ed25519 keys. Only hardened derivation paths
352    /// are supported (all path components must use `'` suffix).
353    ///
354    /// # Arguments
355    ///
356    /// * `phrase` - BIP-39 mnemonic phrase (12, 15, 18, 21, or 24 words)
357    /// * `hd_path` - BIP-32 derivation path (e.g., `"m/44'/397'/0'"`)
358    ///
359    /// # Example
360    ///
361    /// ```rust
362    /// use near_kit::SecretKey;
363    ///
364    /// let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
365    /// let secret_key = SecretKey::from_seed_phrase_with_path(phrase, "m/44'/397'/1'").unwrap();
366    /// ```
367    pub fn from_seed_phrase_with_path(
368        phrase: impl AsRef<str>,
369        hd_path: impl AsRef<str>,
370    ) -> Result<Self, SignerError> {
371        Self::from_seed_phrase_with_path_and_passphrase(phrase, hd_path, None)
372    }
373
374    /// Derive an Ed25519 secret key from a BIP-39 seed phrase with passphrase.
375    ///
376    /// The passphrase provides additional entropy for seed generation (BIP-39 feature).
377    /// An empty passphrase is equivalent to no passphrase.
378    ///
379    /// # Arguments
380    ///
381    /// * `phrase` - BIP-39 mnemonic phrase
382    /// * `hd_path` - BIP-32 derivation path
383    /// * `passphrase` - Optional passphrase for additional entropy
384    ///
385    /// # Example
386    ///
387    /// ```rust
388    /// use near_kit::SecretKey;
389    ///
390    /// let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
391    /// let secret_key = SecretKey::from_seed_phrase_with_path_and_passphrase(
392    ///     phrase,
393    ///     "m/44'/397'/0'",
394    ///     Some("my-passphrase")
395    /// ).unwrap();
396    /// ```
397    pub fn from_seed_phrase_with_path_and_passphrase(
398        phrase: impl AsRef<str>,
399        hd_path: impl AsRef<str>,
400        passphrase: Option<&str>,
401    ) -> Result<Self, SignerError> {
402        // Normalize and parse mnemonic
403        let normalized = phrase
404            .as_ref()
405            .trim()
406            .to_lowercase()
407            .split_whitespace()
408            .collect::<Vec<_>>()
409            .join(" ");
410
411        let mnemonic: Mnemonic = normalized
412            .parse()
413            .map_err(|_| SignerError::InvalidSeedPhrase)?;
414
415        // Convert mnemonic to seed (64 bytes)
416        let seed = mnemonic.to_seed(passphrase.unwrap_or(""));
417
418        // Parse HD path
419        let path: BIP32Path = hd_path
420            .as_ref()
421            .parse()
422            .map_err(|e| SignerError::KeyDerivationFailed(format!("Invalid HD path: {}", e)))?;
423
424        // Derive key using SLIP-10 for Ed25519
425        let derived =
426            slipped10::derive_key_from_path(&seed, Curve::Ed25519, &path).map_err(|e| {
427                SignerError::KeyDerivationFailed(format!("SLIP-10 derivation failed: {:?}", e))
428            })?;
429
430        Ok(Self::ed25519_from_bytes(derived.key))
431    }
432
433    /// Generate a new random seed phrase and derive the corresponding secret key.
434    ///
435    /// Returns both the seed phrase (for backup) and the derived secret key.
436    /// Uses 12 words by default and the standard NEAR HD path.
437    ///
438    /// # Example
439    ///
440    /// ```rust
441    /// use near_kit::SecretKey;
442    ///
443    /// let (phrase, secret_key) = SecretKey::generate_with_seed_phrase().unwrap();
444    /// println!("Backup your seed phrase: {}", phrase);
445    /// ```
446    pub fn generate_with_seed_phrase() -> Result<(String, Self), SignerError> {
447        Self::generate_with_seed_phrase_custom(DEFAULT_WORD_COUNT, DEFAULT_HD_PATH, None)
448    }
449
450    /// Generate a new random seed phrase with custom word count.
451    ///
452    /// # Arguments
453    ///
454    /// * `word_count` - Number of words (12, 15, 18, 21, or 24)
455    ///
456    /// # Example
457    ///
458    /// ```rust
459    /// use near_kit::SecretKey;
460    ///
461    /// let (phrase, secret_key) = SecretKey::generate_with_seed_phrase_words(24).unwrap();
462    /// assert_eq!(phrase.split_whitespace().count(), 24);
463    /// ```
464    pub fn generate_with_seed_phrase_words(
465        word_count: usize,
466    ) -> Result<(String, Self), SignerError> {
467        Self::generate_with_seed_phrase_custom(word_count, DEFAULT_HD_PATH, None)
468    }
469
470    /// Generate a new random seed phrase with full customization.
471    ///
472    /// # Arguments
473    ///
474    /// * `word_count` - Number of words (12, 15, 18, 21, or 24)
475    /// * `hd_path` - BIP-32 derivation path
476    /// * `passphrase` - Optional passphrase for additional entropy
477    pub fn generate_with_seed_phrase_custom(
478        word_count: usize,
479        hd_path: impl AsRef<str>,
480        passphrase: Option<&str>,
481    ) -> Result<(String, Self), SignerError> {
482        let phrase = generate_seed_phrase(word_count)?;
483        let secret_key =
484            Self::from_seed_phrase_with_path_and_passphrase(&phrase, hd_path, passphrase)?;
485        Ok((phrase, secret_key))
486    }
487}
488
489impl FromStr for SecretKey {
490    type Err = ParseKeyError;
491
492    fn from_str(s: &str) -> Result<Self, Self::Err> {
493        let (key_type, data_str) = s.split_once(':').ok_or(ParseKeyError::InvalidFormat)?;
494
495        let key_type = match key_type {
496            "ed25519" => KeyType::Ed25519,
497            "secp256k1" => KeyType::Secp256k1,
498            other => return Err(ParseKeyError::UnknownKeyType(other.to_string())),
499        };
500
501        let data = bs58::decode(data_str)
502            .into_vec()
503            .map_err(|e| ParseKeyError::InvalidBase58(e.to_string()))?;
504
505        // For ed25519, the secret key might be 32 bytes (seed) or 64 bytes (expanded)
506        // For secp256k1, it must be 32 bytes
507        let valid_len = match key_type {
508            KeyType::Ed25519 => data.len() == 32 || data.len() == 64,
509            KeyType::Secp256k1 => data.len() == 32,
510        };
511        if !valid_len {
512            return Err(ParseKeyError::InvalidLength {
513                expected: 32,
514                actual: data.len(),
515            });
516        }
517
518        // Take first 32 bytes if 64-byte expanded key
519        let data = if data.len() == 64 {
520            data[..32].to_vec()
521        } else {
522            data
523        };
524
525        Ok(Self { key_type, data })
526    }
527}
528
529impl TryFrom<&str> for SecretKey {
530    type Error = ParseKeyError;
531
532    fn try_from(s: &str) -> Result<Self, Self::Error> {
533        s.parse()
534    }
535}
536
537impl Display for SecretKey {
538    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
539        write!(
540            f,
541            "{}:{}",
542            self.key_type.as_str(),
543            bs58::encode(&self.data).into_string()
544        )
545    }
546}
547
548impl Debug for SecretKey {
549    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550        write!(f, "SecretKey({}:***)", self.key_type.as_str())
551    }
552}
553
554/// Cryptographic signature.
555#[derive(Clone, PartialEq, Eq)]
556pub struct Signature {
557    key_type: KeyType,
558    data: Vec<u8>,
559}
560
561impl Signature {
562    /// Create an Ed25519 signature from raw 64 bytes.
563    pub fn ed25519_from_bytes(bytes: [u8; 64]) -> Self {
564        Self {
565            key_type: KeyType::Ed25519,
566            data: bytes.to_vec(),
567        }
568    }
569
570    /// Get the key type.
571    pub fn key_type(&self) -> KeyType {
572        self.key_type
573    }
574
575    /// Get the raw signature bytes.
576    pub fn as_bytes(&self) -> &[u8] {
577        &self.data
578    }
579
580    /// Verify this signature against a message and public key.
581    pub fn verify(&self, message: &[u8], public_key: &PublicKey) -> bool {
582        if self.key_type != public_key.key_type() {
583            return false;
584        }
585
586        match self.key_type {
587            KeyType::Ed25519 => {
588                let Some(pk_bytes) = public_key.as_ed25519_bytes() else {
589                    return false;
590                };
591                let Ok(verifying_key) = VerifyingKey::from_bytes(pk_bytes) else {
592                    return false;
593                };
594                let sig_bytes: [u8; 64] = match self.data.as_slice().try_into() {
595                    Ok(b) => b,
596                    Err(_) => return false,
597                };
598                let signature = ed25519_dalek::Signature::from_bytes(&sig_bytes);
599                verifying_key.verify_strict(message, &signature).is_ok()
600            }
601            KeyType::Secp256k1 => false, // not yet implemented
602        }
603    }
604}
605
606impl FromStr for Signature {
607    type Err = ParseKeyError;
608
609    fn from_str(s: &str) -> Result<Self, Self::Err> {
610        let (key_type, data_str) = s.split_once(':').ok_or(ParseKeyError::InvalidFormat)?;
611
612        let key_type = match key_type {
613            "ed25519" => KeyType::Ed25519,
614            "secp256k1" => KeyType::Secp256k1,
615            other => return Err(ParseKeyError::UnknownKeyType(other.to_string())),
616        };
617
618        let data = bs58::decode(data_str)
619            .into_vec()
620            .map_err(|e| ParseKeyError::InvalidBase58(e.to_string()))?;
621
622        if data.len() != key_type.signature_len() {
623            return Err(ParseKeyError::InvalidLength {
624                expected: key_type.signature_len(),
625                actual: data.len(),
626            });
627        }
628
629        Ok(Self { key_type, data })
630    }
631}
632
633impl Display for Signature {
634    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
635        write!(
636            f,
637            "{}:{}",
638            self.key_type.as_str(),
639            bs58::encode(&self.data).into_string()
640        )
641    }
642}
643
644impl Debug for Signature {
645    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
646        write!(f, "Signature({})", self)
647    }
648}
649
650impl Serialize for Signature {
651    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
652        s.serialize_str(&self.to_string())
653    }
654}
655
656impl<'de> Deserialize<'de> for Signature {
657    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
658        let s: String = serde::Deserialize::deserialize(d)?;
659        s.parse().map_err(serde::de::Error::custom)
660    }
661}
662
663impl BorshSerialize for Signature {
664    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
665        borsh::BorshSerialize::serialize(&(self.key_type as u8), writer)?;
666        writer.write_all(&self.data)?;
667        Ok(())
668    }
669}
670
671impl BorshDeserialize for Signature {
672    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
673        let key_type_byte = u8::deserialize_reader(reader)?;
674        let key_type = KeyType::try_from(key_type_byte)
675            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
676
677        let mut data = vec![0u8; key_type.signature_len()];
678        reader.read_exact(&mut data)?;
679
680        Ok(Self { key_type, data })
681    }
682}
683
684// ============================================================================
685// Seed Phrase Generation
686// ============================================================================
687
688/// Generate a random BIP-39 seed phrase.
689///
690/// # Arguments
691///
692/// * `word_count` - Number of words (12, 15, 18, 21, or 24)
693///
694/// # Example
695///
696/// ```rust
697/// use near_kit::generate_seed_phrase;
698///
699/// let phrase = generate_seed_phrase(12).unwrap();
700/// assert_eq!(phrase.split_whitespace().count(), 12);
701/// ```
702pub fn generate_seed_phrase(word_count: usize) -> Result<String, SignerError> {
703    use rand::RngCore;
704
705    // Word count to entropy bytes: 12->16, 15->20, 18->24, 21->28, 24->32
706    let entropy_bytes = match word_count {
707        12 => 16,
708        15 => 20,
709        18 => 24,
710        21 => 28,
711        24 => 32,
712        _ => {
713            return Err(SignerError::KeyDerivationFailed(format!(
714                "Invalid word count: {}. Must be 12, 15, 18, 21, or 24",
715                word_count
716            )));
717        }
718    };
719
720    let mut entropy = vec![0u8; entropy_bytes];
721    OsRng.fill_bytes(&mut entropy);
722
723    let mnemonic = Mnemonic::from_entropy(&entropy).map_err(|e| {
724        SignerError::KeyDerivationFailed(format!("Failed to generate mnemonic: {}", e))
725    })?;
726
727    Ok(mnemonic.to_string())
728}
729
730// ============================================================================
731// KeyPair
732// ============================================================================
733
734/// A cryptographic key pair (secret key + public key).
735///
736/// This is a convenience type that bundles a [`SecretKey`] with its derived
737/// [`PublicKey`] for situations where you need both (e.g., creating accounts,
738/// adding access keys).
739///
740/// # Example
741///
742/// ```rust
743/// use near_kit::KeyPair;
744///
745/// // Generate a random Ed25519 key pair
746/// let keypair = KeyPair::random();
747/// println!("Public key: {}", keypair.public_key);
748/// println!("Secret key: {}", keypair.secret_key);
749///
750/// // Use with account creation
751/// // near.transaction("new.alice.testnet")
752/// //     .create_account()
753/// //     .add_full_access_key(keypair.public_key)
754/// //     .send()
755/// //     .await?;
756/// ```
757#[derive(Clone)]
758pub struct KeyPair {
759    /// The secret (private) key.
760    pub secret_key: SecretKey,
761    /// The public key derived from the secret key.
762    pub public_key: PublicKey,
763}
764
765impl KeyPair {
766    /// Generate a random Ed25519 key pair.
767    ///
768    /// This is the most common key type for NEAR.
769    ///
770    /// # Example
771    ///
772    /// ```rust
773    /// use near_kit::KeyPair;
774    ///
775    /// let keypair = KeyPair::random();
776    /// ```
777    pub fn random() -> Self {
778        Self::random_ed25519()
779    }
780
781    /// Generate a random Ed25519 key pair.
782    ///
783    /// # Example
784    ///
785    /// ```rust
786    /// use near_kit::KeyPair;
787    ///
788    /// let keypair = KeyPair::random_ed25519();
789    /// assert!(keypair.public_key.to_string().starts_with("ed25519:"));
790    /// ```
791    pub fn random_ed25519() -> Self {
792        let secret_key = SecretKey::generate_ed25519();
793        let public_key = secret_key.public_key();
794        Self {
795            secret_key,
796            public_key,
797        }
798    }
799
800    /// Create a key pair from an existing secret key.
801    ///
802    /// # Example
803    ///
804    /// ```rust
805    /// use near_kit::{KeyPair, SecretKey};
806    ///
807    /// let secret_key: SecretKey = "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr".parse().unwrap();
808    /// let keypair = KeyPair::from_secret_key(secret_key);
809    /// ```
810    pub fn from_secret_key(secret_key: SecretKey) -> Self {
811        let public_key = secret_key.public_key();
812        Self {
813            secret_key,
814            public_key,
815        }
816    }
817
818    /// Create a key pair from a seed phrase using the default NEAR HD path.
819    ///
820    /// # Example
821    ///
822    /// ```rust
823    /// use near_kit::KeyPair;
824    ///
825    /// let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
826    /// let keypair = KeyPair::from_seed_phrase(phrase).unwrap();
827    /// ```
828    pub fn from_seed_phrase(phrase: impl AsRef<str>) -> Result<Self, SignerError> {
829        let secret_key = SecretKey::from_seed_phrase(phrase)?;
830        Ok(Self::from_secret_key(secret_key))
831    }
832
833    /// Generate a new random key pair with a seed phrase for backup.
834    ///
835    /// Returns the seed phrase (for backup) and the key pair.
836    ///
837    /// # Example
838    ///
839    /// ```rust
840    /// use near_kit::KeyPair;
841    ///
842    /// let (phrase, keypair) = KeyPair::random_with_seed_phrase().unwrap();
843    /// println!("Backup your seed phrase: {}", phrase);
844    /// println!("Public key: {}", keypair.public_key);
845    /// ```
846    pub fn random_with_seed_phrase() -> Result<(String, Self), SignerError> {
847        let (phrase, secret_key) = SecretKey::generate_with_seed_phrase()?;
848        Ok((phrase, Self::from_secret_key(secret_key)))
849    }
850}
851
852impl std::fmt::Debug for KeyPair {
853    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
854        f.debug_struct("KeyPair")
855            .field("public_key", &self.public_key)
856            .field("secret_key", &"***")
857            .finish()
858    }
859}
860
861#[cfg(test)]
862mod tests {
863    use super::*;
864
865    #[test]
866    fn test_generate_and_sign() {
867        let secret = SecretKey::generate_ed25519();
868        let public = secret.public_key();
869        let message = b"hello world";
870
871        let signature = secret.sign(message);
872        assert!(signature.verify(message, &public));
873        assert!(!signature.verify(b"wrong message", &public));
874    }
875
876    #[test]
877    fn test_public_key_roundtrip() {
878        let secret = SecretKey::generate_ed25519();
879        let public = secret.public_key();
880        let s = public.to_string();
881        let parsed: PublicKey = s.parse().unwrap();
882        assert_eq!(public, parsed);
883    }
884
885    #[test]
886    fn test_secret_key_roundtrip() {
887        let secret = SecretKey::generate_ed25519();
888        let s = secret.to_string();
889        let parsed: SecretKey = s.parse().unwrap();
890        assert_eq!(secret.public_key(), parsed.public_key());
891    }
892
893    // ========================================================================
894    // Seed Phrase Tests
895    // ========================================================================
896
897    #[test]
898    fn test_generate_seed_phrase_12_words() {
899        let phrase = generate_seed_phrase(12).unwrap();
900        assert_eq!(phrase.split_whitespace().count(), 12);
901    }
902
903    #[test]
904    fn test_generate_seed_phrase_24_words() {
905        let phrase = generate_seed_phrase(24).unwrap();
906        assert_eq!(phrase.split_whitespace().count(), 24);
907    }
908
909    #[test]
910    fn test_generate_seed_phrase_invalid_word_count() {
911        let result = generate_seed_phrase(13);
912        assert!(result.is_err());
913    }
914
915    // Valid BIP-39 test vector (from official test vectors)
916    const TEST_PHRASE: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
917
918    #[test]
919    fn test_from_seed_phrase_known_vector() {
920        // Test vector: known seed phrase should produce consistent key
921        let secret_key = SecretKey::from_seed_phrase(TEST_PHRASE).unwrap();
922
923        // Same phrase should always produce the same key
924        let secret_key2 = SecretKey::from_seed_phrase(TEST_PHRASE).unwrap();
925        assert_eq!(secret_key.public_key(), secret_key2.public_key());
926    }
927
928    #[test]
929    fn test_from_seed_phrase_whitespace_normalization() {
930        let phrase1 = TEST_PHRASE;
931        let phrase2 = "  abandon   abandon  abandon abandon abandon abandon abandon abandon abandon abandon abandon about  ";
932        let phrase3 = "ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABOUT";
933
934        let key1 = SecretKey::from_seed_phrase(phrase1).unwrap();
935        let key2 = SecretKey::from_seed_phrase(phrase2).unwrap();
936        let key3 = SecretKey::from_seed_phrase(phrase3).unwrap();
937
938        assert_eq!(key1.public_key(), key2.public_key());
939        assert_eq!(key1.public_key(), key3.public_key());
940    }
941
942    #[test]
943    fn test_from_seed_phrase_invalid() {
944        let result = SecretKey::from_seed_phrase("invalid words that are not a mnemonic");
945        assert!(result.is_err());
946    }
947
948    #[test]
949    fn test_from_seed_phrase_different_paths() {
950        let key1 = SecretKey::from_seed_phrase_with_path(TEST_PHRASE, "m/44'/397'/0'").unwrap();
951        let key2 = SecretKey::from_seed_phrase_with_path(TEST_PHRASE, "m/44'/397'/1'").unwrap();
952
953        // Different paths should produce different keys
954        assert_ne!(key1.public_key(), key2.public_key());
955    }
956
957    #[test]
958    fn test_from_seed_phrase_with_passphrase() {
959        let key_no_pass = SecretKey::from_seed_phrase_with_path_and_passphrase(
960            TEST_PHRASE,
961            DEFAULT_HD_PATH,
962            None,
963        )
964        .unwrap();
965
966        let key_with_pass = SecretKey::from_seed_phrase_with_path_and_passphrase(
967            TEST_PHRASE,
968            DEFAULT_HD_PATH,
969            Some("my-password"),
970        )
971        .unwrap();
972
973        // Passphrase should produce different key
974        assert_ne!(key_no_pass.public_key(), key_with_pass.public_key());
975    }
976
977    #[test]
978    fn test_generate_with_seed_phrase() {
979        let (phrase, secret_key) = SecretKey::generate_with_seed_phrase().unwrap();
980
981        // Phrase should be 12 words
982        assert_eq!(phrase.split_whitespace().count(), 12);
983
984        // Re-deriving from the phrase should produce the same key
985        let derived = SecretKey::from_seed_phrase(&phrase).unwrap();
986        assert_eq!(secret_key.public_key(), derived.public_key());
987    }
988
989    #[test]
990    fn test_generate_with_seed_phrase_24_words() {
991        let (phrase, secret_key) = SecretKey::generate_with_seed_phrase_words(24).unwrap();
992
993        assert_eq!(phrase.split_whitespace().count(), 24);
994
995        let derived = SecretKey::from_seed_phrase(&phrase).unwrap();
996        assert_eq!(secret_key.public_key(), derived.public_key());
997    }
998
999    #[test]
1000    fn test_seed_phrase_key_can_sign() {
1001        let secret_key = SecretKey::from_seed_phrase(TEST_PHRASE).unwrap();
1002
1003        let message = b"test message";
1004        let signature = secret_key.sign(message);
1005        let public_key = secret_key.public_key();
1006
1007        assert!(signature.verify(message, &public_key));
1008    }
1009
1010    // ========================================================================
1011    // Curve Point Validation Tests
1012    // ========================================================================
1013
1014    #[test]
1015    fn test_secp256k1_invalid_curve_point_rejected() {
1016        // This is the invalid key from the NEAR SDK docs that was identified as not being
1017        // on the secp256k1 curve. See: https://github.com/near/near-sdk-rs/pull/1469
1018        let invalid_key = "secp256k1:qMoRgcoXai4mBPsdbHi1wfyxF9TdbPCF4qSDQTRP3TfescSRoUdSx6nmeQoN3aiwGzwMyGXAb1gUjBTv5AY8DXj";
1019        let result: Result<PublicKey, _> = invalid_key.parse();
1020        assert!(result.is_err());
1021        assert!(matches!(
1022            result.unwrap_err(),
1023            ParseKeyError::InvalidCurvePoint | ParseKeyError::InvalidLength { .. }
1024        ));
1025    }
1026
1027    #[test]
1028    fn test_secp256k1_valid_curve_point_accepted() {
1029        // Valid secp256k1 key from near-sdk-js (verified to be on the curve)
1030        let valid_key = "secp256k1:5r22SrjrDvgY3wdQsnjgxkeAbU1VcM71FYvALEQWihjM3Xk4Be1CpETTqFccChQr4iJwDroSDVmgaWZv2AcXvYeL";
1031        let result: Result<PublicKey, _> = valid_key.parse();
1032        // This key is 64 bytes (uncompressed), but we expect 33 bytes (compressed)
1033        // So it should fail with InvalidLength, not InvalidCurvePoint
1034        assert!(result.is_err());
1035    }
1036
1037    #[test]
1038    fn test_ed25519_valid_key_accepted() {
1039        // Valid ed25519 public key
1040        let valid_key = "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp";
1041        let result: Result<PublicKey, _> = valid_key.parse();
1042        assert!(result.is_ok());
1043    }
1044
1045    #[test]
1046    fn test_ed25519_invalid_curve_point_rejected() {
1047        // The high bit of the last byte being set with an invalid x-coordinate recovery
1048        // should produce an invalid point. Specifically, a y-coordinate that when
1049        // the x is computed results in a non-square (no valid x exists).
1050        // This specific byte sequence has been verified to fail ed25519 decompression.
1051        //
1052        // Note: ed25519_dalek may accept many byte patterns as valid curve points.
1053        // Ed25519 point decompression is very permissive - most 32-byte sequences
1054        // decode to valid points.
1055        let invalid_bytes = [
1056            0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1057            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1058            0xFF, 0xFF, 0xFF, 0x7F,
1059        ];
1060        let encoded = bs58::encode(&invalid_bytes).into_string();
1061        let invalid_key = format!("ed25519:{}", encoded);
1062        let result: Result<PublicKey, _> = invalid_key.parse();
1063        if let Err(err) = result {
1064            assert!(matches!(err, ParseKeyError::InvalidCurvePoint));
1065        } else {
1066            // If ed25519_dalek accepts this, we should skip this test case
1067            eprintln!(
1068                "Note: ed25519 point decompression accepted test bytes - validation may be too lenient"
1069            );
1070        }
1071    }
1072
1073    #[test]
1074    fn test_borsh_deserialize_validates_curve_point() {
1075        use borsh::BorshDeserialize;
1076
1077        // Test with secp256k1 since ed25519 validation is very lenient
1078        // Use invalid secp256k1 bytes (all zeros is definitely not on the curve)
1079        let mut invalid_bytes = vec![1u8]; // KeyType::Secp256k1
1080        invalid_bytes.extend_from_slice(&[0u8; 33]); // Invalid curve point
1081
1082        let result = PublicKey::try_from_slice(&invalid_bytes);
1083        assert!(result.is_err());
1084    }
1085
1086    #[test]
1087    fn test_signature_from_str_roundtrip() {
1088        let sig_str = "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5";
1089        let sig: Signature = sig_str.parse().unwrap();
1090        assert_eq!(sig.key_type(), KeyType::Ed25519);
1091        assert_eq!(sig.as_bytes().len(), 64);
1092        assert_eq!(sig.to_string(), sig_str);
1093    }
1094
1095    #[test]
1096    fn test_signature_from_str_invalid_format() {
1097        assert!("no_colon".parse::<Signature>().is_err());
1098        assert!("unknown:abc".parse::<Signature>().is_err());
1099        assert!("ed25519:invalid!!!".parse::<Signature>().is_err());
1100        assert!("ed25519:AAAA".parse::<Signature>().is_err()); // too short
1101    }
1102
1103    #[test]
1104    fn test_signature_serde_roundtrip() {
1105        let sig_str = "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5";
1106        let sig: Signature = sig_str.parse().unwrap();
1107        let json = serde_json::to_value(&sig).unwrap();
1108        assert_eq!(json.as_str().unwrap(), sig_str);
1109        let parsed: Signature = serde_json::from_value(json).unwrap();
1110        assert_eq!(sig, parsed);
1111    }
1112}