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}