Skip to main content

aptos_sdk/account/
multi_ed25519.rs

1//! Multi-Ed25519 account implementation.
2//!
3//! This module provides the [`MultiEd25519Account`] type for M-of-N
4//! threshold signature accounts using Ed25519 keys.
5
6use crate::account::account::{Account, AuthenticationKey};
7use crate::crypto::{
8    Ed25519PrivateKey, Ed25519PublicKey, Ed25519Signature, MULTI_ED25519_SCHEME,
9    MultiEd25519PublicKey, MultiEd25519Signature,
10};
11use crate::error::{AptosError, AptosResult};
12use crate::types::AccountAddress;
13use std::fmt;
14
15/// A multi-Ed25519 account supporting M-of-N threshold signatures.
16///
17/// This account type holds multiple Ed25519 keys and requires a threshold
18/// number of signatures to authorize transactions.
19///
20/// # Example
21///
22/// ```rust,ignore
23/// use aptos_sdk::account::MultiEd25519Account;
24/// use aptos_sdk::crypto::Ed25519PrivateKey;
25///
26/// // Create a 2-of-3 multisig account
27/// let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
28/// let account = MultiEd25519Account::new(keys, 2).unwrap();
29///
30/// println!("Address: {}", account.address());
31/// println!("Threshold: {}/{}", account.threshold(), account.num_keys());
32///
33/// // Sign a message
34/// let message = b"hello";
35/// let signature = account.sign(message);
36/// ```
37pub struct MultiEd25519Account {
38    /// The private keys owned by this account (may be a subset).
39    private_keys: Vec<(u8, Ed25519PrivateKey)>,
40    /// The multi-Ed25519 public key (contains all public keys).
41    public_key: MultiEd25519PublicKey,
42    /// The derived account address.
43    address: AccountAddress,
44}
45
46impl MultiEd25519Account {
47    /// Creates a new multi-Ed25519 account from private keys.
48    ///
49    /// All provided private keys will be used for signing. The threshold
50    /// specifies how many signatures are required.
51    ///
52    /// # Arguments
53    ///
54    /// * `private_keys` - The Ed25519 private keys
55    /// * `threshold` - The required number of signatures (M in M-of-N)
56    ///
57    /// # Example
58    ///
59    /// ```rust,ignore
60    /// // 2-of-3 multisig where we own all 3 keys
61    /// let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
62    /// let account = MultiEd25519Account::new(keys, 2).unwrap();
63    /// ```
64    ///
65    /// # Errors
66    ///
67    /// This function will return an error if:
68    /// - No private keys are provided
69    /// - The threshold exceeds the number of keys
70    /// - The multi-Ed25519 public key creation fails (e.g., too many keys, invalid threshold)
71    pub fn new(private_keys: Vec<Ed25519PrivateKey>, threshold: u8) -> AptosResult<Self> {
72        if private_keys.is_empty() {
73            return Err(AptosError::InvalidPrivateKey(
74                "at least one private key is required".into(),
75            ));
76        }
77        if (threshold as usize) > private_keys.len() {
78            return Err(AptosError::InvalidPrivateKey(format!(
79                "threshold {} exceeds number of keys {}",
80                threshold,
81                private_keys.len()
82            )));
83        }
84
85        let public_keys: Vec<_> = private_keys
86            .iter()
87            .map(Ed25519PrivateKey::public_key)
88            .collect();
89        let multi_public_key = MultiEd25519PublicKey::new(public_keys, threshold)?;
90        let address = multi_public_key.to_address();
91
92        // Index the private keys (safe: validated by MultiEd25519PublicKey::new above)
93        #[allow(clippy::cast_possible_truncation)]
94        let indexed_keys: Vec<_> = private_keys
95            .into_iter()
96            .enumerate()
97            .map(|(i, k)| (i as u8, k))
98            .collect();
99
100        Ok(Self {
101            private_keys: indexed_keys,
102            public_key: multi_public_key,
103            address,
104        })
105    }
106
107    /// Creates a multi-Ed25519 account from public keys with a subset of private keys.
108    ///
109    /// Use this when you don't have all the private keys (e.g., a 2-of-3 where
110    /// you only own 2 keys).
111    ///
112    /// # Arguments
113    ///
114    /// * `public_keys` - All the Ed25519 public keys in the account
115    /// * `private_keys` - The private keys you own, with their indices
116    /// * `threshold` - The required number of signatures
117    ///
118    /// # Example
119    ///
120    /// ```rust,ignore
121    /// // 2-of-3 multisig where we own keys 0 and 2
122    /// let all_public_keys = vec![pk0, pk1, pk2];
123    /// let my_keys = vec![(0, sk0), (2, sk2)];
124    /// let account = MultiEd25519Account::from_keys(all_public_keys, my_keys, 2).unwrap();
125    /// ```
126    ///
127    /// # Errors
128    ///
129    /// This function will return an error if:
130    /// - The multi-Ed25519 public key creation fails
131    /// - A private key index is out of bounds
132    /// - A private key doesn't match the public key at its index
133    pub fn from_keys(
134        public_keys: Vec<Ed25519PublicKey>,
135        private_keys: Vec<(u8, Ed25519PrivateKey)>,
136        threshold: u8,
137    ) -> AptosResult<Self> {
138        let multi_public_key = MultiEd25519PublicKey::new(public_keys, threshold)?;
139
140        // Validate private key indices
141        for (index, key) in &private_keys {
142            if *index as usize >= multi_public_key.num_keys() {
143                return Err(AptosError::InvalidPrivateKey(format!(
144                    "private key index {index} out of bounds"
145                )));
146            }
147            // Verify the private key matches the public key at that index
148            let expected_pk = &multi_public_key.public_keys()[*index as usize];
149            if key.public_key() != *expected_pk {
150                return Err(AptosError::InvalidPrivateKey(format!(
151                    "private key at index {index} doesn't match public key"
152                )));
153            }
154        }
155
156        let address = multi_public_key.to_address();
157
158        Ok(Self {
159            private_keys,
160            public_key: multi_public_key,
161            address,
162        })
163    }
164
165    /// Creates a view-only multi-Ed25519 account (no signing capability).
166    ///
167    /// This is useful for verifying signatures or looking up account information
168    /// when you don't have any private keys.
169    ///
170    /// # Errors
171    ///
172    /// Returns an error if the multi-Ed25519 public key creation fails (e.g., no keys provided, too many keys, invalid threshold).
173    pub fn view_only(public_keys: Vec<Ed25519PublicKey>, threshold: u8) -> AptosResult<Self> {
174        let multi_public_key = MultiEd25519PublicKey::new(public_keys, threshold)?;
175        let address = multi_public_key.to_address();
176
177        Ok(Self {
178            private_keys: vec![],
179            public_key: multi_public_key,
180            address,
181        })
182    }
183
184    /// Returns the account address.
185    pub fn address(&self) -> AccountAddress {
186        self.address
187    }
188
189    /// Returns the multi-Ed25519 public key.
190    pub fn public_key(&self) -> &MultiEd25519PublicKey {
191        &self.public_key
192    }
193
194    /// Returns the number of keys in the account.
195    pub fn num_keys(&self) -> usize {
196        self.public_key.num_keys()
197    }
198
199    /// Returns the signature threshold.
200    pub fn threshold(&self) -> u8 {
201        self.public_key.threshold()
202    }
203
204    /// Returns the number of private keys we have.
205    pub fn num_owned_keys(&self) -> usize {
206        self.private_keys.len()
207    }
208
209    /// Checks if we can sign (have enough private keys to meet threshold).
210    pub fn can_sign(&self) -> bool {
211        self.private_keys.len() >= self.threshold() as usize
212    }
213
214    /// Returns the indices of the private keys we own.
215    pub fn owned_key_indices(&self) -> Vec<u8> {
216        self.private_keys.iter().map(|(i, _)| *i).collect()
217    }
218
219    /// Signs a message using the owned private keys.
220    ///
221    /// Will use up to `threshold` keys for signing.
222    ///
223    /// # Errors
224    ///
225    /// Returns an error if we don't have enough keys to meet the threshold.
226    fn sign_internal(&self, message: &[u8]) -> AptosResult<MultiEd25519Signature> {
227        let threshold = self.threshold() as usize;
228        if self.private_keys.len() < threshold {
229            return Err(AptosError::InsufficientSignatures {
230                required: threshold,
231                provided: self.private_keys.len(),
232            });
233        }
234
235        // Sign with the first `threshold` keys
236        let signatures: Vec<_> = self.private_keys[..threshold]
237            .iter()
238            .map(|(index, key)| (*index, key.sign(message)))
239            .collect();
240
241        MultiEd25519Signature::new(signatures)
242    }
243
244    /// Signs a message using specific key indices.
245    ///
246    /// # Arguments
247    ///
248    /// * `message` - The message to sign
249    /// * `indices` - The indices of keys to use for signing
250    ///
251    /// # Errors
252    ///
253    /// Returns an error if:
254    /// - We don't own a key at the specified index
255    /// - Not enough indices are provided to meet threshold
256    pub fn sign_with_indices(
257        &self,
258        message: &[u8],
259        indices: &[u8],
260    ) -> AptosResult<MultiEd25519Signature> {
261        if indices.len() < self.threshold() as usize {
262            return Err(AptosError::InsufficientSignatures {
263                required: self.threshold() as usize,
264                provided: indices.len(),
265            });
266        }
267
268        let mut signatures = Vec::with_capacity(indices.len());
269
270        for &index in indices {
271            let key = self
272                .private_keys
273                .iter()
274                .find(|(i, _)| *i == index)
275                .ok_or_else(|| {
276                    AptosError::InvalidPrivateKey(format!(
277                        "don't have private key at index {index}"
278                    ))
279                })?;
280
281            signatures.push((index, key.1.sign(message)));
282        }
283
284        MultiEd25519Signature::new(signatures)
285    }
286
287    /// Verifies a signature against a message.
288    ///
289    /// # Errors
290    ///
291    /// Returns an error if signature verification fails (e.g., invalid signature, insufficient signatures, signature mismatch).
292    pub fn verify(&self, message: &[u8], signature: &MultiEd25519Signature) -> AptosResult<()> {
293        self.public_key.verify(message, signature)
294    }
295
296    /// Signs a message using the owned private keys.
297    ///
298    /// Will use up to `threshold` keys for signing.
299    ///
300    /// # Errors
301    ///
302    /// Returns an error if we don't have enough keys to meet the threshold.
303    pub fn sign(&self, message: &[u8]) -> AptosResult<MultiEd25519Signature> {
304        self.sign_internal(message)
305    }
306
307    /// Returns the authentication key for this account.
308    pub fn auth_key(&self) -> AuthenticationKey {
309        AuthenticationKey::new(self.public_key.to_authentication_key())
310    }
311
312    /// Collects individual signatures into a multi-signature.
313    ///
314    /// Use this when collecting signatures from multiple parties.
315    ///
316    /// # Errors
317    ///
318    /// This function will return an error if:
319    /// - No signatures are provided
320    /// - Too many signatures are provided (more than 32)
321    /// - Signer indices are out of bounds or duplicated
322    pub fn aggregate_signatures(
323        signatures: Vec<(u8, Ed25519Signature)>,
324    ) -> AptosResult<MultiEd25519Signature> {
325        MultiEd25519Signature::new(signatures)
326    }
327
328    /// Creates an individual signature contribution for this account.
329    ///
330    /// Returns the signature with the signer index. Use this when contributing
331    /// your signature to a multi-party signing flow.
332    ///
333    /// # Errors
334    ///
335    /// Returns an error if we don't have a private key at the specified index.
336    pub fn create_signature_contribution(
337        &self,
338        message: &[u8],
339        key_index: u8,
340    ) -> AptosResult<(u8, Ed25519Signature)> {
341        let key = self
342            .private_keys
343            .iter()
344            .find(|(i, _)| *i == key_index)
345            .ok_or_else(|| {
346                AptosError::InvalidPrivateKey(format!(
347                    "don't have private key at index {key_index}"
348                ))
349            })?;
350
351        Ok((key_index, key.1.sign(message)))
352    }
353}
354
355impl Account for MultiEd25519Account {
356    fn address(&self) -> AccountAddress {
357        self.address
358    }
359
360    fn public_key_bytes(&self) -> Vec<u8> {
361        self.public_key.to_bytes()
362    }
363
364    fn sign(&self, message: &[u8]) -> AptosResult<Vec<u8>> {
365        let sig = self.sign_internal(message)?;
366        Ok(sig.to_bytes())
367    }
368
369    fn authentication_key(&self) -> AuthenticationKey {
370        AuthenticationKey::new(self.public_key.to_authentication_key())
371    }
372
373    fn signature_scheme(&self) -> u8 {
374        MULTI_ED25519_SCHEME
375    }
376}
377
378impl fmt::Debug for MultiEd25519Account {
379    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380        // SECURITY: Intentionally omit private_keys to prevent secret leakage
381        // in logs, panic messages, or debug output
382        f.debug_struct("MultiEd25519Account")
383            .field("address", &self.address)
384            .field(
385                "keys",
386                &format!(
387                    "{}-of-{} (own {})",
388                    self.threshold(),
389                    self.num_keys(),
390                    self.num_owned_keys()
391                ),
392            )
393            .field("public_key", &self.public_key)
394            .field("private_keys", &"[REDACTED]")
395            .finish()
396    }
397}
398
399impl fmt::Display for MultiEd25519Account {
400    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401        write!(
402            f,
403            "MultiEd25519Account({}, {}-of-{})",
404            self.address.to_short_string(),
405            self.threshold(),
406            self.num_keys()
407        )
408    }
409}
410
411#[cfg(test)]
412mod tests {
413    use super::*;
414
415    #[test]
416    fn test_create_2_of_3() {
417        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
418        let account = MultiEd25519Account::new(keys, 2).unwrap();
419
420        assert_eq!(account.num_keys(), 3);
421        assert_eq!(account.threshold(), 2);
422        assert_eq!(account.num_owned_keys(), 3);
423        assert!(account.can_sign());
424    }
425
426    #[test]
427    fn test_sign_and_verify() {
428        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
429        let account = MultiEd25519Account::new(keys, 2).unwrap();
430
431        let message = b"test message";
432        let signature = account.sign(message).unwrap();
433
434        assert!(account.verify(message, &signature).is_ok());
435        assert!(account.verify(b"wrong message", &signature).is_err());
436    }
437
438    #[test]
439    fn test_partial_keys() {
440        let all_keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
441        let public_keys: Vec<_> = all_keys.iter().map(Ed25519PrivateKey::public_key).collect();
442
443        // Only own keys 0 and 2
444        let my_keys = vec![(0u8, all_keys[0].clone()), (2u8, all_keys[2].clone())];
445
446        let account = MultiEd25519Account::from_keys(public_keys.clone(), my_keys, 2).unwrap();
447
448        assert_eq!(account.num_keys(), 3);
449        assert_eq!(account.num_owned_keys(), 2);
450        assert!(account.can_sign());
451
452        // Should be able to sign
453        let message = b"test";
454        let signature = account.sign(message).unwrap();
455        assert!(account.verify(message, &signature).is_ok());
456    }
457
458    #[test]
459    fn test_insufficient_keys() {
460        let all_keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
461        let public_keys: Vec<_> = all_keys.iter().map(Ed25519PrivateKey::public_key).collect();
462
463        // Only own 1 key but need 2
464        let my_keys = vec![(0u8, all_keys[0].clone())];
465
466        let account = MultiEd25519Account::from_keys(public_keys.clone(), my_keys, 2).unwrap();
467
468        assert!(!account.can_sign());
469        assert!(account.sign(b"test").is_err());
470    }
471
472    #[test]
473    fn test_sign_with_specific_indices() {
474        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
475        let account = MultiEd25519Account::new(keys, 2).unwrap();
476
477        let message = b"test";
478
479        // Sign with keys 1 and 2
480        let signature = account.sign_with_indices(message, &[1, 2]).unwrap();
481        assert!(account.verify(message, &signature).is_ok());
482
483        // Check that signatures are from correct indices
484        assert!(signature.has_signature(1));
485        assert!(signature.has_signature(2));
486        assert!(!signature.has_signature(0));
487    }
488
489    #[test]
490    fn test_view_only_account() {
491        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
492        let public_keys: Vec<_> = keys.iter().map(Ed25519PrivateKey::public_key).collect();
493
494        let view_only = MultiEd25519Account::view_only(public_keys, 2).unwrap();
495
496        assert_eq!(view_only.num_keys(), 3);
497        assert_eq!(view_only.num_owned_keys(), 0);
498        assert!(!view_only.can_sign());
499        assert!(view_only.sign(b"test").is_err());
500    }
501
502    #[test]
503    fn test_signature_aggregation() {
504        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
505        let public_keys: Vec<_> = keys.iter().map(Ed25519PrivateKey::public_key).collect();
506
507        // Simulate 3 parties each with their own key
508        let party0 =
509            MultiEd25519Account::from_keys(public_keys.clone(), vec![(0, keys[0].clone())], 2)
510                .unwrap();
511        let party2 =
512            MultiEd25519Account::from_keys(public_keys.clone(), vec![(2, keys[2].clone())], 2)
513                .unwrap();
514
515        let message = b"transaction data";
516
517        // Each party creates their contribution
518        let contrib0 = party0.create_signature_contribution(message, 0).unwrap();
519        let contrib2 = party2.create_signature_contribution(message, 2).unwrap();
520
521        // Aggregate the signatures
522        let aggregated =
523            MultiEd25519Account::aggregate_signatures(vec![contrib0, contrib2]).unwrap();
524
525        // Any party can verify
526        assert!(party0.verify(message, &aggregated).is_ok());
527        assert!(party2.verify(message, &aggregated).is_ok());
528    }
529
530    #[test]
531    fn test_deterministic_address() {
532        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
533        let public_keys: Vec<_> = keys.iter().map(Ed25519PrivateKey::public_key).collect();
534
535        let account1 = MultiEd25519Account::new(keys.clone(), 2).unwrap();
536        let account2 = MultiEd25519Account::view_only(public_keys, 2).unwrap();
537
538        // Same public keys should produce same address
539        assert_eq!(account1.address(), account2.address());
540    }
541
542    #[test]
543    fn test_empty_keys_error() {
544        let result = MultiEd25519Account::new(vec![], 1);
545        assert!(result.is_err());
546    }
547
548    #[test]
549    fn test_threshold_exceeds_keys_error() {
550        let keys: Vec<_> = (0..2).map(|_| Ed25519PrivateKey::generate()).collect();
551        let result = MultiEd25519Account::new(keys, 5);
552        assert!(result.is_err());
553    }
554
555    #[test]
556    fn test_from_keys_index_out_of_bounds() {
557        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
558        let public_keys: Vec<_> = keys.iter().map(Ed25519PrivateKey::public_key).collect();
559
560        // Index 10 is out of bounds
561        let my_keys = vec![(10u8, keys[0].clone())];
562        let result = MultiEd25519Account::from_keys(public_keys, my_keys, 1);
563        assert!(result.is_err());
564    }
565
566    #[test]
567    fn test_from_keys_mismatched_key() {
568        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
569        let public_keys: Vec<_> = keys.iter().map(Ed25519PrivateKey::public_key).collect();
570
571        // Provide wrong private key for index 0
572        let different_key = Ed25519PrivateKey::generate();
573        let my_keys = vec![(0u8, different_key)];
574        let result = MultiEd25519Account::from_keys(public_keys, my_keys, 1);
575        assert!(result.is_err());
576    }
577
578    #[test]
579    fn test_owned_key_indices() {
580        let all_keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
581        let public_keys: Vec<_> = all_keys.iter().map(Ed25519PrivateKey::public_key).collect();
582
583        let my_keys = vec![(0u8, all_keys[0].clone()), (2u8, all_keys[2].clone())];
584
585        let account = MultiEd25519Account::from_keys(public_keys, my_keys, 2).unwrap();
586        let indices = account.owned_key_indices();
587        assert_eq!(indices, vec![0, 2]);
588    }
589
590    #[test]
591    fn test_sign_with_indices_insufficient() {
592        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
593        let account = MultiEd25519Account::new(keys, 2).unwrap();
594
595        // Only 1 index but threshold is 2
596        let result = account.sign_with_indices(b"test", &[0]);
597        assert!(result.is_err());
598    }
599
600    #[test]
601    fn test_sign_with_indices_missing_key() {
602        let all_keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
603        let public_keys: Vec<_> = all_keys.iter().map(Ed25519PrivateKey::public_key).collect();
604
605        // Only own key 0
606        let my_keys = vec![(0u8, all_keys[0].clone())];
607        let account = MultiEd25519Account::from_keys(public_keys, my_keys, 1).unwrap();
608
609        // Try to sign with key 1 which we don't have
610        let result = account.sign_with_indices(b"test", &[1]);
611        assert!(result.is_err());
612    }
613
614    #[test]
615    fn test_create_signature_contribution_missing_key() {
616        let all_keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
617        let public_keys: Vec<_> = all_keys.iter().map(Ed25519PrivateKey::public_key).collect();
618
619        // Only own key 0
620        let my_keys = vec![(0u8, all_keys[0].clone())];
621        let account = MultiEd25519Account::from_keys(public_keys, my_keys, 1).unwrap();
622
623        // Try to contribute with key 1 which we don't have
624        let result = account.create_signature_contribution(b"test", 1);
625        assert!(result.is_err());
626    }
627
628    #[test]
629    fn test_account_trait_implementation() {
630        use crate::account::Account;
631
632        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
633        let account = MultiEd25519Account::new(keys, 2).unwrap();
634
635        // Test Account trait methods
636        assert!(!account.address().is_zero());
637        assert!(!account.public_key_bytes().is_empty());
638        assert_eq!(
639            account.signature_scheme(),
640            crate::crypto::MULTI_ED25519_SCHEME
641        );
642
643        // Test signing via trait (returns Vec<u8>)
644        let sig_bytes: Vec<u8> = Account::sign(&account, b"test").unwrap();
645        assert!(!sig_bytes.is_empty());
646    }
647
648    #[test]
649    fn test_auth_key() {
650        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
651        let account = MultiEd25519Account::new(keys, 2).unwrap();
652        let auth_key = account.auth_key();
653        assert_eq!(auth_key.as_bytes().len(), 32);
654    }
655
656    #[test]
657    fn test_display_format() {
658        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
659        let account = MultiEd25519Account::new(keys, 2).unwrap();
660        let display = format!("{account}");
661        assert!(display.contains("MultiEd25519Account"));
662        assert!(display.contains("2-of-3"));
663    }
664
665    #[test]
666    fn test_debug_format() {
667        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
668        let account = MultiEd25519Account::new(keys, 2).unwrap();
669        let debug = format!("{account:?}");
670        assert!(debug.contains("MultiEd25519Account"));
671        assert!(debug.contains("address"));
672    }
673
674    #[test]
675    fn test_public_key_accessor() {
676        let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
677        let account = MultiEd25519Account::new(keys, 2).unwrap();
678        let pk = account.public_key();
679        assert_eq!(pk.num_keys(), 3);
680        assert_eq!(pk.threshold(), 2);
681    }
682}