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