dynamic_waas_sdk/
keygen_ed25519.rs1use std::sync::Arc;
13
14use dynamic_waas_sdk_core::{
15 api::{CreateWalletKeygenReq, KeygenCompleteEvent},
16 sse::{stream_sse_keygen, SseEventData},
17 Error, Result, ServerKeyShare, ThresholdSignatureScheme, WalletProperties,
18};
19use dynamic_waas_sdk_mpc::{Ed25519Signer, KeygenId, RoomUuid, SecretShare};
20use tracing::{debug, instrument};
21
22use crate::client::DynamicWalletClient;
23use crate::mpc_config::{threshold_wire, MpcSchemeConfig};
24
25#[derive(Debug)]
26pub struct KeygenOutputEd25519 {
27 pub wallet_properties: WalletProperties,
28 pub external_server_key_shares: Vec<ServerKeyShare>,
29 pub raw_public_key: [u8; 32],
30}
31
32#[derive(Debug, Clone)]
33#[non_exhaustive]
34pub struct KeygenOptsEd25519 {
35 pub chain_name: String,
36 pub threshold_signature_scheme: ThresholdSignatureScheme,
37 pub derivation_path: Vec<u32>,
38 pub back_up_to_dynamic: bool,
40 pub password: Option<String>,
42}
43
44impl KeygenOptsEd25519 {
45 pub fn new(
46 chain_name: impl Into<String>,
47 threshold_signature_scheme: ThresholdSignatureScheme,
48 derivation_path: Vec<u32>,
49 ) -> Self {
50 Self {
51 chain_name: chain_name.into(),
52 threshold_signature_scheme,
53 derivation_path,
54 back_up_to_dynamic: true,
55 password: None,
56 }
57 }
58
59 #[must_use]
60 pub fn with_back_up_to_dynamic(mut self, value: bool) -> Self {
61 self.back_up_to_dynamic = value;
62 self
63 }
64
65 #[must_use]
66 pub fn with_password(mut self, password: impl Into<String>) -> Self {
67 self.password = Some(password.into());
68 self
69 }
70}
71
72#[instrument(skip(client), fields(chain = %opts.chain_name))]
73#[allow(clippy::too_many_lines)]
74pub async fn run_keygen_ed25519(
75 client: &DynamicWalletClient,
76 opts: KeygenOptsEd25519,
77) -> Result<KeygenOutputEd25519> {
78 if !client.is_authenticated() {
79 return Err(Error::Authentication(crate::AUTH_REQUIRED_MSG.into()));
80 }
81
82 crate::keygen::validate_password_for_backup(opts.password.as_deref(), opts.back_up_to_dynamic)?;
83
84 let scheme_cfg = MpcSchemeConfig::from(opts.threshold_signature_scheme);
85
86 let signer = Ed25519Signer::new(client.base_mpc_relay_url().to_string());
88 let mut init_results = Vec::with_capacity(scheme_cfg.client_threshold as usize);
89 for _ in 0..scheme_cfg.client_threshold {
90 init_results.push(signer.init_keygen()?);
91 }
92 let client_keygen_ids: Vec<String> = init_results
93 .iter()
94 .map(|r| r.keygen_id.as_str().to_owned())
95 .collect();
96
97 let body = CreateWalletKeygenReq {
99 chain: opts.chain_name.clone(),
100 client_keygen_ids: client_keygen_ids.clone(),
101 threshold_signature_scheme: threshold_wire(opts.threshold_signature_scheme).to_string(),
102 derivation_path: Some(opts.derivation_path.clone()),
103 address_type: None,
104 };
105 let response = client.api().create_wallet_keygen(&body).await?;
106
107 let host_url = client.base_mpc_relay_url().to_string();
109 let init_results = Arc::new(init_results);
110 let init_results_for_cb = Arc::clone(&init_results);
111 let client_keygen_ids_for_cb = client_keygen_ids.clone();
112 let scheme_n = scheme_cfg.n;
113 let scheme_t = scheme_cfg.t;
114
115 let (mpc_results, ceremony_data) = stream_sse_keygen(response, move |trigger| async move {
116 let event: KeygenCompleteEvent = match trigger {
117 SseEventData::Json(v) => serde_json::from_value(v).map_err(Error::from)?,
118 SseEventData::Raw(s) => {
119 return Err(Error::Sse(format!(
120 "keygen_complete payload was not JSON: {s}"
121 )))
122 }
123 };
124 debug!(room_id = %event.room_id, "running MPC receive_key");
125
126 let signer = Ed25519Signer::new(host_url);
127 let room = RoomUuid::new(event.room_id);
128
129 let mut results = Vec::with_capacity(init_results_for_cb.len());
130 for (i, init) in init_results_for_cb.iter().enumerate() {
131 let other_external_ids: Vec<KeygenId> = client_keygen_ids_for_cb
132 .iter()
133 .enumerate()
134 .filter(|(j, _)| *j != i)
135 .map(|(_, id)| KeygenId::new(id.clone()))
136 .collect();
137 let server_ids: Vec<KeygenId> = event
138 .server_keygen_ids
139 .iter()
140 .map(|s| KeygenId::new(s.clone()))
141 .collect();
142 let all_others: Vec<KeygenId> =
143 server_ids.into_iter().chain(other_external_ids).collect();
144
145 let result = signer
146 .receive_key(&room, scheme_n, scheme_t, &init.keygen_secret, &all_others)
147 .await?;
148 results.push(result);
149 }
150 Ok::<_, Error>(results)
151 })
152 .await?;
153
154 let first = mpc_results
156 .first()
157 .ok_or(Error::Mpc(dynamic_waas_sdk_mpc::MpcError::Unknown))?;
158 let signer = Ed25519Signer::new(client.base_mpc_relay_url().to_string());
159 let raw_pubkey = signer.derive_pubkey(&first.secret_share)?;
160
161 let mut wallet_id = String::new();
162 if let Some(SseEventData::Json(v)) = &ceremony_data {
163 if let Some(id) = v.get("walletId").and_then(|x| x.as_str()) {
164 id.clone_into(&mut wallet_id);
165 }
166 }
167 let mut wp = WalletProperties::new(
168 opts.chain_name.clone(),
169 wallet_id.clone(),
170 String::new(), )
172 .with_threshold(opts.threshold_signature_scheme)
173 .with_derivation_path(opts.derivation_path.clone());
174
175 let key_shares: Vec<ServerKeyShare> = mpc_results
176 .into_iter()
177 .enumerate()
178 .map(|(i, kr)| {
179 ServerKeyShare::new(client_keygen_ids[i].clone(), kr.secret_share.into_string())
180 })
181 .collect();
182
183 if !wallet_id.is_empty() {
186 let signer = Ed25519Signer::new(client.base_mpc_relay_url().to_string());
187 let keygen_id = signer
188 .export_id(&SecretShare::from_string(
189 key_shares[0].secret_share.clone(),
190 ))?
191 .into_string();
192 if opts.back_up_to_dynamic {
193 let password = opts.password.as_deref().unwrap_or("");
194 let backup_info = crate::backup::run_backup_dynamic(
195 client,
196 &wallet_id,
197 &key_shares,
198 &keygen_id,
199 password,
200 )
201 .await?;
202 wp.external_server_key_shares_backup_info = Some(backup_info);
203 } else {
204 crate::backup::run_mark_external_no_backup(client, &wallet_id, &key_shares, &keygen_id)
205 .await?;
206 }
207 }
208
209 Ok(KeygenOutputEd25519 {
210 wallet_properties: wp,
211 external_server_key_shares: key_shares,
212 raw_public_key: raw_pubkey,
213 })
214}