mirror of
https://github.com/conduition/dlctix.git
synced 2025-12-17 16:34:20 +01:00
add checks for OP_CSV enforcement of relative locktimes
This commit is contained in:
114
src/lib.rs
114
src/lib.rs
@@ -2,6 +2,9 @@
|
|||||||
//!
|
//!
|
||||||
//! See [the Github README](https://github.com/conduition/dlctix).
|
//! See [the Github README](https://github.com/conduition/dlctix).
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod regtest;
|
||||||
|
|
||||||
pub(crate) mod consts;
|
pub(crate) mod consts;
|
||||||
pub(crate) mod contract;
|
pub(crate) mod contract;
|
||||||
pub(crate) mod errors;
|
pub(crate) mod errors;
|
||||||
@@ -756,6 +759,32 @@ impl SignedContract {
|
|||||||
.input_weight_for_sellback_tx()
|
.input_weight_for_sellback_tx()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unchecked_sign_outcome_reclaim_tx_input<T: Borrow<TxOut>>(
|
||||||
|
&self,
|
||||||
|
outcome: &Outcome,
|
||||||
|
reclaim_tx: &mut Transaction,
|
||||||
|
input_index: usize,
|
||||||
|
prevouts: &Prevouts<T>,
|
||||||
|
market_maker_secret_key: Scalar,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let outcome_spend_info = self
|
||||||
|
.dlc
|
||||||
|
.outcome_tx_build
|
||||||
|
.outcome_spend_infos()
|
||||||
|
.get(outcome)
|
||||||
|
.ok_or(Error)?;
|
||||||
|
|
||||||
|
let witness = outcome_spend_info.witness_tx_reclaim(
|
||||||
|
reclaim_tx,
|
||||||
|
input_index,
|
||||||
|
prevouts,
|
||||||
|
market_maker_secret_key,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
reclaim_tx.input[input_index].witness = witness;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sign_outcome_reclaim_tx_input<T: Borrow<TxOut>>(
|
pub fn sign_outcome_reclaim_tx_input<T: Borrow<TxOut>>(
|
||||||
&self,
|
&self,
|
||||||
outcome: &Outcome,
|
outcome: &Outcome,
|
||||||
@@ -780,22 +809,13 @@ impl SignedContract {
|
|||||||
expected_prevout,
|
expected_prevout,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let outcome_spend_info = self
|
self.unchecked_sign_outcome_reclaim_tx_input(
|
||||||
.dlc
|
outcome,
|
||||||
.outcome_tx_build
|
|
||||||
.outcome_spend_infos()
|
|
||||||
.get(outcome)
|
|
||||||
.ok_or(Error)?;
|
|
||||||
|
|
||||||
let witness = outcome_spend_info.witness_tx_reclaim(
|
|
||||||
reclaim_tx,
|
reclaim_tx,
|
||||||
input_index,
|
input_index,
|
||||||
prevouts,
|
prevouts,
|
||||||
market_maker_secret_key,
|
market_maker_secret_key,
|
||||||
)?;
|
)
|
||||||
|
|
||||||
reclaim_tx.input[input_index].witness = witness;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign a cooperative closing transaction which spends the outcome transaction output.
|
/// Sign a cooperative closing transaction which spends the outcome transaction output.
|
||||||
@@ -851,6 +871,34 @@ impl SignedContract {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unchecked_sign_split_win_tx_input<T: Borrow<TxOut>>(
|
||||||
|
&self,
|
||||||
|
win_cond: &WinCondition,
|
||||||
|
win_tx: &mut Transaction,
|
||||||
|
input_index: usize,
|
||||||
|
prevouts: &Prevouts<T>,
|
||||||
|
ticket_preimage: Preimage,
|
||||||
|
player_secret_key: Scalar,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let split_spend_info = self
|
||||||
|
.dlc
|
||||||
|
.split_tx_build
|
||||||
|
.split_spend_infos()
|
||||||
|
.get(win_cond)
|
||||||
|
.ok_or(Error)?;
|
||||||
|
|
||||||
|
let witness = split_spend_info.witness_tx_win(
|
||||||
|
win_tx,
|
||||||
|
input_index,
|
||||||
|
prevouts,
|
||||||
|
ticket_preimage,
|
||||||
|
player_secret_key,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
win_tx.input[input_index].witness = witness;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sign_split_win_tx_input<T: Borrow<TxOut>>(
|
pub fn sign_split_win_tx_input<T: Borrow<TxOut>>(
|
||||||
&self,
|
&self,
|
||||||
win_cond: &WinCondition,
|
win_cond: &WinCondition,
|
||||||
@@ -885,6 +933,24 @@ impl SignedContract {
|
|||||||
expected_prevout,
|
expected_prevout,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
self.unchecked_sign_split_win_tx_input(
|
||||||
|
win_cond,
|
||||||
|
win_tx,
|
||||||
|
input_index,
|
||||||
|
prevouts,
|
||||||
|
ticket_preimage,
|
||||||
|
player_secret_key,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unchecked_sign_split_reclaim_tx_input<T: Borrow<TxOut>>(
|
||||||
|
&self,
|
||||||
|
win_cond: &WinCondition,
|
||||||
|
reclaim_tx: &mut Transaction,
|
||||||
|
input_index: usize,
|
||||||
|
prevouts: &Prevouts<T>,
|
||||||
|
market_maker_secret_key: Scalar,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let split_spend_info = self
|
let split_spend_info = self
|
||||||
.dlc
|
.dlc
|
||||||
.split_tx_build
|
.split_tx_build
|
||||||
@@ -892,15 +958,14 @@ impl SignedContract {
|
|||||||
.get(win_cond)
|
.get(win_cond)
|
||||||
.ok_or(Error)?;
|
.ok_or(Error)?;
|
||||||
|
|
||||||
let witness = split_spend_info.witness_tx_win(
|
let witness = split_spend_info.witness_tx_reclaim(
|
||||||
win_tx,
|
reclaim_tx,
|
||||||
input_index,
|
input_index,
|
||||||
prevouts,
|
prevouts,
|
||||||
ticket_preimage,
|
market_maker_secret_key,
|
||||||
player_secret_key,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
win_tx.input[input_index].witness = witness;
|
reclaim_tx.input[input_index].witness = witness;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -928,22 +993,13 @@ impl SignedContract {
|
|||||||
expected_prevout,
|
expected_prevout,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let split_spend_info = self
|
self.unchecked_sign_split_reclaim_tx_input(
|
||||||
.dlc
|
win_cond,
|
||||||
.split_tx_build
|
|
||||||
.split_spend_infos()
|
|
||||||
.get(win_cond)
|
|
||||||
.ok_or(Error)?;
|
|
||||||
|
|
||||||
let witness = split_spend_info.witness_tx_reclaim(
|
|
||||||
reclaim_tx,
|
reclaim_tx,
|
||||||
input_index,
|
input_index,
|
||||||
prevouts,
|
prevouts,
|
||||||
market_maker_secret_key,
|
market_maker_secret_key,
|
||||||
)?;
|
)
|
||||||
|
|
||||||
reclaim_tx.input[input_index].witness = witness;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sign_split_sellback_tx_input<T: Borrow<TxOut>>(
|
pub fn sign_split_sellback_tx_input<T: Borrow<TxOut>>(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
use crate::*;
|
||||||
|
|
||||||
use bitcoincore_rpc::{jsonrpc::serde_json, Auth, Client as BitcoinClient, RpcApi};
|
use bitcoincore_rpc::{jsonrpc::serde_json, Auth, Client as BitcoinClient, RpcApi};
|
||||||
use dlctix::*;
|
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
@@ -7,7 +8,7 @@ use bitcoin::{
|
|||||||
key::TweakedPublicKey,
|
key::TweakedPublicKey,
|
||||||
locktime::absolute::LockTime,
|
locktime::absolute::LockTime,
|
||||||
sighash::{Prevouts, SighashCache, TapSighashType},
|
sighash::{Prevouts, SighashCache, TapSighashType},
|
||||||
Address, Amount, FeeRate, Network, OutPoint, ScriptBuf, Transaction, TxIn, TxOut,
|
Address, Amount, FeeRate, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut,
|
||||||
};
|
};
|
||||||
use musig2::{CompactSignature, LiftedSignature, PartialSignature, PubNonce};
|
use musig2::{CompactSignature, LiftedSignature, PartialSignature, PubNonce};
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
@@ -587,8 +588,6 @@ fn ticketed_dlc_with_on_chain_resolutions() {
|
|||||||
.split_win_tx_input_and_prevout(&bob_win_cond)
|
.split_win_tx_input_and_prevout(&bob_win_cond)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// TODO test OP_CSV by spending without correct min sequence number
|
|
||||||
|
|
||||||
let mut bob_win_tx = Transaction {
|
let mut bob_win_tx = Transaction {
|
||||||
version: bitcoin::transaction::Version::TWO,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: LockTime::ZERO,
|
lock_time: LockTime::ZERO,
|
||||||
@@ -606,6 +605,36 @@ fn ticketed_dlc_with_on_chain_resolutions() {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ensure Bob cannot broadcast a win TX early. OP_CSV should
|
||||||
|
// enforce the relative locktime.
|
||||||
|
{
|
||||||
|
let mut invalid_bob_win_tx = bob_win_tx.clone();
|
||||||
|
invalid_bob_win_tx.input[0].sequence = Sequence::MAX;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.contract
|
||||||
|
.unchecked_sign_split_win_tx_input(
|
||||||
|
&bob_win_cond,
|
||||||
|
&mut invalid_bob_win_tx,
|
||||||
|
0, // input index
|
||||||
|
&Prevouts::All(&[bob_split_prevout]),
|
||||||
|
manager.bob.ticket_preimage,
|
||||||
|
manager.bob.seckey,
|
||||||
|
)
|
||||||
|
.expect("failed to sign win TX");
|
||||||
|
|
||||||
|
let err = manager
|
||||||
|
.rpc
|
||||||
|
.send_raw_transaction(&invalid_bob_win_tx)
|
||||||
|
.expect_err("early broadcast of win TX should fail");
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"JSON-RPC error: RPC error response: RpcError { code: -26, \
|
||||||
|
message: \"mandatory-script-verify-flag-failed (Locktime requirement not satisfied)\", \
|
||||||
|
data: None }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.contract
|
.contract
|
||||||
.sign_split_win_tx_input(
|
.sign_split_win_tx_input(
|
||||||
@@ -638,8 +667,6 @@ fn ticketed_dlc_with_on_chain_resolutions() {
|
|||||||
.split_reclaim_tx_input_and_prevout(&carol_win_cond)
|
.split_reclaim_tx_input_and_prevout(&carol_win_cond)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// TODO test OP_CSV encumberance on reclaim script
|
|
||||||
|
|
||||||
let mut reclaim_tx = Transaction {
|
let mut reclaim_tx = Transaction {
|
||||||
version: bitcoin::transaction::Version::TWO,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: LockTime::ZERO,
|
lock_time: LockTime::ZERO,
|
||||||
@@ -657,6 +684,35 @@ fn ticketed_dlc_with_on_chain_resolutions() {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ensure the Market Maker cannot broadcast a split reclaim TX early. OP_CSV
|
||||||
|
// should enforce the relative locktime.
|
||||||
|
{
|
||||||
|
let mut invalid_reclaim_tx = reclaim_tx.clone();
|
||||||
|
invalid_reclaim_tx.input[0].sequence = Sequence::MAX;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.contract
|
||||||
|
.unchecked_sign_split_reclaim_tx_input(
|
||||||
|
&carol_win_cond,
|
||||||
|
&mut invalid_reclaim_tx,
|
||||||
|
0, // input index
|
||||||
|
&Prevouts::All(&[carol_split_prevout]),
|
||||||
|
manager.market_maker_seckey,
|
||||||
|
)
|
||||||
|
.expect("failed to sign win TX");
|
||||||
|
|
||||||
|
let err = manager
|
||||||
|
.rpc
|
||||||
|
.send_raw_transaction(&invalid_reclaim_tx)
|
||||||
|
.expect_err("early broadcast of split reclaim TX should fail");
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"JSON-RPC error: RPC error response: RpcError { code: -26, \
|
||||||
|
message: \"mandatory-script-verify-flag-failed (Locktime requirement not satisfied)\", \
|
||||||
|
data: None }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.contract
|
.contract
|
||||||
.sign_split_reclaim_tx_input(
|
.sign_split_reclaim_tx_input(
|
||||||
@@ -870,6 +926,35 @@ fn ticketed_dlc_market_maker_reclaims_outcome_tx() {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ensure the Market Maker cannot broadcast an outcome reclaim TX early. OP_CSV
|
||||||
|
// should enforce the relative locktime.
|
||||||
|
{
|
||||||
|
let mut invalid_reclaim_tx = reclaim_tx.clone();
|
||||||
|
invalid_reclaim_tx.input[0].sequence = Sequence::MAX;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.contract
|
||||||
|
.unchecked_sign_outcome_reclaim_tx_input(
|
||||||
|
&outcome,
|
||||||
|
&mut invalid_reclaim_tx,
|
||||||
|
0, // input index
|
||||||
|
&Prevouts::All(&[reclaim_tx_prevout]),
|
||||||
|
manager.market_maker_seckey,
|
||||||
|
)
|
||||||
|
.expect("failed to sign outcome reclaim TX");
|
||||||
|
|
||||||
|
let err = manager
|
||||||
|
.rpc
|
||||||
|
.send_raw_transaction(&invalid_reclaim_tx)
|
||||||
|
.expect_err("early broadcast of outcome reclaim TX should fail");
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"JSON-RPC error: RPC error response: RpcError { code: -26, \
|
||||||
|
message: \"mandatory-script-verify-flag-failed (Locktime requirement not satisfied)\", \
|
||||||
|
data: None }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.contract
|
.contract
|
||||||
.sign_outcome_reclaim_tx_input(
|
.sign_outcome_reclaim_tx_input(
|
||||||
@@ -912,7 +997,6 @@ fn ticketed_dlc_contract_expiry_with_on_chain_resolution() {
|
|||||||
let manager = SimulationManager::new();
|
let manager = SimulationManager::new();
|
||||||
|
|
||||||
// The contract expires, paying out to dave.
|
// The contract expires, paying out to dave.
|
||||||
let outcome = Outcome::Expiry;
|
|
||||||
let expiry_tx = manager
|
let expiry_tx = manager
|
||||||
.contract
|
.contract
|
||||||
.expiry_tx()
|
.expiry_tx()
|
||||||
@@ -973,8 +1057,6 @@ fn ticketed_dlc_contract_expiry_with_on_chain_resolution() {
|
|||||||
.split_win_tx_input_and_prevout(&dave_win_cond)
|
.split_win_tx_input_and_prevout(&dave_win_cond)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// TODO test OP_CSV by spending without correct min sequence number
|
|
||||||
|
|
||||||
let mut dave_win_tx = Transaction {
|
let mut dave_win_tx = Transaction {
|
||||||
version: bitcoin::transaction::Version::TWO,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: LockTime::ZERO,
|
lock_time: LockTime::ZERO,
|
||||||
@@ -992,6 +1074,36 @@ fn ticketed_dlc_contract_expiry_with_on_chain_resolution() {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ensure Dave cannot broadcast the win TX early. OP_CSV should
|
||||||
|
// enforce the relative locktime.
|
||||||
|
{
|
||||||
|
let mut invalid_dave_win_tx = dave_win_tx.clone();
|
||||||
|
invalid_dave_win_tx.input[0].sequence = Sequence::MAX;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.contract
|
||||||
|
.unchecked_sign_split_win_tx_input(
|
||||||
|
&dave_win_cond,
|
||||||
|
&mut invalid_dave_win_tx,
|
||||||
|
0, // input index
|
||||||
|
&Prevouts::All(&[dave_split_prevout]),
|
||||||
|
manager.dave.ticket_preimage,
|
||||||
|
manager.dave.seckey,
|
||||||
|
)
|
||||||
|
.expect("failed to sign win TX");
|
||||||
|
|
||||||
|
let err = manager
|
||||||
|
.rpc
|
||||||
|
.send_raw_transaction(&invalid_dave_win_tx)
|
||||||
|
.expect_err("early broadcast of win TX should fail");
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"JSON-RPC error: RPC error response: RpcError { code: -26, \
|
||||||
|
message: \"mandatory-script-verify-flag-failed (Locktime requirement not satisfied)\", \
|
||||||
|
data: None }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.contract
|
.contract
|
||||||
.sign_split_win_tx_input(
|
.sign_split_win_tx_input(
|
||||||
Reference in New Issue
Block a user