Skip to main content

rust_ipns/
lib.rs

1use bytes::Bytes;
2use chrono::DateTime;
3use chrono::Duration;
4use chrono::FixedOffset;
5use chrono::SecondsFormat;
6use chrono::Utc;
7use cid::Cid;
8use libp2p_identity::Keypair;
9use libp2p_identity::PeerId;
10use libp2p_identity::PublicKey;
11use quick_protobuf::MessageWrite;
12use quick_protobuf::Writer;
13use quick_protobuf::{BytesReader, MessageRead};
14use serde::{Deserialize, Serialize, Serializer};
15use std::ops::Add;
16
17mod generate;
18
19const SIGNATURE_V2_BASE: &[u8] = &[
20    0x69, 0x70, 0x6e, 0x73, 0x2d, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x3a,
21];
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
24#[repr(i32)]
25pub enum ValidityType {
26    EOL = 0,
27}
28
29impl std::fmt::Display for ValidityType {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        write!(f, "EOL")
32    }
33}
34
35impl Serialize for ValidityType {
36    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
37    where
38        S: Serializer,
39    {
40        serializer.serialize_i32(*self as i32)
41    }
42}
43
44impl<'de> Deserialize<'de> for ValidityType {
45    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
46    where
47        D: serde::Deserializer<'de>,
48    {
49        let i = i32::deserialize(deserializer)?;
50        ValidityType::try_from(i).map_err(serde::de::Error::custom)
51    }
52}
53
54impl TryFrom<i32> for ValidityType {
55    type Error = std::io::Error;
56    fn try_from(i: i32) -> Result<Self, Self::Error> {
57        match i {
58            0 => Ok(ValidityType::EOL),
59            _ => Err(std::io::Error::new(
60                std::io::ErrorKind::InvalidData,
61                "invalid validity type",
62            )),
63        }
64    }
65}
66
67impl From<ValidityType> for i32 {
68    fn from(ty: ValidityType) -> Self {
69        ty as i32
70    }
71}
72
73impl From<generate::ipns_pb::mod_IpnsEntry::ValidityType> for ValidityType {
74    fn from(v_ty: generate::ipns_pb::mod_IpnsEntry::ValidityType) -> Self {
75        match v_ty {
76            generate::ipns_pb::mod_IpnsEntry::ValidityType::EOL => ValidityType::EOL,
77        }
78    }
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
82#[repr(i32)]
83pub enum KeyType {
84    RSA = 0,
85    Ed25519 = 1,
86    Secp256k1 = 2,
87    ECDSA = 3,
88}
89
90#[cfg(feature = "libp2p")]
91impl From<libp2p_identity::KeyType> for KeyType {
92    fn from(ty: libp2p_identity::KeyType) -> Self {
93        match ty {
94            libp2p_identity::KeyType::Ed25519 => KeyType::Ed25519,
95            libp2p_identity::KeyType::RSA => KeyType::RSA,
96            libp2p_identity::KeyType::Secp256k1 => KeyType::Secp256k1,
97            libp2p_identity::KeyType::Ecdsa => KeyType::ECDSA,
98        }
99    }
100}
101
102#[derive(Clone, Debug)]
103pub struct Record {
104    data: Vec<u8>,
105
106    value: Vec<u8>,
107    validity_type: ValidityType,
108    validity: Vec<u8>,
109    sequence: u64,
110    ttl: u64,
111
112    public_key: Vec<u8>,
113
114    signature_v1: Vec<u8>,
115    signature_v2: Vec<u8>,
116}
117
118impl From<generate::ipns_pb::IpnsEntry<'_>> for Record {
119    fn from(entry: generate::ipns_pb::IpnsEntry<'_>) -> Self {
120        Record {
121            data: entry.data.into(),
122            value: entry.value.into(),
123            validity_type: entry.validityType.into(),
124            validity: entry.validity.into(),
125            sequence: entry.sequence,
126            ttl: entry.ttl,
127            public_key: entry.pubKey.into(),
128            signature_v1: entry.signatureV1.into(),
129            signature_v2: entry.signatureV2.into(),
130        }
131    }
132}
133
134impl<'a> From<&'a Record> for generate::ipns_pb::IpnsEntry<'a> {
135    fn from(record: &'a Record) -> Self {
136        generate::ipns_pb::IpnsEntry {
137            validity: (&record.validity).into(),
138            validityType: generate::ipns_pb::mod_IpnsEntry::ValidityType::EOL,
139            value: (&record.value).into(),
140            signatureV1: (&record.signature_v1).into(),
141            signatureV2: (&record.signature_v2).into(),
142            sequence: record.sequence,
143            pubKey: (&record.public_key).into(),
144            ttl: record.ttl,
145            data: (&record.data).into(),
146        }
147    }
148}
149
150// Fields of the Bytes type are used here instead of Vec<u8> to ensure that
151// these fields are (de)serialized into "byte string" CBOR values instead of simple arrays.
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct Data {
154    #[serde(rename = "Value")]
155    pub value: Bytes,
156
157    #[serde(rename = "ValidityType")]
158    pub validity_type: ValidityType,
159
160    #[serde(rename = "Validity")]
161    pub validity: Bytes,
162
163    #[serde(rename = "Sequence")]
164    pub sequence: u64,
165
166    #[serde(rename = "TTL")]
167    pub ttl: u64,
168}
169
170impl Data {
171    pub fn value(&self) -> &[u8] {
172        &self.value
173    }
174
175    pub fn validity_type(&self) -> ValidityType {
176        self.validity_type
177    }
178
179    pub fn validity(&self) -> &[u8] {
180        &self.validity
181    }
182
183    pub fn sequence(&self) -> u64 {
184        self.sequence
185    }
186
187    pub fn ttl(&self) -> u64 {
188        self.ttl
189    }
190}
191
192impl Record {
193    #[cfg(feature = "libp2p")]
194    pub fn new(
195        keypair: &Keypair,
196        value: impl AsRef<[u8]>,
197        duration: Duration,
198        seq: u64,
199        ttl: u64,
200    ) -> std::io::Result<Self> {
201        let value = value.as_ref().to_vec();
202
203        let validity = Utc::now()
204            .add(duration)
205            .to_rfc3339_opts(SecondsFormat::Nanos, true)
206            .into_bytes();
207
208        let validity_type = ValidityType::EOL;
209
210        let signature_v1_construct = {
211            let mut data = Vec::with_capacity(value.len() + validity.len() + 3);
212
213            data.extend(value.iter());
214            data.extend(validity.iter());
215            data.extend(validity_type.to_string().as_bytes());
216
217            data
218        };
219
220        let signature_v1 = keypair
221            .sign(&signature_v1_construct)
222            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
223
224        let document = Data {
225            value: Bytes::from(value.clone()),
226            validity_type,
227            validity: Bytes::from(validity.clone()),
228            sequence: seq,
229            ttl,
230        };
231
232        let data = serde_ipld_dagcbor::to_vec(&document)
233            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
234
235        let signature_v2_construct = SIGNATURE_V2_BASE
236            .iter()
237            .chain(data.iter())
238            .copied()
239            .collect::<Vec<_>>();
240
241        let signature_v2 = keypair
242            .sign(&signature_v2_construct)
243            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
244
245        let public_key = match keypair.key_type().into() {
246            KeyType::RSA => keypair
247                .to_protobuf_encoding()
248                .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?,
249            _ => vec![],
250        };
251
252        Ok(Record {
253            data,
254            value,
255            validity_type,
256            validity,
257            sequence: seq,
258            ttl,
259            public_key,
260            signature_v1,
261            signature_v2,
262        })
263    }
264
265    pub fn decode(data: impl AsRef<[u8]>) -> std::io::Result<Self> {
266        let data = data.as_ref();
267
268        if data.len() > 10 * 1024 {
269            return Err(std::io::Error::from(std::io::ErrorKind::InvalidData));
270        }
271
272        let mut reader = BytesReader::from_bytes(data);
273        let entry = generate::ipns_pb::IpnsEntry::from_reader(&mut reader, data)
274            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
275        let record = entry.into();
276        Ok(record)
277    }
278
279    pub fn encode(&self) -> std::io::Result<Vec<u8>> {
280        let entry: generate::ipns_pb::IpnsEntry = self.into();
281
282        let mut buf = Vec::with_capacity(entry.get_size());
283        let mut writer = Writer::new(&mut buf);
284
285        entry
286            .write_message(&mut writer)
287            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
288
289        Ok(buf)
290    }
291}
292
293impl Record {
294    pub fn sequence(&self) -> u64 {
295        self.sequence
296    }
297
298    pub fn validity_type(&self) -> ValidityType {
299        self.validity_type
300    }
301
302    pub fn validity(&self) -> std::io::Result<DateTime<FixedOffset>> {
303        let time = String::from_utf8_lossy(&self.validity);
304        chrono::DateTime::parse_from_rfc3339(&time)
305            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
306    }
307
308    pub fn ttl(&self) -> u64 {
309        self.ttl
310    }
311
312    pub fn signature_v1(&self) -> bool {
313        !self.signature_v1.is_empty()
314    }
315
316    pub fn signature_v2(&self) -> bool {
317        !self.signature_v2.is_empty()
318    }
319
320    pub fn data(&self) -> std::io::Result<Data> {
321        let data: Data = serde_ipld_dagcbor::from_slice(&self.data)
322            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
323
324        if data.value != self.value
325            || data.validity != self.validity
326            || data.validity_type != self.validity_type
327            || data.sequence != self.sequence
328            || data.ttl != self.ttl
329        {
330            return Err(std::io::Error::from(std::io::ErrorKind::InvalidData));
331        }
332
333        Ok(data)
334    }
335
336    pub fn value(&self) -> std::io::Result<Cid> {
337        let cid_str = String::from_utf8_lossy(&self.value);
338        Cid::try_from(cid_str.as_ref())
339            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
340    }
341
342    #[cfg(feature = "libp2p")]
343    pub fn verify(&self, peer_id: PeerId) -> std::io::Result<()> {
344        use multihash::Multihash;
345
346        if self.signature_v2.is_empty() && self.signature_v1.is_empty() {
347            return Err(std::io::Error::new(
348                std::io::ErrorKind::Other,
349                "Empty signature field",
350            ));
351        }
352
353        if self.data.is_empty() {
354            return Err(std::io::Error::new(
355                std::io::ErrorKind::InvalidData,
356                "Empty data field",
357            ));
358        }
359
360        let key = peer_id.to_bytes();
361
362        let mh = Multihash::from_bytes(&key).expect("valid hash");
363        let cid = Cid::new_v1(0x72, mh);
364
365        let public_key = match self.public_key.is_empty() {
366            true => cid.hash().digest(),
367            //TODO: Validate internal public key against the multhash publickey
368            false => self.public_key.as_ref(),
369        };
370
371        let pk = PublicKey::try_decode_protobuf(public_key)
372            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
373
374        //TODO: Implement support for RSA
375        if matches!(pk.key_type().into(), KeyType::RSA) {
376            return Err(std::io::Error::new(
377                std::io::ErrorKind::Unsupported,
378                "RSA Keys are not supported at this time",
379            ));
380        }
381
382        let signature_v2 = SIGNATURE_V2_BASE
383            .iter()
384            .chain(self.data.iter())
385            .copied()
386            .collect::<Vec<_>>();
387
388        self.data()?;
389
390        if !pk.verify(&signature_v2, &self.signature_v2) {
391            return Err(std::io::Error::new(
392                std::io::ErrorKind::InvalidData,
393                "Signature is invalid",
394            ));
395        }
396
397        Ok(())
398    }
399}