bc_components/
seed.rs

1use bc_rand::{rng_random_data, RandomNumberGenerator};
2use bc_ur::prelude::*;
3use crate::{ tags, PrivateKeyDataProvider };
4use anyhow::{ bail, Result };
5
6/// A cryptographic seed for deterministic key generation.
7///
8/// A `Seed` is a source of entropy used to generate cryptographic keys in a deterministic manner.
9/// Unlike randomly generated keys, seed-derived keys can be recreated if you have the original seed,
10/// making them useful for backup and recovery scenarios.
11///
12/// This implementation of `Seed` includes the random seed data as well as optional metadata:
13/// - A name (for identifying the seed)
14/// - A note (for storing additional information)
15/// - A creation date
16///
17/// The minimum seed length is 16 bytes to ensure sufficient security and entropy.
18///
19/// # CBOR Serialization
20///
21/// `Seed` implements the `CBORTaggedCodable` trait, which means it can be serialized to and
22/// deserialized from CBOR with specific tags. The tags used are `TAG_SEED` and the older
23/// `TAG_SEED_V1` for backward compatibility.
24///
25/// When serialized to CBOR, a `Seed` is represented as a map with the following keys:
26/// - 1: The seed data (required)
27/// - 2: The creation date (optional)
28/// - 3: The name (optional, omitted if empty)
29/// - 4: The note (optional, omitted if empty)
30///
31/// # UR Serialization
32///
33/// When serialized as a Uniform Resource (UR), a `Seed` is represented with the type "seed".
34///
35/// # Key Derivation
36///
37/// A `Seed` implements the `PrivateKeyDataProvider` trait, which means it can be used as a source
38/// of entropy for deriving private keys in various cryptographic schemes.
39///
40/// # Examples
41///
42/// Creating a new random seed:
43///
44/// ```
45/// use bc_components::Seed;
46///
47/// // Create a new random seed with default length (16 bytes)
48/// let seed = Seed::new();
49/// ```
50///
51/// Creating a seed with a specific length:
52///
53/// ```
54/// use bc_components::Seed;
55///
56/// // Create a seed with 32 bytes of entropy
57/// let seed = Seed::new_with_len(32).unwrap();
58/// ```
59///
60/// Creating a seed with metadata:
61///
62/// ```
63/// use bc_components::Seed;
64/// use dcbor::Date;
65///
66/// // Create seed data
67/// let data = vec![0u8; 16];
68///
69/// // Create a seed with name, note, and creation date
70/// let mut seed = Seed::new_opt(
71///     data,
72///     Some("Wallet Backup".to_string()),
73///     Some("Cold storage backup for main wallet".to_string()),
74///     Some(Date::now())
75/// ).unwrap();
76///
77/// // Modify metadata
78/// seed.set_name("Updated Wallet Backup");
79/// ```
80#[derive(Debug, Clone, PartialEq, Eq, Hash)]
81pub struct Seed {
82    data: Vec<u8>,
83    name: String,
84    note: String,
85    creation_date: Option<dcbor::Date>,
86}
87
88impl Seed {
89    pub const MIN_SEED_LENGTH: usize = 16;
90
91    /// Create a new random seed.
92    ///
93    /// The length of the seed will be 16 bytes.
94    pub fn new() -> Self {
95        Self::new_with_len(Self::MIN_SEED_LENGTH).unwrap()
96    }
97
98    /// Create a new random seed with a specified length.
99    ///
100    /// If the number of bytes is less than 16, this will return `None`.
101    pub fn new_with_len(count: usize) -> Result<Self> {
102        let mut rng = bc_rand::SecureRandomNumberGenerator;
103        Self::new_with_len_using(count, &mut rng)
104    }
105
106    /// Create a new random seed with a specified length.
107    ///
108    /// If the number of bytes is less than 16, this will return `None`.
109    pub fn new_with_len_using(
110        count: usize,
111        rng: &mut impl RandomNumberGenerator
112    ) -> Result<Self> {
113        let data = rng_random_data(rng, count);
114        Self::new_opt(data, None, None, None)
115    }
116
117    /// Create a new seed from the data and options.
118    ///
119    /// If the data is less than 16 bytes, this will return `None`.
120    pub fn new_opt(
121        data: impl Into<Vec<u8>>,
122        name: Option<String>,
123        note: Option<String>,
124        creation_date: Option<dcbor::Date>
125    ) -> Result<Self> {
126        let data = data.into();
127        if data.len() < Self::MIN_SEED_LENGTH {
128            bail!("Seed data is too short");
129        }
130        Ok(Self {
131            data,
132            name: name.unwrap_or_default(),
133            note: note.unwrap_or_default(),
134            creation_date,
135        })
136    }
137
138    /// Return the data of the seed.
139    pub fn data(&self) -> &Vec<u8> {
140        &self.data
141    }
142
143    /// Return the name of the seed.
144    pub fn name(&self) -> &str {
145        &self.name
146    }
147
148    /// Set the name of the seed.
149    pub fn set_name(&mut self, name: &str) {
150        self.name = name.to_string();
151    }
152
153    /// Return the note of the seed.
154    pub fn note(&self) -> &str {
155        &self.note
156    }
157
158    /// Set the note of the seed.
159    pub fn set_note(&mut self, note: &str) {
160        self.note = note.to_string();
161    }
162
163    /// Return the creation date of the seed.
164    pub fn creation_date(&self) -> &Option<dcbor::Date> {
165        &self.creation_date
166    }
167
168    /// Set the creation date of the seed.
169    pub fn set_creation_date(&mut self, creation_date: Option<dcbor::Date>) {
170        self.creation_date = creation_date;
171    }
172}
173
174/// Provides a default implementation that creates a new random seed with the minimum length.
175impl Default for Seed {
176    fn default() -> Self {
177        Self::new()
178    }
179}
180
181/// Allows using a Seed as a reference to a byte slice.
182impl AsRef<[u8]> for Seed {
183    fn as_ref(&self) -> &[u8] {
184        self.data()
185    }
186}
187
188/// Provides a self-reference, enabling API consistency with other types.
189impl AsRef<Seed> for Seed {
190    fn as_ref(&self) -> &Seed {
191        self
192    }
193}
194
195/// Implements PrivateKeyDataProvider to use seed data for key derivation.
196impl PrivateKeyDataProvider for Seed {
197    fn private_key_data(&self) -> Vec<u8> {
198        self.data().clone()
199    }
200}
201
202/// Identifies the CBOR tags used for Seed serialization, including the legacy tag.
203impl CBORTagged for Seed {
204    fn cbor_tags() -> Vec<Tag> {
205        tags_for_values(&[tags::TAG_SEED, tags::TAG_SEED_V1])
206    }
207}
208
209/// Enables conversion of a Seed into a tagged CBOR value.
210impl From<Seed> for CBOR {
211    fn from(value: Seed) -> Self {
212        value.tagged_cbor()
213    }
214}
215
216/// Defines how a Seed is encoded as CBOR (as a map with data and metadata).
217impl CBORTaggedEncodable for Seed {
218    fn untagged_cbor(&self) -> CBOR {
219        let mut map = dcbor::Map::new();
220        map.insert(1, CBOR::to_byte_string(self.data()));
221        if let Some(creation_date) = self.creation_date().clone() {
222            map.insert(2, creation_date);
223        }
224        if !self.name().is_empty() {
225            map.insert(3, self.name());
226        }
227        if !self.note().is_empty() {
228            map.insert(4, self.note());
229        }
230        map.into()
231    }
232}
233
234/// Enables conversion from CBOR to Seed, with proper error handling.
235impl TryFrom<CBOR> for Seed {
236    type Error = dcbor::Error;
237
238    fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
239        Self::from_tagged_cbor(cbor)
240    }
241}
242
243/// Defines how a Seed is decoded from CBOR.
244impl CBORTaggedDecodable for Seed {
245    fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
246        let map = cbor.try_into_map()?;
247        let data = map.extract::<i32, CBOR>(1)?.try_into_byte_string()?.to_vec();
248        if data.is_empty() {
249            return Err("Seed data is empty".into());
250        }
251        let creation_date = map.get::<i32, dcbor::Date>(2);
252        let name = map.get::<i32, String>(3);
253        let note = map.get::<i32, String>(4);
254        Ok(Self::new_opt(data, name, note, creation_date)?)
255    }
256}