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}