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