diff --git a/src/program/protocols/claim.rs b/src/program/protocols/claim.rs index 1a815d67..fadf91fb 100644 --- a/src/program/protocols/claim.rs +++ b/src/program/protocols/claim.rs @@ -9,7 +9,7 @@ use protocol_builder::{ OutputType, }, }; -use tracing::info; +use tracing::{info, warn}; use crate::{ bitvmx::Context, @@ -22,8 +22,9 @@ use crate::{ action_wins, action_wins_prefix, get_tx_name_from_timeout, ClaimGateConfig, ProtocolHandler, WithClaimGateConfig, }, - timeouts::TxOwnershipTable, + timeouts::{TxOwnership, TxOwnershipTable}, }, + variables::VariableTypes, }, types::ProgramContext, }; @@ -325,8 +326,26 @@ pub fn auto_claim_start( protocol_handler.get_speedup_data_from_tx(&tx, program_context, None)?; info!("{claim_name}: {:?}", tx); protocol_handler.dispatch(program_context, tx, Some(speedup_data), None)?; + } else { + program_context.globals.set_var( + &protocol_handler.context().id, + "our_claim_gate_consumed", + VariableTypes::Bool(true), + )?; } } + + // at least for now, in every protocol, the last tx in the ownership table consumes the claim gate + if let Some(TxOwnership { tx_name, owner }) = ownership_table.txs.last() { + if tx_name == name && owner != &protocol_handler.role() { + program_context.globals.set_var( + &protocol_handler.context().id, + "our_claim_gate_consumed", + VariableTypes::Bool(true), + )?; + } + }; + Ok(()) } @@ -349,6 +368,12 @@ pub fn claim_state_handle( if name == ClaimGate::tx_start(dispute::PROVER_WINS) || name == ClaimGate::tx_start(dispute::VERIFIER_WINS) { + let our_claim_gate_consumed = program_context + .globals + .get_var(&protocol_handler.context().id, "our_claim_gate_consumed")? + .and_then(|var| var.bool().ok()) + .unwrap_or(false); + // my start if name == ClaimGate::tx_start(&my_claim) { info!("{my_claim} SUCCESS dispatch"); @@ -362,6 +387,8 @@ pub fn claim_state_handle( protocol_handler.get_speedup_data_from_tx(&tx, program_context, None)?; let height = Some(current_height + timelock_blocks); protocol_handler.dispatch(program_context, tx, Some(speedup_data), height)?; + } else if our_claim_gate_consumed { + warn!("Can't stop {other_claim}, claim gate was consumed by the opposing party."); } //other start else { diff --git a/src/program/protocols/gc_drp.rs b/src/program/protocols/gc_drp.rs index 420a35dc..1d2dc511 100644 --- a/src/program/protocols/gc_drp.rs +++ b/src/program/protocols/gc_drp.rs @@ -1,20 +1,22 @@ use std::collections::HashMap; -use bitcoin::{script::read_scriptint, PublicKey, ScriptBuf, Transaction, Txid}; +use bitcoin::{script::read_scriptint, PublicKey, ScriptBuf, Transaction, Txid, XOnlyPublicKey}; use bitcoin_coordinator::{coordinator::BitcoinCoordinatorApi, TransactionStatus}; +use bitcoin_script::script; use bitcoin_script_stack::stack::StackTracker; use bitvmx_job_dispatcher_types::garbled_messages::{ GCJobEvaluationResult, GCJobProveResult, GarbledJobType, }; use console::style; use key_manager::{ + errors::LamportError, key_type::BitcoinKeyType, - lamport::{LamportSignature, LamportType}, + lamport::{LamportPublicKey, LamportSignature, LamportType}, }; use protocol_builder::{ builder::ProtocolBuilder, graph::graph::GraphOptions, - scripts::{self, ProtocolScript, SignMode}, + scripts::{self, KeyType, ProtocolScript, SignMode}, types::{ connection::{InputSpec, OutputSpec}, input::{SighashType, SpendMode}, @@ -39,14 +41,16 @@ use crate::{ protocols::{ claim::{auto_claim_start, claim_state_handle, ClaimGate}, dispute::{self}, - protocol_handler::{timeout_input_tx, ClaimGateConfig, WithClaimGateConfig}, + protocol_handler::{ + timeout_input_tx, timeout_tx, ClaimGateConfig, WithClaimGateConfig, + }, timeouts::{auto_dispatch_timeout, cancel_timeout, TxOwnershipTable}, }, setup::steps::{ garbler_step::{GCConfiguration, GC_INPUT_PK, GC_OUTPUT_PK, GC_PUBLIC_DATA}, SetupStepName, }, - variables::{Globals, PartialUtxo, VariableTypes}, + variables::{Globals, PartialUtxo, VariableTypes, WitnessTypes}, }, types::{IncomingBitVMXApiMessages, ParticipantChannel, ProgramContext, PROGRAM_TYPE_GC_DRP}, }; @@ -62,10 +66,7 @@ pub const TIMELOCK_BLOCKS: &str = "timelock_blocks"; pub const EXTERNAL_START: &str = dispute::EXTERNAL_START; pub const START_CH: &str = dispute::START_CH; -pub const COMMITMENT: &str = dispute::COMMITMENT; -pub const CHALLENGE: &str = "CHALLENGE_TX"; pub const INPUT: &str = "INPUT_TX"; -pub const EQUIVOCATION: &str = "EQUIVOCATION_TX"; pub const VERIFIER_FINAL: &str = dispute::VERIFIER_FINAL; pub const PROVER_WINS: &str = dispute::PROVER_WINS; pub const VERIFIER_WINS: &str = dispute::VERIFIER_WINS; @@ -326,46 +327,6 @@ impl ProtocolHandler for GCDisputeResolutionProtocol { )?; } - self.add_connection_with_scripts( - context, - aggregated, - &mut protocol, - timelock_blocks, - AmountType::Auto, - speedup_dust, - START_CH, - COMMITMENT, - &claim_verifier, - Self::lamport_check( - prover_speedup_pub, - SignMode::Single, - &keys[0], - &Vec::<&str>::new(), - None, - )?, - (&prover_speedup_pub, &verifier_speedup_pub), - )?; - - self.add_connection_with_scripts( - context, - aggregated, - &mut protocol, - timelock_blocks, - AmountType::Auto, - speedup_dust, - COMMITMENT, - CHALLENGE, - &claim_prover, - Self::lamport_check( - verifier_speedup_pub, - SignMode::Single, - &keys[1], - &Vec::<&str>::new(), - None, - )?, - (&verifier_speedup_pub, &prover_speedup_pub), - )?; - let gc_input_pk = context .globals .get_var_or_err(&self.context().id, GC_INPUT_PK)? @@ -378,7 +339,7 @@ impl ProtocolHandler for GCDisputeResolutionProtocol { timelock_blocks, AmountType::Auto, speedup_dust, - CHALLENGE, + START_CH, INPUT, &claim_verifier, vec![scripts::verify_lamport_signatures( @@ -395,58 +356,51 @@ impl ProtocolHandler for GCDisputeResolutionProtocol { .get_var_or_err(&self.context().id, GC_OUTPUT_PK)? .lamport_pubkey()?; - self.add_connection_with_scripts( - context, + let mut connection_leaf = scripts::check_signature(aggregated, SignMode::Aggregate); + connection_leaf.set_assert_leaf_id(0); + + let mut timeout = scripts::timelock(2 * timelock_blocks, &aggregated, SignMode::Aggregate); + timeout.set_assert_leaf_id(1); + + let mut script = Self::lamport_check_false( aggregated, - &mut protocol, - timelock_blocks, - AmountType::Auto, - speedup_dust, - INPUT, - EQUIVOCATION, - &claim_prover, - vec![scripts::verify_lamport_signatures( - verifier_speedup_pub, - &vec![("circuit_output", &gc_output_pk)], - SignMode::Single, - Some(vec![Self::get_expects_false_script()]), - )?], - (&verifier_speedup_pub, &prover_speedup_pub), + SignMode::Aggregate, + &gc_output_pk, + "circuit_output", )?; - - let mut speedup_timeout = - scripts::check_aggregated_signature(&aggregated, SignMode::Aggregate); - speedup_timeout.set_assert_leaf_id(0); - let mut verifier_final = - scripts::timelock(2 * timelock_blocks, &aggregated, SignMode::Aggregate); - verifier_final.set_assert_leaf_id(1); + script.set_assert_leaf_id(2); let output_type = OutputType::taproot( AmountType::Auto, aggregated, - &vec![speedup_timeout, verifier_final], + &vec![connection_leaf, timeout, script], )?; protocol.add_connection( - &format!( - "{}_TL_{}_{}", - EQUIVOCATION, - 2 * timelock_blocks, - VERIFIER_FINAL - ), - EQUIVOCATION, - output_type.into(), + &format!("{}__{}", INPUT, VERIFIER_FINAL), + INPUT, + output_type.clone().into(), VERIFIER_FINAL, + InputSpec::Auto(SighashType::taproot_all(), SpendMode::Script { leaf: 2 }), + None, + None, + )?; + + protocol.add_connection( + &format!("{}_TL_{}_{}_TO", INPUT, 2 * timelock_blocks, VERIFIER_FINAL), + INPUT, + OutputSpec::Last, + &timeout_tx(VERIFIER_FINAL), InputSpec::Auto(SighashType::taproot_all(), SpendMode::Script { leaf: 1 }), Some(2 * timelock_blocks), None, )?; protocol.add_connection( - &format!("{}__CONNECTOR__INPUT_TO", EQUIVOCATION), - EQUIVOCATION, + &format!("{}__CONNECTOR__INPUT_TO", INPUT), + INPUT, OutputSpec::Last, - &timeout_input_tx(EQUIVOCATION), + &timeout_input_tx(INPUT), InputSpec::Auto(SighashType::taproot_all(), SpendMode::Script { leaf: 0 }), None, None, @@ -459,7 +413,21 @@ impl ProtocolHandler for GCDisputeResolutionProtocol { &verifier_speedup_pub, )?; + context.globals.set_var( + &self.context().id, + &timeout_tx(VERIFIER_FINAL), + VariableTypes::VecNumber(vec![1, 2 * timelock_blocks as u32]), + )?; + + pb.add_speedup_output( + &mut protocol, + &timeout_tx(VERIFIER_FINAL), + speedup_dust, + &prover_speedup_pub, + )?; + claim_verifier.add_claimer_win_connection(&mut protocol, VERIFIER_FINAL)?; + claim_prover.add_claimer_win_connection(&mut protocol, &timeout_tx(VERIFIER_FINAL))?; protocol.compute_minimum_output_values()?; protocol.build(&context.key_manager, &self.ctx.protocol_name)?; @@ -544,8 +512,7 @@ impl ProtocolHandler for GCDisputeResolutionProtocol { match vout { None => match (name.as_str(), self.role()) { (START_CH, ParticipantRole::Prover) => { - let (tx, speedup) = - self.get_tx_with_speedup_data(program_context, COMMITMENT)?; + let (tx, speedup) = self.get_tx_with_speedup_data(program_context, INPUT)?; self.dispatch(program_context, tx, Some(speedup), None)?; } (VERIFIER_FINAL, ParticipantRole::Verifier) => { @@ -586,18 +553,8 @@ impl ProtocolHandler for GCDisputeResolutionProtocol { } match (name.as_str(), self.role()) { - (COMMITMENT, ParticipantRole::Verifier) => { - let (tx, speedup) = - self.get_tx_with_speedup_data(program_context, CHALLENGE)?; - self.dispatch(program_context, tx, Some(speedup), None)?; - } - (CHALLENGE, ParticipantRole::Prover) => { - let (tx, speedup) = - self.get_tx_with_speedup_data(program_context, INPUT)?; - self.dispatch(program_context, tx, Some(speedup), None)?; - } (INPUT, ParticipantRole::Verifier) => { - let sigs = + let circuit_input = self.decode_lamport_for_speedup(tx_id, vout, &name, transaction)?; let protocol_id = &self.ctx.id; @@ -614,21 +571,15 @@ impl ProtocolHandler for GCDisputeResolutionProtocol { self.execute_job( program_context, &program_context.components_config.garbler, - GarbledJobType::Evaluate(config.circuit, public_data, sigs, output_dir), + GarbledJobType::Evaluate( + config.circuit, + public_data, + circuit_input, + output_dir, + ), "verifier_evaluate_circuit", )?; } - (EQUIVOCATION, ParticipantRole::Verifier) => { - let tx = self.get_signed( - program_context, - &VERIFIER_FINAL, - vec![(1, true).into()], - )?; - let speedup_data = - self.get_speedup_data_from_tx(&tx, program_context, None)?; - let height = Some(current_height + 2 * timelock_blocks as u32); - self.dispatch(program_context, tx, Some(speedup_data), height)?; - } _ => {} } } @@ -692,19 +643,14 @@ impl GCDisputeResolutionProtocol { // TODO: don't send if output is correct // We assume there is a single output let signature = LamportSignature::from_bytes(&result.output[0], 1, LamportType::SHA256)?; - let tx = self.get_signed(context, EQUIVOCATION, vec![(0, true).into()])?; - let protocol = self.load_protocol()?; - let (output_type, _) = protocol.get_script_from_output(EQUIVOCATION, 0)?; - - let sp = SpeedupData::new_with_input( - self.partial_utxo_from(&tx, 0), - output_type, - vec![signature], - 0, - true, - ); + context.witness.set_witness( + &self.ctx.id, + "circuit_output", + WitnessTypes::Lamport(signature), + )?; - self.dispatch(context, tx, Some(sp), None)?; + let tx = self.get_signed(context, VERIFIER_FINAL, vec![(2, true).into()])?; + self.dispatch(context, tx, None, None)?; // Mark this step as processed to prevent duplicate handling if let Some(key) = dedup_key { @@ -716,28 +662,55 @@ impl GCDisputeResolutionProtocol { Ok(()) } - fn lamport_check + std::fmt::Debug>( + fn lamport_check_false( aggregated: &PublicKey, sign_mode: SignMode, - keys: &ParticipantKeys, - var_names: &Vec, - extra_check_scripts: Option>, - ) -> Result, BitVMXError> { - info!("lamport check for variables: {:?}", &var_names); - - let names_and_keys = var_names - .iter() - .map(|v| Ok::<_, BitVMXError>((v, keys.get_lamport(v.as_ref())?))) - .collect::, _>>()?; - - let lamport_check = scripts::verify_lamport_signatures( - aggregated, - &names_and_keys, - sign_mode, - extra_check_scripts, + key: &LamportPublicKey, + key_name: &str, + ) -> Result { + let script = script!( + {XOnlyPublicKey::from(*aggregated).serialize().to_vec()} + OP_CHECKSIGVERIFY + { Self::ots_check_false_lamport(key) } + OP_PUSHNUM_1 + ) + .compile(); + + let mut lamport_check = ProtocolScript::new(script, aggregated, sign_mode); + lamport_check.add_key( + key_name, + key.derivation_index() + .ok_or(LamportError::ExtraDataMissing( + "derivation_index".to_string(), + ))?, + KeyType::lamport(key)?, + 0, )?; - Ok(vec![lamport_check]) + Ok(lamport_check) + } + + fn ots_check_false_lamport(key: &LamportPublicKey) -> ScriptBuf { + let mut stack = StackTracker::new(); + + for i in 0..key.len() { + stack.define(1, format!("signature_{}", i).as_str()); + } + + let (zeros, _ones) = key.to_hashes_string(); + + const OTS_SIZE: u32 = 32; + for idx in (0..key.len()).rev() { + stack.op_size(); + stack.number(OTS_SIZE); + stack.op_equalverify(); + + stack.op_sha256(); + stack.hexstr(&zeros[idx]); + stack.op_equalverify(); + } + + stack.get_script() } fn get_tx_with_speedup_data( @@ -768,21 +741,9 @@ impl GCDisputeResolutionProtocol { table.add_ignored(VERIFIER_FINAL.to_string()); table.add(START_CH, Verifier); - table.add(COMMITMENT, Prover); - table.add(CHALLENGE, Verifier); table.add(INPUT, Prover); - table.add(EQUIVOCATION, Verifier); table.add(VERIFIER_FINAL, Verifier); table } - - fn get_expects_false_script() -> ScriptBuf { - let mut stack = StackTracker::new(); - stack.define(1, "bit"); - stack.op_not(); - stack.op_verify(); - - stack.get_script() - } } diff --git a/src/program/protocols/protocol_handler.rs b/src/program/protocols/protocol_handler.rs index a3d59f07..07a31d78 100644 --- a/src/program/protocols/protocol_handler.rs +++ b/src/program/protocols/protocol_handler.rs @@ -9,10 +9,10 @@ use bitvmx_job_dispatcher::dispatcher_message::DispatcherMessage; use console::style; use enum_dispatch::enum_dispatch; use key_manager::key_manager::KeyManager; -use key_manager::lamport::{LamportSignature}; +use key_manager::lamport::{HashFunction, LamportSignature}; use key_manager::winternitz::{message_bytes_length, WinternitzSignature, WinternitzType}; use protocol_builder::builder::ProtocolBuilder; -use protocol_builder::scripts::{self, ProtocolScript, SignMode}; +use protocol_builder::scripts::{self, KeyType, ProtocolScript, SignMode}; use protocol_builder::types::connection::{InputSpec, OutputSpec}; use protocol_builder::types::input::{SighashType, SpendMode}; use protocol_builder::types::output::{AmountType, SpeedupData}; @@ -339,6 +339,9 @@ pub trait ProtocolHandler { let mut lamp_sigs = Vec::with_capacity(keys.len()); for key in keys.iter().rev() { + if !matches!(key.key_type(), KeyType::LamportKey { .. }) { + continue; + } if let Some(var) = program_context .globals .get_var(&self.context().id, key.name())? @@ -395,6 +398,9 @@ pub trait ProtocolHandler { let mut wots_sigs = vec![]; for k in protocol_script.get_keys().iter().rev() { + if !matches!(k.key_type(), KeyType::WinternitzKey { .. }) { + continue; + } //info!("Getting winternitz signature for key: {}", k.name()); if let Some(var) = program_context .globals @@ -459,6 +465,9 @@ pub trait ProtocolHandler { for sig in self.get_winternitz_signature_for_script(&spend, context)? { spending_args.push_winternitz_signature(sig); } + for sig in self.get_lamport_signature_for_script(&spend, context)? { + spending_args.push_lamport_signature(sig); + } let signature = protocol .input_taproot_script_spend_signature( @@ -588,7 +597,7 @@ pub trait ProtocolHandler { prev_vout: u32, prev_name: &str, transaction: &Transaction, - ) -> Result, BitVMXError> { + ) -> Result, BitVMXError> { let idx = self.find_prevout(prev_tx_id, prev_vout, transaction)?; let protocol = self.load_protocol()?; let script = &protocol.get_script_from_output(prev_name, prev_vout)?.1[0]; @@ -601,13 +610,17 @@ pub trait ProtocolHandler { for key in script.get_keys().iter() { let key_type = key.key_type(); let public_key = key_type.lamport_public_key()?; + let (hashes_0, _) = public_key.to_hashes(); - for _ in 0..public_key.len() { + for hash_0 in hashes_0 { let signature = iter .next() .ok_or(BitVMXError::ScriptSignatureMissing(key.name().to_string()))?; - signatures.push(signature.try_into()?); + let hash = public_key.hash_type().hash(signature).to_bytes(); + let bit = if hash == hash_0 { 0 } else { 1 }; + + signatures.push((signature.try_into()?, bit)); } } diff --git a/src/program/protocols/timeouts.rs b/src/program/protocols/timeouts.rs index c9a58afd..159e7d50 100644 --- a/src/program/protocols/timeouts.rs +++ b/src/program/protocols/timeouts.rs @@ -194,5 +194,23 @@ pub fn cancel_timeout( )?; } + let Some(orig_tx) = name.strip_suffix("_INPUT_TO") else { + return Ok(()); + }; + + let Some((_, Some(next))) = ownership_table.get_tx_and_next(&orig_tx) else { + return Ok(()); + }; + + if next.owner != protocol_handler.role() { + let tx_to_cancel = timeout_tx(&next.tx_name); + info!("Cancel timeout tx: {}", tx_to_cancel); + let tx_id = protocol_handler.get_transaction_id_by_name(&tx_to_cancel)?; + + program_context.bitcoin_coordinator.cancel( + bitcoin_coordinator::TypesToMonitor::Transactions(vec![tx_id], String::default(), None), + )?; + } + Ok(()) } diff --git a/tests/test_gnova_e2e.rs b/tests/test_gnova_e2e.rs index 86f82d5d..c4cacc3d 100644 --- a/tests/test_gnova_e2e.rs +++ b/tests/test_gnova_e2e.rs @@ -577,10 +577,16 @@ pub fn test_full_protocol() -> Result<()> { // Evaluate circuit with Prover's input info!("[Verifier] ✓ Evaluating circuit..."); + let circuit_input = signature + .to_array_hashes()? + .into_iter() + .zip(INPUT_BITS.iter().copied()) + .collect(); + let eval_job = GarbledJobType::Evaluate( TEST_CIRCUIT_PATH.to_string(), prove_result.clone(), - signature.to_array_hashes()?, + circuit_input, format!("{}/evaluate", output_dir), );