use ed25519_dalek::{Signature, Verifier, VerifyingKey};
pub fn verify_domain_signed(
did: &str,
domain_tag: &[u8],
payload: &[u8],
signature_hex: &str,
) -> Result<(), String> {
let pub_bytes = affinidi_crypto::did_key::did_key_to_ed25519_pub(did)
.map_err(|e| format!("{did} is not a parseable did:key: {e}"))?;
let vk = VerifyingKey::from_bytes(&pub_bytes)
.map_err(|e| format!("{did} decodes to an invalid Ed25519 pubkey: {e}"))?;
let raw = hex::decode(signature_hex).map_err(|e| format!("signature is not hex: {e}"))?;
let sig = Signature::from_slice(&raw)
.map_err(|e| format!("signature is not a 64-byte Ed25519 value: {e}"))?;
let mut signing_bytes = Vec::with_capacity(domain_tag.len() + payload.len());
signing_bytes.extend_from_slice(domain_tag);
signing_bytes.extend_from_slice(payload);
vk.verify(&signing_bytes, &sig)
.map_err(|e| format!("holder-binding signature failed: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::{Signer, SigningKey};
const TAG: &[u8] = b"vtc-test/v1\0";
fn signer() -> (SigningKey, String) {
let sk = SigningKey::from_bytes(&[0x11; 32]);
let did = affinidi_crypto::did_key::ed25519_pub_to_did_key(&sk.verifying_key().to_bytes());
(sk, did)
}
fn sign_over(sk: &SigningKey, tag: &[u8], payload: &[u8]) -> String {
let mut buf = tag.to_vec();
buf.extend_from_slice(payload);
hex::encode(sk.sign(&buf).to_bytes())
}
#[test]
fn round_trip_verifies() {
let (sk, did) = signer();
let payload = b"{\"a\":1}";
let sig = sign_over(&sk, TAG, payload);
assert!(verify_domain_signed(&did, TAG, payload, &sig).is_ok());
}
#[test]
fn empty_tag_matches_pre_prefixed_payload() {
let (sk, did) = signer();
let mut combined = TAG.to_vec();
combined.extend_from_slice(b"rotation-body");
let sig = sign_over(&sk, &[], &combined);
assert!(verify_domain_signed(&did, &[], &combined, &sig).is_ok());
}
#[test]
fn wrong_domain_tag_rejected() {
let (sk, did) = signer();
let payload = b"body";
let sig = sign_over(&sk, b"vtc-other/v1\0", payload);
assert!(verify_domain_signed(&did, TAG, payload, &sig).is_err());
}
#[test]
fn wrong_signer_rejected() {
let (_sk, did) = signer();
let other = SigningKey::from_bytes(&[0x22; 32]);
let payload = b"body";
let sig = sign_over(&other, TAG, payload);
assert!(verify_domain_signed(&did, TAG, payload, &sig).is_err());
}
#[test]
fn non_did_key_and_garbage_sig_rejected() {
let (_sk, did) = signer();
assert!(verify_domain_signed("did:web:example.com", TAG, b"x", "00").is_err());
assert!(verify_domain_signed(&did, TAG, b"x", "not-hex").is_err());
assert!(verify_domain_signed(&did, TAG, b"x", "0011").is_err());
}
}