Skip to main content

scp2p_core/
ids.rs

1// Copyright (c) 2024-2026 Vanyo Vanev / Tech Art Ltd
2// SPDX-License-Identifier: MPL-2.0
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7use ed25519_dalek::VerifyingKey;
8use sha2::{Digest, Sha256};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct NodeId(pub [u8; 20]);
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct ShareId(pub [u8; 32]);
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct ContentId(pub [u8; 32]);
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct ManifestId(pub [u8; 32]);
21
22impl NodeId {
23    pub fn from_pubkey(pubkey: &VerifyingKey) -> Self {
24        Self::from_pubkey_bytes(pubkey.as_bytes())
25    }
26
27    pub fn from_pubkey_bytes(pubkey: &[u8; 32]) -> Self {
28        let digest = Sha256::digest(pubkey);
29        let mut id = [0u8; 20];
30        id.copy_from_slice(&digest[..20]);
31        Self(id)
32    }
33
34    pub fn xor_distance(&self, other: &Self) -> [u8; 20] {
35        let mut out = [0u8; 20];
36        for (idx, byte) in out.iter_mut().enumerate() {
37            *byte = self.0[idx] ^ other.0[idx];
38        }
39        out
40    }
41
42    /// Deprecated: use [`NodeId::xor_distance_cmp`] for clarity.
43    #[deprecated(note = "use NodeId::xor_distance_cmp(a, b, target) instead")]
44    pub fn distance_cmp(&self, target: &Self, other: &Self) -> std::cmp::Ordering {
45        Self::xor_distance_cmp(self, other, target)
46    }
47
48    /// Compare which of `a` or `b` is closer to `target` in XOR-distance.
49    ///
50    /// Returns `Ordering::Less` when `a` is closer, `Greater` when `b` is,
51    /// and `Equal` when equidistant.
52    pub fn xor_distance_cmp(a: &Self, b: &Self, target: &Self) -> std::cmp::Ordering {
53        let da = a.xor_distance(target);
54        let db = b.xor_distance(target);
55        da.cmp(&db)
56    }
57}
58
59impl ShareId {
60    pub fn from_pubkey(pubkey: &VerifyingKey) -> Self {
61        Self::from_pubkey_bytes(pubkey.as_bytes())
62    }
63
64    pub fn from_pubkey_bytes(pubkey: &[u8; 32]) -> Self {
65        let digest = Sha256::digest(pubkey);
66        let mut id = [0u8; 32];
67        id.copy_from_slice(&digest[..]);
68        Self(id)
69    }
70
71    pub fn xor_distance(&self, other: &Self) -> [u8; 20] {
72        let mut out = [0u8; 20];
73        for (idx, byte) in out.iter_mut().enumerate() {
74            *byte = self.0[idx] ^ other.0[idx];
75        }
76        out
77    }
78
79    /// Deprecated: use [`ShareId::xor_distance_cmp`] for clarity.
80    #[deprecated(note = "use ShareId::xor_distance_cmp(a, b, target) instead")]
81    pub fn distance_cmp(&self, target: &Self, other: &Self) -> std::cmp::Ordering {
82        Self::xor_distance_cmp(self, other, target)
83    }
84
85    /// Compare which of `a` or `b` is closer to `target` in XOR-distance.
86    pub fn xor_distance_cmp(a: &Self, b: &Self, target: &Self) -> std::cmp::Ordering {
87        let da = a.xor_distance(target);
88        let db = b.xor_distance(target);
89        da.cmp(&db)
90    }
91}
92
93impl ContentId {
94    pub fn from_bytes(bytes: &[u8]) -> Self {
95        Self(*blake3::hash(bytes).as_bytes())
96    }
97}
98
99impl ManifestId {
100    pub fn from_manifest_bytes(bytes: &[u8]) -> Self {
101        Self(*blake3::hash(bytes).as_bytes())
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use ed25519_dalek::SigningKey;
109    use rand::rngs::OsRng;
110
111    #[test]
112    fn node_id_len_matches_spec() {
113        let mut rng = OsRng;
114        let key = SigningKey::generate(&mut rng);
115        let node_id = NodeId::from_pubkey(&key.verifying_key());
116        assert_eq!(node_id.0.len(), 20);
117    }
118
119    #[test]
120    fn content_id_is_stable() {
121        let a = ContentId::from_bytes(b"scp2p");
122        let b = ContentId::from_bytes(b"scp2p");
123        assert_eq!(a, b);
124    }
125
126    #[test]
127    fn node_distance_compare_orders_closest() {
128        let target = NodeId([0u8; 20]);
129        let a = NodeId([1u8; 20]);
130        let b = NodeId([2u8; 20]);
131        assert!(NodeId::xor_distance_cmp(&a, &b, &target).is_lt());
132    }
133}