dynamic_waas_sdk/
keygen.rs1use std::sync::Arc;
17
18use dynamic_waas_sdk_core::{
19 api::{CreateWalletKeygenReq, KeygenCompleteEvent},
20 sse::{stream_sse_keygen, SseEventData},
21 Error, Result, ServerKeyShare, ThresholdSignatureScheme, WalletProperties,
22};
23use dynamic_waas_sdk_mpc::{EcdsaSigner, KeygenId, RoomUuid};
24use tracing::{debug, instrument};
25
26use crate::client::DynamicWalletClient;
27use crate::mpc_config::{threshold_wire, MpcSchemeConfig};
28
29#[derive(Debug)]
32pub struct KeygenOutput {
33 pub wallet_properties: WalletProperties,
36 pub external_server_key_shares: Vec<ServerKeyShare>,
38 pub raw_public_key_uncompressed: Vec<u8>,
41 pub raw_public_key_compressed: Vec<u8>,
43}
44
45#[derive(Debug, Clone)]
47#[non_exhaustive]
48pub struct KeygenOpts {
49 pub chain_name: String,
50 pub threshold_signature_scheme: ThresholdSignatureScheme,
51 pub derivation_path: Vec<u32>,
52 pub address_type: Option<String>,
53 pub back_up_to_dynamic: bool,
60 pub password: Option<String>,
64}
65
66impl KeygenOpts {
67 pub fn new(
68 chain_name: impl Into<String>,
69 threshold_signature_scheme: ThresholdSignatureScheme,
70 derivation_path: Vec<u32>,
71 ) -> Self {
72 Self {
73 chain_name: chain_name.into(),
74 threshold_signature_scheme,
75 derivation_path,
76 address_type: None,
77 back_up_to_dynamic: true,
78 password: None,
79 }
80 }
81
82 #[must_use]
83 pub fn with_address_type(mut self, t: impl Into<String>) -> Self {
84 self.address_type = Some(t.into());
85 self
86 }
87
88 #[must_use]
90 pub fn with_back_up_to_dynamic(mut self, value: bool) -> Self {
91 self.back_up_to_dynamic = value;
92 self
93 }
94
95 #[must_use]
98 pub fn with_password(mut self, password: impl Into<String>) -> Self {
99 self.password = Some(password.into());
100 self
101 }
102}
103
104pub(crate) fn validate_password_for_backup(
109 password: Option<&str>,
110 back_up_to_dynamic: bool,
111) -> Result<()> {
112 if back_up_to_dynamic && password.unwrap_or("").is_empty() {
113 return Err(Error::InvalidArgument(
114 "password is required when back_up_to_dynamic=true".into(),
115 ));
116 }
117 Ok(())
118}
119
120#[instrument(skip(client), fields(chain = %opts.chain_name))]
121#[allow(clippy::too_many_lines)]
122pub async fn run_keygen(client: &DynamicWalletClient, opts: KeygenOpts) -> Result<KeygenOutput> {
123 if !client.is_authenticated() {
124 return Err(Error::Authentication(crate::AUTH_REQUIRED_MSG.into()));
125 }
126
127 validate_password_for_backup(opts.password.as_deref(), opts.back_up_to_dynamic)?;
131
132 let scheme_cfg = MpcSchemeConfig::from(opts.threshold_signature_scheme);
133
134 let signer = EcdsaSigner::new(client.base_mpc_relay_url().to_string());
136 let mut init_results = Vec::with_capacity(scheme_cfg.client_threshold as usize);
137 for _ in 0..scheme_cfg.client_threshold {
138 init_results.push(signer.init_keygen()?);
139 }
140 let client_keygen_ids: Vec<String> = init_results
141 .iter()
142 .map(|r| r.keygen_id.as_str().to_owned())
143 .collect();
144
145 let body = CreateWalletKeygenReq {
147 chain: opts.chain_name.clone(),
148 client_keygen_ids: client_keygen_ids.clone(),
149 threshold_signature_scheme: threshold_wire(opts.threshold_signature_scheme).to_string(),
150 derivation_path: Some(opts.derivation_path.clone()),
151 address_type: opts.address_type.clone(),
152 };
153 let response = client.api().create_wallet_keygen(&body).await?;
154
155 let host_url = client.base_mpc_relay_url().to_string();
157 let init_results = Arc::new(init_results);
158 let init_results_for_cb = Arc::clone(&init_results);
159 let client_keygen_ids_for_cb = client_keygen_ids.clone();
160 let scheme_n = scheme_cfg.n;
161 let scheme_t = scheme_cfg.t;
162
163 let (mpc_results, ceremony_data) = stream_sse_keygen(response, move |trigger| async move {
164 let event: KeygenCompleteEvent = match trigger {
165 SseEventData::Json(v) => serde_json::from_value(v).map_err(Error::from)?,
166 SseEventData::Raw(s) => {
167 return Err(Error::Sse(format!(
168 "keygen_complete payload was not JSON: {s}"
169 )))
170 }
171 };
172 debug!(room_id = %event.room_id, "running MPC keygen");
173
174 let signer = EcdsaSigner::new(host_url);
175 let room = RoomUuid::new(event.room_id);
176
177 let server_ids: Vec<KeygenId> = event
185 .server_keygen_ids
186 .iter()
187 .map(|s| KeygenId::new(s.clone()))
188 .collect();
189 let signer_ref = &signer;
190 let room_ref = &room;
191 let keygen_futures = init_results_for_cb.iter().enumerate().map(|(i, init)| {
192 let other_external_ids: Vec<KeygenId> = client_keygen_ids_for_cb
193 .iter()
194 .enumerate()
195 .filter(|(j, _)| *j != i)
196 .map(|(_, id)| KeygenId::new(id.clone()))
197 .collect();
198 let all_others: Vec<KeygenId> = server_ids
199 .iter()
200 .cloned()
201 .chain(other_external_ids)
202 .collect();
203 async move {
204 signer_ref
205 .keygen(
206 room_ref,
207 scheme_n,
208 scheme_t,
209 &init.keygen_secret,
210 &all_others,
211 )
212 .await
213 }
214 });
215 let results: Vec<_> = futures::future::try_join_all(keygen_futures).await?;
216 Ok::<_, Error>(results)
217 })
218 .await?;
219
220 let first = mpc_results
222 .first()
223 .ok_or(Error::Mpc(dynamic_waas_sdk_mpc::MpcError::Unknown))?;
224 let signer = EcdsaSigner::new(client.base_mpc_relay_url().to_string());
225 let (raw_uncompressed, raw_compressed) =
226 signer.derive_pubkey(&first.secret_share, &opts.derivation_path)?;
227
228 let mut wallet_id = String::new();
231 if let Some(SseEventData::Json(v)) = &ceremony_data {
232 if let Some(id) = v.get("walletId").and_then(|x| x.as_str()) {
233 id.clone_into(&mut wallet_id);
234 }
235 }
236 let mut wp = WalletProperties::new(
237 opts.chain_name.clone(),
238 wallet_id.clone(),
239 String::new(), )
241 .with_threshold(opts.threshold_signature_scheme)
242 .with_derivation_path(opts.derivation_path.clone());
243
244 let key_shares: Vec<ServerKeyShare> = mpc_results
246 .into_iter()
247 .enumerate()
248 .map(|(i, kr)| {
249 ServerKeyShare::new(client_keygen_ids[i].clone(), kr.secret_share.into_string())
250 })
251 .collect();
252
253 if !wallet_id.is_empty() {
266 let signer = EcdsaSigner::new(client.base_mpc_relay_url().to_string());
267 let keygen_id = signer
268 .export_id(&dynamic_waas_sdk_mpc::SecretShare::from_string(
269 key_shares[0].secret_share.clone(),
270 ))?
271 .into_string();
272 if opts.back_up_to_dynamic {
273 let password = opts.password.as_deref().unwrap_or("");
275 let backup_info = crate::backup::run_backup_dynamic(
276 client,
277 &wallet_id,
278 &key_shares,
279 &keygen_id,
280 password,
281 )
282 .await?;
283 wp.external_server_key_shares_backup_info = Some(backup_info);
284 } else {
285 crate::backup::run_mark_external_no_backup(client, &wallet_id, &key_shares, &keygen_id)
286 .await?;
287 }
288 }
289
290 Ok(KeygenOutput {
291 wallet_properties: wp,
292 external_server_key_shares: key_shares,
293 raw_public_key_uncompressed: raw_uncompressed,
294 raw_public_key_compressed: raw_compressed,
295 })
296}