1use base64::Engine;
2use miden_protocol::account::Account;
3use miden_protocol::account::auth::Signature as AccountSignature;
4use miden_protocol::crypto::dsa::ecdsa_k256_keccak;
5use miden_protocol::crypto::dsa::falcon512_poseidon2::Signature as FalconSignature;
6use miden_protocol::transaction::TransactionSummary;
7use miden_protocol::utils::serde::{Deserializable, Serializable};
8use miden_protocol::{Felt, Hasher, Word};
9use serde::{Deserialize, Serialize};
10
11pub mod auth;
12pub mod auth_request_message;
13pub mod auth_request_payload;
14pub mod felt;
15pub mod hex;
16pub mod lookup_auth_message;
17pub mod retry;
18
19use crate::hex::FromHex;
20
21#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, utoipa::ToSchema)]
23#[serde(rename_all = "snake_case")]
24pub enum SignatureScheme {
25 Falcon,
26 Ecdsa,
27}
28
29impl SignatureScheme {
30 pub fn from(ack_scheme: &str) -> Result<Self, String> {
31 match ack_scheme {
32 value if value.eq_ignore_ascii_case("falcon") => Ok(Self::Falcon),
33 value if value.eq_ignore_ascii_case("ecdsa") => Ok(Self::Ecdsa),
34 value => Err(format!("unsupported signature scheme: {}", value)),
35 }
36 }
37
38 pub const fn as_str(self) -> &'static str {
39 match self {
40 Self::Falcon => "falcon",
41 Self::Ecdsa => "ecdsa",
42 }
43 }
44
45 pub fn parse_signature_hex(self, signature_hex: &str) -> Result<AccountSignature, String> {
46 match self {
47 Self::Falcon => {
48 let signature = FalconSignature::from_hex(&ensure_hex_prefix(signature_hex))
49 .map_err(|e| format!("failed to parse Falcon signature: {}", e))?;
50 Ok(AccountSignature::from(signature))
51 }
52 Self::Ecdsa => {
53 let signature_bytes = ::hex::decode(signature_hex.trim_start_matches("0x"))
54 .map_err(|e| format!("invalid ECDSA signature hex: {}", e))?;
55 let signature = ecdsa_k256_keccak::Signature::read_from_bytes(&signature_bytes)
56 .map_err(|e| format!("failed to parse ECDSA signature: {}", e))?;
57 Ok(AccountSignature::EcdsaK256Keccak(signature))
58 }
59 }
60 }
61
62 pub fn build_signature_advice_entry(
63 self,
64 pubkey_commitment: Word,
65 message: Word,
66 signature: &AccountSignature,
67 public_key_hex: Option<&str>,
68 ) -> Result<(Word, Vec<Felt>), String> {
69 let key = signature_advice_key(pubkey_commitment, message);
70
71 let values = match (self, signature) {
72 (Self::Falcon, AccountSignature::Falcon512Poseidon2(_)) => {
73 signature.to_prepared_signature(message)
74 }
75 (Self::Falcon, _) => {
76 return Err("expected Falcon signature for falcon scheme".to_string());
77 }
78 (Self::Ecdsa, AccountSignature::EcdsaK256Keccak(ecdsa_signature)) => {
79 let public_key_hex = public_key_hex.ok_or_else(|| {
80 "ECDSA signature requires public key for advice preparation".to_string()
81 })?;
82 let public_key = parse_ecdsa_public_key_hex(public_key_hex)?;
83 let actual_commitment = public_key.to_commitment();
84 if actual_commitment != pubkey_commitment {
85 return Err(format!(
86 "ECDSA public key commitment mismatch: expected {}, got {}",
87 word_to_hex(pubkey_commitment),
88 word_to_hex(actual_commitment)
89 ));
90 }
91 AccountSignature::EcdsaK256Keccak(ecdsa_signature.clone())
92 .to_prepared_signature(message)
93 }
94 (Self::Ecdsa, _) => {
95 return Err("expected ECDSA signature for ecdsa scheme".to_string());
96 }
97 };
98
99 Ok((key, values))
100 }
101}
102
103impl std::fmt::Display for SignatureScheme {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 f.write_str(self.as_str())
106 }
107}
108
109fn ensure_hex_prefix(hex: &str) -> String {
110 if hex.starts_with("0x") {
111 hex.to_string()
112 } else {
113 format!("0x{}", hex)
114 }
115}
116
117fn word_to_hex(word: Word) -> String {
118 format!("0x{}", ::hex::encode(word.to_bytes()))
119}
120
121fn signature_advice_key(pubkey_commitment: Word, message: Word) -> Word {
122 let mut elements = Vec::with_capacity(8);
123 elements.extend_from_slice(pubkey_commitment.as_elements());
124 elements.extend_from_slice(message.as_elements());
125 Hasher::hash_elements(&elements)
126}
127
128fn parse_ecdsa_public_key_hex(
129 public_key_hex: &str,
130) -> Result<ecdsa_k256_keccak::PublicKey, String> {
131 let public_key_bytes = ::hex::decode(public_key_hex.trim_start_matches("0x"))
132 .map_err(|e| format!("invalid ECDSA public key hex: {}", e))?;
133 ecdsa_k256_keccak::PublicKey::read_from_bytes(&public_key_bytes)
134 .map_err(|e| format!("failed to deserialize ECDSA public key: {}", e))
135}
136
137#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, utoipa::ToSchema)]
139#[serde(tag = "scheme", rename_all = "snake_case")]
140pub enum ProposalSignature {
141 Falcon {
142 signature: String,
144 },
145 Ecdsa {
146 signature: String,
148 #[serde(default, skip_serializing_if = "Option::is_none")]
150 public_key: Option<String>,
151 },
152}
153
154impl ProposalSignature {
155 pub fn from_scheme(
157 scheme: SignatureScheme,
158 signature: String,
159 public_key: Option<String>,
160 ) -> Self {
161 match scheme {
162 SignatureScheme::Falcon => ProposalSignature::Falcon { signature },
163 SignatureScheme::Ecdsa => ProposalSignature::Ecdsa {
164 signature,
165 public_key,
166 },
167 }
168 }
169
170 pub fn public_key(&self) -> Option<&str> {
172 match self {
173 ProposalSignature::Ecdsa { public_key, .. } => public_key.as_deref(),
174 _ => None,
175 }
176 }
177}
178
179#[derive(Serialize, Deserialize, Clone, Debug)]
182pub struct DeltaPayload {
183 pub tx_summary: serde_json::Value,
184 #[serde(default, skip_serializing_if = "Vec::is_empty")]
185 pub signatures: Vec<DeltaSignature>,
186}
187
188impl DeltaPayload {
189 pub fn new(tx_summary: serde_json::Value) -> Self {
190 Self {
191 tx_summary,
192 signatures: Vec::new(),
193 }
194 }
195
196 pub fn with_signature(mut self, signature: DeltaSignature) -> Self {
197 self.signatures.push(signature);
198 self
199 }
200
201 pub fn to_json(&self) -> serde_json::Value {
202 serde_json::to_value(self).expect("DeltaPayload should always serialize")
203 }
204}
205
206#[derive(Serialize, Deserialize, Clone, Debug)]
208pub struct DeltaSignature {
209 pub signer_id: String,
210 pub signature: ProposalSignature,
211}
212
213pub trait ToJson {
214 fn to_json(&self) -> serde_json::Value;
215}
216
217pub trait FromJson: Sized {
218 fn from_json(json: &serde_json::Value) -> Result<Self, String>;
219}
220
221impl ToJson for Account {
222 fn to_json(&self) -> serde_json::Value {
223 let bytes = self.to_bytes();
224 let encoded = base64::engine::general_purpose::STANDARD.encode(&bytes);
225 serde_json::json!({
226 "data": encoded,
227 "account_id": self.id().to_hex(),
228 })
229 }
230}
231
232impl FromJson for Account {
233 fn from_json(json: &serde_json::Value) -> Result<Self, String> {
234 let encoded = json
235 .get("data")
236 .and_then(|v| v.as_str())
237 .ok_or("Missing or invalid 'data' field")?;
238
239 let bytes = base64::engine::general_purpose::STANDARD
240 .decode(encoded)
241 .map_err(|e| format!("Base64 decode error: {e}"))?;
242
243 Account::read_from_bytes(&bytes).map_err(|e| format!("Deserialization error: {e}"))
244 }
245}
246
247impl ToJson for TransactionSummary {
248 fn to_json(&self) -> serde_json::Value {
249 let bytes = self.to_bytes();
250 let encoded = base64::engine::general_purpose::STANDARD.encode(&bytes);
251 serde_json::json!({
252 "data": encoded,
253 })
254 }
255}
256
257impl FromJson for TransactionSummary {
258 fn from_json(json: &serde_json::Value) -> Result<Self, String> {
259 let encoded = json
260 .get("data")
261 .and_then(|v| v.as_str())
262 .ok_or("Missing or invalid 'data' field in delta payload")?;
263
264 let bytes = base64::engine::general_purpose::STANDARD
265 .decode(encoded)
266 .map_err(|e| format!("Base64 decode error: {e}"))?;
267
268 TransactionSummary::read_from_bytes(&bytes)
269 .map_err(|e| format!("AccountDelta deserialization error: {e}"))
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use miden_protocol::{
277 account::auth::{AuthScheme, Signature as AccountSignature},
278 account::{AccountBuilder, auth::PublicKeyCommitment},
279 crypto::dsa::ecdsa_k256_keccak::SigningKey as EcdsaSecretKey,
280 crypto::dsa::falcon512_poseidon2::SecretKey,
281 };
282 use miden_standards::account::{auth::AuthSingleSig, wallets::BasicWallet};
283
284 #[test]
285 fn test_account_json_round_trip() {
286 let secret_key = SecretKey::new();
288 let public_key_commitment =
289 PublicKeyCommitment::from(secret_key.public_key().to_commitment());
290 let account = AccountBuilder::new([0xff; 32])
291 .with_auth_component(AuthSingleSig::new(
292 public_key_commitment,
293 AuthScheme::Falcon512Poseidon2,
294 ))
295 .with_component(BasicWallet)
296 .build()
297 .unwrap();
298
299 let json = account.to_json();
301
302 let deserialized_account =
304 Account::from_json(&json).expect("Failed to deserialize account");
305
306 assert_eq!(account.id(), deserialized_account.id());
308 assert_eq!(account.nonce(), deserialized_account.nonce());
309 assert_eq!(
310 account.to_commitment(),
311 deserialized_account.to_commitment()
312 );
313 assert_eq!(
314 account.storage().to_commitment(),
315 deserialized_account.storage().to_commitment()
316 );
317 assert_eq!(
318 account.code().commitment(),
319 deserialized_account.code().commitment()
320 );
321 }
322
323 #[test]
324 fn signature_scheme_from_accepts_known_values_case_insensitively() {
325 assert_eq!(
326 SignatureScheme::from("falcon").unwrap(),
327 SignatureScheme::Falcon
328 );
329 assert_eq!(
330 SignatureScheme::from("ECDSA").unwrap(),
331 SignatureScheme::Ecdsa
332 );
333 }
334
335 #[test]
336 fn signature_scheme_from_rejects_unknown_values() {
337 let error = SignatureScheme::from("unknown").unwrap_err();
338
339 assert!(error.contains("unsupported signature scheme"));
340 }
341
342 #[test]
343 fn signature_scheme_parse_signature_hex_accepts_falcon_signatures() {
344 let secret_key = SecretKey::new();
345 let message = Word::from([1u32, 2, 3, 4]);
346 let signature = secret_key.sign(message);
347 let signature_hex = format!("0x{}", ::hex::encode(signature.to_bytes()));
348
349 let parsed = SignatureScheme::Falcon
350 .parse_signature_hex(&signature_hex)
351 .unwrap();
352
353 assert!(matches!(parsed, AccountSignature::Falcon512Poseidon2(_)));
354 }
355
356 #[test]
357 fn signature_scheme_parse_signature_hex_accepts_ecdsa_signatures() {
358 let secret_key = EcdsaSecretKey::new();
359 let message = Word::from([1u32, 2, 3, 4]);
360 let signature = secret_key.sign(message);
361 let signature_hex = format!("0x{}", ::hex::encode(signature.to_bytes()));
362
363 let parsed = SignatureScheme::Ecdsa
364 .parse_signature_hex(&signature_hex)
365 .unwrap();
366
367 assert!(matches!(parsed, AccountSignature::EcdsaK256Keccak(_)));
368 }
369
370 #[test]
371 fn signature_scheme_build_signature_advice_entry_accepts_falcon_signatures() {
372 let secret_key = SecretKey::new();
373 let message = Word::from([1u32, 2, 3, 4]);
374 let commitment = Word::from([5u32, 6, 7, 8]);
375 let signature = AccountSignature::from(secret_key.sign(message));
376
377 let (key, values) = SignatureScheme::Falcon
378 .build_signature_advice_entry(commitment, message, &signature, None)
379 .unwrap();
380
381 let mut elements = Vec::with_capacity(8);
382 elements.extend_from_slice(commitment.as_elements());
383 elements.extend_from_slice(message.as_elements());
384
385 assert_eq!(key, Hasher::hash_elements(&elements));
386 assert!(!values.is_empty());
387 }
388
389 #[test]
390 fn signature_scheme_build_signature_advice_entry_accepts_ecdsa_signatures() {
391 let secret_key = EcdsaSecretKey::new();
392 let public_key = secret_key.public_key();
393 let public_key_hex = format!("0x{}", ::hex::encode(public_key.to_bytes()));
394 let message = Word::from([1u32, 2, 3, 4]);
395 let commitment = public_key.to_commitment();
396 let signature = AccountSignature::EcdsaK256Keccak(secret_key.sign(message));
397
398 let (key, values) = SignatureScheme::Ecdsa
399 .build_signature_advice_entry(commitment, message, &signature, Some(&public_key_hex))
400 .unwrap();
401
402 let mut elements = Vec::with_capacity(8);
403 elements.extend_from_slice(commitment.as_elements());
404 elements.extend_from_slice(message.as_elements());
405
406 assert_eq!(key, Hasher::hash_elements(&elements));
407 assert!(!values.is_empty());
408 }
409
410 #[test]
411 fn signature_scheme_build_signature_advice_entry_requires_ecdsa_public_key() {
412 let secret_key = EcdsaSecretKey::new();
413 let message = Word::from([1u32, 2, 3, 4]);
414 let commitment = secret_key.public_key().to_commitment();
415 let signature = AccountSignature::EcdsaK256Keccak(secret_key.sign(message));
416
417 let error = SignatureScheme::Ecdsa
418 .build_signature_advice_entry(commitment, message, &signature, None)
419 .unwrap_err();
420
421 assert!(error.contains("requires public key"));
422 }
423
424 #[test]
425 fn signature_scheme_build_signature_advice_entry_rejects_mismatched_ecdsa_public_key() {
426 let secret_key = EcdsaSecretKey::new();
427 let other_secret_key = EcdsaSecretKey::new();
428 let other_public_key = other_secret_key.public_key();
429 let other_public_key_hex = format!("0x{}", ::hex::encode(other_public_key.to_bytes()));
430 let message = Word::from([1u32, 2, 3, 4]);
431 let commitment = secret_key.public_key().to_commitment();
432 let signature = AccountSignature::EcdsaK256Keccak(secret_key.sign(message));
433
434 let error = SignatureScheme::Ecdsa
435 .build_signature_advice_entry(
436 commitment,
437 message,
438 &signature,
439 Some(&other_public_key_hex),
440 )
441 .unwrap_err();
442
443 assert!(error.contains("ECDSA public key commitment mismatch"));
444 }
445}