use miden_protocol::Felt;
use miden_protocol::utils::bytes_to_packed_u32_elements;
pub const PUBKEY_FELTS: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Signature([u8; 65]);
impl Signature {
pub const fn new(bytes: [u8; 65]) -> Self {
Self(bytes)
}
pub const fn as_bytes(&self) -> &[u8; 65] {
&self.0
}
pub fn to_elements(&self) -> [Felt; 17] {
bytes_to_packed_u32_elements(&self.0)
.try_into()
.expect("65 bytes always pack to exactly 17 u32 felts")
}
}
#[cfg(test)]
mod tests {
use k256::ecdsa::signature::hazmat::PrehashVerifier;
use k256::ecdsa::{RecoveryId, Signature as K256Signature, VerifyingKey};
use miden_protocol::crypto::SequentialCommit;
use miden_protocol::utils::bytes_to_packed_u32_elements;
use sha3::{Digest, Keccak256};
use super::*;
use crate::vectors::load;
fn keccak_digest_felts(digest: &[u8; 32]) -> [Felt; 8] {
bytes_to_packed_u32_elements(digest)
.try_into()
.expect("32 bytes always pack to exactly 8 u32 felts")
}
#[test]
fn tv_att_1_felt_shapes() {
for v in &load().families.att {
let pk = v.public_key().to_elements();
assert_eq!(pk.len(), PUBKEY_FELTS, "{}: pubkey felt width", v.id);
assert_eq!(pk.as_slice(), v.packed_felts_values().as_slice(), "{}: pubkey felts", v.id);
let d = keccak_digest_felts(&v.digest());
assert_eq!(d.len(), 8, "{}: digest felt width", v.id);
assert_eq!(d.as_slice(), v.digest_felts_values().as_slice(), "{}: digest felts", v.id);
let s = Signature::new(v.sig()).to_elements();
assert_eq!(s.len(), 17, "{}: signature felt width", v.id);
assert_eq!(s.as_slice(), v.sig_felts_values().as_slice(), "{}: signature felts", v.id);
}
}
#[test]
fn tv_att_2_commitment() {
for v in &load().families.att {
let commitment = v.public_key().to_commitment();
assert_eq!(
commitment,
v.expected_commitment_word(),
"{}: the commitment must equal miden-crypto PublicKey::to_commitment",
v.id
);
}
}
#[test]
fn tv_att_3_raw_keccak_not_eip712() {
for v in &load().families.att {
let digest = v.digest();
assert_eq!(
keccak_digest_felts(&digest).as_slice(),
bytes_to_packed_u32_elements(&digest).as_slice(),
"{}: digest helper must pack the raw keccak digest verbatim (no EIP-712 framing)",
v.id
);
assert!(
v.payload().len() >= 240,
"{}: digest must be keccak over a full DepositIntent payload, got {} bytes",
v.id,
v.payload().len()
);
assert_eq!(v.sig().len(), 65, "{}: raw r||s||v signature is 65 bytes", v.id);
assert_eq!(
Signature::new(v.sig()).to_elements()[16],
Felt::from(u32::from(v.v_byte)),
"{}: v byte carried in felt 16 (unused on-chain)",
v.id
);
}
}
#[test]
fn attestation_vectors_are_self_consistent() {
for v in &load().families.att {
let digest = v.digest();
let actual: [u8; 32] = Keccak256::digest(v.payload()).into();
assert_eq!(actual, digest, "{}: digest must equal keccak256 of the full payload", v.id);
let pubkey = v.pubkey();
let key = VerifyingKey::from_sec1_bytes(&pubkey)
.unwrap_or_else(|e| panic!("{}: invalid public key: {e}", v.id));
let sig = K256Signature::from_slice(&v.sig()[..64])
.unwrap_or_else(|e| panic!("{}: invalid signature: {e}", v.id));
assert!(
key.verify_prehash(&digest, &sig).is_ok(),
"{}: signature must verify over the digest under the vector's public key",
v.id
);
let recovery_id = RecoveryId::from_byte(v.v_byte)
.unwrap_or_else(|| panic!("{}: invalid recovery id", v.id));
let recovered = VerifyingKey::recover_from_prehash(&digest, &sig, recovery_id)
.unwrap_or_else(|e| panic!("{}: public key recovery failed: {e}", v.id));
assert_eq!(
recovered.to_sec1_point(true).as_bytes(),
&pubkey,
"{}: recovery id must recover the vector's public key",
v.id
);
}
assert!(
load().families.att.iter().any(|v| v.payload().len() > 240),
"att family must retain a vector with non-empty hookData"
);
}
}