bc_components/
seed.rs

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