Skip to content

Commit 2f2eace

Browse files
authored
STR-482 improved master xpriv resolution (#705)
bridge-client: improved master xpriv resolution
1 parent 8f40c27 commit 2f2eace

File tree

2 files changed

+60
-55
lines changed

2 files changed

+60
-55
lines changed

bin/bridge-client/src/modes/operator/bootstrap.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Module to bootstrap the operator node by hooking up all the required services.
22
3-
use std::{path::PathBuf, sync::Arc, time::Duration};
3+
use std::{env, path::PathBuf, sync::Arc, time::Duration};
44

55
use bitcoin::{
66
key::{Keypair, Parity},
@@ -36,7 +36,7 @@ use crate::{
3636
},
3737
db::open_rocksdb_database,
3838
rpc_server::{self, BridgeRpc},
39-
xpriv::resolve_xpriv,
39+
xpriv::{resolve_xpriv, OPXPRIV_ENVVAR},
4040
};
4141

4242
// TODO: move this to some common util and make this usable outside tokio
@@ -112,13 +112,23 @@ pub(crate) async fn bootstrap(args: Cli) -> anyhow::Result<()> {
112112
.expect("cannot get RPC client from pool");
113113

114114
// Get the keypair after deriving the wallet xpriv.
115-
let operator_keys = resolve_xpriv(args.master_xpriv, args.master_xpriv_path)?;
115+
let env_key = match env::var(OPXPRIV_ENVVAR) {
116+
Ok(k) => Some(k),
117+
Err(env::VarError::NotPresent) => None,
118+
Err(env::VarError::NotUnicode(_)) => {
119+
error!("operator master xpriv envvar not unicode, ignoring");
120+
None
121+
}
122+
};
123+
124+
let operator_keys = resolve_xpriv(args.master_xpriv, args.master_xpriv_path, env_key)?;
116125
let wallet_xpriv = operator_keys.wallet_xpriv();
117126

118127
let mut keypair = wallet_xpriv.to_keypair(SECP256K1);
119128
let mut sk = SecretKey::from_keypair(&keypair);
120129

121130
// adjust for parity, which should always be even
131+
// FIXME bake this into the key derivation fn!
122132
let (_, parity) = XOnlyPublicKey::from_keypair(&keypair);
123133
if matches!(parity, Parity::Odd) {
124134
sk = sk.negate();

bin/bridge-client/src/xpriv.rs

+47-52
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,68 @@
11
//! Parses the operator's master xpriv from a file.
22
3-
use std::{
4-
env,
5-
fs::read_to_string,
6-
path::{Path, PathBuf},
7-
};
3+
use std::fs::read_to_string;
84

95
use bitcoin::bip32::Xpriv;
106
use strata_key_derivation::operator::OperatorKeys;
11-
use strata_primitives::keys::ZeroizableXpriv;
7+
use tracing::*;
128
use zeroize::Zeroize;
139

1410
/// The environment variable that contains the operator's master [`Xpriv`].
15-
const OPXPRIV_ENVVAR: &str = "STRATA_OP_MASTER_XPRIV";
11+
pub const OPXPRIV_ENVVAR: &str = "STRATA_OP_MASTER_XPRIV";
1612

17-
/// Parses the master [`Xpriv`] from a file.
18-
pub(crate) fn parse_master_xpriv(path: &Path) -> anyhow::Result<OperatorKeys> {
19-
let mut xpriv_str = read_to_string(path)?;
20-
match xpriv_str.parse::<Xpriv>() {
21-
Ok(mut xpriv) => {
22-
// Zeroize the xpriv string after parsing it.
23-
xpriv_str.zeroize();
24-
25-
// Parse into ZeroizableXpriv
26-
let zeroizable_xpriv: ZeroizableXpriv = xpriv.into();
27-
28-
// Zeroize the xpriv after parsing it.
29-
xpriv.private_key.non_secure_erase();
30-
31-
// Finally return the operator keys
32-
//
33-
// NOTE: `zeroizable_xpriv` is zeroized on drop.
34-
Ok(OperatorKeys::new(&zeroizable_xpriv)
35-
.map_err(|_| anyhow::anyhow!("invalid master xpriv"))?)
36-
}
37-
Err(e) => anyhow::bail!("invalid master xpriv: {}", e),
38-
}
39-
}
40-
41-
/// Resolves the master [`Xpriv`] from CLI arguments or environment variables.
13+
/// Resolves the master [`Xpriv`] from the various sources.
4214
///
43-
/// Precedence order for resolving the master xpriv:
15+
/// Rules:
4416
///
45-
/// 1. If a key is supplied via the `--master-xpriv` CLI argument, it is used.
46-
/// 2. Otherwise, if a file path is supplied via CLI, the key is read from that file.
47-
/// 3. Otherwise, if the `STRATA_OP_MASTER_XPRIV` environment variable is set, its value is used.
48-
/// 4. Otherwise, returns an error.
17+
/// 1. If none are set, error out.
18+
/// 2. If multiple are set, error out.
19+
/// 3. If we have the verbatim key provided, parse it.
20+
/// 4. If we have a path provided, load it and parse that instead.
4921
///
5022
/// # Errors
5123
///
52-
/// Returns an error if the master xpriv is invalid or not found.
24+
/// Returns an error if the master xpriv is invalid or not found, or if
25+
/// conflicting options are set.
5326
pub(crate) fn resolve_xpriv(
5427
cli_arg: Option<String>,
5528
cli_path: Option<String>,
29+
env_val: Option<String>,
5630
) -> anyhow::Result<OperatorKeys> {
57-
match (cli_arg, cli_path) {
58-
(Some(xpriv), _) => OperatorKeys::new(&xpriv.parse::<Xpriv>()?)
59-
.map_err(|_| anyhow::anyhow!("invalid master xpriv from CLI")),
31+
if cli_arg.is_some() {
32+
error!("FOUND CLI ARG KEY, THIS IS INSECURE!");
33+
}
6034

61-
(_, Some(path)) => parse_master_xpriv(&PathBuf::from(path)),
35+
let mut xpriv_str: String = match (cli_arg, cli_path, env_val) {
36+
// If there's none set then we error out.
37+
(None, None, None) => {
38+
anyhow::bail!(
39+
"must provide root xpriv with either `--master-xpriv-path` or {OPXPRIV_ENVVAR}"
40+
)
41+
}
6242

63-
(None, None) => match env::var(OPXPRIV_ENVVAR) {
64-
Ok(xpriv_env_str) => OperatorKeys::new(&xpriv_env_str.parse::<Xpriv>()?)
65-
.map_err(|_| anyhow::anyhow!("invalid master xpriv from envvar")),
66-
Err(_) => {
67-
anyhow::bail!(
68-
"must either set {OPXPRIV_ENVVAR} envvar or pass with `--master-xpriv`"
69-
)
70-
}
71-
},
72-
}
43+
// If multiple are set then we error out.
44+
(_, Some(_), Some(_)) | (Some(_), Some(_), _) | (Some(_), _, Some(_)) => {
45+
anyhow::bail!("multiple root xpriv options specified, don't know what to do, aborting");
46+
}
47+
48+
// In these cases we have the string explicitly.
49+
(Some(xpriv_str), _, _) | (_, _, Some(xpriv_str)) => xpriv_str.to_owned(),
50+
51+
// In this case we fetch it from file.
52+
(_, Some(path), _) => read_to_string(path)?,
53+
};
54+
55+
// Some fancy dance to securely erase things.
56+
let Ok(raw) = xpriv_str.parse::<Xpriv>() else {
57+
xpriv_str.zeroize();
58+
anyhow::bail!("invalid master xpriv");
59+
};
60+
61+
let Ok(keys) = OperatorKeys::new(&raw) else {
62+
xpriv_str.zeroize();
63+
// TODO how to secure erase raw?
64+
anyhow::bail!("unable to generate leaf keys");
65+
};
66+
67+
Ok(keys)
7368
}

0 commit comments

Comments
 (0)