Skip to main content

aptos_sdk/account/
secp256r1.rs

1//! Secp256r1 (P-256) account implementation.
2//!
3//! Secp256r1, also known as P-256 or prime256v1, is commonly used in
4//! WebAuthn/Passkey implementations.
5
6use crate::account::account::{Account, AuthenticationKey};
7use crate::crypto::{
8    SINGLE_KEY_SCHEME, Secp256r1PrivateKey, Secp256r1PublicKey, derive_authentication_key,
9};
10use crate::error::AptosResult;
11use crate::types::AccountAddress;
12use std::fmt;
13
14/// A Secp256r1 (P-256) ECDSA account for signing transactions.
15///
16/// This account type uses the P-256 elliptic curve, which is commonly used
17/// in WebAuthn/Passkey implementations for browser-based authentication.
18///
19/// # Example
20///
21/// ```rust
22/// use aptos_sdk::account::Secp256r1Account;
23///
24/// let account = Secp256r1Account::generate();
25/// println!("Address: {}", account.address());
26/// ```
27#[derive(Clone)]
28pub struct Secp256r1Account {
29    private_key: Secp256r1PrivateKey,
30    public_key: Secp256r1PublicKey,
31    address: AccountAddress,
32}
33
34impl Secp256r1Account {
35    /// Generates a new random Secp256r1 account.
36    pub fn generate() -> Self {
37        let private_key = Secp256r1PrivateKey::generate();
38        Self::from_private_key(private_key)
39    }
40
41    /// Creates an account from a private key.
42    pub fn from_private_key(private_key: Secp256r1PrivateKey) -> Self {
43        let public_key = private_key.public_key();
44        let address = public_key.to_address();
45        Self {
46            private_key,
47            public_key,
48            address,
49        }
50    }
51
52    /// Creates an account from private key bytes.
53    ///
54    /// # Errors
55    ///
56    /// Returns an error if the bytes are not a valid Secp256r1 private key (must be exactly 32 bytes and a valid curve point).
57    pub fn from_private_key_bytes(bytes: &[u8]) -> AptosResult<Self> {
58        let private_key = Secp256r1PrivateKey::from_bytes(bytes)?;
59        Ok(Self::from_private_key(private_key))
60    }
61
62    /// Creates an account from a private key hex string.
63    ///
64    /// # Errors
65    ///
66    /// This function will return an error if:
67    /// - The hex string is invalid or cannot be decoded
68    /// - The decoded bytes are not a valid Secp256r1 private key
69    pub fn from_private_key_hex(hex_str: &str) -> AptosResult<Self> {
70        let private_key = Secp256r1PrivateKey::from_hex(hex_str)?;
71        Ok(Self::from_private_key(private_key))
72    }
73
74    /// Returns the account address.
75    pub fn address(&self) -> AccountAddress {
76        self.address
77    }
78
79    /// Returns the public key.
80    pub fn public_key(&self) -> &Secp256r1PublicKey {
81        &self.public_key
82    }
83
84    /// Returns a reference to the private key.
85    pub fn private_key(&self) -> &Secp256r1PrivateKey {
86        &self.private_key
87    }
88
89    /// Signs a message and returns the Secp256r1 signature.
90    pub fn sign_message(&self, message: &[u8]) -> crate::crypto::Secp256r1Signature {
91        self.private_key.sign(message)
92    }
93}
94
95impl Account for Secp256r1Account {
96    fn address(&self) -> AccountAddress {
97        self.address
98    }
99
100    fn authentication_key(&self) -> AuthenticationKey {
101        // Use correct BCS format: variant_byte || ULEB128(length) || uncompressed_public_key
102        let uncompressed = self.public_key.to_uncompressed_bytes();
103        let mut bcs_bytes = Vec::with_capacity(1 + 1 + uncompressed.len());
104        bcs_bytes.push(0x02); // Secp256r1 variant
105        bcs_bytes.push(65); // ULEB128(65) = 65
106        bcs_bytes.extend_from_slice(&uncompressed);
107        let key = derive_authentication_key(&bcs_bytes, SINGLE_KEY_SCHEME);
108        AuthenticationKey::new(key)
109    }
110
111    fn sign(&self, message: &[u8]) -> crate::error::AptosResult<Vec<u8>> {
112        Ok(self.private_key.sign(message).to_bytes().to_vec())
113    }
114
115    fn public_key_bytes(&self) -> Vec<u8> {
116        // Return uncompressed format (65 bytes) as required by Aptos protocol
117        self.public_key.to_uncompressed_bytes()
118    }
119
120    fn signature_scheme(&self) -> u8 {
121        SINGLE_KEY_SCHEME
122    }
123}
124
125impl fmt::Debug for Secp256r1Account {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        f.debug_struct("Secp256r1Account")
128            .field("address", &self.address)
129            .field("public_key", &self.public_key)
130            .finish_non_exhaustive()
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use crate::account::Account;
138
139    #[test]
140    fn test_generate() {
141        let account = Secp256r1Account::generate();
142        assert!(!account.address().is_zero());
143    }
144
145    #[test]
146    fn test_from_private_key_roundtrip() {
147        let account = Secp256r1Account::generate();
148        let bytes = account.private_key().to_bytes();
149
150        let restored = Secp256r1Account::from_private_key_bytes(&bytes).unwrap();
151        assert_eq!(account.address(), restored.address());
152    }
153
154    #[test]
155    fn test_sign_and_verify() {
156        let account = Secp256r1Account::generate();
157        let message = b"hello world";
158
159        let signature = account.sign_message(message);
160        assert!(account.public_key().verify(message, &signature).is_ok());
161    }
162
163    #[test]
164    fn test_account_trait() {
165        let account = Secp256r1Account::generate();
166        let message = b"test message";
167
168        // Test Account trait methods
169        let sig_bytes = account.sign(message).unwrap();
170        assert_eq!(sig_bytes.len(), 64); // P-256 signature is 64 bytes
171
172        let pub_key_bytes = account.public_key_bytes();
173        assert_eq!(pub_key_bytes.len(), 65); // Uncompressed P-256 pubkey is 65 bytes (required by Aptos protocol)
174
175        assert!(!account.authentication_key().as_bytes().is_empty());
176    }
177
178    #[test]
179    fn test_from_private_key() {
180        let original = Secp256r1Account::generate();
181        let private_key = original.private_key().clone();
182        let restored = Secp256r1Account::from_private_key(private_key);
183        assert_eq!(original.address(), restored.address());
184    }
185
186    #[test]
187    fn test_from_private_key_hex() {
188        let original = Secp256r1Account::generate();
189        let hex = original.private_key().to_hex();
190        let restored = Secp256r1Account::from_private_key_hex(&hex).unwrap();
191        assert_eq!(original.address(), restored.address());
192    }
193
194    #[test]
195    fn test_signature_scheme() {
196        let account = Secp256r1Account::generate();
197        assert_eq!(account.signature_scheme(), SINGLE_KEY_SCHEME);
198    }
199
200    #[test]
201    fn test_debug_output() {
202        let account = Secp256r1Account::generate();
203        let debug = format!("{account:?}");
204        assert!(debug.contains("Secp256r1Account"));
205        assert!(debug.contains("address"));
206    }
207
208    #[test]
209    fn test_invalid_private_key_bytes() {
210        let result = Secp256r1Account::from_private_key_bytes(&[0u8; 16]);
211        assert!(result.is_err());
212    }
213
214    #[test]
215    fn test_invalid_private_key_hex() {
216        let result = Secp256r1Account::from_private_key_hex("invalid");
217        assert!(result.is_err());
218    }
219}