Skip to main content

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