1use super::script::{CheckSig, Multisig, SpendableScript};
33use super::script::{ReplacementDepositScript, SpendPath};
34use crate::builder::address::calculate_taproot_leaf_depths;
35use crate::builder::script::OtherSpendable;
36use crate::builder::transaction::challenge::*;
37use crate::builder::transaction::input::SpendableTxIn;
38use crate::builder::transaction::operator_assert::*;
39use crate::builder::transaction::operator_collateral::*;
40use crate::builder::transaction::operator_reimburse::*;
41use crate::builder::transaction::output::UnspentTxOut;
42use crate::config::protocol::ProtocolParamset;
43use crate::constants::{NON_EPHEMERAL_ANCHOR_AMOUNT, NON_STANDARD_V3};
44use crate::deposit::{DepositData, SecurityCouncil};
45use crate::errors::BridgeError;
46use crate::operator::RoundIndex;
47use crate::rpc::clementine::grpc_transaction_id;
48use crate::rpc::clementine::GrpcTransactionId;
49use crate::rpc::clementine::{
50 NormalSignatureKind, NormalTransactionId, NumberedTransactionId, NumberedTransactionType,
51};
52use bitcoin::hashes::Hash;
53use bitcoin::opcodes::all::OP_RETURN;
54use bitcoin::script::Builder;
55use bitcoin::transaction::Version;
56use bitcoin::{
57 Address, Amount, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, XOnlyPublicKey,
58};
59use hex;
60use input::UtxoVout;
61use serde::{Deserialize, Serialize};
62use std::sync::Arc;
63use thiserror::Error;
64
65pub use crate::builder::transaction::txhandler::*;
67pub use creator::{
68 create_round_txhandlers, create_txhandlers, ContractContext, KickoffWinternitzKeys,
69 ReimburseDbCache, TxHandlerCache,
70};
71pub use operator_collateral::{
72 create_burn_unused_kickoff_connectors_txhandler, create_round_nth_txhandler,
73};
74pub use operator_reimburse::{create_optimistic_payout_txhandler, create_payout_txhandler};
75pub use txhandler::Unsigned;
76
77pub mod challenge;
78mod creator;
79pub mod deposit_signature_owner;
80pub mod input;
81mod operator_assert;
82mod operator_collateral;
83mod operator_reimburse;
84pub mod output;
85pub mod sign;
86mod txhandler;
87
88type HiddenNode<'a> = &'a [u8; 32];
89
90#[derive(Debug, Error)]
91pub enum TxError {
92 #[error("Could not find input of transaction")]
94 TxInputNotFound,
95 #[error("Could not find output of transaction")]
96 TxOutputNotFound,
97 #[error("Attempted to set witness when it's already set")]
98 WitnessAlreadySet,
99 #[error("Script with index {0} not found for transaction")]
100 ScriptNotFound(usize),
101 #[error("Insufficient Context data for the requested TxHandler")]
102 InsufficientContext,
103 #[error("No scripts in TxHandler for the TxIn with index {0}")]
104 NoScriptsForTxIn(usize),
105 #[error("No script in TxHandler for the index {0}")]
106 NoScriptAtIndex(usize),
107 #[error("Spend Path in SpentTxIn in TxHandler not specified")]
108 SpendPathNotSpecified,
109 #[error("Actor does not own the key needed in P2TR keypath")]
110 NotOwnKeyPath,
111 #[error("public key of Checksig in script is not owned by Actor")]
112 NotOwnedScriptPath,
113 #[error("Couldn't find needed signature from database for tx: {:?}", _0)]
114 SignatureNotFound(TransactionType),
115 #[error("Couldn't find needed txhandler during creation for tx: {:?}", _0)]
116 TxHandlerNotFound(TransactionType),
117 #[error("BitvmSetupNotFound for operator {0:?}, deposit_txid {1}")]
118 BitvmSetupNotFound(XOnlyPublicKey, Txid),
119 #[error("Transaction input is missing spend info")]
120 MissingSpendInfo,
121 #[error("Incorrect watchtower challenge data length")]
122 IncorrectWatchtowerChallengeDataLength,
123 #[error("Latest blockhash script must be a single script")]
124 LatestBlockhashScriptNumber,
125 #[error("Round index cannot be used to create a Round transaction: {0:?}")]
126 InvalidRoundIndex(RoundIndex),
127 #[error("Index overflow")]
128 IndexOverflow,
129 #[error("Kickoff winternitz keys in DB has wrong size compared to paramset")]
130 KickoffWinternitzKeysDBInconsistency,
131
132 #[error(transparent)]
133 Other(#[from] eyre::Report),
134}
135
136#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
140pub enum TransactionType {
141 AssertTimeout(usize),
143 BurnUnusedKickoffConnectors,
144 Challenge,
145 ChallengeTimeout,
146 Disprove,
147 DisproveTimeout,
148 EmergencyStop,
149 Kickoff,
150 KickoffNotFinalized,
151 LatestBlockhash,
152 LatestBlockhashTimeout,
153 MiniAssert(usize),
154 MoveToVault,
155 OperatorChallengeAck(usize),
156 OperatorChallengeNack(usize),
157 OptimisticPayout,
158 Payout,
159 ReadyToReimburse,
160 Reimburse,
161 ReplacementDeposit,
162 Round,
163 UnspentKickoff(usize),
164 WatchtowerChallenge(usize),
165 WatchtowerChallengeTimeout(usize),
166
167 AllNeededForDeposit, YieldKickoffTxid, Dummy,
173}
174
175impl TryFrom<GrpcTransactionId> for TransactionType {
177 type Error = ::prost::UnknownEnumValue;
178 fn try_from(value: GrpcTransactionId) -> Result<Self, Self::Error> {
179 use NormalTransactionId as Normal;
180 use NumberedTransactionType as Numbered;
181 let inner_id = value.id.ok_or(::prost::UnknownEnumValue(0))?;
183 match inner_id {
184 grpc_transaction_id::Id::NormalTransaction(idx) => {
185 let tx_type = NormalTransactionId::try_from(idx)?;
186 match tx_type {
187 Normal::Round => Ok(Self::Round),
188 Normal::Kickoff => Ok(Self::Kickoff),
189 Normal::MoveToVault => Ok(Self::MoveToVault),
190 Normal::Payout => Ok(Self::Payout),
191 Normal::Challenge => Ok(Self::Challenge),
192 Normal::Disprove => Ok(Self::Disprove),
193 Normal::DisproveTimeout => Ok(Self::DisproveTimeout),
194 Normal::Reimburse => Ok(Self::Reimburse),
195 Normal::AllNeededForDeposit => Ok(Self::AllNeededForDeposit),
196 Normal::Dummy => Ok(Self::Dummy),
197 Normal::ReadyToReimburse => Ok(Self::ReadyToReimburse),
198 Normal::KickoffNotFinalized => Ok(Self::KickoffNotFinalized),
199 Normal::ChallengeTimeout => Ok(Self::ChallengeTimeout),
200 Normal::UnspecifiedTransactionType => Err(::prost::UnknownEnumValue(idx)),
201 Normal::BurnUnusedKickoffConnectors => Ok(Self::BurnUnusedKickoffConnectors),
202 Normal::YieldKickoffTxid => Ok(Self::YieldKickoffTxid),
203 Normal::ReplacementDeposit => Ok(Self::ReplacementDeposit),
204 Normal::LatestBlockhashTimeout => Ok(Self::LatestBlockhashTimeout),
205 Normal::LatestBlockhash => Ok(Self::LatestBlockhash),
206 Normal::OptimisticPayout => Ok(Self::OptimisticPayout),
207 }
208 }
209 grpc_transaction_id::Id::NumberedTransaction(transaction_id) => {
210 let tx_type = NumberedTransactionType::try_from(transaction_id.transaction_type)?;
211 match tx_type {
212 Numbered::WatchtowerChallenge => {
213 Ok(Self::WatchtowerChallenge(transaction_id.index as usize))
214 }
215 Numbered::OperatorChallengeNack => {
216 Ok(Self::OperatorChallengeNack(transaction_id.index as usize))
217 }
218 Numbered::OperatorChallengeAck => {
219 Ok(Self::OperatorChallengeAck(transaction_id.index as usize))
220 }
221 Numbered::AssertTimeout => {
222 Ok(Self::AssertTimeout(transaction_id.index as usize))
223 }
224 Numbered::UnspentKickoff => {
225 Ok(Self::UnspentKickoff(transaction_id.index as usize))
226 }
227 Numbered::MiniAssert => Ok(Self::MiniAssert(transaction_id.index as usize)),
228 Numbered::WatchtowerChallengeTimeout => Ok(Self::WatchtowerChallengeTimeout(
229 transaction_id.index as usize,
230 )),
231 Numbered::UnspecifiedIndexedTransactionType => {
232 Err(::prost::UnknownEnumValue(transaction_id.transaction_type))
233 }
234 }
235 }
236 }
237 }
238}
239
240impl From<TransactionType> for GrpcTransactionId {
241 fn from(value: TransactionType) -> Self {
242 use grpc_transaction_id::Id::*;
243 use NormalTransactionId as Normal;
244 use NumberedTransactionType as Numbered;
245 GrpcTransactionId {
246 id: Some(match value {
247 TransactionType::Round => NormalTransaction(Normal::Round as i32),
248 TransactionType::Kickoff => NormalTransaction(Normal::Kickoff as i32),
249 TransactionType::MoveToVault => NormalTransaction(Normal::MoveToVault as i32),
250 TransactionType::Payout => NormalTransaction(Normal::Payout as i32),
251 TransactionType::Challenge => NormalTransaction(Normal::Challenge as i32),
252 TransactionType::Disprove => NormalTransaction(Normal::Disprove as i32),
253 TransactionType::DisproveTimeout => {
254 NormalTransaction(Normal::DisproveTimeout as i32)
255 }
256 TransactionType::Reimburse => NormalTransaction(Normal::Reimburse as i32),
257 TransactionType::AllNeededForDeposit => {
258 NormalTransaction(Normal::AllNeededForDeposit as i32)
259 }
260 TransactionType::Dummy => NormalTransaction(Normal::Dummy as i32),
261 TransactionType::ReadyToReimburse => {
262 NormalTransaction(Normal::ReadyToReimburse as i32)
263 }
264 TransactionType::KickoffNotFinalized => {
265 NormalTransaction(Normal::KickoffNotFinalized as i32)
266 }
267 TransactionType::ChallengeTimeout => {
268 NormalTransaction(Normal::ChallengeTimeout as i32)
269 }
270 TransactionType::ReplacementDeposit => {
271 NormalTransaction(Normal::ReplacementDeposit as i32)
272 }
273 TransactionType::LatestBlockhashTimeout => {
274 NormalTransaction(Normal::LatestBlockhashTimeout as i32)
275 }
276 TransactionType::LatestBlockhash => {
277 NormalTransaction(Normal::LatestBlockhash as i32)
278 }
279 TransactionType::OptimisticPayout => {
280 NormalTransaction(Normal::OptimisticPayout as i32)
281 }
282 TransactionType::WatchtowerChallenge(index) => {
283 NumberedTransaction(NumberedTransactionId {
284 transaction_type: Numbered::WatchtowerChallenge as i32,
285 index: index as i32,
286 })
287 }
288 TransactionType::OperatorChallengeNack(index) => {
289 NumberedTransaction(NumberedTransactionId {
290 transaction_type: Numbered::OperatorChallengeNack as i32,
291 index: index as i32,
292 })
293 }
294 TransactionType::OperatorChallengeAck(index) => {
295 NumberedTransaction(NumberedTransactionId {
296 transaction_type: Numbered::OperatorChallengeAck as i32,
297 index: index as i32,
298 })
299 }
300 TransactionType::AssertTimeout(index) => {
301 NumberedTransaction(NumberedTransactionId {
302 transaction_type: Numbered::AssertTimeout as i32,
303 index: index as i32,
304 })
305 }
306 TransactionType::UnspentKickoff(index) => {
307 NumberedTransaction(NumberedTransactionId {
308 transaction_type: Numbered::UnspentKickoff as i32,
309 index: index as i32,
310 })
311 }
312 TransactionType::MiniAssert(index) => NumberedTransaction(NumberedTransactionId {
313 transaction_type: Numbered::MiniAssert as i32,
314 index: index as i32,
315 }),
316 TransactionType::WatchtowerChallengeTimeout(index) => {
317 NumberedTransaction(NumberedTransactionId {
318 transaction_type: Numbered::WatchtowerChallengeTimeout as i32,
319 index: index as i32,
320 })
321 }
322 TransactionType::BurnUnusedKickoffConnectors => {
323 NormalTransaction(Normal::BurnUnusedKickoffConnectors as i32)
324 }
325 TransactionType::YieldKickoffTxid => {
326 NormalTransaction(Normal::YieldKickoffTxid as i32)
327 }
328 TransactionType::EmergencyStop => {
329 NormalTransaction(Normal::UnspecifiedTransactionType as i32)
330 }
331 }),
332 }
333 }
334}
335
336pub fn anchor_output(amount: Amount) -> TxOut {
342 TxOut {
343 value: amount,
344 script_pubkey: ScriptBuf::from_hex("51024e73").expect("statically valid script"),
345 }
346}
347
348pub fn non_ephemeral_anchor_output() -> TxOut {
351 TxOut {
352 value: NON_EPHEMERAL_ANCHOR_AMOUNT,
353 script_pubkey: ScriptBuf::from_hex("51024e73").expect("statically valid script"),
354 }
355}
356
357pub fn op_return_txout<S: AsRef<bitcoin::script::PushBytes>>(slice: S) -> TxOut {
371 let script = Builder::new()
372 .push_opcode(OP_RETURN)
373 .push_slice(slice)
374 .into_script();
375
376 TxOut {
377 value: Amount::from_sat(0),
378 script_pubkey: script,
379 }
380}
381
382pub fn create_move_to_vault_txhandler(
395 deposit_data: &mut DepositData,
396 paramset: &'static ProtocolParamset,
397) -> Result<TxHandler<Unsigned>, BridgeError> {
398 let nofn_xonly_pk = deposit_data.get_nofn_xonly_pk()?;
399 let deposit_outpoint = deposit_data.get_deposit_outpoint();
400 let nofn_script = Arc::new(CheckSig::new(nofn_xonly_pk));
401 let security_council_script = Arc::new(Multisig::from_security_council(
402 deposit_data.security_council.clone(),
403 ));
404
405 let deposit_scripts = deposit_data.get_deposit_scripts(paramset)?;
406
407 Ok(TxHandlerBuilder::new(TransactionType::MoveToVault)
408 .with_version(NON_STANDARD_V3)
409 .add_input(
410 NormalSignatureKind::NotStored,
411 SpendableTxIn::from_scripts(
412 deposit_outpoint,
413 paramset.bridge_amount,
414 deposit_scripts,
415 None,
416 paramset.network,
417 ),
418 SpendPath::ScriptSpend(0),
419 DEFAULT_SEQUENCE,
420 )
421 .add_output(UnspentTxOut::from_scripts(
422 paramset.bridge_amount,
423 vec![nofn_script, security_council_script],
424 None,
425 paramset.network,
426 ))
427 .add_output(UnspentTxOut::from_partial(anchor_output(Amount::from_sat(
429 0,
430 ))))
431 .finalize())
432}
433
434pub fn create_emergency_stop_txhandler(
449 deposit_data: &mut DepositData,
450 move_to_vault_txhandler: &TxHandler,
451 paramset: &'static ProtocolParamset,
452) -> Result<TxHandler<Unsigned>, BridgeError> {
453 const EACH_EMERGENCY_STOP_VBYTES: Amount = Amount::from_sat(126);
455 let security_council = deposit_data.security_council.clone();
456
457 let builder = TxHandlerBuilder::new(TransactionType::EmergencyStop)
458 .add_input(
459 NormalSignatureKind::NotStored,
460 move_to_vault_txhandler.get_spendable_output(UtxoVout::DepositInMove)?,
461 SpendPath::ScriptSpend(0),
462 DEFAULT_SEQUENCE,
463 )
464 .add_output(UnspentTxOut::from_scripts(
465 paramset.bridge_amount - paramset.anchor_amount() - EACH_EMERGENCY_STOP_VBYTES * 3,
466 vec![Arc::new(Multisig::from_security_council(security_council))],
467 None,
468 paramset.network,
469 ))
470 .finalize();
471
472 Ok(builder)
473}
474
475pub fn combine_emergency_stop_txhandler(
490 txs: Vec<(Txid, Transaction)>,
491 add_anchor: bool,
492 paramset: &'static ProtocolParamset,
493) -> Transaction {
494 let (inputs, mut outputs): (Vec<TxIn>, Vec<TxOut>) = txs
495 .into_iter()
496 .map(|(_, tx)| (tx.input[0].clone(), tx.output[0].clone()))
497 .unzip();
498
499 if add_anchor {
500 outputs.push(anchor_output(paramset.anchor_amount()));
501 }
502
503 Transaction {
504 version: Version::non_standard(2),
505 lock_time: bitcoin::absolute::LockTime::ZERO,
506 input: inputs,
507 output: outputs,
508 }
509}
510
511pub fn create_replacement_deposit_txhandler(
528 old_move_txid: Txid,
529 input_outpoint: OutPoint,
530 old_nofn_xonly_pk: XOnlyPublicKey,
531 new_nofn_xonly_pk: XOnlyPublicKey,
532 paramset: &'static ProtocolParamset,
533 security_council: SecurityCouncil,
534) -> Result<TxHandler, BridgeError> {
535 Ok(TxHandlerBuilder::new(TransactionType::ReplacementDeposit)
536 .with_version(NON_STANDARD_V3)
537 .add_input(
538 NormalSignatureKind::NoSignature,
539 SpendableTxIn::from_scripts(
540 input_outpoint,
541 paramset.bridge_amount,
542 vec![
543 Arc::new(CheckSig::new(old_nofn_xonly_pk)),
544 Arc::new(Multisig::from_security_council(security_council.clone())),
545 ],
546 None,
547 paramset.network,
548 ),
549 crate::builder::script::SpendPath::ScriptSpend(0),
550 DEFAULT_SEQUENCE,
551 )
552 .add_output(UnspentTxOut::from_scripts(
553 paramset.bridge_amount,
554 vec![
555 Arc::new(ReplacementDepositScript::new(
556 new_nofn_xonly_pk,
557 old_move_txid,
558 )),
559 Arc::new(Multisig::from_security_council(security_council)),
560 ],
561 None,
562 paramset.network,
563 ))
564 .add_output(UnspentTxOut::from_partial(anchor_output(Amount::from_sat(
566 0,
567 ))))
568 .finalize())
569}
570
571pub fn create_disprove_taproot_output(
585 operator_timeout_script: Arc<dyn SpendableScript>,
586 additional_script: ScriptBuf,
587 disprove_path: DisprovePath,
588 amount: Amount,
589 network: bitcoin::Network,
590) -> UnspentTxOut {
591 use crate::bitvm_client::{SECP, UNSPENDABLE_XONLY_PUBKEY};
592 use bitcoin::taproot::{TapNodeHash, TaprootBuilder};
593
594 let mut scripts: Vec<ScriptBuf> = vec![additional_script.clone()];
595
596 let builder = match disprove_path.clone() {
597 DisprovePath::Scripts(extra_scripts) => {
598 let mut builder = TaprootBuilder::new();
599
600 builder = builder
601 .add_leaf(1, operator_timeout_script.to_script_buf())
602 .expect("add operator timeout script")
603 .add_leaf(2, additional_script)
604 .expect("add additional script");
605
606 let depths = calculate_taproot_leaf_depths(extra_scripts.len());
608
609 for (depth, script) in depths.into_iter().zip(extra_scripts.iter()) {
613 let main_tree_depth = 2 + depth;
614 builder = builder
615 .add_leaf(main_tree_depth, script.clone())
616 .expect("add inlined disprove script");
617 }
618
619 scripts.extend(extra_scripts);
621 builder
622 }
623 DisprovePath::HiddenNode(root_hash) => TaprootBuilder::new()
624 .add_leaf(1, operator_timeout_script.to_script_buf())
625 .expect("empty taptree will accept a script node")
626 .add_leaf(2, additional_script)
627 .expect("taptree with one node will accept a node at depth 2")
628 .add_hidden_node(2, TapNodeHash::from_byte_array(*root_hash))
629 .expect("taptree with two nodes will accept a node at depth 2"),
630 };
631
632 let taproot_spend_info = builder
633 .finalize(&SECP, *UNSPENDABLE_XONLY_PUBKEY)
634 .expect("valid taptree");
635
636 let address = Address::p2tr(
637 &SECP,
638 *UNSPENDABLE_XONLY_PUBKEY,
639 taproot_spend_info.merkle_root(),
640 network,
641 );
642
643 let mut spendable_scripts: Vec<Arc<dyn SpendableScript>> = vec![operator_timeout_script];
644 let other_spendable_scripts: Vec<Arc<dyn SpendableScript>> = scripts
645 .into_iter()
646 .map(|script| Arc::new(OtherSpendable::new(script)) as Arc<dyn SpendableScript>)
647 .collect();
648
649 spendable_scripts.extend(other_spendable_scripts);
650
651 UnspentTxOut::new(
652 TxOut {
653 value: amount,
654 script_pubkey: address.script_pubkey(),
655 },
656 spendable_scripts,
657 Some(taproot_spend_info),
658 )
659}
660
661pub fn create_taproot_output_with_hidden_node(
677 script: Arc<dyn SpendableScript>,
678 hidden_node: HiddenNode,
679 amount: Amount,
680 network: bitcoin::Network,
681) -> UnspentTxOut {
682 use crate::bitvm_client::{SECP, UNSPENDABLE_XONLY_PUBKEY};
683 use bitcoin::taproot::{TapNodeHash, TaprootBuilder};
684
685 let builder = TaprootBuilder::new()
686 .add_leaf(1, script.to_script_buf())
687 .expect("empty taptree will accept a script node")
688 .add_hidden_node(1, TapNodeHash::from_byte_array(*hidden_node))
689 .expect("taptree with one node will accept a node at depth 1");
690
691 let taproot_spend_info = builder
692 .finalize(&SECP, *UNSPENDABLE_XONLY_PUBKEY)
693 .expect("cannot fail since it is a valid taptree");
694
695 let address = Address::p2tr(
696 &SECP,
697 *UNSPENDABLE_XONLY_PUBKEY,
698 taproot_spend_info.merkle_root(),
699 network,
700 );
701
702 UnspentTxOut::new(
703 TxOut {
704 value: amount,
705 script_pubkey: address.script_pubkey(),
706 },
707 vec![script.clone()],
708 Some(taproot_spend_info),
709 )
710}
711
712#[cfg(test)]
713mod tests {
714 use super::*;
715 use bitcoin::secp256k1::XOnlyPublicKey;
716 use std::str::FromStr;
717
718 #[test]
719 fn test_security_council_from_str() {
720 let pk1 = XOnlyPublicKey::from_slice(&[1; 32]).unwrap();
722 let pk2 = XOnlyPublicKey::from_slice(&[2; 32]).unwrap();
723
724 let input = format!(
726 "2:{},{}",
727 hex::encode(pk1.serialize()),
728 hex::encode(pk2.serialize())
729 );
730 let council = SecurityCouncil::from_str(&input).unwrap();
731 assert_eq!(council.threshold, 2);
732 assert_eq!(council.pks.len(), 2);
733 assert_eq!(council.pks[0], pk1);
734 assert_eq!(council.pks[1], pk2);
735
736 let input = format!(
738 "3:{},{}",
739 hex::encode(pk1.serialize()),
740 hex::encode(pk2.serialize())
741 );
742 assert!(SecurityCouncil::from_str(&input).is_err());
743
744 let input = "2:invalid,pk2";
746 assert!(SecurityCouncil::from_str(input).is_err());
747
748 assert!(SecurityCouncil::from_str("2").is_err());
750 assert!(SecurityCouncil::from_str(":").is_err());
751
752 let input = format!(
754 "2:{},{}:extra",
755 hex::encode(pk1.serialize()),
756 hex::encode(pk2.serialize())
757 );
758 assert!(SecurityCouncil::from_str(&input).is_err());
759
760 assert!(SecurityCouncil::from_str("2:").is_err());
762 }
763
764 #[test]
765 fn test_security_council_round_trip() {
766 let pk1 = XOnlyPublicKey::from_slice(&[1; 32]).unwrap();
768 let pk2 = XOnlyPublicKey::from_slice(&[2; 32]).unwrap();
769
770 let original = SecurityCouncil {
771 pks: vec![pk1, pk2],
772 threshold: 2,
773 };
774
775 let string = original.to_string();
776 let parsed = SecurityCouncil::from_str(&string).unwrap();
777
778 assert_eq!(original, parsed);
779 }
780}