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, 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 over a fixed subset of account types.
130///
131/// This is useful when you need to store different account types
132/// in the same collection or pass them around generically.
133///
134/// Despite the name, `AnyAccount` does **not** cover every account type in
135/// this module. It holds exactly the following (feature-gated) variants:
136/// [`Ed25519`](AnyAccount::Ed25519), [`MultiEd25519`](AnyAccount::MultiEd25519),
137/// [`MultiKey`](AnyAccount::MultiKey), and
138/// [`Secp256k1`](AnyAccount::Secp256k1). It cannot hold
139/// [`Ed25519SingleKeyAccount`](super::Ed25519SingleKeyAccount),
140/// [`Secp256r1Account`](super::Secp256r1Account), or
141/// [`WebAuthnAccount`](super::WebAuthnAccount).
142#[derive(Debug)]
143pub enum AnyAccount {
144    /// An Ed25519 account.
145    #[cfg(feature = "ed25519")]
146    Ed25519(super::Ed25519Account),
147    /// A multi-Ed25519 account.
148    #[cfg(feature = "ed25519")]
149    MultiEd25519(super::MultiEd25519Account),
150    /// A multi-key account (mixed signature types).
151    MultiKey(super::MultiKeyAccount),
152    /// A Secp256k1 account.
153    #[cfg(feature = "secp256k1")]
154    Secp256k1(super::Secp256k1Account),
155}
156
157impl Account for AnyAccount {
158    fn address(&self) -> AccountAddress {
159        match self {
160            #[cfg(feature = "ed25519")]
161            AnyAccount::Ed25519(account) => account.address(),
162            #[cfg(feature = "ed25519")]
163            AnyAccount::MultiEd25519(account) => account.address(),
164            AnyAccount::MultiKey(account) => account.address(),
165            #[cfg(feature = "secp256k1")]
166            AnyAccount::Secp256k1(account) => account.address(),
167        }
168    }
169
170    fn authentication_key(&self) -> AuthenticationKey {
171        match self {
172            #[cfg(feature = "ed25519")]
173            AnyAccount::Ed25519(account) => account.authentication_key(),
174            #[cfg(feature = "ed25519")]
175            AnyAccount::MultiEd25519(account) => account.authentication_key(),
176            AnyAccount::MultiKey(account) => account.authentication_key(),
177            #[cfg(feature = "secp256k1")]
178            AnyAccount::Secp256k1(account) => account.authentication_key(),
179        }
180    }
181
182    fn sign(&self, message: &[u8]) -> AptosResult<Vec<u8>> {
183        match self {
184            #[cfg(feature = "ed25519")]
185            AnyAccount::Ed25519(account) => Account::sign(account, message),
186            #[cfg(feature = "ed25519")]
187            AnyAccount::MultiEd25519(account) => Account::sign(account, message),
188            AnyAccount::MultiKey(account) => Account::sign(account, message),
189            #[cfg(feature = "secp256k1")]
190            AnyAccount::Secp256k1(account) => Account::sign(account, message),
191        }
192    }
193
194    fn public_key_bytes(&self) -> Vec<u8> {
195        match self {
196            #[cfg(feature = "ed25519")]
197            AnyAccount::Ed25519(account) => account.public_key_bytes(),
198            #[cfg(feature = "ed25519")]
199            AnyAccount::MultiEd25519(account) => account.public_key_bytes(),
200            AnyAccount::MultiKey(account) => account.public_key_bytes(),
201            #[cfg(feature = "secp256k1")]
202            AnyAccount::Secp256k1(account) => account.public_key_bytes(),
203        }
204    }
205
206    fn signature_scheme(&self) -> u8 {
207        match self {
208            #[cfg(feature = "ed25519")]
209            AnyAccount::Ed25519(account) => account.signature_scheme(),
210            #[cfg(feature = "ed25519")]
211            AnyAccount::MultiEd25519(account) => account.signature_scheme(),
212            AnyAccount::MultiKey(account) => account.signature_scheme(),
213            #[cfg(feature = "secp256k1")]
214            AnyAccount::Secp256k1(account) => account.signature_scheme(),
215        }
216    }
217}
218
219#[cfg(feature = "ed25519")]
220impl From<super::Ed25519Account> for AnyAccount {
221    fn from(account: super::Ed25519Account) -> Self {
222        AnyAccount::Ed25519(account)
223    }
224}
225
226#[cfg(feature = "ed25519")]
227impl From<super::MultiEd25519Account> for AnyAccount {
228    fn from(account: super::MultiEd25519Account) -> Self {
229        AnyAccount::MultiEd25519(account)
230    }
231}
232
233#[cfg(feature = "secp256k1")]
234impl From<super::Secp256k1Account> for AnyAccount {
235    fn from(account: super::Secp256k1Account) -> Self {
236        AnyAccount::Secp256k1(account)
237    }
238}
239
240impl From<super::MultiKeyAccount> for AnyAccount {
241    fn from(account: super::MultiKeyAccount) -> Self {
242        AnyAccount::MultiKey(account)
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    fn test_authentication_key() {
252        let key = AuthenticationKey::new([1u8; 32]);
253        assert_eq!(key.as_bytes(), &[1u8; 32]);
254
255        let hex = key.to_hex();
256        let restored = AuthenticationKey::from_hex(&hex).unwrap();
257        assert_eq!(key, restored);
258    }
259
260    #[test]
261    fn test_auth_key_to_address() {
262        let key = AuthenticationKey::new([42u8; 32]);
263        let address = key.to_address();
264        assert_eq!(address.as_bytes(), &[42u8; 32]);
265    }
266
267    #[test]
268    fn test_auth_key_from_bytes() {
269        let bytes = [5u8; 32];
270        let key = AuthenticationKey::from_bytes(&bytes).unwrap();
271        assert_eq!(key.to_bytes(), bytes);
272    }
273
274    #[test]
275    fn test_auth_key_from_bytes_invalid_length() {
276        let bytes = [5u8; 16];
277        let result = AuthenticationKey::from_bytes(&bytes);
278        assert!(result.is_err());
279    }
280
281    #[test]
282    fn test_auth_key_from_hex_with_prefix() {
283        let key = AuthenticationKey::new([0xab; 32]);
284        let hex = key.to_hex();
285        assert!(hex.starts_with("0x"));
286        let restored = AuthenticationKey::from_hex(&hex).unwrap();
287        assert_eq!(key, restored);
288    }
289
290    #[test]
291    fn test_auth_key_from_hex_without_prefix() {
292        let key = AuthenticationKey::new([0xcd; 32]);
293        let hex = key.to_hex();
294        let hex_without_prefix = hex.trim_start_matches("0x");
295        let restored = AuthenticationKey::from_hex(hex_without_prefix).unwrap();
296        assert_eq!(key, restored);
297    }
298
299    #[test]
300    fn test_auth_key_display() {
301        let key = AuthenticationKey::new([0xff; 32]);
302        let display = format!("{key}");
303        assert!(display.starts_with("0x"));
304        assert_eq!(display.len(), 66); // 0x + 64 hex chars
305    }
306
307    #[test]
308    fn test_auth_key_debug() {
309        let key = AuthenticationKey::new([0xaa; 32]);
310        let debug = format!("{key:?}");
311        assert!(debug.contains("AuthenticationKey"));
312    }
313
314    #[test]
315    fn test_auth_key_from_array() {
316        let bytes = [7u8; 32];
317        let key: AuthenticationKey = bytes.into();
318        assert_eq!(key.to_bytes(), bytes);
319    }
320
321    #[test]
322    fn test_auth_key_to_array() {
323        let key = AuthenticationKey::new([8u8; 32]);
324        let bytes: [u8; 32] = key.into();
325        assert_eq!(bytes, [8u8; 32]);
326    }
327
328    #[test]
329    fn test_auth_key_to_account_address() {
330        let key = AuthenticationKey::new([9u8; 32]);
331        let address: AccountAddress = key.into();
332        assert_eq!(address.as_bytes(), &[9u8; 32]);
333    }
334
335    #[cfg(feature = "ed25519")]
336    #[test]
337    fn test_any_account_from_ed25519() {
338        let ed25519 = super::super::Ed25519Account::generate();
339        let any_account: AnyAccount = ed25519.into();
340        if let AnyAccount::Ed25519(account) = any_account {
341            assert!(!account.address().is_zero());
342        } else {
343            panic!("Expected Ed25519 account");
344        }
345    }
346
347    #[cfg(feature = "ed25519")]
348    #[test]
349    fn test_any_account_ed25519_trait_methods() {
350        let ed25519 = super::super::Ed25519Account::generate();
351        let address = ed25519.address();
352        let auth_key = ed25519.authentication_key();
353        let any_account: AnyAccount = ed25519.into();
354
355        assert_eq!(any_account.address(), address);
356        assert_eq!(any_account.authentication_key(), auth_key);
357        assert!(!any_account.public_key_bytes().is_empty());
358
359        let sig = any_account.sign(b"test message").unwrap();
360        assert!(!sig.is_empty());
361    }
362
363    #[cfg(feature = "secp256k1")]
364    #[test]
365    fn test_any_account_from_secp256k1() {
366        let secp = super::super::Secp256k1Account::generate();
367        let any_account: AnyAccount = secp.into();
368        if let AnyAccount::Secp256k1(account) = any_account {
369            assert!(!account.address().is_zero());
370        } else {
371            panic!("Expected Secp256k1 account");
372        }
373    }
374
375    #[cfg(feature = "secp256k1")]
376    #[test]
377    fn test_any_account_secp256k1_trait_methods() {
378        let secp = super::super::Secp256k1Account::generate();
379        let address = secp.address();
380        let auth_key = secp.authentication_key();
381        let any_account: AnyAccount = secp.into();
382
383        assert_eq!(any_account.address(), address);
384        assert_eq!(any_account.authentication_key(), auth_key);
385        assert!(!any_account.public_key_bytes().is_empty());
386
387        let sig = any_account.sign(b"test message").unwrap();
388        assert!(!sig.is_empty());
389    }
390
391    #[cfg(feature = "ed25519")]
392    #[test]
393    fn test_any_account_from_multi_ed25519() {
394        use crate::crypto::Ed25519PrivateKey;
395
396        let keys: Vec<_> = (0..2).map(|_| Ed25519PrivateKey::generate()).collect();
397        let account = super::super::MultiEd25519Account::new(keys, 2).unwrap();
398        let any_account: AnyAccount = account.into();
399
400        if let AnyAccount::MultiEd25519(_) = any_account {
401            // Success
402        } else {
403            panic!("Expected MultiEd25519 account");
404        }
405    }
406
407    #[cfg(feature = "ed25519")]
408    #[test]
409    fn test_any_account_multi_ed25519_trait_methods() {
410        use crate::crypto::Ed25519PrivateKey;
411
412        let keys: Vec<_> = (0..2).map(|_| Ed25519PrivateKey::generate()).collect();
413        let account = super::super::MultiEd25519Account::new(keys, 2).unwrap();
414        let address = account.address();
415        let auth_key = account.authentication_key();
416        let any_account: AnyAccount = account.into();
417
418        assert_eq!(any_account.address(), address);
419        assert_eq!(any_account.authentication_key(), auth_key);
420        assert!(!any_account.public_key_bytes().is_empty());
421        assert!(any_account.signature_scheme() > 0);
422
423        let sig = any_account.sign(b"test").unwrap();
424        assert!(!sig.is_empty());
425    }
426
427    #[cfg(feature = "ed25519")]
428    #[test]
429    fn test_any_account_from_multi_key() {
430        use crate::account::AnyPrivateKey;
431        use crate::crypto::Ed25519PrivateKey;
432
433        let keys: Vec<_> = (0..2)
434            .map(|_| AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()))
435            .collect();
436        let account = super::super::MultiKeyAccount::new(keys, 2).unwrap();
437        let any_account: AnyAccount = account.into();
438
439        if let AnyAccount::MultiKey(_) = any_account {
440            // Success
441        } else {
442            panic!("Expected MultiKey account");
443        }
444    }
445
446    #[cfg(feature = "ed25519")]
447    #[test]
448    fn test_any_account_multi_key_trait_methods() {
449        use crate::account::AnyPrivateKey;
450        use crate::crypto::Ed25519PrivateKey;
451
452        let keys: Vec<_> = (0..2)
453            .map(|_| AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()))
454            .collect();
455        let account = super::super::MultiKeyAccount::new(keys, 2).unwrap();
456        let address = account.address();
457        let auth_key = account.authentication_key();
458        let any_account: AnyAccount = account.into();
459
460        assert_eq!(any_account.address(), address);
461        assert_eq!(any_account.authentication_key(), auth_key);
462        assert!(!any_account.public_key_bytes().is_empty());
463
464        let sig = any_account.sign(b"test").unwrap();
465        assert!(!sig.is_empty());
466    }
467
468    #[test]
469    fn test_auth_key_json_serialization() {
470        let key = AuthenticationKey::new([0xab; 32]);
471        let json = serde_json::to_string(&key).unwrap();
472        let restored: AuthenticationKey = serde_json::from_str(&json).unwrap();
473        assert_eq!(key, restored);
474    }
475
476    #[test]
477    fn test_auth_key_hash() {
478        use std::collections::HashSet;
479        let key1 = AuthenticationKey::new([1u8; 32]);
480        let key2 = AuthenticationKey::new([2u8; 32]);
481
482        let mut set = HashSet::new();
483        set.insert(key1);
484        set.insert(key2);
485        assert_eq!(set.len(), 2);
486        assert!(set.contains(&key1));
487    }
488
489    #[test]
490    fn test_auth_key_clone() {
491        let key = AuthenticationKey::new([42u8; 32]);
492        let cloned = key;
493        assert_eq!(key, cloned);
494    }
495
496    #[test]
497    fn test_any_account_debug() {
498        #[cfg(feature = "ed25519")]
499        {
500            let ed25519 = super::super::Ed25519Account::generate();
501            let any_account: AnyAccount = ed25519.into();
502            let debug = format!("{any_account:?}");
503            assert!(debug.contains("Ed25519"));
504        }
505    }
506}