use chia_consensus::consensus_constants::ConsensusConstants;
use chia_protocol::Bytes32;
use hex_literal::hex;
#[derive(Debug, Clone)]
pub struct NetworkConstants {
inner: ConsensusConstants,
}
impl NetworkConstants {
pub fn consensus(&self) -> &ConsensusConstants {
&self.inner
}
pub fn genesis_challenge(&self) -> Bytes32 {
self.inner.genesis_challenge
}
pub fn agg_sig_me_additional_data(&self) -> Bytes32 {
self.inner.agg_sig_me_additional_data
}
pub fn max_block_cost_clvm(&self) -> u64 {
self.inner.max_block_cost_clvm
}
pub fn cost_per_byte(&self) -> u64 {
self.inner.cost_per_byte
}
pub fn max_coin_amount(&self) -> u64 {
self.inner.max_coin_amount
}
}
const DIG_MAINNET_GENESIS_CHALLENGE: [u8; 32] =
hex!("0af981862a4df51f51ec59c312315d959931d917c375730b89b9e2b0854d1abf");
pub const DIG_MAINNET: NetworkConstants = NetworkConstants {
inner: ConsensusConstants {
genesis_challenge: Bytes32::new(DIG_MAINNET_GENESIS_CHALLENGE),
agg_sig_me_additional_data: Bytes32::new(DIG_MAINNET_GENESIS_CHALLENGE),
agg_sig_parent_additional_data: Bytes32::new(hex!(
"196d63b6dfbd4440656f9c1eadc686cacfaae771c565762a8cd6e51c892a0077"
)),
agg_sig_puzzle_additional_data: Bytes32::new(hex!(
"9ca719659b5e2355a91ff330c8612cb58c74f1063eaff99e507602d450b1f71f"
)),
agg_sig_amount_additional_data: Bytes32::new(hex!(
"d13767da4a8bd9520dbd9e039e68b3eb4b16fdcbb7e7755b5064840eaeb553ce"
)),
agg_sig_puzzle_amount_additional_data: Bytes32::new(hex!(
"73eea3473bd0daa28793d4bcd218ade462b634b53af97f9a01a91f3059ac75df"
)),
agg_sig_parent_amount_additional_data: Bytes32::new(hex!(
"eb7302224e77c0f269d0c8b105d4cc786775ae012ed2db49751c33c244c3f647"
)),
agg_sig_parent_puzzle_additional_data: Bytes32::new(hex!(
"ccac5983685257d50ee7b439bbb502128ddb262813dde4e4a11ac6cdfc66fa8e"
)),
max_block_cost_clvm: 11_000_000_000, cost_per_byte: 12_000,
max_coin_amount: u64::MAX,
max_generator_ref_list_size: 512,
hard_fork_height: 0,
hard_fork2_height: 0,
genesis_pre_farm_pool_puzzle_hash: Bytes32::new([0u8; 32]),
genesis_pre_farm_farmer_puzzle_hash: Bytes32::new([0u8; 32]),
slot_blocks_target: 32,
min_blocks_per_challenge_block: 16,
max_sub_slot_blocks: 128,
num_sps_sub_slot: 64,
sub_slot_iters_starting: 1 << 27,
difficulty_constant_factor: 1 << 67,
difficulty_starting: 7,
difficulty_change_max_factor: 3,
sub_epoch_blocks: 384,
epoch_blocks: 4608,
significant_bits: 8,
discriminant_size_bits: 1024,
number_zero_bits_plot_filter_v1: 9,
number_zero_bits_plot_filter_v2: 9,
min_plot_size_v1: 32,
max_plot_size_v1: 50,
plot_size_v2: 28,
sub_slot_time_target: 600,
num_sp_intervals_extra: 3,
max_future_time2: 120,
number_of_timestamps: 11,
max_vdf_witness_size: 64,
mempool_block_buffer: 10,
weight_proof_threshold: 2,
blocks_cache_size: 4608 + (128 * 4),
weight_proof_recent_blocks: 1000,
max_block_count_per_requests: 32,
pool_sub_slot_iters: 37_600_000_000,
plot_filter_128_height: 0xffff_ffff,
plot_filter_64_height: 0xffff_ffff,
plot_filter_32_height: 0xffff_ffff,
plot_v1_phase_out_epoch_bits: 8,
min_plot_strength: 2,
max_plot_strength: 32,
plot_filter_v2_first_adjustment_height: 0xffff_ffff,
plot_filter_v2_second_adjustment_height: 0xffff_ffff,
plot_filter_v2_third_adjustment_height: 0xffff_ffff,
},
};
pub const DIG_RELAY_URL: &str = "wss://relay.dig.net:443";
pub const DIG_NODE_PORT: u16 = 9778;
pub const DIG_LOCAL_HOST: &str = "dig.local";
pub const RPC_DIG_NET_URL: &str = "https://rpc.dig.net";
pub const DIG_BOOTSTRAP_PEERS: &[&str] =
&["741592c0e1e1e9b1a02d3e0bb165bfe54b7adbb5878a3c5de59893949524b68f@node-rpc.dig.net:9444"];
pub const DIG_ASSET_ID: Bytes32 = Bytes32::new(hex!(
"a406d3a9de984d03c9591c10d917593b434d5263cabe2b42f6b367df16832f81"
));
pub const DIG_TREASURY_INNER_PUZZLE_HASH: Bytes32 = Bytes32::new(hex!(
"ec7c304708c7d59c078d5ae098d0dea004decf47fa1cafebb266c10ad6466ce8"
));
pub const DIG_TREASURY_ADDRESS: &str =
"xch1a37rq3cgcl2ecpudttsf35x75qzdan68lgw2l6ajvmqs44jxdn5qv6pk3y";
pub const CHIA_L1_MAINNET_AGG_SIG_ME: [u8; 32] =
hex!("ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb");
pub const CHIA_L1_TESTNET11_AGG_SIG_ME: [u8; 32] =
hex!("37a90eb5185a9c4439a91ddc98bbadce7b4feba060d50116a067de66bf236615");
const DIG_TESTNET_GENESIS_CHALLENGE: [u8; 32] =
hex!("088c18d6b7859d885dc2f03166e862c958f74b63b6353c3df71d103b9b806c3b");
pub const DIG_TESTNET: NetworkConstants = NetworkConstants {
inner: ConsensusConstants {
genesis_challenge: Bytes32::new(DIG_TESTNET_GENESIS_CHALLENGE),
agg_sig_me_additional_data: Bytes32::new(DIG_TESTNET_GENESIS_CHALLENGE),
agg_sig_parent_additional_data: Bytes32::new(hex!(
"85b3963bdeb9848af970a9bbd1d36809ae41491ffd67aee7f27e8883936d495c"
)),
agg_sig_puzzle_additional_data: Bytes32::new(hex!(
"66aba1939e128e1465d58fde414325630e891747c1428d76ebce193cbe966301"
)),
agg_sig_amount_additional_data: Bytes32::new(hex!(
"eccab86920a6d982a68898b2dcb7c150383529fcd532fe84c693fb4592c38ae3"
)),
agg_sig_puzzle_amount_additional_data: Bytes32::new(hex!(
"eb088fad0d4caba66e29130fb07407e60a7545d035d19a188fef0855c874084e"
)),
agg_sig_parent_amount_additional_data: Bytes32::new(hex!(
"232aec0a351ba4936b04920e074aebcc621a458f6b1461c4b28c658552f2f35d"
)),
agg_sig_parent_puzzle_additional_data: Bytes32::new(hex!(
"96263ac395703ab9b3b0f0587e79185f4a9898574a28b4491015ddcf9d321873"
)),
max_block_cost_clvm: 11_000_000_000,
cost_per_byte: 12_000,
max_coin_amount: u64::MAX,
max_generator_ref_list_size: 512,
hard_fork_height: 0,
hard_fork2_height: 0,
genesis_pre_farm_pool_puzzle_hash: Bytes32::new([0u8; 32]),
genesis_pre_farm_farmer_puzzle_hash: Bytes32::new([0u8; 32]),
slot_blocks_target: 32,
min_blocks_per_challenge_block: 16,
max_sub_slot_blocks: 128,
num_sps_sub_slot: 64,
sub_slot_iters_starting: 1 << 27,
difficulty_constant_factor: 1 << 67,
difficulty_starting: 7,
difficulty_change_max_factor: 3,
sub_epoch_blocks: 384,
epoch_blocks: 4608,
significant_bits: 8,
discriminant_size_bits: 1024,
number_zero_bits_plot_filter_v1: 9,
number_zero_bits_plot_filter_v2: 9,
min_plot_size_v1: 32,
max_plot_size_v1: 50,
plot_size_v2: 28,
sub_slot_time_target: 600,
num_sp_intervals_extra: 3,
max_future_time2: 120,
number_of_timestamps: 11,
max_vdf_witness_size: 64,
mempool_block_buffer: 10,
weight_proof_threshold: 2,
blocks_cache_size: 4608 + (128 * 4),
weight_proof_recent_blocks: 1000,
max_block_count_per_requests: 32,
pool_sub_slot_iters: 37_600_000_000,
plot_filter_128_height: 0xffff_ffff,
plot_filter_64_height: 0xffff_ffff,
plot_filter_32_height: 0xffff_ffff,
plot_v1_phase_out_epoch_bits: 8,
min_plot_strength: 2,
max_plot_strength: 32,
plot_filter_v2_first_adjustment_height: 0xffff_ffff,
plot_filter_v2_second_adjustment_height: 0xffff_ffff,
plot_filter_v2_third_adjustment_height: 0xffff_ffff,
},
};
pub const DEK_SALT: &[u8] = b"dig-app:dek-salt:v1";
pub const IDENTITY_IKM_VERSION: u8 = 2;
pub const PROFILE_DEK_LABEL: &[u8] = b"dig-app:profile-dek:v2";
pub const SYMMETRIC_KEY_LEN: usize = 32;
pub const PROFILE_SEALING_X25519_LABEL: &[u8] = b"dig-app:profile-sealing-x25519:v1";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dig_relay_url_is_canonical_endpoint() {
assert_eq!(DIG_RELAY_URL, "wss://relay.dig.net:443");
}
#[test]
fn dig_relay_url_is_well_formed() {
assert!(
DIG_RELAY_URL.starts_with("wss://"),
"relay must use secure WebSocket"
);
assert!(
DIG_RELAY_URL.contains("relay.dig.net"),
"relay must point at the canonical host"
);
assert!(
DIG_RELAY_URL.ends_with(":443"),
"relay must use the live NLB public TLS port 443"
);
}
#[test]
fn dig_node_port_is_canonical() {
assert_eq!(DIG_NODE_PORT, 9778);
}
#[test]
fn dig_local_host_is_canonical() {
assert_eq!(DIG_LOCAL_HOST, "dig.local");
}
#[test]
fn rpc_dig_net_url_is_canonical() {
assert_eq!(RPC_DIG_NET_URL, "https://rpc.dig.net");
}
#[test]
fn rpc_dig_net_url_is_well_formed() {
assert!(
RPC_DIG_NET_URL.starts_with("https://"),
"the public read gateway must use HTTPS"
);
}
#[test]
fn bootstrap_peers_name_the_peer_host_not_the_read_gateway() {
assert!(
!DIG_BOOTSTRAP_PEERS.is_empty(),
"a fresh node with no bootstrap anchor has nothing to dial"
);
for entry in DIG_BOOTSTRAP_PEERS {
let authority = entry.split_once('@').expect("pinned identity").1;
let host = authority.rsplit_once(':').expect("explicit port").0;
assert_ne!(
host, "rpc.dig.net",
"{entry} dials the CloudFront read gateway, whose peer ports are closed"
);
assert!(
host.ends_with(".dig.net"),
"{entry} must anchor on a DIG-operated host"
);
}
}
#[test]
fn every_bootstrap_entry_carries_a_pinned_identity_and_an_explicit_port() {
for entry in DIG_BOOTSTRAP_PEERS {
let (peer_id, authority) = entry
.split_once('@')
.unwrap_or_else(|| panic!("{entry} carries no pinned identity"));
assert_eq!(peer_id.len(), 64, "{entry}: peer_id must be 64 hex chars");
assert!(
peer_id.chars().all(|c| c.is_ascii_hexdigit()),
"{entry}: peer_id must be hex"
);
let (host, port) = authority
.rsplit_once(':')
.unwrap_or_else(|| panic!("{entry} carries no explicit port"));
assert!(!host.is_empty(), "{entry}: empty host");
assert!(
port.parse::<u16>().is_ok(),
"{entry}: port must be a u16, got {port}"
);
}
}
#[test]
fn bootstrap_port_is_the_peer_port_not_the_local_rpc_port() {
for entry in DIG_BOOTSTRAP_PEERS {
let port: u16 = entry
.rsplit_once(':')
.expect("explicit port")
.1
.parse()
.expect("numeric port");
assert_ne!(
port, DIG_NODE_PORT,
"{entry} dials the local JSON-RPC port, not the peer port"
);
assert_eq!(port, 9444, "{entry} must dial the DIG peer port");
}
}
use sha2::{Digest, Sha256};
const AGG_SIG_OPCODES: [u8; 6] = [43, 44, 45, 46, 47, 48];
fn sha256(bytes: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(bytes);
hasher.finalize().into()
}
#[test]
fn genesis_challenges_are_non_zero() {
assert_ne!(DIG_MAINNET.genesis_challenge(), Bytes32::new([0u8; 32]));
assert_ne!(DIG_TESTNET.genesis_challenge(), Bytes32::new([0u8; 32]));
}
#[test]
fn genesis_challenges_are_the_pinned_values() {
assert_eq!(
DIG_MAINNET_GENESIS_CHALLENGE,
hex_literal::hex!("0af981862a4df51f51ec59c312315d959931d917c375730b89b9e2b0854d1abf"),
);
assert_eq!(
DIG_TESTNET_GENESIS_CHALLENGE,
sha256(b"DIG_TESTNET:genesis:v1"),
);
}
#[test]
fn mainnet_and_testnet_genesis_differ() {
assert_ne!(
DIG_MAINNET.genesis_challenge(),
DIG_TESTNET.genesis_challenge(),
);
}
#[test]
fn dig_asset_id_is_canonical() {
assert_eq!(
DIG_ASSET_ID,
Bytes32::new(hex_literal::hex!(
"a406d3a9de984d03c9591c10d917593b434d5263cabe2b42f6b367df16832f81"
)),
);
}
const EXPECTED_MAINNET_GENESIS: [u8; 32] =
hex_literal::hex!("0af981862a4df51f51ec59c312315d959931d917c375730b89b9e2b0854d1abf");
const EXPECTED_TESTNET_GENESIS: [u8; 32] =
hex_literal::hex!("088c18d6b7859d885dc2f03166e862c958f74b63b6353c3df71d103b9b806c3b");
#[test]
fn mainnet_genesis_equals_independent_literal() {
assert_eq!(DIG_MAINNET_GENESIS_CHALLENGE, EXPECTED_MAINNET_GENESIS);
assert_eq!(
DIG_MAINNET.genesis_challenge(),
Bytes32::new(EXPECTED_MAINNET_GENESIS),
);
}
#[test]
fn testnet_genesis_equals_independent_literal() {
assert_eq!(DIG_TESTNET_GENESIS_CHALLENGE, EXPECTED_TESTNET_GENESIS);
assert_eq!(
DIG_TESTNET.genesis_challenge(),
Bytes32::new(EXPECTED_TESTNET_GENESIS),
);
}
#[test]
fn mainnet_genesis_is_not_an_obvious_placeholder() {
let g = DIG_MAINNET_GENESIS_CHALLENGE;
assert_ne!(g, [0u8; 32], "genesis must not be all-zeros (0.1.0 stub)");
assert_ne!(g, [0xFFu8; 32], "genesis must not be all-0xFF");
let counting: [u8; 32] = core::array::from_fn(|i| i as u8);
assert_ne!(g, counting, "genesis must not be a counting pattern");
assert!(
g.iter().any(|&b| b != g[0]),
"genesis must not be a single repeated byte",
);
}
#[test]
fn mainnet_agg_sig_domains_equal_independent_literal_derivation() {
let c = DIG_MAINNET.consensus();
assert_eq!(
c.agg_sig_me_additional_data,
Bytes32::new(EXPECTED_MAINNET_GENESIS),
"AGG_SIG_ME must equal the genesis literal directly",
);
let expected: Vec<Bytes32> = AGG_SIG_OPCODES
.iter()
.map(|&op| {
let mut preimage = EXPECTED_MAINNET_GENESIS.to_vec();
preimage.push(op);
Bytes32::new(sha256(&preimage))
})
.collect();
assert_eq!(c.agg_sig_parent_additional_data, expected[0]);
assert_eq!(c.agg_sig_puzzle_additional_data, expected[1]);
assert_eq!(c.agg_sig_amount_additional_data, expected[2]);
assert_eq!(c.agg_sig_puzzle_amount_additional_data, expected[3]);
assert_eq!(c.agg_sig_parent_amount_additional_data, expected[4]);
assert_eq!(c.agg_sig_parent_puzzle_additional_data, expected[5]);
}
#[test]
fn testnet_agg_sig_domains_equal_independent_literal_derivation() {
let c = DIG_TESTNET.consensus();
assert_eq!(
c.agg_sig_me_additional_data,
Bytes32::new(EXPECTED_TESTNET_GENESIS),
);
let expected: Vec<Bytes32> = AGG_SIG_OPCODES
.iter()
.map(|&op| {
let mut preimage = EXPECTED_TESTNET_GENESIS.to_vec();
preimage.push(op);
Bytes32::new(sha256(&preimage))
})
.collect();
assert_eq!(c.agg_sig_parent_additional_data, expected[0]);
assert_eq!(c.agg_sig_puzzle_additional_data, expected[1]);
assert_eq!(c.agg_sig_amount_additional_data, expected[2]);
assert_eq!(c.agg_sig_puzzle_amount_additional_data, expected[3]);
assert_eq!(c.agg_sig_parent_amount_additional_data, expected[4]);
assert_eq!(c.agg_sig_parent_puzzle_additional_data, expected[5]);
}
#[test]
fn chia_l1_agg_sig_me_constants_are_the_pinned_values() {
assert_eq!(
CHIA_L1_MAINNET_AGG_SIG_ME,
hex_literal::hex!("ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb"),
);
assert_eq!(
CHIA_L1_TESTNET11_AGG_SIG_ME,
hex_literal::hex!("37a90eb5185a9c4439a91ddc98bbadce7b4feba060d50116a067de66bf236615"),
);
}
#[test]
fn chia_l1_agg_sig_me_matches_chia_sdk_types() {
use chia_sdk_types::{MAINNET_CONSTANTS, TESTNET11_CONSTANTS};
assert_eq!(
CHIA_L1_MAINNET_AGG_SIG_ME.as_slice(),
MAINNET_CONSTANTS.agg_sig_me_additional_data.as_ref(),
);
assert_eq!(
CHIA_L1_TESTNET11_AGG_SIG_ME.as_slice(),
TESTNET11_CONSTANTS.agg_sig_me_additional_data.as_ref(),
);
}
#[test]
fn chia_l1_agg_sig_me_differs_from_dig_l2_genesis() {
assert_ne!(
Bytes32::new(CHIA_L1_MAINNET_AGG_SIG_ME),
DIG_MAINNET.genesis_challenge(),
);
assert_ne!(
Bytes32::new(CHIA_L1_TESTNET11_AGG_SIG_ME),
DIG_TESTNET.genesis_challenge(),
);
}
#[test]
fn dig_treasury_inner_puzzle_hash_is_canonical() {
assert_eq!(
DIG_TREASURY_INNER_PUZZLE_HASH,
Bytes32::new(hex_literal::hex!(
"ec7c304708c7d59c078d5ae098d0dea004decf47fa1cafebb266c10ad6466ce8"
)),
);
}
#[test]
fn dig_treasury_address_is_canonical() {
assert_eq!(
DIG_TREASURY_ADDRESS,
"xch1a37rq3cgcl2ecpudttsf35x75qzdan68lgw2l6ajvmqs44jxdn5qv6pk3y",
);
}
#[test]
fn dig_treasury_address_decodes_to_inner_puzzle_hash() {
use bech32::Hrp;
let (hrp, data) = bech32::decode(DIG_TREASURY_ADDRESS).expect("valid bech32m");
assert_eq!(hrp, Hrp::parse("xch").unwrap(), "HRP must be xch");
assert_eq!(
data.as_slice(),
DIG_TREASURY_INNER_PUZZLE_HASH.to_bytes(),
"address must decode to the pinned inner puzzle hash",
);
}
#[test]
fn dek_salt_is_the_pinned_value() {
assert_eq!(DEK_SALT, b"dig-app:dek-salt:v1");
}
#[test]
fn identity_ikm_version_is_the_pinned_value() {
assert_eq!(IDENTITY_IKM_VERSION, 2);
}
#[test]
fn profile_dek_label_is_the_pinned_value() {
assert_eq!(PROFILE_DEK_LABEL, b"dig-app:profile-dek:v2");
}
#[test]
fn symmetric_key_len_is_the_pinned_value() {
assert_eq!(SYMMETRIC_KEY_LEN, 32);
}
#[test]
fn profile_sealing_x25519_label_is_the_pinned_value() {
assert_eq!(
PROFILE_SEALING_X25519_LABEL,
b"dig-app:profile-sealing-x25519:v1"
);
}
#[test]
fn profile_sealing_label_is_domain_separated_from_dek_label() {
assert_ne!(PROFILE_SEALING_X25519_LABEL, PROFILE_DEK_LABEL);
}
#[test]
fn agg_sig_additional_data_matches_derivation_rule() {
for net in [&DIG_MAINNET, &DIG_TESTNET] {
let genesis = net.genesis_challenge();
assert_eq!(net.agg_sig_me_additional_data(), genesis);
let c = net.consensus();
let derived: Vec<Bytes32> = AGG_SIG_OPCODES
.iter()
.map(|&op| {
let mut preimage = genesis.as_ref().to_vec();
preimage.push(op);
Bytes32::new(sha256(&preimage))
})
.collect();
assert_eq!(c.agg_sig_parent_additional_data, derived[0]);
assert_eq!(c.agg_sig_puzzle_additional_data, derived[1]);
assert_eq!(c.agg_sig_amount_additional_data, derived[2]);
assert_eq!(c.agg_sig_puzzle_amount_additional_data, derived[3]);
assert_eq!(c.agg_sig_parent_amount_additional_data, derived[4]);
assert_eq!(c.agg_sig_parent_puzzle_additional_data, derived[5]);
}
}
}