Skip to main content

aptos_sdk/account/
secp256k1.rs

1//! Secp256k1 account implementation.
2
3use crate::account::account::{Account, AuthenticationKey};
4use crate::crypto::{
5    SINGLE_KEY_SCHEME, Secp256k1PrivateKey, Secp256k1PublicKey, derive_authentication_key,
6};
7use crate::error::AptosResult;
8use crate::types::AccountAddress;
9use std::fmt;
10
11/// A Secp256k1 ECDSA account for signing transactions.
12///
13/// This account type uses the same elliptic curve as Bitcoin and Ethereum.
14///
15/// # Example
16///
17/// ```rust
18/// use aptos_sdk::account::Secp256k1Account;
19///
20/// let account = Secp256k1Account::generate();
21/// println!("Address: {}", account.address());
22/// ```
23#[derive(Clone)]
24pub struct Secp256k1Account {
25    private_key: Secp256k1PrivateKey,
26    public_key: Secp256k1PublicKey,
27    address: AccountAddress,
28}
29
30impl Secp256k1Account {
31    /// Generates a new random Secp256k1 account.
32    pub fn generate() -> Self {
33        let private_key = Secp256k1PrivateKey::generate();
34        Self::from_private_key(private_key)
35    }
36
37    /// Creates an account from a private key.
38    pub fn from_private_key(private_key: Secp256k1PrivateKey) -> Self {
39        let public_key = private_key.public_key();
40        let address = public_key.to_address();
41        Self {
42            private_key,
43            public_key,
44            address,
45        }
46    }
47
48    /// Creates an account from private key bytes.
49    ///
50    /// # Errors
51    ///
52    /// Returns an error if the bytes are not a valid Secp256k1 private key (must be exactly 32 bytes and a valid curve point).
53    pub fn from_private_key_bytes(bytes: &[u8]) -> AptosResult<Self> {
54        let private_key = Secp256k1PrivateKey::from_bytes(bytes)?;
55        Ok(Self::from_private_key(private_key))
56    }
57
58    /// Creates an account from a private key hex string.
59    ///
60    /// # Errors
61    ///
62    /// This function will return an error if:
63    /// - The hex string is invalid or cannot be decoded
64    /// - The decoded bytes are not a valid Secp256k1 private key
65    pub fn from_private_key_hex(hex_str: &str) -> AptosResult<Self> {
66        let private_key = Secp256k1PrivateKey::from_hex(hex_str)?;
67        Ok(Self::from_private_key(private_key))
68    }
69
70    /// Returns the account address.
71    pub fn address(&self) -> AccountAddress {
72        self.address
73    }
74
75    /// Returns the public key.
76    pub fn public_key(&self) -> &Secp256k1PublicKey {
77        &self.public_key
78    }
79
80    /// Returns a reference to the private key.
81    pub fn private_key(&self) -> &Secp256k1PrivateKey {
82        &self.private_key
83    }
84
85    /// Signs a message and returns the Secp256k1 signature.
86    pub fn sign_message(&self, message: &[u8]) -> crate::crypto::Secp256k1Signature {
87        self.private_key.sign(message)
88    }
89}
90
91impl Account for Secp256k1Account {
92    fn address(&self) -> AccountAddress {
93        self.address
94    }
95
96    fn authentication_key(&self) -> AuthenticationKey {
97        // BCS format: variant_byte || ULEB128(65) || 65 bytes SEC1 uncompressed.
98        // The chain's auth-key derivation canonicalises the public key through
99        // `libsecp256k1::PublicKey::serialize()` (always 65 bytes), so the
100        // address that the chain associates with this account is computed
101        // from the same 65-byte representation.
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(0x01); // Secp256k1Ecdsa variant
105        bcs_bytes.push(65); // ULEB128(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        // Return BCS-serialized `AnySignature::Secp256k1Ecdsa`
113        // (variant=1, ULEB128(64), 64 raw signature bytes). The chain's
114        // `SingleKeyAuthenticator.signature` field expects exactly this layout.
115        let sig = self.private_key.sign(message).to_bytes().to_vec();
116        debug_assert_eq!(
117            sig.len(),
118            64,
119            "Secp256k1 signature must be exactly 64 bytes (R || S)"
120        );
121        let mut out = Vec::with_capacity(1 + 1 + sig.len());
122        out.push(0x01); // AnySignature::Secp256k1Ecdsa variant
123        out.push(64); // ULEB128(64)
124        out.extend_from_slice(&sig);
125        Ok(out)
126    }
127
128    fn public_key_bytes(&self) -> Vec<u8> {
129        // BCS-serialized `AnyPublicKey::Secp256k1Ecdsa` (variant=1, ULEB128(65), 65 bytes
130        // SEC1 uncompressed). The chain re-serialises into the same 65-byte form when
131        // computing auth keys, so emitting 65 bytes here gives the chain a byte sequence
132        // it will canonicalize identically.
133        let uncompressed = self.public_key.to_uncompressed_bytes();
134        let mut out = Vec::with_capacity(1 + 1 + uncompressed.len());
135        out.push(0x01); // AnyPublicKey::Secp256k1Ecdsa variant
136        out.push(65); // ULEB128(65)
137        out.extend_from_slice(&uncompressed);
138        out
139    }
140
141    fn signature_scheme(&self) -> u8 {
142        SINGLE_KEY_SCHEME
143    }
144}
145
146impl fmt::Debug for Secp256k1Account {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        f.debug_struct("Secp256k1Account")
149            .field("address", &self.address)
150            .field("public_key", &self.public_key)
151            .finish_non_exhaustive()
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use crate::account::Account;
159
160    #[test]
161    fn test_generate() {
162        let account = Secp256k1Account::generate();
163        assert!(!account.address().is_zero());
164    }
165
166    #[test]
167    fn test_from_private_key_roundtrip() {
168        let account = Secp256k1Account::generate();
169        let bytes = account.private_key().to_bytes();
170
171        let restored = Secp256k1Account::from_private_key_bytes(&bytes).unwrap();
172        assert_eq!(account.address(), restored.address());
173    }
174
175    #[test]
176    fn test_sign_and_verify() {
177        let account = Secp256k1Account::generate();
178        let message = b"hello world";
179
180        let signature = account.sign_message(message);
181        assert!(account.public_key().verify(message, &signature).is_ok());
182    }
183
184    #[test]
185    fn test_from_private_key() {
186        let original = Secp256k1Account::generate();
187        let private_key = original.private_key().clone();
188        let restored = Secp256k1Account::from_private_key(private_key);
189        assert_eq!(original.address(), restored.address());
190    }
191
192    #[test]
193    fn test_from_private_key_hex() {
194        let original = Secp256k1Account::generate();
195        let hex = original.private_key().to_hex();
196        let restored = Secp256k1Account::from_private_key_hex(&hex).unwrap();
197        assert_eq!(original.address(), restored.address());
198    }
199
200    #[test]
201    fn test_authentication_key() {
202        let account = Secp256k1Account::generate();
203        let auth_key = account.authentication_key();
204        assert_eq!(auth_key.as_bytes().len(), 32);
205    }
206
207    #[test]
208    fn test_public_key_bytes() {
209        let account = Secp256k1Account::generate();
210        let bytes = account.public_key_bytes();
211        // BCS(AnyPublicKey::Secp256k1Ecdsa): variant index 1 (wire byte 0x01) + ULEB128(65)
212        // + 65-byte SEC1 uncompressed (0x04 || X || Y). Total = 1 + 1 + 65 = 67 bytes.
213        assert_eq!(bytes.len(), 67);
214        assert_eq!(bytes[0], 0x01, "AnyPublicKey::Secp256k1Ecdsa variant tag");
215        assert_eq!(bytes[1], 65, "ULEB128(65)");
216        assert_eq!(bytes[2], 0x04, "SEC1 uncompressed marker");
217    }
218
219    #[test]
220    fn test_signature_scheme() {
221        let account = Secp256k1Account::generate();
222        assert_eq!(account.signature_scheme(), SINGLE_KEY_SCHEME);
223    }
224
225    #[test]
226    fn test_sign_trait() {
227        let account = Secp256k1Account::generate();
228        let message = b"test message";
229        let sig_bytes = account.sign(message).unwrap();
230        // Sign returns BCS(AnySignature::Secp256k1) = 1 + 1 + 64 = 66 bytes.
231        assert_eq!(sig_bytes.len(), 66);
232        assert_eq!(sig_bytes[0], 0x01, "AnySignature::Secp256k1 variant tag");
233        assert_eq!(sig_bytes[1], 64, "ULEB128(64)");
234    }
235
236    #[test]
237    fn test_debug_output() {
238        let account = Secp256k1Account::generate();
239        let debug = format!("{account:?}");
240        assert!(debug.contains("Secp256k1Account"));
241        assert!(debug.contains("address"));
242    }
243
244    #[test]
245    fn test_invalid_private_key_bytes() {
246        let result = Secp256k1Account::from_private_key_bytes(&[0u8; 16]);
247        assert!(result.is_err());
248    }
249
250    #[test]
251    fn test_invalid_private_key_hex() {
252        let result = Secp256k1Account::from_private_key_hex("invalid");
253        assert!(result.is_err());
254    }
255}