diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 0461c75772c..06fd753c671 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -259,7 +259,7 @@ impl SignerProvider for KeyProvider { }) } - fn get_destination_script(&self) -> Script { + fn get_destination_script(&self, _channel_keys_id : [u8; 32]) -> Script { let secp_ctx = Secp256k1::signing_only(); let channel_monitor_claim_key = SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, self.node_secret[31]]).unwrap(); let our_channel_monitor_claim_key_hash = WPubkeyHash::hash(&PublicKey::from_secret_key(&secp_ctx, &channel_monitor_claim_key).serialize()); diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 876a412da5f..2e26ccb0852 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -376,7 +376,7 @@ impl SignerProvider for KeyProvider { )) } - fn get_destination_script(&self) -> Script { + fn get_destination_script(&self, _channel_keys_id : [u8; 32]) -> Script { let secp_ctx = Secp256k1::signing_only(); let channel_monitor_claim_key = SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(); let our_channel_monitor_claim_key_hash = WPubkeyHash::hash(&PublicKey::from_secret_key(&secp_ctx, &channel_monitor_claim_key).serialize()); diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index 9bbf8b9cdff..c50dcfa44d8 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -141,7 +141,7 @@ impl SignerProvider for KeyProvider { fn read_chan_signer(&self, _data: &[u8]) -> Result<EnforcingSigner, DecodeError> { unreachable!() } - fn get_destination_script(&self) -> Script { unreachable!() } + fn get_destination_script(&self, _channel_keys_id : [u8; 32]) -> Script { unreachable!() } fn get_shutdown_scriptpubkey(&self) -> ShutdownScript { unreachable!() } } diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index f2c6ea18249..463f333bd9c 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3683,6 +3683,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> { spendable_output = Some(SpendableOutputDescriptor::StaticOutput { outpoint: OutPoint { txid: tx.txid(), index: i as u16 }, output: outp.clone(), + channel_keys_id: Some(self.channel_keys_id), }); break; } @@ -3713,6 +3714,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> { spendable_output = Some(SpendableOutputDescriptor::StaticOutput { outpoint: OutPoint { txid: tx.txid(), index: i as u16 }, output: outp.clone(), + channel_keys_id: Some(self.channel_keys_id), }); break; } diff --git a/lightning/src/chain/keysinterface.rs b/lightning/src/chain/keysinterface.rs index b1290708b95..574ddb0628b 100644 --- a/lightning/src/chain/keysinterface.rs +++ b/lightning/src/chain/keysinterface.rs @@ -152,6 +152,10 @@ pub enum SpendableOutputDescriptor { outpoint: OutPoint, /// The output which is referenced by the given outpoint. output: TxOut, + /// Arbitrary identification information returned by a call to + /// `Sign::channel_keys_id()`. This may be useful in re-deriving keys used in + /// the channel to spend the output. + channel_keys_id: Option<[u8; 32]> }, /// An output to a P2WSH script which can be spent with a single signature after an `OP_CSV` /// delay. @@ -208,6 +212,7 @@ impl_writeable_tlv_based_enum!(SpendableOutputDescriptor, (0, StaticOutput) => { (0, outpoint, required), (2, output, required), + (3, channel_keys_id, option), }, ; (1, DelayedPaymentOutput), @@ -545,7 +550,7 @@ pub trait SignerProvider { /// /// This method should return a different value each time it is called, to avoid linking /// on-chain funds across channels as controlled to the same user. - fn get_destination_script(&self) -> Script; + fn get_destination_script(&self, channel_keys_id: [u8; 32]) -> Script; /// Get a script pubkey which we will send funds to when closing a channel. /// @@ -711,7 +716,7 @@ impl InMemorySigner { if spend_tx.input[input_idx].previous_output != descriptor.outpoint.into_bitcoin_outpoint() { return Err(()); } let remotepubkey = self.pubkeys().payment_point; - let witness_script = bitcoin::Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: remotepubkey}, Network::Testnet).script_pubkey(); + let witness_script = bitcoin::Address::p2pkh(&bitcoin::PublicKey{compressed: true, inner: remotepubkey}, Network::Testnet).script_pubkey(); let sighash = hash_to_message!(&sighash::SighashCache::new(spend_tx).segwit_signature_hash(input_idx, &witness_script, descriptor.output.value, EcdsaSighashType::All).unwrap()[..]); let remotesig = sign_with_aux_rand(secp_ctx, &sighash, &self.payment_key, &self); let payment_script = bitcoin::Address::p2wpkh(&::bitcoin::PublicKey{compressed: true, inner: remotepubkey}, Network::Bitcoin).unwrap().script_pubkey(); @@ -1014,9 +1019,10 @@ pub struct KeysManager { node_id: PublicKey, inbound_payment_key: KeyMaterial, destination_script: Script, - shutdown_pubkey: PublicKey, + shutdown_pubkey: ExtendedPubKey, channel_master_key: ExtendedPrivKey, channel_child_index: AtomicUsize, + disallow_downgrades: bool, rand_bytes_unique_start: [u8; 32], rand_bytes_index: AtomicCounter, @@ -1061,7 +1067,7 @@ impl KeysManager { Err(_) => panic!("Your RNG is busted"), }; let shutdown_pubkey = match master_key.ckd_priv(&secp_ctx, ChildNumber::from_hardened_idx(2).unwrap()) { - Ok(shutdown_key) => ExtendedPubKey::from_priv(&secp_ctx, &shutdown_key).public_key, + Ok(shutdown_key) => ExtendedPubKey::from_priv(&secp_ctx, &shutdown_key), Err(_) => panic!("Your RNG is busted"), }; let channel_master_key = master_key.ckd_priv(&secp_ctx, ChildNumber::from_hardened_idx(3).unwrap()).expect("Your RNG is busted"); @@ -1087,6 +1093,7 @@ impl KeysManager { channel_master_key, channel_child_index: AtomicUsize::new(0), + disallow_downgrades: true, rand_bytes_unique_start, rand_bytes_index: AtomicCounter::new(), @@ -1202,7 +1209,7 @@ impl KeysManager { input_value += descriptor.output.value; if !output_set.insert(descriptor.outpoint) { return Err(()); } }, - SpendableOutputDescriptor::StaticOutput { ref outpoint, ref output } => { + SpendableOutputDescriptor::StaticOutput { ref outpoint, ref output, .. } => { input.push(TxIn { previous_output: outpoint.into_bitcoin_outpoint(), script_sig: Script::new(), @@ -1245,17 +1252,22 @@ impl KeysManager { } spend_tx.input[input_idx].witness = Witness::from_vec(keys_cache.as_ref().unwrap().0.sign_dynamic_p2wsh_input(&spend_tx, input_idx, &descriptor, &secp_ctx)?); }, - SpendableOutputDescriptor::StaticOutput { ref output, .. } => { + SpendableOutputDescriptor::StaticOutput { ref output, channel_keys_id, .. } => { + let destination_script = channel_keys_id.map_or( None,|s| Some(self.get_destination_script(s))); let derivation_idx = if output.script_pubkey == self.destination_script { 1 - } else { + } else if Some(&output.script_pubkey) == destination_script.as_ref() { + u32::from_be_bytes(channel_keys_id.unwrap()[0..4].try_into().unwrap()) + } + else { 2 }; - let secret = { + + let secret = { // Note that when we aren't serializing the key, network doesn't matter match ExtendedPrivKey::new_master(Network::Testnet, &self.seed) { Ok(master_key) => { - match master_key.ckd_priv(&secp_ctx, ChildNumber::from_hardened_idx(derivation_idx).expect("key space exhausted")) { + match master_key.ckd_priv(&secp_ctx, ChildNumber::from_hardened_idx((derivation_idx) % (1 << 31)).expect("key space exhausted")) { Ok(key) => key, Err(_) => panic!("Your RNG is busted"), } @@ -1265,7 +1277,7 @@ impl KeysManager { }; let pubkey = ExtendedPubKey::from_priv(&secp_ctx, &secret).to_pub(); if derivation_idx == 2 { - assert_eq!(pubkey.inner, self.shutdown_pubkey); + assert_eq!(pubkey.inner, self.shutdown_pubkey.public_key); } let witness_script = bitcoin::Address::p2pkh(&pubkey, Network::Testnet).script_pubkey(); let payment_script = bitcoin::Address::p2wpkh(&pubkey, Network::Testnet).expect("uncompressed key found").script_pubkey(); @@ -1302,6 +1314,10 @@ impl EntropySource for KeysManager { } impl NodeSigner for KeysManager { + fn get_inbound_payment_key_material(&self) -> KeyMaterial { + self.inbound_payment_key.clone() + } + fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> { match recipient { Recipient::Node => Ok(self.node_id.clone()), @@ -1320,10 +1336,6 @@ impl NodeSigner for KeysManager { Ok(SharedSecret::new(other_key, &node_secret)) } - fn get_inbound_payment_key_material(&self) -> KeyMaterial { - self.inbound_payment_key.clone() - } - fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()> { let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data); let secret = match recipient { @@ -1366,12 +1378,30 @@ impl SignerProvider for KeysManager { InMemorySigner::read(&mut io::Cursor::new(reader), self) } - fn get_destination_script(&self) -> Script { - self.destination_script.clone() + fn get_destination_script(&self, channel_keys_id: [u8; 32]) -> Script { + let derivation_idx = if self.disallow_downgrades { + u32::from_be_bytes(channel_keys_id[0..4].try_into().unwrap()) + } else { + 3 + }; + match ExtendedPrivKey::new_master(Network::Testnet, &self.seed) { + Ok(master_key) => { + match master_key.ckd_priv(&self.secp_ctx, ChildNumber::from_hardened_idx((derivation_idx) % (1 << 31)).expect("key space exhausted")) { + Ok(destination_key) => { + let wpubkey_hash = WPubkeyHash::hash(&ExtendedPubKey::from_priv(&self.secp_ctx, &destination_key).to_pub().to_bytes()); + Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0) + .push_slice(&wpubkey_hash.into_inner()) + .into_script() + }, + Err(_) => panic!("Your RNG is busted"), + } + } + Err(_) => panic!("Your RNG is busted") + } } fn get_shutdown_scriptpubkey(&self) -> ShutdownScript { - ShutdownScript::new_p2wpkh_from_pubkey(self.shutdown_pubkey.clone()) + ShutdownScript::new_p2wpkh_from_pubkey(self.shutdown_pubkey.public_key.clone()) } } @@ -1410,6 +1440,10 @@ impl EntropySource for PhantomKeysManager { } impl NodeSigner for PhantomKeysManager { + fn get_inbound_payment_key_material(&self) -> KeyMaterial { + self.inbound_payment_key.clone() + } + fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> { match recipient { Recipient::Node => self.inner.get_node_id(Recipient::Node), @@ -1428,10 +1462,6 @@ impl NodeSigner for PhantomKeysManager { Ok(SharedSecret::new(other_key, &node_secret)) } - fn get_inbound_payment_key_material(&self) -> KeyMaterial { - self.inbound_payment_key.clone() - } - fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()> { let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data); let secret = match recipient { @@ -1461,8 +1491,8 @@ impl SignerProvider for PhantomKeysManager { self.inner.read_chan_signer(reader) } - fn get_destination_script(&self) -> Script { - self.inner.get_destination_script() + fn get_destination_script(&self, channel_keys_id: [u8; 32]) -> Script { + self.inner.get_destination_script(channel_keys_id) } fn get_shutdown_scriptpubkey(&self) -> ShutdownScript { diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 8b5e89d9441..ef67563e6c3 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -984,6 +984,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> { let mut secp_ctx = Secp256k1::new(); secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); + let channel_keys_id = holder_signer.channel_keys_id(); let shutdown_scriptpubkey = if config.channel_handshake_config.commit_upfront_shutdown_pubkey { Some(signer_provider.get_shutdown_scriptpubkey()) @@ -1021,7 +1022,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> { holder_signer, shutdown_scriptpubkey, - destination_script: signer_provider.get_destination_script(), + destination_script: signer_provider.get_destination_script(channel_keys_id), cur_holder_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, cur_counterparty_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, @@ -1344,6 +1345,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> { let mut secp_ctx = Secp256k1::new(); secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); + let channel_keys_id = holder_signer.channel_keys_id(); let chan = Channel { user_id, @@ -1368,7 +1370,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> { holder_signer, shutdown_scriptpubkey, - destination_script: signer_provider.get_destination_script(), + destination_script: signer_provider.get_destination_script(channel_keys_id), cur_holder_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, cur_counterparty_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, @@ -7087,7 +7089,7 @@ mod tests { fn read_chan_signer(&self, _data: &[u8]) -> Result<Self::Signer, DecodeError> { panic!(); } - fn get_destination_script(&self) -> Script { + fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Script { let secp_ctx = Secp256k1::signing_only(); let channel_monitor_claim_key = SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(); let channel_monitor_claim_key_hash = WPubkeyHash::hash(&PublicKey::from_secret_key(&secp_ctx, &channel_monitor_claim_key).serialize()); diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 67f6fd6b288..ba1ce2cc0b0 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -180,7 +180,7 @@ impl SignerProvider for OnlyReadsKeysInterface { )) } - fn get_destination_script(&self) -> Script { unreachable!(); } + fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Script { unreachable!(); } fn get_shutdown_scriptpubkey(&self) -> ShutdownScript { unreachable!(); } } @@ -809,7 +809,7 @@ impl SignerProvider for TestKeysInterface { )) } - fn get_destination_script(&self) -> Script { self.backing.get_destination_script() } + fn get_destination_script(&self, channel_keys_id: [u8; 32]) -> Script { self.backing.get_destination_script(channel_keys_id) } fn get_shutdown_scriptpubkey(&self) -> ShutdownScript { match &mut *self.expectations.lock().unwrap() {