Skip to main content

dig_store/
types.rs

1//! The shared identifier + value types of the store surface.
2//!
3//! The coin/identity types are re-exported VERBATIM from `dig-merkle` (which re-exports the
4//! `chia-wallet-sdk` byte-source-of-truth) so a consumer depends on ONE canonical shape and never a
5//! shadow copy that could byte-drift:
6//!
7//! - [`Bytes32`] — a 32-byte identifier (a `store_id` / `launcher_id`, a merkle root, a DID id);
8//! - [`Coin`] / [`CoinSpend`] — the Chia coin + confirmed spend;
9//! - [`DataStore`] / [`DigDataStoreMetadata`] — the hydrated DataLayer coin + its on-chain metadata;
10//! - [`DidRef`] — a reference to an owning DID by its launcher id;
11//! - [`MerkleCoinSpend`] — the unsigned result of a lifecycle operation (coin spends + child store);
12//! - [`Proof`] / [`LineageProof`] — a singleton's eve-or-lineage proof and the lineage proof a child
13//!   spend must carry (see [`crate::child_lineage_proof`]);
14//! - [`DelegatedPuzzle`] — an admin/writer/oracle delegated puzzle a store may carry (SPEC §5).
15//!
16//! Two `dig-store`-owned view types are added here:
17//!
18//! - [`RootHistory`] — the ordered list of merkle roots a store has anchored across its generations,
19//!   produced by the on-chain lineage walk (SPEC §5);
20//! - [`CapsuleIdentity`] — the `(store_id, root_hash)` a capsule declares, recovered OFF-CHAIN from a
21//!   compiled `.dig` module's bytes (SPEC §5/§11). It is the `dig-store`-native view (canonical
22//!   [`Bytes32`]) of a `dig_capsule::capsule::Capsule`, so the whole store surface speaks ONE byte
23//!   type rather than exposing `dig-capsule`'s separate `Bytes32`.
24
25pub use dig_merkle::{
26    Bytes32, Coin, CoinSpend, DataStore, DelegatedPuzzle, DidRef, DigDataStoreMetadata,
27    LineageProof, MerkleCoinSpend, Proof,
28};
29
30/// The ordered history of merkle roots a store has anchored, oldest first.
31///
32/// Each entry is proven on chain by walking the singleton's lineage from the launcher forward (NC-9,
33/// SPEC §5). The last element is the latest root. A live store is never empty (the mint anchors
34/// generation 0); a fully-melted store's history still lists every root it anchored while live.
35#[derive(Debug, Clone, PartialEq, Eq, Default)]
36pub struct RootHistory {
37    /// The anchored roots, oldest → newest.
38    pub roots: Vec<Bytes32>,
39}
40
41impl RootHistory {
42    /// The most recently anchored root, if any.
43    pub fn latest(&self) -> Option<Bytes32> {
44        self.roots.last().copied()
45    }
46
47    /// The number of generations (root anchorings) the store has had.
48    pub fn generation_count(&self) -> usize {
49        self.roots.len()
50    }
51}
52
53/// The identity a capsule declares: one immutable store generation, the pair `(store_id, root_hash)`.
54///
55/// Recovered OFF-CHAIN from a compiled `.dig` module's bytes by [`crate::get_capsule_identity`] /
56/// [`crate::open_capsule`] (SPEC §5/§11). This is `dig-store`'s canonical-[`Bytes32`] view of a
57/// `dig_capsule::capsule::Capsule`.
58///
59/// # `store_id` is NOT self-verified
60///
61/// `root_hash` is proven internally consistent by the reader (it recomputes the merkle root from the
62/// module's committed leaves and rejects a forged one). `store_id`, however, is the store's on-chain
63/// Chia launcher id, baked into the module at compile time and NOT self-verifiable from the module
64/// bytes alone — nothing in the bytes binds them to that launcher. Treat a `store_id` from
65/// [`crate::get_capsule_identity`] as a CLAIM until cross-checked against a trusted anchor (the URN you
66/// resolved, the on-chain singleton, or a verified `ChainSource`). [`crate::open_capsule`] performs
67/// that cross-check for you.
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69pub struct CapsuleIdentity {
70    /// The store's on-chain launcher id (a CLAIM until cross-checked — see the type docs).
71    pub store_id: Bytes32,
72    /// The merkle root of this capsule generation, proven internally consistent by the reader.
73    pub root_hash: Bytes32,
74}
75
76impl CapsuleIdentity {
77    /// The capsule URN `urn:dig:chia:<store_id>:<root_hash>` pinning this exact generation.
78    pub fn capsule_urn(&self) -> String {
79        crate::urn::capsule_urn(self.store_id, self.root_hash)
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    fn id(b: u8) -> Bytes32 {
88        Bytes32::new([b; 32])
89    }
90
91    #[test]
92    fn capsule_identity_formats_its_pinning_urn() {
93        let identity = CapsuleIdentity {
94            store_id: id(0xaa),
95            root_hash: id(0xbb),
96        };
97        assert_eq!(
98            identity.capsule_urn(),
99            format!("urn:dig:chia:{}:{}", "aa".repeat(32), "bb".repeat(32))
100        );
101    }
102
103    #[test]
104    fn root_history_reports_latest_and_generation_count() {
105        let empty = RootHistory::default();
106        assert_eq!(empty.latest(), None);
107        assert_eq!(empty.generation_count(), 0);
108
109        let history = RootHistory {
110            roots: vec![id(1), id(2), id(3)],
111        };
112        assert_eq!(history.latest(), Some(id(3)));
113        assert_eq!(history.generation_count(), 3);
114    }
115}