Skip to main content

ant_protocol/
pointer.rs

1//! Pointers — paid mutable references with an immutable owner.
2//!
3//! A pointer is a mutable, owner-signed reference stored at an address derived
4//! from its owner's public key. Unlike a
5//! [`DataChunk`](crate::data_types::DataChunk) its address is **not** its
6//! content hash: the address is fixed for the pointer's life while the bytes
7//! under it change with every update.
8//!
9//! The owner *key* is fixed at creation. There is no lineage, no certificates
10//! and no key rotation: a former owner keeps its key forever, so ownership
11//! cannot be re-keyed by a local rule. An earlier draft of this module
12//! implemented transfer through a genesis object and a chain of transfer
13//! certificates; it was reviewed and withdrawn. See `ADR-0016` in `ant-node`.
14//!
15//! That choice is what lets the design be this small: the owner key is inlined
16//! in the record, so validating a pointer needs nothing but the pointer — no
17//! fetch, no cache, no second close group, nothing that can be missing.
18//!
19//! # Finality, and transfer by final redirection
20//!
21//! A record at `counter == u64::MAX` is **final**: no record replaces it, not
22//! even another one at `u64::MAX`. That is what makes ownership transferable
23//! without touching the record: the owner signs one last state, at the final
24//! counter, pointing at a pointer the new owner holds the key to
25//! ([`Pointer::transfer_to`]). A reader of the old address is redirected to the
26//! new owner's pointer by every node that holds that final state, the address
27//! never changes, and no node that holds the final state gives it up — a
28//! larger counter does not exist, and an equal one does not replace.
29//!
30//! Two different final states can still exist, because only the owner decides
31//! what it signs, and the rule only stops a node from changing its mind. A
32//! node that holds no final state takes whichever one reaches it first. So the
33//! former owner can fork the pointer by signing a second final state and
34//! getting it to nodes the first has not reached: racing both at once, or
35//! sending the second later. Nothing local can settle that. What the rule does
36//! settle is that no node ever gives up a final state it holds, so a fork
37//! cannot take a final state back from the nodes that hold it; it can only
38//! split the nodes that held none. Finding a fork, and reading the state most of the
39//! close group holds, is the client's job; not taking a rival of a final state
40//! the close group already holds is the node's. See `ADR-0018` in `ant-node`.
41//!
42//! # Encoding
43//!
44//! Encoded by hand: canonical, fixed-width, big-endian, no length prefixes and
45//! no `serde`. The signed payload must not depend on a serializer whose output
46//! can change — 1.0's pointer called `rmp_serde` inside its signing routine and
47//! silently signed nothing when it failed.
48//!
49//! ```text
50//! format_version  1     byte
51//! owner        1952     bytes   ML-DSA-65 public key
52//! counter         8     bytes   big-endian u64
53//! target         33     bytes   1-byte kind tag + 32-byte address, opaque here
54//! signature    3309     bytes   ML-DSA-65 over the preceding 1994 bytes
55//!             -----
56//!              5303     bytes
57//! ```
58//!
59//! # Identity
60//!
61//! ```text
62//! A        = BLAKE3::derive_key("autonomi.pointer.address.v1", owner)
63//! state_id = BLAKE3::derive_key("autonomi.pointer.state.v1",   body)
64//! ```
65//!
66//! Derive-key, not a hash of a prefix and the input. A chunk is addressed by a
67//! plain hash of its content, so a prefix would put both of these inside the
68//! chunk address space for anyone who could write the preimage down: squatting
69//! an owner's address before they used it, or buying a pointer and a chunk with
70//! one payment. Both modes still produce 32 bytes, so the ranges overlap; what
71//! changed is that crossing them is the preimage problem BLAKE3 is assumed to
72//! resist.
73//!
74//! `A` routes and decides which nodes are responsible. `state_id` names the
75//! authenticated state: it is what sync hints carry, what a fetch decision
76//! compares, and **what a quote is paid against**. They are separate because
77//! `A` must be stable for the pointer's life while the paid identifier must
78//! change with every update, or updates after the first would be free — the
79//! defect this design exists to fix.
80//!
81//! # What this module does not do
82//!
83//! Payment verification, storage and replication. Those are the node's job.
84
85use saorsa_pqc::api::sig::{
86    ml_dsa_65, MlDsaPublicKey, MlDsaSecretKey, MlDsaSignature, MlDsaVariant,
87};
88
89use crate::chunk::XORNAME_LEN;
90use crate::data_types::XorName;
91
92// =============================================================================
93// Constants
94// =============================================================================
95
96/// Data type identifier for pointers, alongside
97/// [`DATA_TYPE_CHUNK`](crate::chunk::DATA_TYPE_CHUNK).
98pub const DATA_TYPE_POINTER: u32 = 1;
99
100/// Wire format discriminator carried by every pointer.
101///
102/// Covered by the signature, so it cannot be downgraded, and part of the signed
103/// body, so it reaches `state_id`: a record differing only in this byte is a
104/// different state and is paid for separately.
105pub const POINTER_FORMAT_VERSION: u8 = 1;
106
107/// The final counter. A record signed at it is never replaced.
108///
109/// See [`PointerState::replaces`] for why, and [`Pointer::transfer_to`] for
110/// what it is for.
111pub const FINAL_COUNTER: u64 = u64::MAX;
112
113/// Length of a raw ML-DSA-65 public key.
114pub const ML_DSA_65_PUBLIC_KEY_LEN: usize = 1952;
115
116/// Length of a raw ML-DSA-65 signature.
117pub const ML_DSA_65_SIGNATURE_LEN: usize = 3309;
118
119/// Length of an encoded [`PointerTarget`]: a kind tag plus an address.
120pub const TARGET_WIRE_LEN: usize = 1 + XORNAME_LEN;
121
122/// Byte offset of the owner key within an encoded pointer.
123const OWNER_OFFSET: usize = 1;
124
125/// Byte offset of the counter within an encoded pointer.
126const COUNTER_OFFSET: usize = OWNER_OFFSET + ML_DSA_65_PUBLIC_KEY_LEN;
127
128/// Byte offset of the target within an encoded pointer.
129const TARGET_OFFSET: usize = COUNTER_OFFSET + 8;
130
131/// Length of the signed body: everything before the signature.
132pub const POINTER_BODY_LEN: usize = TARGET_OFFSET + TARGET_WIRE_LEN;
133
134/// Total size of an encoded pointer.
135pub const POINTER_WIRE_LEN: usize = POINTER_BODY_LEN + ML_DSA_65_SIGNATURE_LEN;
136
137/// Key-derivation context for the pointer address.
138///
139/// Used with BLAKE3's `derive_key`, not as a hash prefix. A prefix separates
140/// nothing: a chunk's address is `BLAKE3(content)`, so a chunk whose content is
141/// the prefix followed by an owner key would land on exactly that owner's
142/// pointer address, and an attacker could squat any address it could name.
143/// `derive_key` runs BLAKE3 in a different mode. The two still share a 32-byte
144/// range; what changes is that hitting a value from one mode with the other is
145/// the preimage problem BLAKE3 is assumed to resist, rather than a string
146/// anyone can write down.
147const CONTEXT_ADDRESS: &str = "autonomi.pointer.address.v1";
148
149/// Key-derivation context for the authenticated-state identifier.
150///
151/// Also `derive_key`, and for a sharper reason: `state_id` is what a pointer's
152/// payment is quoted against. Were it a plain hash, one settled quote would pay
153/// for both the pointer and a chunk sitting at the same address.
154const CONTEXT_STATE: &str = "autonomi.pointer.state.v1";
155
156/// ML-DSA signing context for a pointer.
157///
158/// Domain-separates a pointer signature from every other signature in the
159/// system, so a signature lifted from elsewhere cannot authenticate a pointer
160/// and vice versa.
161const SIGNING_CONTEXT: &[u8] = b"autonomi.pointer.head.v1";
162
163// =============================================================================
164// Errors
165// =============================================================================
166
167/// Why a pointer was refused.
168///
169/// `Display` is written by hand rather than derived, because this crate is a
170/// wire contract and does not take a macro dependency for it — the same reason
171/// [`crate::error::Error`] is hand-written.
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub enum PointerError {
174    /// The encoding is not exactly [`POINTER_WIRE_LEN`] bytes.
175    WrongLength(usize),
176    /// The format version is not one this build understands.
177    UnknownFormatVersion(u8),
178    /// The owner key does not decode.
179    InvalidOwnerKey(String),
180    /// The signature does not decode.
181    InvalidSignatureFormat(String),
182    /// The signature does not verify under the owner key in the record.
183    SignatureInvalid,
184    /// Signing failed.
185    SigningFailed(String),
186    /// The counter is at [`FINAL_COUNTER`]: the pointer is final and no
187    /// further state can replace it.
188    CounterExhausted,
189}
190
191impl std::fmt::Display for PointerError {
192    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193        match self {
194            Self::WrongLength(len) => {
195                write!(f, "pointer must be {POINTER_WIRE_LEN} bytes, got {len}")
196            }
197            Self::UnknownFormatVersion(version) => {
198                write!(f, "unknown pointer format version {version}")
199            }
200            Self::InvalidOwnerKey(reason) => write!(f, "invalid pointer owner key: {reason}"),
201            Self::InvalidSignatureFormat(reason) => {
202                write!(f, "invalid pointer signature format: {reason}")
203            }
204            Self::SignatureInvalid => {
205                write!(f, "pointer signature does not verify under its owner key")
206            }
207            Self::SigningFailed(reason) => write!(f, "pointer signing failed: {reason}"),
208            Self::CounterExhausted => write!(
209                f,
210                "pointer is final: its counter is at u64::MAX, and no later state \
211                 can replace it"
212            ),
213        }
214    }
215}
216
217impl std::error::Error for PointerError {}
218
219// =============================================================================
220// Address derivation
221// =============================================================================
222
223/// Derive a pointer address from an owner key.
224///
225/// `BLAKE3::derive_key` rather than a hash of a prefix and the key. Chunk
226/// addresses are `BLAKE3(content)` over the same 32-byte range, and derive-key
227/// is a different mode of the same function: the ranges still overlap, but
228/// putting content on one of these values is a preimage problem rather than a
229/// string anyone can write down. The node still refuses an address the other
230/// kind occupies; that guard now covers the cryptographic case only.
231#[must_use]
232pub fn pointer_address(owner: &MlDsaPublicKey) -> XorName {
233    blake3::derive_key(CONTEXT_ADDRESS, &owner.to_bytes())
234}
235
236/// Derive the authenticated-state identifier from a signed body.
237///
238/// Derive-key for the same reason as the address, and one more: this is the
239/// identifier a pointer's storage is paid against, so a chunk that could occupy
240/// it would be a second record bought with one payment.
241#[must_use]
242pub fn state_id_for_body(body: &[u8]) -> XorName {
243    blake3::derive_key(CONTEXT_STATE, body)
244}
245
246// =============================================================================
247// Target
248// =============================================================================
249
250/// What a pointer points at, as clients interpret it.
251///
252/// A node never reads this. It stores the tag as an opaque byte, so a new kind
253/// is a client-side change and not a network upgrade.
254#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
255pub enum PointerTargetKind {
256    /// An immutable content-addressed chunk.
257    Chunk,
258    /// Another pointer, which is how handover-by-indirection is expressed.
259    Pointer,
260}
261
262impl PointerTargetKind {
263    /// Wire tag for this kind.
264    #[must_use]
265    pub const fn tag(self) -> u8 {
266        match self {
267            Self::Chunk => 0,
268            Self::Pointer => 1,
269        }
270    }
271
272    /// Interpret a wire tag, if it names a kind this build knows.
273    ///
274    /// Returns `None` for anything else rather than an error: an unrecognised
275    /// tag is a target this client cannot follow, not a malformed record.
276    #[must_use]
277    pub const fn from_tag(tag: u8) -> Option<Self> {
278        match tag {
279            0 => Some(Self::Chunk),
280            1 => Some(Self::Pointer),
281            _ => None,
282        }
283    }
284}
285
286/// A kind tag plus the address it refers to.
287///
288/// The tag is carried and signed but never interpreted by a storing node.
289#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
290pub struct PointerTarget {
291    /// The raw kind tag, meaningful only to clients.
292    kind_tag: u8,
293    /// The address pointed at.
294    pub address: XorName,
295}
296
297impl PointerTarget {
298    /// Build a target of a known kind.
299    #[must_use]
300    pub const fn new(kind: PointerTargetKind, address: XorName) -> Self {
301        Self {
302            kind_tag: kind.tag(),
303            address,
304        }
305    }
306
307    /// Build a target from a raw tag, including one this build does not know.
308    #[must_use]
309    pub const fn from_raw_tag(kind_tag: u8, address: XorName) -> Self {
310        Self { kind_tag, address }
311    }
312
313    /// The raw kind tag as carried on the wire.
314    #[must_use]
315    pub const fn kind_tag(&self) -> u8 {
316        self.kind_tag
317    }
318
319    /// The kind, if this build recognises the tag.
320    #[must_use]
321    pub const fn kind(&self) -> Option<PointerTargetKind> {
322        PointerTargetKind::from_tag(self.kind_tag)
323    }
324
325    /// Encode to its [`TARGET_WIRE_LEN`] wire bytes.
326    #[must_use]
327    pub fn to_bytes(&self) -> [u8; TARGET_WIRE_LEN] {
328        let mut out = [0u8; TARGET_WIRE_LEN];
329        if let Some((tag, address)) = out.split_first_mut() {
330            *tag = self.kind_tag;
331            address.copy_from_slice(&self.address);
332        }
333        out
334    }
335
336    /// Decode from wire bytes.
337    ///
338    /// Only the length can fail: every tag value is accepted, because a node
339    /// stores the target without interpreting it.
340    fn from_bytes(bytes: &[u8]) -> Option<Self> {
341        let (tag, rest) = bytes.split_first()?;
342        if rest.len() != XORNAME_LEN {
343            return None;
344        }
345        let mut address = [0u8; XORNAME_LEN];
346        address.copy_from_slice(rest);
347        Some(Self {
348            kind_tag: *tag,
349            address,
350        })
351    }
352}
353
354// =============================================================================
355// Merge order
356// =============================================================================
357
358/// The merge rule's comparison key: larger wins.
359///
360/// One type so that every comparison — an arriving record against a parsed one,
361/// or against a store's index entry — is literally the same ordering. Deriving
362/// `Ord` over the fields in declaration order is what encodes the rule:
363/// `counter` first, then the target, inverted so that *smaller* target bytes
364/// rank higher.
365#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
366struct MergeRank {
367    /// The update counter. Larger wins.
368    counter: u64,
369    /// The encoded target, inverted: smaller bytes win.
370    target: std::cmp::Reverse<[u8; TARGET_WIRE_LEN]>,
371}
372
373// =============================================================================
374// State
375// =============================================================================
376
377/// The parts of a record that determine its authenticated state.
378///
379/// Parsed from the body alone, so obtaining one costs no signature
380/// verification. That is what lets a node recognise a re-submission of what it
381/// already holds and stop before doing expensive work.
382#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub struct PointerState {
384    /// The authenticated-state identifier, derived from the signed body.
385    pub state_id: XorName,
386    /// The address the record belongs at.
387    pub address: XorName,
388    /// The update counter, the merge rule's first key.
389    pub counter: u64,
390    /// The target, whose bytes are the merge rule's second key.
391    pub target: PointerTarget,
392}
393
394impl PointerState {
395    /// Parse the state out of a record's bytes without verifying its signature.
396    ///
397    /// Checks length, format version and structure only. A `PointerState` says
398    /// what a record *claims*; only [`Pointer::from_bytes`] establishes that
399    /// the claim is signed.
400    ///
401    /// # Errors
402    ///
403    /// Returns [`PointerError`] for a record of the wrong length, an unknown
404    /// format version, or an unparseable owner key.
405    pub fn parse(bytes: &[u8]) -> Result<Self, PointerError> {
406        Self::parse_with_owner(bytes).map(|(state, _)| state)
407    }
408
409    /// As [`Self::parse`], also returning the decoded owner key.
410    fn parse_with_owner(bytes: &[u8]) -> Result<(Self, MlDsaPublicKey), PointerError> {
411        if bytes.len() != POINTER_WIRE_LEN {
412            return Err(PointerError::WrongLength(bytes.len()));
413        }
414        let version = bytes
415            .first()
416            .copied()
417            .ok_or(PointerError::WrongLength(bytes.len()))?;
418        if version != POINTER_FORMAT_VERSION {
419            return Err(PointerError::UnknownFormatVersion(version));
420        }
421
422        let owner_bytes = bytes
423            .get(OWNER_OFFSET..COUNTER_OFFSET)
424            .ok_or(PointerError::WrongLength(bytes.len()))?;
425        let owner = MlDsaPublicKey::from_bytes(MlDsaVariant::MlDsa65, owner_bytes)
426            .map_err(|e| PointerError::InvalidOwnerKey(e.to_string()))?;
427
428        let counter_bytes: [u8; 8] = bytes
429            .get(COUNTER_OFFSET..TARGET_OFFSET)
430            .and_then(|slice| slice.try_into().ok())
431            .ok_or(PointerError::WrongLength(bytes.len()))?;
432
433        let target = bytes
434            .get(TARGET_OFFSET..POINTER_BODY_LEN)
435            .and_then(PointerTarget::from_bytes)
436            .ok_or(PointerError::WrongLength(bytes.len()))?;
437
438        let body = bytes
439            .get(..POINTER_BODY_LEN)
440            .ok_or(PointerError::WrongLength(bytes.len()))?;
441
442        Ok((
443            Self {
444                state_id: state_id_for_body(body),
445                address: pointer_address(&owner),
446                counter: u64::from_be_bytes(counter_bytes),
447                target,
448            },
449            owner,
450        ))
451    }
452
453    /// The comparison key for [`Self::replaces`].
454    #[must_use]
455    fn rank(&self) -> MergeRank {
456        MergeRank {
457            counter: self.counter,
458            target: std::cmp::Reverse(self.target.to_bytes()),
459        }
460    }
461
462    /// Whether this state is final: signed at [`FINAL_COUNTER`], and so
463    /// replaced by nothing.
464    #[must_use]
465    pub const fn is_terminal(&self) -> bool {
466        self.counter == FINAL_COUNTER
467    }
468
469    /// The pointer this state hands the address over to, if it is a transfer:
470    /// a final state whose target is another pointer.
471    ///
472    /// A final state targeting anything else freezes the pointer rather than
473    /// handing it on, and a pointer target below the final counter is a
474    /// forwarding the owner can still take back. Neither is a transfer.
475    #[must_use]
476    pub const fn transferred_to(&self) -> Option<XorName> {
477        transfer_target(self.counter, self.target)
478    }
479
480    /// Whether `self` replaces `other` under the merge rule.
481    ///
482    /// ```text
483    /// 0. a final state (counter == u64::MAX) is replaced by nothing
484    /// 1. larger counter
485    /// 2. smaller target bytes
486    /// ```
487    ///
488    /// Records of different owners are not comparable and neither replaces the
489    /// other.
490    ///
491    /// Equal state never replaces, whatever the signature bytes: ML-DSA signing
492    /// is randomized, so one authenticated state has unboundedly many valid
493    /// encodings, and ordering records by their bytes would let an owner sign
494    /// one paid state repeatedly, sort worst-first and have every submission
495    /// win — unbounded storage, replication and audit work for a single
496    /// payment.
497    ///
498    /// Rule 0 is what makes a pointer's last state final, and so what makes
499    /// ownership transferable by redirection ([`Pointer::transfer_to`]). Under
500    /// rules 1 and 2 alone a state at the final counter could still be
501    /// displaced by another at the same counter with smaller target bytes, so
502    /// a former owner could grind a target and take the address back after
503    /// handing it on.
504    ///
505    /// The cost is that the order is total only below the final counter. Two
506    /// different final states are incomparable: neither replaces the other, so
507    /// whichever a node took first is the one it keeps. That is the only
508    /// conflict this rule leaves unordered, and so the only fork no later
509    /// state heals; a conflict at a lower counter is ordered by target bytes
510    /// and replaced by any later counter. Only the owner can make it — by
511    /// signing a second
512    /// final state and getting it to nodes the first has not reached, at once
513    /// or later. Once a node holds a final state no arrival can change it, so a
514    /// second final state can never displace an established one; it can only
515    /// land on a node that held no final state. A reader tells the two sides
516    /// apart by how many of the close group hold each.
517    ///
518    /// Every other pair of distinct states is ordered, and the order is a
519    /// strict partial order everywhere: never both ways round, and transitive.
520    #[must_use]
521    pub fn replaces(&self, other: &Self) -> bool {
522        self.address == other.address && !other.is_terminal() && self.rank() > other.rank()
523    }
524}
525
526/// The pointer a state at `counter` with `target` transfers to, if any.
527///
528/// One definition for [`PointerState::transferred_to`] and
529/// [`Pointer::transferred_to`], so a record and its parsed state cannot
530/// disagree about whether it is a transfer.
531const fn transfer_target(counter: u64, target: PointerTarget) -> Option<XorName> {
532    match target.kind() {
533        Some(PointerTargetKind::Pointer) if counter == FINAL_COUNTER => Some(target.address),
534        _ => None,
535    }
536}
537
538// =============================================================================
539// Parsed-but-unverified record
540// =============================================================================
541
542/// Bytes that have been structurally parsed but not yet verified.
543///
544/// The bytes travel with their parse, so a caller cannot pair one record's
545/// bytes with another's state. Only [`Pointer::verify_parsed`] consumes it, and
546/// only that step establishes that the bytes are signed.
547pub struct ParsedPointer {
548    /// The record's canonical encoding.
549    bytes: Vec<u8>,
550    /// What those bytes claim.
551    state: PointerState,
552    /// The owner key decoded from those bytes.
553    owner: MlDsaPublicKey,
554}
555
556impl std::fmt::Debug for ParsedPointer {
557    /// Shows what the unverified bytes claim, not the bytes themselves.
558    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
559        f.debug_struct("ParsedPointer")
560            .field("address", &hex::encode(self.state.address))
561            .field("counter", &self.state.counter)
562            .finish_non_exhaustive()
563    }
564}
565
566impl ParsedPointer {
567    /// Parse `bytes`, without verifying the signature.
568    ///
569    /// # Errors
570    ///
571    /// As [`PointerState::parse`].
572    pub fn parse(bytes: Vec<u8>) -> Result<Self, PointerError> {
573        let (state, owner) = PointerState::parse_with_owner(&bytes)?;
574        Ok(Self {
575            bytes,
576            state,
577            owner,
578        })
579    }
580
581    /// What the unverified bytes claim.
582    #[must_use]
583    pub const fn state(&self) -> &PointerState {
584        &self.state
585    }
586}
587
588// =============================================================================
589// The record
590// =============================================================================
591
592/// A paid, mutable, owner-signed reference.
593///
594/// A value of this type has been through [`Pointer::from_bytes`], so its
595/// signature covers exactly its body and its address derives from its owner.
596#[derive(Clone)]
597pub struct Pointer {
598    /// Wire format discriminator. Signed, so it cannot be downgraded.
599    version: u8,
600    /// The owner's public key — the pointer's identity.
601    ///
602    /// The address is derived from the owner key alone, so a pointer is public-key
603    /// addressed, and the key that verifies it travels with it: ML-DSA has no
604    /// key recovery and a 1,952-byte key cannot be a 32-byte address.
605    owner: MlDsaPublicKey,
606    /// The merge rule's first key: a larger counter wins.
607    ///
608    /// [`Self::create`] starts at 0 and [`Self::update`] adds one, but nothing
609    /// requires the next state to be exactly one on. The counter orders states;
610    /// it does not meter them. Every stored state is paid for once, whatever
611    /// its number, so a number that is skipped is never stored and owes nothing.
612    counter: u64,
613    /// What this points at: a chunk or another pointer. Opaque to a node.
614    target: PointerTarget,
615    /// The owner's signature over every field above.
616    sig: MlDsaSignature,
617}
618
619impl std::fmt::Debug for Pointer {
620    /// Shows what identifies the record, not its 5 KB of key and signature.
621    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
622        f.debug_struct("Pointer")
623            .field("address", &hex::encode(self.address()))
624            .field("counter", &self.counter)
625            .field("target_tag", &self.target.kind_tag())
626            .field("target", &hex::encode(self.target.address))
627            .field("state_id", &hex::encode(self.state_id()))
628            .finish_non_exhaustive()
629    }
630}
631
632impl Pointer {
633    /// Build and sign a pointer.
634    ///
635    /// # Errors
636    ///
637    /// Returns [`PointerError::SigningFailed`] if signing fails, or a parse
638    /// error if the resulting record does not parse, which would be a bug here
639    /// rather than bad input.
640    pub fn sign(
641        secret_key: &MlDsaSecretKey,
642        owner: &MlDsaPublicKey,
643        counter: u64,
644        target: PointerTarget,
645    ) -> Result<Self, PointerError> {
646        let body = encode_body(POINTER_FORMAT_VERSION, owner, counter, target)?;
647        let signature = ml_dsa_65()
648            .sign_with_context(secret_key, &body, SIGNING_CONTEXT)
649            .map_err(|e| PointerError::SigningFailed(e.to_string()))?;
650        let mut bytes = body;
651        bytes.extend_from_slice(&signature.to_bytes());
652        Self::from_bytes(&bytes)
653    }
654
655    /// Create a pointer at counter 0.
656    ///
657    /// # Errors
658    ///
659    /// As [`Self::sign`].
660    pub fn create(
661        secret_key: &MlDsaSecretKey,
662        owner: &MlDsaPublicKey,
663        target: PointerTarget,
664    ) -> Result<Self, PointerError> {
665        Self::sign(secret_key, owner, 0, target)
666    }
667
668    /// Sign the update that follows this one: `counter + 1`, new target.
669    ///
670    /// One past this record is the smallest counter that replaces it, and so
671    /// the natural next state. A larger one would replace it just as well.
672    ///
673    /// # Errors
674    ///
675    /// Returns [`PointerError::CounterExhausted`] at `u64::MAX`, otherwise as
676    /// [`Self::sign`].
677    pub fn update(
678        &self,
679        secret_key: &MlDsaSecretKey,
680        target: PointerTarget,
681    ) -> Result<Self, PointerError> {
682        Self::sign(secret_key, &self.owner, self.next_counter()?, target)
683    }
684
685    /// Sign this pointer's final state, pointing at `target`.
686    ///
687    /// Signed at [`FINAL_COUNTER`], so once a node holds it nothing replaces
688    /// it — not a later update, which cannot exist, and not another final
689    /// state, which does not replace. Use [`Self::transfer_to`] to hand the
690    /// pointer to someone else; a final state pointing at a chunk freezes the
691    /// pointer on that chunk at every node that holds it.
692    ///
693    /// Signing a second final state is the one way to fork a pointer for
694    /// good: each node keeps whichever of the two it saw first, and no later
695    /// state heals it, where a fork at a lower counter is. So this refuses to sign
696    /// past a record that is already final, and a caller must read the
697    /// network's state before finalizing rather than after. That refusal guards
698    /// a caller against a mistake, not the network against the owner: an
699    /// earlier record can be finalized again, and [`Self::sign`] signs any
700    /// counter.
701    ///
702    /// # Errors
703    ///
704    /// Returns [`PointerError::CounterExhausted`] if this record is already
705    /// final, otherwise as [`Self::sign`].
706    pub fn finalize(
707        &self,
708        secret_key: &MlDsaSecretKey,
709        target: PointerTarget,
710    ) -> Result<Self, PointerError> {
711        if self.is_terminal() {
712            return Err(PointerError::CounterExhausted);
713        }
714        Self::sign(secret_key, &self.owner, FINAL_COUNTER, target)
715    }
716
717    /// Sign the final state that hands this pointer's address over to the
718    /// pointer at `recipient`.
719    ///
720    /// The owner key cannot change, but what the address resolves to can be
721    /// handed over: the final state points at a pointer the new owner holds
722    /// the key to, so a reader of this address is redirected there by every
723    /// node that holds this state, and from there only the new owner moves it
724    /// on. It is not exclusive: the former owner can still finalize an earlier
725    /// record towards someone else, and a node that holds neither takes
726    /// whichever reaches it first (see [`Self::finalize`]). The address readers
727    /// use does not change.
728    ///
729    /// `recipient` should be a pointer that exists, and one whose owner uses
730    /// it only for this address: a pointer's address derives from its owner
731    /// key, so every address handed to the same recipient pointer resolves to
732    /// the same place.
733    ///
734    /// # Errors
735    ///
736    /// As [`Self::finalize`].
737    pub fn transfer_to(
738        &self,
739        secret_key: &MlDsaSecretKey,
740        recipient: XorName,
741    ) -> Result<Self, PointerError> {
742        self.finalize(
743            secret_key,
744            PointerTarget::new(PointerTargetKind::Pointer, recipient),
745        )
746    }
747
748    /// Parse and fully validate a record.
749    ///
750    /// Checks, cheapest first: length, format version, structure, then the
751    /// signature. A returned `Pointer` is structurally valid and correctly
752    /// signed by the key it carries; it says nothing about payment, which is a
753    /// separate external check.
754    ///
755    /// # Errors
756    ///
757    /// Returns [`PointerError`] for a malformed record or one whose signature
758    /// does not verify.
759    pub fn from_bytes(bytes: &[u8]) -> Result<Self, PointerError> {
760        Self::verify_parsed(ParsedPointer::parse(bytes.to_vec())?)
761    }
762
763    /// Verify a record that has already been parsed.
764    ///
765    /// Decoding an ML-DSA public key is not free, and the request path parses
766    /// before it decides whether verification is worth doing at all. This lets
767    /// it hand over what it already has instead of decoding a second time.
768    ///
769    /// # Errors
770    ///
771    /// As [`Self::from_bytes`].
772    pub fn verify_parsed(parsed: ParsedPointer) -> Result<Self, PointerError> {
773        let ParsedPointer {
774            bytes,
775            state,
776            owner,
777        } = parsed;
778
779        let body = bytes
780            .get(..POINTER_BODY_LEN)
781            .ok_or(PointerError::WrongLength(bytes.len()))?;
782        let signature_bytes = bytes
783            .get(POINTER_BODY_LEN..)
784            .ok_or(PointerError::WrongLength(bytes.len()))?;
785        let signature = MlDsaSignature::from_bytes(MlDsaVariant::MlDsa65, signature_bytes)
786            .map_err(|e| PointerError::InvalidSignatureFormat(e.to_string()))?;
787
788        let valid = ml_dsa_65()
789            .verify_with_context(&owner, body, &signature, SIGNING_CONTEXT)
790            .map_err(|e| PointerError::InvalidSignatureFormat(e.to_string()))?;
791        if !valid {
792            return Err(PointerError::SignatureInvalid);
793        }
794
795        Ok(Self {
796            version: POINTER_FORMAT_VERSION,
797            owner,
798            counter: state.counter,
799            target: state.target,
800            sig: signature,
801        })
802    }
803
804    /// The canonical encoding of this record.
805    ///
806    /// Re-encoded from the fields rather than cached. Every field is
807    /// fixed-width with no optionality, so there is exactly one encoding and
808    /// this is always byte-identical to what was signed.
809    #[must_use]
810    pub fn to_bytes(&self) -> Vec<u8> {
811        let mut out = self.body();
812        out.extend_from_slice(&self.sig.to_bytes());
813        out
814    }
815
816    /// The signed body: every field the signature covers.
817    fn body(&self) -> Vec<u8> {
818        encode_body(self.version, &self.owner, self.counter, self.target)
819            .unwrap_or_else(|_| Vec::with_capacity(POINTER_BODY_LEN))
820    }
821
822    /// The owner's public key. Fixed for the pointer's life.
823    #[must_use]
824    pub const fn owner(&self) -> &MlDsaPublicKey {
825        &self.owner
826    }
827
828    /// The signed state this record carries.
829    ///
830    /// Derived, not stored: the identifiers are hashes of fields already here.
831    #[must_use]
832    pub fn state(&self) -> PointerState {
833        PointerState {
834            state_id: self.state_id(),
835            address: self.address(),
836            counter: self.counter,
837            target: self.target,
838        }
839    }
840
841    /// The update counter.
842    #[must_use]
843    pub const fn counter(&self) -> u64 {
844        self.counter
845    }
846
847    /// What this pointer points at.
848    #[must_use]
849    pub const fn target(&self) -> PointerTarget {
850        self.target
851    }
852
853    /// The address this record belongs at, derived from the owner key.
854    #[must_use]
855    pub fn address(&self) -> XorName {
856        pointer_address(&self.owner)
857    }
858
859    /// The authenticated-state identifier, derived from the signed body.
860    ///
861    /// Names the state rather than the encoding, so two records carrying
862    /// different valid signatures over one state share a `state_id`. This is
863    /// what sync hints carry and what a quote is paid against.
864    #[must_use]
865    pub fn state_id(&self) -> XorName {
866        state_id_for_body(&self.body())
867    }
868
869    /// The successor counter for an update to this pointer.
870    ///
871    /// # Errors
872    ///
873    /// Returns [`PointerError::CounterExhausted`] at [`FINAL_COUNTER`].
874    ///
875    /// A record at the final counter is replaced by nothing, so there is no
876    /// update to sign past it. Migrating a pointer to a fresh one is exactly
877    /// that final update: see [`Self::transfer_to`].
878    pub fn next_counter(&self) -> Result<u64, PointerError> {
879        self.counter
880            .checked_add(1)
881            .ok_or(PointerError::CounterExhausted)
882    }
883
884    /// Whether this record is final: signed at [`FINAL_COUNTER`].
885    ///
886    /// A node that holds a final record keeps it: neither a lower counter nor
887    /// another final record replaces it. See [`PointerState::replaces`].
888    #[must_use]
889    pub const fn is_terminal(&self) -> bool {
890        self.counter == FINAL_COUNTER
891    }
892
893    /// The pointer this record hands its address over to, if it is a transfer.
894    ///
895    /// As [`PointerState::transferred_to`].
896    #[must_use]
897    pub const fn transferred_to(&self) -> Option<XorName> {
898        transfer_target(self.counter, self.target)
899    }
900
901    /// Whether `self` replaces `other` under the merge rule.
902    ///
903    /// See [`PointerState::replaces`], which is where the rule lives.
904    #[must_use]
905    pub fn replaces(&self, other: &Self) -> bool {
906        self.state().replaces(&other.state())
907    }
908}
909
910/// Encode the signed body of a pointer.
911fn encode_body(
912    version: u8,
913    owner: &MlDsaPublicKey,
914    counter: u64,
915    target: PointerTarget,
916) -> Result<Vec<u8>, PointerError> {
917    let owner_bytes = owner.to_bytes();
918    if owner_bytes.len() != ML_DSA_65_PUBLIC_KEY_LEN {
919        return Err(PointerError::InvalidOwnerKey(format!(
920            "owner key must be {ML_DSA_65_PUBLIC_KEY_LEN} bytes, got {}",
921            owner_bytes.len()
922        )));
923    }
924    let mut body = Vec::with_capacity(POINTER_BODY_LEN);
925    // The record's own version, not this build's: a record is re-encoded as
926    // what was signed. Only `POINTER_FORMAT_VERSION` parses today, so the two
927    // coincide — but the encoder must not be the thing that assumes it.
928    body.push(version);
929    body.extend_from_slice(&owner_bytes);
930    body.extend_from_slice(&counter.to_be_bytes());
931    body.extend_from_slice(&target.to_bytes());
932    Ok(body)
933}
934
935#[cfg(test)]
936#[allow(
937    clippy::unwrap_used,
938    clippy::expect_used,
939    clippy::panic,
940    reason = "the crate guide allows these in tests"
941)]
942mod tests {
943    use super::*;
944
945    fn keypair(seed: u8) -> (MlDsaPublicKey, MlDsaSecretKey) {
946        ml_dsa_65().generate_keypair_from_seed(&[seed; 32])
947    }
948
949    fn signed(seed: u8, counter: u64, target_byte: u8) -> Pointer {
950        let (pk, sk) = keypair(seed);
951        let target = PointerTarget::new(PointerTargetKind::Chunk, [target_byte; XORNAME_LEN]);
952        Pointer::sign(&sk, &pk, counter, target).expect("sign")
953    }
954
955    #[test]
956    fn sizes_match_the_design() {
957        assert_eq!(POINTER_BODY_LEN, 1994);
958        assert_eq!(POINTER_WIRE_LEN, 5303);
959        assert_eq!(signed(1, 0, 0).to_bytes().len(), POINTER_WIRE_LEN);
960    }
961
962    #[test]
963    fn roundtrips_and_validates() {
964        let record = signed(1, 42, 7);
965        let parsed = Pointer::from_bytes(&record.to_bytes()).expect("reparse");
966        assert_eq!(parsed.counter(), 42);
967        assert_eq!(parsed.address(), record.address());
968        assert_eq!(parsed.state_id(), record.state_id());
969    }
970
971    #[test]
972    fn a_tampered_record_does_not_verify() {
973        let record = signed(1, 5, 5);
974        let mut bytes = record.to_bytes();
975        if let Some(byte) = bytes.get_mut(COUNTER_OFFSET + 7) {
976            *byte ^= 1;
977        }
978        assert!(matches!(
979            Pointer::from_bytes(&bytes),
980            Err(PointerError::SignatureInvalid)
981        ));
982    }
983
984    #[test]
985    fn an_unknown_format_version_is_refused() {
986        let record = signed(1, 1, 1);
987        let mut bytes = record.to_bytes();
988        if let Some(byte) = bytes.first_mut() {
989            *byte = 2;
990        }
991        assert!(matches!(
992            Pointer::from_bytes(&bytes),
993            Err(PointerError::UnknownFormatVersion(2))
994        ));
995    }
996
997    #[test]
998    fn the_format_version_reaches_the_paid_identifier() {
999        // A record differing only in its version byte is a different state and
1000        // must be paid for separately. Without this a future version could
1001        // reuse a receipt bought for version 1.
1002        let record = signed(1, 3, 3);
1003        let mut body = record
1004            .to_bytes()
1005            .get(..POINTER_BODY_LEN)
1006            .expect("body")
1007            .to_vec();
1008        let baseline = state_id_for_body(&body);
1009        assert_eq!(baseline, record.state_id());
1010
1011        for version in 0u8..=255 {
1012            if version == POINTER_FORMAT_VERSION {
1013                continue;
1014            }
1015            if let Some(byte) = body.first_mut() {
1016                *byte = version;
1017            }
1018            assert_ne!(
1019                state_id_for_body(&body),
1020                baseline,
1021                "version {version} must not share version 1's paid identifier"
1022            );
1023        }
1024    }
1025
1026    #[test]
1027    fn different_owners_are_not_comparable() {
1028        let mine = signed(1, 1, 9);
1029        let theirs = signed(2, 500, 0);
1030        assert!(!mine.replaces(&theirs));
1031        assert!(!theirs.replaces(&mine));
1032    }
1033
1034    #[test]
1035    fn the_counter_is_terminal_at_its_maximum() {
1036        let ordinary = signed(1, 0, 0);
1037        assert!(!ordinary.is_terminal());
1038        assert_eq!(ordinary.next_counter().expect("next"), 1);
1039
1040        let terminal = signed(1, u64::MAX, 0);
1041        assert!(terminal.is_terminal());
1042        assert_eq!(terminal.next_counter(), Err(PointerError::CounterExhausted));
1043        assert!(terminal.next_counter().is_err());
1044    }
1045
1046    #[test]
1047    fn nothing_out_ranks_a_terminal_counter() {
1048        // No lower counter replaces a final state, and a final state replaces
1049        // every lower one.
1050        let terminal = signed(1, u64::MAX, 5);
1051        for counter in [0u64, 1, 42, u64::MAX - 1] {
1052            let earlier = signed(1, counter, 0);
1053            assert!(
1054                !earlier.replaces(&terminal),
1055                "counter {counter} must not replace a terminal pointer"
1056            );
1057            assert!(terminal.replaces(&earlier));
1058        }
1059    }
1060
1061    #[test]
1062    fn a_final_state_is_replaced_by_nothing_not_even_a_smaller_target() {
1063        // Below the final counter, equal counters resolve by target bytes. At
1064        // it they do not: a former owner who handed the address on could
1065        // otherwise grind a smaller target and take it back. So two final
1066        // states are incomparable, and a node keeps whichever it took first.
1067        let first = signed(1, FINAL_COUNTER, 9);
1068        let smaller_target = signed(1, FINAL_COUNTER, 1);
1069        assert!(
1070            !smaller_target.replaces(&first),
1071            "a smaller target no longer wins"
1072        );
1073        assert!(!first.replaces(&smaller_target));
1074
1075        // Below the final counter nothing changed.
1076        let low = signed(1, 7, 1);
1077        let high = signed(1, 7, 9);
1078        assert!(
1079            low.replaces(&high),
1080            "smaller target still wins below the end"
1081        );
1082    }
1083
1084    #[test]
1085    fn a_state_at_the_final_counter_is_final_whatever_it_targets() {
1086        let (pk, sk) = keypair(40);
1087        let chunk = PointerTarget::new(PointerTargetKind::Chunk, [4; XORNAME_LEN]);
1088        let pointer = PointerTarget::new(PointerTargetKind::Pointer, [5; XORNAME_LEN]);
1089        let unknown = PointerTarget::from_raw_tag(200, [6; XORNAME_LEN]);
1090        for target in [chunk, pointer, unknown] {
1091            let held = Pointer::sign(&sk, &pk, FINAL_COUNTER, target).expect("sign");
1092            assert!(held.is_terminal());
1093            assert!(held.state().is_terminal());
1094            for other in [chunk, pointer, unknown] {
1095                for counter in [0, 1, FINAL_COUNTER - 1, FINAL_COUNTER] {
1096                    let arrival = Pointer::sign(&sk, &pk, counter, other).expect("sign");
1097                    assert!(
1098                        !arrival.replaces(&held),
1099                        "counter {counter} must not replace a final state"
1100                    );
1101                }
1102            }
1103        }
1104    }
1105
1106    #[test]
1107    fn a_transfer_is_a_final_state_that_targets_the_recipient_pointer() {
1108        let (pk, sk) = keypair(41);
1109        let recipient = [0x42; XORNAME_LEN];
1110        let current = Pointer::create(
1111            &sk,
1112            &pk,
1113            PointerTarget::new(PointerTargetKind::Chunk, [1; XORNAME_LEN]),
1114        )
1115        .expect("create");
1116        assert_eq!(current.transferred_to(), None);
1117
1118        let transfer = current.transfer_to(&sk, recipient).expect("transfer");
1119        assert_eq!(transfer.counter(), FINAL_COUNTER);
1120        assert_eq!(transfer.address(), current.address(), "the address stays");
1121        assert_eq!(
1122            transfer.target(),
1123            PointerTarget::new(PointerTargetKind::Pointer, recipient)
1124        );
1125        assert_eq!(transfer.transferred_to(), Some(recipient));
1126        assert_eq!(transfer.state().transferred_to(), Some(recipient));
1127        assert!(transfer.replaces(&current));
1128
1129        // Nothing replaces the final state: no later counter exists, and a
1130        // second final state, however its target sorts, does not replace it.
1131        assert!(matches!(
1132            transfer.update(&sk, current.target()),
1133            Err(PointerError::CounterExhausted)
1134        ));
1135        assert!(
1136            matches!(
1137                transfer.transfer_to(&sk, [0; XORNAME_LEN]),
1138                Err(PointerError::CounterExhausted)
1139            ),
1140            "finalizing twice is refused rather than signed into a fork"
1141        );
1142        let grinded = Pointer::sign(
1143            &sk,
1144            &pk,
1145            FINAL_COUNTER,
1146            PointerTarget::new(PointerTargetKind::Pointer, [0; XORNAME_LEN]),
1147        )
1148        .expect("sign");
1149        assert!(!grinded.replaces(&transfer));
1150    }
1151
1152    #[test]
1153    fn an_earlier_record_can_be_finalized_twice_and_neither_replaces_the_other() {
1154        // The refusal to finalize a final record is a guard for the caller
1155        // that holds it. The owner still holds the earlier record and its key,
1156        // so it can sign a second, different final state. The merge rule does
1157        // not order the two: a node keeps whichever it took first.
1158        let (pk, sk) = keypair(44);
1159        let current = Pointer::create(
1160            &sk,
1161            &pk,
1162            PointerTarget::new(PointerTargetKind::Chunk, [1; XORNAME_LEN]),
1163        )
1164        .expect("create");
1165        let to_a = current.transfer_to(&sk, [0xA; XORNAME_LEN]).expect("first");
1166        let to_b = current
1167            .transfer_to(&sk, [0xB; XORNAME_LEN])
1168            .expect("second");
1169        assert_ne!(to_a.state().state_id, to_b.state().state_id);
1170        assert!(to_a.replaces(&current) && to_b.replaces(&current));
1171        assert!(
1172            !to_a.replaces(&to_b),
1173            "neither final state replaces the other"
1174        );
1175        assert!(!to_b.replaces(&to_a));
1176    }
1177
1178    #[test]
1179    fn only_a_final_pointer_target_is_a_transfer() {
1180        let (pk, sk) = keypair(43);
1181        let forward = PointerTarget::new(PointerTargetKind::Pointer, [7; XORNAME_LEN]);
1182        let chunk = PointerTarget::new(PointerTargetKind::Chunk, [7; XORNAME_LEN]);
1183
1184        // A pointer target below the final counter is a forwarding the owner
1185        // can still take back, not a transfer.
1186        let forwarding = Pointer::sign(&sk, &pk, FINAL_COUNTER - 1, forward).expect("sign");
1187        assert_eq!(forwarding.transferred_to(), None);
1188
1189        // A final chunk target freezes the pointer at the nodes that hold it;
1190        // nobody receives it.
1191        let frozen = Pointer::sign(&sk, &pk, FINAL_COUNTER, chunk).expect("sign");
1192        assert_eq!(frozen.transferred_to(), None);
1193
1194        let handed_on = Pointer::sign(&sk, &pk, FINAL_COUNTER, forward).expect("sign");
1195        assert_eq!(handed_on.transferred_to(), Some([7; XORNAME_LEN]));
1196    }
1197
1198    #[test]
1199    fn with_final_states_a_fold_keeps_the_first_final_it_meets() {
1200        // A node folds `replaces` over arrivals in arrival order. Below the
1201        // final counter the order is total, so the fold ends in one place
1202        // whatever the order; two final states are the one pair it does not
1203        // order, so the fold keeps whichever came first. That is the
1204        // first-come rule, and the only place arrival order matters.
1205        let fold = |arrivals: &[&Pointer]| -> XorName {
1206            let mut held: Option<&Pointer> = None;
1207            for arrival in arrivals {
1208                if held.is_none_or(|h| arrival.replaces(h)) {
1209                    held = Some(arrival);
1210                }
1211            }
1212            held.expect("something arrived").state_id()
1213        };
1214        let early = signed(44, 3, 1);
1215        let final_a = signed(44, FINAL_COUNTER, 9);
1216        let final_b = signed(44, FINAL_COUNTER, 1);
1217
1218        assert_eq!(fold(&[&early, &final_a, &final_b]), final_a.state_id());
1219        assert_eq!(fold(&[&final_b, &early, &final_a]), final_b.state_id());
1220        assert_eq!(fold(&[&final_a, &final_b, &early]), final_a.state_id());
1221        assert_eq!(fold(&[&early, &final_b]), final_b.state_id());
1222    }
1223
1224    #[test]
1225    fn create_starts_at_zero_and_update_adds_one() {
1226        let (pk, sk) = keypair(20);
1227        let target = |b: u8| PointerTarget::new(PointerTargetKind::Chunk, [b; XORNAME_LEN]);
1228
1229        let created = Pointer::create(&sk, &pk, target(1)).expect("create");
1230        assert_eq!(created.counter(), 0);
1231
1232        let first = created.update(&sk, target(2)).expect("update");
1233        assert_eq!(first.counter(), 1);
1234        assert!(first.replaces(&created));
1235
1236        let second = first.update(&sk, target(3)).expect("update");
1237        assert_eq!(second.counter(), 2);
1238        assert!(second.replaces(&first));
1239    }
1240
1241    #[test]
1242    fn any_larger_counter_replaces_so_counters_may_skip() {
1243        // The counter orders states; it does not meter them. Each stored state
1244        // is paid for once whatever its number, so a skipped number is never
1245        // stored and owes nothing.
1246        let held = signed(21, 5, 7);
1247        for later in [6u64, 7, 99, u64::MAX] {
1248            assert!(
1249                signed(21, later, 7).replaces(&held),
1250                "counter {later} must replace counter 5"
1251            );
1252        }
1253        for earlier in [0u64, 4] {
1254            assert!(
1255                !signed(21, earlier, 7).replaces(&held),
1256                "counter {earlier} must not replace counter 5"
1257            );
1258        }
1259    }
1260
1261    #[test]
1262    fn a_fork_at_one_counter_resolves_by_target_and_any_later_counter_heals_it() {
1263        // Two records at one counter are a fork. Every node that sees both
1264        // keeps the smaller target, and a record at any later counter replaces
1265        // whichever side a node was left holding.
1266        let smaller = signed(25, 5, 1);
1267        let larger = signed(25, 5, 9);
1268        assert!(smaller.replaces(&larger), "smaller target bytes win");
1269        assert!(!larger.replaces(&smaller));
1270
1271        for later in [6u64, 50] {
1272            let healed = signed(25, later, 9);
1273            assert!(healed.replaces(&smaller));
1274            assert!(healed.replaces(&larger));
1275        }
1276    }
1277
1278    #[test]
1279    fn a_terminal_pointer_cannot_be_updated() {
1280        let (pk, sk) = keypair(24);
1281        let target = PointerTarget::new(PointerTargetKind::Chunk, [1; XORNAME_LEN]);
1282        let terminal = Pointer::sign(&sk, &pk, u64::MAX, target).expect("sign");
1283        assert!(
1284            terminal.update(&sk, target).is_err(),
1285            "the counter must not wrap around into a fresh-looking pointer"
1286        );
1287    }
1288
1289    /// Re-encoding must be byte-identical to what was signed.
1290    ///
1291    /// This is what lets the record hold its five fields and nothing else: the
1292    /// encoding is fixed-width with no optionality, so there is exactly one
1293    /// byte sequence for a given record and caching it would buy nothing. If
1294    /// this ever fails, the cache has to come back.
1295    #[test]
1296    fn re_encoding_is_byte_identical() {
1297        let (pk, sk) = keypair(30);
1298        for counter in [0u64, 1, 42, u64::MAX] {
1299            for tag in [0u8, 1, 200] {
1300                let target = PointerTarget::from_raw_tag(tag, [tag; XORNAME_LEN]);
1301                let signed = Pointer::sign(&sk, &pk, counter, target).expect("sign");
1302                let wire = signed.to_bytes();
1303
1304                // Parsed from the wire, re-encoded, identical.
1305                let parsed = Pointer::from_bytes(&wire).expect("parse");
1306                assert_eq!(parsed.to_bytes(), wire, "counter {counter}, tag {tag}");
1307
1308                // And still verifies, which is the property that actually matters.
1309                assert!(Pointer::from_bytes(&parsed.to_bytes()).is_ok());
1310            }
1311        }
1312    }
1313
1314    #[test]
1315    fn equal_state_never_replaces() {
1316        let variants: Vec<Pointer> = (0..8).map(|_| signed(1, 7, 3)).collect();
1317        for a in &variants {
1318            for b in &variants {
1319                assert!(!a.replaces(b));
1320                assert_eq!(a.state_id(), b.state_id());
1321            }
1322        }
1323    }
1324
1325    #[test]
1326    fn the_obvious_chunk_preimages_miss_both_pointer_identities() {
1327        // A chunk's address is BLAKE3 over its content, so a hash of a prefix
1328        // and a key was reachable by anyone who could write that prefix as a
1329        // chunk: they could squat an owner's address before it was created, or
1330        // buy a pointer and a chunk with one payment. These are the preimages
1331        // that construction handed out. Nothing here proves no content exists —
1332        // that is a preimage assumption, not a test.
1333        let (pk, sk) = keypair(77);
1334        let record = Pointer::create(
1335            &sk,
1336            &pk,
1337            PointerTarget::new(PointerTargetKind::Chunk, [3; XORNAME_LEN]),
1338        )
1339        .expect("create");
1340
1341        let address_preimage = [b"autonomi.pointer.address.v1".as_slice(), &pk.to_bytes()].concat();
1342        assert_ne!(
1343            *blake3::hash(&address_preimage).as_bytes(),
1344            record.address(),
1345            "a chunk holding this content must not land on the pointer's address"
1346        );
1347
1348        let body = record.to_bytes();
1349        let body = body.get(..POINTER_BODY_LEN).expect("body");
1350        let state_preimage = [b"autonomi.pointer.state.v1".as_slice(), body].concat();
1351        assert_ne!(
1352            *blake3::hash(&state_preimage).as_bytes(),
1353            record.state_id(),
1354            "a chunk holding this content must not land on the paid identifier"
1355        );
1356
1357        // Nor does the bare key, the bare body, or the context alone.
1358        for content in [
1359            pk.to_bytes().as_slice(),
1360            body,
1361            b"autonomi.pointer.address.v1",
1362        ] {
1363            let address = *blake3::hash(content).as_bytes();
1364            assert_ne!(address, record.address());
1365            assert_ne!(address, record.state_id());
1366        }
1367    }
1368
1369    #[test]
1370    fn the_rule_is_a_strict_order_so_only_final_states_depend_on_arrival_order() {
1371        // Nodes and clients both pick a winner by folding `replaces` over
1372        // whatever arrives. That fold gives the same answer whatever the order
1373        // exactly when the rule is a strict total order on one address: never
1374        // both ways round, always one way for distinct states, and transitive.
1375        // It is, for every pair but two different final states — the pair the
1376        // first-come rule leaves unordered on purpose.
1377        let records = [
1378            signed(1, 0, 9),
1379            signed(1, 2, 1),
1380            signed(1, 5, 1),
1381            signed(1, 5, 2),
1382            signed(1, FINAL_COUNTER, 3),
1383            signed(1, FINAL_COUNTER, 4),
1384        ];
1385        for a in &records {
1386            assert!(!a.replaces(a), "an equal state never replaces");
1387            for b in &records {
1388                assert!(
1389                    !(a.replaces(b) && b.replaces(a)),
1390                    "two records cannot each replace the other"
1391                );
1392                let both_final = a.is_terminal() && b.is_terminal();
1393                assert!(
1394                    a.replaces(b) || b.replaces(a) || a.state_id() == b.state_id() || both_final,
1395                    "distinct states are always ordered unless both are final"
1396                );
1397                for c in &records {
1398                    assert!(
1399                        !(a.replaces(b) && b.replaces(c)) || a.replaces(c),
1400                        "the order is transitive"
1401                    );
1402                }
1403            }
1404        }
1405    }
1406}