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 mut results = Vec::with_capacity(init_results_for_cb.len());
180 for (i, init) in init_results_for_cb.iter().enumerate() {
181 let other_external_ids: Vec<KeygenId> = client_keygen_ids_for_cb
182 .iter()
183 .enumerate()
184 .filter(|(j, _)| *j != i)
185 .map(|(_, id)| KeygenId::new(id.clone()))
186 .collect();
187 let server_ids: Vec<KeygenId> = event
188 .server_keygen_ids
189 .iter()
190 .map(|s| KeygenId::new(s.clone()))
191 .collect();
192 let all_others: Vec<KeygenId> =
193 server_ids.into_iter().chain(other_external_ids).collect();
194
195 let result = signer
196 .keygen(&room, scheme_n, scheme_t, &init.keygen_secret, &all_others)
197 .await?;
198 results.push(result);
199 }
200 Ok::<_, Error>(results)
201 })
202 .await?;
203
204 let first = mpc_results
206 .first()
207 .ok_or(Error::Mpc(dynamic_waas_sdk_mpc::MpcError::Unknown))?;
208 let signer = EcdsaSigner::new(client.base_mpc_relay_url().to_string());
209 let (raw_uncompressed, raw_compressed) =
210 signer.derive_pubkey(&first.secret_share, &opts.derivation_path)?;
211
212 let mut wallet_id = String::new();
215 if let Some(SseEventData::Json(v)) = &ceremony_data {
216 if let Some(id) = v.get("walletId").and_then(|x| x.as_str()) {
217 id.clone_into(&mut wallet_id);
218 }
219 }
220 let mut wp = WalletProperties::new(
221 opts.chain_name.clone(),
222 wallet_id.clone(),
223 String::new(), )
225 .with_threshold(opts.threshold_signature_scheme)
226 .with_derivation_path(opts.derivation_path.clone());
227
228 let key_shares: Vec<ServerKeyShare> = mpc_results
230 .into_iter()
231 .enumerate()
232 .map(|(i, kr)| {
233 ServerKeyShare::new(client_keygen_ids[i].clone(), kr.secret_share.into_string())
234 })
235 .collect();
236
237 if !wallet_id.is_empty() {
250 let signer = EcdsaSigner::new(client.base_mpc_relay_url().to_string());
251 let keygen_id = signer
252 .export_id(&dynamic_waas_sdk_mpc::SecretShare::from_string(
253 key_shares[0].secret_share.clone(),
254 ))?
255 .into_string();
256 if opts.back_up_to_dynamic {
257 let password = opts.password.as_deref().unwrap_or("");
259 let backup_info = crate::backup::run_backup_dynamic(
260 client,
261 &wallet_id,
262 &key_shares,
263 &keygen_id,
264 password,
265 )
266 .await?;
267 wp.external_server_key_shares_backup_info = Some(backup_info);
268 } else {
269 crate::backup::run_mark_external_no_backup(client, &wallet_id, &key_shares, &keygen_id)
270 .await?;
271 }
272 }
273
274 Ok(KeygenOutput {
275 wallet_properties: wp,
276 external_server_key_shares: key_shares,
277 raw_public_key_uncompressed: raw_uncompressed,
278 raw_public_key_compressed: raw_compressed,
279 })
280}