use blake3::Hasher;
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use crate::crypto::{DOMAIN_CERT_FP, DOMAIN_CERT_SIG};
use crate::identity::DyoloIdentity;
use crate::intent::IntentHash;
use crate::registry::fresh_nonce;
use crate::SubScopeProof;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DelegationCert {
pub delegator_pk: VerifyingKey,
pub delegate_pk: VerifyingKey,
pub scope_root: IntentHash,
pub scope_proof: SubScopeProof,
pub nonce: [u8; 16],
pub issued_at: u64,
pub expiration_unix: u64,
pub max_depth: u8,
pub signature: Signature,
}
impl DelegationCert {
#[inline(always)]
pub(crate) fn signable_bytes(
delegator_pk: &VerifyingKey,
delegate_pk: &VerifyingKey,
scope_root: &IntentHash,
scope_proof: &SubScopeProof,
nonce: &[u8; 16],
issued_at: u64,
expiration_unix: u64,
max_depth: u8,
) -> Vec<u8> {
let domain_bytes = DOMAIN_CERT_SIG.as_bytes();
let exact_len = domain_bytes.len() + 32 + 32 + 32 + 32 + 16 + 8 + 8 + 1;
let mut m = Vec::with_capacity(exact_len);
m.extend_from_slice(domain_bytes);
m.extend_from_slice(delegator_pk.as_bytes());
m.extend_from_slice(delegate_pk.as_bytes());
m.extend_from_slice(scope_root);
m.extend_from_slice(&scope_proof.commitment());
m.extend_from_slice(nonce);
m.extend_from_slice(&issued_at.to_be_bytes());
m.extend_from_slice(&expiration_unix.to_be_bytes());
m.push(max_depth);
debug_assert_eq!(
m.len(),
exact_len,
"signable_bytes length mismatch: expected {exact_len}, got {}",
m.len()
);
m
}
pub(crate) fn issue(
delegator: &DyoloIdentity,
delegate_pk: VerifyingKey,
scope_root: IntentHash,
scope_proof: SubScopeProof,
nonce: [u8; 16],
issued_at: u64,
expiration_unix: u64,
max_depth: u8,
) -> Self {
let delegator_pk = delegator.verifying_key();
let msg = Self::signable_bytes(
&delegator_pk,
&delegate_pk,
&scope_root,
&scope_proof,
&nonce,
issued_at,
expiration_unix,
max_depth,
);
Self {
delegator_pk,
delegate_pk,
scope_root,
scope_proof,
nonce,
issued_at,
expiration_unix,
max_depth,
signature: delegator.sign(&msg),
}
}
pub(crate) fn verify_signature(&self) -> bool {
let msg = Self::signable_bytes(
&self.delegator_pk,
&self.delegate_pk,
&self.scope_root,
&self.scope_proof,
&self.nonce,
self.issued_at,
self.expiration_unix,
self.max_depth,
);
self.delegator_pk.verify(&msg, &self.signature).is_ok()
}
#[must_use]
pub fn fingerprint(&self) -> [u8; 32] {
let mut h = Hasher::new_derive_key(DOMAIN_CERT_FP);
h.update(&self.signature.to_bytes());
h.finalize().into()
}
}
pub struct CertBuilder {
delegate_pk: VerifyingKey,
scope_root: IntentHash,
scope_proof: SubScopeProof,
nonce: [u8; 16],
issued_at: u64,
expiration_unix: u64,
max_depth: u8,
}
impl CertBuilder {
pub fn new(
delegate_pk: VerifyingKey,
scope_root: IntentHash,
issued_at: u64,
expiration_unix: u64,
) -> Self {
Self {
delegate_pk,
scope_root,
scope_proof: SubScopeProof::full_passthrough(),
nonce: fresh_nonce(),
issued_at,
expiration_unix,
max_depth: 16,
}
}
pub fn scope_proof(mut self, proof: SubScopeProof) -> Self {
self.scope_proof = proof;
self
}
pub fn nonce(mut self, nonce: [u8; 16]) -> Self {
self.nonce = nonce;
self
}
pub fn max_depth(mut self, depth: u8) -> Self {
self.max_depth = depth;
self
}
pub fn sign(self, delegator: &DyoloIdentity) -> DelegationCert {
DelegationCert::issue(
delegator,
self.delegate_pk,
self.scope_root,
self.scope_proof,
self.nonce,
self.issued_at,
self.expiration_unix,
self.max_depth,
)
}
}