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

Generate node's uuid deterministically #1039

Merged
merged 1 commit into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
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
Loading