diff --git a/smite/src/bolt/types.rs b/smite/src/bolt/types.rs index 45e0ea6c..882106c1 100644 --- a/smite/src/bolt/types.rs +++ b/smite/src/bolt/types.rs @@ -2,8 +2,9 @@ use super::{FeatureBit, Features}; use bitcoin::OutPoint; -use bitcoin::hashes::Hash; +use bitcoin::hashes::{Hash, sha256}; use bitcoin::hex::DisplayHex; +use bitcoin::secp256k1::PublicKey; use serde::{Deserialize, Serialize}; use std::fmt; @@ -79,6 +80,37 @@ impl ChannelId { res[31] ^= (outpoint.vout & 0xff) as u8; Self(res) } + + /// Creates a _v2_ channel ID from both peers' revocation basepoints. + #[must_use] + pub fn v2_from_revocation_basepoints(basepoint1: &PublicKey, basepoint2: &PublicKey) -> Self { + Self::v2_from_serialized_basepoints(basepoint1.serialize(), basepoint2.serialize()) + } + + /// Creates a _v2_ `temporary_channel_id` from the opener's revocation + /// basepoint. + #[must_use] + pub fn v2_temporary_from_revocation_basepoint( + opener_basepoint: &PublicKey, + ) -> TemporaryChannelId { + Self::v2_from_serialized_basepoints([0u8; PUBLIC_KEY_SIZE], opener_basepoint.serialize()) + } + + /// Hashes two already-serialized basepoints in BOLT 2's canonical order. + fn v2_from_serialized_basepoints( + basepoint1: [u8; PUBLIC_KEY_SIZE], + basepoint2: [u8; PUBLIC_KEY_SIZE], + ) -> Self { + let (lesser, greater) = if basepoint1 <= basepoint2 { + (basepoint1, basepoint2) + } else { + (basepoint2, basepoint1) + }; + let mut preimage = [0u8; PUBLIC_KEY_SIZE * 2]; + preimage[..PUBLIC_KEY_SIZE].copy_from_slice(&lesser); + preimage[PUBLIC_KEY_SIZE..].copy_from_slice(&greater); + Self(sha256::Hash::hash(&preimage).to_byte_array()) + } } impl fmt::Display for ChannelId { @@ -426,6 +458,16 @@ mod tests { use super::*; use bitcoin::{OutPoint, Txid}; + /// The two `funding_pubkey`s of the 2-of-2 output in the BOLT 3 + /// "Appendix G: Dual Funded Transaction Test Vectors". + const BASEPOINT_1: &str = "0292edb5f7bbf9e900f7e024be1c1339c6d149c11930e613af3a983d2565f4e41e"; + const BASEPOINT_2: &str = "02e16172a41e928cbd78f761bd1c657c4afc7495a1244f7f30166b654fbf7661e3"; + + fn pubkey(hex_str: &str) -> PublicKey { + let bytes = hex::decode(hex_str).expect("valid hex"); + PublicKey::from_slice(&bytes).expect("valid pubkey") + } + #[test] fn bigsize_new() { let bs = BigSize::new(42); @@ -516,6 +558,64 @@ mod tests { } } + #[test] + fn channel_id_v2_hashes_sorted_basepoints() { + // Expected value computed by LDK's `ChannelId::v2_from_revocation_basepoints`. + let channel_id = + ChannelId::v2_from_revocation_basepoints(&pubkey(BASEPOINT_1), &pubkey(BASEPOINT_2)); + + assert_eq!( + channel_id.to_string(), + "59bc22f722836ce5095a37504f8ab87b1b2dbdc8aad638b77da3f5f3e8330edd", + ); + } + + #[test] + fn channel_id_v2_from_revocation_basepoints_is_order_independent() { + let basepoint_1 = pubkey(BASEPOINT_1); + let basepoint_2 = pubkey(BASEPOINT_2); + + assert_eq!( + ChannelId::v2_from_revocation_basepoints(&basepoint_1, &basepoint_2), + ChannelId::v2_from_revocation_basepoints(&basepoint_2, &basepoint_1), + ); + } + + #[test] + fn channel_id_v2_temporary_hashes_zeroed_basepoint_first() { + // Expected values computed by LDK's `ChannelId::temporary_v2_from_revocation_basepoint`. + assert_eq!( + ChannelId::v2_temporary_from_revocation_basepoint(&pubkey(BASEPOINT_1)).to_string(), + "90fc2d0fcef3376e4c26de47e9c86a7362adecf87c8c1a01cdaa3263abd74c5a", + ); + assert_eq!( + ChannelId::v2_temporary_from_revocation_basepoint(&pubkey(BASEPOINT_2)).to_string(), + "270d4e8fa33e0c15f46c4978186d02c51a5307229ba5689c9f600c68360e25e3", + ); + } + + #[test] + fn channel_id_v2_temporary_differs_from_final() { + let basepoint_1 = pubkey(BASEPOINT_1); + let basepoint_2 = pubkey(BASEPOINT_2); + + assert_ne!( + ChannelId::v2_temporary_from_revocation_basepoint(&basepoint_1), + ChannelId::v2_from_revocation_basepoints(&basepoint_1, &basepoint_2), + ); + } + + #[test] + fn channel_id_v2_distinguishes_basepoints() { + let basepoint_1 = pubkey(BASEPOINT_1); + let basepoint_2 = pubkey(BASEPOINT_2); + + assert_ne!( + ChannelId::v2_from_revocation_basepoints(&basepoint_1, &basepoint_1), + ChannelId::v2_from_revocation_basepoints(&basepoint_1, &basepoint_2), + ); + } + #[test] fn short_channel_id_ord_matches_packed_u64() { let a = ShortChannelId::new(100, 0, 0);