amadeus_node/consensus/doms/
attestation.rs1use crate::Context;
2use crate::consensus::agg_sig::DST_ATT;
3use crate::node::protocol;
4use crate::node::protocol::Protocol;
5use crate::utils::bls12_381 as bls;
6use crate::utils::bls12_381::Error as BlsError;
7use crate::utils::misc::{TermExt, TermMap};
8use crate::utils::safe_etf::encode_safe;
9use eetf::DecodeError as EtfDecodeError;
10use eetf::EncodeError as EtfEncodeError;
11use eetf::{Atom, Binary, Term};
12use std::collections::HashMap;
13use std::fmt::Debug;
14use std::net::Ipv4Addr;
15use tracing::{instrument, warn};
16
17#[derive(Debug, thiserror::Error)]
18pub enum Error {
19 #[error("wrong type: {0}")]
20 WrongType(&'static str),
21 #[error("missing field: {0}")]
22 Missing(&'static str),
23 #[error("too large")]
24 TooLarge,
25 #[error("not deterministically encoded")]
26 NotDeterministic,
27 #[error("invalid length: {0}")]
28 InvalidLength(&'static str),
29 #[error(transparent)]
30 EtfDecode(#[from] EtfDecodeError),
31 #[error(transparent)]
32 EtfEncode(#[from] EtfEncodeError),
33 #[error(transparent)]
34 Bls(#[from] BlsError),
35}
36
37#[derive(Debug, Clone)]
38pub struct EventAttestation {
39 pub attestation: Attestation,
40}
41
42#[derive(Clone)]
43pub struct Attestation {
44 pub entry_hash: [u8; 32],
45 pub mutations_hash: [u8; 32],
46 pub signer: [u8; 48],
47 pub signature: [u8; 96],
48}
49
50impl Debug for Attestation {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.debug_struct("Attestation")
53 .field("entry_hash", &bs58::encode(self.entry_hash).into_string())
54 .field("mutations_hash", &bs58::encode(self.mutations_hash).into_string())
55 .field("signer", &bs58::encode(self.signer).into_string())
56 .finish()
57 }
58}
59
60impl crate::utils::misc::Typename for EventAttestation {
61 fn typename(&self) -> &'static str {
62 Self::TYPENAME
63 }
64}
65
66#[async_trait::async_trait]
67impl Protocol for EventAttestation {
68 #[instrument(skip(map), name = "EventAttestation::from_etf_map_validated")]
69 fn from_etf_map_validated(map: TermMap) -> Result<Self, protocol::Error> {
70 let bin = map.get_binary("attestation_packed").ok_or(Error::Missing("attestations_packed"))?;
71 let attestation = Attestation::from_etf_bin(bin)?;
72
73 Ok(Self { attestation })
74 }
75
76 fn to_etf_bin(&self) -> Result<Vec<u8>, protocol::Error> {
77 let attestation = self.attestation.to_etf_bin()?;
78 let mut m = HashMap::new();
79 m.insert(Term::Atom(Atom::from("op")), Term::Atom(Atom::from(Self::TYPENAME)));
80 m.insert(Term::Atom(Atom::from("attestation_packed")), Term::from(Binary { bytes: attestation }));
81 let term = Term::from(eetf::Map { map: m });
82 let etf_data = encode_safe(&term);
83 Ok(etf_data)
84 }
85
86 #[instrument(skip(self, _ctx), name = "EventAttestation::handle", err)]
87 async fn handle(&self, _ctx: &Context, _src: Ipv4Addr) -> Result<Vec<protocol::Instruction>, protocol::Error> {
88 Ok(vec![protocol::Instruction::Noop { why: "event_attestation handling not implemented".to_string() }])
90 }
91}
92
93impl EventAttestation {
94 pub const TYPENAME: &'static str = "event_attestation";
95}
96
97impl Attestation {
98 #[instrument(skip(bin), name = "Attestation::from_etf_bin", err)]
99 pub fn from_etf_bin(bin: &[u8]) -> Result<Self, Error> {
100 let term = Term::decode(bin)?;
101 let map = match term {
102 Term::Map(m) => m.map,
103 _ => return Err(Error::WrongType("attestation map")),
104 };
105 let entry_hash_v = map
106 .get(&Term::Atom(Atom::from("entry_hash")))
107 .and_then(|t| t.get_binary())
108 .map(|b| b.to_vec())
109 .ok_or(Error::Missing("entry_hash"))?;
110 let mutations_hash_v = map
111 .get(&Term::Atom(Atom::from("mutations_hash")))
112 .and_then(|t| t.get_binary())
113 .map(|b| b.to_vec())
114 .ok_or(Error::Missing("mutations_hash"))?;
115 let signer_v = map
116 .get(&Term::Atom(Atom::from("signer")))
117 .and_then(|t| t.get_binary())
118 .map(|b| b.to_vec())
119 .ok_or(Error::Missing("signer"))?;
120 let signature_v = map
121 .get(&Term::Atom(Atom::from("signature")))
122 .and_then(|t| t.get_binary())
123 .map(|b| b.to_vec())
124 .ok_or(Error::Missing("signature"))?;
125
126 Ok(Attestation {
127 entry_hash: entry_hash_v.try_into().map_err(|_| Error::InvalidLength("entry_hash"))?,
128 mutations_hash: mutations_hash_v.try_into().map_err(|_| Error::InvalidLength("mutations_hash"))?,
129 signer: signer_v.try_into().map_err(|_| Error::InvalidLength("signer"))?,
130 signature: signature_v.try_into().map_err(|_| Error::InvalidLength("signature"))?,
131 })
132 }
133 #[instrument(skip(self), name = "Attestation::to_etf_bin", err)]
135 pub fn to_etf_bin(&self) -> Result<Vec<u8>, Error> {
136 let mut m = HashMap::new();
137 m.insert(Term::Atom(Atom::from("entry_hash")), Term::from(Binary { bytes: self.entry_hash.to_vec() }));
138 m.insert(Term::Atom(Atom::from("mutations_hash")), Term::from(Binary { bytes: self.mutations_hash.to_vec() }));
139 m.insert(Term::Atom(Atom::from("signer")), Term::from(Binary { bytes: self.signer.to_vec() }));
140 m.insert(Term::Atom(Atom::from("signature")), Term::from(Binary { bytes: self.signature.to_vec() }));
141 let term = Term::from(eetf::Map { map: m });
142 let out = encode_safe(&term);
143 Ok(out)
144 }
145
146 #[instrument(skip(self), name = "Attestation::validate", err)]
148 pub fn validate(&self) -> Result<(), Error> {
149 let mut to_sign = [0u8; 64];
150 to_sign[..32].copy_from_slice(&self.entry_hash);
151 to_sign[32..].copy_from_slice(&self.mutations_hash);
152 bls::verify(&self.signer, &self.signature, &to_sign, DST_ATT)?;
153 Ok(())
154 }
155
156 pub fn validate_vs_trainers<TPk>(&self, trainers: &[TPk]) -> Result<(), Error>
159 where
160 TPk: AsRef<[u8]>,
161 {
162 let is_allowed = trainers.iter().any(|pk| pk.as_ref() == self.signer);
163 if !is_allowed {
164 return Err(Error::WrongType("signer_not_trainer"));
165 }
166 self.validate()
167 }
168
169 pub fn sign_with(
172 pk_g1_48: &[u8],
173 trainer_sk: &[u8],
174 entry_hash: &[u8; 32],
175 mutations_hash: &[u8; 32],
176 ) -> Result<Self, Error> {
177 let mut msg = [0u8; 64];
178 msg[..32].copy_from_slice(entry_hash);
179 msg[32..].copy_from_slice(mutations_hash);
180 let signature = bls::sign(trainer_sk, &msg, DST_ATT)?;
181 let signer: [u8; 48] = pk_g1_48.try_into().map_err(|_| Error::InvalidLength("signer"))?;
182 let signature: [u8; 96] = signature.as_slice().try_into().map_err(|_| Error::InvalidLength("signature"))?;
183 Ok(Self { entry_hash: *entry_hash, mutations_hash: *mutations_hash, signer, signature })
184 }
185}