dig_slashing/evidence/attestation_data.rs
1//! `AttestationData` — the Ethereum-parity attester vote payload.
2//!
3//! Traces to: [SPEC.md §3.3](../../docs/resources/SPEC.md), catalogue row
4//! [DSL-004](../../docs/requirements/domains/evidence/specs/DSL-004.md).
5//!
6//! # Role
7//!
8//! `AttestationData` is the signable payload every attester BLS-signs. It
9//! carries:
10//!
11//! - `slot` + `index` — the committee coordinates.
12//! - `beacon_block_root` — the head vote.
13//! - `source` + `target` — the FFG vote pair ([`Checkpoint`]).
14//!
15//! `signing_root(&network_id)` hashes the payload under `DOMAIN_BEACON_ATTESTER`
16//! with the network id mixed in; the result is the BLS signing message
17//! consumed by `IndexedAttestation::verify_signature` (DSL-006) and by
18//! `classify_timeliness` (DSL-075..077) participation tracking.
19//!
20//! # Determinism + replay resistance
21//!
22//! - Identical inputs always produce identical output (verified by
23//! `test_dsl_004_signing_root_deterministic`).
24//! - The domain tag stops a signature produced here from verifying against
25//! a proposer signing message (which uses a different tag, DSL-050).
26//! - The network_id mix stops cross-network replay (testnet → mainnet).
27//! - Every field (including both `Checkpoint`s in full) participates in
28//! the hash, so mutation anywhere shifts the output.
29
30use chia_sha2::Sha256;
31use dig_protocol::Bytes32;
32use serde::{Deserialize, Serialize};
33
34use crate::constants::DOMAIN_BEACON_ATTESTER;
35use crate::evidence::checkpoint::Checkpoint;
36
37/// Attester vote payload.
38///
39/// Per [SPEC §3.3](../../docs/resources/SPEC.md). Field layout is frozen
40/// as wire protocol — see [`AttestationData::signing_root`] for the exact
41/// byte order used by BLS signing.
42#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
43pub struct AttestationData {
44 /// L2 slot the attestation targets.
45 pub slot: u64,
46 /// Committee index within the slot.
47 pub index: u64,
48 /// Head vote: canonical beacon block root at `slot`.
49 pub beacon_block_root: Bytes32,
50 /// FFG source checkpoint.
51 pub source: Checkpoint,
52 /// FFG target checkpoint.
53 pub target: Checkpoint,
54}
55
56impl AttestationData {
57 /// Compute the BLS signing root for this attestation.
58 ///
59 /// Implements [DSL-004](../../docs/requirements/domains/evidence/specs/DSL-004.md).
60 /// Traces to SPEC §3.3.
61 ///
62 /// # Wire layout
63 ///
64 /// The hasher is fed the following bytes, in order:
65 ///
66 /// ```text
67 /// DOMAIN_BEACON_ATTESTER (22 bytes, "DIG_BEACON_ATTESTER_V1")
68 /// network_id (32 bytes)
69 /// slot (8 bytes, little-endian u64)
70 /// index (8 bytes, little-endian u64)
71 /// beacon_block_root (32 bytes)
72 /// source.epoch (8 bytes, little-endian u64)
73 /// source.root (32 bytes)
74 /// target.epoch (8 bytes, little-endian u64)
75 /// target.root (32 bytes)
76 /// ```
77 ///
78 /// Total input: 182 bytes. Output: 32-byte SHA-256 digest.
79 ///
80 /// # Invariants
81 ///
82 /// - **Deterministic:** identical `(self, network_id)` inputs always
83 /// produce bit-identical output.
84 /// - **Domain-bound:** prefixed with `DOMAIN_BEACON_ATTESTER`; the tag
85 /// is NOT a separate field of the struct.
86 /// - **Network-bound:** `network_id` mixed in after the tag; a
87 /// signing root produced for testnet does NOT verify under mainnet.
88 /// - **Field-covering:** every field (including both `Checkpoint`
89 /// components) contributes to the digest; mutating any one shifts
90 /// the output.
91 ///
92 /// All four invariants are enforced by
93 /// `tests/dsl_004_attestation_data_signing_root_test.rs`.
94 ///
95 /// # Endianness
96 ///
97 /// All integer fields (`slot`, `index`, `source.epoch`, `target.epoch`)
98 /// use little-endian encoding via `u64::to_le_bytes`. Little-endian is
99 /// the wire-level standard for DIG / Chia; the test suite guards
100 /// against accidental big-endian drift.
101 ///
102 /// # No custom hashing
103 ///
104 /// Uses `chia_sha2::Sha256` directly — do NOT introduce a generic
105 /// `sha2` crate dep for this codebase (SPEC §5 hard rule, dt-hard-rules
106 /// Rule 4).
107 pub fn signing_root(&self, network_id: &Bytes32) -> Bytes32 {
108 let mut h = Sha256::new();
109 h.update(DOMAIN_BEACON_ATTESTER);
110 h.update(network_id.as_ref());
111 h.update(self.slot.to_le_bytes());
112 h.update(self.index.to_le_bytes());
113 h.update(self.beacon_block_root.as_ref());
114 h.update(self.source.epoch.to_le_bytes());
115 h.update(self.source.root.as_ref());
116 h.update(self.target.epoch.to_le_bytes());
117 h.update(self.target.root.as_ref());
118 let out: [u8; 32] = h.finalize();
119 Bytes32::new(out)
120 }
121}