Skip to content

Commit c5e0871

Browse files
binarybaronEinliterflasche
authored andcommitted
add tx_refund_amnesty
1 parent 97429a8 commit c5e0871

4 files changed

Lines changed: 216 additions & 7 deletions

File tree

swap-core/src/bitcoin.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod lock;
66
mod punish;
77
mod redeem;
88
mod refund;
9+
mod refund_amnesty;
910
mod timelocks;
1011

1112
pub use crate::bitcoin::cancel::TxCancel;
@@ -14,6 +15,7 @@ pub use crate::bitcoin::lock::TxLock;
1415
pub use crate::bitcoin::punish::TxPunish;
1516
pub use crate::bitcoin::redeem::TxRedeem;
1617
pub use crate::bitcoin::refund::TxRefund;
18+
pub use crate::bitcoin::refund_amnesty::TxRefundAmnesty;
1719
pub use crate::bitcoin::timelocks::{BlockHeight, ExpiredTimelocks};
1820
pub use crate::bitcoin::timelocks::{CancelTimelock, PunishTimelock};
1921
pub use ::bitcoin::amount::Amount;

swap-core/src/bitcoin/cancel.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,42 @@ impl TxCancel {
193193
}
194194
}
195195

196+
pub fn build_refund_with_amnesty_transaction(
197+
&self,
198+
refund_address: &Address,
199+
amnesty_descriptor: &Descriptor<::bitcoin::PublicKey>,
200+
amnesty_amount: Amount,
201+
spending_fee: Amount,
202+
) -> Transaction {
203+
let previous_output = self.as_outpoint();
204+
205+
let tx_in = TxIn {
206+
previous_output,
207+
script_sig: Default::default(),
208+
sequence: Sequence(0xFFFF_FFFF),
209+
witness: Default::default(),
210+
};
211+
212+
let refund_amount = self.amount() - amnesty_amount - spending_fee;
213+
214+
let tx_out_refund = TxOut {
215+
value: refund_amount,
216+
script_pubkey: refund_address.script_pubkey(),
217+
};
218+
219+
let tx_out_amnesty = TxOut {
220+
value: amnesty_amount,
221+
script_pubkey: amnesty_descriptor.script_pubkey(),
222+
};
223+
224+
Transaction {
225+
version: Version(2),
226+
lock_time: PackedLockTime::from_height(0).expect("0 to be below lock time threshold"),
227+
input: vec![tx_in],
228+
output: vec![tx_out_refund, tx_out_amnesty],
229+
}
230+
}
231+
196232
pub fn weight() -> Weight {
197233
Weight::from_wu(596)
198234
}

swap-core/src/bitcoin/refund.rs

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
use crate::bitcoin;
44
use crate::bitcoin::{
5-
verify_sig, Address, Amount, EmptyWitnessStack, NoInputs, NotThreeWitnesses, PublicKey,
6-
TooManyInputs, Transaction, TxCancel,
5+
build_shared_output_descriptor, verify_sig, Address, Amount, EmptyWitnessStack, NoInputs,
6+
NotThreeWitnesses, PublicKey, TooManyInputs, Transaction, TxCancel,
77
};
88
use ::bitcoin::sighash::SighashCache;
99
use ::bitcoin::{secp256k1, ScriptBuf, Weight};
@@ -23,16 +23,32 @@ pub struct TxRefund {
2323
inner: Transaction,
2424
digest: Sighash,
2525
cancel_output_descriptor: Descriptor<::bitcoin::PublicKey>,
26+
pub(in crate::bitcoin) amnesty_output_descriptor: Descriptor<::bitcoin::PublicKey>,
2627
watch_script: ScriptBuf,
2728
}
2829

2930
impl TxRefund {
30-
pub fn new(tx_cancel: &TxCancel, refund_address: &Address, spending_fee: Amount) -> Self {
31-
let tx_refund = tx_cancel.build_spend_transaction(refund_address, None, spending_fee);
31+
pub fn new(
32+
tx_cancel: &TxCancel,
33+
refund_address: &Address,
34+
A: PublicKey,
35+
B: PublicKey,
36+
amnesty_amount: Amount,
37+
spending_fee: Amount,
38+
) -> Result<Self> {
39+
let amnesty_output_descriptor = build_shared_output_descriptor(A.0, B.0)?;
40+
41+
let tx_refund = tx_cancel.build_refund_with_amnesty_transaction(
42+
refund_address,
43+
&amnesty_output_descriptor,
44+
amnesty_amount,
45+
spending_fee,
46+
);
3247

3348
let digest = SighashCache::new(&tx_refund)
3449
.p2wsh_signature_hash(
35-
0, // Only one input: cancel transaction
50+
// Only one input: cancel transaction
51+
0,
3652
&tx_cancel
3753
.output_descriptor
3854
.script_code()
@@ -42,12 +58,13 @@ impl TxRefund {
4258
)
4359
.expect("sighash");
4460

45-
Self {
61+
Ok(Self {
4662
inner: tx_refund,
4763
digest,
4864
cancel_output_descriptor: tx_cancel.output_descriptor.clone(),
65+
amnesty_output_descriptor,
4966
watch_script: refund_address.script_pubkey(),
50-
}
67+
})
5168
}
5269

5370
pub fn txid(&self) -> Txid {
@@ -58,6 +75,41 @@ impl TxRefund {
5875
self.digest
5976
}
6077

78+
pub fn amnesty_amount(&self) -> Amount {
79+
self.inner.output[1].value
80+
}
81+
82+
pub fn amnesty_outpoint(&self) -> ::bitcoin::OutPoint {
83+
::bitcoin::OutPoint::new(self.txid(), 1)
84+
}
85+
86+
pub fn build_amnesty_spend_transaction(
87+
&self,
88+
refund_address: &Address,
89+
spending_fee: Amount,
90+
) -> Transaction {
91+
use ::bitcoin::{transaction::Version, locktime::absolute::LockTime as PackedLockTime, Sequence, TxIn, TxOut};
92+
93+
let tx_in = TxIn {
94+
previous_output: self.amnesty_outpoint(),
95+
script_sig: Default::default(),
96+
sequence: Sequence(0xFFFF_FFFF),
97+
witness: Default::default(),
98+
};
99+
100+
let tx_out = TxOut {
101+
value: self.amnesty_amount() - spending_fee,
102+
script_pubkey: refund_address.script_pubkey(),
103+
};
104+
105+
Transaction {
106+
version: Version(2),
107+
lock_time: PackedLockTime::from_height(0).expect("0 to be below lock time threshold"),
108+
input: vec![tx_in],
109+
output: vec![tx_out],
110+
}
111+
}
112+
61113
pub fn add_signatures(
62114
self,
63115
(A, sig_a): (PublicKey, Signature),
@@ -77,6 +129,7 @@ impl TxRefund {
77129

78130
let sig_a = secp256k1::ecdsa::Signature::from_compact(&sig_a.to_bytes())?;
79131
let sig_b = secp256k1::ecdsa::Signature::from_compact(&sig_b.to_bytes())?;
132+
80133
// The order in which these are inserted doesn't matter
81134
satisfier.insert(
82135
A,
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use crate::bitcoin;
2+
use crate::bitcoin::{
3+
verify_sig, Address, Amount, EmptyWitnessStack, NoInputs, NotThreeWitnesses, PublicKey,
4+
TooManyInputs, Transaction, TxRefund,
5+
};
6+
use ::bitcoin::sighash::SighashCache;
7+
use ::bitcoin::{secp256k1, ScriptBuf, Weight};
8+
use ::bitcoin::{sighash::SegwitV0Sighash as Sighash, EcdsaSighashType, Txid};
9+
use anyhow::{bail, Context, Result};
10+
use bdk_wallet::miniscript::Descriptor;
11+
use bitcoin_wallet::primitives::Watchable;
12+
use curve25519_dalek::scalar::Scalar;
13+
use ecdsa_fun::Signature;
14+
use std::collections::HashMap;
15+
use std::sync::Arc;
16+
17+
use super::extract_ecdsa_sig;
18+
19+
#[derive(Debug, Clone)]
20+
pub struct TxRefundAmnesty {
21+
inner: Transaction,
22+
digest: Sighash,
23+
amensty_output_descriptor: Descriptor<::bitcoin::PublicKey>,
24+
watch_script: ScriptBuf,
25+
}
26+
27+
impl TxRefundAmnesty {
28+
pub fn new(tx_refund: &TxRefund, refund_address: &Address, spending_fee: Amount) -> Self {
29+
let tx_refund_amnesty = tx_refund.build_amnesty_spend_transaction(refund_address, spending_fee);
30+
31+
let digest = SighashCache::new(&tx_refund_amnesty)
32+
.p2wsh_signature_hash(
33+
0, // Only one input: amnesty box from tx_refund
34+
&tx_refund
35+
.amnesty_output_descriptor
36+
.script_code()
37+
.expect("scriptcode"),
38+
tx_refund.amnesty_amount(),
39+
EcdsaSighashType::All,
40+
)
41+
.expect("sighash");
42+
43+
Self {
44+
inner: tx_refund_amnesty,
45+
digest,
46+
amensty_output_descriptor: tx_refund.amnesty_output_descriptor.clone(),
47+
watch_script: refund_address.script_pubkey(),
48+
}
49+
}
50+
51+
pub fn txid(&self) -> Txid {
52+
self.inner.compute_txid()
53+
}
54+
55+
pub fn digest(&self) -> Sighash {
56+
self.digest
57+
}
58+
59+
pub fn add_signatures(
60+
self,
61+
(A, sig_a): (PublicKey, Signature),
62+
(B, sig_b): (PublicKey, Signature),
63+
) -> Result<Transaction> {
64+
let satisfier = {
65+
let mut satisfier = HashMap::with_capacity(2);
66+
67+
let A = ::bitcoin::PublicKey {
68+
compressed: true,
69+
inner: secp256k1::PublicKey::from_slice(&A.0.to_bytes())?,
70+
};
71+
let B = ::bitcoin::PublicKey {
72+
compressed: true,
73+
inner: secp256k1::PublicKey::from_slice(&B.0.to_bytes())?,
74+
};
75+
76+
let sig_a = secp256k1::ecdsa::Signature::from_compact(&sig_a.to_bytes())?;
77+
let sig_b = secp256k1::ecdsa::Signature::from_compact(&sig_b.to_bytes())?;
78+
79+
// The order in which these are inserted doesn't matter
80+
satisfier.insert(
81+
A,
82+
::bitcoin::ecdsa::Signature {
83+
signature: sig_a,
84+
sighash_type: EcdsaSighashType::All,
85+
},
86+
);
87+
satisfier.insert(
88+
B,
89+
::bitcoin::ecdsa::Signature {
90+
signature: sig_b,
91+
sighash_type: EcdsaSighashType::All,
92+
},
93+
);
94+
95+
satisfier
96+
};
97+
98+
let mut tx_refund = self.inner;
99+
self.amensty_output_descriptor
100+
.satisfy(&mut tx_refund.input[0], satisfier)?;
101+
102+
Ok(tx_refund)
103+
}
104+
105+
pub fn weight() -> Weight {
106+
Weight::from_wu(548)
107+
}
108+
}
109+
110+
impl Watchable for TxRefundAmnesty {
111+
fn id(&self) -> Txid {
112+
self.txid()
113+
}
114+
115+
fn script(&self) -> ScriptBuf {
116+
self.watch_script.clone()
117+
}
118+
}

0 commit comments

Comments
 (0)