Skip to main content

aptos_sdk/account/
multi_key.rs

1//! `MultiKey` account implementation.
2//!
3//! This module provides the [`MultiKeyAccount`] type for M-of-N
4//! threshold signature accounts with mixed key types.
5
6use crate::account::account::{Account, AuthenticationKey};
7use crate::crypto::{
8    AnyPublicKey, AnyPublicKeyVariant, AnySignature, MULTI_KEY_SCHEME, MultiKeyPublicKey,
9    MultiKeySignature,
10};
11use crate::error::{AptosError, AptosResult};
12use crate::types::AccountAddress;
13use std::fmt;
14
15/// A private key that can be any supported signature scheme.
16pub enum AnyPrivateKey {
17    /// Ed25519 private key.
18    #[cfg(feature = "ed25519")]
19    Ed25519(crate::crypto::Ed25519PrivateKey),
20    /// Secp256k1 private key.
21    #[cfg(feature = "secp256k1")]
22    Secp256k1(crate::crypto::Secp256k1PrivateKey),
23    /// Secp256r1 private key.
24    #[cfg(feature = "secp256r1")]
25    Secp256r1(crate::crypto::Secp256r1PrivateKey),
26}
27
28impl AnyPrivateKey {
29    /// Gets the signature scheme variant.
30    #[allow(unreachable_code)]
31    pub fn variant(&self) -> AnyPublicKeyVariant {
32        match self {
33            #[cfg(feature = "ed25519")]
34            Self::Ed25519(_) => AnyPublicKeyVariant::Ed25519,
35            #[cfg(feature = "secp256k1")]
36            Self::Secp256k1(_) => AnyPublicKeyVariant::Secp256k1,
37            #[cfg(feature = "secp256r1")]
38            Self::Secp256r1(_) => AnyPublicKeyVariant::Secp256r1,
39            #[allow(unreachable_patterns)]
40            _ => unreachable!("AnyPrivateKey requires at least one crypto feature to be enabled"),
41        }
42    }
43
44    /// Gets the public key.
45    #[allow(unreachable_code)]
46    pub fn public_key(&self) -> AnyPublicKey {
47        match self {
48            #[cfg(feature = "ed25519")]
49            Self::Ed25519(key) => AnyPublicKey::ed25519(&key.public_key()),
50            #[cfg(feature = "secp256k1")]
51            Self::Secp256k1(key) => AnyPublicKey::secp256k1(&key.public_key()),
52            #[cfg(feature = "secp256r1")]
53            Self::Secp256r1(key) => AnyPublicKey::secp256r1(&key.public_key()),
54            #[allow(unreachable_patterns)]
55            _ => unreachable!("AnyPrivateKey requires at least one crypto feature to be enabled"),
56        }
57    }
58
59    /// Signs a message.
60    #[allow(unreachable_code, unused_variables)]
61    pub fn sign(&self, message: &[u8]) -> AnySignature {
62        match self {
63            #[cfg(feature = "ed25519")]
64            Self::Ed25519(key) => AnySignature::ed25519(&key.sign(message)),
65            #[cfg(feature = "secp256k1")]
66            Self::Secp256k1(key) => AnySignature::secp256k1(&key.sign(message)),
67            #[cfg(feature = "secp256r1")]
68            Self::Secp256r1(key) => AnySignature::secp256r1(&key.sign(message)),
69            #[allow(unreachable_patterns)]
70            _ => unreachable!("AnyPrivateKey requires at least one crypto feature to be enabled"),
71        }
72    }
73
74    /// Creates an Ed25519 private key.
75    #[cfg(feature = "ed25519")]
76    pub fn ed25519(key: crate::crypto::Ed25519PrivateKey) -> Self {
77        Self::Ed25519(key)
78    }
79
80    /// Creates a Secp256k1 private key.
81    #[cfg(feature = "secp256k1")]
82    pub fn secp256k1(key: crate::crypto::Secp256k1PrivateKey) -> Self {
83        Self::Secp256k1(key)
84    }
85
86    /// Creates a Secp256r1 private key.
87    #[cfg(feature = "secp256r1")]
88    pub fn secp256r1(key: crate::crypto::Secp256r1PrivateKey) -> Self {
89        Self::Secp256r1(key)
90    }
91}
92
93impl Clone for AnyPrivateKey {
94    #[allow(unreachable_code)]
95    fn clone(&self) -> Self {
96        match self {
97            #[cfg(feature = "ed25519")]
98            Self::Ed25519(key) => Self::Ed25519(key.clone()),
99            #[cfg(feature = "secp256k1")]
100            Self::Secp256k1(key) => Self::Secp256k1(key.clone()),
101            #[cfg(feature = "secp256r1")]
102            Self::Secp256r1(key) => Self::Secp256r1(key.clone()),
103            #[allow(unreachable_patterns)]
104            _ => unreachable!("AnyPrivateKey requires at least one crypto feature to be enabled"),
105        }
106    }
107}
108
109impl fmt::Debug for AnyPrivateKey {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        write!(f, "AnyPrivateKey({:?})", self.variant())
112    }
113}
114
115/// A multi-key account supporting M-of-N threshold signatures with mixed key types.
116///
117/// Unlike `MultiEd25519Account`, this account type supports mixed signature
118/// schemes (e.g., 2-of-3 where one key is Ed25519 and two are Secp256k1).
119///
120/// # Example
121///
122/// ```rust,ignore
123/// use aptos_sdk::account::{MultiKeyAccount, AnyPrivateKey};
124/// use aptos_sdk::crypto::{Ed25519PrivateKey, Secp256k1PrivateKey};
125///
126/// // Create a 2-of-3 multisig with mixed key types
127/// let keys = vec![
128///     AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
129///     AnyPrivateKey::secp256k1(Secp256k1PrivateKey::generate()),
130///     AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
131/// ];
132/// let account = MultiKeyAccount::new(keys, 2).unwrap();
133///
134/// println!("Address: {}", account.address());
135/// println!("Threshold: {}/{}", account.threshold(), account.num_keys());
136/// ```
137pub struct MultiKeyAccount {
138    /// The private keys owned by this account (may be a subset).
139    private_keys: Vec<(u8, AnyPrivateKey)>,
140    /// The multi-key public key (contains all public keys).
141    public_key: MultiKeyPublicKey,
142    /// The derived account address.
143    address: AccountAddress,
144}
145
146impl MultiKeyAccount {
147    /// Creates a new multi-key account from private keys.
148    ///
149    /// All provided private keys will be used for signing. The threshold
150    /// specifies how many signatures are required.
151    ///
152    /// # Arguments
153    ///
154    /// * `private_keys` - The private keys (can be mixed types)
155    /// * `threshold` - The required number of signatures (M in M-of-N)
156    ///
157    /// # Errors
158    ///
159    /// This function will return an error if:
160    /// - No private keys are provided
161    /// - The threshold exceeds the number of keys
162    /// - The multi-key public key creation fails (e.g., too many keys, invalid threshold)
163    pub fn new(private_keys: Vec<AnyPrivateKey>, threshold: u8) -> AptosResult<Self> {
164        if private_keys.is_empty() {
165            return Err(AptosError::InvalidPrivateKey(
166                "at least one private key is required".into(),
167            ));
168        }
169        if (threshold as usize) > private_keys.len() {
170            return Err(AptosError::InvalidPrivateKey(format!(
171                "threshold {} exceeds number of keys {}",
172                threshold,
173                private_keys.len()
174            )));
175        }
176
177        let public_keys: Vec<_> = private_keys.iter().map(AnyPrivateKey::public_key).collect();
178        let multi_public_key = MultiKeyPublicKey::new(public_keys, threshold)?;
179        let address = multi_public_key.to_address();
180
181        // Index the private keys (safe: validated by MultiKeyPublicKey::new above)
182        #[allow(clippy::cast_possible_truncation)]
183        let indexed_keys: Vec<_> = private_keys
184            .into_iter()
185            .enumerate()
186            .map(|(i, k)| (i as u8, k))
187            .collect();
188
189        Ok(Self {
190            private_keys: indexed_keys,
191            public_key: multi_public_key,
192            address,
193        })
194    }
195
196    /// Creates a multi-key account from public keys with a subset of private keys.
197    ///
198    /// Use this when you don't have all the private keys.
199    ///
200    /// # Arguments
201    ///
202    /// * `public_keys` - All the public keys in the account
203    /// * `private_keys` - The private keys you own, with their indices
204    /// * `threshold` - The required number of signatures
205    ///
206    /// # Errors
207    ///
208    /// This function will return an error if:
209    /// - The multi-key public key creation fails
210    /// - A private key index is out of bounds
211    /// - A private key doesn't match the public key at its index (wrong type or bytes)
212    pub fn from_keys(
213        public_keys: Vec<AnyPublicKey>,
214        private_keys: Vec<(u8, AnyPrivateKey)>,
215        threshold: u8,
216    ) -> AptosResult<Self> {
217        let multi_public_key = MultiKeyPublicKey::new(public_keys, threshold)?;
218
219        // Validate private key indices and types
220        for (index, key) in &private_keys {
221            if *index as usize >= multi_public_key.num_keys() {
222                return Err(AptosError::InvalidPrivateKey(format!(
223                    "private key index {index} out of bounds"
224                )));
225            }
226
227            // Verify the private key matches the public key at that index
228            let Some(expected_pk) = multi_public_key.get(*index as usize) else {
229                return Err(AptosError::InvalidPrivateKey(format!(
230                    "private key index {index} out of bounds"
231                )));
232            };
233
234            let actual_pk = key.public_key();
235            if expected_pk.variant != actual_pk.variant || expected_pk.bytes != actual_pk.bytes {
236                return Err(AptosError::InvalidPrivateKey(format!(
237                    "private key at index {index} doesn't match public key"
238                )));
239            }
240        }
241
242        let address = multi_public_key.to_address();
243
244        Ok(Self {
245            private_keys,
246            public_key: multi_public_key,
247            address,
248        })
249    }
250
251    /// Creates a view-only multi-key account (no signing capability).
252    ///
253    /// # Errors
254    ///
255    /// Returns an error if the multi-key public key creation fails (e.g., no keys provided, too many keys, invalid threshold).
256    pub fn view_only(public_keys: Vec<AnyPublicKey>, threshold: u8) -> AptosResult<Self> {
257        let multi_public_key = MultiKeyPublicKey::new(public_keys, threshold)?;
258        let address = multi_public_key.to_address();
259
260        Ok(Self {
261            private_keys: vec![],
262            public_key: multi_public_key,
263            address,
264        })
265    }
266
267    /// Returns the account address.
268    pub fn address(&self) -> AccountAddress {
269        self.address
270    }
271
272    /// Returns the multi-key public key.
273    pub fn public_key(&self) -> &MultiKeyPublicKey {
274        &self.public_key
275    }
276
277    /// Returns the number of keys in the account.
278    pub fn num_keys(&self) -> usize {
279        self.public_key.num_keys()
280    }
281
282    /// Returns the signature threshold.
283    pub fn threshold(&self) -> u8 {
284        self.public_key.threshold()
285    }
286
287    /// Returns the number of private keys we have.
288    pub fn num_owned_keys(&self) -> usize {
289        self.private_keys.len()
290    }
291
292    /// Checks if we can sign (have enough private keys to meet threshold).
293    pub fn can_sign(&self) -> bool {
294        self.private_keys.len() >= self.threshold() as usize
295    }
296
297    /// Returns the indices of the private keys we own.
298    pub fn owned_key_indices(&self) -> Vec<u8> {
299        self.private_keys.iter().map(|(i, _)| *i).collect()
300    }
301
302    /// Returns the key types for each index.
303    pub fn key_types(&self) -> Vec<AnyPublicKeyVariant> {
304        self.public_key
305            .public_keys()
306            .iter()
307            .map(|pk| pk.variant)
308            .collect()
309    }
310
311    /// Signs a message using the owned private keys.
312    ///
313    /// Will use up to `threshold` keys for signing.
314    ///
315    /// # Errors
316    ///
317    /// Returns an error if we don't have enough private keys to meet the threshold.
318    pub fn sign_message(&self, message: &[u8]) -> AptosResult<MultiKeySignature> {
319        let threshold = self.threshold() as usize;
320        if self.private_keys.len() < threshold {
321            return Err(AptosError::InsufficientSignatures {
322                required: threshold,
323                provided: self.private_keys.len(),
324            });
325        }
326
327        // Sign with the first `threshold` keys
328        let signatures: Vec<_> = self.private_keys[..threshold]
329            .iter()
330            .map(|(index, key)| (*index, key.sign(message)))
331            .collect();
332
333        MultiKeySignature::new(signatures)
334    }
335
336    /// Signs a message using specific key indices.
337    ///
338    /// # Errors
339    ///
340    /// This function will return an error if:
341    /// - Not enough indices are provided to meet the threshold
342    /// - We don't own a private key at one of the specified indices
343    pub fn sign_with_indices(
344        &self,
345        message: &[u8],
346        indices: &[u8],
347    ) -> AptosResult<MultiKeySignature> {
348        if indices.len() < self.threshold() as usize {
349            return Err(AptosError::InsufficientSignatures {
350                required: self.threshold() as usize,
351                provided: indices.len(),
352            });
353        }
354
355        let mut signatures = Vec::with_capacity(indices.len());
356
357        for &index in indices {
358            let key = self
359                .private_keys
360                .iter()
361                .find(|(i, _)| *i == index)
362                .ok_or_else(|| {
363                    AptosError::InvalidPrivateKey(format!(
364                        "don't have private key at index {index}"
365                    ))
366                })?;
367
368            signatures.push((index, key.1.sign(message)));
369        }
370
371        MultiKeySignature::new(signatures)
372    }
373
374    /// Verifies a signature against a message.
375    ///
376    /// # Errors
377    ///
378    /// Returns an error if signature verification fails (e.g., invalid signature, insufficient signatures, signature mismatch).
379    pub fn verify(&self, message: &[u8], signature: &MultiKeySignature) -> AptosResult<()> {
380        self.public_key.verify(message, signature)
381    }
382
383    /// Returns the authentication key for this account.
384    pub fn auth_key(&self) -> AuthenticationKey {
385        AuthenticationKey::new(self.public_key.to_authentication_key())
386    }
387
388    /// Collects individual signatures into a multi-key signature.
389    ///
390    /// # Errors
391    ///
392    /// This function will return an error if:
393    /// - No signatures are provided
394    /// - Too many signatures are provided (more than 32)
395    /// - Signer indices are out of bounds or duplicated
396    pub fn aggregate_signatures(
397        signatures: Vec<(u8, AnySignature)>,
398    ) -> AptosResult<MultiKeySignature> {
399        MultiKeySignature::new(signatures)
400    }
401
402    /// Creates an individual signature contribution.
403    ///
404    /// # Errors
405    ///
406    /// Returns an error if we don't have a private key at the specified index.
407    pub fn create_signature_contribution(
408        &self,
409        message: &[u8],
410        key_index: u8,
411    ) -> AptosResult<(u8, AnySignature)> {
412        let key = self
413            .private_keys
414            .iter()
415            .find(|(i, _)| *i == key_index)
416            .ok_or_else(|| {
417                AptosError::InvalidPrivateKey(format!(
418                    "don't have private key at index {key_index}"
419                ))
420            })?;
421
422        Ok((key_index, key.1.sign(message)))
423    }
424}
425
426impl Account for MultiKeyAccount {
427    fn address(&self) -> AccountAddress {
428        self.address
429    }
430
431    fn public_key_bytes(&self) -> Vec<u8> {
432        self.public_key.to_bytes()
433    }
434
435    fn sign(&self, message: &[u8]) -> AptosResult<Vec<u8>> {
436        let sig = self.sign_message(message)?;
437        Ok(sig.to_bytes())
438    }
439
440    fn authentication_key(&self) -> AuthenticationKey {
441        self.auth_key()
442    }
443
444    fn signature_scheme(&self) -> u8 {
445        MULTI_KEY_SCHEME
446    }
447}
448
449impl fmt::Debug for MultiKeyAccount {
450    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451        // SECURITY: Intentionally omit private_keys to prevent secret leakage
452        // in logs, panic messages, or debug output
453        f.debug_struct("MultiKeyAccount")
454            .field("address", &self.address)
455            .field(
456                "keys",
457                &format!(
458                    "{}-of-{} (own {})",
459                    self.threshold(),
460                    self.num_keys(),
461                    self.num_owned_keys()
462                ),
463            )
464            .field("types", &self.key_types())
465            .field("public_key", &self.public_key)
466            .field("private_keys", &"[REDACTED]")
467            .finish()
468    }
469}
470
471impl fmt::Display for MultiKeyAccount {
472    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473        write!(
474            f,
475            "MultiKeyAccount({}, {}-of-{})",
476            self.address.to_short_string(),
477            self.threshold(),
478            self.num_keys()
479        )
480    }
481}
482
483#[cfg(test)]
484mod tests {
485    use super::*;
486
487    #[test]
488    #[cfg(feature = "ed25519")]
489    fn test_create_ed25519_only() {
490        use crate::crypto::Ed25519PrivateKey;
491
492        let keys: Vec<_> = (0..3)
493            .map(|_| AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()))
494            .collect();
495        let account = MultiKeyAccount::new(keys, 2).unwrap();
496
497        assert_eq!(account.num_keys(), 3);
498        assert_eq!(account.threshold(), 2);
499        assert_eq!(account.num_owned_keys(), 3);
500        assert!(account.can_sign());
501
502        // All keys should be Ed25519
503        for variant in account.key_types() {
504            assert_eq!(variant, AnyPublicKeyVariant::Ed25519);
505        }
506    }
507
508    #[test]
509    #[cfg(all(feature = "ed25519", feature = "secp256k1"))]
510    fn test_create_mixed_types() {
511        use crate::crypto::{Ed25519PrivateKey, Secp256k1PrivateKey};
512
513        let keys = vec![
514            AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
515            AnyPrivateKey::secp256k1(Secp256k1PrivateKey::generate()),
516            AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
517        ];
518        let account = MultiKeyAccount::new(keys, 2).unwrap();
519
520        assert_eq!(account.num_keys(), 3);
521        let types = account.key_types();
522        assert_eq!(types[0], AnyPublicKeyVariant::Ed25519);
523        assert_eq!(types[1], AnyPublicKeyVariant::Secp256k1);
524        assert_eq!(types[2], AnyPublicKeyVariant::Ed25519);
525    }
526
527    #[test]
528    #[cfg(feature = "ed25519")]
529    fn test_sign_and_verify() {
530        use crate::crypto::Ed25519PrivateKey;
531
532        let keys: Vec<_> = (0..3)
533            .map(|_| AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()))
534            .collect();
535        let account = MultiKeyAccount::new(keys, 2).unwrap();
536
537        let message = b"test message";
538        let signature = account.sign_message(message).unwrap();
539
540        assert!(account.verify(message, &signature).is_ok());
541        assert!(account.verify(b"wrong message", &signature).is_err());
542    }
543
544    #[test]
545    #[cfg(all(feature = "ed25519", feature = "secp256k1"))]
546    fn test_sign_mixed_types() {
547        use crate::crypto::{Ed25519PrivateKey, Secp256k1PrivateKey};
548
549        let keys = vec![
550            AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
551            AnyPrivateKey::secp256k1(Secp256k1PrivateKey::generate()),
552            AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
553        ];
554        let account = MultiKeyAccount::new(keys, 2).unwrap();
555
556        let message = b"test message";
557        let signature = account.sign_message(message).unwrap();
558
559        assert!(account.verify(message, &signature).is_ok());
560    }
561
562    #[test]
563    #[cfg(feature = "ed25519")]
564    fn test_partial_keys() {
565        use crate::crypto::Ed25519PrivateKey;
566
567        let all_keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
568        let public_keys: Vec<_> = all_keys
569            .iter()
570            .map(|k| AnyPublicKey::ed25519(&k.public_key()))
571            .collect();
572
573        // Only own keys 0 and 2
574        let my_keys = vec![
575            (0u8, AnyPrivateKey::ed25519(all_keys[0].clone())),
576            (2u8, AnyPrivateKey::ed25519(all_keys[2].clone())),
577        ];
578
579        let account = MultiKeyAccount::from_keys(public_keys, my_keys, 2).unwrap();
580
581        assert_eq!(account.num_keys(), 3);
582        assert_eq!(account.num_owned_keys(), 2);
583        assert!(account.can_sign());
584
585        // Should be able to sign
586        let message = b"test";
587        let signature = account.sign_message(message).unwrap();
588        assert!(account.verify(message, &signature).is_ok());
589    }
590
591    #[test]
592    #[cfg(feature = "ed25519")]
593    fn test_insufficient_keys() {
594        use crate::crypto::Ed25519PrivateKey;
595
596        let all_keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
597        let public_keys: Vec<_> = all_keys
598            .iter()
599            .map(|k| AnyPublicKey::ed25519(&k.public_key()))
600            .collect();
601
602        // Only own 1 key but need 2
603        let my_keys = vec![(0u8, AnyPrivateKey::ed25519(all_keys[0].clone()))];
604
605        let account = MultiKeyAccount::from_keys(public_keys, my_keys, 2).unwrap();
606
607        assert!(!account.can_sign());
608        assert!(account.sign_message(b"test").is_err());
609    }
610
611    #[test]
612    #[cfg(feature = "ed25519")]
613    fn test_view_only() {
614        use crate::crypto::Ed25519PrivateKey;
615
616        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
617        let public_keys: Vec<_> = keys
618            .iter()
619            .map(|k| AnyPublicKey::ed25519(&k.public_key()))
620            .collect();
621
622        let view_only = MultiKeyAccount::view_only(public_keys, 2).unwrap();
623
624        assert_eq!(view_only.num_keys(), 3);
625        assert_eq!(view_only.num_owned_keys(), 0);
626        assert!(!view_only.can_sign());
627        assert!(view_only.sign_message(b"test").is_err());
628    }
629
630    #[test]
631    #[cfg(feature = "ed25519")]
632    fn test_deterministic_address() {
633        use crate::crypto::Ed25519PrivateKey;
634
635        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
636        let public_keys: Vec<_> = keys
637            .iter()
638            .map(|k| AnyPublicKey::ed25519(&k.public_key()))
639            .collect();
640
641        let account1 = MultiKeyAccount::new(
642            keys.iter()
643                .map(|k| AnyPrivateKey::ed25519(k.clone()))
644                .collect(),
645            2,
646        )
647        .unwrap();
648        let account2 = MultiKeyAccount::view_only(public_keys, 2).unwrap();
649
650        // Same public keys should produce same address
651        assert_eq!(account1.address(), account2.address());
652    }
653
654    #[test]
655    #[cfg(feature = "ed25519")]
656    fn test_any_private_key_variant() {
657        use crate::crypto::Ed25519PrivateKey;
658
659        let key = AnyPrivateKey::ed25519(Ed25519PrivateKey::generate());
660        assert_eq!(key.variant(), AnyPublicKeyVariant::Ed25519);
661    }
662
663    #[test]
664    #[cfg(feature = "secp256k1")]
665    fn test_any_private_key_secp256k1() {
666        use crate::crypto::Secp256k1PrivateKey;
667
668        let key = AnyPrivateKey::secp256k1(Secp256k1PrivateKey::generate());
669        assert_eq!(key.variant(), AnyPublicKeyVariant::Secp256k1);
670
671        // Test public key extraction
672        let pk = key.public_key();
673        assert_eq!(pk.variant, AnyPublicKeyVariant::Secp256k1);
674
675        // Test signing - just verify it doesn't panic
676        let _sig = key.sign(b"test message");
677    }
678
679    #[test]
680    #[cfg(feature = "secp256r1")]
681    fn test_any_private_key_secp256r1() {
682        use crate::crypto::Secp256r1PrivateKey;
683
684        let key = AnyPrivateKey::secp256r1(Secp256r1PrivateKey::generate());
685        assert_eq!(key.variant(), AnyPublicKeyVariant::Secp256r1);
686
687        // Test public key extraction
688        let pk = key.public_key();
689        assert_eq!(pk.variant, AnyPublicKeyVariant::Secp256r1);
690
691        // Test signing - just verify it doesn't panic
692        let _sig = key.sign(b"test message");
693    }
694
695    #[test]
696    #[cfg(feature = "ed25519")]
697    fn test_any_private_key_clone() {
698        use crate::crypto::Ed25519PrivateKey;
699
700        let key = AnyPrivateKey::ed25519(Ed25519PrivateKey::generate());
701        let cloned = key.clone();
702        assert_eq!(key.variant(), cloned.variant());
703        // Both should produce same public key
704        assert_eq!(
705            key.public_key().to_bcs_bytes(),
706            cloned.public_key().to_bcs_bytes()
707        );
708    }
709
710    #[test]
711    #[cfg(feature = "ed25519")]
712    fn test_any_private_key_debug() {
713        use crate::crypto::Ed25519PrivateKey;
714
715        let key = AnyPrivateKey::ed25519(Ed25519PrivateKey::generate());
716        let debug = format!("{key:?}");
717        assert!(debug.contains("AnyPrivateKey"));
718        assert!(debug.contains("Ed25519"));
719    }
720
721    #[test]
722    #[cfg(feature = "ed25519")]
723    fn test_multi_key_account_empty_keys() {
724        let result = MultiKeyAccount::new(vec![], 1);
725        assert!(result.is_err());
726    }
727
728    #[test]
729    #[cfg(feature = "ed25519")]
730    fn test_multi_key_account_threshold_zero() {
731        use crate::crypto::Ed25519PrivateKey;
732
733        let keys: Vec<_> = (0..2)
734            .map(|_| AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()))
735            .collect();
736        let result = MultiKeyAccount::new(keys, 0);
737        assert!(result.is_err());
738    }
739
740    #[test]
741    #[cfg(feature = "ed25519")]
742    fn test_multi_key_account_threshold_exceeds_keys() {
743        use crate::crypto::Ed25519PrivateKey;
744
745        let keys: Vec<_> = (0..2)
746            .map(|_| AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()))
747            .collect();
748        let result = MultiKeyAccount::new(keys, 5);
749        assert!(result.is_err());
750    }
751
752    #[test]
753    #[cfg(feature = "ed25519")]
754    fn test_from_keys_invalid_index() {
755        use crate::crypto::Ed25519PrivateKey;
756
757        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
758        let public_keys: Vec<_> = keys
759            .iter()
760            .map(|k| AnyPublicKey::ed25519(&k.public_key()))
761            .collect();
762
763        // Index 10 is out of bounds (only 3 keys)
764        let my_keys = vec![(10u8, AnyPrivateKey::ed25519(keys[0].clone()))];
765
766        let result = MultiKeyAccount::from_keys(public_keys, my_keys, 1);
767        assert!(result.is_err());
768    }
769
770    #[test]
771    #[cfg(feature = "ed25519")]
772    fn test_from_keys_mismatched_public_key() {
773        use crate::crypto::Ed25519PrivateKey;
774
775        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
776        let public_keys: Vec<_> = keys
777            .iter()
778            .map(|k| AnyPublicKey::ed25519(&k.public_key()))
779            .collect();
780
781        // Provide a different private key at index 0
782        let different_key = Ed25519PrivateKey::generate();
783        let my_keys = vec![(0u8, AnyPrivateKey::ed25519(different_key))];
784
785        let result = MultiKeyAccount::from_keys(public_keys, my_keys, 1);
786        assert!(result.is_err());
787    }
788
789    #[test]
790    #[cfg(feature = "ed25519")]
791    fn test_multi_key_account_public_key() {
792        use crate::crypto::Ed25519PrivateKey;
793
794        let keys: Vec<_> = (0..2)
795            .map(|_| AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()))
796            .collect();
797        let account = MultiKeyAccount::new(keys, 2).unwrap();
798        let pk = account.public_key();
799        assert_eq!(pk.num_keys(), 2);
800    }
801
802    #[test]
803    #[cfg(feature = "ed25519")]
804    fn test_multi_key_account_address_not_zero() {
805        use crate::crypto::Ed25519PrivateKey;
806
807        let keys: Vec<_> = (0..2)
808            .map(|_| AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()))
809            .collect();
810        let account = MultiKeyAccount::new(keys, 2).unwrap();
811        assert!(!account.address().is_zero());
812    }
813
814    #[test]
815    #[cfg(feature = "ed25519")]
816    fn test_multi_key_account_display() {
817        use crate::crypto::Ed25519PrivateKey;
818
819        let keys: Vec<_> = (0..2)
820            .map(|_| AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()))
821            .collect();
822        let account = MultiKeyAccount::new(keys, 2).unwrap();
823        let display = format!("{account}");
824        // Display contains the address which starts with 0x
825        assert!(display.contains("0x") || display.contains("MultiKeyAccount"));
826    }
827
828    #[test]
829    #[cfg(feature = "ed25519")]
830    fn test_multi_key_account_debug() {
831        use crate::crypto::Ed25519PrivateKey;
832
833        let keys: Vec<_> = (0..2)
834            .map(|_| AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()))
835            .collect();
836        let account = MultiKeyAccount::new(keys, 2).unwrap();
837        let debug = format!("{account:?}");
838        assert!(debug.contains("MultiKeyAccount"));
839    }
840}