rust_ipns/
lib.rs

1use std::ops::Add;
2use bytes::Bytes;
3use chrono::DateTime;
4use chrono::Duration;
5use chrono::FixedOffset;
6use chrono::SecondsFormat;
7use chrono::Utc;
8use cid::Cid;
9use libp2p_identity::Keypair;
10use libp2p_identity::PeerId;
11use libp2p_identity::PublicKey;
12use quick_protobuf::MessageWrite;
13use quick_protobuf::Writer;
14use quick_protobuf::{BytesReader, MessageRead};
15use serde::{Deserialize, Serialize, Serializer};
16
17mod generate;
18
19#[derive(
20    Clone,
21    Copy,
22    Debug,
23    PartialEq,
24    Eq,
25    Hash,
26    PartialOrd,
27    Ord,
28    derive_more::Display,
29)]
30#[repr(i32)]
31pub enum ValidityType {
32    EOL = 0,
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 mut signature_v2_construct = vec![
236            0x69, 0x70, 0x6e, 0x73, 0x2d, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65,
237            0x3a,
238        ];
239
240        signature_v2_construct.extend(data.iter());
241
242        let signature_v2 = keypair
243            .sign(&signature_v2_construct)
244            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
245
246        let public_key = match keypair.key_type().into() {
247            KeyType::RSA => keypair
248                .to_protobuf_encoding()
249                .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?,
250            _ => vec![],
251        };
252
253        Ok(Record {
254            data,
255            value,
256            validity_type,
257            validity,
258            sequence: seq,
259            ttl,
260            public_key,
261            signature_v1,
262            signature_v2,
263        })
264    }
265
266    pub fn decode(data: impl AsRef<[u8]>) -> std::io::Result<Self> {
267        let data = data.as_ref();
268
269        if data.len() > 10 * 1024 {
270            return Err(std::io::Error::from(std::io::ErrorKind::InvalidData));
271        }
272
273        let mut reader = BytesReader::from_bytes(data);
274        let entry = generate::ipns_pb::IpnsEntry::from_reader(&mut reader, data)
275            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
276        let record = entry.into();
277        Ok(record)
278    }
279
280    pub fn encode(&self) -> std::io::Result<Vec<u8>> {
281        let entry: generate::ipns_pb::IpnsEntry = self.into();
282
283        let mut buf = Vec::with_capacity(entry.get_size());
284        let mut writer = Writer::new(&mut buf);
285
286        entry
287            .write_message(&mut writer)
288            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
289
290        Ok(buf)
291    }
292}
293
294impl Record {
295    pub fn sequence(&self) -> u64 {
296        self.sequence
297    }
298
299    pub fn validity_type(&self) -> ValidityType {
300        self.validity_type
301    }
302
303    pub fn validity(&self) -> std::io::Result<DateTime<FixedOffset>> {
304        let time = String::from_utf8_lossy(&self.validity);
305        chrono::DateTime::parse_from_rfc3339(&time)
306            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
307    }
308
309    pub fn ttl(&self) -> u64 {
310        self.ttl
311    }
312
313    pub fn signature_v1(&self) -> bool {
314        !self.signature_v1.is_empty()
315    }
316
317    pub fn signature_v2(&self) -> bool {
318        !self.signature_v2.is_empty()
319    }
320
321    pub fn data(&self) -> std::io::Result<Data> {
322        let data: Data = serde_ipld_dagcbor::from_slice(&self.data)
323            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
324
325        if data.value != self.value
326            || data.validity != self.validity
327            || data.validity_type != self.validity_type
328            || data.sequence != self.sequence
329            || data.ttl != self.ttl
330        {
331            return Err(std::io::Error::from(std::io::ErrorKind::InvalidData));
332        }
333
334        Ok(data)
335    }
336
337    pub fn value(&self) -> std::io::Result<Cid> {
338        let cid_str = String::from_utf8_lossy(&self.value);
339        Cid::try_from(cid_str.as_ref())
340            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
341    }
342
343    #[cfg(feature = "libp2p")]
344    pub fn verify(&self, peer_id: PeerId) -> std::io::Result<()> {
345        use multihash::Multihash;
346
347        if self.signature_v2.is_empty() && self.signature_v1.is_empty() {
348            return Err(std::io::Error::new(
349                std::io::ErrorKind::Other,
350                "Empty signature field",
351            ));
352        }
353
354        if self.data.is_empty() {
355            return Err(std::io::Error::new(
356                std::io::ErrorKind::InvalidData,
357                "Empty data field",
358            ));
359        }
360
361        let key = peer_id.to_bytes();
362
363        let mh = Multihash::from_bytes(&key).expect("valid hash");
364        let cid = Cid::new_v1(0x72, mh);
365
366        let public_key = match self.public_key.is_empty() {
367            true => cid.hash().digest(),
368            //TODO: Validate internal public key against the multhash publickey
369            false => self.public_key.as_ref(),
370        };
371
372        let pk = PublicKey::try_decode_protobuf(public_key)
373            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
374
375        //TODO: Implement support for RSA
376        if matches!(pk.key_type().into(), KeyType::RSA) {
377            return Err(std::io::Error::new(
378                std::io::ErrorKind::Unsupported,
379                "RSA Keys are not supported at this time",
380            ));
381        }
382
383        let mut signature_v2 = vec![
384            0x69, 0x70, 0x6e, 0x73, 0x2d, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65,
385            0x3a,
386        ];
387
388        signature_v2.extend(self.data.iter());
389
390        self.data()?;
391
392        if !pk.verify(&signature_v2, &self.signature_v2) {
393            return Err(std::io::Error::new(
394                std::io::ErrorKind::InvalidData,
395                "Signature is invalid",
396            ));
397        }
398
399        Ok(())
400    }
401}