Skip to content

Add util fn for creating a Transaction from spendable outputs #789

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
10 changes: 5 additions & 5 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ use ln::channelmanager::{HTLCSource, PaymentPreimage, PaymentHash};
use ln::onchaintx::{OnchainTxHandler, InputDescriptors};
use chain::chaininterface::{BroadcasterInterface, FeeEstimator};
use chain::transaction::{OutPoint, TransactionData};
use chain::keysinterface::{SpendableOutputDescriptor, ChannelKeys, KeysInterface};
use chain::keysinterface::{SpendableOutputDescriptor, StaticPaymentOutputDescriptor, DelayedPaymentOutputDescriptor, ChannelKeys, KeysInterface};
use util::logger::Logger;
use util::ser::{Readable, ReadableArgs, MaybeReadable, Writer, Writeable, U48};
use util::byte_utils;
Expand Down Expand Up @@ -2201,24 +2201,24 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
break;
} else if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script {
if broadcasted_holder_revokable_script.0 == outp.script_pubkey {
spendable_output = Some(SpendableOutputDescriptor::DynamicOutputP2WSH {
spendable_output = Some(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor {
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
per_commitment_point: broadcasted_holder_revokable_script.1,
to_self_delay: self.on_holder_tx_csv,
output: outp.clone(),
revocation_pubkey: broadcasted_holder_revokable_script.2.clone(),
channel_keys_id: self.channel_keys_id,
channel_value_satoshis: self.channel_value_satoshis,
});
}));
break;
}
} else if self.counterparty_payment_script == outp.script_pubkey {
spendable_output = Some(SpendableOutputDescriptor::StaticOutputCounterpartyPayment {
spendable_output = Some(SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
output: outp.clone(),
channel_keys_id: self.channel_keys_id,
channel_value_satoshis: self.channel_value_satoshis,
});
}));
break;
} else if outp.script_pubkey == self.shutdown_script {
spendable_output = Some(SpendableOutputDescriptor::StaticOutput {
Expand Down
317 changes: 253 additions & 64 deletions lightning/src/chain/keysinterface.rs

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,11 +384,16 @@ impl TxCreationKeys {
}
}

/// The maximum length of a script returned by get_revokeable_redeemscript.
// Calculated as 6 bytes of opcodes, 1 byte push plus 2 bytes for contest_delay, and two public
// keys of 33 bytes (+ 1 push).
pub const REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH: usize = 6 + 3 + 34*2;

/// A script either spendable by the revocation
/// key or the broadcaster_delayed_payment_key and satisfying the relative-locktime OP_CSV constrain.
/// Encumbering a `to_holder` output on a commitment transaction or 2nd-stage HTLC transactions.
pub fn get_revokeable_redeemscript(revocation_key: &PublicKey, contest_delay: u16, broadcaster_delayed_payment_key: &PublicKey) -> Script {
Builder::new().push_opcode(opcodes::all::OP_IF)
let res = Builder::new().push_opcode(opcodes::all::OP_IF)
.push_slice(&revocation_key.serialize())
.push_opcode(opcodes::all::OP_ELSE)
.push_int(contest_delay as i64)
Expand All @@ -397,7 +402,9 @@ pub fn get_revokeable_redeemscript(revocation_key: &PublicKey, contest_delay: u1
.push_slice(&broadcaster_delayed_payment_key.serialize())
.push_opcode(opcodes::all::OP_ENDIF)
.push_opcode(opcodes::all::OP_CHECKSIG)
.into_script()
.into_script();
debug_assert!(res.len() <= REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH);
res
}

#[derive(Clone, PartialEq)]
Expand Down
156 changes: 33 additions & 123 deletions lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use chain::Watch;
use chain::channelmonitor;
use chain::channelmonitor::{ChannelMonitor, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY};
use chain::transaction::OutPoint;
use chain::keysinterface::{ChannelKeys, KeysInterface, SpendableOutputDescriptor};
use chain::keysinterface::{ChannelKeys, KeysInterface};
use ln::channel::{COMMITMENT_TX_BASE_WEIGHT, COMMITMENT_TX_WEIGHT_PER_HTLC};
use ln::channelmanager::{ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentPreimage, PaymentHash, PaymentSecret, PaymentSendFailure, BREAKDOWN_TIMEOUT};
use ln::channel::{Channel, ChannelError};
Expand All @@ -33,12 +33,8 @@ use util::config::UserConfig;

use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hash_types::{Txid, BlockHash};
use bitcoin::util::bip143;
use bitcoin::util::address::Address;
use bitcoin::util::bip32::{ChildNumber, ExtendedPubKey, ExtendedPrivKey};
use bitcoin::blockdata::block::{Block, BlockHeader};
use bitcoin::blockdata::transaction::{Transaction, TxOut, TxIn, SigHashType};
use bitcoin::blockdata::script::{Builder, Script};
use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::network::constants::Network;
Expand Down Expand Up @@ -4655,122 +4651,27 @@ fn test_manager_serialize_deserialize_inconsistent_monitor() {
macro_rules! check_spendable_outputs {
($node: expr, $der_idx: expr, $keysinterface: expr, $chan_value: expr) => {
{
let events = $node.chain_monitor.chain_monitor.get_and_clear_pending_events();
let mut events = $node.chain_monitor.chain_monitor.get_and_clear_pending_events();
let mut txn = Vec::new();
for event in events {
let mut all_outputs = Vec::new();
let secp_ctx = Secp256k1::new();
for event in events.drain(..) {
match event {
Event::SpendableOutputs { ref outputs } => {
for outp in outputs {
match *outp {
SpendableOutputDescriptor::StaticOutputCounterpartyPayment { ref outpoint, ref output, ref channel_keys_id, channel_value_satoshis } => {
assert_eq!(channel_value_satoshis, $chan_value);
let input = TxIn {
previous_output: outpoint.into_bitcoin_outpoint(),
script_sig: Script::new(),
sequence: 0,
witness: Vec::new(),
};
let outp = TxOut {
script_pubkey: Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(),
value: output.value,
};
let mut spend_tx = Transaction {
version: 2,
lock_time: 0,
input: vec![input],
output: vec![outp],
};
spend_tx.output[0].value -= (spend_tx.get_weight() + 2 + 1 + 73 + 35 + 3) as u64 / 4; // (Max weight + 3 (to round up)) / 4
let secp_ctx = Secp256k1::new();
let keys = $keysinterface.derive_channel_keys($chan_value, channel_keys_id);
let remotepubkey = keys.pubkeys().payment_point;
let witness_script = Address::p2pkh(&::bitcoin::PublicKey{compressed: true, key: remotepubkey}, Network::Testnet).script_pubkey();
let sighash = Message::from_slice(&bip143::SigHashCache::new(&spend_tx).signature_hash(0, &witness_script, output.value, SigHashType::All)[..]).unwrap();
let remotesig = secp_ctx.sign(&sighash, &keys.inner.payment_key);
spend_tx.input[0].witness.push(remotesig.serialize_der().to_vec());
spend_tx.input[0].witness[0].push(SigHashType::All as u8);
spend_tx.input[0].witness.push(remotepubkey.serialize().to_vec());
txn.push(spend_tx);
},
SpendableOutputDescriptor::DynamicOutputP2WSH { ref outpoint, ref per_commitment_point, ref to_self_delay, ref output, ref revocation_pubkey, ref channel_keys_id, channel_value_satoshis } => {
assert_eq!(channel_value_satoshis, $chan_value);
let input = TxIn {
previous_output: outpoint.into_bitcoin_outpoint(),
script_sig: Script::new(),
sequence: *to_self_delay as u32,
witness: Vec::new(),
};
let outp = TxOut {
script_pubkey: Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(),
value: output.value,
};
let mut spend_tx = Transaction {
version: 2,
lock_time: 0,
input: vec![input],
output: vec![outp],
};
let secp_ctx = Secp256k1::new();
let keys = $keysinterface.derive_channel_keys($chan_value, channel_keys_id);
if let Ok(delayed_payment_key) = chan_utils::derive_private_key(&secp_ctx, &per_commitment_point, &keys.inner.delayed_payment_base_key) {

let delayed_payment_pubkey = PublicKey::from_secret_key(&secp_ctx, &delayed_payment_key);
let witness_script = chan_utils::get_revokeable_redeemscript(revocation_pubkey, *to_self_delay, &delayed_payment_pubkey);
spend_tx.output[0].value -= (spend_tx.get_weight() + 2 + 1 + 73 + 1 + witness_script.len() + 1 + 3) as u64 / 4; // (Max weight + 3 (to round up)) / 4
let sighash = Message::from_slice(&bip143::SigHashCache::new(&spend_tx).signature_hash(0, &witness_script, output.value, SigHashType::All)[..]).unwrap();
let local_delayedsig = secp_ctx.sign(&sighash, &delayed_payment_key);
spend_tx.input[0].witness.push(local_delayedsig.serialize_der().to_vec());
spend_tx.input[0].witness[0].push(SigHashType::All as u8);
spend_tx.input[0].witness.push(vec!()); //MINIMALIF
spend_tx.input[0].witness.push(witness_script.clone().into_bytes());
} else { panic!() }
txn.push(spend_tx);
},
SpendableOutputDescriptor::StaticOutput { ref outpoint, ref output } => {
let secp_ctx = Secp256k1::new();
let input = TxIn {
previous_output: outpoint.into_bitcoin_outpoint(),
script_sig: Script::new(),
sequence: 0,
witness: Vec::new(),
};
let outp = TxOut {
script_pubkey: Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(),
value: output.value,
};
let mut spend_tx = Transaction {
version: 2,
lock_time: 0,
input: vec![input],
output: vec![outp.clone()],
};
spend_tx.output[0].value -= (spend_tx.get_weight() + 2 + 1 + 73 + 35 + 3) as u64 / 4; // (Max weight + 3 (to round up)) / 4
let secret = {
match ExtendedPrivKey::new_master(Network::Testnet, &$node.node_seed) {
Ok(master_key) => {
match master_key.ckd_priv(&secp_ctx, ChildNumber::from_hardened_idx($der_idx).expect("key space exhausted")) {
Ok(key) => key,
Err(_) => panic!("Your RNG is busted"),
}
}
Err(_) => panic!("Your rng is busted"),
}
};
let pubkey = ExtendedPubKey::from_private(&secp_ctx, &secret).public_key;
let witness_script = Address::p2pkh(&pubkey, Network::Testnet).script_pubkey();
let sighash = Message::from_slice(&bip143::SigHashCache::new(&spend_tx).signature_hash(0, &witness_script, output.value, SigHashType::All)[..]).unwrap();
let sig = secp_ctx.sign(&sighash, &secret.private_key.key);
spend_tx.input[0].witness.push(sig.serialize_der().to_vec());
spend_tx.input[0].witness[0].push(SigHashType::All as u8);
spend_tx.input[0].witness.push(pubkey.key.serialize().to_vec());
txn.push(spend_tx);
},
}
Event::SpendableOutputs { mut outputs } => {
for outp in outputs.drain(..) {
let mut outputs = vec![outp];
txn.push($keysinterface.backing.spend_spendable_outputs(&outputs, Vec::new(), Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, &secp_ctx).unwrap());
all_outputs.push(outputs.pop().unwrap());
}
},
_ => panic!("Unexpected event"),
};
}
if all_outputs.len() > 1 {
if let Ok(tx) = $keysinterface.backing.spend_spendable_outputs(&all_outputs, Vec::new(), Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, &secp_ctx) {
txn.push(tx);
}
}
txn
}
}
Expand Down Expand Up @@ -4860,9 +4761,10 @@ fn test_claim_on_remote_revoked_sizeable_push_msat() {
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1, 1, true, header.block_hash());

let spend_txn = check_spendable_outputs!(nodes[1], 1, node_cfgs[1].keys_manager, 100000);
assert_eq!(spend_txn.len(), 2);
assert_eq!(spend_txn.len(), 3);
check_spends!(spend_txn[0], revoked_local_txn[0]); // to_remote output on revoked remote commitment_tx
check_spends!(spend_txn[1], node_txn[0]);
check_spends!(spend_txn[2], revoked_local_txn[0], node_txn[0]); // Both outputs
}

#[test]
Expand Down Expand Up @@ -4957,8 +4859,10 @@ fn test_static_spendable_outputs_timeout_tx() {
expect_payment_failed!(nodes[1], our_payment_hash, true);

let spend_txn = check_spendable_outputs!(nodes[1], 1, node_cfgs[1].keys_manager, 100000);
assert_eq!(spend_txn.len(), 2); // SpendableOutput: remote_commitment_tx.to_remote, timeout_tx.output
assert_eq!(spend_txn.len(), 3); // SpendableOutput: remote_commitment_tx.to_remote, timeout_tx.output
check_spends!(spend_txn[0], commitment_tx[0]);
check_spends!(spend_txn[1], node_txn[0]);
check_spends!(spend_txn[2], node_txn[0], commitment_tx[0]); // All outputs
}

#[test]
Expand Down Expand Up @@ -5135,11 +5039,12 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_success_tx() {

// Check A's ChannelMonitor was able to generate the right spendable output descriptor
let spend_txn = check_spendable_outputs!(nodes[0], 1, node_cfgs[0].keys_manager, 100000);
assert_eq!(spend_txn.len(), 2);
assert_eq!(spend_txn.len(), 3);
assert_eq!(spend_txn[0].input.len(), 1);
check_spends!(spend_txn[0], revoked_local_txn[0]); // spending to_remote output from revoked local tx
assert_ne!(spend_txn[0].input[0].previous_output, revoked_htlc_txn[0].input[0].previous_output);
check_spends!(spend_txn[1], node_txn[1]); // spending justice tx output on the htlc success tx
check_spends!(spend_txn[2], revoked_local_txn[0], node_txn[1]); // Both outputs
}

#[test]
Expand Down Expand Up @@ -5372,6 +5277,7 @@ fn test_dynamic_spendable_outputs_local_htlc_success_tx() {

let payment_preimage = route_payment(&nodes[0], &vec!(&nodes[1])[..], 9000000).0;
let local_txn = get_local_commitment_txn!(nodes[1], chan_1.2);
assert_eq!(local_txn.len(), 1);
assert_eq!(local_txn[0].input.len(), 1);
check_spends!(local_txn[0], chan_1.3);

Expand All @@ -5392,10 +5298,13 @@ fn test_dynamic_spendable_outputs_local_htlc_success_tx() {
}
let node_txn = {
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
assert_eq!(node_txn.len(), 3);
assert_eq!(node_txn[0], node_txn[2]);
assert_eq!(node_txn[1], local_txn[0]);
assert_eq!(node_txn[0].input.len(), 1);
assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
check_spends!(node_txn[0], local_txn[0]);
vec![node_txn[0].clone(), node_txn[2].clone()]
vec![node_txn[0].clone()]
};

let header_201 = BlockHeader { version: 0x20000000, prev_blockhash: header.block_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
Expand All @@ -5404,9 +5313,8 @@ fn test_dynamic_spendable_outputs_local_htlc_success_tx() {

// Verify that B is able to spend its own HTLC-Success tx thanks to spendable output event given back by its ChannelMonitor
let spend_txn = check_spendable_outputs!(nodes[1], 1, node_cfgs[1].keys_manager, 100000);
assert_eq!(spend_txn.len(), 2);
assert_eq!(spend_txn.len(), 1);
check_spends!(spend_txn[0], node_txn[0]);
check_spends!(spend_txn[1], node_txn[1]);
}

fn do_test_fail_backwards_unrevoked_remote_announce(deliver_last_raa: bool, announce_latest: bool) {
Expand Down Expand Up @@ -5701,9 +5609,10 @@ fn test_dynamic_spendable_outputs_local_htlc_timeout_tx() {

// Verify that A is able to spend its own HTLC-Timeout tx thanks to spendable output event given back by its ChannelMonitor
let spend_txn = check_spendable_outputs!(nodes[0], 1, node_cfgs[0].keys_manager, 100000);
assert_eq!(spend_txn.len(), 2);
assert_eq!(spend_txn.len(), 3);
check_spends!(spend_txn[0], local_txn[0]);
check_spends!(spend_txn[1], htlc_timeout);
check_spends!(spend_txn[2], local_txn[0], htlc_timeout);
}

#[test]
Expand Down Expand Up @@ -5771,9 +5680,10 @@ fn test_key_derivation_params() {
// Verify that A is able to spend its own HTLC-Timeout tx thanks to spendable output event given back by its ChannelMonitor
let new_keys_manager = test_utils::TestKeysInterface::new(&seed, Network::Testnet);
let spend_txn = check_spendable_outputs!(nodes[0], 1, new_keys_manager, 100000);
assert_eq!(spend_txn.len(), 2);
assert_eq!(spend_txn.len(), 3);
check_spends!(spend_txn[0], local_txn_1[0]);
check_spends!(spend_txn[1], htlc_timeout);
check_spends!(spend_txn[2], local_txn_1[0], htlc_timeout);
}

#[test]
Expand Down
8 changes: 4 additions & 4 deletions lightning/src/util/macro_logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,11 @@ impl<'a> std::fmt::Display for DebugSpendable<'a> {
&SpendableOutputDescriptor::StaticOutput { ref outpoint, .. } => {
write!(f, "StaticOutput {}:{} marked for spending", outpoint.txid, outpoint.index)?;
}
&SpendableOutputDescriptor::DynamicOutputP2WSH { ref outpoint, .. } => {
write!(f, "DynamicOutputP2WSH {}:{} marked for spending", outpoint.txid, outpoint.index)?;
&SpendableOutputDescriptor::DelayedPaymentOutput(ref descriptor) => {
write!(f, "DelayedPaymentOutput {}:{} marked for spending", descriptor.outpoint.txid, descriptor.outpoint.index)?;
}
&SpendableOutputDescriptor::StaticOutputCounterpartyPayment { ref outpoint, .. } => {
write!(f, "DynamicOutputP2WPKH {}:{} marked for spending", outpoint.txid, outpoint.index)?;
&SpendableOutputDescriptor::StaticPaymentOutput(ref descriptor) => {
write!(f, "DynamicOutputP2WPKH {}:{} marked for spending", descriptor.outpoint.txid, descriptor.outpoint.index)?;
}
}
Ok(())
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/util/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ impl Logger for TestLogger {
}

pub struct TestKeysInterface {
backing: keysinterface::KeysManager,
pub backing: keysinterface::KeysManager,
pub override_session_priv: Mutex<Option<[u8; 32]>>,
pub override_channel_id_priv: Mutex<Option<[u8; 32]>>,
pub disable_revocation_policy_check: bool,
Expand Down Expand Up @@ -475,7 +475,7 @@ impl TestKeysInterface {
pub fn new(seed: &[u8; 32], network: Network) -> Self {
let now = Duration::from_secs(genesis_block(network).header.time as u64);
Self {
backing: keysinterface::KeysManager::new(seed, network, now.as_secs(), now.subsec_nanos()),
backing: keysinterface::KeysManager::new(seed, now.as_secs(), now.subsec_nanos()),
override_session_priv: Mutex::new(None),
override_channel_id_priv: Mutex::new(None),
disable_revocation_policy_check: false,
Expand Down
Loading