Skip to main content

dig_constants/
lib.rs

1//! DIG Network Constants
2//!
3//! Defines network parameters for the DIG L2 blockchain. This crate exists
4//! separately so that any DIG crate can import network constants without
5//! pulling in the full CLVM engine or other heavy dependencies.
6//!
7//! The core type is [`NetworkConstants`], which wraps `chia-consensus`'s
8//! `ConsensusConstants` with DIG-specific values (genesis challenge,
9//! AGG_SIG additional data, cost limits, etc.).
10//!
11//! # Chia L1 vs DIG L2 (do not mix)
12//!
13//! [`DIG_MAINNET`] / [`DIG_TESTNET`] describe the DIG **L2** network. Separately,
14//! [`CHIA_L1_MAINNET_AGG_SIG_ME`] / [`CHIA_L1_TESTNET11_AGG_SIG_ME`] hold the
15//! **Chia L1 (foreign chain)** genesis challenge that DIG wallet code needs as
16//! AGG_SIG_ME additional data when signing L1 spends. They live here as the
17//! ecosystem's single source of truth, but are DELIBERATELY distinct from the DIG
18//! L2 genesis — signing an L1 spend with the DIG L2 genesis produces an invalid
19//! signature. The `CHIA_L1_` prefix is the anti-mixup guard.
20//!
21//! # Usage
22//!
23//! ```rust,ignore
24//! use dig_constants::DIG_MAINNET;
25//!
26//! let genesis = DIG_MAINNET.genesis_challenge();
27//! let consensus = DIG_MAINNET.consensus();
28//! ```
29
30use chia_consensus::consensus_constants::ConsensusConstants;
31use chia_protocol::Bytes32;
32use hex_literal::hex;
33
34/// DIG network constants.
35///
36/// Wraps `chia-consensus::ConsensusConstants` with accessors for the fields
37/// that DIG validators and wallet code commonly need. The underlying
38/// `ConsensusConstants` is available via [`consensus()`](Self::consensus)
39/// for direct use with `chia-consensus` functions like `run_spendbundle()`.
40#[derive(Debug, Clone)]
41pub struct NetworkConstants {
42    inner: ConsensusConstants,
43}
44
45impl NetworkConstants {
46    /// The underlying `chia-consensus` constants, for passing directly to
47    /// `run_spendbundle()`, `validate_clvm_and_signature()`, etc.
48    pub fn consensus(&self) -> &ConsensusConstants {
49        &self.inner
50    }
51
52    /// DIG genesis challenge.
53    pub fn genesis_challenge(&self) -> Bytes32 {
54        self.inner.genesis_challenge
55    }
56
57    /// AGG_SIG_ME additional data (== genesis_challenge on Chia L1).
58    pub fn agg_sig_me_additional_data(&self) -> Bytes32 {
59        self.inner.agg_sig_me_additional_data
60    }
61
62    /// Maximum CLVM cost per block.
63    pub fn max_block_cost_clvm(&self) -> u64 {
64        self.inner.max_block_cost_clvm
65    }
66
67    /// Cost per byte of generator program.
68    pub fn cost_per_byte(&self) -> u64 {
69        self.inner.cost_per_byte
70    }
71
72    /// Maximum coin amount (u64::MAX).
73    pub fn max_coin_amount(&self) -> u64 {
74        self.inner.max_coin_amount
75    }
76}
77
78// =============================================================================
79// AGG_SIG additional data derivation
80//
81// On Chia L1, each AGG_SIG_* variant's additional_data is:
82//   sha256(genesis_challenge || opcode_byte)
83// except AGG_SIG_ME which uses genesis_challenge directly.
84//
85// See: condition_tools.py:58-71
86//   https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/condition_tools.py#L58
87// =============================================================================
88
89// ---------------------------------------------------------------------------
90// DIG Mainnet
91//
92// The genesis challenge is the 32-byte consensus anchor for the DIG L2 network.
93// It doubles as the gossip `network_id` gate: `dig-gossip` REJECTS an all-zero
94// network_id, so this value MUST be non-zero for the node's gossip pool / DHT /
95// PEX to start.
96//
97// DIG_MAINNET L2 genesis = the Chia mainnet header hash @ height 9,021,277
98//   (0af981...1abf), pinned 2026-07-17 — anchors the DIG L2 genesis to a real,
99//   verifiable Chia block (captured via coinset.org get_blockchain_state).
100//
101//   DIG_MAINNET_GENESIS_CHALLENGE
102//     = 0af981862a4df51f51ec59c312315d959931d917c375730b89b9e2b0854d1abf
103//
104// This is the PRE-LAUNCH canonical DIG mainnet genesis. Per CLAUDE.md §3.7 the
105// ecosystem is pre-release with no live users, so this value is revisable at
106// true mainnet launch — re-anchor to the launch-time Chia header hash and
107// recompute every derived value below if it is ever changed.
108//
109// All `agg_sig_*_additional_data` values are derived from this genesis as
110// `sha256(genesis_challenge || opcode_byte)` (AGG_SIG_ME = genesis directly),
111// so they were all recomputed for this genesis.
112// ---------------------------------------------------------------------------
113
114/// Canonical DIG mainnet genesis challenge.
115///
116/// The Chia mainnet header hash at block height 9,021,277 (`0af981…1abf`),
117/// pinned 2026-07-17 — a real, verifiable, fixed 32-byte value anchoring the
118/// DIG L2 genesis to a real Chia block. This is the pre-launch canonical value;
119/// per §3.7 it is revisable at true mainnet launch. All
120/// `agg_sig_*_additional_data` fields are derived from this.
121const DIG_MAINNET_GENESIS_CHALLENGE: [u8; 32] =
122    hex!("0af981862a4df51f51ec59c312315d959931d917c375730b89b9e2b0854d1abf");
123
124/// DIG mainnet constants.
125///
126/// Uses DIG's own genesis challenge and AGG_SIG domain separation.
127/// Proof-of-space and VDF fields are set to neutral values since DIG L2
128/// does not use Chia's proof-of-space consensus.
129pub const DIG_MAINNET: NetworkConstants = NetworkConstants {
130    inner: ConsensusConstants {
131        // -- DIG-specific values --
132        genesis_challenge: Bytes32::new(DIG_MAINNET_GENESIS_CHALLENGE),
133
134        // AGG_SIG additional data: derived from genesis_challenge.
135        // AGG_SIG_ME = genesis_challenge directly.
136        // Others = sha256(genesis_challenge || opcode_byte).
137        // Derivation: condition_tools.py:58-71
138        //   https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/condition_tools.py#L58
139        // Opcode bytes: AGG_SIG_PARENT=43, PUZZLE=44, AMOUNT=45,
140        //   PUZZLE_AMOUNT=46, PARENT_AMOUNT=47, PARENT_PUZZLE=48
141        // NOTE: Recompute ALL values when genesis_challenge is finalized.
142        agg_sig_me_additional_data: Bytes32::new(DIG_MAINNET_GENESIS_CHALLENGE),
143        agg_sig_parent_additional_data: Bytes32::new(hex!(
144            "196d63b6dfbd4440656f9c1eadc686cacfaae771c565762a8cd6e51c892a0077"
145        )),
146        agg_sig_puzzle_additional_data: Bytes32::new(hex!(
147            "9ca719659b5e2355a91ff330c8612cb58c74f1063eaff99e507602d450b1f71f"
148        )),
149        agg_sig_amount_additional_data: Bytes32::new(hex!(
150            "d13767da4a8bd9520dbd9e039e68b3eb4b16fdcbb7e7755b5064840eaeb553ce"
151        )),
152        agg_sig_puzzle_amount_additional_data: Bytes32::new(hex!(
153            "73eea3473bd0daa28793d4bcd218ade462b634b53af97f9a01a91f3059ac75df"
154        )),
155        agg_sig_parent_amount_additional_data: Bytes32::new(hex!(
156            "eb7302224e77c0f269d0c8b105d4cc786775ae012ed2db49751c33c244c3f647"
157        )),
158        agg_sig_parent_puzzle_additional_data: Bytes32::new(hex!(
159            "ccac5983685257d50ee7b439bbb502128ddb262813dde4e4a11ac6cdfc66fa8e"
160        )),
161
162        // DIG L2 cost limits
163        max_block_cost_clvm: 11_000_000_000, // per-spend limit, same as Chia L1
164        cost_per_byte: 12_000,
165        max_coin_amount: u64::MAX,
166
167        // Block generator limits
168        max_generator_size: 1_000_000,
169        max_generator_ref_list_size: 512,
170
171        // Hard fork heights — set to 0 to always use latest consensus rules.
172        // DIG L2 starts with all features enabled from block 0.
173        hard_fork_height: 0,
174        hard_fork2_height: 0,
175
176        // Pre-farm puzzle hashes — not used by DIG L2, set to zero.
177        genesis_pre_farm_pool_puzzle_hash: Bytes32::new([0u8; 32]),
178        genesis_pre_farm_farmer_puzzle_hash: Bytes32::new([0u8; 32]),
179
180        // -- Proof-of-space / VDF fields (not used by DIG L2) --
181        // These must be valid values since ConsensusConstants is passed to
182        // chia-consensus functions, but DIG does not use PoS consensus.
183        slot_blocks_target: 32,
184        min_blocks_per_challenge_block: 16,
185        max_sub_slot_blocks: 128,
186        num_sps_sub_slot: 64,
187        sub_slot_iters_starting: 1 << 27,
188        difficulty_constant_factor: 1 << 67,
189        difficulty_starting: 7,
190        difficulty_change_max_factor: 3,
191        sub_epoch_blocks: 384,
192        epoch_blocks: 4608,
193        significant_bits: 8,
194        discriminant_size_bits: 1024,
195        number_zero_bits_plot_filter_v1: 9,
196        number_zero_bits_plot_filter_v2: 9,
197        min_plot_size_v1: 32,
198        max_plot_size_v1: 50,
199        min_plot_size_v2: 28,
200        max_plot_size_v2: 32,
201        sub_slot_time_target: 600,
202        num_sp_intervals_extra: 3,
203        max_future_time2: 120,
204        number_of_timestamps: 11,
205        max_vdf_witness_size: 64,
206        mempool_block_buffer: 10,
207        weight_proof_threshold: 2,
208        blocks_cache_size: 4608 + (128 * 4),
209        weight_proof_recent_blocks: 1000,
210        max_block_count_per_requests: 32,
211        pool_sub_slot_iters: 37_600_000_000,
212        plot_filter_128_height: 0xffff_ffff,
213        plot_filter_64_height: 0xffff_ffff,
214        plot_filter_32_height: 0xffff_ffff,
215        plot_difficulty_initial: 2,
216        plot_difficulty_4_height: 0xffff_ffff,
217        plot_difficulty_5_height: 0xffff_ffff,
218        plot_difficulty_6_height: 0xffff_ffff,
219        plot_difficulty_7_height: 0xffff_ffff,
220        plot_difficulty_8_height: 0xffff_ffff,
221    },
222};
223
224// =============================================================================
225// NAT-traversal relay endpoint
226//
227// A DIG Node behind NAT cannot accept inbound dials, so it holds a constant
228// reservation with a publicly-reachable relay to stay discoverable. The
229// canonical public relay is `relay.dig.net`, serving the `RelayMessage`
230// WebSocket wire (RLY-001..RLY-007) on port 9450.
231//
232// This constant is the single source of truth for that endpoint so consumers
233// (`dig-node`, `dig-gossip`) don't each hardcode it. It MUST stay byte-identical
234// to `dig-node`'s `relay::DEFAULT_RELAY_URL` (the string a node actually dials
235// when `DIG_RELAY_URL` is unset) and to the `dig-relay` server's documented
236// client endpoint.
237//
238// Port 443: the live `relay.dig.net` NLB exposes its public TLS listener on the
239// standard HTTPS port 443 (the earlier :9450 listener is closed). Using 443 also
240// maximizes reachability from restrictive networks that only allow outbound 443.
241// =============================================================================
242
243/// Canonical DIG NAT-traversal relay endpoint.
244///
245/// This is the WebSocket URL a DIG Node dials by default to obtain a relay
246/// reservation (so NAT'd peers stay reachable). It is the value used unless an
247/// operator overrides it via the `DIG_RELAY_URL` environment variable (or
248/// disables the reservation with `DIG_RELAY_URL=off`).
249///
250/// Format: `wss://<host>:<port>` — the relay protocol (`RelayMessage`,
251/// RLY-001..RLY-007) is JSON over a secure WebSocket. Mainnet uses the canonical
252/// public deployment `relay.dig.net` on port 443 (the live NLB public TLS
253/// listener; the earlier :9450 listener is closed).
254///
255/// Kept byte-identical to `dig-node`'s `relay::DEFAULT_RELAY_URL` and the
256/// `dig-relay` server's documented client endpoint.
257pub const DIG_RELAY_URL: &str = "wss://relay.dig.net:443";
258
259// =============================================================================
260// DIG Node localhost endpoint
261//
262// A client connecting to a local DIG node (§5.3 client→node connection order)
263// resolves `dig.local` or `localhost` to reach the node via localhost TCP on
264// port 9778. This constant is the single source of truth for that port so
265// consumers (dig-node, dig-dns, dig-installer, SDK, CLI) don't each hardcode it.
266// =============================================================================
267
268/// The default localhost port a client uses to reach the local DIG node.
269///
270/// This is used to implement §5.3 client→node connection order: when a client
271/// needs to connect to a DIG node, it tries `dig.local` and `localhost` on this
272/// port before falling back to the public `rpc.dig.net` gateway. This constant
273/// ensures all consumers (dig-node, dig-dns, dig-installer, dig-sdk, digstore CLI)
274/// use an identical port, preventing port-mismatch bugs. It MUST stay byte-identical
275/// to `dig-node`'s documented localhost serve port and the installer's registered
276/// `dig.local` address.
277pub const DIG_NODE_PORT: u16 = 9778;
278
279// =============================================================================
280// DIG CAT asset id ($DIG token)
281//
282// $DIG is a Chia CAT (CHIP-0004); its asset id is the TAIL program's hash,
283// fixed for the token's lifetime. This is the single canonical home for that
284// value — `chip35_dl_coin`, `dig-cat-decoder`, and any DIG-aware wallet/
285// balance/spend code import it from HERE rather than each hardcoding a copy.
286// =============================================================================
287
288/// Canonical $DIG CAT asset id (TAIL hash) on Chia mainnet.
289///
290/// The single token every capsule (commit) payment is denominated in
291/// (`chip35_dl_coin::build_dig_store_payment`) and the value a wallet/decoder
292/// checks a CAT coin's `asset_id` against to recognize $DIG.
293///
294/// CONTRACT: byte-identical to `chip35_dl_coin::DIG_ASSET_ID`, digstore-chain's
295/// `DIG_ASSET_ID`, and DataLayer-Driver's. Do not change without changing every
296/// consumer in lockstep (SYSTEM.md → Shared contracts → DIG CAT payment).
297pub const DIG_ASSET_ID: Bytes32 = Bytes32::new(hex!(
298    "a406d3a9de984d03c9591c10d917593b434d5263cabe2b42f6b367df16832f81"
299));
300
301// =============================================================================
302// Chia L1 (foreign chain) AGG_SIG_ME additional data
303//
304// The DIG wallet signs and validates spends on the Chia L1 chain. On Chia L1 the
305// AGG_SIG_ME additional data IS the network genesis challenge, so every L1 spend
306// signature is bound to it. This is a FOREIGN chain's value — completely distinct
307// from the DIG L2 genesis (`DIG_MAINNET_GENESIS_CHALLENGE`, 0af98186…).
308//
309// Both the wallet's signer seam AND the engine's message-binding seam MUST read
310// the SAME 32 bytes from here, or a spend the engine builds is signed with a
311// different domain than it binds — a custody break (invalid, unspendable
312// signatures on mainnet). This crate is the single source of truth for those
313// bytes; the `[u8; 32]` shape matches the signer field directly (the engine wraps
314// it once via `Bytes32::new(...)`).
315//
316// The value is invariant-forced: it is exactly Chia's well-known mainnet genesis
317// (ccd5bb71…) / testnet11 genesis (37a90eb5…), the same values
318// `chia-wallet-sdk`'s `MAINNET_CONSTANTS` / `TESTNET11_CONSTANTS` carry (asserted
319// by an anti-drift dev-dependency test).
320// =============================================================================
321
322/// Chia **L1 mainnet** genesis challenge, used as AGG_SIG_ME additional data.
323///
324/// The 32-byte domain every Chia L1 mainnet spend signature is bound to. This is
325/// the foreign-chain (Chia) value — DISTINCT from the DIG L2 genesis
326/// ([`DIG_MAINNET`]); signing an L1 spend with the DIG L2 genesis yields an
327/// invalid signature.
328///
329/// CONTRACT: DIG wallet consumers (the client signer AND the engine's
330/// message-binding path) MUST both use this constant so signer == engine,
331/// producing byte-identical, valid signatures. Equals Chia's canonical mainnet
332/// genesis `ccd5bb71…` (== `chia_sdk_types::MAINNET_CONSTANTS.agg_sig_me_additional_data`).
333pub const CHIA_L1_MAINNET_AGG_SIG_ME: [u8; 32] =
334    hex!("ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb");
335
336/// Chia **L1 testnet11** genesis challenge, used as AGG_SIG_ME additional data.
337///
338/// The 32-byte domain every Chia L1 testnet11 spend signature is bound to. As
339/// with [`CHIA_L1_MAINNET_AGG_SIG_ME`], this is the foreign-chain (Chia) value,
340/// DISTINCT from the DIG L2 genesis ([`DIG_TESTNET`]).
341///
342/// CONTRACT: DIG wallet consumers (signer AND engine) MUST both use this constant
343/// so signer == engine on testnet11. Equals Chia's canonical testnet11 genesis
344/// `37a90eb5…` (== `chia_sdk_types::TESTNET11_CONSTANTS.agg_sig_me_additional_data`).
345pub const CHIA_L1_TESTNET11_AGG_SIG_ME: [u8; 32] =
346    hex!("37a90eb5185a9c4439a91ddc98bbadce7b4feba060d50116a067de66bf236615");
347
348// ---------------------------------------------------------------------------
349// DIG Testnet
350// ---------------------------------------------------------------------------
351
352/// Canonical DIG testnet genesis challenge.
353///
354/// Deterministically derived as `sha256(b"DIG_TESTNET:genesis:v1")` — distinct
355/// from mainnet so the two networks never share a `network_id`. Non-zero so the
356/// gossip network_id gate accepts it. Pre-launch canonical value (§3.7),
357/// revisable at true launch; all derived agg_sig data below follows it.
358///   = 088c18d6b7859d885dc2f03166e862c958f74b63b6353c3df71d103b9b806c3b
359const DIG_TESTNET_GENESIS_CHALLENGE: [u8; 32] =
360    hex!("088c18d6b7859d885dc2f03166e862c958f74b63b6353c3df71d103b9b806c3b");
361
362/// DIG testnet constants.
363///
364/// Same structure as mainnet but with a different genesis challenge.
365/// Useful for testing without risking mainnet state.
366pub const DIG_TESTNET: NetworkConstants = NetworkConstants {
367    inner: ConsensusConstants {
368        genesis_challenge: Bytes32::new(DIG_TESTNET_GENESIS_CHALLENGE),
369        // AGG_SIG_ME = genesis_challenge. Others = sha256(genesis || opcode_byte).
370        agg_sig_me_additional_data: Bytes32::new(DIG_TESTNET_GENESIS_CHALLENGE),
371        agg_sig_parent_additional_data: Bytes32::new(hex!(
372            "85b3963bdeb9848af970a9bbd1d36809ae41491ffd67aee7f27e8883936d495c"
373        )),
374        agg_sig_puzzle_additional_data: Bytes32::new(hex!(
375            "66aba1939e128e1465d58fde414325630e891747c1428d76ebce193cbe966301"
376        )),
377        agg_sig_amount_additional_data: Bytes32::new(hex!(
378            "eccab86920a6d982a68898b2dcb7c150383529fcd532fe84c693fb4592c38ae3"
379        )),
380        agg_sig_puzzle_amount_additional_data: Bytes32::new(hex!(
381            "eb088fad0d4caba66e29130fb07407e60a7545d035d19a188fef0855c874084e"
382        )),
383        agg_sig_parent_amount_additional_data: Bytes32::new(hex!(
384            "232aec0a351ba4936b04920e074aebcc621a458f6b1461c4b28c658552f2f35d"
385        )),
386        agg_sig_parent_puzzle_additional_data: Bytes32::new(hex!(
387            "96263ac395703ab9b3b0f0587e79185f4a9898574a28b4491015ddcf9d321873"
388        )),
389        // All other fields same as mainnet
390        max_block_cost_clvm: 11_000_000_000,
391        cost_per_byte: 12_000,
392        max_coin_amount: u64::MAX,
393        max_generator_size: 1_000_000,
394        max_generator_ref_list_size: 512,
395        hard_fork_height: 0,
396        hard_fork2_height: 0,
397        genesis_pre_farm_pool_puzzle_hash: Bytes32::new([0u8; 32]),
398        genesis_pre_farm_farmer_puzzle_hash: Bytes32::new([0u8; 32]),
399        slot_blocks_target: 32,
400        min_blocks_per_challenge_block: 16,
401        max_sub_slot_blocks: 128,
402        num_sps_sub_slot: 64,
403        sub_slot_iters_starting: 1 << 27,
404        difficulty_constant_factor: 1 << 67,
405        difficulty_starting: 7,
406        difficulty_change_max_factor: 3,
407        sub_epoch_blocks: 384,
408        epoch_blocks: 4608,
409        significant_bits: 8,
410        discriminant_size_bits: 1024,
411        number_zero_bits_plot_filter_v1: 9,
412        number_zero_bits_plot_filter_v2: 9,
413        min_plot_size_v1: 32,
414        max_plot_size_v1: 50,
415        min_plot_size_v2: 28,
416        max_plot_size_v2: 32,
417        sub_slot_time_target: 600,
418        num_sp_intervals_extra: 3,
419        max_future_time2: 120,
420        number_of_timestamps: 11,
421        max_vdf_witness_size: 64,
422        mempool_block_buffer: 10,
423        weight_proof_threshold: 2,
424        blocks_cache_size: 4608 + (128 * 4),
425        weight_proof_recent_blocks: 1000,
426        max_block_count_per_requests: 32,
427        pool_sub_slot_iters: 37_600_000_000,
428        plot_filter_128_height: 0xffff_ffff,
429        plot_filter_64_height: 0xffff_ffff,
430        plot_filter_32_height: 0xffff_ffff,
431        plot_difficulty_initial: 2,
432        plot_difficulty_4_height: 0xffff_ffff,
433        plot_difficulty_5_height: 0xffff_ffff,
434        plot_difficulty_6_height: 0xffff_ffff,
435        plot_difficulty_7_height: 0xffff_ffff,
436        plot_difficulty_8_height: 0xffff_ffff,
437    },
438};
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443
444    /// The canonical relay endpoint must equal exactly what a DIG Node dials by
445    /// default. This pins the value byte-for-byte against `dig-node`'s
446    /// `relay::DEFAULT_RELAY_URL` (`wss://relay.dig.net:9450`) and the
447    /// `dig-relay` server's documented client endpoint. If either side ever
448    /// changes the scheme, host, or port, this guard fails so the shared
449    /// contract can't silently drift.
450    #[test]
451    fn dig_relay_url_is_canonical_endpoint() {
452        assert_eq!(DIG_RELAY_URL, "wss://relay.dig.net:443");
453    }
454
455    /// The relay endpoint is a secure-WebSocket URL pointing at the canonical
456    /// public host on the relay protocol port.
457    #[test]
458    fn dig_relay_url_is_well_formed() {
459        assert!(
460            DIG_RELAY_URL.starts_with("wss://"),
461            "relay must use secure WebSocket"
462        );
463        assert!(
464            DIG_RELAY_URL.contains("relay.dig.net"),
465            "relay must point at the canonical host"
466        );
467        assert!(
468            DIG_RELAY_URL.ends_with(":443"),
469            "relay must use the live NLB public TLS port 443"
470        );
471    }
472
473    /// The DIG node localhost port must equal the expected default.
474    ///
475    /// This guards against accidental mutations and ensures all consumers
476    /// (dig-node, dig-dns, dig-installer, dig-sdk, digstore) use a consistent
477    /// port when connecting to the local node on `dig.local` or `localhost`.
478    #[test]
479    fn dig_node_port_is_canonical() {
480        assert_eq!(DIG_NODE_PORT, 9778);
481    }
482
483    // -- Genesis challenge canonical-value guards --------------------------
484    //
485    // These pin the pre-launch canonical genesis challenges byte-for-byte AND
486    // prove they are reproducible from their documented preimages, so the
487    // values can never silently drift (a drift changes every derived signature
488    // domain + the gossip network_id — a cross-repo breaking event).
489
490    use sha2::{Digest, Sha256};
491
492    /// AGG_SIG opcode bytes, per §4.2 of `SPEC.md` (Chia L1 `condition_tools`).
493    const AGG_SIG_OPCODES: [u8; 6] = [43, 44, 45, 46, 47, 48];
494
495    fn sha256(bytes: &[u8]) -> [u8; 32] {
496        let mut hasher = Sha256::new();
497        hasher.update(bytes);
498        hasher.finalize().into()
499    }
500
501    /// The genesis MUST be non-zero: `dig-gossip` rejects an all-zero
502    /// `network_id`, so a zero genesis would stop the node's gossip pool / DHT /
503    /// PEX from ever starting. This is the connect-enabler invariant.
504    #[test]
505    fn genesis_challenges_are_non_zero() {
506        assert_ne!(DIG_MAINNET.genesis_challenge(), Bytes32::new([0u8; 32]));
507        assert_ne!(DIG_TESTNET.genesis_challenge(), Bytes32::new([0u8; 32]));
508    }
509
510    /// The mainnet genesis is pinned to the Chia mainnet header hash @ height
511    /// 9,021,277 (a real anchored value), and the testnet genesis is the
512    /// reproducible `sha256` of its documented preimage. These pin both values
513    /// byte-for-byte so neither can silently drift.
514    #[test]
515    fn genesis_challenges_are_the_pinned_values() {
516        assert_eq!(
517            DIG_MAINNET_GENESIS_CHALLENGE,
518            hex_literal::hex!("0af981862a4df51f51ec59c312315d959931d917c375730b89b9e2b0854d1abf"),
519        );
520        assert_eq!(
521            DIG_TESTNET_GENESIS_CHALLENGE,
522            sha256(b"DIG_TESTNET:genesis:v1"),
523        );
524    }
525
526    /// Mainnet and testnet MUST NOT share a genesis (no cross-network replay).
527    #[test]
528    fn mainnet_and_testnet_genesis_differ() {
529        assert_ne!(
530            DIG_MAINNET.genesis_challenge(),
531            DIG_TESTNET.genesis_challenge(),
532        );
533    }
534
535    /// Pins the $DIG CAT asset id byte-for-byte against the value shipped in
536    /// `chip35_dl_coin::DIG_ASSET_ID` — a drift here silently breaks $DIG
537    /// recognition across every consumer (wallets, decoders, payment builders).
538    #[test]
539    fn dig_asset_id_is_canonical() {
540        assert_eq!(
541            DIG_ASSET_ID,
542            Bytes32::new(hex_literal::hex!(
543                "a406d3a9de984d03c9591c10d917593b434d5263cabe2b42f6b367df16832f81"
544            )),
545        );
546    }
547
548    // -- Chia L1 AGG_SIG_ME anti-drift guards ------------------------------
549
550    /// Literal pin: the Chia L1 AGG_SIG_ME constants equal Chia's well-known
551    /// mainnet / testnet11 genesis challenges byte-for-byte. This catches any
552    /// accidental mutation independently of any external crate.
553    #[test]
554    fn chia_l1_agg_sig_me_constants_are_the_pinned_values() {
555        assert_eq!(
556            CHIA_L1_MAINNET_AGG_SIG_ME,
557            hex_literal::hex!("ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb"),
558        );
559        assert_eq!(
560            CHIA_L1_TESTNET11_AGG_SIG_ME,
561            hex_literal::hex!("37a90eb5185a9c4439a91ddc98bbadce7b4feba060d50116a067de66bf236615"),
562        );
563    }
564
565    /// Source KAT: the Chia L1 constants MUST equal the values `chia-wallet-sdk`
566    /// (via `chia-sdk-types`) uses in its `MAINNET_CONSTANTS` / `TESTNET11_CONSTANTS`.
567    /// This is the primary anti-drift guard — the wallet engine binds spends with
568    /// those SDK constants, so if a future SDK version ever changed the value, this
569    /// fails and forces a deliberate re-pin instead of a silent custody break.
570    #[test]
571    fn chia_l1_agg_sig_me_matches_chia_sdk_types() {
572        use chia_sdk_types::{MAINNET_CONSTANTS, TESTNET11_CONSTANTS};
573        assert_eq!(
574            CHIA_L1_MAINNET_AGG_SIG_ME.as_slice(),
575            MAINNET_CONSTANTS.agg_sig_me_additional_data.as_ref(),
576        );
577        assert_eq!(
578            CHIA_L1_TESTNET11_AGG_SIG_ME.as_slice(),
579            TESTNET11_CONSTANTS.agg_sig_me_additional_data.as_ref(),
580        );
581    }
582
583    /// The Chia L1 (foreign chain) AGG_SIG_ME MUST NOT equal the DIG L2 genesis —
584    /// this is the whole reason the constants exist. Signing an L1 spend with the
585    /// DIG L2 genesis would be a custody break.
586    #[test]
587    fn chia_l1_agg_sig_me_differs_from_dig_l2_genesis() {
588        assert_ne!(
589            Bytes32::new(CHIA_L1_MAINNET_AGG_SIG_ME),
590            DIG_MAINNET.genesis_challenge(),
591        );
592        assert_ne!(
593            Bytes32::new(CHIA_L1_TESTNET11_AGG_SIG_ME),
594            DIG_TESTNET.genesis_challenge(),
595        );
596    }
597
598    /// Every baked-in AGG_SIG additional-data value MUST equal the §4.1 rule
599    /// applied to the network's genesis: AGG_SIG_ME == genesis, and each other
600    /// variant == `sha256(genesis || opcode_byte)`. This regenerates the values
601    /// independently and asserts the constants match — so a genesis change that
602    /// forgets to recompute a derived value is caught.
603    #[test]
604    fn agg_sig_additional_data_matches_derivation_rule() {
605        for net in [&DIG_MAINNET, &DIG_TESTNET] {
606            let genesis = net.genesis_challenge();
607            assert_eq!(net.agg_sig_me_additional_data(), genesis);
608
609            let c = net.consensus();
610            let derived: Vec<Bytes32> = AGG_SIG_OPCODES
611                .iter()
612                .map(|&op| {
613                    let mut preimage = genesis.as_ref().to_vec();
614                    preimage.push(op);
615                    Bytes32::new(sha256(&preimage))
616                })
617                .collect();
618            assert_eq!(c.agg_sig_parent_additional_data, derived[0]);
619            assert_eq!(c.agg_sig_puzzle_additional_data, derived[1]);
620            assert_eq!(c.agg_sig_amount_additional_data, derived[2]);
621            assert_eq!(c.agg_sig_puzzle_amount_additional_data, derived[3]);
622            assert_eq!(c.agg_sig_parent_amount_additional_data, derived[4]);
623            assert_eq!(c.agg_sig_parent_puzzle_additional_data, derived[5]);
624        }
625    }
626}