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_rpo::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::Falcon512Rpo(_)) => {
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 mut encoded = encode_ecdsa_signature_felts(&public_key, ecdsa_signature);
81 encoded.reverse();
82 encoded
83 }
84 (Self::Ecdsa, _) => {
85 return Err("expected ECDSA signature for ecdsa scheme".to_string());
86 }
87 };
88
89 Ok((key, values))
90 }
91}
92
93impl std::fmt::Display for SignatureScheme {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.write_str(self.as_str())
96 }
97}
98
99fn ensure_hex_prefix(hex: &str) -> String {
100 if hex.starts_with("0x") {
101 hex.to_string()
102 } else {
103 format!("0x{}", hex)
104 }
105}
106
107fn signature_advice_key(pubkey_commitment: Word, message: Word) -> Word {
108 let mut elements = Vec::with_capacity(8);
109 elements.extend_from_slice(pubkey_commitment.as_elements());
110 elements.extend_from_slice(message.as_elements());
111 Hasher::hash_elements(&elements)
112}
113
114fn parse_ecdsa_public_key_hex(
115 public_key_hex: &str,
116) -> Result<ecdsa_k256_keccak::PublicKey, String> {
117 let public_key_bytes = ::hex::decode(public_key_hex.trim_start_matches("0x"))
118 .map_err(|e| format!("invalid ECDSA public key hex: {}", e))?;
119 ecdsa_k256_keccak::PublicKey::read_from_bytes(&public_key_bytes)
120 .map_err(|e| format!("failed to deserialize ECDSA public key: {}", e))
121}
122
123fn bytes_to_packed_u32_felts(bytes: &[u8]) -> Vec<Felt> {
124 bytes
125 .chunks(4)
126 .map(|chunk| {
127 let mut packed = [0u8; 4];
128 packed[..chunk.len()].copy_from_slice(chunk);
129 Felt::from(u32::from_le_bytes(packed))
130 })
131 .collect()
132}
133
134fn encode_ecdsa_signature_felts(
135 public_key: &ecdsa_k256_keccak::PublicKey,
136 signature: &ecdsa_k256_keccak::Signature,
137) -> Vec<Felt> {
138 let mut out = Vec::new();
139 out.extend(bytes_to_packed_u32_felts(&public_key.to_bytes()));
140 out.extend(bytes_to_packed_u32_felts(&signature.to_bytes()));
141 out
142}
143
144#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
146#[serde(tag = "scheme", rename_all = "snake_case")]
147pub enum ProposalSignature {
148 Falcon {
149 signature: String,
151 },
152 Ecdsa {
153 signature: String,
155 #[serde(default, skip_serializing_if = "Option::is_none")]
157 public_key: Option<String>,
158 },
159}
160
161impl ProposalSignature {
162 pub fn from_scheme(
164 scheme: SignatureScheme,
165 signature: String,
166 public_key: Option<String>,
167 ) -> Self {
168 match scheme {
169 SignatureScheme::Falcon => ProposalSignature::Falcon { signature },
170 SignatureScheme::Ecdsa => ProposalSignature::Ecdsa {
171 signature,
172 public_key,
173 },
174 }
175 }
176
177 pub fn public_key(&self) -> Option<&str> {
179 match self {
180 ProposalSignature::Ecdsa { public_key, .. } => public_key.as_deref(),
181 _ => None,
182 }
183 }
184}
185
186#[derive(Serialize, Deserialize, Clone, Debug)]
189pub struct DeltaPayload {
190 pub tx_summary: serde_json::Value,
191 #[serde(default, skip_serializing_if = "Vec::is_empty")]
192 pub signatures: Vec<DeltaSignature>,
193}
194
195impl DeltaPayload {
196 pub fn new(tx_summary: serde_json::Value) -> Self {
197 Self {
198 tx_summary,
199 signatures: Vec::new(),
200 }
201 }
202
203 pub fn with_signature(mut self, signature: DeltaSignature) -> Self {
204 self.signatures.push(signature);
205 self
206 }
207
208 pub fn to_json(&self) -> serde_json::Value {
209 serde_json::to_value(self).expect("DeltaPayload should always serialize")
210 }
211}
212
213#[derive(Serialize, Deserialize, Clone, Debug)]
215pub struct DeltaSignature {
216 pub signer_id: String,
217 pub signature: ProposalSignature,
218}
219
220pub trait ToJson {
221 fn to_json(&self) -> serde_json::Value;
222}
223
224pub trait FromJson: Sized {
225 fn from_json(json: &serde_json::Value) -> Result<Self, String>;
226}
227
228impl ToJson for Account {
229 fn to_json(&self) -> serde_json::Value {
230 let bytes = self.to_bytes();
231 let encoded = base64::engine::general_purpose::STANDARD.encode(&bytes);
232 serde_json::json!({
233 "data": encoded,
234 "account_id": self.id().to_hex(),
235 })
236 }
237}
238
239impl FromJson for Account {
240 fn from_json(json: &serde_json::Value) -> Result<Self, String> {
241 let encoded = json
242 .get("data")
243 .and_then(|v| v.as_str())
244 .ok_or("Missing or invalid 'data' field")?;
245
246 let bytes = base64::engine::general_purpose::STANDARD
247 .decode(encoded)
248 .map_err(|e| format!("Base64 decode error: {e}"))?;
249
250 Account::read_from_bytes(&bytes).map_err(|e| format!("Deserialization error: {e}"))
251 }
252}
253
254impl ToJson for TransactionSummary {
255 fn to_json(&self) -> serde_json::Value {
256 let bytes = self.to_bytes();
257 let encoded = base64::engine::general_purpose::STANDARD.encode(&bytes);
258 serde_json::json!({
259 "data": encoded,
260 })
261 }
262}
263
264impl FromJson for TransactionSummary {
265 fn from_json(json: &serde_json::Value) -> Result<Self, String> {
266 let encoded = json
267 .get("data")
268 .and_then(|v| v.as_str())
269 .ok_or("Missing or invalid 'data' field in delta payload")?;
270
271 let bytes = base64::engine::general_purpose::STANDARD
272 .decode(encoded)
273 .map_err(|e| format!("Base64 decode error: {e}"))?;
274
275 TransactionSummary::read_from_bytes(&bytes)
276 .map_err(|e| format!("AccountDelta deserialization error: {e}"))
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use miden_protocol::{
284 account::auth::Signature as AccountSignature,
285 account::{AccountBuilder, auth::PublicKeyCommitment},
286 crypto::dsa::ecdsa_k256_keccak::SecretKey as EcdsaSecretKey,
287 crypto::dsa::falcon512_rpo::SecretKey,
288 };
289 use miden_standards::account::{auth::AuthFalcon512Rpo, wallets::BasicWallet};
290
291 #[test]
292 fn test_account_json_round_trip() {
293 let secret_key = SecretKey::new();
295 let public_key_commitment =
296 PublicKeyCommitment::from(secret_key.public_key().to_commitment());
297 let account = AccountBuilder::new([0xff; 32])
298 .with_auth_component(AuthFalcon512Rpo::new(public_key_commitment))
299 .with_component(BasicWallet)
300 .build()
301 .unwrap();
302
303 let json = account.to_json();
305
306 let deserialized_account =
308 Account::from_json(&json).expect("Failed to deserialize account");
309
310 assert_eq!(account.id(), deserialized_account.id());
312 assert_eq!(account.nonce(), deserialized_account.nonce());
313 assert_eq!(account.commitment(), deserialized_account.commitment());
314 assert_eq!(
315 account.storage().to_commitment(),
316 deserialized_account.storage().to_commitment()
317 );
318 assert_eq!(
319 account.code().commitment(),
320 deserialized_account.code().commitment()
321 );
322 }
323
324 #[test]
325 fn signature_scheme_from_accepts_known_values_case_insensitively() {
326 assert_eq!(
327 SignatureScheme::from("falcon").unwrap(),
328 SignatureScheme::Falcon
329 );
330 assert_eq!(
331 SignatureScheme::from("ECDSA").unwrap(),
332 SignatureScheme::Ecdsa
333 );
334 }
335
336 #[test]
337 fn signature_scheme_from_rejects_unknown_values() {
338 let error = SignatureScheme::from("unknown").unwrap_err();
339
340 assert!(error.contains("unsupported signature scheme"));
341 }
342
343 #[test]
344 fn signature_scheme_parse_signature_hex_accepts_falcon_signatures() {
345 let secret_key = SecretKey::new();
346 let message = Word::from([1u32, 2, 3, 4]);
347 let signature = secret_key.sign(message);
348 let signature_hex = format!("0x{}", ::hex::encode(signature.to_bytes()));
349
350 let parsed = SignatureScheme::Falcon
351 .parse_signature_hex(&signature_hex)
352 .unwrap();
353
354 assert!(matches!(parsed, AccountSignature::Falcon512Rpo(_)));
355 }
356
357 #[test]
358 fn signature_scheme_parse_signature_hex_accepts_ecdsa_signatures() {
359 let secret_key = EcdsaSecretKey::new();
360 let message = Word::from([1u32, 2, 3, 4]);
361 let signature = secret_key.sign(message);
362 let signature_hex = format!("0x{}", ::hex::encode(signature.to_bytes()));
363
364 let parsed = SignatureScheme::Ecdsa
365 .parse_signature_hex(&signature_hex)
366 .unwrap();
367
368 assert!(matches!(parsed, AccountSignature::EcdsaK256Keccak(_)));
369 }
370
371 #[test]
372 fn signature_scheme_build_signature_advice_entry_accepts_falcon_signatures() {
373 let secret_key = SecretKey::new();
374 let message = Word::from([1u32, 2, 3, 4]);
375 let commitment = Word::from([5u32, 6, 7, 8]);
376 let signature = AccountSignature::from(secret_key.sign(message));
377
378 let (key, values) = SignatureScheme::Falcon
379 .build_signature_advice_entry(commitment, message, &signature, None)
380 .unwrap();
381
382 let mut elements = Vec::with_capacity(8);
383 elements.extend_from_slice(commitment.as_elements());
384 elements.extend_from_slice(message.as_elements());
385
386 assert_eq!(key, Hasher::hash_elements(&elements));
387 assert!(!values.is_empty());
388 }
389
390 #[test]
391 fn signature_scheme_build_signature_advice_entry_accepts_ecdsa_signatures() {
392 let secret_key = EcdsaSecretKey::new();
393 let public_key = secret_key.public_key();
394 let public_key_hex = format!("0x{}", ::hex::encode(public_key.to_bytes()));
395 let message = Word::from([1u32, 2, 3, 4]);
396 let commitment = public_key.to_commitment();
397 let signature = AccountSignature::EcdsaK256Keccak(secret_key.sign(message));
398
399 let (key, values) = SignatureScheme::Ecdsa
400 .build_signature_advice_entry(commitment, message, &signature, Some(&public_key_hex))
401 .unwrap();
402
403 let mut elements = Vec::with_capacity(8);
404 elements.extend_from_slice(commitment.as_elements());
405 elements.extend_from_slice(message.as_elements());
406
407 assert_eq!(key, Hasher::hash_elements(&elements));
408 assert!(!values.is_empty());
409 }
410
411 #[test]
412 fn signature_scheme_build_signature_advice_entry_requires_ecdsa_public_key() {
413 let secret_key = EcdsaSecretKey::new();
414 let message = Word::from([1u32, 2, 3, 4]);
415 let commitment = secret_key.public_key().to_commitment();
416 let signature = AccountSignature::EcdsaK256Keccak(secret_key.sign(message));
417
418 let error = SignatureScheme::Ecdsa
419 .build_signature_advice_entry(commitment, message, &signature, None)
420 .unwrap_err();
421
422 assert!(error.contains("requires public key"));
423 }
424}