Skip to main content

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}