mina_core/
chain_id.rs

1//! Chain identifier and network discrimination for Mina Protocol.
2//!
3//! This module provides the [`ChainId`] type, which uniquely identifies
4//! different Mina blockchain networks (Mainnet, Devnet, etc.) and ensures peers
5//! only connect to compatible networks. The chain ID is computed from protocol
6//! parameters, genesis state, and constraint system digests to create a
7//! deterministic network identifier.
8//!
9//! ## Purpose
10//!
11//! Chain IDs serve multiple critical functions in the Mina protocol:
12//!
13//! - **Network Isolation**: Prevents nodes from different networks (e.g.,
14//!   mainnet vs devnet) from connecting to each other
15//! - **Protocol Compatibility**: Ensures all peers use the same protocol
16//!   parameters
17//! - **Security**: Used in cryptographic operations and peer authentication
18//! - **Private Network Support**: Enables creation of isolated test networks
19//!
20//! ## Chain ID Computation
21//!
22//! The chain ID is a 32-byte Blake2b hash computed from:
23//!
24//! - **Genesis State Hash**: The hash of the initial blockchain state
25//! - **Constraint System Digests**: Hashes of the SNARK constraint systems
26//! - **Genesis Constants**: Protocol parameters like slot timing and consensus
27//!   settings
28//! - **Protocol Versions**: Transaction and network protocol version numbers
29//! - **Transaction Pool Size**: Maximum transaction pool configuration
30//!
31//! This ensures that any change to fundamental protocol parameters results in a
32//! different chain ID, preventing incompatible nodes from connecting.
33//!
34//! ## Network Identifiers
35//!
36//! Mina includes predefined chain IDs for official networks:
37//!
38//! - [`MAINNET_CHAIN_ID`]: The production Mina blockchain
39//! - [`DEVNET_CHAIN_ID`]: The development/testing blockchain
40//!
41//! Custom networks can compute their own chain IDs using [`ChainId::compute()`].
42//!
43//! ## Usage in Networking
44//!
45//! Chain IDs are used throughout Mina's networking stack:
46//!
47//! - **Peer Discovery**: Nodes advertise their chain ID to find compatible
48//!   peers
49//! - **Connection Authentication**: WebRTC and libp2p connections verify chain
50//!   ID compatibility
51//! - **Private Networks**: The [`preshared_key()`](ChainId::preshared_key)
52//!   method generates cryptographic keys for private network isolation
53//!
54//! ## Example
55//!
56//! ```rust
57//! use mina_core::ChainId;
58//!
59//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
60//! // Use predefined network
61//! let mainnet_id = mina_core::MAINNET_CHAIN_ID;
62//! println!("Mainnet ID: {}", mainnet_id);
63//!
64//! // Parse from hex string
65//! let chain_id = ChainId::from_hex("a7351abc7ddf2ea92d1b38cc8e636c271c1dfd2c081c637f62ebc2af34eb7cc1")?;
66//!
67//! // Generate preshared key for private networking
68//! let psk = chain_id.preshared_key();
69//! # Ok(())
70//! # }
71//! ```
72
73use mina_p2p_messages::v2::{
74    MinaBaseProtocolConstantsCheckedValueStableV1, StateHash, UnsignedExtendedUInt32StableV1,
75};
76use multihash::{Blake2b256, Hasher};
77use time::{macros::format_description, OffsetDateTime};
78
79use std::{
80    fmt::{self, Debug, Display, Formatter},
81    io::{Read, Write},
82};
83
84use binprot::{BinProtRead, BinProtWrite};
85use serde::{Deserialize, Deserializer, Serialize, Serializer};
86
87/// Unique identifier for a Mina blockchain network.
88///
89/// `ChainId` is a 32-byte cryptographic hash that uniquely identifies a
90/// specific Mina blockchain network. It ensures network isolation by preventing
91/// nodes from different chains (mainnet, devnet, custom testnets) from
92/// connecting to each other.
93///
94/// ## Security Properties
95///
96/// The chain ID provides several security guarantees:
97///
98/// - **Deterministic**: Always produces the same ID for identical protocol
99///   parameters
100/// - **Collision Resistant**: Uses Blake2b hashing to prevent ID conflicts
101/// - **Tamper Evident**: Any change to protocol parameters changes the chain ID
102/// - **Network Isolation**: Incompatible networks cannot connect accidentally
103///
104/// ## Computation Method
105///
106/// Chain IDs are computed using [`ChainId::compute()`] from these inputs:
107///
108/// 1. **Constraint System Digests**: MD5 hashes of SNARK constraint systems
109/// 2. **Genesis State Hash**: Hash of the initial blockchain state
110/// 3. **Genesis Constants**: Protocol timing and consensus parameters
111/// 4. **Protocol Versions**: Transaction and network protocol versions
112/// 5. **Transaction Pool Size**: Maximum mempool configuration
113///
114/// The computation uses Blake2b-256 to hash these components in a specific
115/// order, ensuring reproducible results across different implementations.
116///
117/// ## Network Usage
118///
119/// Chain IDs are used throughout the networking stack:
120///
121/// - **Peer Discovery**: Nodes broadcast their chain ID during discovery
122/// - **Connection Handshakes**: WebRTC offers include chain ID for validation
123/// - **Private Networks**: [`preshared_key()`](Self::preshared_key) generates
124///   libp2p private network keys
125/// - **Protocol Compatibility**: Ensures all peers use compatible protocol
126///   versions
127///
128/// ## Serialization Formats
129///
130/// Chain IDs support multiple serialization formats:
131///
132/// - **Hex String**: Human-readable format for configuration files
133/// - **Binary**: 32-byte array for network transmission
134/// - **JSON**: String representation for APIs and debugging
135///
136/// ## Example Usage
137///
138/// ```rust
139/// use mina_core::{ChainId, MAINNET_CHAIN_ID};
140///
141/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
142/// // Use predefined mainnet ID
143/// let mainnet = MAINNET_CHAIN_ID;
144/// println!("Mainnet: {}", mainnet.to_hex());
145///
146/// // Parse from configuration
147/// let custom_id = ChainId::from_hex("29936104443aaf264a7f0192ac64b1c7173198c1ed404c1bcff5e562e05eb7f6")?;
148///
149/// // Generate private network key
150/// let psk = mainnet.preshared_key();
151/// # Ok(())
152/// # }
153/// ```
154#[derive(Clone, PartialEq, Eq)]
155pub struct ChainId([u8; 32]);
156
157fn md5_hash(data: u8) -> String {
158    let mut hasher = md5::Context::new();
159    hasher.consume(data.to_string().as_bytes());
160    let hash: Md5 = *hasher.compute();
161    hex::encode(hash)
162}
163
164type Md5 = [u8; 16];
165
166fn hash_genesis_constants(
167    constants: &MinaBaseProtocolConstantsCheckedValueStableV1,
168    tx_pool_max_size: &UnsignedExtendedUInt32StableV1,
169) -> [u8; 32] {
170    let mut hasher = Blake2b256::default();
171    let genesis_timestamp = OffsetDateTime::from_unix_timestamp_nanos(
172        (constants.genesis_state_timestamp.as_u64() * 1000000) as i128,
173    )
174    .unwrap();
175    let time_format =
176        format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:6]Z");
177    hasher.update(constants.k.to_string().as_bytes());
178    hasher.update(constants.slots_per_epoch.to_string().as_bytes());
179    hasher.update(constants.slots_per_sub_window.to_string().as_bytes());
180    hasher.update(constants.delta.to_string().as_bytes());
181    hasher.update(tx_pool_max_size.to_string().as_bytes());
182    hasher.update(genesis_timestamp.format(&time_format).unwrap().as_bytes());
183    hasher.finalize().try_into().unwrap()
184}
185
186impl ChainId {
187    /// Computes a chain ID from protocol parameters and network configuration.
188    ///
189    /// This method creates a deterministic 32-byte chain identifier by hashing
190    /// all the fundamental parameters that define a Mina blockchain network.
191    /// Any change to these parameters will result in a different chain ID,
192    /// ensuring network isolation and protocol compatibility.
193    ///
194    /// # Parameters
195    ///
196    /// * `constraint_system_digests` - MD5 hashes of the SNARK constraint
197    ///   systems used for transaction and block verification
198    /// * `genesis_state_hash` - Hash of the initial blockchain state
199    /// * `genesis_constants` - Protocol constants including timing parameters,
200    ///   consensus settings, and economic parameters
201    /// * `protocol_transaction_version` - Version number of the transaction
202    ///   protocol
203    /// * `protocol_network_version` - Version number of the network protocol
204    /// * `tx_max_pool_size` - Maximum number of transactions in the mempool
205    ///
206    /// # Returns
207    ///
208    /// A new `ChainId` representing the unique identifier for this network
209    /// configuration.
210    ///
211    /// # Algorithm
212    ///
213    /// The computation process:
214    ///
215    /// 1. Hash all constraint system digests into a combined string
216    /// 2. Hash the genesis constants with transaction pool size
217    /// 3. Create Blake2b-256 hash of:
218    ///    - Genesis state hash (as string)
219    ///    - Combined constraint system hash
220    ///    - Genesis constants hash (as hex)
221    ///    - Protocol transaction version (as MD5 hash)
222    ///    - Protocol network version (as MD5 hash)
223    ///
224    /// # Example
225    ///
226    /// ```rust
227    /// use mina_core::ChainId;
228    /// use mina_p2p_messages::v2::UnsignedExtendedUInt32StableV1;
229    ///
230    /// # // Use actual devnet values for the example
231    /// # let constraint_digests = mina_core::network::devnet::CONSTRAINT_SYSTEM_DIGESTS;
232    /// # let genesis_hash: mina_p2p_messages::v2::StateHash =
233    /// #     "3NL93SipJfAMNDBRfQ8Uo8LPovC74mnJZfZYB5SK7mTtkL72dsPx".parse().unwrap();
234    /// # let protocol_constants = mina_core::constants::PROTOCOL_CONSTANTS.clone();
235    /// let chain_id = ChainId::compute(
236    ///     &constraint_digests,
237    ///     &genesis_hash,
238    ///     &protocol_constants,
239    ///     1,  // transaction version
240    ///     1,  // network version
241    ///     &UnsignedExtendedUInt32StableV1::from(3000),
242    /// );
243    /// ```
244    pub fn compute(
245        constraint_system_digests: &[Md5],
246        genesis_state_hash: &StateHash,
247        genesis_constants: &MinaBaseProtocolConstantsCheckedValueStableV1,
248        protocol_transaction_version: u8,
249        protocol_network_version: u8,
250        tx_max_pool_size: &UnsignedExtendedUInt32StableV1,
251    ) -> ChainId {
252        let mut hasher = Blake2b256::default();
253        let constraint_system_hash = constraint_system_digests
254            .iter()
255            .map(hex::encode)
256            .reduce(|acc, el| acc + &el)
257            .unwrap_or_default();
258        let genesis_constants_hash = hash_genesis_constants(genesis_constants, tx_max_pool_size);
259        hasher.update(genesis_state_hash.to_string().as_bytes());
260        hasher.update(constraint_system_hash.to_string().as_bytes());
261        hasher.update(hex::encode(genesis_constants_hash).as_bytes());
262        hasher.update(md5_hash(protocol_transaction_version).as_bytes());
263        hasher.update(md5_hash(protocol_network_version).as_bytes());
264        ChainId(hasher.finalize().try_into().unwrap())
265    }
266
267    /// Generates a preshared key for libp2p private networking.
268    ///
269    /// This method creates a cryptographic key used by libp2p's private network
270    /// (Pnet) protocol to ensure only nodes with the same chain ID can connect.
271    /// The preshared key provides an additional layer of network isolation
272    /// beyond basic chain ID validation.
273    ///
274    /// # Algorithm
275    ///
276    /// The preshared key is computed as:
277    /// ```text
278    /// Blake2b-256("/coda/0.0.1/" + chain_id_hex)
279    /// ```
280    ///
281    /// The "/coda/0.0.1/" prefix is a protocol identifier that ensures the
282    /// preshared key is unique to the Mina protocol and not accidentally
283    /// compatible with other systems.
284    ///
285    /// # Returns
286    ///
287    /// A 32-byte array containing the preshared key for this chain ID.
288    ///
289    /// # Usage
290    ///
291    /// This key is used to configure libp2p's private network transport,
292    /// which encrypts all network traffic and prevents unauthorized nodes
293    /// from joining the network even if they know peer addresses.
294    ///
295    /// # Example
296    ///
297    /// ```rust
298    /// use mina_core::MAINNET_CHAIN_ID;
299    ///
300    /// let psk = MAINNET_CHAIN_ID.preshared_key();
301    /// // Use psk to configure libp2p Pnet transport
302    /// ```
303    pub fn preshared_key(&self) -> [u8; 32] {
304        let mut hasher = Blake2b256::default();
305        hasher.update(b"/coda/0.0.1/");
306        hasher.update(self.to_hex().as_bytes());
307        let hash = hasher.finalize();
308        let mut psk_fixed: [u8; 32] = Default::default();
309        psk_fixed.copy_from_slice(hash.as_ref());
310        psk_fixed
311    }
312
313    /// Converts the chain ID to a hexadecimal string representation.
314    ///
315    /// This method creates a lowercase hex string of the 32-byte chain ID,
316    /// suitable for display, logging, configuration files, and JSON
317    /// serialization.
318    ///
319    /// # Returns
320    ///
321    /// A 64-character hexadecimal string representing the chain ID.
322    ///
323    /// # Example
324    ///
325    /// ```rust
326    /// use mina_core::MAINNET_CHAIN_ID;
327    ///
328    /// let hex_id = MAINNET_CHAIN_ID.to_hex();
329    /// assert_eq!(hex_id.len(), 64);
330    /// println!("Mainnet ID: {}", hex_id);
331    /// ```
332    pub fn to_hex(&self) -> String {
333        hex::encode(self.0)
334    }
335
336    /// Parses a chain ID from a hexadecimal string.
337    ///
338    /// This method converts a hex string back into a `ChainId` instance.
339    /// The input string must represent exactly 32 bytes (64 hex characters).
340    /// Case-insensitive parsing is supported.
341    ///
342    /// # Parameters
343    ///
344    /// * `s` - A hexadecimal string representing the chain ID
345    ///
346    /// # Returns
347    ///
348    /// * `Ok(ChainId)` if the string is valid 64-character hex
349    /// * `Err(hex::FromHexError)` if the string is invalid or wrong length
350    ///
351    /// # Errors
352    ///
353    /// This method returns an error if:
354    /// - The string contains non-hexadecimal characters
355    /// - The string length is not exactly 64 characters
356    /// - The string represents fewer than 32 bytes
357    ///
358    /// # Example
359    ///
360    /// ```rust
361    /// use mina_core::ChainId;
362    ///
363    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
364    /// let chain_id = ChainId::from_hex(
365    ///     "a7351abc7ddf2ea92d1b38cc8e636c271c1dfd2c081c637f62ebc2af34eb7cc1"
366    /// )?;
367    /// # Ok(())
368    /// # }
369    /// ```
370    pub fn from_hex(s: &str) -> Result<ChainId, hex::FromHexError> {
371        let h = hex::decode(s)?;
372        let bs = h[..32]
373            .try_into()
374            .or(Err(hex::FromHexError::InvalidStringLength))?;
375        Ok(ChainId(bs))
376    }
377
378    /// Creates a chain ID from raw bytes.
379    ///
380    /// This method constructs a `ChainId` from a byte slice, taking the first
381    /// 32 bytes as the chain identifier. If the input has fewer than 32 bytes,
382    /// the remaining bytes are zero-padded.
383    ///
384    /// # Parameters
385    ///
386    /// * `bytes` - A byte slice containing at least 32 bytes
387    ///
388    /// # Returns
389    ///
390    /// A new `ChainId` instance created from the input bytes.
391    ///
392    /// # Panics
393    ///
394    /// This method will panic if the input slice has fewer than 32 bytes.
395    ///
396    /// # Example
397    ///
398    /// ```rust
399    /// use mina_core::ChainId;
400    ///
401    /// let bytes = [0u8; 32]; // All zeros for testing
402    /// let chain_id = ChainId::from_bytes(&bytes);
403    /// ```
404    pub fn from_bytes(bytes: &[u8]) -> ChainId {
405        let mut arr = [0u8; 32];
406        arr.copy_from_slice(&bytes[..32]);
407        ChainId(arr)
408    }
409}
410
411impl BinProtWrite for ChainId {
412    fn binprot_write<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
413        w.write_all(&self.0)
414    }
415}
416
417impl BinProtRead for ChainId {
418    fn binprot_read<R: Read + ?Sized>(r: &mut R) -> Result<Self, binprot::Error>
419    where
420        Self: Sized,
421    {
422        let mut bytes = [0; 32];
423        r.read_exact(&mut bytes)?;
424        Ok(Self(bytes))
425    }
426}
427
428impl Serialize for ChainId {
429    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
430        serializer.serialize_str(&self.to_hex())
431    }
432}
433
434impl<'de> Deserialize<'de> for ChainId {
435    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
436        let s = String::deserialize(deserializer)?;
437        ChainId::from_hex(&s).map_err(serde::de::Error::custom)
438    }
439}
440
441impl AsRef<[u8]> for ChainId {
442    fn as_ref(&self) -> &[u8] {
443        &self.0
444    }
445}
446
447impl Display for ChainId {
448    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
449        write!(f, "{}", self.to_hex())?;
450        Ok(())
451    }
452}
453
454impl Debug for ChainId {
455    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
456        write!(f, "ChainId({})", self)
457    }
458}
459
460/// Chain ID for the Mina development network (Devnet).
461///
462/// This is the official chain identifier for Mina's development and testing
463/// network.
464/// Devnet is used for:
465///
466/// - Protocol development and testing
467/// - New feature validation before mainnet deployment
468/// - Developer experimentation and testing
469/// - Stress testing and performance evaluation
470///
471/// The devnet chain ID ensures that devnet nodes cannot accidentally connect to
472/// mainnet, providing network isolation for development activities.
473///
474/// # Hex Representation
475///
476/// `29936104443aaf264a7f0192ac64b1c7173198c1ed404c1bcff5e562e05eb7f6`
477///
478/// # Usage
479///
480/// ```rust
481/// use mina_core::DEVNET_CHAIN_ID;
482///
483/// println!("Devnet ID: {}", DEVNET_CHAIN_ID.to_hex());
484/// let psk = DEVNET_CHAIN_ID.preshared_key();
485/// ```
486pub const DEVNET_CHAIN_ID: ChainId = ChainId([
487    0x29, 0x93, 0x61, 0x04, 0x44, 0x3a, 0xaf, 0x26, 0x4a, 0x7f, 0x01, 0x92, 0xac, 0x64, 0xb1, 0xc7,
488    0x17, 0x31, 0x98, 0xc1, 0xed, 0x40, 0x4c, 0x1b, 0xcf, 0xf5, 0xe5, 0x62, 0xe0, 0x5e, 0xb7, 0xf6,
489]);
490
491/// Chain ID for the Mina production network (Mainnet).
492///
493/// This is the official chain identifier for Mina's production blockchain
494/// network. Mainnet is the live network where real MINA tokens are transacted
495/// and the blockchain consensus operates for production use.
496///
497/// Key characteristics:
498///
499/// - **Production Ready**: Used for real-world transactions and value transfer
500/// - **Consensus Network**: Participates in the live Mina protocol consensus
501/// - **Economic Security**: Protected by real economic incentives and staking
502/// - **Finality**: Transactions have real-world financial consequences
503///
504/// The mainnet chain ID ensures network isolation from test networks and
505/// prevents accidental cross-network connections that could compromise security.
506///
507/// # Hex Representation
508///
509/// `a7351abc7ddf2ea92d1b38cc8e636c271c1dfd2c081c637f62ebc2af34eb7cc1`
510///
511/// # Usage
512///
513/// ```rust
514/// use mina_core::MAINNET_CHAIN_ID;
515///
516/// println!("Mainnet ID: {}", MAINNET_CHAIN_ID.to_hex());
517/// let psk = MAINNET_CHAIN_ID.preshared_key();
518/// ```
519pub const MAINNET_CHAIN_ID: ChainId = ChainId([
520    0xa7, 0x35, 0x1a, 0xbc, 0x7d, 0xdf, 0x2e, 0xa9, 0x2d, 0x1b, 0x38, 0xcc, 0x8e, 0x63, 0x6c, 0x27,
521    0x1c, 0x1d, 0xfd, 0x2c, 0x08, 0x1c, 0x63, 0x7f, 0x62, 0xeb, 0xc2, 0xaf, 0x34, 0xeb, 0x7c, 0xc1,
522]);
523
524#[cfg(test)]
525mod test {
526    use time::format_description::well_known::Rfc3339;
527
528    use super::*;
529    use crate::constants::*;
530
531    #[test]
532    fn test_devnet_chain_id() {
533        // First block after fork: https://devnet.minaexplorer.com/block/3NL93SipJfAMNDBRfQ8Uo8LPovC74mnJZfZYB5SK7mTtkL72dsPx
534        let genesis_state_hash = "3NL93SipJfAMNDBRfQ8Uo8LPovC74mnJZfZYB5SK7mTtkL72dsPx"
535            .parse()
536            .unwrap();
537
538        let mut protocol_constants = PROTOCOL_CONSTANTS.clone();
539        protocol_constants.genesis_state_timestamp =
540            OffsetDateTime::parse("2024-04-09T21:00:00Z", &Rfc3339)
541                .unwrap()
542                .into();
543
544        // Compute the chain id for the Devnet network and compare it the real one.
545        let chain_id = ChainId::compute(
546            crate::network::devnet::CONSTRAINT_SYSTEM_DIGESTS.as_slice(),
547            &genesis_state_hash,
548            &protocol_constants,
549            PROTOCOL_TRANSACTION_VERSION,
550            PROTOCOL_NETWORK_VERSION,
551            &UnsignedExtendedUInt32StableV1::from(TX_POOL_MAX_SIZE),
552        );
553        assert_eq!(chain_id, DEVNET_CHAIN_ID);
554    }
555
556    #[test]
557    fn test_mainnet_chain_id() {
558        // First block after fork: https://www.minaexplorer.com/block/3NK4BpDSekaqsG6tx8Nse2zJchRft2JpnbvMiog55WCr5xJZaKeP
559        let genesis_state_hash = "3NK4BpDSekaqsG6tx8Nse2zJchRft2JpnbvMiog55WCr5xJZaKeP"
560            .parse()
561            .unwrap();
562
563        let mut protocol_constants = PROTOCOL_CONSTANTS.clone();
564        protocol_constants.genesis_state_timestamp =
565            OffsetDateTime::parse("2024-06-05T00:00:00Z", &Rfc3339)
566                .unwrap()
567                .into();
568
569        // Compute the chain id for the Mainnet network and compare it the real one.
570        let chain_id = ChainId::compute(
571            crate::network::mainnet::CONSTRAINT_SYSTEM_DIGESTS.as_slice(),
572            &genesis_state_hash,
573            &protocol_constants,
574            PROTOCOL_TRANSACTION_VERSION,
575            PROTOCOL_NETWORK_VERSION,
576            &UnsignedExtendedUInt32StableV1::from(TX_POOL_MAX_SIZE),
577        );
578        assert_eq!(chain_id, MAINNET_CHAIN_ID);
579    }
580
581    #[test]
582    fn test_devnet_chain_id_as_hex() {
583        assert_eq!(
584            DEVNET_CHAIN_ID.to_hex(),
585            "29936104443aaf264a7f0192ac64b1c7173198c1ed404c1bcff5e562e05eb7f6"
586        );
587    }
588
589    #[test]
590    fn test_mainnet_chain_id_as_hex() {
591        assert_eq!(
592            MAINNET_CHAIN_ID.to_hex(),
593            "a7351abc7ddf2ea92d1b38cc8e636c271c1dfd2c081c637f62ebc2af34eb7cc1"
594        );
595    }
596}