mainline/common/
mutable.rs

1//! Helper functions and structs for mutable items.
2
3use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
4use serde::{Deserialize, Serialize};
5use sha1_smol::Sha1;
6use std::convert::TryFrom;
7
8use crate::Id;
9
10use super::PutMutableRequestArguments;
11
12#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
13/// [BEP_0044](https://www.bittorrent.org/beps/bep_0044.html)'s Mutable item.
14pub struct MutableItem {
15    /// hash of the key and optional salt
16    target: Id,
17    /// ed25519 public key
18    key: [u8; 32],
19    /// sequence number
20    pub(crate) seq: i64,
21    /// mutable value
22    pub(crate) value: Box<[u8]>,
23    /// ed25519 signature
24    #[serde(with = "serde_bytes")]
25    signature: [u8; 64],
26    /// Optional salt
27    salt: Option<Box<[u8]>>,
28}
29
30impl MutableItem {
31    /// Create a new mutable item from a signing key, value, sequence number and optional salt.
32    pub fn new(signer: SigningKey, value: &[u8], seq: i64, salt: Option<&[u8]>) -> Self {
33        let signable = encode_signable(seq, value, salt);
34        let signature = signer.sign(&signable);
35
36        Self::new_signed_unchecked(
37            signer.verifying_key().to_bytes(),
38            signature.into(),
39            value,
40            seq,
41            salt,
42        )
43    }
44
45    /// Return the target of a [MutableItem] by hashing its `public_key` and an optional `salt`
46    pub fn target_from_key(public_key: &[u8; 32], salt: Option<&[u8]>) -> Id {
47        let mut encoded = vec![];
48
49        encoded.extend(public_key);
50
51        if let Some(salt) = salt {
52            encoded.extend(salt);
53        }
54
55        let mut hasher = Sha1::new();
56        hasher.update(&encoded);
57        let bytes = hasher.digest().bytes();
58
59        bytes.into()
60    }
61
62    /// Create a new mutable item from an already signed value.
63    pub fn new_signed_unchecked(
64        key: [u8; 32],
65        signature: [u8; 64],
66        value: &[u8],
67        seq: i64,
68        salt: Option<&[u8]>,
69    ) -> Self {
70        Self {
71            target: MutableItem::target_from_key(&key, salt),
72            key,
73            value: value.into(),
74            seq,
75            signature,
76            salt: salt.map(|s| s.into()),
77        }
78    }
79
80    pub(crate) fn from_dht_message(
81        target: Id,
82        key: &[u8],
83        v: Box<[u8]>,
84        seq: i64,
85        signature: &[u8],
86        salt: Option<Box<[u8]>>,
87    ) -> Result<Self, MutableError> {
88        let key = VerifyingKey::try_from(key).map_err(|_| MutableError::InvalidMutablePublicKey)?;
89
90        let signature =
91            Signature::from_slice(signature).map_err(|_| MutableError::InvalidMutableSignature)?;
92
93        key.verify(&encode_signable(seq, &v, salt.as_deref()), &signature)
94            .map_err(|_| MutableError::InvalidMutableSignature)?;
95
96        Ok(Self {
97            target,
98            key: key.to_bytes(),
99            value: v,
100            seq,
101            signature: signature.to_bytes(),
102            salt,
103        })
104    }
105
106    // === Getters ===
107
108    /// Returns the target (info hash) of this item.
109    pub fn target(&self) -> &Id {
110        &self.target
111    }
112
113    /// Returns a reference to the 32 bytes Ed25519 public key of this item.
114    pub fn key(&self) -> &[u8; 32] {
115        &self.key
116    }
117
118    /// Returns a byte slice of the value of this item.
119    pub fn value(&self) -> &[u8] {
120        &self.value
121    }
122
123    /// Returns the `seq` (sequence) number of this item.
124    pub fn seq(&self) -> i64 {
125        self.seq
126    }
127
128    /// Returns the signature over this item.
129    pub fn signature(&self) -> &[u8; 64] {
130        &self.signature
131    }
132
133    /// Returns the `Salt` value used for generating the
134    /// [Self::target] if any.
135    pub fn salt(&self) -> Option<&[u8]> {
136        self.salt.as_deref()
137    }
138}
139
140pub fn encode_signable(seq: i64, value: &[u8], salt: Option<&[u8]>) -> Box<[u8]> {
141    let mut signable = vec![];
142
143    if let Some(salt) = salt {
144        signable.extend(format!("4:salt{}:", salt.len()).into_bytes());
145        signable.extend(salt);
146    }
147
148    signable.extend(format!("3:seqi{}e1:v{}:", seq, value.len()).into_bytes());
149    signable.extend(value);
150
151    signable.into()
152}
153
154#[derive(thiserror::Error, Debug)]
155/// Mainline crate error enum.
156pub enum MutableError {
157    #[error("Invalid mutable item signature")]
158    /// Invalid mutable item signature
159    InvalidMutableSignature,
160
161    #[error("Invalid mutable item public key")]
162    /// Invalid mutable item public key
163    InvalidMutablePublicKey,
164}
165
166impl PutMutableRequestArguments {
167    /// Create a [PutMutableRequestArguments] from a [MutableItem],
168    /// and an optional CAS condition, which is usually the [MutableItem::seq]
169    /// of the most recent known [MutableItem]
170    pub fn from(item: MutableItem, cas: Option<i64>) -> Self {
171        Self {
172            target: item.target,
173            v: item.value,
174            k: item.key,
175            seq: item.seq,
176            sig: item.signature,
177            salt: item.salt,
178            cas,
179        }
180    }
181}
182
183impl From<PutMutableRequestArguments> for MutableItem {
184    fn from(request: PutMutableRequestArguments) -> Self {
185        Self {
186            target: request.target,
187            value: request.v,
188            key: request.k,
189            seq: request.seq,
190            signature: request.sig,
191            salt: request.salt,
192        }
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn signable_without_salt() {
202        let signable = encode_signable(4, b"Hello world!", None);
203
204        assert_eq!(&*signable, b"3:seqi4e1:v12:Hello world!");
205    }
206    #[test]
207    fn signable_with_salt() {
208        let signable = encode_signable(4, b"Hello world!", Some(b"foobar"));
209
210        assert_eq!(&*signable, b"4:salt6:foobar3:seqi4e1:v12:Hello world!");
211    }
212}