Skip to main content

aptos_sdk/account/
account.rs

1//! Account trait and common types.
2
3use crate::error::AptosResult;
4use crate::types::AccountAddress;
5use serde::{Deserialize, Serialize};
6use std::fmt;
7
8/// An authentication key used to verify account ownership.
9///
10/// The authentication key is derived from the public key and can be
11/// rotated to support key rotation.
12#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct AuthenticationKey([u8; 32]);
14
15impl AuthenticationKey {
16    /// Creates an authentication key from bytes.
17    pub fn new(bytes: [u8; 32]) -> Self {
18        Self(bytes)
19    }
20
21    /// Creates an authentication key from a byte slice.
22    ///
23    /// # Errors
24    ///
25    /// Returns an error if the byte slice length is not exactly 32 bytes.
26    pub fn from_bytes(bytes: &[u8]) -> AptosResult<Self> {
27        if bytes.len() != 32 {
28            return Err(crate::error::AptosError::InvalidAddress(format!(
29                "authentication key must be 32 bytes, got {}",
30                bytes.len()
31            )));
32        }
33        let mut key = [0u8; 32];
34        key.copy_from_slice(bytes);
35        Ok(Self(key))
36    }
37
38    /// Creates an authentication key from a hex string.
39    ///
40    /// # Errors
41    ///
42    /// This function will return an error if:
43    /// - The hex string is invalid or cannot be decoded
44    /// - The decoded bytes are not exactly 32 bytes long
45    pub fn from_hex(hex_str: &str) -> AptosResult<Self> {
46        let bytes = const_hex::decode(hex_str)?;
47        Self::from_bytes(&bytes)
48    }
49
50    /// Returns the authentication key as bytes.
51    pub fn as_bytes(&self) -> &[u8; 32] {
52        &self.0
53    }
54
55    /// Returns the authentication key as a byte array.
56    pub fn to_bytes(&self) -> [u8; 32] {
57        self.0
58    }
59
60    /// Returns the authentication key as a hex string.
61    pub fn to_hex(&self) -> String {
62        const_hex::encode_prefixed(self.0)
63    }
64
65    /// Derives the account address from this authentication key.
66    ///
67    /// For most accounts, the address equals the authentication key.
68    pub fn to_address(&self) -> AccountAddress {
69        AccountAddress::new(self.0)
70    }
71}
72
73impl fmt::Debug for AuthenticationKey {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(f, "AuthenticationKey({})", self.to_hex())
76    }
77}
78
79impl fmt::Display for AuthenticationKey {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        write!(f, "{}", self.to_hex())
82    }
83}
84
85impl From<[u8; 32]> for AuthenticationKey {
86    fn from(bytes: [u8; 32]) -> Self {
87        Self(bytes)
88    }
89}
90
91impl From<AuthenticationKey> for [u8; 32] {
92    fn from(key: AuthenticationKey) -> Self {
93        key.0
94    }
95}
96
97impl From<AuthenticationKey> for AccountAddress {
98    fn from(key: AuthenticationKey) -> Self {
99        key.to_address()
100    }
101}
102
103/// Trait for account types that can sign transactions.
104///
105/// This trait provides a common interface for different account types
106/// (Ed25519, Secp256k1, multi-sig, keyless, etc.).
107pub trait Account: Send + Sync {
108    /// Returns the account address.
109    fn address(&self) -> AccountAddress;
110
111    /// Returns the authentication key.
112    fn authentication_key(&self) -> AuthenticationKey;
113
114    /// Signs a message and returns the signature bytes.
115    ///
116    /// # Errors
117    ///
118    /// May return an error if signing fails (e.g., insufficient signatures
119    /// for multi-sig accounts).
120    fn sign(&self, message: &[u8]) -> AptosResult<Vec<u8>>;
121
122    /// Returns the public key bytes.
123    fn public_key_bytes(&self) -> Vec<u8>;
124
125    /// Returns the scheme identifier for this account type.
126    fn signature_scheme(&self) -> u8;
127}
128
129/// An enum that can hold any account type.
130///
131/// This is useful when you need to store different account types
132/// in the same collection or pass them around generically.
133#[derive(Debug)]
134#[allow(clippy::large_enum_variant)] // Keyless account is intentionally large; boxing would complicate API
135pub enum AnyAccount {
136    /// An Ed25519 account.
137    #[cfg(feature = "ed25519")]
138    Ed25519(super::Ed25519Account),
139    /// A multi-Ed25519 account.
140    #[cfg(feature = "ed25519")]
141    MultiEd25519(super::MultiEd25519Account),
142    /// A multi-key account (mixed signature types).
143    MultiKey(super::MultiKeyAccount),
144    /// A Keyless account.
145    #[cfg(feature = "keyless")]
146    Keyless(super::KeylessAccount),
147    /// A Secp256k1 account.
148    #[cfg(feature = "secp256k1")]
149    Secp256k1(super::Secp256k1Account),
150}
151
152impl Account for AnyAccount {
153    fn address(&self) -> AccountAddress {
154        match self {
155            #[cfg(feature = "ed25519")]
156            AnyAccount::Ed25519(account) => account.address(),
157            #[cfg(feature = "ed25519")]
158            AnyAccount::MultiEd25519(account) => account.address(),
159            AnyAccount::MultiKey(account) => account.address(),
160            #[cfg(feature = "keyless")]
161            AnyAccount::Keyless(account) => account.address(),
162            #[cfg(feature = "secp256k1")]
163            AnyAccount::Secp256k1(account) => account.address(),
164        }
165    }
166
167    fn authentication_key(&self) -> AuthenticationKey {
168        match self {
169            #[cfg(feature = "ed25519")]
170            AnyAccount::Ed25519(account) => account.authentication_key(),
171            #[cfg(feature = "ed25519")]
172            AnyAccount::MultiEd25519(account) => account.authentication_key(),
173            AnyAccount::MultiKey(account) => account.authentication_key(),
174            #[cfg(feature = "keyless")]
175            AnyAccount::Keyless(account) => account.authentication_key(),
176            #[cfg(feature = "secp256k1")]
177            AnyAccount::Secp256k1(account) => account.authentication_key(),
178        }
179    }
180
181    fn sign(&self, message: &[u8]) -> AptosResult<Vec<u8>> {
182        match self {
183            #[cfg(feature = "ed25519")]
184            AnyAccount::Ed25519(account) => Account::sign(account, message),
185            #[cfg(feature = "ed25519")]
186            AnyAccount::MultiEd25519(account) => Account::sign(account, message),
187            AnyAccount::MultiKey(account) => Account::sign(account, message),
188            #[cfg(feature = "keyless")]
189            AnyAccount::Keyless(account) => Account::sign(account, message),
190            #[cfg(feature = "secp256k1")]
191            AnyAccount::Secp256k1(account) => Account::sign(account, message),
192        }
193    }
194
195    fn public_key_bytes(&self) -> Vec<u8> {
196        match self {
197            #[cfg(feature = "ed25519")]
198            AnyAccount::Ed25519(account) => account.public_key_bytes(),
199            #[cfg(feature = "ed25519")]
200            AnyAccount::MultiEd25519(account) => account.public_key_bytes(),
201            AnyAccount::MultiKey(account) => account.public_key_bytes(),
202            #[cfg(feature = "keyless")]
203            AnyAccount::Keyless(account) => account.public_key_bytes(),
204            #[cfg(feature = "secp256k1")]
205            AnyAccount::Secp256k1(account) => account.public_key_bytes(),
206        }
207    }
208
209    fn signature_scheme(&self) -> u8 {
210        match self {
211            #[cfg(feature = "ed25519")]
212            AnyAccount::Ed25519(account) => account.signature_scheme(),
213            #[cfg(feature = "ed25519")]
214            AnyAccount::MultiEd25519(account) => account.signature_scheme(),
215            AnyAccount::MultiKey(account) => account.signature_scheme(),
216            #[cfg(feature = "keyless")]
217            AnyAccount::Keyless(account) => account.signature_scheme(),
218            #[cfg(feature = "secp256k1")]
219            AnyAccount::Secp256k1(account) => account.signature_scheme(),
220        }
221    }
222}
223
224#[cfg(feature = "ed25519")]
225impl From<super::Ed25519Account> for AnyAccount {
226    fn from(account: super::Ed25519Account) -> Self {
227        AnyAccount::Ed25519(account)
228    }
229}
230
231#[cfg(feature = "ed25519")]
232impl From<super::MultiEd25519Account> for AnyAccount {
233    fn from(account: super::MultiEd25519Account) -> Self {
234        AnyAccount::MultiEd25519(account)
235    }
236}
237
238#[cfg(feature = "keyless")]
239impl From<super::KeylessAccount> for AnyAccount {
240    fn from(account: super::KeylessAccount) -> Self {
241        AnyAccount::Keyless(account)
242    }
243}
244
245#[cfg(feature = "secp256k1")]
246impl From<super::Secp256k1Account> for AnyAccount {
247    fn from(account: super::Secp256k1Account) -> Self {
248        AnyAccount::Secp256k1(account)
249    }
250}
251
252impl From<super::MultiKeyAccount> for AnyAccount {
253    fn from(account: super::MultiKeyAccount) -> Self {
254        AnyAccount::MultiKey(account)
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    #[test]
263    fn test_authentication_key() {
264        let key = AuthenticationKey::new([1u8; 32]);
265        assert_eq!(key.as_bytes(), &[1u8; 32]);
266
267        let hex = key.to_hex();
268        let restored = AuthenticationKey::from_hex(&hex).unwrap();
269        assert_eq!(key, restored);
270    }
271
272    #[test]
273    fn test_auth_key_to_address() {
274        let key = AuthenticationKey::new([42u8; 32]);
275        let address = key.to_address();
276        assert_eq!(address.as_bytes(), &[42u8; 32]);
277    }
278
279    #[test]
280    fn test_auth_key_from_bytes() {
281        let bytes = [5u8; 32];
282        let key = AuthenticationKey::from_bytes(&bytes).unwrap();
283        assert_eq!(key.to_bytes(), bytes);
284    }
285
286    #[test]
287    fn test_auth_key_from_bytes_invalid_length() {
288        let bytes = [5u8; 16];
289        let result = AuthenticationKey::from_bytes(&bytes);
290        assert!(result.is_err());
291    }
292
293    #[test]
294    fn test_auth_key_from_hex_with_prefix() {
295        let key = AuthenticationKey::new([0xab; 32]);
296        let hex = key.to_hex();
297        assert!(hex.starts_with("0x"));
298        let restored = AuthenticationKey::from_hex(&hex).unwrap();
299        assert_eq!(key, restored);
300    }
301
302    #[test]
303    fn test_auth_key_from_hex_without_prefix() {
304        let key = AuthenticationKey::new([0xcd; 32]);
305        let hex = key.to_hex();
306        let hex_without_prefix = hex.trim_start_matches("0x");
307        let restored = AuthenticationKey::from_hex(hex_without_prefix).unwrap();
308        assert_eq!(key, restored);
309    }
310
311    #[test]
312    fn test_auth_key_display() {
313        let key = AuthenticationKey::new([0xff; 32]);
314        let display = format!("{key}");
315        assert!(display.starts_with("0x"));
316        assert_eq!(display.len(), 66); // 0x + 64 hex chars
317    }
318
319    #[test]
320    fn test_auth_key_debug() {
321        let key = AuthenticationKey::new([0xaa; 32]);
322        let debug = format!("{key:?}");
323        assert!(debug.contains("AuthenticationKey"));
324    }
325
326    #[test]
327    fn test_auth_key_from_array() {
328        let bytes = [7u8; 32];
329        let key: AuthenticationKey = bytes.into();
330        assert_eq!(key.to_bytes(), bytes);
331    }
332
333    #[test]
334    fn test_auth_key_to_array() {
335        let key = AuthenticationKey::new([8u8; 32]);
336        let bytes: [u8; 32] = key.into();
337        assert_eq!(bytes, [8u8; 32]);
338    }
339
340    #[test]
341    fn test_auth_key_to_account_address() {
342        let key = AuthenticationKey::new([9u8; 32]);
343        let address: AccountAddress = key.into();
344        assert_eq!(address.as_bytes(), &[9u8; 32]);
345    }
346
347    #[cfg(feature = "ed25519")]
348    #[test]
349    fn test_any_account_from_ed25519() {
350        let ed25519 = super::super::Ed25519Account::generate();
351        let any_account: AnyAccount = ed25519.into();
352        if let AnyAccount::Ed25519(account) = any_account {
353            assert!(!account.address().is_zero());
354        } else {
355            panic!("Expected Ed25519 account");
356        }
357    }
358
359    #[cfg(feature = "ed25519")]
360    #[test]
361    fn test_any_account_ed25519_trait_methods() {
362        let ed25519 = super::super::Ed25519Account::generate();
363        let address = ed25519.address();
364        let auth_key = ed25519.authentication_key();
365        let any_account: AnyAccount = ed25519.into();
366
367        assert_eq!(any_account.address(), address);
368        assert_eq!(any_account.authentication_key(), auth_key);
369        assert!(!any_account.public_key_bytes().is_empty());
370
371        let sig = any_account.sign(b"test message").unwrap();
372        assert!(!sig.is_empty());
373    }
374
375    #[cfg(feature = "secp256k1")]
376    #[test]
377    fn test_any_account_from_secp256k1() {
378        let secp = super::super::Secp256k1Account::generate();
379        let any_account: AnyAccount = secp.into();
380        if let AnyAccount::Secp256k1(account) = any_account {
381            assert!(!account.address().is_zero());
382        } else {
383            panic!("Expected Secp256k1 account");
384        }
385    }
386
387    #[cfg(feature = "secp256k1")]
388    #[test]
389    fn test_any_account_secp256k1_trait_methods() {
390        let secp = super::super::Secp256k1Account::generate();
391        let address = secp.address();
392        let auth_key = secp.authentication_key();
393        let any_account: AnyAccount = secp.into();
394
395        assert_eq!(any_account.address(), address);
396        assert_eq!(any_account.authentication_key(), auth_key);
397        assert!(!any_account.public_key_bytes().is_empty());
398
399        let sig = any_account.sign(b"test message").unwrap();
400        assert!(!sig.is_empty());
401    }
402
403    #[cfg(feature = "ed25519")]
404    #[test]
405    fn test_any_account_from_multi_ed25519() {
406        use crate::crypto::Ed25519PrivateKey;
407
408        let keys: Vec<_> = (0..2).map(|_| Ed25519PrivateKey::generate()).collect();
409        let account = super::super::MultiEd25519Account::new(keys, 2).unwrap();
410        let any_account: AnyAccount = account.into();
411
412        if let AnyAccount::MultiEd25519(_) = any_account {
413            // Success
414        } else {
415            panic!("Expected MultiEd25519 account");
416        }
417    }
418
419    #[cfg(feature = "ed25519")]
420    #[test]
421    fn test_any_account_multi_ed25519_trait_methods() {
422        use crate::crypto::Ed25519PrivateKey;
423
424        let keys: Vec<_> = (0..2).map(|_| Ed25519PrivateKey::generate()).collect();
425        let account = super::super::MultiEd25519Account::new(keys, 2).unwrap();
426        let address = account.address();
427        let auth_key = account.authentication_key();
428        let any_account: AnyAccount = account.into();
429
430        assert_eq!(any_account.address(), address);
431        assert_eq!(any_account.authentication_key(), auth_key);
432        assert!(!any_account.public_key_bytes().is_empty());
433        assert!(any_account.signature_scheme() > 0);
434
435        let sig = any_account.sign(b"test").unwrap();
436        assert!(!sig.is_empty());
437    }
438
439    #[cfg(feature = "ed25519")]
440    #[test]
441    fn test_any_account_from_multi_key() {
442        use crate::account::AnyPrivateKey;
443        use crate::crypto::Ed25519PrivateKey;
444
445        let keys: Vec<_> = (0..2)
446            .map(|_| AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()))
447            .collect();
448        let account = super::super::MultiKeyAccount::new(keys, 2).unwrap();
449        let any_account: AnyAccount = account.into();
450
451        if let AnyAccount::MultiKey(_) = any_account {
452            // Success
453        } else {
454            panic!("Expected MultiKey account");
455        }
456    }
457
458    #[cfg(feature = "ed25519")]
459    #[test]
460    fn test_any_account_multi_key_trait_methods() {
461        use crate::account::AnyPrivateKey;
462        use crate::crypto::Ed25519PrivateKey;
463
464        let keys: Vec<_> = (0..2)
465            .map(|_| AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()))
466            .collect();
467        let account = super::super::MultiKeyAccount::new(keys, 2).unwrap();
468        let address = account.address();
469        let auth_key = account.authentication_key();
470        let any_account: AnyAccount = account.into();
471
472        assert_eq!(any_account.address(), address);
473        assert_eq!(any_account.authentication_key(), auth_key);
474        assert!(!any_account.public_key_bytes().is_empty());
475
476        let sig = any_account.sign(b"test").unwrap();
477        assert!(!sig.is_empty());
478    }
479
480    #[test]
481    fn test_auth_key_json_serialization() {
482        let key = AuthenticationKey::new([0xab; 32]);
483        let json = serde_json::to_string(&key).unwrap();
484        let restored: AuthenticationKey = serde_json::from_str(&json).unwrap();
485        assert_eq!(key, restored);
486    }
487
488    #[test]
489    fn test_auth_key_hash() {
490        use std::collections::HashSet;
491        let key1 = AuthenticationKey::new([1u8; 32]);
492        let key2 = AuthenticationKey::new([2u8; 32]);
493
494        let mut set = HashSet::new();
495        set.insert(key1);
496        set.insert(key2);
497        assert_eq!(set.len(), 2);
498        assert!(set.contains(&key1));
499    }
500
501    #[test]
502    fn test_auth_key_clone() {
503        let key = AuthenticationKey::new([42u8; 32]);
504        let cloned = key;
505        assert_eq!(key, cloned);
506    }
507
508    #[test]
509    fn test_any_account_debug() {
510        #[cfg(feature = "ed25519")]
511        {
512            let ed25519 = super::super::Ed25519Account::generate();
513            let any_account: AnyAccount = ed25519.into();
514            let debug = format!("{any_account:?}");
515            assert!(debug.contains("Ed25519"));
516        }
517    }
518}