Skip to content
This repository has been archived by the owner on Feb 3, 2025. It is now read-only.

Commit

Permalink
Generate node's uuid deterministically
Browse files Browse the repository at this point in the history
  • Loading branch information
benthecarman committed Feb 18, 2024
1 parent 560aab2 commit be61c19
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 28 deletions.
69 changes: 68 additions & 1 deletion mutiny-core/src/keymanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use bip39::Mnemonic;
use bitcoin::absolute::LockTime;
use bitcoin::bech32::u5;
use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey};
use bitcoin::hashes::{sha256, Hash, HashEngine, Hmac, HmacEngine};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
use bitcoin::secp256k1::ecdsa::Signature;
Expand All @@ -25,6 +26,7 @@ use lightning::sign::{
};
use lightning::util::logger::Logger;
use std::sync::Arc;
use uuid::Uuid;

pub struct PhantomKeysManager<S: MutinyStorage> {
inner: LdkPhantomKeysManager,
Expand Down Expand Up @@ -258,6 +260,26 @@ pub(crate) fn pubkey_from_keys_manager<S: MutinyStorage>(
.expect("cannot parse node id")
}

pub(crate) fn deterministic_uuid_from_keys_manager<S: MutinyStorage>(
keys_manager: &PhantomKeysManager<S>,
) -> Uuid {
let secret_bytes = keys_manager.get_node_secret_key().secret_bytes();
// hash secret bytes just in case
let hashed_secret_bytes = sha256::Hash::hash(&secret_bytes);
let node_id = pubkey_from_keys_manager(keys_manager);

// create a Hmac that commits to a secret plus their node id and a salt,
// this way it can't be calculated off of only public info.
let mut engine = HmacEngine::new(&hashed_secret_bytes.to_byte_array());
engine.input(&node_id.serialize());
engine.input(b"Mutiny Node UUID");
let hmac = Hmac::<sha256::Hash>::from_engine(engine);

// take first 16 bytes to create the UUID
let bytes = hmac.as_byte_array();
Uuid::from_slice(&bytes[..16]).expect("exactly 16 bytes")
}

#[cfg(test)]
mod tests {
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
Expand All @@ -268,7 +290,7 @@ mod tests {
encrypt::encryption_key_from_pass, keymanager::pubkey_from_keys_manager, test_utils::*,
};

use super::create_keys_manager;
use super::{create_keys_manager, deterministic_uuid_from_keys_manager};
use crate::fees::MutinyFeeEstimator;
use crate::logging::MutinyLogger;
use crate::onchain::OnChainWallet;
Expand Down Expand Up @@ -328,4 +350,49 @@ mod tests {

assert_eq!(second_pubkey, second_pubkey_again);
}

#[test]
async fn derive_uuid_from_key_manager() {
let test_name = "derive_uuid_from_key_manager";
log!("{}", test_name);

let mnemonic = Mnemonic::from_str("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").expect("could not generate");
let esplora = Arc::new(
Builder::new("https://blockstream.info/testnet/api/")
.build_async()
.unwrap(),
);
let network = Network::Testnet;
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let db = MemoryStorage::new(Some(pass), Some(cipher), None);
let logger = Arc::new(MutinyLogger::default());
let fees = Arc::new(MutinyFeeEstimator::new(
db.clone(),
esplora.clone(),
logger.clone(),
));
let stop = Arc::new(AtomicBool::new(false));
let xpriv = ExtendedPrivKey::new_master(network, &mnemonic.to_seed("")).unwrap();

let wallet = Arc::new(
OnChainWallet::new(xpriv, db, network, esplora, fees, stop, logger.clone()).unwrap(),
);

let km = create_keys_manager(wallet.clone(), xpriv, 1, logger.clone()).unwrap();
let uuid = deterministic_uuid_from_keys_manager(&km);
assert_eq!("1f586dda-909b-e737-e49b-f1dfbcfd21c0", uuid.to_string());

let km = create_keys_manager(wallet.clone(), xpriv, 2, logger.clone()).unwrap();
let second_uuid = deterministic_uuid_from_keys_manager(&km);
assert_eq!(
"acb9c94d-780a-5ef5-f576-069a7bb4c5ae",
second_uuid.to_string()
);

let km = create_keys_manager(wallet, xpriv, 2, logger).unwrap();
let second_uuid_again = deterministic_uuid_from_keys_manager(&km);

assert_eq!(second_uuid, second_uuid_again);
}
}
25 changes: 15 additions & 10 deletions mutiny-core/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use crate::{
event::{EventHandler, HTLCStatus, MillisatAmount, PaymentInfo},
fees::MutinyFeeEstimator,
gossip::{get_all_peers, read_peer_info, save_peer_connection_info},
keymanager::{create_keys_manager, pubkey_from_keys_manager},
keymanager::{
create_keys_manager, deterministic_uuid_from_keys_manager, pubkey_from_keys_manager,
},
ldkstorage::{MutinyNodePersister, PhantomChannelManager},
logging::MutinyLogger,
lsp::{AnyLsp, FeeRequest, Lsp},
Expand Down Expand Up @@ -297,10 +299,6 @@ impl<S: MutinyStorage> NodeBuilder<S> {
pub async fn build(self) -> Result<Node<S>, MutinyError> {
let node_start = Instant::now();
// check for all required parameters
let uuid = self.uuid.as_ref().map_or_else(
|| Err(MutinyError::InvalidArgumentsError),
|v| Ok(v.clone()),
)?;
let node_index = self.node_index.as_ref().map_or_else(
|| Err(MutinyError::InvalidArgumentsError),
|v| Ok(v.clone()),
Expand Down Expand Up @@ -340,7 +338,7 @@ impl<S: MutinyStorage> NodeBuilder<S> {

let logger = self.logger.unwrap_or(Arc::new(MutinyLogger::default()));

log_info!(logger, "initializing a new node: {uuid}");
log_info!(logger, "initializing a new node: {:?}", self.uuid);

// a list of components that need to be stopped and whether or not they are stopped
let stopped_components = Arc::new(RwLock::new(vec![]));
Expand All @@ -353,6 +351,13 @@ impl<S: MutinyStorage> NodeBuilder<S> {
)?);
let pubkey = pubkey_from_keys_manager(&keys_manager);

// if no UUID was given then this is new node, we deterministically generate
// it from our key manager.
let uuid = match self.uuid {
Some(uuid) => uuid,
None => deterministic_uuid_from_keys_manager(&keys_manager).to_string(),
};

// init the persister
let persister = Arc::new(MutinyNodePersister::new(
uuid.clone(),
Expand Down Expand Up @@ -860,7 +865,7 @@ impl<S: MutinyStorage> NodeBuilder<S> {
);

Ok(Node {
_uuid: uuid,
uuid: uuid,
stopped_components,
child_index: node_index.child_index,
pubkey,
Expand All @@ -883,7 +888,7 @@ impl<S: MutinyStorage> NodeBuilder<S> {
}

pub(crate) struct Node<S: MutinyStorage> {
pub _uuid: String,
pub uuid: String,
pub child_index: u32,
stopped_components: Arc<RwLock<Vec<bool>>>,
pub pubkey: PublicKey,
Expand Down Expand Up @@ -971,7 +976,7 @@ impl<S: MutinyStorage> Node<S> {
if saved != peer_connection_info.original_connection_string {
match save_peer_connection_info(
&self.persister.storage,
&self._uuid,
&self.uuid,
&node_id,
&peer_connection_info.original_connection_string,
label,
Expand All @@ -986,7 +991,7 @@ impl<S: MutinyStorage> Node<S> {
// store this so we can reconnect later
if let Err(e) = save_peer_connection_info(
&self.persister.storage,
&self._uuid,
&self.uuid,
&node_id,
&peer_connection_info.original_connection_string,
label,
Expand Down
30 changes: 13 additions & 17 deletions mutiny-core/src/nodemanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(not(target_arch = "wasm32"))]
use std::time::Instant;
use std::{collections::HashMap, ops::Deref, sync::Arc};
use uuid::Uuid;

const BITCOIN_PRICE_CACHE_SEC: u64 = 300;
pub const DEVICE_LOCK_INTERVAL_SECS: u64 = 30;
Expand Down Expand Up @@ -450,7 +449,7 @@ impl<S: MutinyStorage> NodeManagerBuilder<S> {
let mut updated_nodes: HashMap<String, NodeIndex> =
HashMap::with_capacity(nodes_map.len());
for n in nodes_map.values() {
updated_nodes.insert(n._uuid.clone(), n.node_index().await);
updated_nodes.insert(n.uuid.clone(), n.node_index().await);
}

// insert updated nodes in background, isn't a huge deal if this fails,
Expand Down Expand Up @@ -1286,7 +1285,7 @@ impl<S: MutinyStorage> NodeManager<S> {
if node.channel_manager.list_channels().is_empty()
&& node.chain_monitor.get_claimable_balances(&[]).is_empty()
{
self.archive_node_by_uuid(node._uuid.clone()).await
self.archive_node_by_uuid(node.uuid.clone()).await
} else {
Err(anyhow!("Node has active channels, cannot archive").into())
}
Expand Down Expand Up @@ -1408,7 +1407,7 @@ impl<S: MutinyStorage> NodeManager<S> {
peer: &NodeId,
) -> Result<(), MutinyError> {
let node = self.get_node_by_key_or_first(self_node_pubkey).await?;
gossip::delete_peer_info(&self.storage, &node._uuid, peer)?;
gossip::delete_peer_info(&self.storage, &node.uuid, peer)?;
Ok(())
}

Expand Down Expand Up @@ -2026,9 +2025,6 @@ pub(crate) async fn create_new_node_from_node_manager<S: MutinyStorage>(
Some((_, v)) => v.child_index + 1,
};

// Create and save a new node using the next child index
let next_node_uuid = Uuid::new_v4().to_string();

let lsp = node_manager.lsp_config.clone();

let next_node = NodeIndex {
Expand All @@ -2037,17 +2033,8 @@ pub(crate) async fn create_new_node_from_node_manager<S: MutinyStorage>(
archived: Some(false),
};

existing_nodes.version += 1;
existing_nodes
.nodes
.insert(next_node_uuid.clone(), next_node.clone());

node_manager.storage.insert_nodes(&existing_nodes).await?;
node_mutex.nodes = existing_nodes.nodes.clone();

let mut node_builder = NodeBuilder::new(node_manager.xprivkey, node_manager.storage.clone())
.with_uuid(next_node_uuid.clone())
.with_node_index(next_node)
.with_node_index(next_node.clone())
.with_gossip_sync(node_manager.gossip_sync.clone())
.with_scorer(node_manager.scorer.clone())
.with_chain(node_manager.chain.clone())
Expand All @@ -2069,6 +2056,15 @@ pub(crate) async fn create_new_node_from_node_manager<S: MutinyStorage>(

let new_node = node_builder.build().await?;
let node_pubkey = new_node.pubkey;
let next_node_uuid = new_node.uuid.clone();

existing_nodes.version += 1;
existing_nodes
.nodes
.insert(next_node_uuid.clone(), next_node);
node_manager.storage.insert_nodes(&existing_nodes).await?;
node_mutex.nodes = existing_nodes.nodes.clone();

let mut nodes = node_manager.nodes.write().await;
nodes.insert(node_pubkey, Arc::new(new_node));

Expand Down

0 comments on commit be61c19

Please sign in to comment.