amadeus_node/consensus/doms/
attestation.rs

1use crate::Context;
2use crate::node::protocol;
3use crate::node::protocol::{Handle, Typename};
4use crate::utils::bls12_381 as bls;
5use crate::utils::bls12_381::Error as BlsError;
6use crate::utils::{Hash, PublicKey, Signature};
7use amadeus_utils::constants::DST_ATT;
8use amadeus_utils::vecpak::{Term, VecpakExt, decode, encode};
9use std::fmt::Debug;
10use std::net::Ipv4Addr;
11use tracing::instrument;
12
13#[derive(Debug, thiserror::Error)]
14pub enum Error {
15    #[error("wrong type: {0}")]
16    WrongType(&'static str),
17    #[error("missing field: {0}")]
18    Missing(&'static str),
19    #[error("attestation is not vecpak")]
20    AttestationNotVecpak,
21    #[error("invalid length: {0}")]
22    InvalidLength(&'static str),
23    #[error(transparent)]
24    Bls(#[from] BlsError),
25}
26
27#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
28pub struct EventAttestation {
29    pub attestations: Vec<Attestation>,
30}
31
32#[derive(Clone, serde::Serialize, serde::Deserialize)]
33pub struct Attestation {
34    pub entry_hash: Hash,
35    pub mutations_hash: Hash,
36    pub signer: PublicKey,
37    pub signature: Signature,
38}
39
40impl Debug for Attestation {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        f.debug_struct("Attestation")
43            .field("entry_hash", &bs58::encode(self.entry_hash).into_string())
44            .field("mutations_hash", &bs58::encode(self.mutations_hash).into_string())
45            .field("signer", &bs58::encode(self.signer).into_string())
46            .finish()
47    }
48}
49
50impl Typename for EventAttestation {
51    fn typename(&self) -> &'static str {
52        Self::TYPENAME
53    }
54}
55
56#[async_trait::async_trait]
57impl Handle for EventAttestation {
58    #[instrument(skip(self, _ctx), name = "EventAttestation::handle", err)]
59    async fn handle(&self, _ctx: &Context, _src: Ipv4Addr) -> Result<Vec<protocol::Instruction>, protocol::Error> {
60        Ok(vec![protocol::Instruction::Noop { why: "event_attestation handling not implemented".to_string() }])
61    }
62}
63
64impl EventAttestation {
65    pub const TYPENAME: &'static str = "event_attestation";
66
67    pub fn new(attestations: Vec<Attestation>) -> Self {
68        Self { attestations }
69    }
70}
71
72impl Attestation {
73    /// Parse from vecpak PropListMap (primary format)
74    #[instrument(skip(map), name = "Attestation::from_vecpak_map", err)]
75    pub fn from_vecpak_map(map: &amadeus_utils::vecpak::PropListMap) -> Result<Self, Error> {
76        let entry_hash_v = map.get_binary::<Vec<u8>>(b"entry_hash").ok_or(Error::Missing("entry_hash"))?;
77        let mutations_hash_v = map.get_binary::<Vec<u8>>(b"mutations_hash").ok_or(Error::Missing("mutations_hash"))?;
78        let signer_v = map.get_binary::<Vec<u8>>(b"signer").ok_or(Error::Missing("signer"))?;
79        let signature_v = map.get_binary::<Vec<u8>>(b"signature").ok_or(Error::Missing("signature"))?;
80
81        Ok(Attestation {
82            entry_hash: entry_hash_v.try_into().map_err(|_| Error::InvalidLength("entry_hash"))?,
83            mutations_hash: mutations_hash_v.try_into().map_err(|_| Error::InvalidLength("mutations_hash"))?,
84            signer: signer_v.try_into().map_err(|_| Error::InvalidLength("signer"))?,
85            signature: signature_v.try_into().map_err(|_| Error::InvalidLength("signature"))?,
86        })
87    }
88
89    /// Validate sizes and signature with DST_ATT
90    #[instrument(skip(self), name = "Attestation::validate", err)]
91    pub fn validate(&self) -> Result<(), Error> {
92        let mut to_sign = [0u8; 64];
93        to_sign[..32].copy_from_slice(self.entry_hash.as_ref());
94        to_sign[32..].copy_from_slice(self.mutations_hash.as_ref());
95        bls::verify(&self.signer, &self.signature, &to_sign, DST_ATT)?;
96        Ok(())
97    }
98
99    /// Verify this attestation against an allowed set of trainers (public keys)
100    /// Returns Ok(()) only if signer is present in `trainers` and signature is valid
101    pub fn validate_vs_trainers<TPk>(&self, trainers: &[TPk]) -> Result<(), Error>
102    where
103        TPk: AsRef<[u8]>,
104    {
105        let is_allowed = trainers.iter().any(|pk| pk.as_ref() == self.signer.as_ref() as &[u8]);
106        if !is_allowed {
107            return Err(Error::WrongType("signer_not_trainer"));
108        }
109        self.validate()
110    }
111
112    /// Create an attestation from provided public/secret material
113    /// NOTE: we intentionally do not read global env here, caller supplies keys
114    pub fn sign_with(
115        pk_g1_48: &[u8],
116        trainer_sk: &[u8],
117        entry_hash: &Hash,
118        mutations_hash: &Hash,
119    ) -> Result<Self, Error> {
120        let mut msg = [0u8; 64];
121        msg[..32].copy_from_slice(entry_hash.as_ref());
122        msg[32..].copy_from_slice(mutations_hash.as_ref());
123        let signature = bls::sign(trainer_sk, &msg, DST_ATT)?;
124        let signer: PublicKey = pk_g1_48.try_into().map_err(|_| Error::InvalidLength("signer"))?;
125        let signature: Signature = signature.as_slice().try_into().map_err(|_| Error::InvalidLength("signature"))?;
126        Ok(Self { entry_hash: *entry_hash, mutations_hash: *mutations_hash, signer, signature })
127    }
128
129    pub fn to_vecpak_bin(&self) -> Vec<u8> {
130        encode(self.to_vecpak_term())
131    }
132
133    pub fn from_vecpak_bin(data: &[u8]) -> Option<Self> {
134        let term = decode(data).ok()?.get_proplist_map()?;
135        Self::from_vecpak_map(&term).ok()
136    }
137
138    pub fn to_vecpak_term(&self) -> Term {
139        Term::PropList(vec![
140            (Term::Binary(b"entry_hash".to_vec()), Term::Binary(self.entry_hash.to_vec())),
141            (Term::Binary(b"mutations_hash".to_vec()), Term::Binary(self.mutations_hash.to_vec())),
142            (Term::Binary(b"signer".to_vec()), Term::Binary(self.signer.to_vec())),
143            (Term::Binary(b"signature".to_vec()), Term::Binary(self.signature.to_vec())),
144        ])
145    }
146}