use miden_objects::account::auth::Signature;
use miden_objects::crypto::dsa::ecdsa_k256_keccak;
use miden_objects::utils::{Deserializable, Serializable};
use miden_objects::{Felt, Hasher, Word};
use crate::error::{MultisigError, Result};
fn bytes_to_packed_u32_felts(bytes: &[u8]) -> Vec<Felt> {
bytes
.chunks(4)
.map(|chunk| {
let mut packed = [0u8; 4];
packed[..chunk.len()].copy_from_slice(chunk);
Felt::from(u32::from_le_bytes(packed))
})
.collect()
}
fn encode_ecdsa_signature_felts(
pk: &ecdsa_k256_keccak::PublicKey,
sig: &ecdsa_k256_keccak::Signature,
) -> Vec<Felt> {
let mut out = Vec::new();
out.extend(bytes_to_packed_u32_felts(&pk.to_bytes()));
out.extend(bytes_to_packed_u32_felts(&sig.to_bytes()));
out
}
pub fn build_signature_advice_entry(
pubkey_commitment: Word,
message: Word,
signature: &Signature,
ecdsa_pubkey: Option<&ecdsa_k256_keccak::PublicKey>,
) -> (Word, Vec<Felt>) {
let mut elements = Vec::with_capacity(8);
elements.extend_from_slice(pubkey_commitment.as_elements());
elements.extend_from_slice(message.as_elements());
let key: Word = Hasher::hash_elements(&elements);
let values = match signature {
Signature::RpoFalcon512(_) => signature.to_prepared_signature(message),
Signature::EcdsaK256Keccak(ecdsa_sig) => {
let pk = ecdsa_pubkey
.expect("ECDSA public key must be provided for ECDSA signature advice preparation");
let encoded = encode_ecdsa_signature_felts(pk, ecdsa_sig);
let mut reversed = encoded;
reversed.reverse();
reversed
}
};
(key, values)
}
pub fn build_ecdsa_signature_advice_entry(
pubkey_commitment: Word,
message: Word,
signature: &Signature,
pubkey_hex: &str,
) -> Result<(Word, Vec<Felt>)> {
let hex_str = pubkey_hex.trim_start_matches("0x");
let pk_bytes = hex::decode(hex_str)
.map_err(|e| MultisigError::Signature(format!("invalid ECDSA public key hex: {}", e)))?;
let pk = ecdsa_k256_keccak::PublicKey::read_from_bytes(&pk_bytes).map_err(|e| {
MultisigError::Signature(format!("failed to deserialize ECDSA public key: {}", e))
})?;
Ok(build_signature_advice_entry(
pubkey_commitment,
message,
signature,
Some(&pk),
))
}
#[cfg(test)]
mod tests {
use super::*;
use miden_objects::account::auth::Signature as AccountSignature;
use miden_objects::crypto::dsa::rpo_falcon512::SecretKey;
#[test]
fn ecdsa_commitment_matches_packed_u32_hash() {
use miden_objects::crypto::dsa::ecdsa_k256_keccak::SecretKey as EcdsaSecretKey;
let sk = EcdsaSecretKey::new();
let pk = sk.public_key();
let commitment_via_trait = pk.to_commitment();
let pk_bytes = pk.to_bytes();
let packed_felts = bytes_to_packed_u32_felts(&pk_bytes);
let commitment_via_manual: Word = Hasher::hash_elements(&packed_felts);
assert_eq!(
commitment_via_trait, commitment_via_manual,
"PublicKey::to_commitment() must match manual packed-u32 hash"
);
}
#[test]
fn signature_advice_key_matches_hash_elements_concat() {
let pubkey_commitment =
Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
let message = Word::from([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]);
let secret_key = SecretKey::new();
let rpo_sig = secret_key.sign(message);
let signature = AccountSignature::from(rpo_sig);
let (key, _) = build_signature_advice_entry(pubkey_commitment, message, &signature, None);
let mut elements = Vec::with_capacity(8);
elements.extend_from_slice(pubkey_commitment.as_elements());
elements.extend_from_slice(message.as_elements());
let expected: Word = Hasher::hash_elements(&elements);
assert_eq!(key, expected);
}
#[test]
fn falcon_advice_entry_produces_non_empty_values() {
let pubkey_commitment =
Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
let message = Word::from([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]);
let secret_key = SecretKey::new();
let rpo_sig = secret_key.sign(message);
let signature = AccountSignature::from(rpo_sig);
let (_key, values) =
build_signature_advice_entry(pubkey_commitment, message, &signature, None);
assert!(!values.is_empty());
}
#[test]
fn ecdsa_advice_entry_produces_non_empty_values() {
use miden_objects::crypto::dsa::ecdsa_k256_keccak::SecretKey as EcdsaSecretKey;
let sk = EcdsaSecretKey::new();
let pk = sk.public_key();
let commitment = pk.to_commitment();
let message = Word::from([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]);
let sig = sk.sign(message);
let account_sig = AccountSignature::EcdsaK256Keccak(sig);
let (_key, values) =
build_signature_advice_entry(commitment, message, &account_sig, Some(&pk));
assert!(!values.is_empty());
}
#[test]
fn ecdsa_advice_entry_key_matches_expected() {
use miden_objects::crypto::dsa::ecdsa_k256_keccak::SecretKey as EcdsaSecretKey;
let sk = EcdsaSecretKey::new();
let pk = sk.public_key();
let commitment = pk.to_commitment();
let message = Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
let sig = sk.sign(message);
let account_sig = AccountSignature::EcdsaK256Keccak(sig);
let (key, _) = build_signature_advice_entry(commitment, message, &account_sig, Some(&pk));
let mut elements = Vec::with_capacity(8);
elements.extend_from_slice(commitment.as_elements());
elements.extend_from_slice(message.as_elements());
let expected: Word = Hasher::hash_elements(&elements);
assert_eq!(key, expected);
}
#[test]
fn build_ecdsa_signature_advice_entry_valid() {
use miden_objects::crypto::dsa::ecdsa_k256_keccak::SecretKey as EcdsaSecretKey;
let sk = EcdsaSecretKey::new();
let pk = sk.public_key();
let pk_hex = format!("0x{}", hex::encode(pk.to_bytes()));
let commitment = pk.to_commitment();
let message = Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
let sig = sk.sign(message);
let account_sig = AccountSignature::EcdsaK256Keccak(sig);
let result = build_ecdsa_signature_advice_entry(commitment, message, &account_sig, &pk_hex);
assert!(result.is_ok());
let (key, values) = result.unwrap();
assert!(!values.is_empty());
let (expected_key, _) =
build_signature_advice_entry(commitment, message, &account_sig, Some(&pk));
assert_eq!(key, expected_key);
}
#[test]
fn build_ecdsa_signature_advice_entry_invalid_hex() {
let commitment = Word::default();
let message = Word::default();
let secret_key = SecretKey::new();
let rpo_sig = secret_key.sign(message);
let signature = AccountSignature::from(rpo_sig);
let result =
build_ecdsa_signature_advice_entry(commitment, message, &signature, "0xinvalid!!!");
assert!(result.is_err());
}
#[test]
fn build_ecdsa_signature_advice_entry_wrong_key_bytes() {
let commitment = Word::default();
let message = Word::default();
let secret_key = SecretKey::new();
let rpo_sig = secret_key.sign(message);
let signature = AccountSignature::from(rpo_sig);
let bad_hex = format!("0x{}", "ab".repeat(33));
let result = build_ecdsa_signature_advice_entry(commitment, message, &signature, &bad_hex);
assert!(result.is_err());
}
#[test]
fn bytes_to_packed_u32_felts_basic() {
let bytes = [1u8, 0, 0, 0, 2, 0, 0, 0];
let felts = bytes_to_packed_u32_felts(&bytes);
assert_eq!(felts.len(), 2);
assert_eq!(felts[0], Felt::from(1u32));
assert_eq!(felts[1], Felt::from(2u32));
}
#[test]
fn bytes_to_packed_u32_felts_partial_chunk() {
let bytes = [1u8, 2, 3]; let felts = bytes_to_packed_u32_felts(&bytes);
assert_eq!(felts.len(), 1);
assert_eq!(felts[0], Felt::from(0x00030201u32));
}
#[test]
fn bytes_to_packed_u32_felts_empty() {
let felts = bytes_to_packed_u32_felts(&[]);
assert!(felts.is_empty());
}
}