1use dynamic_waas_sdk_core::{
21 api::{KeygenCompleteEvent, SignMessageReq},
22 sse::{stream_sse_with_callback, SseEventData},
23 Error, Result, ServerKeyShare,
24};
25use dynamic_waas_sdk_mpc::{EcdsaSignature, EcdsaSigner, MessageHash, RoomUuid, SecretShare};
26use tracing::{debug, instrument};
27
28use crate::client::DynamicWalletClient;
29
30#[derive(Debug, Clone)]
32#[non_exhaustive]
33pub struct SignOpts {
34 pub wallet_id: String,
36 pub msg_hash: [u8; 32],
39 pub server_message: String,
43 pub server_is_formatted: bool,
46 pub secret_share: ServerKeyShare,
48 pub derivation_path: Vec<u32>,
51}
52
53impl SignOpts {
54 pub fn new(
55 wallet_id: impl Into<String>,
56 msg_hash: [u8; 32],
57 server_message: impl Into<String>,
58 server_is_formatted: bool,
59 secret_share: ServerKeyShare,
60 derivation_path: Vec<u32>,
61 ) -> Self {
62 Self {
63 wallet_id: wallet_id.into(),
64 msg_hash,
65 server_message: server_message.into(),
66 server_is_formatted,
67 secret_share,
68 derivation_path,
69 }
70 }
71}
72
73#[instrument(skip(client, opts), fields(wallet_id = %opts.wallet_id))]
74pub async fn run_sign_ecdsa(
75 client: &DynamicWalletClient,
76 opts: SignOpts,
77) -> Result<EcdsaSignature> {
78 if !client.is_authenticated() {
79 return Err(Error::Authentication(crate::AUTH_REQUIRED_MSG.into()));
80 }
81
82 let body = SignMessageReq {
83 message: opts.server_message,
84 is_formatted: opts.server_is_formatted,
85 server_is_formatted: None,
86 context: None,
87 };
88 let response = client
89 .api()
90 .sign_message_with_callback(&opts.wallet_id, &body)
91 .await?;
92
93 let host_url = client.base_mpc_relay_url().to_string();
94 let msg_hash = opts.msg_hash;
95 let derivation_path = opts.derivation_path.clone();
96 let secret_share = SecretShare::from_string(opts.secret_share.secret_share);
97
98 let (signature, _ceremony_data) =
99 stream_sse_with_callback(response, "room_created", move |trigger| async move {
100 let event: KeygenCompleteEvent = match trigger {
101 SseEventData::Json(v) => serde_json::from_value(v).map_err(Error::from)?,
102 SseEventData::Raw(s) => {
103 return Err(Error::Sse(format!(
104 "room_created payload was not JSON: {s}"
105 )))
106 }
107 };
108 debug!(room_id = %event.room_id, "running MPC sign");
109
110 let signer = EcdsaSigner::new(host_url);
111 let room = RoomUuid::new(event.room_id);
112 let hash = MessageHash(msg_hash);
113 let sig = signer
114 .sign(&room, &secret_share, &hash, &derivation_path)
115 .await?;
116 Ok::<_, Error>(sig)
117 })
118 .await?;
119
120 Ok(signature)
121}