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