Skip to main content

aptos_sdk/account/
ed25519.rs

1//! Ed25519 account implementations.
2//!
3//! This module provides two Ed25519 account types:
4//!
5//! - [`Ed25519Account`]: Uses the legacy Ed25519 authenticator (scheme 0).
6//!   This is the most common account type and is backwards compatible.
7//!
8//! - [`Ed25519SingleKeyAccount`]: Uses the modern `SingleKey` authenticator (scheme 2).
9//!   This format is more flexible and recommended for new implementations.
10//!
11//! **Note**: The two account types produce DIFFERENT addresses for the same private key
12//! because they use different authentication key derivation schemes.
13
14#[cfg(feature = "mnemonic")]
15use crate::account::Mnemonic;
16use crate::account::account::{Account, AuthenticationKey};
17use crate::crypto::{
18    ED25519_SCHEME, Ed25519PrivateKey, Ed25519PublicKey, SINGLE_KEY_SCHEME,
19    derive_authentication_key,
20};
21use crate::error::AptosResult;
22use crate::types::AccountAddress;
23use std::fmt;
24
25/// An Ed25519 account for signing transactions.
26///
27/// This is the most common account type on Aptos.
28///
29/// # Example
30///
31/// ```rust
32/// use aptos_sdk::account::Ed25519Account;
33///
34/// // Generate a new random account
35/// let account = Ed25519Account::generate();
36/// println!("Address: {}", account.address());
37/// ```
38#[derive(Clone)]
39pub struct Ed25519Account {
40    private_key: Ed25519PrivateKey,
41    public_key: Ed25519PublicKey,
42    address: AccountAddress,
43}
44
45impl Ed25519Account {
46    /// Generates a new random Ed25519 account.
47    pub fn generate() -> Self {
48        let private_key = Ed25519PrivateKey::generate();
49        Self::from_private_key(private_key)
50    }
51
52    /// Creates an account from a private key.
53    pub fn from_private_key(private_key: Ed25519PrivateKey) -> Self {
54        let public_key = private_key.public_key();
55        let address = public_key.to_address();
56        Self {
57            private_key,
58            public_key,
59            address,
60        }
61    }
62
63    /// Creates an account from private key bytes.
64    ///
65    /// # Errors
66    ///
67    /// Returns an error if the bytes are not a valid Ed25519 private key (must be exactly 32 bytes).
68    pub fn from_private_key_bytes(bytes: &[u8]) -> AptosResult<Self> {
69        let private_key = Ed25519PrivateKey::from_bytes(bytes)?;
70        Ok(Self::from_private_key(private_key))
71    }
72
73    /// Creates an account from a private key hex string.
74    ///
75    /// # Errors
76    ///
77    /// This function will return an error if:
78    /// - The hex string is invalid or cannot be decoded
79    /// - The decoded bytes are not a valid Ed25519 private key
80    pub fn from_private_key_hex(hex_str: &str) -> AptosResult<Self> {
81        let private_key = Ed25519PrivateKey::from_hex(hex_str)?;
82        Ok(Self::from_private_key(private_key))
83    }
84
85    /// Creates an account from a BIP-39 mnemonic phrase.
86    ///
87    /// Uses the standard Aptos derivation path: `m/44'/637'/0'/0'/index'`
88    ///
89    /// # Arguments
90    ///
91    /// * `mnemonic` - A BIP-39 mnemonic phrase (12, 15, 18, 21, or 24 words)
92    /// * `index` - The account index in the derivation path
93    ///
94    /// # Example
95    ///
96    /// ```rust,ignore
97    /// use aptos_sdk::account::Ed25519Account;
98    ///
99    /// let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
100    /// let account = Ed25519Account::from_mnemonic(mnemonic, 0).unwrap();
101    /// ```
102    ///
103    /// # Errors
104    ///
105    /// Returns an error if the mnemonic phrase is invalid or if key derivation fails.
106    #[cfg(feature = "mnemonic")]
107    pub fn from_mnemonic(mnemonic: &str, index: u32) -> AptosResult<Self> {
108        let mnemonic = Mnemonic::from_phrase(mnemonic)?;
109        let private_key = mnemonic.derive_ed25519_key(index)?;
110        Ok(Self::from_private_key(private_key))
111    }
112
113    /// Generates a new account with a random mnemonic.
114    ///
115    /// Returns both the account and the mnemonic phrase (for backup).
116    ///
117    /// # Errors
118    ///
119    /// Returns an error if mnemonic generation or key derivation fails.
120    #[cfg(feature = "mnemonic")]
121    pub fn generate_with_mnemonic() -> AptosResult<(Self, String)> {
122        let mnemonic = Mnemonic::generate(24)?;
123        let phrase = mnemonic.phrase().to_string();
124        let private_key = mnemonic.derive_ed25519_key(0)?;
125        let account = Self::from_private_key(private_key);
126        Ok((account, phrase))
127    }
128
129    /// Overrides the account address, keeping this key.
130    ///
131    /// By default the address is derived from the key. Use this for an account
132    /// whose on-chain address no longer matches its key — most commonly after an
133    /// authentication-key **rotation**, where a new key controls an existing
134    /// address. [`authentication_key`](Account::authentication_key) still
135    /// reflects this key (which is what the chain stores post-rotation), while
136    /// [`address`](Self::address) returns the overridden value.
137    #[must_use]
138    pub fn with_address(mut self, address: AccountAddress) -> Self {
139        self.address = address;
140        self
141    }
142
143    /// Returns the account address.
144    pub fn address(&self) -> AccountAddress {
145        self.address
146    }
147
148    /// Returns the public key.
149    pub fn public_key(&self) -> &Ed25519PublicKey {
150        &self.public_key
151    }
152
153    /// Returns a reference to the private key.
154    ///
155    /// **Warning**: Handle with care to avoid leaking sensitive key material.
156    pub fn private_key(&self) -> &Ed25519PrivateKey {
157        &self.private_key
158    }
159
160    /// Signs a message and returns the Ed25519 signature.
161    pub fn sign_message(&self, message: &[u8]) -> crate::crypto::Ed25519Signature {
162        self.private_key.sign(message)
163    }
164}
165
166impl Account for Ed25519Account {
167    fn address(&self) -> AccountAddress {
168        self.address
169    }
170
171    fn authentication_key(&self) -> AuthenticationKey {
172        AuthenticationKey::new(self.public_key.to_authentication_key())
173    }
174
175    fn sign(&self, message: &[u8]) -> AptosResult<Vec<u8>> {
176        Ok(self.private_key.sign(message).to_bytes().to_vec())
177    }
178
179    fn public_key_bytes(&self) -> Vec<u8> {
180        self.public_key.to_bytes().to_vec()
181    }
182
183    fn signature_scheme(&self) -> u8 {
184        ED25519_SCHEME
185    }
186}
187
188impl fmt::Debug for Ed25519Account {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        f.debug_struct("Ed25519Account")
191            .field("address", &self.address)
192            .field("public_key", &self.public_key)
193            .finish_non_exhaustive()
194    }
195}
196
197/// An Ed25519 account using the modern `SingleKey` authenticator format.
198///
199/// This account type uses the `SingleSender` > `SingleKey` > `AnyPublicKey::Ed25519`
200/// authenticator path, which is the modern unified format recommended for new
201/// implementations.
202///
203/// **Note**: This produces a DIFFERENT address than [`Ed25519Account`] for the
204/// same private key because it uses scheme ID 2 instead of 0.
205///
206/// # Authentication Key Derivation
207///
208/// The authentication key is derived as:
209/// ```text
210/// auth_key = SHA3-256(BCS(AnyPublicKey::Ed25519) || 0x02)
211/// ```
212///
213/// Where `BCS(AnyPublicKey::Ed25519) = 0x00 || ULEB128(32) || public_key_bytes`
214///
215/// # Example
216///
217/// ```rust
218/// use aptos_sdk::account::Ed25519SingleKeyAccount;
219///
220/// // Generate a new random account
221/// let account = Ed25519SingleKeyAccount::generate();
222/// println!("Address: {}", account.address());
223/// ```
224#[derive(Clone)]
225pub struct Ed25519SingleKeyAccount {
226    private_key: Ed25519PrivateKey,
227    public_key: Ed25519PublicKey,
228    address: AccountAddress,
229}
230
231impl Ed25519SingleKeyAccount {
232    /// Generates a new random Ed25519 `SingleKey` account.
233    pub fn generate() -> Self {
234        let private_key = Ed25519PrivateKey::generate();
235        Self::from_private_key(private_key)
236    }
237
238    /// Creates an account from a private key.
239    pub fn from_private_key(private_key: Ed25519PrivateKey) -> Self {
240        let public_key = private_key.public_key();
241        let address = Self::derive_address(&public_key);
242        Self {
243            private_key,
244            public_key,
245            address,
246        }
247    }
248
249    /// Creates an account from private key bytes.
250    ///
251    /// # Errors
252    ///
253    /// Returns an error if the bytes are not a valid Ed25519 private key (must be exactly 32 bytes).
254    pub fn from_private_key_bytes(bytes: &[u8]) -> AptosResult<Self> {
255        let private_key = Ed25519PrivateKey::from_bytes(bytes)?;
256        Ok(Self::from_private_key(private_key))
257    }
258
259    /// Creates an account from a private key hex string.
260    ///
261    /// # Errors
262    ///
263    /// This function will return an error if:
264    /// - The hex string is invalid or cannot be decoded
265    /// - The decoded bytes are not a valid Ed25519 private key
266    pub fn from_private_key_hex(hex_str: &str) -> AptosResult<Self> {
267        let private_key = Ed25519PrivateKey::from_hex(hex_str)?;
268        Ok(Self::from_private_key(private_key))
269    }
270
271    /// Creates an account from a BIP-39 mnemonic phrase.
272    ///
273    /// Uses the standard Aptos derivation path: `m/44'/637'/0'/0'/index'`
274    ///
275    /// # Errors
276    ///
277    /// Returns an error if the mnemonic phrase is invalid or if key derivation fails.
278    #[cfg(feature = "mnemonic")]
279    pub fn from_mnemonic(mnemonic: &str, index: u32) -> AptosResult<Self> {
280        let mnemonic = Mnemonic::from_phrase(mnemonic)?;
281        let private_key = mnemonic.derive_ed25519_key(index)?;
282        Ok(Self::from_private_key(private_key))
283    }
284
285    /// Returns the account address.
286    pub fn address(&self) -> AccountAddress {
287        self.address
288    }
289
290    /// Returns the public key.
291    pub fn public_key(&self) -> &Ed25519PublicKey {
292        &self.public_key
293    }
294
295    /// Returns a reference to the private key.
296    pub fn private_key(&self) -> &Ed25519PrivateKey {
297        &self.private_key
298    }
299
300    /// Signs a message and returns the Ed25519 signature.
301    pub fn sign_message(&self, message: &[u8]) -> crate::crypto::Ed25519Signature {
302        self.private_key.sign(message)
303    }
304
305    /// Derives the address for an Ed25519 public key using `SingleKey` scheme.
306    fn derive_address(public_key: &Ed25519PublicKey) -> AccountAddress {
307        // BCS format: variant_byte || ULEB128(length) || public_key_bytes
308        let pk_bytes = public_key.to_bytes();
309        let mut bcs_bytes = Vec::with_capacity(1 + 1 + pk_bytes.len());
310        bcs_bytes.push(0x00); // Ed25519 variant
311        bcs_bytes.push(32); // ULEB128(32) = 32 (since 32 < 128)
312        bcs_bytes.extend_from_slice(&pk_bytes);
313        let auth_key = derive_authentication_key(&bcs_bytes, SINGLE_KEY_SCHEME);
314        AccountAddress::new(auth_key)
315    }
316
317    /// Returns the BCS-serialized public key bytes for `SingleKey` authenticator.
318    ///
319    /// Format: `0x00 || ULEB128(32) || public_key_bytes`
320    fn bcs_public_key_bytes(&self) -> Vec<u8> {
321        let pk_bytes = self.public_key.to_bytes();
322        let mut bcs_bytes = Vec::with_capacity(1 + 1 + pk_bytes.len());
323        bcs_bytes.push(0x00); // Ed25519 variant
324        bcs_bytes.push(32); // ULEB128(32) = 32
325        bcs_bytes.extend_from_slice(&pk_bytes);
326        bcs_bytes
327    }
328
329    /// Returns the BCS-serialized `AnySignature::Ed25519` bytes for `SingleKey` authenticator.
330    ///
331    /// Format: `0x00 || ULEB128(64) || signature_bytes`
332    fn bcs_signature_bytes(sig: &crate::crypto::Ed25519Signature) -> Vec<u8> {
333        let sig_bytes = sig.to_bytes();
334        let mut out = Vec::with_capacity(1 + 1 + sig_bytes.len());
335        out.push(0x00); // Ed25519 variant
336        out.push(64); // ULEB128(64)
337        out.extend_from_slice(&sig_bytes);
338        out
339    }
340}
341
342impl Account for Ed25519SingleKeyAccount {
343    fn address(&self) -> AccountAddress {
344        self.address
345    }
346
347    fn authentication_key(&self) -> AuthenticationKey {
348        let bcs_bytes = self.bcs_public_key_bytes();
349        let key = derive_authentication_key(&bcs_bytes, SINGLE_KEY_SCHEME);
350        AuthenticationKey::new(key)
351    }
352
353    fn sign(&self, message: &[u8]) -> AptosResult<Vec<u8>> {
354        // Return BCS-serialized `AnySignature::Ed25519` so the wire format consumed
355        // by `AccountAuthenticator::SingleKey` matches what the on-chain
356        // `SingleKeyAuthenticator { signature: AnySignature }` deserializer expects.
357        let sig = self.private_key.sign(message);
358        Ok(Self::bcs_signature_bytes(&sig))
359    }
360
361    fn public_key_bytes(&self) -> Vec<u8> {
362        // Return BCS-serialized AnyPublicKey::Ed25519 format
363        self.bcs_public_key_bytes()
364    }
365
366    fn signature_scheme(&self) -> u8 {
367        SINGLE_KEY_SCHEME
368    }
369}
370
371impl fmt::Debug for Ed25519SingleKeyAccount {
372    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373        f.debug_struct("Ed25519SingleKeyAccount")
374            .field("address", &self.address)
375            .field("public_key", &self.public_key)
376            .finish_non_exhaustive()
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383
384    #[test]
385    fn test_generate() {
386        let account = Ed25519Account::generate();
387        assert!(!account.address().is_zero());
388    }
389
390    #[test]
391    #[cfg(feature = "mnemonic")]
392    fn test_from_mnemonic() {
393        // Standard test mnemonic
394        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
395        let account = Ed25519Account::from_mnemonic(mnemonic, 0).unwrap();
396
397        // Same mnemonic should produce same account
398        let account2 = Ed25519Account::from_mnemonic(mnemonic, 0).unwrap();
399        assert_eq!(account.address(), account2.address());
400
401        // Different index should produce different account
402        let account3 = Ed25519Account::from_mnemonic(mnemonic, 1).unwrap();
403        assert_ne!(account.address(), account3.address());
404    }
405
406    #[test]
407    fn test_sign_and_verify() {
408        let account = Ed25519Account::generate();
409        let message = b"hello world";
410
411        let signature = account.sign_message(message);
412        assert!(account.public_key().verify(message, &signature).is_ok());
413    }
414
415    #[test]
416    #[cfg(feature = "mnemonic")]
417    fn test_generate_with_mnemonic() {
418        let (account, mnemonic) = Ed25519Account::generate_with_mnemonic().unwrap();
419
420        // Should be able to restore from the mnemonic
421        let restored = Ed25519Account::from_mnemonic(&mnemonic, 0).unwrap();
422        assert_eq!(account.address(), restored.address());
423    }
424
425    #[test]
426    fn test_from_private_key() {
427        let original = Ed25519Account::generate();
428        let private_key = original.private_key().clone();
429        let restored = Ed25519Account::from_private_key(private_key);
430        assert_eq!(original.address(), restored.address());
431    }
432
433    #[test]
434    fn test_from_private_key_bytes() {
435        let original = Ed25519Account::generate();
436        let bytes = original.private_key().to_bytes();
437        let restored = Ed25519Account::from_private_key_bytes(&bytes).unwrap();
438        assert_eq!(original.address(), restored.address());
439    }
440
441    #[test]
442    fn test_from_private_key_hex() {
443        let original = Ed25519Account::generate();
444        let hex = original.private_key().to_hex();
445        let restored = Ed25519Account::from_private_key_hex(&hex).unwrap();
446        assert_eq!(original.address(), restored.address());
447    }
448
449    #[test]
450    fn test_authentication_key() {
451        let account = Ed25519Account::generate();
452        let auth_key = account.authentication_key();
453        assert_eq!(auth_key.as_bytes().len(), 32);
454    }
455
456    #[test]
457    fn test_public_key_bytes() {
458        let account = Ed25519Account::generate();
459        let bytes = account.public_key_bytes();
460        assert_eq!(bytes.len(), 32);
461    }
462
463    #[test]
464    fn test_signature_scheme() {
465        let account = Ed25519Account::generate();
466        assert_eq!(account.signature_scheme(), ED25519_SCHEME);
467    }
468
469    #[test]
470    fn test_sign_trait() {
471        let account = Ed25519Account::generate();
472        let message = b"test message";
473        let sig_bytes = account.sign(message).unwrap();
474        assert_eq!(sig_bytes.len(), 64);
475    }
476
477    #[test]
478    fn test_debug_output() {
479        let account = Ed25519Account::generate();
480        let debug = format!("{account:?}");
481        assert!(debug.contains("Ed25519Account"));
482        assert!(debug.contains("address"));
483    }
484
485    #[test]
486    fn test_invalid_private_key_bytes() {
487        let result = Ed25519Account::from_private_key_bytes(&[0u8; 16]);
488        assert!(result.is_err());
489    }
490
491    #[test]
492    fn test_invalid_private_key_hex() {
493        let result = Ed25519Account::from_private_key_hex("invalid");
494        assert!(result.is_err());
495    }
496
497    #[test]
498    #[cfg(feature = "mnemonic")]
499    fn test_invalid_mnemonic() {
500        let result = Ed25519Account::from_mnemonic("invalid mnemonic phrase", 0);
501        assert!(result.is_err());
502    }
503
504    // Ed25519SingleKeyAccount tests
505
506    #[test]
507    fn test_single_key_generate() {
508        let account = Ed25519SingleKeyAccount::generate();
509        assert!(!account.address().is_zero());
510    }
511
512    #[test]
513    fn test_single_key_different_address() {
514        // Same private key should produce different addresses for Ed25519Account vs Ed25519SingleKeyAccount
515        let legacy_account = Ed25519Account::generate();
516        let private_key = legacy_account.private_key().clone();
517
518        let single_key_account = Ed25519SingleKeyAccount::from_private_key(private_key);
519
520        // Addresses should be DIFFERENT because they use different scheme IDs
521        assert_ne!(legacy_account.address(), single_key_account.address());
522    }
523
524    #[test]
525    fn test_single_key_sign_and_verify() {
526        let account = Ed25519SingleKeyAccount::generate();
527        let message = b"hello world";
528
529        let signature = account.sign_message(message);
530        assert!(account.public_key().verify(message, &signature).is_ok());
531    }
532
533    #[test]
534    fn test_single_key_from_private_key() {
535        let original = Ed25519SingleKeyAccount::generate();
536        let private_key = original.private_key().clone();
537        let restored = Ed25519SingleKeyAccount::from_private_key(private_key);
538        assert_eq!(original.address(), restored.address());
539    }
540
541    #[test]
542    fn test_single_key_from_private_key_bytes() {
543        let original = Ed25519SingleKeyAccount::generate();
544        let bytes = original.private_key().to_bytes();
545        let restored = Ed25519SingleKeyAccount::from_private_key_bytes(&bytes).unwrap();
546        assert_eq!(original.address(), restored.address());
547    }
548
549    #[test]
550    fn test_single_key_from_private_key_hex() {
551        let original = Ed25519SingleKeyAccount::generate();
552        let hex = original.private_key().to_hex();
553        let restored = Ed25519SingleKeyAccount::from_private_key_hex(&hex).unwrap();
554        assert_eq!(original.address(), restored.address());
555    }
556
557    #[test]
558    fn test_single_key_authentication_key() {
559        let account = Ed25519SingleKeyAccount::generate();
560        let auth_key = account.authentication_key();
561        assert_eq!(auth_key.as_bytes().len(), 32);
562    }
563
564    #[test]
565    fn test_single_key_public_key_bytes() {
566        let account = Ed25519SingleKeyAccount::generate();
567        let bytes = account.public_key_bytes();
568        // BCS(AnyPublicKey::Ed25519): 1-byte variant (0x00) + ULEB128(32) + 32-byte key = 34 bytes
569        assert_eq!(bytes.len(), 34);
570        assert_eq!(bytes[0], 0x00); // Ed25519 variant
571        assert_eq!(bytes[1], 32); // ULEB128(32)
572    }
573
574    #[test]
575    fn test_single_key_signature_scheme() {
576        let account = Ed25519SingleKeyAccount::generate();
577        assert_eq!(account.signature_scheme(), SINGLE_KEY_SCHEME);
578    }
579
580    #[test]
581    fn test_single_key_sign_trait() {
582        let account = Ed25519SingleKeyAccount::generate();
583        let message = b"test message";
584        let sig_bytes = account.sign(message).unwrap();
585        // sign() returns BCS(AnySignature::Ed25519) = 1 + 1 + 64 = 66 bytes.
586        assert_eq!(sig_bytes.len(), 66);
587        assert_eq!(sig_bytes[0], 0x00, "AnySignature::Ed25519 variant tag");
588        assert_eq!(sig_bytes[1], 64, "ULEB128(64)");
589    }
590
591    #[test]
592    fn test_single_key_debug_output() {
593        let account = Ed25519SingleKeyAccount::generate();
594        let debug = format!("{account:?}");
595        assert!(debug.contains("Ed25519SingleKeyAccount"));
596        assert!(debug.contains("address"));
597    }
598
599    #[test]
600    #[cfg(feature = "mnemonic")]
601    fn test_single_key_from_mnemonic() {
602        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
603        let account = Ed25519SingleKeyAccount::from_mnemonic(mnemonic, 0).unwrap();
604
605        // Same mnemonic should produce same account
606        let account2 = Ed25519SingleKeyAccount::from_mnemonic(mnemonic, 0).unwrap();
607        assert_eq!(account.address(), account2.address());
608
609        // Different index should produce different account
610        let account3 = Ed25519SingleKeyAccount::from_mnemonic(mnemonic, 1).unwrap();
611        assert_ne!(account.address(), account3.address());
612    }
613
614    #[test]
615    #[cfg(feature = "mnemonic")]
616    fn test_single_key_vs_legacy_mnemonic() {
617        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
618
619        let legacy = Ed25519Account::from_mnemonic(mnemonic, 0).unwrap();
620        let single_key = Ed25519SingleKeyAccount::from_mnemonic(mnemonic, 0).unwrap();
621
622        // Same mnemonic, same private key, but DIFFERENT addresses
623        assert_eq!(
624            legacy.private_key().to_bytes(),
625            single_key.private_key().to_bytes()
626        );
627        assert_ne!(legacy.address(), single_key.address());
628    }
629}