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;
13use slipped10::{BIP32Path, Curve};
14
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
529        let path: BIP32Path = hd_path
530            .as_ref()
531            .parse()
532            .map_err(|e| SignerError::KeyDerivationFailed(format!("Invalid HD path: {}", e)))?;
533
534        // Derive key using SLIP-10 for Ed25519
535        let derived =
536            slipped10::derive_key_from_path(&seed, Curve::Ed25519, &path).map_err(|e| {
537                SignerError::KeyDerivationFailed(format!("SLIP-10 derivation failed: {:?}", e))
538            })?;
539
540        Ok(Self::ed25519_from_bytes(derived.key))
541    }
542
543    /// Generate a new random seed phrase and derive the corresponding secret key.
544    ///
545    /// Returns both the seed phrase (for backup) and the derived secret key.
546    /// Uses 12 words by default and the standard NEAR HD path.
547    ///
548    /// # Example
549    ///
550    /// ```rust
551    /// use near_kit::SecretKey;
552    ///
553    /// let (phrase, secret_key) = SecretKey::generate_with_seed_phrase().unwrap();
554    /// println!("Backup your seed phrase: {}", phrase);
555    /// ```
556    pub fn generate_with_seed_phrase() -> Result<(String, Self), SignerError> {
557        Self::generate_with_seed_phrase_custom(DEFAULT_WORD_COUNT, DEFAULT_HD_PATH, None)
558    }
559
560    /// Generate a new random seed phrase with custom word count.
561    ///
562    /// # Arguments
563    ///
564    /// * `word_count` - Number of words (12, 15, 18, 21, or 24)
565    ///
566    /// # Example
567    ///
568    /// ```rust
569    /// use near_kit::SecretKey;
570    ///
571    /// let (phrase, secret_key) = SecretKey::generate_with_seed_phrase_words(24).unwrap();
572    /// assert_eq!(phrase.split_whitespace().count(), 24);
573    /// ```
574    pub fn generate_with_seed_phrase_words(
575        word_count: usize,
576    ) -> Result<(String, Self), SignerError> {
577        Self::generate_with_seed_phrase_custom(word_count, DEFAULT_HD_PATH, None)
578    }
579
580    /// Generate a new random seed phrase with full customization.
581    ///
582    /// # Arguments
583    ///
584    /// * `word_count` - Number of words (12, 15, 18, 21, or 24)
585    /// * `hd_path` - BIP-32 derivation path
586    /// * `passphrase` - Optional passphrase for additional entropy
587    pub fn generate_with_seed_phrase_custom(
588        word_count: usize,
589        hd_path: impl AsRef<str>,
590        passphrase: Option<&str>,
591    ) -> Result<(String, Self), SignerError> {
592        let phrase = generate_seed_phrase(word_count)?;
593        let secret_key =
594            Self::from_seed_phrase_with_path_and_passphrase(&phrase, hd_path, passphrase)?;
595        Ok((phrase, secret_key))
596    }
597}
598
599impl FromStr for SecretKey {
600    type Err = ParseKeyError;
601
602    fn from_str(s: &str) -> Result<Self, Self::Err> {
603        let (key_type, data_str) = s.split_once(':').ok_or(ParseKeyError::InvalidFormat)?;
604
605        let key_type = match key_type {
606            "ed25519" => KeyType::Ed25519,
607            "secp256k1" => KeyType::Secp256k1,
608            other => return Err(ParseKeyError::UnknownKeyType(other.to_string())),
609        };
610
611        let data = bs58::decode(data_str)
612            .into_vec()
613            .map_err(|e| ParseKeyError::InvalidBase58(e.to_string()))?;
614
615        // For ed25519, the secret key might be 32 bytes (seed) or 64 bytes (expanded)
616        // For secp256k1, it must be 32 bytes
617        let valid_len = match key_type {
618            KeyType::Ed25519 => data.len() == 32 || data.len() == 64,
619            KeyType::Secp256k1 => data.len() == 32,
620        };
621        if !valid_len {
622            return Err(ParseKeyError::InvalidLength {
623                expected: 32,
624                actual: data.len(),
625            });
626        }
627
628        // Take first 32 bytes if 64-byte expanded key
629        let bytes: [u8; 32] = data[..32]
630            .try_into()
631            .map_err(|_| ParseKeyError::InvalidFormat)?;
632
633        match key_type {
634            KeyType::Ed25519 => Ok(Self::Ed25519(bytes)),
635            KeyType::Secp256k1 => {
636                // Validate secp256k1 scalar (non-zero and < curve order)
637                k256::SecretKey::from_bytes((&bytes).into())
638                    .map_err(|_| ParseKeyError::InvalidScalar)?;
639                Ok(Self::Secp256k1(bytes))
640            }
641        }
642    }
643}
644
645impl TryFrom<&str> for SecretKey {
646    type Error = ParseKeyError;
647
648    fn try_from(s: &str) -> Result<Self, Self::Error> {
649        s.parse()
650    }
651}
652
653impl Display for SecretKey {
654    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
655        write!(
656            f,
657            "{}:{}",
658            self.key_type().as_str(),
659            bs58::encode(self.as_bytes()).into_string()
660        )
661    }
662}
663
664impl Debug for SecretKey {
665    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
666        write!(f, "SecretKey({}:***)", self.key_type().as_str())
667    }
668}
669
670/// Cryptographic signature.
671#[derive(Clone, PartialEq, Eq, SerializeDisplay, DeserializeFromStr)]
672pub enum Signature {
673    /// Ed25519 signature (64 bytes).
674    Ed25519([u8; 64]),
675    /// Secp256k1 signature (65 bytes: `[r (32) | s (32) | v (1)]`).
676    Secp256k1([u8; 65]),
677}
678
679impl Signature {
680    /// Create an Ed25519 signature from raw 64 bytes.
681    pub fn ed25519_from_bytes(bytes: [u8; 64]) -> Self {
682        Self::Ed25519(bytes)
683    }
684
685    /// Create a Secp256k1 signature from raw 65 bytes.
686    ///
687    /// The format is `[r (32 bytes) | s (32 bytes) | v (1 byte recovery id)]`,
688    /// matching the NEAR protocol's secp256k1 signature format.
689    pub fn secp256k1_from_bytes(bytes: [u8; 65]) -> Self {
690        Self::Secp256k1(bytes)
691    }
692
693    /// Get the key type.
694    pub fn key_type(&self) -> KeyType {
695        match self {
696            Self::Ed25519(_) => KeyType::Ed25519,
697            Self::Secp256k1(_) => KeyType::Secp256k1,
698        }
699    }
700
701    /// Get the raw signature bytes.
702    pub fn as_bytes(&self) -> &[u8] {
703        match self {
704            Self::Ed25519(bytes) => bytes.as_slice(),
705            Self::Secp256k1(bytes) => bytes.as_slice(),
706        }
707    }
708
709    /// Verify this signature against a message and public key.
710    pub fn verify(&self, message: &[u8], public_key: &PublicKey) -> bool {
711        match (self, public_key) {
712            (Self::Ed25519(sig_bytes), PublicKey::Ed25519(pk_bytes)) => {
713                let Ok(verifying_key) = VerifyingKey::from_bytes(pk_bytes) else {
714                    return false;
715                };
716                let signature = ed25519_dalek::Signature::from_bytes(sig_bytes);
717                verifying_key.verify_strict(message, &signature).is_ok()
718            }
719            (Self::Secp256k1(sig_bytes), PublicKey::Secp256k1(pk_bytes)) => {
720                // Reconstruct the 65-byte uncompressed key with 0x04 prefix for verification
721                let mut uncompressed = [0u8; 65];
722                uncompressed[0] = 0x04;
723                uncompressed[1..].copy_from_slice(pk_bytes);
724                let Ok(verifying_key) = k256::ecdsa::VerifyingKey::from_sec1_bytes(&uncompressed)
725                else {
726                    return false;
727                };
728
729                // Validate recovery id byte is in expected range (0..=3)
730                let v = sig_bytes[64];
731                if v > 3 {
732                    return false;
733                }
734
735                let Ok(signature) = k256::ecdsa::Signature::from_bytes((&sig_bytes[..64]).into())
736                else {
737                    return false;
738                };
739
740                // NEAR protocol: verify against SHA-256 hash of the message
741                let hash = sha2::Sha256::digest(message);
742                use k256::ecdsa::signature::hazmat::PrehashVerifier;
743                verifying_key.verify_prehash(&hash, &signature).is_ok()
744            }
745            // Mismatched key types
746            _ => false,
747        }
748    }
749}
750
751impl FromStr for Signature {
752    type Err = ParseKeyError;
753
754    fn from_str(s: &str) -> Result<Self, Self::Err> {
755        let (key_type, data_str) = s.split_once(':').ok_or(ParseKeyError::InvalidFormat)?;
756
757        let key_type = match key_type {
758            "ed25519" => KeyType::Ed25519,
759            "secp256k1" => KeyType::Secp256k1,
760            other => return Err(ParseKeyError::UnknownKeyType(other.to_string())),
761        };
762
763        let data = bs58::decode(data_str)
764            .into_vec()
765            .map_err(|e| ParseKeyError::InvalidBase58(e.to_string()))?;
766
767        if data.len() != key_type.signature_len() {
768            return Err(ParseKeyError::InvalidLength {
769                expected: key_type.signature_len(),
770                actual: data.len(),
771            });
772        }
773
774        match key_type {
775            KeyType::Ed25519 => {
776                let bytes: [u8; 64] = data
777                    .as_slice()
778                    .try_into()
779                    .map_err(|_| ParseKeyError::InvalidFormat)?;
780                Ok(Self::Ed25519(bytes))
781            }
782            KeyType::Secp256k1 => {
783                let bytes: [u8; 65] = data
784                    .as_slice()
785                    .try_into()
786                    .map_err(|_| ParseKeyError::InvalidFormat)?;
787                Ok(Self::Secp256k1(bytes))
788            }
789        }
790    }
791}
792
793impl Display for Signature {
794    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
795        write!(
796            f,
797            "{}:{}",
798            self.key_type().as_str(),
799            bs58::encode(self.as_bytes()).into_string()
800        )
801    }
802}
803
804impl Debug for Signature {
805    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
806        write!(f, "Signature({})", self)
807    }
808}
809
810impl BorshSerialize for Signature {
811    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
812        borsh::BorshSerialize::serialize(&(self.key_type() as u8), writer)?;
813        writer.write_all(self.as_bytes())?;
814        Ok(())
815    }
816}
817
818impl BorshDeserialize for Signature {
819    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
820        let key_type_byte = u8::deserialize_reader(reader)?;
821        let key_type = KeyType::try_from(key_type_byte)
822            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
823
824        match key_type {
825            KeyType::Ed25519 => {
826                let mut bytes = [0u8; 64];
827                reader.read_exact(&mut bytes)?;
828                Ok(Self::Ed25519(bytes))
829            }
830            KeyType::Secp256k1 => {
831                let mut bytes = [0u8; 65];
832                reader.read_exact(&mut bytes)?;
833                Ok(Self::Secp256k1(bytes))
834            }
835        }
836    }
837}
838
839// ============================================================================
840// Seed Phrase Generation
841// ============================================================================
842
843/// Generate a random BIP-39 seed phrase.
844///
845/// # Arguments
846///
847/// * `word_count` - Number of words (12, 15, 18, 21, or 24)
848///
849/// # Example
850///
851/// ```rust
852/// use near_kit::generate_seed_phrase;
853///
854/// let phrase = generate_seed_phrase(12).unwrap();
855/// assert_eq!(phrase.split_whitespace().count(), 12);
856/// ```
857pub fn generate_seed_phrase(word_count: usize) -> Result<String, SignerError> {
858    use rand::RngCore;
859
860    // Word count to entropy bytes: 12->16, 15->20, 18->24, 21->28, 24->32
861    let entropy_bytes = match word_count {
862        12 => 16,
863        15 => 20,
864        18 => 24,
865        21 => 28,
866        24 => 32,
867        _ => {
868            return Err(SignerError::KeyDerivationFailed(format!(
869                "Invalid word count: {}. Must be 12, 15, 18, 21, or 24",
870                word_count
871            )));
872        }
873    };
874
875    let mut entropy = vec![0u8; entropy_bytes];
876    OsRng.fill_bytes(&mut entropy);
877
878    let mnemonic = Mnemonic::from_entropy(&entropy).map_err(|e| {
879        SignerError::KeyDerivationFailed(format!("Failed to generate mnemonic: {}", e))
880    })?;
881
882    Ok(mnemonic.to_string())
883}
884
885// ============================================================================
886// KeyPair
887// ============================================================================
888
889/// A cryptographic key pair (secret key + public key).
890///
891/// This is a convenience type that bundles a [`SecretKey`] with its derived
892/// [`PublicKey`] for situations where you need both (e.g., creating accounts,
893/// adding access keys).
894///
895/// # Example
896///
897/// ```rust
898/// use near_kit::KeyPair;
899///
900/// // Generate a random Ed25519 key pair
901/// let keypair = KeyPair::random();
902/// println!("Public key: {}", keypair.public_key);
903/// println!("Secret key: {}", keypair.secret_key);
904///
905/// // Use with account creation
906/// // near.transaction("new.alice.testnet")
907/// //     .create_account()
908/// //     .add_full_access_key(keypair.public_key)
909/// //     .send()
910/// //     .await?;
911/// ```
912#[derive(Clone)]
913pub struct KeyPair {
914    /// The secret (private) key.
915    pub secret_key: SecretKey,
916    /// The public key derived from the secret key.
917    pub public_key: PublicKey,
918}
919
920impl KeyPair {
921    /// Generate a random Ed25519 key pair.
922    ///
923    /// This is the most common key type for NEAR.
924    ///
925    /// # Example
926    ///
927    /// ```rust
928    /// use near_kit::KeyPair;
929    ///
930    /// let keypair = KeyPair::random();
931    /// ```
932    pub fn random() -> Self {
933        Self::random_ed25519()
934    }
935
936    /// Generate a random Ed25519 key pair.
937    ///
938    /// # Example
939    ///
940    /// ```rust
941    /// use near_kit::KeyPair;
942    ///
943    /// let keypair = KeyPair::random_ed25519();
944    /// assert!(keypair.public_key.to_string().starts_with("ed25519:"));
945    /// ```
946    pub fn random_ed25519() -> Self {
947        let secret_key = SecretKey::generate_ed25519();
948        let public_key = secret_key.public_key();
949        Self {
950            secret_key,
951            public_key,
952        }
953    }
954
955    /// Generate a random Secp256k1 key pair.
956    ///
957    /// # Example
958    ///
959    /// ```rust
960    /// use near_kit::KeyPair;
961    ///
962    /// let keypair = KeyPair::random_secp256k1();
963    /// assert!(keypair.public_key.to_string().starts_with("secp256k1:"));
964    /// ```
965    pub fn random_secp256k1() -> Self {
966        let secret_key = SecretKey::generate_secp256k1();
967        let public_key = secret_key.public_key();
968        Self {
969            secret_key,
970            public_key,
971        }
972    }
973
974    /// Create a key pair from an existing secret key.
975    ///
976    /// # Example
977    ///
978    /// ```rust
979    /// use near_kit::{KeyPair, SecretKey};
980    ///
981    /// let secret_key: SecretKey = "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr".parse().unwrap();
982    /// let keypair = KeyPair::from_secret_key(secret_key);
983    /// ```
984    pub fn from_secret_key(secret_key: SecretKey) -> Self {
985        let public_key = secret_key.public_key();
986        Self {
987            secret_key,
988            public_key,
989        }
990    }
991
992    /// Create a key pair from a seed phrase using the default NEAR HD path.
993    ///
994    /// # Example
995    ///
996    /// ```rust
997    /// use near_kit::KeyPair;
998    ///
999    /// let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
1000    /// let keypair = KeyPair::from_seed_phrase(phrase).unwrap();
1001    /// ```
1002    pub fn from_seed_phrase(phrase: impl AsRef<str>) -> Result<Self, SignerError> {
1003        let secret_key = SecretKey::from_seed_phrase(phrase)?;
1004        Ok(Self::from_secret_key(secret_key))
1005    }
1006
1007    /// Generate a new random key pair with a seed phrase for backup.
1008    ///
1009    /// Returns the seed phrase (for backup) and the key pair.
1010    ///
1011    /// # Example
1012    ///
1013    /// ```rust
1014    /// use near_kit::KeyPair;
1015    ///
1016    /// let (phrase, keypair) = KeyPair::random_with_seed_phrase().unwrap();
1017    /// println!("Backup your seed phrase: {}", phrase);
1018    /// println!("Public key: {}", keypair.public_key);
1019    /// ```
1020    pub fn random_with_seed_phrase() -> Result<(String, Self), SignerError> {
1021        let (phrase, secret_key) = SecretKey::generate_with_seed_phrase()?;
1022        Ok((phrase, Self::from_secret_key(secret_key)))
1023    }
1024}
1025
1026impl std::fmt::Debug for KeyPair {
1027    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1028        f.debug_struct("KeyPair")
1029            .field("public_key", &self.public_key)
1030            .field("secret_key", &"***")
1031            .finish()
1032    }
1033}
1034
1035#[cfg(test)]
1036mod tests {
1037    use super::*;
1038
1039    #[test]
1040    fn test_generate_and_sign() {
1041        let secret = SecretKey::generate_ed25519();
1042        let public = secret.public_key();
1043        let message = b"hello world";
1044
1045        let signature = secret.sign(message);
1046        assert!(signature.verify(message, &public));
1047        assert!(!signature.verify(b"wrong message", &public));
1048    }
1049
1050    #[test]
1051    fn test_public_key_roundtrip() {
1052        let secret = SecretKey::generate_ed25519();
1053        let public = secret.public_key();
1054        let s = public.to_string();
1055        let parsed: PublicKey = s.parse().unwrap();
1056        assert_eq!(public, parsed);
1057    }
1058
1059    #[test]
1060    fn test_secret_key_roundtrip() {
1061        let secret = SecretKey::generate_ed25519();
1062        let s = secret.to_string();
1063        let parsed: SecretKey = s.parse().unwrap();
1064        assert_eq!(secret.public_key(), parsed.public_key());
1065    }
1066
1067    #[test]
1068    fn test_ed25519_secret_key_64_byte_expanded_form() {
1069        // Generate a key, get its 32-byte seed, then construct a 64-byte expanded form
1070        // (seed || public_key) which near-cli sometimes stores
1071        let secret = SecretKey::generate_ed25519();
1072        let public = secret.public_key();
1073        let seed_bytes = secret.as_bytes();
1074
1075        // Construct 64-byte expanded key: seed (32) + public key bytes (32)
1076        let mut expanded = Vec::with_capacity(64);
1077        expanded.extend_from_slice(seed_bytes);
1078        expanded.extend_from_slice(public.as_bytes());
1079        let expanded_b58 = bs58::encode(&expanded).into_string();
1080        let expanded_str = format!("ed25519:{}", expanded_b58);
1081
1082        // Parse the 64-byte form — should succeed and produce same public key
1083        let parsed: SecretKey = expanded_str.parse().unwrap();
1084        assert_eq!(parsed.public_key(), public);
1085
1086        // Re-serializing yields the 32-byte seed form
1087        let reserialized = parsed.to_string();
1088        assert_eq!(reserialized, secret.to_string());
1089    }
1090
1091    // ========================================================================
1092    // Seed Phrase Tests
1093    // ========================================================================
1094
1095    #[test]
1096    fn test_generate_seed_phrase_12_words() {
1097        let phrase = generate_seed_phrase(12).unwrap();
1098        assert_eq!(phrase.split_whitespace().count(), 12);
1099    }
1100
1101    #[test]
1102    fn test_generate_seed_phrase_24_words() {
1103        let phrase = generate_seed_phrase(24).unwrap();
1104        assert_eq!(phrase.split_whitespace().count(), 24);
1105    }
1106
1107    #[test]
1108    fn test_generate_seed_phrase_invalid_word_count() {
1109        let result = generate_seed_phrase(13);
1110        assert!(result.is_err());
1111    }
1112
1113    // Valid BIP-39 test vector (from official test vectors)
1114    const TEST_PHRASE: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
1115
1116    #[test]
1117    fn test_from_seed_phrase_known_vector() {
1118        // Test vector: known seed phrase should produce consistent key
1119        let secret_key = SecretKey::from_seed_phrase(TEST_PHRASE).unwrap();
1120
1121        // Same phrase should always produce the same key
1122        let secret_key2 = SecretKey::from_seed_phrase(TEST_PHRASE).unwrap();
1123        assert_eq!(secret_key.public_key(), secret_key2.public_key());
1124    }
1125
1126    #[test]
1127    fn test_from_seed_phrase_whitespace_normalization() {
1128        let phrase1 = TEST_PHRASE;
1129        let phrase2 = "  abandon   abandon  abandon abandon abandon abandon abandon abandon abandon abandon abandon about  ";
1130        let phrase3 = "ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABOUT";
1131
1132        let key1 = SecretKey::from_seed_phrase(phrase1).unwrap();
1133        let key2 = SecretKey::from_seed_phrase(phrase2).unwrap();
1134        let key3 = SecretKey::from_seed_phrase(phrase3).unwrap();
1135
1136        assert_eq!(key1.public_key(), key2.public_key());
1137        assert_eq!(key1.public_key(), key3.public_key());
1138    }
1139
1140    #[test]
1141    fn test_from_seed_phrase_invalid() {
1142        let result = SecretKey::from_seed_phrase("invalid words that are not a mnemonic");
1143        assert!(result.is_err());
1144    }
1145
1146    #[test]
1147    fn test_from_seed_phrase_different_paths() {
1148        let key1 = SecretKey::from_seed_phrase_with_path(TEST_PHRASE, "m/44'/397'/0'").unwrap();
1149        let key2 = SecretKey::from_seed_phrase_with_path(TEST_PHRASE, "m/44'/397'/1'").unwrap();
1150
1151        // Different paths should produce different keys
1152        assert_ne!(key1.public_key(), key2.public_key());
1153    }
1154
1155    #[test]
1156    fn test_from_seed_phrase_with_passphrase() {
1157        let key_no_pass = SecretKey::from_seed_phrase_with_path_and_passphrase(
1158            TEST_PHRASE,
1159            DEFAULT_HD_PATH,
1160            None,
1161        )
1162        .unwrap();
1163
1164        let key_with_pass = SecretKey::from_seed_phrase_with_path_and_passphrase(
1165            TEST_PHRASE,
1166            DEFAULT_HD_PATH,
1167            Some("my-password"),
1168        )
1169        .unwrap();
1170
1171        // Passphrase should produce different key
1172        assert_ne!(key_no_pass.public_key(), key_with_pass.public_key());
1173    }
1174
1175    #[test]
1176    fn test_generate_with_seed_phrase() {
1177        let (phrase, secret_key) = SecretKey::generate_with_seed_phrase().unwrap();
1178
1179        // Phrase should be 12 words
1180        assert_eq!(phrase.split_whitespace().count(), 12);
1181
1182        // Re-deriving from the phrase should produce the same key
1183        let derived = SecretKey::from_seed_phrase(&phrase).unwrap();
1184        assert_eq!(secret_key.public_key(), derived.public_key());
1185    }
1186
1187    #[test]
1188    fn test_generate_with_seed_phrase_24_words() {
1189        let (phrase, secret_key) = SecretKey::generate_with_seed_phrase_words(24).unwrap();
1190
1191        assert_eq!(phrase.split_whitespace().count(), 24);
1192
1193        let derived = SecretKey::from_seed_phrase(&phrase).unwrap();
1194        assert_eq!(secret_key.public_key(), derived.public_key());
1195    }
1196
1197    #[test]
1198    fn test_seed_phrase_key_can_sign() {
1199        let secret_key = SecretKey::from_seed_phrase(TEST_PHRASE).unwrap();
1200
1201        let message = b"test message";
1202        let signature = secret_key.sign(message);
1203        let public_key = secret_key.public_key();
1204
1205        assert!(signature.verify(message, &public_key));
1206    }
1207
1208    // ========================================================================
1209    // Curve Point Validation Tests
1210    // ========================================================================
1211
1212    #[test]
1213    fn test_secp256k1_invalid_curve_point_rejected() {
1214        // This is the invalid key from the NEAR SDK docs that was identified as not being
1215        // on the secp256k1 curve. See: https://github.com/near/near-sdk-rs/pull/1469
1216        let invalid_key = "secp256k1:qMoRgcoXai4mBPsdbHi1wfyxF9TdbPCF4qSDQTRP3TfescSRoUdSx6nmeQoN3aiwGzwMyGXAb1gUjBTv5AY8DXj";
1217        let result: Result<PublicKey, _> = invalid_key.parse();
1218        assert!(result.is_err());
1219        assert!(matches!(
1220            result.unwrap_err(),
1221            ParseKeyError::InvalidCurvePoint | ParseKeyError::InvalidLength { .. }
1222        ));
1223    }
1224
1225    #[test]
1226    fn test_secp256k1_valid_curve_point_accepted() {
1227        // Valid secp256k1 key from near-sdk-js (verified to be on the curve)
1228        // This key is 64 bytes (uncompressed without prefix) — matches our new format
1229        let valid_key = "secp256k1:5r22SrjrDvgY3wdQsnjgxkeAbU1VcM71FYvALEQWihjM3Xk4Be1CpETTqFccChQr4iJwDroSDVmgaWZv2AcXvYeL";
1230        let result: Result<PublicKey, _> = valid_key.parse();
1231        // This key is now parseable because we expect 64-byte uncompressed format
1232        assert!(result.is_ok());
1233    }
1234
1235    #[test]
1236    fn test_ed25519_valid_key_accepted() {
1237        // Valid ed25519 public key
1238        let valid_key = "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp";
1239        let result: Result<PublicKey, _> = valid_key.parse();
1240        assert!(result.is_ok());
1241    }
1242
1243    #[test]
1244    fn test_ed25519_invalid_curve_point_rejected() {
1245        // The high bit of the last byte being set with an invalid x-coordinate recovery
1246        // should produce an invalid point. Specifically, a y-coordinate that when
1247        // the x is computed results in a non-square (no valid x exists).
1248        // This specific byte sequence has been verified to fail ed25519 decompression.
1249        //
1250        // Note: ed25519_dalek may accept many byte patterns as valid curve points.
1251        // Ed25519 point decompression is very permissive - most 32-byte sequences
1252        // decode to valid points.
1253        let invalid_bytes = [
1254            0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1255            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1256            0xFF, 0xFF, 0xFF, 0x7F,
1257        ];
1258        let encoded = bs58::encode(&invalid_bytes).into_string();
1259        let invalid_key = format!("ed25519:{}", encoded);
1260        let result: Result<PublicKey, _> = invalid_key.parse();
1261        if let Err(err) = result {
1262            assert!(matches!(err, ParseKeyError::InvalidCurvePoint));
1263        } else {
1264            // If ed25519_dalek accepts this, we should skip this test case
1265            eprintln!(
1266                "Note: ed25519 point decompression accepted test bytes - validation may be too lenient"
1267            );
1268        }
1269    }
1270
1271    #[test]
1272    fn test_borsh_deserialize_validates_curve_point() {
1273        use borsh::BorshDeserialize;
1274
1275        // Test with secp256k1 since ed25519 validation is very lenient
1276        // Use invalid secp256k1 bytes (all zeros is definitely not on the curve)
1277        let mut invalid_bytes = vec![1u8]; // KeyType::Secp256k1
1278        invalid_bytes.extend_from_slice(&[0u8; 64]); // Invalid curve point (64 bytes now)
1279
1280        let result = PublicKey::try_from_slice(&invalid_bytes);
1281        assert!(result.is_err());
1282    }
1283
1284    #[test]
1285    fn test_signature_from_str_roundtrip() {
1286        let sig_str = "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5";
1287        let sig: Signature = sig_str.parse().unwrap();
1288        assert_eq!(sig.key_type(), KeyType::Ed25519);
1289        assert_eq!(sig.as_bytes().len(), 64);
1290        assert_eq!(sig.to_string(), sig_str);
1291    }
1292
1293    #[test]
1294    fn test_signature_from_str_invalid_format() {
1295        assert!("no_colon".parse::<Signature>().is_err());
1296        assert!("unknown:abc".parse::<Signature>().is_err());
1297        assert!("ed25519:invalid!!!".parse::<Signature>().is_err());
1298        assert!("ed25519:AAAA".parse::<Signature>().is_err()); // too short
1299    }
1300
1301    #[test]
1302    fn test_signature_serde_roundtrip() {
1303        let sig_str = "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5";
1304        let sig: Signature = sig_str.parse().unwrap();
1305        let json = serde_json::to_value(&sig).unwrap();
1306        assert_eq!(json.as_str().unwrap(), sig_str);
1307        let parsed: Signature = serde_json::from_value(json).unwrap();
1308        assert_eq!(sig, parsed);
1309    }
1310
1311    // ========================================================================
1312    // Secp256k1 Tests
1313    // ========================================================================
1314
1315    #[test]
1316    fn test_secp256k1_generate_and_sign_verify() {
1317        let secret = SecretKey::generate_secp256k1();
1318        let public = secret.public_key();
1319        let message = b"hello world";
1320
1321        assert_eq!(secret.key_type(), KeyType::Secp256k1);
1322        assert_eq!(public.key_type(), KeyType::Secp256k1);
1323
1324        let signature = secret.sign(message);
1325        assert_eq!(signature.key_type(), KeyType::Secp256k1);
1326        assert_eq!(signature.as_bytes().len(), 65);
1327
1328        assert!(signature.verify(message, &public));
1329        assert!(!signature.verify(b"wrong message", &public));
1330    }
1331
1332    #[test]
1333    fn test_secp256k1_public_key_is_64_bytes() {
1334        let secret = SecretKey::generate_secp256k1();
1335        let public = secret.public_key();
1336
1337        // Public key should be 64 bytes (uncompressed without prefix)
1338        let pk_bytes = public.as_secp256k1_bytes().unwrap();
1339        assert_eq!(pk_bytes.len(), 64);
1340    }
1341
1342    #[test]
1343    fn test_secp256k1_secret_key_to_public_key_derivation() {
1344        // Deterministic: same secret key bytes should always produce the same public key
1345        let bytes = [42u8; 32];
1346        let sk1 = SecretKey::secp256k1_from_bytes(bytes).unwrap();
1347        let sk2 = SecretKey::secp256k1_from_bytes(bytes).unwrap();
1348        assert_eq!(sk1.public_key(), sk2.public_key());
1349
1350        // Different secret keys produce different public keys
1351        let bytes2 = [43u8; 32];
1352        let sk3 = SecretKey::secp256k1_from_bytes(bytes2).unwrap();
1353        assert_ne!(sk1.public_key(), sk3.public_key());
1354    }
1355
1356    #[test]
1357    fn test_secp256k1_public_key_string_roundtrip() {
1358        let secret = SecretKey::generate_secp256k1();
1359        let public = secret.public_key();
1360        let s = public.to_string();
1361        assert!(s.starts_with("secp256k1:"));
1362        let parsed: PublicKey = s.parse().unwrap();
1363        assert_eq!(public, parsed);
1364    }
1365
1366    #[test]
1367    fn test_secp256k1_secret_key_string_roundtrip() {
1368        let secret = SecretKey::generate_secp256k1();
1369        let s = secret.to_string();
1370        assert!(s.starts_with("secp256k1:"));
1371        let parsed: SecretKey = s.parse().unwrap();
1372        assert_eq!(secret.public_key(), parsed.public_key());
1373    }
1374
1375    #[test]
1376    fn test_secp256k1_keypair_random() {
1377        let keypair = KeyPair::random_secp256k1();
1378        assert_eq!(keypair.public_key.key_type(), KeyType::Secp256k1);
1379        assert_eq!(keypair.secret_key.key_type(), KeyType::Secp256k1);
1380        assert!(keypair.public_key.to_string().starts_with("secp256k1:"));
1381
1382        // Sign/verify through keypair
1383        let message = b"keypair test";
1384        let signature = keypair.secret_key.sign(message);
1385        assert!(signature.verify(message, &keypair.public_key));
1386    }
1387
1388    #[test]
1389    fn test_secp256k1_cross_type_verify_fails() {
1390        // Ed25519 signature should not verify against secp256k1 key
1391        let ed_secret = SecretKey::generate_ed25519();
1392        let secp_secret = SecretKey::generate_secp256k1();
1393
1394        let message = b"cross type test";
1395        let ed_sig = ed_secret.sign(message);
1396        let secp_sig = secp_secret.sign(message);
1397
1398        assert!(!ed_sig.verify(message, &secp_secret.public_key()));
1399        assert!(!secp_sig.verify(message, &ed_secret.public_key()));
1400    }
1401
1402    #[test]
1403    fn test_secp256k1_invalid_scalar_rejected() {
1404        // Zero scalar is invalid for secp256k1
1405        let zero_bytes = [0u8; 32];
1406        let result = SecretKey::secp256k1_from_bytes(zero_bytes);
1407        assert!(result.is_err());
1408        assert!(matches!(result.unwrap_err(), ParseKeyError::InvalidScalar));
1409    }
1410
1411    #[test]
1412    fn test_secp256k1_invalid_scalar_rejected_from_str() {
1413        // Construct a secp256k1 secret key string with zero bytes (invalid scalar)
1414        let zero_bytes = [0u8; 32];
1415        let encoded = bs58::encode(&zero_bytes).into_string();
1416        let key_str = format!("secp256k1:{}", encoded);
1417        let result: Result<SecretKey, _> = key_str.parse();
1418        assert!(result.is_err());
1419        assert!(matches!(result.unwrap_err(), ParseKeyError::InvalidScalar));
1420    }
1421
1422    #[test]
1423    fn test_secp256k1_invalid_recovery_id_rejected() {
1424        let secret = SecretKey::generate_secp256k1();
1425        let public = secret.public_key();
1426        let message = b"test message";
1427        let signature = secret.sign(message);
1428
1429        // Create a tampered signature with invalid recovery id
1430        if let Signature::Secp256k1(mut sig_bytes) = signature.clone() {
1431            sig_bytes[64] = 4; // valid range is 0..=3
1432            let tampered = Signature::Secp256k1(sig_bytes);
1433            assert!(!tampered.verify(message, &public));
1434
1435            sig_bytes[64] = 255;
1436            let tampered = Signature::Secp256k1(sig_bytes);
1437            assert!(!tampered.verify(message, &public));
1438        } else {
1439            panic!("Expected Secp256k1 signature");
1440        }
1441    }
1442
1443    #[test]
1444    fn test_secp256k1_from_compressed() {
1445        let secret = SecretKey::generate_secp256k1();
1446        let public = secret.public_key();
1447
1448        // Get the uncompressed bytes and create a compressed version
1449        let pk_bytes = public.as_secp256k1_bytes().unwrap();
1450        let mut uncompressed = [0u8; 65];
1451        uncompressed[0] = 0x04;
1452        uncompressed[1..].copy_from_slice(pk_bytes);
1453        let encoded =
1454            k256::EncodedPoint::from_bytes(uncompressed.as_ref()).expect("valid encoded point");
1455        let point = k256::AffinePoint::from_encoded_point(&encoded).expect("valid point on curve");
1456        let compressed = point.to_encoded_point(true);
1457        let compressed_bytes: [u8; 33] = compressed
1458            .as_bytes()
1459            .try_into()
1460            .expect("compressed should be 33 bytes");
1461
1462        // Reconstruct from compressed form
1463        let public2 = PublicKey::secp256k1_from_compressed(compressed_bytes);
1464        assert_eq!(public, public2);
1465    }
1466
1467    #[test]
1468    fn test_ed25519_borsh_roundtrip() {
1469        use borsh::BorshDeserialize;
1470
1471        let secret = SecretKey::generate_ed25519();
1472        let public = secret.public_key();
1473        let serialized = borsh::to_vec(&public).unwrap();
1474        // Ed25519: 1 byte key type + 32 bytes key data
1475        assert_eq!(serialized.len(), 33);
1476        assert_eq!(serialized[0], 0); // Ed25519
1477        let deserialized = PublicKey::try_from_slice(&serialized).unwrap();
1478        assert_eq!(public, deserialized);
1479    }
1480
1481    #[test]
1482    fn test_secp256k1_borsh_roundtrip() {
1483        use borsh::BorshDeserialize;
1484
1485        let secret = SecretKey::generate_secp256k1();
1486        let public = secret.public_key();
1487        let serialized = borsh::to_vec(&public).unwrap();
1488        // Secp256k1: 1 byte key type + 64 bytes key data
1489        assert_eq!(serialized.len(), 65);
1490        assert_eq!(serialized[0], 1); // Secp256k1
1491        let deserialized = PublicKey::try_from_slice(&serialized).unwrap();
1492        assert_eq!(public, deserialized);
1493    }
1494
1495    #[test]
1496    fn test_signature_borsh_roundtrip() {
1497        use borsh::BorshDeserialize;
1498
1499        let secret = SecretKey::generate_ed25519();
1500        let sig = secret.sign(b"test");
1501        let serialized = borsh::to_vec(&sig).unwrap();
1502        assert_eq!(serialized.len(), 65); // 1 + 64
1503        let deserialized = Signature::try_from_slice(&serialized).unwrap();
1504        assert_eq!(sig, deserialized);
1505    }
1506
1507    #[test]
1508    fn test_enum_variants_match_expected_types() {
1509        let ed_secret = SecretKey::generate_ed25519();
1510        let ed_public = ed_secret.public_key();
1511
1512        assert!(matches!(ed_public, PublicKey::Ed25519(_)));
1513        assert!(matches!(ed_secret, SecretKey::Ed25519(_)));
1514
1515        let secp_secret = SecretKey::generate_secp256k1();
1516        let secp_public = secp_secret.public_key();
1517
1518        assert!(matches!(secp_public, PublicKey::Secp256k1(_)));
1519        assert!(matches!(secp_secret, SecretKey::Secp256k1(_)));
1520    }
1521}