pod_types/consensus/
attestation.rs1use alloy_primitives::{Address, Signature};
2use alloy_sol_types::{SolStruct, SolValue};
3use serde::{Deserialize, Serialize};
4
5use crate::{
6 Merkleizable, Receipt, Signed, Timestamp,
7 cryptography::{Hash, hash::Hashable, merkle_tree::MerkleBuilder},
8};
9
10pub type ReceiptAttestation = Attestation<Receipt>;
11
12#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
13#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
14pub struct Indexed<T> {
15 #[serde(rename = "timestamp")]
16 pub index: Timestamp, pub value: T,
18}
19
20impl<T> Indexed<T> {
21 pub fn new(index: Timestamp, value: T) -> Self {
22 Indexed { index, value }
23 }
24}
25
26impl<T> From<Indexed<Attestation<T>>> for Indexed<HeadlessAttestation> {
27 fn from(value: Indexed<Attestation<T>>) -> Self {
28 Indexed {
29 index: value.index,
30 value: value.value.into(),
31 }
32 }
33}
34
35impl<T: Hashable> Hashable for Indexed<T> {
36 fn hash_custom(&self) -> Hash {
37 alloy_primitives::keccak256(
38 [
39 self.index.as_micros().abi_encode(),
40 self.value.hash_custom().to_vec(),
41 ]
42 .concat(),
43 )
44 }
45}
46
47#[derive(Copy, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
49pub struct Attestation<T> {
50 pub public_key: Address,
51 pub signature: Signature,
52 pub attested: T,
53}
54
55#[cfg(feature = "arbitrary")]
56impl<'a, T: arbitrary::Arbitrary<'a> + Hashable> arbitrary::Arbitrary<'a> for Attestation<T> {
57 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
58 use alloy_signer::SignerSync;
59 let signer = alloy_signer_local::PrivateKeySigner::random();
60 let attested = T::arbitrary(u)?;
61
62 Ok(Attestation {
63 public_key: signer.address(),
64 signature: signer.sign_hash_sync(&attested.hash_custom()).unwrap(),
65 attested,
66 })
67 }
68}
69
70impl<T: Hashable> Hashable for Attestation<T> {
71 fn hash_custom(&self) -> Hash {
72 let mut hasher = alloy_primitives::Keccak256::default();
73 hasher.update(self.public_key.as_slice());
74 hasher.update(self.signature.as_bytes());
75 hasher.update(self.attested.hash_custom().as_slice());
76 hasher.finalize()
77 }
78}
79
80impl<T> From<Signed<T>> for Attestation<T> {
81 fn from(signed: Signed<T>) -> Self {
82 Attestation {
83 public_key: signed.signer,
84 signature: signed.signature,
85 attested: signed.signed,
86 }
87 }
88}
89
90#[derive(Clone, Debug, Serialize, Deserialize)]
91#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
92pub struct TimestampedHeadlessAttestation {
93 pub timestamp: Timestamp,
94 pub public_key: Address,
95 pub signature: Signature,
96}
97
98#[derive(Clone, Debug, Serialize, Deserialize)]
99#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
100pub struct HeadlessAttestation {
101 pub public_key: Address,
102 pub signature: Signature,
103}
104
105impl<T> From<Attestation<T>> for HeadlessAttestation {
106 fn from(attestation: Attestation<T>) -> Self {
107 HeadlessAttestation {
108 public_key: attestation.public_key,
109 signature: attestation.signature,
110 }
111 }
112}
113
114impl From<Indexed<HeadlessAttestation>> for TimestampedHeadlessAttestation {
115 fn from(indexed: Indexed<HeadlessAttestation>) -> Self {
116 TimestampedHeadlessAttestation {
117 timestamp: indexed.index,
118 public_key: indexed.value.public_key,
119 signature: indexed.value.signature,
120 }
121 }
122}
123
124impl<T> From<Indexed<Attestation<T>>> for TimestampedHeadlessAttestation {
125 fn from(indexed: Indexed<Attestation<T>>) -> Self {
126 Self {
127 timestamp: indexed.index,
128 public_key: indexed.value.public_key,
129 signature: indexed.value.signature,
130 }
131 }
132}
133
134mod sol {
135 alloy_sol_types::sol! {
136 #[derive(Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
137 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
138 struct AttestedTx {
139 bytes32 tx_hash;
140 bool success;
141 uint64 committee_epoch;
142 }
143 }
144}
145
146pub use sol::AttestedTx;
147
148impl AttestedTx {
149 pub fn success(tx_hash: Hash, committee_epoch: u64) -> Self {
150 Self {
151 tx_hash,
152 success: true,
153 committee_epoch,
154 }
155 }
156}
157
158impl Hashable for AttestedTx {
159 fn hash_custom(&self) -> Hash {
160 self.eip712_signing_hash(&alloy_sol_types::eip712_domain! {
161 name: "attest_tx",
162 version: "1",
163 chain_id: 0x50d,
164 })
165 }
166}
167
168impl Merkleizable for AttestedTx {
169 fn append_leaves(&self, builder: &mut MerkleBuilder) {
170 builder.add_field("tx_hash", self.tx_hash);
171 builder.add_field("success", self.success.abi_encode().hash_custom());
172 builder.add_field(
173 "committee_epoch",
174 self.committee_epoch.abi_encode().hash_custom(),
175 );
176 }
177}