Skip to content

Commit 5bcd15d

Browse files
seditedh4sh3d
andauthored
Refactor(KeyManager): Don't persist master secret (#322)
* Refactor(Keymanager): Don't persist master secret Derive account level keys on initialization. This has the benefit of informationally segregating instantiated key managers. Since the swap index is not mutable, every swap has to instantiate its own key manager. In order to informationally guarantee that each swap can only read and reach keys with its own swap index, derive the keys to the account index on key manager generation. * test: assert restored keys are the same as original ones * chore: update changelog Co-authored-by: h4sh3d <h4sh3d@protonmail.com>
1 parent 904b35c commit 5bcd15d

File tree

3 files changed

+155
-28
lines changed

3 files changed

+155
-28
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Implement `From<trade::DealId>` and `From<swap::SwapId>` for `Uuid` by @h4sh3d ([#323](https://github.com/farcaster-project/farcaster-core/pull/323))
1313

14+
### Changed
15+
16+
- Don't persist master secret key in `KeyManager` and derive account level keys on initialization by @TheCharlatan ([#322](https://github.com/farcaster-project/farcaster-core/pull/322))
17+
1418
## [0.6.3] - 2022-12-28
1519

1620
### Added

src/crypto/slip10.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ pub use bitcoin::util::bip32::ChainCode;
4545
pub use bitcoin::util::bip32::Fingerprint;
4646
pub use bitcoin::util::bip32::{ChildNumber, DerivationPath};
4747

48+
use crate::consensus::{self, Decodable, Encodable};
49+
4850
/// Possible errors when deriving keys as described in SLIP-10.
4951
#[derive(Error, Debug)]
5052
pub enum Error {
@@ -73,6 +75,44 @@ pub struct Ed25519ExtSecretKey {
7375
pub chain_code: ChainCode,
7476
}
7577

78+
impl Encodable for Ed25519ExtSecretKey {
79+
fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
80+
let mut len = self.depth.consensus_encode(writer)?;
81+
len += self
82+
.parent_fingerprint
83+
.into_bytes()
84+
.to_vec()
85+
.consensus_encode(writer)?;
86+
len += Into::<u32>::into(self.child_number).consensus_encode(writer)?;
87+
len += self.secret_key.consensus_encode(writer)?;
88+
len += self
89+
.chain_code
90+
.into_bytes()
91+
.to_vec()
92+
.consensus_encode(writer)?;
93+
Ok(len)
94+
}
95+
}
96+
97+
impl Decodable for Ed25519ExtSecretKey {
98+
fn consensus_decode<D: std::io::Read>(d: &mut D) -> Result<Self, consensus::Error> {
99+
let depth = u8::consensus_decode(d)?;
100+
let parent_fingerprint = Fingerprint::try_from(Vec::<u8>::consensus_decode(d)?.as_slice())
101+
.map_err(consensus::Error::new)?;
102+
let child_number = ChildNumber::from(u32::consensus_decode(d)?);
103+
let secret_key = <[u8; 32]>::consensus_decode(d)?;
104+
let chain_code = ChainCode::try_from(Vec::<u8>::consensus_decode(d)?.as_slice())
105+
.map_err(consensus::Error::new)?;
106+
Ok(Ed25519ExtSecretKey {
107+
depth,
108+
parent_fingerprint,
109+
child_number,
110+
secret_key,
111+
chain_code,
112+
})
113+
}
114+
}
115+
76116
impl Ed25519ExtSecretKey {
77117
/// Construct a new master key from a seed value, as defined in SLIP10 the HMAC engine is setup
78118
/// with the value `"ed25519 seed"`.
@@ -188,6 +228,45 @@ pub struct Secp256k1ExtSecretKey {
188228
pub chain_code: ChainCode,
189229
}
190230

231+
impl Encodable for Secp256k1ExtSecretKey {
232+
fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
233+
let mut len = self.depth.consensus_encode(writer)?;
234+
len += self
235+
.parent_fingerprint
236+
.into_bytes()
237+
.to_vec()
238+
.consensus_encode(writer)?;
239+
len += Into::<u32>::into(self.child_number).consensus_encode(writer)?;
240+
len += self.secret_key.secret_bytes().consensus_encode(writer)?;
241+
len += self
242+
.chain_code
243+
.into_bytes()
244+
.to_vec()
245+
.consensus_encode(writer)?;
246+
Ok(len)
247+
}
248+
}
249+
250+
impl Decodable for Secp256k1ExtSecretKey {
251+
fn consensus_decode<D: std::io::Read>(d: &mut D) -> Result<Self, consensus::Error> {
252+
let depth = u8::consensus_decode(d)?;
253+
let parent_fingerprint = Fingerprint::try_from(Vec::<u8>::consensus_decode(d)?.as_slice())
254+
.map_err(consensus::Error::new)?;
255+
let child_number = ChildNumber::from(u32::consensus_decode(d)?);
256+
let secret_key = secp256k1::SecretKey::from_slice(&<[u8; 32]>::consensus_decode(d)?)
257+
.map_err(consensus::Error::new)?;
258+
let chain_code = ChainCode::try_from(Vec::<u8>::consensus_decode(d)?.as_slice())
259+
.map_err(consensus::Error::new)?;
260+
Ok(Secp256k1ExtSecretKey {
261+
depth,
262+
parent_fingerprint,
263+
child_number,
264+
secret_key,
265+
chain_code,
266+
})
267+
}
268+
}
269+
191270
impl Secp256k1ExtSecretKey {
192271
/// Construct a new master key from a seed value, as defined in SLIP10 if secret key is not
193272
/// valid retry with a new round on the HMAC engine.

src/swap/btcxmr.rs

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,12 @@ impl Derivation for SharedKeyId {
190190
/// handling [`GenerateKey`], [`GenerateSharedKey`] and [`Sign`].
191191
#[derive(Clone, Debug)]
192192
pub struct KeyManager {
193-
/// The master 32-bytes seed used to derive all the keys for all the swaps.
194-
master_seed: [u8; 32],
195193
/// The swap identifier used in the derivation.
196194
swap_index: ChildNumber,
197-
/// The master secp256k1 seed.
198-
bitcoin_master_key: Secp256k1ExtSecretKey,
199-
/// The master ed25519 seed.
200-
monero_master_key: Ed25519ExtSecretKey,
195+
/// The secp256k1 account key as derived from swap_index.
196+
bitcoin_account_key: Secp256k1ExtSecretKey,
197+
/// The ed25519 account key as derived from swap_index.
198+
monero_account_key: Ed25519ExtSecretKey,
201199
/// A list of already derived keys for secp256k1 by derivation path.
202200
bitcoin_derivations: HashMap<DerivationPath, SecretKey>,
203201
/// A list of already derived monero keys for ed25519 by derivation path.
@@ -206,22 +204,22 @@ pub struct KeyManager {
206204

207205
impl Encodable for KeyManager {
208206
fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
209-
let mut len = self.master_seed.consensus_encode(writer)?;
210-
len += Into::<u32>::into(self.swap_index).consensus_encode(writer)?;
211-
// TODO: don't add derivations, but test that key manager encoding is correct modulo cached derivations
207+
let mut len = Into::<u32>::into(self.swap_index).consensus_encode(writer)?;
208+
len += self.bitcoin_account_key.consensus_encode(writer)?;
209+
len += self.monero_account_key.consensus_encode(writer)?;
212210
Ok(len)
213211
}
214212
}
215213

216214
impl Decodable for KeyManager {
217215
fn consensus_decode<D: std::io::Read>(d: &mut D) -> Result<Self, consensus::Error> {
218-
let master_seed = Decodable::consensus_decode(d)?;
219216
let swap_index: u32 = Decodable::consensus_decode(d)?;
217+
let bitcoin_account_key = Secp256k1ExtSecretKey::consensus_decode(d)?;
218+
let monero_account_key = Ed25519ExtSecretKey::consensus_decode(d)?;
220219
Ok(KeyManager {
221-
master_seed,
222220
swap_index: ChildNumber::from(swap_index),
223-
bitcoin_master_key: Secp256k1ExtSecretKey::new_master(master_seed.as_ref()),
224-
monero_master_key: Ed25519ExtSecretKey::new_master(master_seed.as_ref()),
221+
bitcoin_account_key,
222+
monero_account_key,
225223
bitcoin_derivations: HashMap::new(),
226224
monero_derivations: HashMap::new(),
227225
})
@@ -231,16 +229,14 @@ impl Decodable for KeyManager {
231229
impl_strict_encoding!(KeyManager);
232230

233231
impl KeyManager {
234-
/// Generate the derivation path of a key, computed as:
235-
/// `m/44'/{blockchain}'/{swap_index}'/{key_type}'/{key_idx}'`.
236-
pub fn get_derivation_path(
237-
&self,
232+
/// Generate the derivation path of an account key, computed as:
233+
/// `m/44'/{blockchain}'/{swap_index}'`.
234+
pub fn get_account_derivation_path(
238235
blockchain: Blockchain,
239-
key_id: impl Derivation,
236+
swap_index: ChildNumber,
240237
) -> Result<DerivationPath, crypto::Error> {
241238
let path = blockchain.derivation_path()?;
242-
let path = path.extend([self.swap_index]);
243-
Ok(path.extend(&key_id.derivation_path()?))
239+
Ok(path.extend([swap_index]))
244240
}
245241

246242
/// Try to retreive the secret key internally if already generated, if the key is not found
@@ -249,7 +245,7 @@ impl KeyManager {
249245
&mut self,
250246
key_id: impl Derivation,
251247
) -> Result<SecretKey, crypto::Error> {
252-
let path = self.get_derivation_path(Blockchain::Bitcoin, key_id)?;
248+
let path = key_id.derivation_path()?;
253249
self.bitcoin_derivations
254250
.get(&path)
255251
// Option<Result<SecretKey, _>>
@@ -258,7 +254,7 @@ impl KeyManager {
258254
// None => || { ... } => Result<SecretKey, crypto::Error>
259255
.unwrap_or_else(|| {
260256
let secp = Secp256k1::new();
261-
match self.bitcoin_master_key.derive_priv(&secp, &path) {
257+
match self.bitcoin_account_key.derive_priv(&secp, &path) {
262258
Ok(key) => {
263259
self.bitcoin_derivations.insert(path, key.secret_key);
264260
Ok(key.secret_key)
@@ -274,7 +270,7 @@ impl KeyManager {
274270
&mut self,
275271
key_id: impl Derivation,
276272
) -> Result<monero::PrivateKey, crypto::Error> {
277-
let path = self.get_derivation_path(Blockchain::Monero, key_id)?;
273+
let path = key_id.derivation_path()?;
278274
self.monero_derivations
279275
.get(&path)
280276
// Option<Result<PrivateKey, _>>
@@ -283,7 +279,7 @@ impl KeyManager {
283279
// None => || { ... } => Result<PrivateKey, crypto::Error>
284280
.unwrap_or_else(|| {
285281
let key_seed = self
286-
.monero_master_key
282+
.monero_account_key
287283
.derive_priv(&path)
288284
.expect("Path does not contain non-hardened derivation")
289285
.secret_key;
@@ -308,11 +304,17 @@ impl KeyManager {
308304
/// Create a new key manager with the provided master seed, returns an error if the swap index is
309305
/// not within `[0, 2^31 - 1]`.
310306
pub fn new(seed: [u8; 32], swap_index: u32) -> Result<Self, crypto::Error> {
307+
let swap_index = ChildNumber::from_hardened_idx(swap_index).map_err(crypto::Error::new)?;
308+
let secp = Secp256k1::new();
311309
Ok(Self {
312-
master_seed: seed,
313-
swap_index: ChildNumber::from_hardened_idx(swap_index).map_err(crypto::Error::new)?,
314-
bitcoin_master_key: Secp256k1ExtSecretKey::new_master(seed.as_ref()),
315-
monero_master_key: Ed25519ExtSecretKey::new_master(seed.as_ref()),
310+
swap_index,
311+
bitcoin_account_key: Secp256k1ExtSecretKey::new_master(seed.as_ref()).derive_priv(
312+
&secp,
313+
&Self::get_account_derivation_path(Blockchain::Bitcoin, swap_index)?,
314+
)?,
315+
monero_account_key: Ed25519ExtSecretKey::new_master(seed.as_ref()).derive_priv(
316+
&Self::get_account_derivation_path(Blockchain::Monero, swap_index)?,
317+
)?,
316318
bitcoin_derivations: HashMap::new(),
317319
monero_derivations: HashMap::new(),
318320
})
@@ -565,3 +567,45 @@ fn test_keymanager_consensus_encoding() {
565567
key_manager.consensus_encode(&mut encoder).unwrap();
566568
KeyManager::consensus_decode(&mut std::io::Cursor::new(encoder)).unwrap();
567569
}
570+
571+
#[test]
572+
fn test_keymanager_restore_index() {
573+
let key_manager = KeyManager::new([0; 32], 42).unwrap();
574+
let mut encoder = Vec::new();
575+
key_manager.consensus_encode(&mut encoder).unwrap();
576+
let restore = KeyManager::consensus_decode(&mut std::io::Cursor::new(encoder)).unwrap();
577+
assert_eq!(
578+
restore.swap_index,
579+
ChildNumber::from_hardened_idx(42).unwrap()
580+
);
581+
}
582+
583+
#[test]
584+
fn test_keymanager_restore_bitcoin_key() {
585+
let mut key_manager = KeyManager::new([0; 32], 1).unwrap();
586+
let orig_key = key_manager
587+
.get_or_derive_bitcoin_key(ArbitratingKeyId::Lock)
588+
.unwrap();
589+
let mut encoder = Vec::new();
590+
key_manager.consensus_encode(&mut encoder).unwrap();
591+
let mut restore = KeyManager::consensus_decode(&mut std::io::Cursor::new(encoder)).unwrap();
592+
let restored_key = restore
593+
.get_or_derive_bitcoin_key(ArbitratingKeyId::Lock)
594+
.unwrap();
595+
assert_eq!(orig_key, restored_key);
596+
}
597+
598+
#[test]
599+
fn test_keymanager_restore_monero_key() {
600+
let mut key_manager = KeyManager::new([0; 32], 1).unwrap();
601+
let orig_key = key_manager
602+
.get_or_derive_monero_key(AccordantKeyId::Spend)
603+
.unwrap();
604+
let mut encoder = Vec::new();
605+
key_manager.consensus_encode(&mut encoder).unwrap();
606+
let mut restore = KeyManager::consensus_decode(&mut std::io::Cursor::new(encoder)).unwrap();
607+
let restored_key = restore
608+
.get_or_derive_monero_key(AccordantKeyId::Spend)
609+
.unwrap();
610+
assert_eq!(orig_key, restored_key);
611+
}

0 commit comments

Comments
 (0)