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