Skip to content

Commit 1a1a31b

Browse files
committed
listener: expect and treat all the Cancel transactions
1 parent 2585495 commit 1a1a31b

File tree

5 files changed

+159
-89
lines changed

5 files changed

+159
-89
lines changed

contrib/tools/txbuilder/src/main.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{collections::BTreeMap, env, process, str::FromStr};
22

3+
use revault_net::message::watchtower::CancelFeerate;
34
use revault_tx::{
45
bitcoin::{
56
consensus::encode::serialize_hex, secp256k1, util::bip32, Address, Amount, OutPoint,
@@ -190,7 +191,6 @@ fn main() {
190191
eprintln!("Failed to derive transaction chain: '{}'", e);
191192
process::exit(1);
192193
});
193-
let mut cancel_tx = cancel_batch.into_feerate_20();
194194
let der_unvault_desc = unvault_desc.derive(derivation_index.into(), &secp);
195195
let unvault_txin = unvault_tx.spend_unvault_txin(&der_unvault_desc);
196196
let spend_txo = revault_tx::txouts::SpendTxOut::new(TxOut {
@@ -222,7 +222,15 @@ fn main() {
222222
})
223223
.collect();
224224
let unvault_sigs = sign(&mut unvault_tx, &stk_privkeys, &secp);
225-
let cancel_sigs = sign(&mut cancel_tx, &stk_privkeys, &secp);
225+
let mut cancel_sigs = BTreeMap::new();
226+
let mut cancel_txs = BTreeMap::new();
227+
for (feerate, mut cancel_tx) in cancel_batch.feerates_map() {
228+
cancel_sigs.insert(
229+
CancelFeerate(feerate),
230+
sign(&mut cancel_tx, &stk_privkeys, &secp),
231+
);
232+
cancel_txs.insert(CancelFeerate(feerate), serialize_hex(&cancel_tx.into_tx()));
233+
}
226234
let emer_sigs = sign(&mut emer_tx, &stk_privkeys, &secp);
227235
let unemer_sigs = sign(&mut unemer_tx, &stk_privkeys, &secp);
228236
let spend_privkeys: Vec<secp256k1::SecretKey> = man_xprivs
@@ -246,7 +254,7 @@ fn main() {
246254
"sigs": unvault_sigs,
247255
}),
248256
"cancel": serde_json::json!({
249-
"tx": serialize_hex(&cancel_tx.into_tx()),
257+
"tx": cancel_txs,
250258
"sigs": cancel_sigs,
251259
}),
252260
"emer": serde_json::json!({

src/listener.rs

+139-75
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66

77
use revault_net::{
88
message::{
9-
watchtower::{Signatures, Sigs, SigsResult},
9+
watchtower::{CancelFeerate, Signatures, Sigs, SigsResult},
1010
RequestParams, ResponseResult,
1111
},
1212
noise::SecretKey as NoisePrivkey,
@@ -16,7 +16,7 @@ use revault_tx::{
1616
transactions::{transaction_chain, RevaultPresignedTransaction, RevaultTransaction},
1717
};
1818

19-
use std::{collections::BTreeMap, io, net::TcpListener, path, sync};
19+
use std::{io, net::TcpListener, path, sync};
2020

2121
#[derive(Debug)]
2222
pub enum ListenerError {
@@ -92,7 +92,6 @@ fn process_sigs_message<C: secp256k1::Verification>(
9292
scripts_config.emergency_address.clone(),
9393
secp,
9494
)?;
95-
let mut cancel_tx = cancel_batch.into_feerate_20();
9695

9796
// If we already have it, just acknowledge it.
9897
if db_vault(db_path, &msg.deposit_outpoint)?.is_some() {
@@ -103,7 +102,8 @@ fn process_sigs_message<C: secp256k1::Verification>(
103102
return Ok(SigsResult { ack: true });
104103
}
105104

106-
// Otherwise, check the sigs they gave us are valid
105+
// Otherwise, check the sigs they gave us are valid and they provided all the expected feerates
106+
// for the Cancel transaction.
107107
let Sigs {
108108
signatures:
109109
Signatures {
@@ -113,19 +113,27 @@ fn process_sigs_message<C: secp256k1::Verification>(
113113
},
114114
..
115115
} = msg;
116-
let cancel_sigs = cancel
117-
.values()
118-
.cloned()
119-
.next()
120-
.unwrap_or_else(|| BTreeMap::new()); // FIXME
116+
121117
for (key, sig) in emergency.iter() {
122118
emer_tx.add_sig(*key, *sig, secp)?;
123119
}
124120
emer_tx.finalize(secp)?;
125-
for (key, sig) in cancel_sigs.iter() {
126-
cancel_tx.add_sig(*key, *sig, secp)?;
121+
122+
for (feerate, mut cancel_tx) in cancel_batch.feerates_map() {
123+
if let Some(sigs) = cancel.get(&CancelFeerate(feerate)) {
124+
for (key, sig) in sigs {
125+
cancel_tx.add_sig(*key, *sig, secp)?;
126+
}
127+
cancel_tx.finalize(secp)?;
128+
} else {
129+
eprintln!(
130+
"'sig' message is missing Cancel signature at {} sat/vb",
131+
feerate
132+
);
133+
return Ok(SigsResult { ack: false });
134+
}
127135
}
128-
cancel_tx.finalize(secp)?;
136+
129137
for (key, sig) in unvault_emergency.iter() {
130138
unemer_tx.add_sig(*key, *sig, secp)?;
131139
}
@@ -462,7 +470,7 @@ mod tests {
462470
&secp_ctx,
463471
)
464472
.unwrap();
465-
let cancel_tx = cancel_batch.into_feerate_20();
473+
let cancel_feerates_map = cancel_batch.feerates_map();
466474

467475
// This starts a dummy server to answer our gettxout requests
468476
let bitcoind = sync::Arc::from(dummy_bitcoind(deposit_value));
@@ -534,7 +542,7 @@ mod tests {
534542
.contains("Invalid signature"),
535543
);
536544

537-
// Enough invalid emergency sigs
545+
// Enough valid emergency sigs, but not for the right key
538546
let emergency: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> = stakeholders_priv
539547
.iter()
540548
.map(|xpriv| {
@@ -581,24 +589,70 @@ mod tests {
581589
})
582590
.collect();
583591

584-
// Now do the same dance with Cancel signatures.
592+
// Now do a similar dance with Cancel signatures.
593+
594+
// Cancel signatures are missing one required feerate. It will NACK it.
595+
let mut cancel = BTreeMap::new();
596+
for (feerate, cancel_tx) in cancel_feerates_map.iter() {
597+
if feerate == &Amount::from_sat(500) {
598+
continue;
599+
}
600+
601+
let sighash = cancel_tx.sig_hash().unwrap();
602+
let sighash = secp256k1::Message::from_slice(&sighash).unwrap();
603+
let cancel_sigs: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> =
604+
stakeholders_priv
605+
.iter()
606+
.map(|xpriv| {
607+
let privkey = xpriv.derive_priv(&secp_ctx, &[derivation_index]).unwrap();
608+
(
609+
privkey.private_key.public_key(&secp_ctx).key,
610+
secp_ctx.sign(&sighash, &privkey.private_key.key),
611+
)
612+
})
613+
.collect();
614+
cancel.insert(CancelFeerate(*feerate), cancel_sigs);
615+
}
616+
let unvault_emergency: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> =
617+
BTreeMap::new();
618+
let signatures = Signatures {
619+
emergency: emergency_valid.clone(),
620+
cancel,
621+
unvault_emergency,
622+
};
623+
let msg = Sigs {
624+
signatures,
625+
deposit_outpoint,
626+
derivation_index,
627+
};
628+
assert!(
629+
!process_sigs_message(&db_path, &scripts_config, &bitcoind, msg, &secp_ctx)
630+
.unwrap()
631+
.ack
632+
);
585633

586634
// Not enough Cancel signatures
587-
let sighash = cancel_tx.sig_hash().unwrap();
588-
let sighash = secp256k1::Message::from_slice(&sighash).unwrap();
589-
let cancel_sigs: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> = stakeholders_priv
590-
[..2]
591-
.iter()
592-
.map(|xpriv| {
593-
let privkey = xpriv.derive_priv(&secp_ctx, &[derivation_index]).unwrap();
594-
(
595-
privkey.private_key.public_key(&secp_ctx).key,
596-
secp_ctx.sign(&sighash, &privkey.private_key.key),
597-
)
598-
})
599-
.collect();
600-
let cancel =
601-
IntoIterator::into_iter([(CancelFeerate(Amount::from_sat(20)), cancel_sigs)]).collect();
635+
let mut cancel = BTreeMap::new();
636+
for (feerate, cancel_tx) in cancel_feerates_map.iter() {
637+
let sighash = cancel_tx.sig_hash().unwrap();
638+
let sighash = secp256k1::Message::from_slice(&sighash).unwrap();
639+
let privs = if feerate == &Amount::from_sat(20) {
640+
&stakeholders_priv[..2]
641+
} else {
642+
&stakeholders_priv
643+
};
644+
let cancel_sigs: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> = privs
645+
.iter()
646+
.map(|xpriv| {
647+
let privkey = xpriv.derive_priv(&secp_ctx, &[derivation_index]).unwrap();
648+
(
649+
privkey.private_key.public_key(&secp_ctx).key,
650+
secp_ctx.sign(&sighash, &privkey.private_key.key),
651+
)
652+
})
653+
.collect();
654+
cancel.insert(CancelFeerate(*feerate), cancel_sigs);
655+
}
602656
let unvault_emergency: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> =
603657
BTreeMap::new();
604658
let signatures = Signatures {
@@ -618,21 +672,24 @@ mod tests {
618672
.contains("Revault transaction finalisation error"),
619673
);
620674

621-
// Enough Cancel sigs, but invalid signature
622-
let bad_sighash = [0; 32];
623-
let bad_sighash = secp256k1::Message::from_slice(&bad_sighash).unwrap();
624-
let cancel_sigs: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> = stakeholders_priv
625-
.iter()
626-
.map(|xpriv| {
627-
let privkey = xpriv.derive_priv(&secp_ctx, &[derivation_index]).unwrap();
628-
(
629-
privkey.private_key.public_key(&secp_ctx).key,
630-
secp_ctx.sign(&bad_sighash, &privkey.private_key.key),
631-
)
632-
})
633-
.collect();
634-
let cancel =
635-
IntoIterator::into_iter([(CancelFeerate(Amount::from_sat(20)), cancel_sigs)]).collect();
675+
// Enough Cancel sigs, but invalid signatures
676+
let mut cancel = BTreeMap::new();
677+
let sighash = [0; 32];
678+
let sighash = secp256k1::Message::from_slice(&sighash).unwrap();
679+
for (feerate, _) in cancel_feerates_map.iter() {
680+
let cancel_sigs: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> =
681+
stakeholders_priv
682+
.iter()
683+
.map(|xpriv| {
684+
let privkey = xpriv.derive_priv(&secp_ctx, &[derivation_index]).unwrap();
685+
(
686+
privkey.private_key.public_key(&secp_ctx).key,
687+
secp_ctx.sign(&sighash, &privkey.private_key.key),
688+
)
689+
})
690+
.collect();
691+
cancel.insert(CancelFeerate(*feerate), cancel_sigs);
692+
}
636693
let unvault_emergency: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> =
637694
BTreeMap::new();
638695
let signatures = Signatures {
@@ -652,22 +709,26 @@ mod tests {
652709
.contains("Invalid signature")
653710
);
654711

655-
// Enough invalid Cancel sigs
656-
let cancel_sigs: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> = stakeholders_priv
657-
.iter()
658-
.map(|xpriv| {
659-
// Note the derivation index increment
660-
let privkey = xpriv
661-
.derive_priv(&secp_ctx, &[derivation_index.increment().unwrap()])
662-
.unwrap();
663-
(
664-
privkey.private_key.public_key(&secp_ctx).key,
665-
secp_ctx.sign(&sighash, &privkey.private_key.key),
666-
)
667-
})
668-
.collect();
669-
let cancel =
670-
IntoIterator::into_iter([(CancelFeerate(Amount::from_sat(20)), cancel_sigs)]).collect();
712+
// Enough Cancel sigs, but invalid pubkeys
713+
let mut cancel = BTreeMap::new();
714+
for (feerate, cancel_tx) in cancel_feerates_map.iter() {
715+
let sighash = cancel_tx.sig_hash().unwrap();
716+
let sighash = secp256k1::Message::from_slice(&sighash).unwrap();
717+
let cancel_sigs: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> =
718+
stakeholders_priv
719+
.iter()
720+
.map(|xpriv| {
721+
let privkey = xpriv
722+
.derive_priv(&secp_ctx, &[derivation_index.increment().unwrap()])
723+
.unwrap();
724+
(
725+
privkey.private_key.public_key(&secp_ctx).key,
726+
secp_ctx.sign(&sighash, &privkey.private_key.key),
727+
)
728+
})
729+
.collect();
730+
cancel.insert(CancelFeerate(*feerate), cancel_sigs);
731+
}
671732
let unvault_emergency: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> =
672733
BTreeMap::new();
673734
let signatures = Signatures {
@@ -688,20 +749,23 @@ mod tests {
688749
);
689750

690751
// Valid Cancel signatures.
691-
let cancel_valid_sigs: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> =
692-
stakeholders_priv
693-
.iter()
694-
.map(|xpriv| {
695-
let privkey = xpriv.derive_priv(&secp_ctx, &[derivation_index]).unwrap();
696-
(
697-
privkey.private_key.public_key(&secp_ctx).key,
698-
secp_ctx.sign(&sighash, &privkey.private_key.key),
699-
)
700-
})
701-
.collect();
702-
let cancel_valid: BTreeMap<_, _> =
703-
IntoIterator::into_iter([(CancelFeerate(Amount::from_sat(20)), cancel_valid_sigs)])
704-
.collect();
752+
let mut cancel_valid = BTreeMap::new();
753+
for (feerate, cancel_tx) in cancel_feerates_map.iter() {
754+
let sighash = cancel_tx.sig_hash().unwrap();
755+
let sighash = secp256k1::Message::from_slice(&sighash).unwrap();
756+
let cancel_sigs: BTreeMap<secp256k1::PublicKey, secp256k1::Signature> =
757+
stakeholders_priv
758+
.iter()
759+
.map(|xpriv| {
760+
let privkey = xpriv.derive_priv(&secp_ctx, &[derivation_index]).unwrap();
761+
(
762+
privkey.private_key.public_key(&secp_ctx).key,
763+
secp_ctx.sign(&sighash, &privkey.private_key.key),
764+
)
765+
})
766+
.collect();
767+
cancel_valid.insert(CancelFeerate(*feerate), cancel_sigs);
768+
}
705769

706770
// Now do the same dance with Unvault Emergency signatures.
707771

tests/test_chain.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ def test_simple_unvault_broadcast(miradord, bitcoind):
2727
bitcoind.rpc.sendrawtransaction(txs["unvault"]["tx"])
2828
bitcoind.generate_block(1, unvault_txid)
2929

30-
cancel_txid = bitcoind.rpc.decoderawtransaction(txs["cancel"]["tx"])["txid"]
30+
cancel_txid = bitcoind.rpc.decoderawtransaction(txs["cancel"]["tx"]["20"])["txid"]
3131
miradord.wait_for_logs(
3232
[
3333
f"Got a confirmed Unvault UTXO for vault at '{deposit_outpoint}'",
34-
f"Broadcasted Cancel transaction '{txs['cancel']['tx']}'",
34+
f"Broadcasted Cancel transaction '{txs['cancel']['tx']['20']}'",
3535
f"Unvault transaction '{unvault_txid}' for vault at '{deposit_outpoint}' is still unspent",
3636
]
3737
)
@@ -65,11 +65,11 @@ def test_spent_cancel_detection(miradord, bitcoind):
6565
unvault_txid = bitcoind.rpc.decoderawtransaction(txs["unvault"]["tx"])["txid"]
6666
bitcoind.rpc.sendrawtransaction(txs["unvault"]["tx"])
6767
bitcoind.generate_block(1, unvault_txid)
68-
cancel_tx = bitcoind.rpc.decoderawtransaction(txs["cancel"]["tx"])
68+
cancel_tx = bitcoind.rpc.decoderawtransaction(txs["cancel"]["tx"]["20"])
6969
miradord.wait_for_logs(
7070
[
7171
f"Got a confirmed Unvault UTXO for vault at '{deposit_outpoint}'",
72-
f"Broadcasted Cancel transaction '{txs['cancel']['tx']}'",
72+
f"Broadcasted Cancel transaction '{txs['cancel']['tx']['20']}'",
7373
f"Unvault transaction '{unvault_txid}' for vault at '{deposit_outpoint}' is still unspent",
7474
]
7575
)

tests/test_framework/miradord.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,7 @@ def send_sigs(
215215
params = {
216216
"signatures": {
217217
"emergency": emer_sigs,
218-
"cancel": {
219-
"20": cancel_sigs,
220-
},
218+
"cancel": cancel_sigs,
221219
"unvault_emergency": unemer_sigs,
222220
},
223221
"deposit_outpoint": deposit_outpoint,

0 commit comments

Comments
 (0)