quorum_wallet/
quorum_wallet.rs1use anyhow::Result;
24use p256::elliptic_curve::SecretKey;
25use privy_rs::{
26 AuthorizationContext, PrivateKey, PrivyClient,
27 generated::types::{
28 CreateKeyQuorumBody, CreateKeyQuorumBodyDisplayName, CreateWalletBody, WalletChainType,
29 },
30};
31use tracing_subscriber::EnvFilter;
32use uuid::Uuid;
33
34#[tokio::main]
35async fn main() -> Result<()> {
36 tracing_subscriber::fmt()
37 .with_env_filter(
38 EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
39 )
40 .init();
41
42 let client = PrivyClient::new_from_env()?;
44
45 tracing::info!("initialized privy client from environment");
46
47 let timestamp = std::time::SystemTime::now()
49 .duration_since(std::time::SystemTime::UNIX_EPOCH)?
50 .as_secs();
51 let wallet_idempotency_key = format!("quorum-wallet-{}", Uuid::new_v4());
52
53 tracing::info!("Starting quorum wallet example");
54
55 tracing::info!("Generating three P-256 private keys for 2-of-3 quorum");
57 let mut rng = rand::thread_rng();
58 let key1 = SecretKey::<p256::NistP256>::random(&mut rng);
59 let key2 = SecretKey::<p256::NistP256>::random(&mut rng);
60 let key3 = SecretKey::<p256::NistP256>::random(&mut rng);
61
62 let pubkey1 = key1.public_key().to_string();
63 let pubkey2 = key2.public_key().to_string();
64 let pubkey3 = key3.public_key().to_string();
65
66 tracing::info!("Generated public keys:");
67 tracing::info!("Key 1: {}", pubkey1);
68 tracing::info!("Key 2: {}", pubkey2);
69 tracing::info!("Key 3: {}", pubkey3);
70
71 tracing::info!("Creating 2-of-3 key quorum");
73
74 let quorum_display_name =
75 CreateKeyQuorumBodyDisplayName::try_from(format!("Quorum Example {timestamp}").as_str())?;
76
77 let quorum_body = CreateKeyQuorumBody {
78 authorization_threshold: Some(2.0), display_name: Some(quorum_display_name),
80 public_keys: vec![pubkey1, pubkey2, pubkey3],
81 user_ids: vec![],
82 };
83
84 let key_quorum = client.key_quorums().create(&quorum_body).await?;
85
86 tracing::info!("Created key quorum with ID: {}", key_quorum.id);
87 tracing::info!("Quorum threshold: {:?}", key_quorum.authorization_threshold);
88
89 tracing::info!(
91 "Creating new Ethereum wallet owned by quorum with idempotency key: {}",
92 wallet_idempotency_key
93 );
94
95 let create_body = CreateWalletBody {
96 chain_type: WalletChainType::Ethereum,
97 additional_signers: None,
98 owner: None,
99 owner_id: Some(key_quorum.id.parse().unwrap()),
100 policy_ids: vec![],
101 };
102
103 let wallet = client
104 .wallets()
105 .create(Some(&wallet_idempotency_key), &create_body)
106 .await?;
107
108 tracing::info!("Created wallet with ID: {}", wallet.id);
109 tracing::info!("Wallet address: {}", wallet.address);
110 tracing::info!("Wallet owner ID: {:?}", wallet.owner_id);
111
112 tracing::info!("Testing wallet export with only one key (should fail due to quorum threshold)");
114
115 let single_key_ctx = AuthorizationContext::new().push(PrivateKey(
116 key1.to_sec1_pem(der::pem::LineEnding::LF)
117 .unwrap()
118 .as_str()
119 .to_owned(),
120 ));
121
122 let single_key_result = client.wallets().export(&wallet.id, &single_key_ctx).await;
123
124 match single_key_result {
125 Err(err) => {
126 tracing::info!(
127 "✓ Single key authorization correctly failed as expected: {:?}",
128 err
129 );
130 }
131 Ok(_) => {
132 tracing::error!("✗ Single key authorization should have failed but succeeded!");
133 return Err(anyhow::anyhow!(
134 "Single key authorization should have failed due to quorum threshold"
135 ));
136 }
137 }
138
139 tracing::info!("Testing wallet export with two keys (should succeed)");
141
142 let two_key_ctx = single_key_ctx.push(PrivateKey(
143 key2.to_sec1_pem(der::pem::LineEnding::LF)
144 .unwrap()
145 .as_str()
146 .to_owned(),
147 ));
148
149 let two_key_result = client.wallets().export(&wallet.id, &two_key_ctx).await;
150
151 match two_key_result {
152 Ok(export_result) => {
153 tracing::info!("✓ Two key authorization succeeded as expected");
154 tracing::info!("Exported private key length: {} bytes", export_result.len());
155 }
156 Err(err) => {
157 tracing::error!("✗ Two key authorization failed unexpectedly: {:?}", err);
158 return Err(anyhow::anyhow!(
159 "Two key authorization should have succeeded"
160 ));
161 }
162 }
163
164 Ok(())
165}