diff --git a/Cargo.lock b/Cargo.lock index 585b518fd..8372ab2f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3597,6 +3597,7 @@ dependencies = [ "reqwest 0.11.27", "secp256k1", "serde", + "serde-big-array", "serde_json", "serial_test", "sha2", @@ -6740,6 +6741,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde-wasm-bindgen" version = "0.6.5" diff --git a/goat/Cargo.toml b/goat/Cargo.toml index 7379b6c03..b1a6c0c41 100644 --- a/goat/Cargo.toml +++ b/goat/Cargo.toml @@ -12,6 +12,7 @@ strum.workspace = true strum_macros.workspace = true bitcoin-scriptexec.workspace = true serde.workspace = true +serde-big-array.workspace = true num-traits.workspace = true sha2.workspace = true tokio.workspace = true @@ -48,4 +49,3 @@ lto = true [profile.profiling] inherits = "release" debug = true - diff --git a/goat/src/assert_scripts.rs b/goat/src/assert_scripts.rs new file mode 100644 index 000000000..a91830de0 --- /dev/null +++ b/goat/src/assert_scripts.rs @@ -0,0 +1,253 @@ +use crate::wots::{Wots, Wots64, Wots96}; +use bitvm::{signatures::WinternitzSecret, treepp::*}; +use serde::{Deserialize, Serialize}; + +pub const INPUT_WIRE_NUM: usize = 512; +pub const PROVER_SIG_LEN: usize = 2 * Wots64::TOTAL_DIGIT_LEN as usize; +pub type OperatorAssertSecretKey = WinternitzSecret; +pub type OperatorAssertPublicKey = ::PublicKey; + +pub type Label = Vec; +pub type LabelHash = [u8; 20]; +#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] +pub struct WireHash { + pub true_label_hash: LabelHash, + pub false_label_hash: LabelHash, +} + +pub fn label_hash(label: &Label) -> LabelHash { + use bitcoin::hashes::{hash160, Hash}; + hash160::Hash::hash(label).to_byte_array() +} + +fn label_hash_script() -> Script { + script! { + OP_HASH160 + } +} + +pub fn wrongly_challenged_script(hashlock: &LabelHash) -> Script { + script! { + { label_hash_script() } + { hashlock.to_vec() } + OP_EQUALVERIFY + OP_TRUE + } +} + +pub fn verify_prover_assert_script_512_wire( + prover_wots_pubkey: &::PublicKey, +) -> Script { + script! { + { Wots64::checksig_verify_and_clear_stack(prover_wots_pubkey) } + OP_TRUE + } +} + +pub fn verify_verifier_assert_script_512_wire( + prover_wots_pubkey: &::PublicKey, + label_hashes: &[WireHash; 512], +) -> Script { + script! { + for byte_hashes in label_hashes.chunks(8).rev() { + for wire_hashes in byte_hashes.chunks(4).rev() { + { 0 } + for (bit_index, wire_hash) in wire_hashes.iter().enumerate().rev() { + OP_SWAP + OP_DUP + { label_hash_script() } + { wire_hash.true_label_hash.to_vec() } + OP_EQUAL + OP_IF + OP_DROP + { 1 << bit_index } + OP_ADD + OP_ELSE + { label_hash_script() } + { wire_hash.false_label_hash.to_vec() } + OP_EQUALVERIFY + OP_ENDIF + } + OP_TOALTSTACK + } + } + { Wots64::checksig_verify(prover_wots_pubkey) } + for _ in 0..(Wots64::MSG_BYTE_LEN * 2) { + OP_FROMALTSTACK + OP_EQUALVERIFY + } + OP_TRUE + } +} + +pub fn verify_prover_assert_script_768_wire( + prover_wots_pubkey: &::PublicKey, +) -> Script { + script! { + { Wots96::checksig_verify_and_clear_stack(prover_wots_pubkey) } + OP_TRUE + } +} + +pub fn verify_verifier_assert_script_768_wire( + prover_wots_pubkey: &::PublicKey, + label_hashes: [WireHash; 768], +) -> Script { + script! { + for byte_hashes in label_hashes.chunks(8).rev() { + { 0 } + for (bit_index, wire_hash) in byte_hashes.iter().enumerate().rev() { + OP_SWAP + OP_DUP + { label_hash_script() } + { wire_hash.true_label_hash.to_vec() } + OP_EQUAL + OP_IF + OP_DROP + { 1 << bit_index } + OP_ADD + OP_ELSE + { label_hash_script() } + { wire_hash.false_label_hash.to_vec() } + OP_EQUALVERIFY + OP_ENDIF + } + OP_TOALTSTACK + } + { Wots96::checksig_verify(prover_wots_pubkey) } + for _ in 0..Wots96::MSG_BYTE_LEN { + OP_FROMALTSTACK + OP_EQUALVERIFY + } + OP_TRUE + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bitvm::execute_script; + use rand::RngCore; + + #[test] + fn test_verify_verifier_assert_script_512_wire() { + let secret = Wots64::generate_secret_key(); + let public_key = Wots64::generate_public_key(&secret); + + let true_labels: Vec> = (0..512) + .map(|i| { + let mut label = vec![0x54; 20]; + label[0] = (i & 0xff) as u8; + label[1] = (i >> 8) as u8; + label + }) + .collect(); + let false_labels: Vec> = (0..512) + .map(|i| { + let mut label = vec![0x46; 20]; + label[0] = (i & 0xff) as u8; + label[1] = (i >> 8) as u8; + label + }) + .collect(); + + let mut msg: [u8; 64] = [0; 64]; + rand::thread_rng().fill_bytes(&mut msg); + + let mut bits = [false; 512]; + for i in 0..512 { + bits[i] = (msg[i / 8] >> (i % 8)) & 1 == 1; + } + + let label_hashes = std::array::from_fn(|i| WireHash { + true_label_hash: label_hash(&true_labels[i]), + false_label_hash: label_hash(&false_labels[i]), + }); + let selected_labels: Vec> = (0..512) + .map(|i| { + if bits[i] { + true_labels[i].clone() + } else { + false_labels[i].clone() + } + }) + .collect(); + + let s = script! { + { Wots64::sign_to_raw_witness(&secret, &msg) } + for wire_index in 0..512 { + { selected_labels[wire_index].clone() } + } + { verify_verifier_assert_script_512_wire(&public_key, &label_hashes) } + }; + println!("verifier assert full script size: {}", s.len()); + let result = execute_script(s); + println!( + "verifier assert max stack item size: {:?}", + result.stats.max_nb_stack_items + ); + assert!(result.success); + assert_eq!(result.final_stack.len(), 1); + } + + #[test] + fn test_verify_verifier_assert_script_768_wire() { + let secret = Wots96::generate_secret_key(); + let public_key = Wots96::generate_public_key(&secret); + + let true_labels: Vec> = (0..768) + .map(|i| { + let mut label = vec![0x54; 20]; + label[0] = (i & 0xff) as u8; + label[1] = (i >> 8) as u8; + label + }) + .collect(); + let false_labels: Vec> = (0..768) + .map(|i| { + let mut label = vec![0x46; 20]; + label[0] = (i & 0xff) as u8; + label[1] = (i >> 8) as u8; + label + }) + .collect(); + + let mut msg: [u8; 96] = [0; 96]; + rand::thread_rng().fill_bytes(&mut msg); + + let mut bits = [false; 768]; + for i in 0..768 { + bits[i] = (msg[i / 8] >> (i % 8)) & 1 == 1; + } + + let label_hashes = std::array::from_fn(|i| WireHash { + true_label_hash: label_hash(&true_labels[i]), + false_label_hash: label_hash(&false_labels[i]), + }); + let selected_labels: Vec> = (0..768) + .map(|i| { + if bits[i] { + true_labels[i].clone() + } else { + false_labels[i].clone() + } + }) + .collect(); + + let s = script! { + { Wots96::sign_to_raw_witness(&secret, &msg) } + for wire_index in 0..768 { + { selected_labels[wire_index].clone() } + } + { verify_verifier_assert_script_768_wire(&public_key, label_hashes) } + }; + println!("verifier assert full script size: {}", s.len()); + let result = execute_script(s); + println!( + "verifier assert max stack item size: {:?}", + result.stats.max_nb_stack_items + ); + assert!(result.success); + assert_eq!(result.final_stack.len(), 1); + } +} diff --git a/goat/src/connectors/assert_connectors.rs b/goat/src/connectors/assert_connectors.rs index e15250560..13249a618 100644 --- a/goat/src/connectors/assert_connectors.rs +++ b/goat/src/connectors/assert_connectors.rs @@ -1,154 +1,78 @@ -use super::{super::transactions::base::Input, base::*}; use crate::{ - constants::ASSERT_COMMIT_TIMELOCK, - disprove_scripts::AssertPubkeys as AssertWotsPublicKeys, - error::Error, - scripts::generate_timelock_taproot_script, - utils::{num_blocks_per_network, remove_script_and_control_block_from_witness}, + assert_scripts::*, constants::PROVER_CONNECTOR_TIMELOCK, utils::num_blocks_per_network, }; use bitcoin::{ taproot::{TaprootBuilder, TaprootSpendInfo}, - Address, Network, ScriptBuf, TxIn, Witness, XOnlyPublicKey, + Address, Network, ScriptBuf, TxIn, XOnlyPublicKey, }; -use bitvm::{ - chunk::api::type_conversion_utils::RawWitness, - signatures::{ - signing_winternitz::WinternitzPublicKey, CompactWots, WinternitzSecret, Wots, Wots32, - }, -}; -use bitvm::{signatures::Wots16, treepp::*}; +use bitvm::{chunk::api::type_conversion_utils::RawWitness, treepp::*}; use secp256k1::SECP256K1; use serde::{Deserialize, Serialize}; +use serde_big_array::BigArray; + +use super::{ + super::{error::Error, scripts::*, transactions::base::Input}, + base::*, +}; #[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] -pub struct AssertCommitConnector { +pub struct VerifierConnector { pub network: Network, pub n_of_n_taproot_public_key: XOnlyPublicKey, - pub wots32_pubkeys: Vec, - pub wots16_pubkeys: Vec, - pub assert_commit_blocks_timelock: u32, + #[serde(with = "BigArray")] + pub operator_wots_public_key: OperatorAssertPublicKey, + #[serde(with = "BigArray")] + pub label_hashes: [WireHash; INPUT_WIRE_NUM], } -impl AssertCommitConnector { +impl VerifierConnector { pub fn new( network: Network, n_of_n_taproot_public_key: &XOnlyPublicKey, - wots32_pubkeys: &Vec<::PublicKey>, - wots16_pubkeys: &Vec<::PublicKey>, + operator_wots_public_key: &OperatorAssertPublicKey, + label_hashes: [WireHash; INPUT_WIRE_NUM], ) -> Self { - let wots32_pubkeys = wots32_pubkeys - .iter() - .map(|pk| WinternitzPublicKey { - public_key: pk.to_vec(), - parameters: ::PARAMETERS, - }) - .collect::>(); - let wots16_pubkeys = wots16_pubkeys - .iter() - .map(|pk| WinternitzPublicKey { - public_key: pk.to_vec(), - parameters: ::PARAMETERS, - }) - .collect::>(); - AssertCommitConnector { + VerifierConnector { network, n_of_n_taproot_public_key: *n_of_n_taproot_public_key, - wots32_pubkeys, - wots16_pubkeys, - assert_commit_blocks_timelock: num_blocks_per_network(network, ASSERT_COMMIT_TIMELOCK), + operator_wots_public_key: *operator_wots_public_key, + label_hashes, } } fn generate_taproot_leaf_0_script(&self) -> ScriptBuf { - let wots32_pubkeys = self - .wots32_pubkeys - .iter() - .map(|pk| pk.public_key.as_slice().try_into().unwrap()) - .collect::::PublicKey>>(); - let wots16_pubkeys = self - .wots16_pubkeys - .iter() - .map(|pk| pk.public_key.as_slice().try_into().unwrap()) - .collect::::PublicKey>>(); - script! { - for pk in wots16_pubkeys.iter().rev() { - { Wots16::checksig_verify_and_clear_stack(&pk) } - } - for pk in wots32_pubkeys.iter().rev() { - { Wots32::checksig_verify_and_clear_stack(&pk) } - } - OP_TRUE - } - .compile() + verify_verifier_assert_script_512_wire(&self.operator_wots_public_key, &self.label_hashes) + .compile() + } + + fn generate_taproot_leaf_0_tx_in(&self, input: &Input) -> TxIn { + generate_default_tx_in(input) } pub fn generate_leaf_0_unlock_data( &self, - wots32_secret_keys: &Vec, - wots16_secret_keys: &Vec, - wots32_values: &Vec<[u8; 32]>, - wots16_values: &Vec<[u8; 16]>, + labels: [Label; INPUT_WIRE_NUM], + operator_assertion: &RawWitness, ) -> Result>, Error> { - fn append_witness(a: &mut Witness, b: Witness) { - for item in b.into_iter() { - a.push(item); - } - } - - if wots32_secret_keys.len() != self.wots32_pubkeys.len() - || wots16_secret_keys.len() != self.wots16_pubkeys.len() - || wots32_values.len() != self.wots32_pubkeys.len() - || wots16_values.len() != self.wots16_pubkeys.len() - { - return Err(Error::Other( - "The length of WOTS secret-keys or values does not match the length of public-keys.", - )); - }; - - let mut witness = Witness::new(); - for (i, sk) in wots32_secret_keys.iter().enumerate() { - let wit = Wots32::sign_to_raw_witness(sk, &wots32_values[i]); - append_witness(&mut witness, wit); - } - for (i, sk) in wots16_secret_keys.iter().enumerate() { - let wit = Wots16::sign_to_raw_witness(sk, &wots16_values[i]); - append_witness(&mut witness, wit); - } - + let mut witness = Vec::with_capacity(operator_assertion.len() + INPUT_WIRE_NUM); + witness.extend(operator_assertion.iter().cloned()); + witness.extend(labels); let witness_script = script! { { witness.clone() } }; let verification_script = witness_script.push_script(self.generate_taproot_leaf_0_script()); let exec_result = execute_script(verification_script); match exec_result.success { - true => Ok(witness.to_vec()), - false => Err(Error::Other( - "Invalid WOTS secret-key for Assert Commit Connector.", - )), + true => Ok(witness), + false => Err(Error::Other("Invalid unlock data for VerifierConnector.")), } } - - fn generate_taproot_leaf_0_tx_in(&self, input: &Input) -> TxIn { - generate_default_tx_in(input) - } - - fn generate_taproot_leaf_1_script(&self) -> ScriptBuf { - generate_timelock_taproot_script( - &self.n_of_n_taproot_public_key, - self.assert_commit_blocks_timelock, - ) - } - - fn generate_taproot_leaf_1_tx_in(&self, input: &Input) -> TxIn { - generate_timelock_tx_in(input, self.assert_commit_blocks_timelock) - } } -impl TaprootConnector for AssertCommitConnector { +impl TaprootConnector for VerifierConnector { fn generate_taproot_leaf_script(&self, leaf_index: u32) -> ScriptBuf { match leaf_index { 0 => self.generate_taproot_leaf_0_script(), - 1 => self.generate_taproot_leaf_1_script(), _ => panic!("Invalid leaf index."), } } @@ -156,17 +80,14 @@ impl TaprootConnector for AssertCommitConnector { fn generate_taproot_leaf_tx_in(&self, leaf_index: u32, input: &Input) -> TxIn { match leaf_index { 0 => self.generate_taproot_leaf_0_tx_in(input), - 1 => self.generate_taproot_leaf_1_tx_in(input), _ => panic!("Invalid leaf index."), } } fn generate_taproot_spend_info(&self) -> TaprootSpendInfo { TaprootBuilder::new() - .add_leaf(1, self.generate_taproot_leaf_0_script()) + .add_leaf(0, self.generate_taproot_leaf_0_script()) .expect("Unable to add leaf 0") - .add_leaf(1, self.generate_taproot_leaf_1_script()) - .expect("Unable to add leaf 1") .finalize(SECP256K1, self.n_of_n_taproot_public_key) .expect("Unable to finalize taproot") } @@ -179,307 +100,78 @@ impl TaprootConnector for AssertCommitConnector { } } -pub fn extract_commits_from_txin( - input: &TxIn, - wots32_num: usize, - wots16_num: usize, -) -> Result, Error> { - let mut res = Vec::new(); - let witness = remove_script_and_control_block_from_witness(input.witness.to_vec()); - let (wots32_witness_size, wots16_witness_size) = ( - Wots32::TOTAL_DIGIT_LEN as usize * 2, - Wots16::TOTAL_DIGIT_LEN as usize * 2, - ); - let expected_witness_size = wots32_witness_size * wots32_num + wots16_witness_size * wots16_num; - if expected_witness_size != witness.len() { - return Err(Error::Other( - "Invalid witness size for Assert Commit Connector.", - )); - } - for i in 0..wots32_num { - let start = i * wots32_witness_size; - let end = start + wots32_witness_size; - res.push(witness[start..end].to_vec()); - } - for i in 0..wots16_num { - let start = wots32_witness_size * wots32_num + i * wots16_witness_size; - let end = start + wots16_witness_size; - res.push(witness[start..end].to_vec()); - } - Ok(res) -} - -pub fn extract_commits_from_txins( - inputs: Vec, - wots32_num: usize, - wots16_num: usize, -) -> Result, Error> { - let mut sorted_inputs = inputs; - sorted_inputs.sort_by_key(|txin| txin.previous_output.vout); - let mut res = vec![]; - let use_compact_wots = false; - let chunks = chunk_assert_commit(wots32_num, wots16_num, use_compact_wots); - for (i, input) in sorted_inputs.into_iter().enumerate() { - let (start, len) = chunks[i]; - let end = start + len; - - let chunk_wots32_num = end.min(wots32_num).saturating_sub(start.min(wots32_num)); - let chunk_wots16_num = end - .saturating_sub(wots32_num) - .saturating_sub(start.saturating_sub(wots32_num)); - - let mut chunk_commits = - extract_commits_from_txin(&input, chunk_wots32_num, chunk_wots16_num)?; - res.append(&mut chunk_commits); - } - Ok(res) +pub struct ProverConnector { + pub network: Network, + pub n_of_n_taproot_public_key: XOnlyPublicKey, + pub disprove_blocks_timelock: u32, + pub hashlock: LabelHash, } -pub fn generate_chunked_assert_commit_connectors( - network: Network, - n_of_n_taproot_public_key: &XOnlyPublicKey, - wots_keys: AssertWotsPublicKeys, -) -> Vec { - let mut wots32_pubkeys = wots_keys.0.to_vec(); - wots32_pubkeys.extend(wots_keys.1 .0.to_vec()); - wots32_pubkeys.extend(wots_keys.1 .1.to_vec()); - let wots16_pubkeys = wots_keys.1 .2.to_vec(); - - let use_compact_wots = false; - let chunks = chunk_assert_commit(wots32_pubkeys.len(), wots16_pubkeys.len(), use_compact_wots); - let mut connectors = vec![]; - let n32 = wots32_pubkeys.len(); - for (start_index, wots_num) in chunks { - let end_index = start_index + wots_num; - - let start32 = start_index.min(n32); - let end32 = end_index.min(n32); - - let start16 = start_index.saturating_sub(n32); - let end16 = end_index.saturating_sub(n32); - - let chunk32 = wots32_pubkeys[start32..end32].to_vec(); - let chunk16 = wots16_pubkeys[start16..end16].to_vec(); - - let connector = - AssertCommitConnector::new(network, n_of_n_taproot_public_key, &chunk32, &chunk16); - connectors.push(connector); +impl ProverConnector { + pub fn new( + network: Network, + n_of_n_taproot_public_key: XOnlyPublicKey, + hashlock: LabelHash, + ) -> Self { + ProverConnector { + network, + n_of_n_taproot_public_key, + disprove_blocks_timelock: num_blocks_per_network(network, PROVER_CONNECTOR_TIMELOCK), + hashlock, + } } - connectors -} - -// return Vec<(start_index, wots_num)> -pub fn chunk_assert_commit( - wots32_num: usize, - wots16_num: usize, - use_compact_wots: bool, -) -> Vec<(usize, usize)> { - // let (wots32_witness_max_stack_items, wots16_witness_max_stack_items, runtime_extra_stack_items) = wots_stack_size(use_compact_wots); - let (wots32_witness_max_stack_items, wots16_witness_max_stack_items, runtime_extra_stack_items) = - precomputed_wots_stack_size(use_compact_wots); - let max_stack_limit: usize = 1000; - let max_pure_wot32_num = - (max_stack_limit - runtime_extra_stack_items) / wots32_witness_max_stack_items; - let max_pure_wot16_num = - (max_stack_limit - runtime_extra_stack_items) / wots16_witness_max_stack_items; - - let mut res = vec![]; - let mut cur_index = 0; - for _ in 0..(wots32_num / max_pure_wot32_num) { - res.push((cur_index, max_pure_wot32_num)); - cur_index += max_pure_wot32_num; + fn generate_taproot_leaf_0_script(&self) -> ScriptBuf { + wrongly_challenged_script(&self.hashlock).compile() } - let remaining_wots32_num = wots32_num % max_pure_wot32_num; - let mut remaining_wots16_num = wots16_num; - if remaining_wots32_num != 0 { - let append_wots16_num = (max_stack_limit - - runtime_extra_stack_items - - remaining_wots32_num * wots32_witness_max_stack_items) - / wots16_witness_max_stack_items; - res.push((cur_index, append_wots16_num + remaining_wots32_num)); - cur_index += append_wots16_num + remaining_wots32_num; - remaining_wots16_num -= append_wots16_num; + fn generate_taproot_leaf_0_tx_in(&self, input: &Input) -> TxIn { + generate_default_tx_in(input) } - for _ in 0..(remaining_wots16_num / max_pure_wot16_num) { - res.push((cur_index, max_pure_wot16_num)); - cur_index += max_pure_wot16_num; + fn generate_taproot_leaf_1_script(&self) -> ScriptBuf { + generate_timelock_taproot_script( + &self.n_of_n_taproot_public_key, + self.disprove_blocks_timelock, + ) } - let remaining_wots16_num = remaining_wots16_num % max_pure_wot16_num; - if remaining_wots16_num != 0 { - res.push((cur_index, remaining_wots16_num)); + fn generate_taproot_leaf_1_tx_in(&self, input: &Input) -> TxIn { + generate_timelock_tx_in(input, self.disprove_blocks_timelock) } - - res } -#[allow(dead_code)] -fn precomputed_wots_stack_size(use_compact_wots: bool) -> (usize, usize, usize) { - if use_compact_wots { - (68, 36, 5) - } else { - (136, 72, 9) +impl TaprootConnector for ProverConnector { + fn generate_taproot_leaf_script(&self, leaf_index: u32) -> ScriptBuf { + match leaf_index { + 0 => self.generate_taproot_leaf_0_script(), + 1 => self.generate_taproot_leaf_1_script(), + _ => panic!("Invalid leaf index."), + } } -} - -#[allow(dead_code)] -fn wots_stack_size(use_compact_wots: bool) -> (usize, usize, usize) { - let privkey32 = Wots32::generate_secret_key(); - let pubkey32 = Wots32::generate_public_key(&privkey32); - let msg32 = [1u8; 32]; - - let privkey16 = Wots16::generate_secret_key(); - let pubkey16 = Wots16::generate_public_key(&privkey16); - let msg16 = [1u8; 16]; - - if use_compact_wots { - // Wots32 - let witness = Wots32::compact_sign_to_raw_witness(&privkey32, &msg32); - let witness_script = script! { - { witness.clone() } - }; - let exec_result = execute_script(witness_script); - let wots32_witness_stack_size = exec_result.stats.max_nb_stack_items; - let lock_script = script! { - { witness.clone() } - { Wots32::compact_checksig_verify_and_clear_stack(&pubkey32) } - }; - let exec_result = execute_script(lock_script); - let wots32_full_script_stack_size = exec_result.stats.max_nb_stack_items; - let wots32_runtime_extra_stack_items = - wots32_full_script_stack_size - wots32_witness_stack_size; - - // Wots16 - let witness = Wots16::compact_sign_to_raw_witness(&privkey16, &msg16); - let witness_script = script! { - { witness.clone() } - }; - let exec_result = execute_script(witness_script); - let wots16_witness_stack_size = exec_result.stats.max_nb_stack_items; - let lock_script = script! { - { witness.clone() } - { Wots16::compact_checksig_verify_and_clear_stack(&pubkey16) } - }; - let exec_result = execute_script(lock_script); - let wots16_full_script_stack_size = exec_result.stats.max_nb_stack_items; - let wots16_runtime_extra_stack_items = - wots16_full_script_stack_size - wots16_witness_stack_size; - - ( - wots32_witness_stack_size, - wots16_witness_stack_size, - std::cmp::max( - wots32_runtime_extra_stack_items, - wots16_runtime_extra_stack_items, - ), - ) - } else { - // Wots32 - let witness = Wots32::sign_to_raw_witness(&privkey32, &msg32); - let witness_script = script! { - { witness.clone() } - }; - let exec_result = execute_script(witness_script); - let wots32_witness_stack_size = exec_result.stats.max_nb_stack_items; - let lock_script = script! { - { witness.clone() } - { Wots32::checksig_verify_and_clear_stack(&pubkey32) } - }; - let exec_result = execute_script(lock_script); - let wots32_full_script_stack_size = exec_result.stats.max_nb_stack_items; - let wots32_runtime_extra_stack_items = - wots32_full_script_stack_size - wots32_witness_stack_size; - - // Wots16 - let witness = Wots16::sign_to_raw_witness(&privkey16, &msg16); - let witness_script = script! { - { witness.clone() } - }; - let exec_result = execute_script(witness_script); - let wots16_witness_stack_size = exec_result.stats.max_nb_stack_items; - let lock_script = script! { - { witness.clone() } - { Wots16::checksig_verify_and_clear_stack(&pubkey16) } - }; - let exec_result = execute_script(lock_script); - let wots16_full_script_stack_size = exec_result.stats.max_nb_stack_items; - let wots16_runtime_extra_stack_items = - wots16_full_script_stack_size - wots16_witness_stack_size; - ( - wots32_witness_stack_size, - wots16_witness_stack_size, - std::cmp::max( - wots32_runtime_extra_stack_items, - wots16_runtime_extra_stack_items, - ), - ) + fn generate_taproot_leaf_tx_in(&self, leaf_index: u32, input: &Input) -> TxIn { + match leaf_index { + 0 => self.generate_taproot_leaf_0_tx_in(input), + 1 => self.generate_taproot_leaf_1_tx_in(input), + _ => panic!("Invalid leaf index."), + } } -} - -#[test] -fn test_wots_stack_size() { - assert_eq!(wots_stack_size(true), precomputed_wots_stack_size(true)); - assert_eq!(wots_stack_size(false), precomputed_wots_stack_size(false)); -} -#[test] -fn test_assert_commit_connector_leaf_0() { - fn run(wots32_num: usize, wots16_num: usize) { - let secp = &SECP256K1; - let mut rng = rand::thread_rng(); - let kp = bitcoin::key::Keypair::new(secp, &mut rng); - let (xonly_pk, _) = XOnlyPublicKey::from_keypair(&kp); - - let wots32_privkeys = (0..wots32_num) - .map(|_| Wots32::generate_secret_key()) - .collect::>(); - let wots16_privkeys = (0..wots16_num) - .map(|_| Wots16::generate_secret_key()) - .collect::>(); - let wots32_pubkeys = wots32_privkeys - .iter() - .map(|sk| Wots32::generate_public_key(sk)) - .collect::::PublicKey>>(); - let wots16_pubkeys = wots16_privkeys - .iter() - .map(|sk| Wots16::generate_public_key(sk)) - .collect::::PublicKey>>(); - let wots32_values = (0..wots32_num) - .map(|i| [i as u8; 32]) - .collect::>(); - let wots16_values = (0..wots16_num) - .map(|i| [i as u8; 16]) - .collect::>(); - - let connector = AssertCommitConnector::new( - Network::Regtest, - &xonly_pk, - &wots32_pubkeys, - &wots16_pubkeys, - ); - - let unlock_data = connector - .generate_leaf_0_unlock_data( - &wots32_privkeys, - &wots16_privkeys, - &wots32_values, - &wots16_values, - ) - .unwrap(); + fn generate_taproot_spend_info(&self) -> TaprootSpendInfo { + TaprootBuilder::new() + .add_leaf(1, self.generate_taproot_leaf_0_script()) + .expect("Unable to add leaf 0") + .add_leaf(1, self.generate_taproot_leaf_1_script()) + .expect("Unable to add leaf 1") + .finalize(SECP256K1, self.n_of_n_taproot_public_key) + .expect("Unable to finalize taproot") + } - let witness_script = script! { - { unlock_data } - }; - let verification_script = - witness_script.push_script(connector.generate_taproot_leaf_0_script()); - let exec_result = execute_script(verification_script); - assert_eq!(exec_result.success, true); + fn generate_taproot_address(&self) -> Address { + Address::p2tr_tweaked( + self.generate_taproot_spend_info().output_key(), + self.network, + ) } - run(7, 0); - run(2, 9); - run(0, 13); } diff --git a/goat/src/connectors/connector_c.rs b/goat/src/connectors/connector_c.rs index f0da092c4..7ec8f31c5 100644 --- a/goat/src/connectors/connector_c.rs +++ b/goat/src/connectors/connector_c.rs @@ -1,42 +1,88 @@ +use crate::{assert_scripts::*, utils::remove_script_and_control_block_from_witness}; use bitcoin::{ taproot::{TaprootBuilder, TaprootSpendInfo}, Address, Network, ScriptBuf, TxIn, XOnlyPublicKey, }; +use bitvm::{chunk::api::type_conversion_utils::RawWitness, treepp::*}; use secp256k1::SECP256K1; use serde::{Deserialize, Serialize}; +use serde_big_array::BigArray; use super::{ - super::{scripts::*, transactions::base::Input}, + super::{error::Error, scripts::*, transactions::base::Input, wots::*}, base::*, }; #[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] pub struct ConnectorC { pub network: Network, - pub operator_taproot_public_key: XOnlyPublicKey, + pub n_of_n_taproot_public_key: XOnlyPublicKey, + #[serde(with = "BigArray")] + pub operator_wots_public_key: OperatorAssertPublicKey, } impl ConnectorC { - pub fn new(network: Network, operator_taproot_public_key: &XOnlyPublicKey) -> Self { + pub fn new( + network: Network, + n_of_n_taproot_public_key: &XOnlyPublicKey, + operator_wots_public_key: &OperatorAssertPublicKey, + ) -> Self { ConnectorC { network, - operator_taproot_public_key: *operator_taproot_public_key, + n_of_n_taproot_public_key: *n_of_n_taproot_public_key, + operator_wots_public_key: *operator_wots_public_key, } } fn generate_taproot_leaf_0_script(&self) -> ScriptBuf { - generate_pay_to_pubkey_taproot_script(&self.operator_taproot_public_key) + generate_pay_to_pubkey_taproot_script(&self.n_of_n_taproot_public_key) } fn generate_taproot_leaf_0_tx_in(&self, input: &Input) -> TxIn { generate_default_tx_in(input) } + + fn generate_taproot_leaf_1_script(&self) -> ScriptBuf { + verify_prover_assert_script_512_wire(&self.operator_wots_public_key).compile() + } + + fn generate_taproot_leaf_1_tx_in(&self, input: &Input) -> TxIn { + generate_default_tx_in(input) + } + + pub fn generate_leaf_1_unlock_data( + &self, + sk: &OperatorAssertSecretKey, + proof: &[u8; 64], + ) -> Result>, Error> { + let witness = Wots64::sign_to_raw_witness(sk, proof); + let witness_script = script! { + { witness.clone() } + }; + let verification_script = witness_script.push_script(self.generate_taproot_leaf_1_script()); + let exec_result = execute_script(verification_script); + match exec_result.success { + true => Ok(witness.to_vec()), + false => Err(Error::Other("Invalid WOTS secret-key for Connector-C.")), + } + } + + pub fn extract_leaf_1_raw_witness(&self, txin: &TxIn) -> Result { + let witness = txin.witness.to_vec(); + if witness.len() != PROVER_SIG_LEN + 2 { + return Err(Error::Other( + "Invalid witness length for Connector-C leaf 1.", + )); + } + Ok(remove_script_and_control_block_from_witness(witness)) + } } impl TaprootConnector for ConnectorC { fn generate_taproot_leaf_script(&self, leaf_index: u32) -> ScriptBuf { match leaf_index { 0 => self.generate_taproot_leaf_0_script(), + 1 => self.generate_taproot_leaf_1_script(), _ => panic!("Invalid leaf index."), } } @@ -44,15 +90,18 @@ impl TaprootConnector for ConnectorC { fn generate_taproot_leaf_tx_in(&self, leaf_index: u32, input: &Input) -> TxIn { match leaf_index { 0 => self.generate_taproot_leaf_0_tx_in(input), + 1 => self.generate_taproot_leaf_1_tx_in(input), _ => panic!("Invalid leaf index."), } } fn generate_taproot_spend_info(&self) -> TaprootSpendInfo { TaprootBuilder::new() - .add_leaf(0, self.generate_taproot_leaf_0_script()) + .add_leaf(1, self.generate_taproot_leaf_0_script()) .expect("Unable to add leaf 0") - .finalize(SECP256K1, self.operator_taproot_public_key) + .add_leaf(1, self.generate_taproot_leaf_1_script()) + .expect("Unable to add leaf 1") + .finalize(SECP256K1, self.n_of_n_taproot_public_key) .expect("Unable to finalize taproot") } diff --git a/goat/src/connectors/connector_e.rs b/goat/src/connectors/connector_e.rs deleted file mode 100644 index f8df45c03..000000000 --- a/goat/src/connectors/connector_e.rs +++ /dev/null @@ -1,63 +0,0 @@ -use bitcoin::{ - address::NetworkUnchecked, - taproot::{TaprootBuilder, TaprootSpendInfo}, - Address, Network, ScriptBuf, TapNodeHash, XOnlyPublicKey, -}; -use secp256k1::SECP256K1; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] -pub struct ConnectorE { - pub network: Network, - pub operator_taproot_public_key: XOnlyPublicKey, - pub address: Address, - pub taproot_merkle_root: Option, -} - -impl ConnectorE { - pub fn new_with_scripts( - network: Network, - operator_taproot_public_key: &XOnlyPublicKey, - lock_scripts: Vec, // guest_validation_scripts || proof_validation_scripts - ) -> (Self, TaprootSpendInfo) { - // println!("Generating new taproot spend info for connector E..."); - let script_weights = lock_scripts.into_iter().map(|b| (1, b)); - let spend_info = TaprootBuilder::with_huffman_tree(script_weights) - .expect("Unable to add assert leaves") - .finalize(SECP256K1, *operator_taproot_public_key) - .expect("Unable to finalize assert transaction connector c taproot"); - let address = Address::p2tr_tweaked(spend_info.output_key(), network); - let merkle_root = spend_info.merkle_root(); - ( - ConnectorE { - network, - operator_taproot_public_key: *operator_taproot_public_key, - address: address.as_unchecked().clone(), - taproot_merkle_root: merkle_root, - }, - spend_info, - ) - } - - pub fn new_with_precomputed_info( - network: Network, - operator_taproot_public_key: &XOnlyPublicKey, - address: &Address, - taproot_merkle_root: Option, - ) -> Self { - ConnectorE { - network, - operator_taproot_public_key: *operator_taproot_public_key, - address: address.clone(), - taproot_merkle_root, - } - } - - pub fn generate_taproot_address(&self) -> Address { - self.address.clone().assume_checked() - } - - pub fn taproot_merkle_root(&self) -> Option { - self.taproot_merkle_root - } -} diff --git a/goat/src/connectors/connector_f.rs b/goat/src/connectors/connector_f.rs deleted file mode 100644 index 9da754882..000000000 --- a/goat/src/connectors/connector_f.rs +++ /dev/null @@ -1,90 +0,0 @@ -use bitcoin::{ - taproot::{TaprootBuilder, TaprootSpendInfo}, - Address, Network, ScriptBuf, TxIn, XOnlyPublicKey, -}; -use secp256k1::SECP256K1; -use serde::{Deserialize, Serialize}; - -use crate::{constants::CONNECTOR_F_TIMELOCK, utils::num_blocks_per_network}; - -use super::{ - super::{scripts::*, transactions::base::Input}, - base::*, -}; - -#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] -pub struct ConnectorF { - pub network: Network, - pub operator_taproot_public_key: XOnlyPublicKey, - pub n_of_n_taproot_public_key: XOnlyPublicKey, - pub take2_blocks_timelock: u32, -} - -impl ConnectorF { - pub fn new( - network: Network, - operator_taproot_public_key: &XOnlyPublicKey, - n_of_n_taproot_public_key: &XOnlyPublicKey, - ) -> Self { - ConnectorF { - network, - operator_taproot_public_key: *operator_taproot_public_key, - n_of_n_taproot_public_key: *n_of_n_taproot_public_key, - take2_blocks_timelock: num_blocks_per_network(network, CONNECTOR_F_TIMELOCK), - } - } - - fn generate_taproot_leaf_0_script(&self) -> ScriptBuf { - generate_timelock_taproot_script( - &self.operator_taproot_public_key, - self.take2_blocks_timelock, - ) - } - - fn generate_taproot_leaf_0_tx_in(&self, input: &Input) -> TxIn { - generate_timelock_tx_in(input, self.take2_blocks_timelock) - } - - fn generate_taproot_leaf_1_script(&self) -> ScriptBuf { - generate_pay_to_pubkey_taproot_script(&self.n_of_n_taproot_public_key) - } - - fn generate_taproot_leaf_1_tx_in(&self, input: &Input) -> TxIn { - generate_default_tx_in(input) - } -} - -impl TaprootConnector for ConnectorF { - fn generate_taproot_leaf_script(&self, leaf_index: u32) -> ScriptBuf { - match leaf_index { - 0 => self.generate_taproot_leaf_0_script(), - 1 => self.generate_taproot_leaf_1_script(), - _ => panic!("Invalid leaf index."), - } - } - - fn generate_taproot_leaf_tx_in(&self, leaf_index: u32, input: &Input) -> TxIn { - match leaf_index { - 0 => self.generate_taproot_leaf_0_tx_in(input), - 1 => self.generate_taproot_leaf_1_tx_in(input), - _ => panic!("Invalid leaf index."), - } - } - - fn generate_taproot_spend_info(&self) -> TaprootSpendInfo { - TaprootBuilder::new() - .add_leaf(1, self.generate_taproot_leaf_0_script()) - .expect("Unable to add leaf 0") - .add_leaf(1, self.generate_taproot_leaf_1_script()) - .expect("Unable to add leaf 1") - .finalize(SECP256K1, self.n_of_n_taproot_public_key) - .expect("Unable to finalize taproot") - } - - fn generate_taproot_address(&self) -> Address { - Address::p2tr_tweaked( - self.generate_taproot_spend_info().output_key(), - self.network, - ) - } -} diff --git a/goat/src/connectors/connector_g.rs b/goat/src/connectors/connector_g.rs deleted file mode 100644 index 358f9fe7c..000000000 --- a/goat/src/connectors/connector_g.rs +++ /dev/null @@ -1,151 +0,0 @@ -use bitcoin::{ - taproot::{TaprootBuilder, TaprootSpendInfo}, - Address, Network, ScriptBuf, TxIn, XOnlyPublicKey, -}; -use bitvm::signatures::{signing_winternitz::WinternitzPublicKey, WinternitzSecret, Wots, Wots32}; -use bitvm::treepp::*; -use secp256k1::SECP256K1; -use serde::{Deserialize, Serialize}; - -use crate::{constants::CONNECTOR_G_TIMELOCK, utils::num_blocks_per_network}; - -use super::{ - super::{error::Error, scripts::*, transactions::base::Input}, - base::*, -}; - -#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] -pub struct ConnectorG { - pub network: Network, - pub n_of_n_taproot_public_key: XOnlyPublicKey, - pub operator_taproot_public_key: XOnlyPublicKey, - pub blockhash_wots_pubkey: WinternitzPublicKey, - pub operator_commit_blocks_timelock: u32, -} - -impl ConnectorG { - pub fn new( - network: Network, - n_of_n_taproot_public_key: &XOnlyPublicKey, - operator_taproot_public_key: &XOnlyPublicKey, - blockhash_wots_pubkey: &::PublicKey, - ) -> Self { - let blockhash_wots_pubkey = WinternitzPublicKey { - public_key: blockhash_wots_pubkey.to_vec(), - parameters: ::PARAMETERS, - }; - ConnectorG { - network, - n_of_n_taproot_public_key: *n_of_n_taproot_public_key, - operator_taproot_public_key: *operator_taproot_public_key, - blockhash_wots_pubkey, - operator_commit_blocks_timelock: num_blocks_per_network(network, CONNECTOR_G_TIMELOCK), - } - } - - fn generate_taproot_leaf_0_script(&self) -> ScriptBuf { - let blockhash_wots_pubkey: ::PublicKey = self - .blockhash_wots_pubkey - .public_key - .as_slice() - .try_into() - .unwrap(); - script! { - { Wots32::checksig_verify_and_clear_stack(&blockhash_wots_pubkey) } - OP_TRUE - } - .compile() - } - - pub fn generate_leaf_0_unlock_data( - &self, - wots_secret_key: &WinternitzSecret, - latest_blockhash: &[u8; 32], - ) -> Result>, Error> { - let witness = Wots32::sign_to_raw_witness(wots_secret_key, latest_blockhash); - let witness_script = script! { - { witness.clone() } - }; - let verification_script = witness_script.push_script(self.generate_taproot_leaf_0_script()); - let exec_result = execute_script(verification_script); - match exec_result.success { - true => Ok(witness.to_vec()), - false => Err(Error::Other("Invalid WOTS secret-key for Connector G.")), - } - } - - fn generate_taproot_leaf_0_tx_in(&self, input: &Input) -> TxIn { - generate_default_tx_in(input) - } - - fn generate_taproot_leaf_1_script(&self) -> ScriptBuf { - generate_timelock_taproot_script( - &self.n_of_n_taproot_public_key, - self.operator_commit_blocks_timelock, - ) - } - - fn generate_taproot_leaf_1_tx_in(&self, input: &Input) -> TxIn { - generate_timelock_tx_in(input, self.operator_commit_blocks_timelock) - } -} - -impl TaprootConnector for ConnectorG { - fn generate_taproot_leaf_script(&self, leaf_index: u32) -> ScriptBuf { - match leaf_index { - 0 => self.generate_taproot_leaf_0_script(), - 1 => self.generate_taproot_leaf_1_script(), - _ => panic!("Invalid leaf index."), - } - } - - fn generate_taproot_leaf_tx_in(&self, leaf_index: u32, input: &Input) -> TxIn { - match leaf_index { - 0 => self.generate_taproot_leaf_0_tx_in(input), - 1 => self.generate_taproot_leaf_1_tx_in(input), - _ => panic!("Invalid leaf index."), - } - } - - fn generate_taproot_spend_info(&self) -> TaprootSpendInfo { - TaprootBuilder::new() - .add_leaf(1, self.generate_taproot_leaf_0_script()) - .expect("Unable to add leaf 0") - .add_leaf(1, self.generate_taproot_leaf_1_script()) - .expect("Unable to add leaf 1") - .finalize(SECP256K1, self.n_of_n_taproot_public_key) - .expect("Unable to finalize taproot") - } - - fn generate_taproot_address(&self) -> Address { - Address::p2tr_tweaked( - self.generate_taproot_spend_info().output_key(), - self.network, - ) - } -} - -#[test] -fn test_connector_g_leaf_0() { - let secp = &SECP256K1; - let mut rng = rand::thread_rng(); - let kp = bitcoin::key::Keypair::new(secp, &mut rng); - let (xonly_pk, _) = XOnlyPublicKey::from_keypair(&kp); - - let wots_privkey = Wots32::generate_secret_key(); - let wots_pubkey = Wots32::generate_public_key(&wots_privkey); - let latest_blockhash = [1u8; 32]; - - let connector_g = ConnectorG::new(Network::Regtest, &xonly_pk, &xonly_pk, &wots_pubkey); - - let unlock_data = connector_g - .generate_leaf_0_unlock_data(&wots_privkey, &latest_blockhash) - .unwrap(); - - let verification_script = script! { - { unlock_data } - } - .push_script(connector_g.generate_taproot_leaf_0_script()); - let exec_result = execute_script(verification_script); - assert!(exec_result.success); -} diff --git a/goat/src/connectors/mod.rs b/goat/src/connectors/mod.rs index 7020de9ec..45b5236c4 100644 --- a/goat/src/connectors/mod.rs +++ b/goat/src/connectors/mod.rs @@ -5,9 +5,6 @@ pub mod connector_a; pub mod connector_b; pub mod connector_c; pub mod connector_d; -pub mod connector_e; -pub mod connector_f; -pub mod connector_g; pub mod connector_z; pub mod kickoff_connectors; pub mod watchtower_connectors; diff --git a/goat/src/connectors/watchtower_connectors.rs b/goat/src/connectors/watchtower_connectors.rs index 132a36e4a..44725da87 100644 --- a/goat/src/connectors/watchtower_connectors.rs +++ b/goat/src/connectors/watchtower_connectors.rs @@ -2,43 +2,24 @@ use bitcoin::{ taproot::{TaprootBuilder, TaprootSpendInfo}, Address, Network, ScriptBuf, TxIn, XOnlyPublicKey, }; -use bitvm::{chunk::api::type_conversion_utils::script_to_witness, treepp::*}; use secp256k1::SECP256K1; use serde::{Deserialize, Serialize}; -use crate::{ - constants::{ACK_TIMELOCK, WATCHTOWER_CHALLENGE_TIMELOCK}, - utils::num_blocks_per_network, -}; - use super::{ - super::{error::Error, scripts::*, transactions::base::Input}, + super::{scripts::*, transactions::base::Input}, base::*, }; -pub type WatchctowerConnectors = (WatchtowerChallengeConnector, AckConnector); - #[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] pub struct WatchtowerChallengeConnector { pub network: Network, - pub operator_taproot_public_key: XOnlyPublicKey, pub watchtower_taproot_public_key: XOnlyPublicKey, - pub watchtower_challenge_blocks_timelock: u32, } impl WatchtowerChallengeConnector { - pub fn new( - network: Network, - operator_taproot_public_key: &XOnlyPublicKey, - watchtower_taproot_public_key: &XOnlyPublicKey, - ) -> Self { + pub fn new(network: Network, watchtower_taproot_public_key: &XOnlyPublicKey) -> Self { WatchtowerChallengeConnector { network, - operator_taproot_public_key: *operator_taproot_public_key, watchtower_taproot_public_key: *watchtower_taproot_public_key, - watchtower_challenge_blocks_timelock: num_blocks_per_network( - network, - WATCHTOWER_CHALLENGE_TIMELOCK, - ), } } @@ -50,24 +31,11 @@ impl WatchtowerChallengeConnector { fn generate_taproot_leaf_0_tx_in(&self, input: &Input) -> TxIn { generate_default_tx_in(input) } - - fn generate_taproot_leaf_1_script(&self) -> ScriptBuf { - // for Watchtower-Challenge-Timeout tx - generate_timelock_taproot_script( - &self.operator_taproot_public_key, - self.watchtower_challenge_blocks_timelock, - ) - } - - fn generate_taproot_leaf_1_tx_in(&self, input: &Input) -> TxIn { - generate_timelock_tx_in(input, self.watchtower_challenge_blocks_timelock) - } } impl TaprootConnector for WatchtowerChallengeConnector { fn generate_taproot_leaf_script(&self, leaf_index: u32) -> ScriptBuf { match leaf_index { 0 => self.generate_taproot_leaf_0_script(), - 1 => self.generate_taproot_leaf_1_script(), _ => panic!("Invalid leaf index."), } } @@ -75,17 +43,14 @@ impl TaprootConnector for WatchtowerChallengeConnector { fn generate_taproot_leaf_tx_in(&self, leaf_index: u32, input: &Input) -> TxIn { match leaf_index { 0 => self.generate_taproot_leaf_0_tx_in(input), - 1 => self.generate_taproot_leaf_1_tx_in(input), _ => panic!("Invalid leaf index."), } } fn generate_taproot_spend_info(&self) -> TaprootSpendInfo { TaprootBuilder::new() - .add_leaf(1, self.generate_taproot_leaf_0_script()) + .add_leaf(0, self.generate_taproot_leaf_0_script()) .expect("Unable to add leaf 0") - .add_leaf(1, self.generate_taproot_leaf_1_script()) - .expect("Unable to add leaf 1") .finalize(SECP256K1, self.watchtower_taproot_public_key) .expect("Unable to finalize taproot") } @@ -97,157 +62,3 @@ impl TaprootConnector for WatchtowerChallengeConnector { ) } } - -#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] -pub struct AckConnector { - pub network: Network, - pub n_of_n_taproot_public_key: XOnlyPublicKey, - pub hashlock: [u8; 20], - pub ack_blocks_timelock: u32, -} -impl AckConnector { - pub fn new( - network: Network, - n_of_n_taproot_public_key: &XOnlyPublicKey, - hashlock: &[u8; 20], - ) -> Self { - AckConnector { - network, - n_of_n_taproot_public_key: *n_of_n_taproot_public_key, - hashlock: *hashlock, - ack_blocks_timelock: num_blocks_per_network(network, ACK_TIMELOCK), - } - } - - fn generate_taproot_leaf_0_script(&self) -> ScriptBuf { - // for Watchtower-Challenge-Timeout tx - generate_pay_to_pubkey_taproot_script(&self.n_of_n_taproot_public_key) - } - - fn generate_taproot_leaf_0_tx_in(&self, input: &Input) -> TxIn { - generate_default_tx_in(input) - } - - fn generate_taproot_leaf_1_script(&self) -> ScriptBuf { - // for Operator-ACK tx - script! { - OP_HASH160 - { self.hashlock.to_vec() } - OP_EQUALVERIFY - OP_TRUE - } - .compile() - } - - pub fn generate_leaf_1_unlock_data(&self, preimage: &[u8]) -> Result>, Error> { - let witness_script = script! { - { preimage.to_vec() } - }; - let witness = script_to_witness(witness_script.clone()); - let verification_script = witness_script.push_script(self.generate_taproot_leaf_1_script()); - let exec_result = execute_script(verification_script); - match exec_result.success { - true => Ok(witness), - false => Err(Error::Other("Invalid preimage for ACK connector.")), - } - } - - fn generate_taproot_leaf_1_tx_in(&self, input: &Input) -> TxIn { - generate_default_tx_in(input) - } - - fn generate_taproot_leaf_2_script(&self) -> ScriptBuf { - // for Operator-NACK tx - generate_timelock_taproot_script(&self.n_of_n_taproot_public_key, self.ack_blocks_timelock) - } - - fn generate_taproot_leaf_2_tx_in(&self, input: &Input) -> TxIn { - generate_timelock_tx_in(input, self.ack_blocks_timelock) - } -} -impl TaprootConnector for AckConnector { - fn generate_taproot_leaf_script(&self, leaf_index: u32) -> ScriptBuf { - match leaf_index { - 0 => self.generate_taproot_leaf_0_script(), - 1 => self.generate_taproot_leaf_1_script(), - 2 => self.generate_taproot_leaf_2_script(), - _ => panic!("Invalid leaf index."), - } - } - - fn generate_taproot_leaf_tx_in(&self, leaf_index: u32, input: &Input) -> TxIn { - match leaf_index { - 0 => self.generate_taproot_leaf_0_tx_in(input), - 1 => self.generate_taproot_leaf_1_tx_in(input), - 2 => self.generate_taproot_leaf_2_tx_in(input), - _ => panic!("Invalid leaf index."), - } - } - - fn generate_taproot_spend_info(&self) -> TaprootSpendInfo { - TaprootBuilder::new() - .add_leaf(2, self.generate_taproot_leaf_0_script()) - .expect("Unable to add leaf 0") - .add_leaf(2, self.generate_taproot_leaf_1_script()) - .expect("Unable to add leaf 1") - .add_leaf(1, self.generate_taproot_leaf_2_script()) - .expect("Unable to add leaf 2") - .finalize(SECP256K1, self.n_of_n_taproot_public_key) - .expect("Unable to finalize taproot") - } - - fn generate_taproot_address(&self) -> Address { - Address::p2tr_tweaked( - self.generate_taproot_spend_info().output_key(), - self.network, - ) - } -} - -#[test] -fn test_ack_connector_leaf_1() { - use crate::disprove_scripts::hash160; - - let secp = &SECP256K1; - let mut rng = rand::thread_rng(); - let kp = bitcoin::key::Keypair::new(secp, &mut rng); - let (xonly_pk, _) = XOnlyPublicKey::from_keypair(&kp); - - let preimage = b"this is a hashlock".to_vec(); - let hashlock = hash160(&preimage); - let ack_connector = AckConnector::new(Network::Regtest, &xonly_pk, &hashlock); - - let unlock_data = ack_connector - .generate_leaf_1_unlock_data(&preimage) - .unwrap(); - - let verification_script = script! { - { unlock_data.clone() } - } - .push_script(ack_connector.generate_taproot_leaf_1_script()); - let exec_result = execute_script(verification_script); - assert!(exec_result.success); - - use crate::transactions::signing::populate_taproot_txin_witness; - use crate::transactions::watchtower_challenge::extract_operator_preimage_from_ack_txin; - use bitcoin::{Amount, OutPoint, Txid}; - use std::str::FromStr; - let mut txin = ack_connector.generate_taproot_leaf_1_tx_in(&Input { - outpoint: OutPoint { - txid: Txid::from_str( - "4d3c2b1a0f9e8d7c6b5a493827161514131211100f0e0d0c0b0a090807060504", - ) - .unwrap(), - vout: 0, - }, - amount: Amount::from_sat(100_000), - }); - populate_taproot_txin_witness( - &mut txin, - &ack_connector.generate_taproot_spend_info(), - &ack_connector.generate_taproot_leaf_1_script(), - unlock_data, - ); - let preimage_from_witness = extract_operator_preimage_from_ack_txin(&txin).unwrap(); - assert_eq!(preimage, preimage_from_witness); -} diff --git a/goat/src/constants.rs b/goat/src/constants.rs index abf361b5d..9892a7572 100644 --- a/goat/src/constants.rs +++ b/goat/src/constants.rs @@ -1,25 +1,15 @@ pub const NUM_BLOCKS_PER_HOUR: u32 = 6; -pub const NUM_BLOCKS_PER_6_HOURS: u32 = NUM_BLOCKS_PER_HOUR * 6; - pub const NUM_BLOCKS_PER_DAY: u32 = NUM_BLOCKS_PER_HOUR * 24; -pub const NUM_BLOCKS_PER_3_DAYS: u32 = NUM_BLOCKS_PER_DAY * 3; - pub const NUM_BLOCKS_PER_WEEK: u32 = NUM_BLOCKS_PER_DAY * 7; -pub const NUM_BLOCKS_PER_2_WEEKS: u32 = NUM_BLOCKS_PER_WEEK * 2; -pub const NUM_BLOCKS_PER_4_WEEKS: u32 = NUM_BLOCKS_PER_WEEK * 4; pub const N_SEQUENCE_FOR_LOCK_TIME: u32 = 0xFFFFFFFE; // The nSequence field must be set to less than 0xffffffff, usually 0xffffffff-1 to avoid confilcts with relative timelocks. // connectors' locktime // TBD: adjust these timelocks -pub const CONNECTOR_Z_TIMELOCK: u32 = NUM_BLOCKS_PER_DAY * 1; // pegin-cancel timelock -pub const CONNECTOR_A_TIMELOCK: u32 = NUM_BLOCKS_PER_DAY * 2; // take-1 timelock -pub const WATCHTOWER_CHALLENGE_TIMELOCK: u32 = NUM_BLOCKS_PER_DAY * 2; // watchtower challenge timelock -pub const ACK_TIMELOCK: u32 = NUM_BLOCKS_PER_DAY * 3; // operator ack timelock -pub const CONNECTOR_G_TIMELOCK: u32 = WATCHTOWER_CHALLENGE_TIMELOCK + NUM_BLOCKS_PER_HOUR * 6; // operator commit blockhash timelock -pub const CONNECTOR_F_TIMELOCK: u32 = NUM_BLOCKS_PER_DAY * 5; // take-2 timelock -pub const ASSERT_COMMIT_TIMELOCK: u32 = NUM_BLOCKS_PER_DAY * 2; // assert-init timelock -pub const CONNECTOR_D_TIMELOCK: u32 = NUM_BLOCKS_PER_DAY * 5; // take-2 timelock +pub const CONNECTOR_Z_TIMELOCK: u32 = NUM_BLOCKS_PER_DAY; // pegin-cancel timelock +pub const CONNECTOR_A_TIMELOCK: u32 = NUM_BLOCKS_PER_DAY; // take-1 timelock +pub const PROVER_CONNECTOR_TIMELOCK: u32 = NUM_BLOCKS_PER_DAY; // disprove timelock for the prover connector +pub const CONNECTOR_D_TIMELOCK: u32 = NUM_BLOCKS_PER_DAY * 3; // take-2 timelock // Commitment message parameters. Hardcoded number of bytes per message. pub const BITCOIN_TXID_LENGTH: usize = 32; diff --git a/goat/src/contexts/verifier.rs b/goat/src/contexts/committee.rs similarity index 71% rename from goat/src/contexts/verifier.rs rename to goat/src/contexts/committee.rs index d6aad205a..e42871cdd 100644 --- a/goat/src/contexts/verifier.rs +++ b/goat/src/contexts/committee.rs @@ -3,18 +3,18 @@ use bitcoin::{key::Keypair, Network, PublicKey, XOnlyPublicKey}; use super::base::{generate_keys_from_secret, generate_n_of_n_public_key, BaseContext}; #[derive(Debug)] -pub struct VerifierContext { +pub struct CommitteeContext { pub network: Network, - pub verifier_keypair: Keypair, - pub verifier_public_key: PublicKey, + pub committee_keypair: Keypair, + pub committee_public_key: PublicKey, pub n_of_n_public_keys: Vec, pub n_of_n_public_key: PublicKey, pub n_of_n_taproot_public_key: XOnlyPublicKey, } -impl BaseContext for VerifierContext { +impl BaseContext for CommitteeContext { fn network(&self) -> Network { self.network } @@ -29,17 +29,17 @@ impl BaseContext for VerifierContext { } } -impl VerifierContext { - pub fn new(network: Network, verifier_secret: &str, n_of_n_public_keys: &[PublicKey]) -> Self { - let (keypair, public_key) = generate_keys_from_secret(network, verifier_secret); +impl CommitteeContext { + pub fn new(network: Network, committee_secret: &str, n_of_n_public_keys: &[PublicKey]) -> Self { + let (keypair, public_key) = generate_keys_from_secret(network, committee_secret); let (n_of_n_public_key, n_of_n_taproot_public_key) = generate_n_of_n_public_key(n_of_n_public_keys); - VerifierContext { + CommitteeContext { network, - verifier_keypair: keypair, - verifier_public_key: public_key, + committee_keypair: keypair, + committee_public_key: public_key, n_of_n_public_keys: n_of_n_public_keys.to_owned(), n_of_n_public_key, diff --git a/goat/src/contexts/mod.rs b/goat/src/contexts/mod.rs index 09492596a..7f0cec2c5 100644 --- a/goat/src/contexts/mod.rs +++ b/goat/src/contexts/mod.rs @@ -1,3 +1,3 @@ pub mod base; +pub mod committee; pub mod operator; -pub mod verifier; diff --git a/goat/src/disprove_scripts.rs b/goat/src/disprove_scripts.rs deleted file mode 100644 index fbfa8b4fd..000000000 --- a/goat/src/disprove_scripts.rs +++ /dev/null @@ -1,803 +0,0 @@ -use bitcoin::{ScriptBuf, Witness}; -use bitvm::bigint::U256; -use bitvm::chunk::api::type_conversion_utils::RawWitness; -use bitvm::chunk::api::{ - Assertions as ProofAssertions, PublicKeys as ProofPubkeys, Signatures as ProofSignatures, - NUM_HASH, NUM_PUBS, NUM_U256, -}; -use bitvm::hash::sha256_u4::sha256 as sha256_u4; -use bitvm::signatures::{Wots, Wots16, Wots32}; -#[allow(unused_imports)] -use bitvm::u4::u4_std::u4_hex_to_nibbles; -use bitvm::{treepp::*, FmtStack}; - -pub type ChallengeHashType = [u8; 20]; // OP_HASH160 - -pub const GUEST_PUBIN_G16_PUBIN_INDEX: usize = 1; -pub const GUEST_PUBIN_COMMITMENT_INDEX: usize = 0; // public inputs are reversed in assertions -pub const NUM_GUEST_PUBS_ASSERT: usize = 2; // commit [constants, watchtower-inclued-map] -pub const NUM_GUEST_PUBS_EXTRA: usize = 1; // commit blockhash -pub const NUM_GUEST: usize = NUM_GUEST_PUBS_ASSERT + NUM_GUEST_PUBS_EXTRA; -pub const GUEST_VALIDATION_TAPS: usize = 1; - -pub type AssertGuestValidationPubkeys = [::PublicKey; NUM_GUEST_PUBS_ASSERT]; -pub type AssertGuestValidationAssertions = [[u8; 32]; NUM_GUEST_PUBS_ASSERT]; - -pub type AssertPubkeys = (AssertGuestValidationPubkeys, ProofPubkeys); -pub type AssertAssertions = (AssertGuestValidationAssertions, ProofAssertions); - -pub type GuestPubinSignatures = Box<[::Signature; NUM_GUEST]>; -pub type Groth16PubinSignatures = Box<[::Signature; NUM_PUBS]>; - -pub fn validate_guest_assertions( - guest_sigs: &GuestPubinSignatures, - pubin_sigs: &Groth16PubinSignatures, - ack_preimages: &Vec>, // preimages length should match the number of hashes, use empty vec for unknown preimages - guest_validation_scripts: &[ScriptBuf; GUEST_VALIDATION_TAPS], -) -> Option<(usize, Script)> { - let witness_script = script! { - { Wots32::signature_to_raw_witness(&pubin_sigs[GUEST_PUBIN_COMMITMENT_INDEX]) } - { Wots32::signature_to_raw_witness(&guest_sigs[0]) } - { Wots32::signature_to_raw_witness(&guest_sigs[1]) } - { push_preimage_to_stack(&ack_preimages) } - { Wots32::signature_to_raw_witness(&guest_sigs[2]) } - }; - let full_script = witness_script - .clone() - .push_script(guest_validation_scripts[0].clone()); - let res = execute_script(full_script); - if res.success { - Some((0, witness_script)) - } else if res.final_stack.len() == 1 { - None - } else { - panic!("unexpected script execution result, maybe sigs/scripts do not match?"); - } -} - -pub fn generate_guest_pubin_commitment(guest_pubin_num: u32) -> Script { - script! { - for i in 0..guest_pubin_num as usize { - { lift_and_reverse_bytes_u4(64 * i, 32) } - } - { sha256_u4(guest_pubin_num * 32) } - { reverse_bytes_u4(32) } - OP_SWAP { mod2_u4() } OP_SWAP - } -} - -// // with 8-bytes length prefix (deprecated) -// pub fn generate_guest_pubin_commitment(guest_pubin_num: u32) -> Script { -// fn push_length_prefix_rev_u4() -> Script { -// script! { -// { u4_hex_to_nibbles("0000000000000020")} -// } -// } -// script! { -// for i in 0..guest_pubin_num as usize { -// { push_length_prefix_rev_u4() } -// { lift_and_reverse_bytes_u4(80 * i + 16, 32) } -// } -// { sha256_u4(guest_pubin_num * 40) } -// { reverse_bytes_u4(32) } -// OP_SWAP { mod2_u4() } OP_SWAP -// } -// } - -pub fn verify_guest_pubin( - guest_pubin_wots_pubkeys: &[::PublicKey; NUM_GUEST], - groth16_pubin_wots_pubkeys: &[::PublicKey; NUM_PUBS], - constant_value: &[u8; 32], - hashes: &Vec, -) -> [Script; GUEST_VALIDATION_TAPS] { - let wots32_msg_stack_items_num = Wots32::MSG_BYTE_LEN as usize * 2; - let wots32_sig_stack_items_num = Wots32::TOTAL_DIGIT_LEN as usize * 2; - let zipped_wots32_msg_stack_items_num = Wots32::MSG_BYTE_LEN as usize; - let scr = script! { - { 1 } OP_TOALTSTACK // flag for overall result - - { verify_hashlock_pubin_script_dup(&guest_pubin_wots_pubkeys[2], hashes) } - OP_FROMALTSTACK OP_BOOLAND OP_TOALTSTACK - - { roll_n(wots32_msg_stack_items_num, wots32_sig_stack_items_num) } - - { verify_constant_pubin_script_dup(&guest_pubin_wots_pubkeys[1], constant_value) } - OP_FROMALTSTACK OP_BOOLAND OP_TOALTSTACK - - { roll_n(wots32_msg_stack_items_num * 2, wots32_sig_stack_items_num) } - - { Wots32::checksig_verify(&guest_pubin_wots_pubkeys[0]) } - - { roll_n(wots32_msg_stack_items_num * 3, wots32_sig_stack_items_num) } - - { Wots32::checksig_verify(&groth16_pubin_wots_pubkeys[GUEST_PUBIN_COMMITMENT_INDEX]) } - - { reverse_zip_nibbles_bytes32() } - - { roll_n(zipped_wots32_msg_stack_items_num, wots32_msg_stack_items_num * 3) } - - { generate_guest_pubin_commitment(NUM_GUEST as u32) } // guest-pubin-commitment = hash(blockhash || constant || included_watchtowers_map) - - { zip_nibbles_bytes32() } - - OP_FROMALTSTACK - for i in (0..32).rev() { - OP_SWAP { i + 2 } OP_ROLL OP_NUMEQUAL OP_BOOLAND - } - - // if flag remains 1, all checks passed, disprove shall fail - OP_NOT - }; - [scr] -} - -/// Returns a Bitcoin script that verifies a Winternitz signature for the given `wots_pk` -/// and additionally checks that message matches the provided `constant_value` -/// -/// ## Precondition -/// -/// - The Winternitz signature (compact) is at the stack top. -/// -/// ## Postcondition -/// -/// - If the signature is invalid: the script fails immediately -/// - If the signature is valid: the script continues to compare msg against `constant_value` -/// - `1` (true): every nibble matches exactly; -/// - `0` (false): at least one nibble does not match. -/// - The comparison process consumes the message, leaving only the final boolean result. -/// -pub fn verify_constant_pubin_script( - wots_pk: &::PublicKey, - constant_value: &[u8; 32], -) -> Script { - script! { - { Wots32::checksig_verify(wots_pk) } - { verify_constant_pubin_script_inner(constant_value) } - } -} -// same as `verify_constant_pubin_script` but leave message in stack (below the boolean result) -pub fn verify_constant_pubin_script_dup( - wots_pk: &::PublicKey, - constant_value: &[u8; 32], -) -> Script { - script! { - { Wots32::checksig_verify(wots_pk) } - for i in 0..64 { - { i } OP_PICK - OP_TOALTSTACK - } - { verify_constant_pubin_script_inner(constant_value) } - for _ in 0..64 { - OP_FROMALTSTACK - } - { 64 } OP_ROLL - } -} -// inner part of `verify_constant_pubin_script` that assumes the signature has been verified -pub fn verify_constant_pubin_script_inner(constant_value: &[u8; 32]) -> Script { - script! { - { 1 } - for byte in constant_value.to_vec() { - OP_SWAP - { byte & 0x0F } - OP_NUMEQUAL - OP_BOOLAND - - OP_SWAP - { byte >> 4 } - OP_NUMEQUAL - OP_BOOLAND - } - } -} - -/// Returns a Bitcoin script that verifies a Winternitz signature for the given `wots_pk` -/// corresponding to a watchtower-included bitmap, and checks the relationship between -/// the bitmap and provided `preimages`. -/// -/// ## Precondition -/// -/// - The stack top contains the compact Winternitz signature for the watchtower-included bitmap. -/// - Below the signature on the stack is the list of `preimages` -/// -/// ## Postcondition -/// -/// - If the Winternitz signature is invalid: the script fails immediately. -/// - If the signature is valid: the script iterates over each index `i` of the bitmap: -/// - If `watchtower_included_bitmap[i] == 0` and `hash(preimages[i]) == hashes[i]`, -/// the final stack result is `0` (false). -/// - Otherwise, the final stack result is `1` (true). -/// - The comparison consumes the message and preimages from the stack, leaving only the final boolean result. -/// -pub fn verify_hashlock_pubin_script( - wots_pk: &::PublicKey, - hashes: &Vec, -) -> Script { - script! { - { Wots32::checksig_verify(wots_pk) } - { verify_hashlock_pubin_script_inner(hashes) } - } -} -// same as `verify_hashlock_pubin_script` but leave bitmap in stack (below the boolean result) -pub fn verify_hashlock_pubin_script_dup( - wots_pk: &::PublicKey, - hashes: &Vec, -) -> Script { - script! { - { Wots32::checksig_verify(wots_pk) } - for i in 0..64 { - { i } OP_PICK - OP_TOALTSTACK - } - { verify_hashlock_pubin_script_inner(hashes) } - for _ in 0..64 { - OP_FROMALTSTACK - } - { 64 } OP_ROLL - } -} -// inner part of `verify_hashlock_pubin_script` that assumes the signature has been verified -fn verify_hashlock_pubin_script_inner(hashes: &Vec) -> Script { - // assert!(hashes.len() <= TBD); - fn chunk_count(len: usize, chunk_size: usize) -> usize { - (len + chunk_size - 1) / chunk_size - } - script! { - for _ in 0..hashes.len() { - { 64 } OP_ROLL OP_TOALTSTACK - } - { 1 } - for chunk in hashes.chunks(4) { - OP_SWAP - for i in (1..4).rev() { - if i >= chunk.len() { - { 1 << i } OP_2DUP - OP_GREATERTHANOREQUAL - OP_IF - OP_SUB - OP_ELSE - OP_DROP - OP_ENDIF - } else { - { 1 << i } OP_2DUP - OP_GREATERTHANOREQUAL - OP_IF - OP_SUB OP_FROMALTSTACK OP_DROP - OP_ELSE - OP_DROP - OP_SWAP - OP_FROMALTSTACK - OP_HASH160 - { chunk[i].to_vec() } - OP_EQUAL - OP_NOT - OP_BOOLAND - OP_SWAP - OP_ENDIF - } - } - OP_IF - OP_FROMALTSTACK OP_DROP - OP_ELSE - OP_FROMALTSTACK - OP_HASH160 - { chunk[0].to_vec() } - OP_EQUAL - OP_NOT - OP_BOOLAND - OP_ENDIF - } - for _ in 0..(64 - chunk_count(hashes.len(), 4)) { - OP_NIP - } - } -} - -pub fn push_preimage_to_stack(preimages: &Vec>) -> Script { - script! { - for chunk in preimages.chunks(4) { - for i in (0..chunk.len()).rev() { - { chunk[i].to_vec() } - } - } - } -} - -fn roll(d: usize) -> Script { - match d { - 0 => script! {}, - 1 => script! { OP_SWAP }, - 2 => script! { OP_ROT }, - _ => script! { { d } OP_ROLL }, - } -} - -fn lift_and_reverse_bytes_u4(depth: usize, num_bytes: usize) -> Script { - script! { - for i in 0..num_bytes { - { roll(depth + 2*i + 1) } - { roll(depth + 2*i + 1) } - } - } -} - -fn reverse_bytes_u4(num_bytes: usize) -> Script { - lift_and_reverse_bytes_u4(0, num_bytes) -} - -fn roll_n(depth: usize, num_items: usize) -> Script { - script! { - for _ in 0..num_items { - { roll(depth + num_items - 1) } - } - } -} - -fn mod2_u4() -> Script { - script! { - OP_DUP - OP_8 - OP_GREATERTHANOREQUAL - OP_IF - OP_8 - OP_SUB - OP_ENDIF - - OP_DUP - OP_4 - OP_GREATERTHANOREQUAL - OP_IF - OP_4 - OP_SUB - OP_ENDIF - - OP_DUP - OP_3 - OP_EQUAL - - OP_SWAP - OP_1 - OP_EQUAL - OP_BOOLOR - } -} - -fn zip_nibbles_bytes32() -> Script { - script! { - { U256::transform_limbsize(4, 8) } - } -} - -fn reverse_zip_nibbles_bytes32() -> Script { - script! { - for _ in 0..32 { - { roll(62) } - { roll(63) } - } - { zip_nibbles_bytes32() } - } -} - -pub fn bits_to_bytes32(bits: &[bool]) -> [u8; 32] { - let mut out = [0u8; 32]; - let len = bits.len().min(256); - for i in 0..len { - let byte_index = i / 8; - let bit_index = i % 8; - if bits[i] { - out[byte_index] |= 1 << bit_index; - } - } - out -} - -pub fn hash160(msg: &Vec) -> [u8; 20] { - use bitcoin::hashes::{hash160, Hash}; - hash160::Hash::hash(msg).to_byte_array() -} - -pub fn utils_signatures_from_raw_witnesses( - raw_wits: &[RawWitness], // commit_blockhash witnesses || assert_commit witnesses -) -> (GuestPubinSignatures, ProofSignatures) { - assert_eq!(raw_wits.len(), NUM_GUEST + NUM_PUBS + NUM_U256 + NUM_HASH); - let mut guest_sigs = vec![]; - for i in 0..NUM_GUEST { - let a = Wots32::raw_witness_to_signature(&Witness::from_slice(&raw_wits[i])); - guest_sigs.push(a); - } - let mut asigs = vec![]; - for i in 0..NUM_PUBS { - let a = Wots32::raw_witness_to_signature(&Witness::from_slice(&raw_wits[i + NUM_GUEST])); - asigs.push(a); - } - let mut bsigs = vec![]; - for i in 0..NUM_U256 { - let a = Wots32::raw_witness_to_signature(&Witness::from_slice( - &raw_wits[i + NUM_GUEST + NUM_PUBS], - )); - bsigs.push(a); - } - let mut csigs = vec![]; - for i in 0..NUM_HASH { - let a = Wots16::raw_witness_to_signature(&Witness::from_slice( - &raw_wits[i + NUM_GUEST + NUM_PUBS + NUM_U256], - )); - csigs.push(a); - } - let guest_sigs = guest_sigs.try_into().unwrap(); - let asigs = asigs.try_into().unwrap(); - let bsigs = bsigs.try_into().unwrap(); - let csigs = csigs.try_into().unwrap(); - (guest_sigs, (asigs, bsigs, csigs)) -} - -/// Parse `num_bytes` bytes from the stack, starting at `start_index` (from the top). -/// Each byte is encoded as two 4-bit nibbles (high, low) on the stack. -pub fn parse_u4_stack(start_index: usize, num_bytes: usize, stack: &FmtStack) -> Vec { - fn get_stack_element_from_top(stack: &FmtStack, index_from_top: usize) -> Vec { - stack.get(stack.len() - 1 - index_from_top).to_vec() - } - fn to_u4(v: &[u8]) -> Option { - if v.is_empty() { - return Some(0); - } - if v[0] > 0x0F { - return None; - } - for &byte in &v[1..] { - if byte != 0 { - return None; - } - } - Some(v[0]) - } - let mut v = vec![]; - assert!( - stack.len() >= start_index + num_bytes * 2, - "stack too short" - ); - for i in 0..num_bytes { - let low = get_stack_element_from_top(stack, start_index + i * 2); - let low = to_u4(&low).expect(&format!("low nibble out of range: {}", hex::encode(low))); - let high = get_stack_element_from_top(stack, start_index + i * 2 + 1); - let high = to_u4(&high).expect(&format!("high nibble out of range: {}", hex::encode(high))); - v.push(high * 16 + low); - } - v -} - -pub fn reverse_each_byte(input: [u8; 32]) -> [u8; 32] { - let mut output = [0u8; 32]; - for i in 0..32 { - let byte = input[i]; - let high_nibble = (byte & 0xF0) >> 4; - let low_nibble = byte & 0x0F; - output[i] = (low_nibble << 4) | high_nibble; - } - output -} - -#[test] -fn test_hash160() { - let input = "hello world".as_bytes().to_vec(); - let hash = hash160(&input); - let hash160_script = script! { - { input } OP_HASH160 - { hash.to_vec() } - OP_EQUAL - }; - let result = execute_script(hash160_script); - assert!(result.success); -} - -#[test] -fn test_verify_constant_pubin_script() { - use hex::FromHex; - let secret = Wots32::generate_secret_key(); - let public_key = Wots32::generate_public_key(&secret); - let correct_msg = - <[u8; 32]>::from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") - .unwrap(); - let incorrect_msg = - <[u8; 32]>::from_hex("100102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") - .unwrap(); - let constant = - <[u8; 32]>::from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") - .unwrap(); - - let s = script! { - { Wots32::sign_to_raw_witness(&secret, &correct_msg) } - { verify_constant_pubin_script(&public_key, &constant) } - { 1 } - OP_EQUALVERIFY - OP_TRUE - }; - println!("constant check script length: {}", s.len()); - let result = execute_script(s); - assert!(result.success); - assert_eq!(result.final_stack.len(), 1); - - let s = script! { - { Wots32::sign_to_raw_witness(&secret, &incorrect_msg) } - { verify_constant_pubin_script(&public_key, &constant) } - { 0 } - OP_EQUALVERIFY - OP_TRUE - }; - let result = execute_script(s); - assert!(result.success); - assert_eq!(result.final_stack.len(), 1); - - let s = script! { - { Wots32::sign_to_raw_witness(&secret, &correct_msg) } - { verify_constant_pubin_script_dup(&public_key, &constant) } - }; - let result = execute_script(s); - assert_eq!( - parse_u4_stack(1, 32, &result.final_stack), - correct_msg.to_vec() - ); -} - -#[test] -fn test_verify_hashlock_pubin_script() { - let secret = Wots32::generate_secret_key(); - let public_key = Wots32::generate_public_key(&secret); - let hashes_len = 11; - let mut preimages: Vec> = vec![]; - let mut hashes: Vec<[u8; 20]> = vec![]; - for i in 0..hashes_len { - preimages.push(format!("preimage_{:02x}", i).into_bytes()); - hashes.push(hash160(&preimages[i].clone())); - // println!("hash {}: {:?}", i, hex::encode(hashes[i].iter().rev().cloned().collect::>())); - } - fn to_bool_vec(input: &[u8]) -> Vec { - input.iter().map(|x| *x != 0).collect() - } - - { - // TEST OP_TRUE case: all bitmap=0 indices have preimages that do NOT match the corresponding hashes - let inclueded = to_bool_vec(&[1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1]); - let mut input_preimages = vec![vec![]; hashes_len]; - input_preimages[1] = preimages[1].clone(); - input_preimages[2] = preimages[1].clone(); - let s = script! { - { push_preimage_to_stack(&input_preimages) } - { Wots32::sign_to_raw_witness(&secret, &bits_to_bytes32(&inclueded)) } - { verify_hashlock_pubin_script(&public_key, &hashes) } - { 1 } - OP_EQUALVERIFY - OP_TRUE - }; - println!("hashlock check script length: {}", s.len()); - let result = execute_script(s); - assert!(result.success); - assert_eq!(result.final_stack.len(), 1); - } - - { - // TEST OP_FALSE case 1: at least one bitmap=0 index has a matching preimage hash - let inclueded = to_bool_vec(&[1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1]); - let mut input_preimages = vec![vec![]; hashes_len]; - input_preimages[2] = preimages[2].clone(); - let s = script! { - { push_preimage_to_stack(&input_preimages) } - { Wots32::sign_to_raw_witness(&secret, &bits_to_bytes32(&inclueded)) } - { verify_hashlock_pubin_script(&public_key, &hashes) } - { 0 } - OP_EQUALVERIFY - OP_TRUE - }; - let result = execute_script(s); - assert!(result.success); - assert_eq!(result.final_stack.len(), 1); - } - - { - // TEST OP_FALSE case 2: bitmap padded with extra bits - // extra padded bitmap bits should not change the result - let inclueded = to_bool_vec(&[1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1]); - let mut input_preimages = vec![vec![]; hashes_len]; - input_preimages[2] = preimages[2].clone(); - let s = script! { - { push_preimage_to_stack(&input_preimages) } - { Wots32::sign_to_raw_witness(&secret, &bits_to_bytes32(&inclueded)) } - { verify_hashlock_pubin_script(&public_key, &hashes) } - { 0 } - OP_EQUALVERIFY - OP_TRUE - }; - let result = execute_script(s); - assert!(result.success); - assert_eq!(result.final_stack.len(), 1); - } - - { - // TEST dup version: leave bitmap in stack - let inclueded = to_bool_vec(&[1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1]); - let mut input_preimages = vec![vec![]; hashes_len]; - input_preimages[1] = preimages[1].clone(); - let s = script! { - { push_preimage_to_stack(&input_preimages) } - { Wots32::sign_to_raw_witness(&secret, &bits_to_bytes32(&inclueded)) } - { verify_hashlock_pubin_script_dup(&public_key, &hashes) } - }; - let result = execute_script(s); - assert_eq!( - parse_u4_stack(1, 32, &result.final_stack), - bits_to_bytes32(&inclueded).to_vec() - ); - } -} - -#[test] -fn test_verify_guest_pubin_ziren() { - use hex::FromHex; - let secrets = std::iter::repeat(Wots32::generate_secret_key()) - .take(NUM_GUEST + NUM_PUBS) - .collect::>(); - let pubkeys = secrets - .iter() - .map(|s| Wots32::generate_public_key(s)) - .collect::>(); - let blockhash = - <[u8; 32]>::from_hex("5a690bba0ba076d621f77665398f4b1ddbfc2349bbb3e8880307625ac5cfa900") - .unwrap(); - let constant = - <[u8; 32]>::from_hex("2df7bde0605973f5809a1338094cb47a309bc38363dd35ce178c088cd3cda79f") - .unwrap(); - let included_bitmap = - <[u8; 32]>::from_hex("0100000000000000000000000000000000000000000000000000000000000000") - .unwrap(); - // let groth16_pubin = - // <[u8; 32]>::from_hex("1a5605834864faf9cb10055606d9ae06425ea5cf8cf757f996182cd1da196158") - // .unwrap(); - - use ark_serialize::CanonicalDeserialize; - use ark_ff::{BigInteger, PrimeField}; - // let proof_file = "ziren/proof.bin"; - // let proof_bin = std::fs::read(proof_file).unwrap(); - // let groth16_proof = ark_groth16::Proof::::deserialize_compressed(&*proof_bin).unwrap(); - // let vk_file = "ziren/vk.bin"; - // let vk_bin = std::fs::read(vk_file).unwrap(); - // let groth16_vkey = ark_groth16::VerifyingKey::::deserialize_compressed(&*vk_bin).unwrap(); - let pubin_file = "ziren/public_inputs.bin"; - let pubin_bin = std::fs::read(pubin_file).unwrap(); - let groth16_public_inputs = <[ark_bn254::Fr; 2]>::deserialize_compressed(&*pubin_bin).unwrap(); - let guest_pubin_commitment = groth16_public_inputs[GUEST_PUBIN_G16_PUBIN_INDEX]; - let guest_pubin_commitment_bytes = guest_pubin_commitment.into_bigint().to_bytes_be(); - let mut groth16_pubin = [0u8; 32]; - groth16_pubin[32 - guest_pubin_commitment_bytes.len()..] - .copy_from_slice(&guest_pubin_commitment_bytes); - - let hashes_len = 2; - let mut preimages: Vec> = vec![]; - let mut hashes: Vec<[u8; 20]> = vec![]; - for i in 0..hashes_len { - preimages.push(format!("preimage_{:02x}", i).into_bytes()); - hashes.push(hash160(&preimages[i].clone())); - } - - let lock_scr = script! { - { verify_guest_pubin( - &pubkeys[0..NUM_GUEST].try_into().unwrap(), - &pubkeys[NUM_GUEST..NUM_GUEST + NUM_PUBS].try_into().unwrap(), - &constant, - &hashes, - )[0].clone() } - }; - println!("guest pubin validation script length: {}", lock_scr.len()); - - { - // TEST SUCCESS case: provide preimages for some bitmap = 0 indices - let mut input_preimages = vec![vec![]; hashes_len]; - input_preimages[1] = preimages[1].clone(); - let full_scr = script! { - { Wots32::sign_to_raw_witness(&secrets[NUM_GUEST + GUEST_PUBIN_COMMITMENT_INDEX], &reverse_each_byte(groth16_pubin)) } - { Wots32::sign_to_raw_witness(&secrets[0], &blockhash) } - { Wots32::sign_to_raw_witness(&secrets[1], &constant) } - { push_preimage_to_stack(&input_preimages) } - { Wots32::sign_to_raw_witness(&secrets[2], &included_bitmap) } - { lock_scr.clone() } - }; - println!("full script length: {}", full_scr.len()); - let result = execute_script_without_stack_limit(full_scr); - assert!(result.success); - assert_eq!(result.final_stack.len(), 1); - } - - { - // TEST SUCCESS case: mismatch constant - let input_preimages = vec![vec![]; hashes_len]; - let mut mismatched_constant = constant.clone(); - mismatched_constant[0] ^= 0xFF; - let full_scr = script! { - { Wots32::sign_to_raw_witness(&secrets[NUM_PUBS + GUEST_PUBIN_COMMITMENT_INDEX], &reverse_each_byte(groth16_pubin)) } - { Wots32::sign_to_raw_witness(&secrets[0], &blockhash) } - { Wots32::sign_to_raw_witness(&secrets[1], &mismatched_constant) } - { push_preimage_to_stack(&input_preimages) } - { Wots32::sign_to_raw_witness(&secrets[2], &included_bitmap) } - { lock_scr.clone() } - }; - let result = execute_script_without_stack_limit(full_scr); - assert!(result.success); - assert_eq!(result.final_stack.len(), 1); - } - - { - // TEST SUCCESS case: mismatch groth16 pubin - let input_preimages = vec![vec![]; hashes_len]; - let mut mismatched_groth16_pubin = groth16_pubin.clone(); - mismatched_groth16_pubin[1] ^= 0xFF; - let full_scr = script! { - { Wots32::sign_to_raw_witness(&secrets[NUM_PUBS + GUEST_PUBIN_COMMITMENT_INDEX], &reverse_each_byte(mismatched_groth16_pubin)) } - { Wots32::sign_to_raw_witness(&secrets[0], &blockhash) } - { Wots32::sign_to_raw_witness(&secrets[1], &constant) } - { push_preimage_to_stack(&input_preimages) } - { Wots32::sign_to_raw_witness(&secrets[2], &included_bitmap) } - { lock_scr.clone() } - }; - let result = execute_script_without_stack_limit(full_scr); - assert!(result.success); - assert_eq!(result.final_stack.len(), 1); - } - - { - // TEST FAILURE case: everything correct - let mut input_preimages = vec![vec![]; hashes_len]; - input_preimages[0] = preimages[0].clone(); - let full_scr = script! { - { Wots32::sign_to_raw_witness(&secrets[NUM_PUBS + GUEST_PUBIN_COMMITMENT_INDEX], &reverse_each_byte(groth16_pubin)) } - { Wots32::sign_to_raw_witness(&secrets[0], &blockhash) } - { Wots32::sign_to_raw_witness(&secrets[1], &constant) } - { push_preimage_to_stack(&input_preimages) } - { Wots32::sign_to_raw_witness(&secrets[2], &included_bitmap) } - { lock_scr.clone() } - }; - let result = execute_script_without_stack_limit(full_scr); - assert!(!result.success); - assert_eq!(result.final_stack.len(), 1); - } -} - -#[test] -fn test_generate_guest_pubin_commitment_ziren() { - fn u4_bytes_to_nibbles(bytes: &[u8]) -> Script { - let mut rev_bytes = bytes.to_vec(); - rev_bytes.reverse(); - // u4_hex_to_nibbles takes little-endian hex strings - script! { - { u4_hex_to_nibbles(&hex::encode(rev_bytes)) } - } - } - let pubins: Vec<[u8; 32]> = vec![ - hex::decode("5a690bba0ba076d621f77665398f4b1ddbfc2349bbb3e8880307625ac5cfa900") - .unwrap() - .try_into() - .unwrap(), - hex::decode("2df7bde0605973f5809a1338094cb47a309bc38363dd35ce178c088cd3cda79f") - .unwrap() - .try_into() - .unwrap(), - hex::decode("0100000000000000000000000000000000000000000000000000000000000000") - .unwrap() - .try_into() - .unwrap(), - ]; - // expected_commit_value with length prefix: 001df6fc84f5d020f1453744b865e29750c935c11c89e3c1605e27db449d8821 - let expected_commit_value: [u8; 32] = - hex::decode("1a5605834864faf9cb10055606d9ae06425ea5cf8cf757f996182cd1da196158") - .unwrap() - .try_into() - .unwrap(); - - let s = script! { - { u4_bytes_to_nibbles(&pubins[2]) } - { u4_bytes_to_nibbles(&pubins[1]) } - { u4_bytes_to_nibbles(&pubins[0]) } - { generate_guest_pubin_commitment(3) } - }; - let result = execute_script(s); - let commit_values = parse_u4_stack(0, 32, &result.final_stack); - assert_eq!(expected_commit_value.to_vec(), commit_values); -} diff --git a/goat/src/lib.rs b/goat/src/lib.rs index b10b946b2..0eebb4fb6 100644 --- a/goat/src/lib.rs +++ b/goat/src/lib.rs @@ -1,11 +1,12 @@ +pub mod assert_scripts; pub mod common; pub mod connectors; pub mod constants; pub mod contexts; -pub mod disprove_scripts; pub mod error; pub mod proof; pub mod scripts; pub mod serialization; pub mod transactions; pub mod utils; +pub mod wots; diff --git a/goat/src/proof.rs b/goat/src/proof.rs index 9aaf88769..5abd6c0fa 100644 --- a/goat/src/proof.rs +++ b/goat/src/proof.rs @@ -25,9 +25,7 @@ pub fn serialize_pubin(pubin: Vec) -> Vec { use ark_ff::BigInt; use ark_ff::PrimeField; - let f_big = match f.into_bigint() { - BigInt(x) => x, - }; + let BigInt(f_big) = f.into_bigint(); let mut res = Vec::with_capacity(f_big.len() * 8); for &num in f_big.iter() { res.extend_from_slice(&num.to_le_bytes()); @@ -60,7 +58,7 @@ pub fn deserialize_vk(buffer: Vec) -> ark_groth16::VerifyingKey { .unwrap() } pub fn deserialize_pubin(buffer: Vec) -> Vec { - fn tmp_fr_deserialization(v: Vec) -> ark_bn254::Fr { + fn tmp_fr_deserialization(v: &[u8]) -> ark_bn254::Fr { use ark_ff::BigInt; use ark_ff::PrimeField; @@ -73,8 +71,8 @@ pub fn deserialize_pubin(buffer: Vec) -> Vec { let buffer: Vec> = bincode::deserialize(&buffer).unwrap(); let mut pubin = vec![]; - for i in 0..buffer.len() { - let f = tmp_fr_deserialization(buffer[i].clone()); + for item in &buffer { + let f = tmp_fr_deserialization(item); pubin.push(f); } pubin @@ -84,6 +82,7 @@ pub fn deserialize_pubin(buffer: Vec) -> Vec { #[ignore] #[allow(deprecated)] fn verify_zkm2_proof() { + use crate::wots::{Wots, Wots16, Wots32}; /// TODO: Update NUM_PUBS, NUM_256, NUM_160, mock_proof /// NUM_PUBS = 2 /// NUM_256 = 14 @@ -92,12 +91,11 @@ fn verify_zkm2_proof() { api_generate_full_tapscripts, api_generate_partial_script, generate_signatures, validate_assertions, PublicKeys, NUM_HASH, NUM_PUBS, NUM_U256, }; - use bitvm::signatures::{Wots, Wots16, Wots32}; fn get_pubkeys(secret_key: Vec) -> PublicKeys { let mut pubins = vec![]; - for i in 0..NUM_PUBS { + for secret in secret_key.iter().take(NUM_PUBS) { pubins.push(Wots32::generate_public_key(&Wots32::secret_from_str( - secret_key[i].as_str(), + secret.as_str(), ))); } let mut fq_arr = vec![]; @@ -218,12 +216,7 @@ fn genearte_test_proof() { impl Clone for DummyCircuit { fn clone(&self) -> Self { - DummyCircuit { - a: self.a, - b: self.b, - num_variables: self.num_variables, - num_constraints: self.num_constraints, - } + *self } } diff --git a/goat/src/scripts.rs b/goat/src/scripts.rs index de7dc9d73..180d8e520 100644 --- a/goat/src/scripts.rs +++ b/goat/src/scripts.rs @@ -6,7 +6,7 @@ use bitcoin::{ use bitvm::treepp::script; use std::{str::FromStr, sync::LazyLock}; -use crate::transactions::base::DUST_AMOUNT; +use crate::transactions::base::{DUST_AMOUNT, P2A_AMOUNT}; // TODO replace these public keys pub static UNSPENDABLE_PUBLIC_KEY: LazyLock = LazyLock::new(|| { @@ -172,7 +172,7 @@ pub fn generate_opreturn_script(msg: Vec) -> ScriptBuf { } pub fn p2a_amount() -> Amount { - Amount::from_sat(240) + Amount::from_sat(P2A_AMOUNT) } pub fn p2a_script() -> ScriptBuf { diff --git a/goat/src/transactions/assert.rs b/goat/src/transactions/assert.rs index 64be723f2..dad85bf7c 100644 --- a/goat/src/transactions/assert.rs +++ b/goat/src/transactions/assert.rs @@ -1,21 +1,27 @@ -use ark_std::iterable::Iterable; +use std::vec; + use bitcoin::{absolute, consensus, Amount, ScriptBuf, TapSighashType, Transaction, TxIn, TxOut}; -use bitvm::signatures::WinternitzSecret; +use bitvm::{chunk::api::type_conversion_utils::RawWitness, treepp::*}; use musig2::{errors::SigningError, AggNonce, PartialSignature, SecNonce}; use serde::{Deserialize, Serialize}; use crate::{ + assert_scripts::{Label, OperatorAssertSecretKey, INPUT_WIRE_NUM}, connectors::{ - assert_connectors::AssertCommitConnector, base::TaprootConnector, connector_c::ConnectorC, + assert_connectors::{ProverConnector, VerifierConnector}, + base::TaprootConnector, + connector_c::ConnectorC, connector_d::ConnectorD, }, - contexts::{base::BaseContext, operator::OperatorContext, verifier::VerifierContext}, - disprove_scripts::AssertAssertions, + contexts::{base::BaseContext, committee::CommitteeContext}, error::{Error, TransactionError::InsufficientInputAmount}, scripts::p2a_output, transactions::{ + base::*, + pre_signed::PreSignedTransaction, signing::{ - populate_taproot_txin_witness, push_taproot_leaf_script_and_control_block_to_witness, + populate_taproot_input_witness, populate_taproot_txin_witness, + push_taproot_leaf_script_and_control_block_to_witness, }, signing_musig2::{ generate_taproot_aggregated_signature, generate_taproot_partial_signature, @@ -23,102 +29,16 @@ use crate::{ }, }; -use super::{base::*, pre_signed::*}; - -pub fn operator_commit_proof( - assert_commit_connectors: &Vec, - wots_secret_keys: &Vec, - assert_commit_inputs: &Vec, - assertions: &AssertAssertions, -) -> Result, Error> { - if assert_commit_connectors.len() != assert_commit_inputs.len() { - return Err(Error::Other( - "Mismatched number of AssertCommit connectors and inputs", - )); - } - let mut wots_32_num = 0; - let mut wots_16_num = 0; - let nguest = assertions.0.len(); - let npub = assertions.1 .0.len(); - let n32 = assertions.1 .1.len(); - let n16 = assertions.1 .2.len(); - for acc in assert_commit_connectors.iter() { - wots_32_num += acc.wots32_pubkeys.len(); - wots_16_num += acc.wots16_pubkeys.len(); - } - if (wots_32_num + wots_16_num) != wots_secret_keys.len() { - return Err(Error::Other("Mismatched number of WOTS keys")); - } - if wots_32_num != nguest + npub + n32 { - return Err(Error::Other("Mismatched number of WOTS32 assertions")); - } - if wots_16_num != n16 { - return Err(Error::Other("Mismatched number of WOTS16 assertions")); - } - - let mut res = vec![]; - let mut cur_index = 0; - for (i, acc) in assert_commit_connectors.iter().enumerate() { - let input_0_leaf = 0; - let mut txin = acc.generate_taproot_leaf_tx_in(input_0_leaf, &assert_commit_inputs[i]); - let start_index = cur_index; - let end_index = cur_index + acc.wots32_pubkeys.len() + acc.wots16_pubkeys.len(); - - let startguest = start_index.min(nguest); - let endguest = end_index.min(nguest); - - let startpub = start_index.saturating_sub(nguest).min(npub); - let endpub = end_index.saturating_sub(nguest).min(npub); - - let start32 = start_index.saturating_sub(npub + nguest).min(n32); - let end32 = end_index.saturating_sub(npub + nguest).min(n32); - - let start16 = start_index.saturating_sub(nguest + npub + n32); - let end16 = end_index.saturating_sub(nguest + npub + n32); - - cur_index = end_index; - - let wots32_sks = - wots_secret_keys[start_index..(start_index + acc.wots32_pubkeys.len())].to_vec(); - let wots16_sks = - wots_secret_keys[(start_index + acc.wots32_pubkeys.len())..end_index].to_vec(); - - let mut wots32_values = assertions.0[startguest..endguest].to_vec(); - wots32_values.extend(assertions.1 .0[startpub..endpub].to_vec()); - wots32_values.extend(assertions.1 .1[start32..end32].to_vec()); - let wots16_values = assertions.1 .2[start16..end16].to_vec(); - - match acc.generate_leaf_0_unlock_data( - &wots32_sks, - &wots16_sks, - &wots32_values, - &wots16_values, - ) { - Ok(unlock_data) => { - populate_taproot_txin_witness( - &mut txin, - &acc.generate_taproot_spend_info(), - &acc.generate_taproot_leaf_script(0), - unlock_data, - ); - } - Err(e) => return Err(e), - } - res.push(txin); - } - - Ok(res) -} - #[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] -pub struct AssertInitTransaction { +pub struct OperatorAssertTransaction { #[serde(with = "consensus::serde::With::")] tx: Transaction, #[serde(with = "consensus::serde::With::")] prev_outs: Vec, prev_scripts: Vec, } -impl PreSignedTransaction for AssertInitTransaction { + +impl PreSignedTransaction for OperatorAssertTransaction { fn tx(&self) -> &Transaction { &self.tx } @@ -135,48 +55,46 @@ impl PreSignedTransaction for AssertInitTransaction { &self.prev_scripts } } -impl AssertInitTransaction { + +impl OperatorAssertTransaction { pub fn new_for_validation( connector_c: &ConnectorC, + verifier_connectors: &Vec, connector_d: &ConnectorD, - assert_commit_connectors: &Vec, - input_0: &Input, + input_0: Input, ) -> Result { - let input_0_leaf = 0; + let input_0_leaf = 1; let _input_0 = connector_c.generate_taproot_leaf_tx_in(input_0_leaf, &input_0); if input_0.amount - < Amount::from_sat( - min_relay_fee_assert_init(assert_commit_connectors.len()) - + (assert_commit_connectors.len() as u64 + 2) * DUST_AMOUNT, - ) + < Amount::from_sat(operator_assert_input_amount(verifier_connectors.len())) { return Err(Error::Transaction(InsufficientInputAmount)); } let mut total_output_amount = input_0.amount - - Amount::from_sat(min_relay_fee_assert_init(assert_commit_connectors.len())); + - Amount::from_sat(min_relay_fee_operator_assert(verifier_connectors.len())); let mut txouts = vec![]; - for assert_commit_connector in assert_commit_connectors { - let commit_output = TxOut { - value: Amount::from_sat(DUST_AMOUNT), - script_pubkey: assert_commit_connector + for verifier_connector in verifier_connectors { + let assert_output = TxOut { + value: Amount::from_sat(verifier_assert_input_amount()), + script_pubkey: verifier_connector .generate_taproot_address() .script_pubkey(), }; - total_output_amount -= commit_output.value; - txouts.push(commit_output); + total_output_amount -= assert_output.value; + txouts.push(assert_output); } let anchor_output = p2a_output(); total_output_amount -= anchor_output.value; - let output_connector_d = TxOut { + let connector_d_output = TxOut { value: total_output_amount, script_pubkey: connector_d.generate_taproot_address().script_pubkey(), }; - txouts.push(output_connector_d); + txouts.push(connector_d_output); txouts.push(anchor_output); - Ok(AssertInitTransaction { + Ok(OperatorAssertTransaction { tx: Transaction { version: bitcoin::transaction::Version(2), lock_time: absolute::LockTime::ZERO, @@ -191,35 +109,144 @@ impl AssertInitTransaction { }) } - pub fn sign_input_0(&mut self, context: &OperatorContext, connector_c: &ConnectorC) { + pub fn operator_commit_proof( + &mut self, + wots_sk: &OperatorAssertSecretKey, + connector_c: &ConnectorC, + proof: &[u8; 64], + ) -> Result<(), Error> { let input_index = 0; - pre_sign_taproot_input_default( - self, + let leaf_index = 1; + match connector_c.generate_leaf_1_unlock_data(wots_sk, proof) { + Ok(wit) => { + populate_taproot_input_witness( + self.tx_mut(), + input_index, + &connector_c.generate_taproot_spend_info(), + &connector_c.generate_taproot_leaf_script(leaf_index), + wit, + ); + } + Err(e) => return Err(e), + } + Ok(()) + } +} + +impl BaseTransaction for OperatorAssertTransaction { + fn finalize(&self) -> Transaction { + self.tx.clone() + } + + fn name(&self) -> &'static str { + "OperatorAssert" + } +} + +#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] +pub struct VerifierAssertTransaction { + #[serde(with = "consensus::serde::With::")] + tx: Transaction, + #[serde(with = "consensus::serde::With::")] + prev_outs: Vec, + prev_scripts: Vec, +} + +impl PreSignedTransaction for VerifierAssertTransaction { + fn tx(&self) -> &Transaction { + &self.tx + } + + fn tx_mut(&mut self) -> &mut Transaction { + &mut self.tx + } + + fn prev_outs(&self) -> &Vec { + &self.prev_outs + } + + fn prev_scripts(&self) -> &Vec { + &self.prev_scripts + } +} + +impl VerifierAssertTransaction { + pub fn new_for_validation( + verifier_connector: &VerifierConnector, + prover_connector: &ProverConnector, + input_0: Input, + ) -> Result { + let input_0_leaf = 0; + let _input_0 = verifier_connector.generate_taproot_leaf_tx_in(input_0_leaf, &input_0); + + if input_0.amount < Amount::from_sat(verifier_assert_input_amount()) { + return Err(Error::Transaction(InsufficientInputAmount)); + } + + let output_0 = TxOut { + value: Amount::from_sat(verifier_assert_prover_output_amount()), + script_pubkey: prover_connector.generate_taproot_address().script_pubkey(), + }; + + let output_1 = p2a_output(); + + Ok(VerifierAssertTransaction { + tx: Transaction { + version: bitcoin::transaction::Version(2), + lock_time: absolute::LockTime::ZERO, + input: vec![_input_0], + output: vec![output_0, output_1], + }, + prev_outs: vec![TxOut { + value: input_0.amount, + script_pubkey: verifier_connector + .generate_taproot_address() + .script_pubkey(), + }], + prev_scripts: vec![verifier_connector.generate_taproot_leaf_script(input_0_leaf)], + }) + } + + pub fn verifier_publish_labels( + &mut self, + verifier_connector: &VerifierConnector, + labels: [Label; INPUT_WIRE_NUM], + operator_assertion: &RawWitness, + ) -> Result<(), Error> { + let input_index = 0; + let leaf_index = 0; + let wit = verifier_connector.generate_leaf_0_unlock_data(labels, operator_assertion)?; + populate_taproot_input_witness( + self.tx_mut(), input_index, - TapSighashType::All, - connector_c.generate_taproot_spend_info(), - &vec![&context.operator_keypair], + &verifier_connector.generate_taproot_spend_info(), + &verifier_connector.generate_taproot_leaf_script(leaf_index), + wit, ); + Ok(()) } } -impl BaseTransaction for AssertInitTransaction { + +impl BaseTransaction for VerifierAssertTransaction { fn finalize(&self) -> Transaction { self.tx.clone() } + fn name(&self) -> &'static str { - "AssertInit" + "VerifierAssert" } } #[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] -pub struct AssertCommitTimeoutTransaction { +pub struct DisproveTransaction { #[serde(with = "consensus::serde::With::")] tx: Transaction, #[serde(with = "consensus::serde::With::")] prev_outs: Vec, prev_scripts: Vec, } -impl PreSignedTransaction for AssertCommitTimeoutTransaction { + +impl PreSignedTransaction for DisproveTransaction { fn tx(&self) -> &Transaction { &self.tx } @@ -236,34 +263,36 @@ impl PreSignedTransaction for AssertCommitTimeoutTransaction { &self.prev_scripts } } -impl AssertCommitTimeoutTransaction { + +impl DisproveTransaction { pub fn new_for_validation( - assert_commit_connector: &AssertCommitConnector, + prover_connector: &ProverConnector, connector_d: &ConnectorD, - input_0: &Input, - input_1: &Input, - ) -> Self { + input_0: Input, + input_1: Input, + _final_msg: Vec, + ) -> Result { let input_0_leaf = 1; - let _input_0 = assert_commit_connector.generate_taproot_leaf_tx_in(input_0_leaf, &input_0); + let _input_0 = prover_connector.generate_taproot_leaf_tx_in(input_0_leaf, &input_0); let input_1_leaf = 1; let _input_1 = connector_d.generate_taproot_leaf_tx_in(input_1_leaf, &input_1); - let output_0 = p2a_output(); + if input_0.amount + input_1.amount < Amount::from_sat(disprove_input_amount()) { + return Err(Error::Transaction(InsufficientInputAmount)); + } - AssertCommitTimeoutTransaction { + Ok(DisproveTransaction { tx: Transaction { version: bitcoin::transaction::Version(2), lock_time: absolute::LockTime::ZERO, input: vec![_input_0, _input_1], - output: vec![output_0], + output: vec![p2a_output()], }, prev_outs: vec![ TxOut { value: input_0.amount, - script_pubkey: assert_commit_connector - .generate_taproot_address() - .script_pubkey(), + script_pubkey: prover_connector.generate_taproot_address().script_pubkey(), }, TxOut { value: input_1.amount, @@ -271,22 +300,22 @@ impl AssertCommitTimeoutTransaction { }, ], prev_scripts: vec![ - assert_commit_connector.generate_taproot_leaf_script(input_0_leaf), + prover_connector.generate_taproot_leaf_script(input_0_leaf), connector_d.generate_taproot_leaf_script(input_1_leaf), ], - } + }) } fn sign_input_0_musig2( &mut self, - context: &VerifierContext, + context: &CommitteeContext, sec_nonce: &SecNonce, agg_nonce: &AggNonce, ) -> Result { let input_index = 0; - let sighash_type = TapSighashType::All; + let sighash_type = TapSighashType::None; generate_taproot_partial_signature( - &context, + context, self.tx(), sec_nonce, agg_nonce, @@ -299,12 +328,12 @@ impl AssertCommitTimeoutTransaction { fn push_input_0_signature( &mut self, - assert_commit_connector: &AssertCommitConnector, + prover_connector: &ProverConnector, input_0_sig: bitcoin::taproot::Signature, ) { let input_index = 0; let script = self.prev_scripts()[input_index].clone(); - let spend_info = assert_commit_connector.generate_taproot_spend_info(); + let spend_info = prover_connector.generate_taproot_spend_info(); let tx_mut = self.tx_mut(); // Push signature to witness tx_mut.input[input_index] @@ -322,14 +351,14 @@ impl AssertCommitTimeoutTransaction { fn sign_input_1_musig2( &mut self, - context: &VerifierContext, + context: &CommitteeContext, sec_nonce: &SecNonce, agg_nonce: &AggNonce, ) -> Result { let input_index = 1; - let sighash_type = TapSighashType::All; + let sighash_type = TapSighashType::None; generate_taproot_partial_signature( - &context, + context, self.tx(), sec_nonce, agg_nonce, @@ -365,7 +394,7 @@ impl AssertCommitTimeoutTransaction { pub fn pre_sign( &mut self, - context: &VerifierContext, + context: &CommitteeContext, sec_nonces: &[SecNonce; 2], agg_nonces: &[AggNonce; 2], ) -> Result<[PartialSignature; 2], SigningError> { @@ -390,7 +419,7 @@ impl AssertCommitTimeoutTransaction { { let input_index = 0; let sig_index = 0; - let sighash_type = TapSighashType::All; + let sighash_type = TapSighashType::None; input_0_sig = match generate_taproot_aggregated_signature( context, self.tx(), @@ -411,7 +440,7 @@ impl AssertCommitTimeoutTransaction { { let input_index = 1; let sig_index = 1; - let sighash_type = TapSighashType::All; + let sighash_type = TapSighashType::None; input_1_sig = match generate_taproot_aggregated_signature( context, self.tx(), @@ -434,98 +463,50 @@ impl AssertCommitTimeoutTransaction { pub fn push_pre_sigs( &mut self, - assert_commit_connector: &AssertCommitConnector, + prover_connector: &ProverConnector, connector_d: &ConnectorD, pre_sigs: [bitcoin::taproot::Signature; 2], ) { - self.push_input_0_signature(assert_commit_connector, pre_sigs[0].clone()); - self.push_input_1_signature(connector_d, pre_sigs[1].clone()); + self.push_input_0_signature(prover_connector, pre_sigs[0]); + self.push_input_1_signature(connector_d, pre_sigs[1]); } } -impl BaseTransaction for AssertCommitTimeoutTransaction { + +impl BaseTransaction for DisproveTransaction { fn finalize(&self) -> Transaction { self.tx.clone() } + fn name(&self) -> &'static str { - "AssertCommitTimeout" + "Disprove" } } -#[test] -fn test_operator_commit_proof() { - use crate::connectors::assert_connectors::generate_chunked_assert_commit_connectors; - use crate::disprove_scripts::NUM_GUEST_PUBS_ASSERT; - use bitcoin::{Network, XOnlyPublicKey}; - use bitvm::chunk::api::{NUM_HASH, NUM_PUBS, NUM_U256}; - use bitvm::signatures::{WinternitzSecret, Wots, Wots16, Wots32}; - use std::str::FromStr; - let test_assertions: AssertAssertions = ( - [[0u8; 32]; NUM_GUEST_PUBS_ASSERT], - ( - [[1u8; 32]; NUM_PUBS], - [[2u8; 32]; NUM_U256], - [[3u8; 16]; NUM_HASH], - ), - ); - let guest_privkeys = (0..NUM_GUEST_PUBS_ASSERT) - .map(|_| Wots32::generate_secret_key()) - .collect::>(); - let pub_privkeys = (0..NUM_PUBS) - .map(|_| Wots32::generate_secret_key()) - .collect::>(); - let u32_privkeys = (0..NUM_U256) - .map(|_| Wots32::generate_secret_key()) - .collect::>(); - let hash_privkeys = (0..NUM_HASH) - .map(|_| Wots16::generate_secret_key()) - .collect::>(); - let guest_pubkeys = guest_privkeys - .iter() - .map(|sk| Wots32::generate_public_key(sk)) - .collect::::PublicKey>>(); - let pub_pubkeys = pub_privkeys - .iter() - .map(|sk| Wots32::generate_public_key(sk)) - .collect::::PublicKey>>(); - let u32_pubkeys = u32_privkeys - .iter() - .map(|sk| Wots32::generate_public_key(sk)) - .collect::::PublicKey>>(); - let hash_pubkeys = hash_privkeys - .iter() - .map(|sk| Wots16::generate_public_key(sk)) - .collect::::PublicKey>>(); - let assert_commit_connector = generate_chunked_assert_commit_connectors( - Network::Regtest, - &XOnlyPublicKey::from_slice(&[2u8; 32]).unwrap(), - ( - guest_pubkeys.try_into().unwrap(), - ( - pub_pubkeys.try_into().unwrap(), - u32_pubkeys.try_into().unwrap(), - hash_pubkeys.try_into().unwrap(), - ), - ), - ); - let test_input = Input { - outpoint: bitcoin::OutPoint { - txid: bitcoin::Txid::from_str( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap(), - vout: 0, - }, - amount: bitcoin::Amount::from_sat(0), +pub fn wrongly_challenged( + prover_connector: &ProverConnector, + input_0: &Input, + final_msg: &[u8], +) -> Result { + let leaf_index = 0; + let unlock_data = vec![final_msg.to_owned()]; + let witness_script = script! { + { unlock_data.clone() } }; - let test_inputs = std::iter::repeat(test_input) - .take(assert_commit_connector.len()) - .collect::>(); - let res = operator_commit_proof( - &assert_commit_connector, - &[guest_privkeys, pub_privkeys, u32_privkeys, hash_privkeys].concat(), - &test_inputs, - &test_assertions, - ) - .unwrap(); - println!("num txins: {:?}", res.len()); + let script = prover_connector.generate_taproot_leaf_script(leaf_index); + let verification_script = witness_script.push_script(script.clone()); + let exec_result = execute_script(verification_script); + if !exec_result.success { + return Err(Error::Other( + "Invalid hashlock preimage for ProverConnector.", + )); + } + + let mut txin = prover_connector.generate_taproot_leaf_tx_in(leaf_index, input_0); + populate_taproot_txin_witness( + &mut txin, + &prover_connector.generate_taproot_spend_info(), + &script, + unlock_data, + ); + Ok(txin) } diff --git a/goat/src/transactions/base.rs b/goat/src/transactions/base.rs index 0722fb041..f6a945486 100644 --- a/goat/src/transactions/base.rs +++ b/goat/src/transactions/base.rs @@ -21,24 +21,57 @@ pub const ACCELERATE_FEE_MULTIPLIER: u64 = 2; pub const MIN_RELAY_FEE_KICKOFF: u64 = relay_fee(500); pub const MIN_RELAY_FEE_TAKE_1: u64 = relay_fee(500); pub const MIN_RELAY_FEE_TAKE_2: u64 = relay_fee(500); +pub const MIN_RELAY_FEE_VERIFIER_ASSERT: u64 = relay_fee(60000); +pub const MIN_RELAY_FEE_WRONGLY_CHALLENGED: u64 = relay_fee(500); +pub const MIN_RELAY_FEE_DISPROVE: u64 = relay_fee(800); +pub const P2A_AMOUNT: u64 = 240; pub const fn min_relay_fee_watchtower_challenge_init(watchtower_num: usize) -> u64 { - relay_fee(watchtower_num * 200 + 500) + relay_fee(watchtower_num * 100 + 200) } -pub const fn min_relay_fee_assert_init(num_assert_commits: usize) -> u64 { - relay_fee(num_assert_commits * 100 + 300) +pub const fn min_relay_fee_operator_assert(num_verifier: usize) -> u64 { + relay_fee(num_verifier * 100 + 15000) } -pub const fn max_assert_cost(num_assert_commits: usize) -> u64 { - min_relay_fee_assert_init(num_assert_commits) + (num_assert_commits + 2) as u64 * DUST_AMOUNT +pub const fn wrongly_challenged_input_amount() -> u64 { + MIN_RELAY_FEE_WRONGLY_CHALLENGED + P2A_AMOUNT +} +pub const fn verifier_assert_prover_output_amount() -> u64 { + max(DUST_AMOUNT, wrongly_challenged_input_amount()) +} +pub const fn verifier_assert_input_amount() -> u64 { + MIN_RELAY_FEE_VERIFIER_ASSERT + verifier_assert_prover_output_amount() + P2A_AMOUNT +} +pub const fn disprove_input_amount() -> u64 { + MIN_RELAY_FEE_DISPROVE + P2A_AMOUNT +} +pub const fn connector_d_assert_output_amount() -> u64 { + if disprove_input_amount() > verifier_assert_prover_output_amount() { + max( + DUST_AMOUNT, + disprove_input_amount() - verifier_assert_prover_output_amount(), + ) + } else { + DUST_AMOUNT + } +} +pub const fn operator_assert_input_amount(num_verifier: usize) -> u64 { + min_relay_fee_operator_assert(num_verifier) + + num_verifier as u64 * verifier_assert_input_amount() + + connector_d_assert_output_amount() + + P2A_AMOUNT +} +pub const fn max_assert_cost(num_verifier: usize) -> u64 { + operator_assert_input_amount(num_verifier) } pub const fn max_watchtower_challenge_cost(num_watchtowers: usize) -> u64 { min_relay_fee_watchtower_challenge_init(num_watchtowers) - + (num_watchtowers * 2 + 3) as u64 * DUST_AMOUNT + + num_watchtowers as u64 * DUST_AMOUNT + + P2A_AMOUNT } -pub const fn max_pegout_cost(num_watchtowers: usize, num_assert_commits: usize) -> u64 { - max_assert_cost(num_assert_commits) +pub const fn max_pegout_cost(num_watchtowers: usize, num_verifier: usize) -> u64 { + max_assert_cost(num_verifier) + max_watchtower_challenge_cost(num_watchtowers) + MIN_RELAY_FEE_KICKOFF - + DUST_AMOUNT * 4 + + DUST_AMOUNT * 2 } #[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] @@ -223,10 +256,10 @@ mod tests { const DUMMY_TXID: &str = "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456"; - fn get_test_nonces() -> ( - HashMap>, - HashMap>, - ) { + type TestNonces = HashMap>; + type TestNonceSignatures = HashMap>; + + fn get_test_nonces() -> (TestNonces, TestNonceSignatures) { const SIGNERS: usize = 3; const INPUTS: usize = 4; diff --git a/goat/src/transactions/challenge.rs b/goat/src/transactions/challenge.rs index 2f4bf11fb..b0705dc9e 100644 --- a/goat/src/transactions/challenge.rs +++ b/goat/src/transactions/challenge.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::{ connectors::base::TaprootConnector, - contexts::{base::BaseContext, verifier::VerifierContext}, + contexts::{base::BaseContext, committee::CommitteeContext}, error::Error, transactions::{ signing::push_taproot_leaf_script_and_control_block_to_witness, @@ -85,14 +85,14 @@ impl ChallengeTransaction { fn sign_input_0_musig2( &mut self, - context: &VerifierContext, + context: &CommitteeContext, sec_nonce: &SecNonce, agg_nonce: &AggNonce, ) -> Result { let input_index = 0; let sighash_type = TapSighashType::SinglePlusAnyoneCanPay; generate_taproot_partial_signature( - &context, + context, self.tx(), sec_nonce, agg_nonce, @@ -128,7 +128,7 @@ impl ChallengeTransaction { pub fn pre_sign( &mut self, - context: &VerifierContext, + context: &CommitteeContext, sec_nonces: &[SecNonce; 1], agg_nonces: &[AggNonce; 1], ) -> Result<[PartialSignature; 1], SigningError> { @@ -175,7 +175,7 @@ impl ChallengeTransaction { connector_a: &ConnectorA, pre_sigs: [bitcoin::taproot::Signature; 1], ) { - self.push_input_0_signature(connector_a, pre_sigs[0].clone()); + self.push_input_0_signature(connector_a, pre_sigs[0]); } } diff --git a/goat/src/transactions/disprove.rs b/goat/src/transactions/disprove.rs deleted file mode 100644 index b65e9089e..000000000 --- a/goat/src/transactions/disprove.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::connectors::base::generate_default_tx_in; -use crate::disprove_scripts::{ - utils_signatures_from_raw_witnesses, validate_guest_assertions, GUEST_VALIDATION_TAPS, -}; -use crate::{error::Error, transactions::base::Input}; -use ark_bn254::Bn254; -use ark_groth16::VerifyingKey; -use bitcoin::taproot::LeafVersion; -use bitcoin::{taproot::TaprootSpendInfo, ScriptBuf, TxIn}; -use bitvm::chunk::api::type_conversion_utils::script_to_witness; -use bitvm::chunk::api::validate_assertions_lit; -use bitvm::chunk::api::{type_conversion_utils::RawWitness, NUM_TAPS}; - -pub fn validate_assert( - raw_commit_blockhash_witness: Vec, - raw_assert_witness: Vec, - ack_preimages: Vec>, // preimages length should match the number of hashes, use empty vec for unknown preimages - guest_validation_scripts: &[ScriptBuf; GUEST_VALIDATION_TAPS], - vk: &VerifyingKey, - proof_validation_scripts: &[ScriptBuf; NUM_TAPS], -) -> Option<(RawWitness, ScriptBuf)> { - let mut raw_wits = raw_commit_blockhash_witness; - raw_wits.extend(raw_assert_witness); - let wots_sigs = utils_signatures_from_raw_witnesses(&raw_wits); - - if let Some((index, wit)) = validate_guest_assertions( - &wots_sigs.0, - &wots_sigs.1 .0.clone(), - &ack_preimages, - guest_validation_scripts, - ) { - return Some(( - script_to_witness(wit), - guest_validation_scripts[index].clone(), - )); - }; - if let Some((index, wit)) = validate_assertions_lit(vk, wots_sigs.1, proof_validation_scripts) { - return Some(( - script_to_witness(wit), - proof_validation_scripts[index].clone(), - )); - }; - None -} - -pub fn disprove( - connector_e_taproot_spend_info: &TaprootSpendInfo, - connector_e_input: &Input, - input_script_witness: RawWitness, - input_lock_script: ScriptBuf, -) -> Result { - let mut txin = generate_default_tx_in(connector_e_input); - // push witness - input_script_witness - .into_iter() - .for_each(|x| txin.witness.push(x)); - // push script and control block - let prevout_leaf = (input_lock_script, LeafVersion::TapScript); - let control_block = match connector_e_taproot_spend_info.control_block(&prevout_leaf) { - Some(c) => c, - _ => { - return Err(Error::Other( - "Unable to generate control block for disprove txin", - )) - } - }; - txin.witness.push(prevout_leaf.0.to_bytes()); - txin.witness.push(control_block.serialize()); - Ok(txin) -} diff --git a/goat/src/transactions/kickoff.rs b/goat/src/transactions/kickoff.rs index 442932852..7e2ec0508 100644 --- a/goat/src/transactions/kickoff.rs +++ b/goat/src/transactions/kickoff.rs @@ -7,7 +7,6 @@ use crate::{ connector_a::ConnectorA, connector_b::ConnectorB, connector_c::ConnectorC, - connector_e::ConnectorE, kickoff_connectors::{GuardianConnector, KickoffConnector}, }, contexts::operator::OperatorContext, @@ -48,19 +47,19 @@ impl PreSignedTransaction for KickoffTransaction { } } impl KickoffTransaction { + #[allow(clippy::too_many_arguments)] pub fn new_for_validation( kickoff_connector: &KickoffConnector, connector_a: &ConnectorA, connector_b: &ConnectorB, connector_c: &ConnectorC, - connector_e: &ConnectorE, guardian_connector: &GuardianConnector, input_0: &Input, watchtower_num: usize, assert_commit_num: usize, ) -> Result { let input_0_leaf = 0; - let _input_0 = kickoff_connector.generate_taproot_leaf_tx_in(input_0_leaf, &input_0); + let _input_0 = kickoff_connector.generate_taproot_leaf_tx_in(input_0_leaf, input_0); if input_0.amount < Amount::from_sat( @@ -81,25 +80,20 @@ impl KickoffTransaction { value: Amount::from_sat(max_watchtower_challenge_cost(watchtower_num)), script_pubkey: connector_b.generate_taproot_address().script_pubkey(), }; - let output_2 = TxOut { - value: Amount::from_sat(max_assert_cost(assert_commit_num)), - script_pubkey: connector_c.generate_taproot_address().script_pubkey(), - }; - let output_4 = TxOut { + let output_3 = TxOut { value: Amount::from_sat(DUST_AMOUNT), script_pubkey: guardian_connector .generate_taproot_address() .script_pubkey(), }; let anchor_output = p2a_output(); - let output_3 = TxOut { + let output_2 = TxOut { value: total_output_amount - output_0.value - output_1.value - - output_2.value - - output_4.value + - output_3.value - anchor_output.value, - script_pubkey: connector_e.generate_taproot_address().script_pubkey(), + script_pubkey: connector_c.generate_taproot_address().script_pubkey(), }; Ok(KickoffTransaction { @@ -107,14 +101,7 @@ impl KickoffTransaction { version: bitcoin::transaction::Version(2), lock_time: absolute::LockTime::ZERO, input: vec![_input_0], - output: vec![ - output_0, - output_1, - output_2, - output_3, - output_4, - anchor_output, - ], + output: vec![output_0, output_1, output_2, output_3, anchor_output], }, prev_outs: vec![TxOut { value: input_0.amount, diff --git a/goat/src/transactions/mod.rs b/goat/src/transactions/mod.rs index ad4c6c66f..1b0e0408c 100644 --- a/goat/src/transactions/mod.rs +++ b/goat/src/transactions/mod.rs @@ -1,7 +1,6 @@ pub mod assert; pub mod base; pub mod challenge; -pub mod disprove; pub mod kickoff; pub mod pegin; pub mod pre_signed; diff --git a/goat/src/transactions/pegin.rs b/goat/src/transactions/pegin.rs index 1af4a944a..9fa893fb8 100644 --- a/goat/src/transactions/pegin.rs +++ b/goat/src/transactions/pegin.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::{ connectors::connector_0::Connector0, - contexts::{base::BaseContext, verifier::VerifierContext}, + contexts::{base::BaseContext, committee::CommitteeContext}, error::{Error, TransactionError::InsufficientInputAmount}, scripts::generate_opreturn_script, transactions::{ @@ -272,14 +272,14 @@ impl PegInConfirmTransaction { pub fn sign_input_0_musig2( &mut self, - context: &VerifierContext, + context: &CommitteeContext, sec_nonce: &SecNonce, agg_nonce: &AggNonce, ) -> Result { let input_index = 0; let sighash_type = TapSighashType::All; generate_taproot_partial_signature( - &context, + context, self.tx(), sec_nonce, agg_nonce, @@ -312,7 +312,7 @@ impl PegInConfirmTransaction { signature: sig.into(), sighash_type, }), - Err(_) => return Err(Error::Other("Failed to aggregate signatures")), + Err(_) => Err(Error::Other("Failed to aggregate signatures")), } } diff --git a/goat/src/transactions/pre_signed_musig2.rs b/goat/src/transactions/pre_signed_musig2.rs index 0d99eb723..d6f3fb5bd 100644 --- a/goat/src/transactions/pre_signed_musig2.rs +++ b/goat/src/transactions/pre_signed_musig2.rs @@ -12,7 +12,7 @@ use musig2::{ use std::collections::HashMap; use super::{ - super::contexts::{base::BaseContext, verifier::VerifierContext}, + super::contexts::{base::BaseContext, committee::CommitteeContext}, super::error::Error, pre_signed::PreSignedTransaction, signing::push_taproot_leaf_script_and_control_block_to_witness, @@ -64,14 +64,14 @@ pub trait PreSignedMusig2Transaction: PreSignedTransaction { }) } - fn push_nonces(&mut self, context: &VerifierContext) -> HashMap { + fn push_nonces(&mut self, context: &CommitteeContext) -> HashMap { self.verifier_inputs() .iter() .map(|input_index| (*input_index, self.push_nonce(context, *input_index))) .collect() } - fn push_nonce(&mut self, context: &VerifierContext, input_index: usize) -> SecNonce { + fn push_nonce(&mut self, context: &CommitteeContext, input_index: usize) -> SecNonce { // Push nonce let musig2_nonces = self.musig2_nonces_mut(); if musig2_nonces.get(&input_index).is_none() { @@ -82,7 +82,7 @@ pub trait PreSignedMusig2Transaction: PreSignedTransaction { musig2_nonces .get_mut(&input_index) .unwrap() - .insert(context.verifier_public_key, secret_nonce.public_nonce()); + .insert(context.committee_public_key, secret_nonce.public_nonce()); // Sign the nonce and push the signature let musig2_nonce_signatures = self.musig2_nonce_signatures_mut(); @@ -91,13 +91,13 @@ pub trait PreSignedMusig2Transaction: PreSignedTransaction { } let nonce_signature = context - .verifier_keypair + .committee_keypair .sign_schnorr(get_nonce_message(&secret_nonce.public_nonce())); musig2_nonce_signatures .get_mut(&input_index) .unwrap() - .insert(context.verifier_public_key, nonce_signature); + .insert(context.committee_public_key, nonce_signature); secret_nonce } @@ -140,7 +140,7 @@ pub trait PreSignedMusig2Transaction: PreSignedTransaction { musig2_nonce_signatures .get_mut(&index) .unwrap() - .insert(*verifier_pubkey, nonce_signature.clone()); + .insert(*verifier_pubkey, *nonce_signature); } None } @@ -194,7 +194,7 @@ pub fn verify_public_nonce(sig: &Signature, nonce: &PubNonce, pubkey: &XOnlyPubl pub fn pre_sign_musig2_taproot_input( tx: &mut T, - context: &VerifierContext, + context: &CommitteeContext, input_index: usize, sighash_type: TapSighashType, secret_nonce: &SecNonce, @@ -224,7 +224,7 @@ pub fn pre_sign_musig2_taproot_input( diff --git a/goat/src/transactions/prekickoff.rs b/goat/src/transactions/prekickoff.rs index 9189f91cd..1681a1611 100644 --- a/goat/src/transactions/prekickoff.rs +++ b/goat/src/transactions/prekickoff.rs @@ -89,6 +89,7 @@ impl PreSignedTransaction for PrekickoffTransaction { } } impl PrekickoffTransaction { + #[allow(clippy::too_many_arguments)] pub fn new_for_validation( prev_prekickoff_connector: &PrekickoffConnector, force_skip_connector: &ForceSkipConnector, @@ -289,7 +290,7 @@ impl ForceSkipKickoffTransaction { context: &OperatorContext, kickoff_connector: &KickoffConnector, ) { - let input_index = 1; + let input_index = 0; pre_sign_taproot_input_default( self, input_index, @@ -424,7 +425,7 @@ impl QuickChallengeTransaction { context: &OperatorContext, guardian_connector: &GuardianConnector, ) { - let input_index = 1; + let input_index = 0; pre_sign_taproot_input_default( self, input_index, @@ -559,7 +560,7 @@ impl ChallengeIncompleteKickoffTransaction { context: &OperatorContext, guardian_connector: &GuardianConnector, ) { - let input_index = 1; + let input_index = 0; pre_sign_taproot_input_default( self, input_index, diff --git a/goat/src/transactions/signing_musig2.rs b/goat/src/transactions/signing_musig2.rs index b8e70c53c..c7f016ebb 100644 --- a/goat/src/transactions/signing_musig2.rs +++ b/goat/src/transactions/signing_musig2.rs @@ -2,7 +2,7 @@ use bitcoin::{ sighash::{Prevouts, SighashCache}, taproot::LeafVersion, - Script, TapLeafHash, TapSighashType, Transaction, TxOut, + Script, TapLeafHash, TapSighash, TapSighashType, Transaction, TxOut, }; use musig2::{ aggregate_partial_signatures, @@ -11,7 +11,38 @@ use musig2::{ sign_partial, AggNonce, KeyAggContext, LiftedSignature, PartialSignature, PubNonce, SecNonce, }; -use super::super::contexts::{base::BaseContext, verifier::VerifierContext}; +use super::super::contexts::{base::BaseContext, committee::CommitteeContext}; + +fn taproot_script_spend_sighash( + tx: &Transaction, + input_index: usize, + prevouts: &[TxOut], + leaf_hash: TapLeafHash, + sighash_type: TapSighashType, +) -> TapSighash { + if sighash_type == TapSighashType::AllPlusAnyoneCanPay + || sighash_type == TapSighashType::SinglePlusAnyoneCanPay + || sighash_type == TapSighashType::NonePlusAnyoneCanPay + { + SighashCache::new(tx) + .taproot_script_spend_signature_hash( + input_index, + &Prevouts::One(input_index, &prevouts[input_index]), + leaf_hash, + sighash_type, + ) + .expect("Failed to construct sighash") + } else { + SighashCache::new(tx) + .taproot_script_spend_signature_hash( + input_index, + &Prevouts::All(prevouts), + leaf_hash, + sighash_type, + ) + .expect("Failed to construct sighash") + } +} pub fn generate_nonce() -> SecNonce { SecNonce::build(&mut rand::rngs::OsRng).build() @@ -22,7 +53,7 @@ pub fn generate_aggregated_nonce(nonces: &Vec) -> AggNonce { } pub fn generate_taproot_partial_signature( - context: &VerifierContext, + context: &CommitteeContext, tx: &Transaction, secret_nonce: &SecNonce, aggregated_nonce: &AggNonce, @@ -40,18 +71,11 @@ pub fn generate_taproot_partial_signature( let key_agg_ctx = KeyAggContext::new(pubkeys).unwrap(); let leaf_hash = TapLeafHash::from_script(script, LeafVersion::TapScript); - let sighash = SighashCache::new(tx) - .taproot_script_spend_signature_hash( - input_index, - &Prevouts::All(prevouts), // TODO: add Prevouts::One - leaf_hash, - sighash_type, - ) - .expect("Failed to construct sighash"); + let sighash = taproot_script_spend_sighash(tx, input_index, prevouts, leaf_hash, sighash_type); sign_partial( &key_agg_ctx, - context.verifier_keypair.secret_key(), + context.committee_keypair.secret_key(), secret_nonce.clone(), aggregated_nonce, sighash, @@ -77,14 +101,8 @@ pub fn generate_taproot_aggregated_signature( let key_agg_ctx = KeyAggContext::new(pubkeys).unwrap(); let leaf_hash = TapLeafHash::from_script(script, LeafVersion::TapScript); - let sighash_cache = SighashCache::new(tx) - .taproot_script_spend_signature_hash( - input_index, - &Prevouts::All(prevouts), // TODO: add Prevouts::One - leaf_hash, - sighash_type, - ) - .expect("Failed to construct sighash"); + let sighash_cache = + taproot_script_spend_sighash(tx, input_index, prevouts, leaf_hash, sighash_type); aggregate_partial_signatures( &key_agg_ctx, diff --git a/goat/src/transactions/take1.rs b/goat/src/transactions/take1.rs index 1c412d905..d76704e6d 100644 --- a/goat/src/transactions/take1.rs +++ b/goat/src/transactions/take1.rs @@ -19,7 +19,7 @@ use super::{ base::*, connector_0::Connector0, connector_a::ConnectorA, kickoff_connectors::GuardianConnector, }, - contexts::{operator::OperatorContext, verifier::VerifierContext}, + contexts::{committee::CommitteeContext, operator::OperatorContext}, scripts::*, }, base::*, @@ -53,6 +53,7 @@ impl PreSignedTransaction for Take1Transaction { } } impl Take1Transaction { + #[allow(clippy::too_many_arguments)] pub fn new_for_validation( connector_0: &Connector0, connector_a: &ConnectorA, @@ -140,14 +141,14 @@ impl Take1Transaction { fn sign_input_0_musig2( &mut self, - context: &VerifierContext, + context: &CommitteeContext, sec_nonce: &SecNonce, agg_nonce: &AggNonce, ) -> Result { let input_index = 0; let sighash_type = TapSighashType::All; generate_taproot_partial_signature( - &context, + context, self.tx(), sec_nonce, agg_nonce, @@ -181,14 +182,58 @@ impl Take1Transaction { ); } + fn sign_input_3_musig2( + &mut self, + context: &CommitteeContext, + sec_nonce: &SecNonce, + agg_nonce: &AggNonce, + ) -> Result { + let input_index = 3; + let sighash_type = TapSighashType::All; + generate_taproot_partial_signature( + context, + self.tx(), + sec_nonce, + agg_nonce, + input_index, + self.prev_outs(), + &self.prev_scripts()[input_index], + sighash_type, + ) + } + + fn push_input_3_signature( + &mut self, + connector_c: &ConnectorC, + input_3_sig: bitcoin::taproot::Signature, + ) { + let input_index = 3; + let script = self.prev_scripts()[input_index].clone(); + let spend_info = connector_c.generate_taproot_spend_info(); + let tx_mut = self.tx_mut(); + // Push signature to witness + tx_mut.input[input_index] + .witness + .push(input_3_sig.serialize()); + + // Push script + control block + push_taproot_leaf_script_and_control_block_to_witness( + tx_mut, + input_index, + &spend_info, + &script, + ); + } + pub fn pre_sign( &mut self, - context: &VerifierContext, - sec_nonces: &[SecNonce; 1], - agg_nonces: &[AggNonce; 1], - ) -> Result<[PartialSignature; 1], SigningError> { + context: &CommitteeContext, + sec_nonces: &[SecNonce; 2], + agg_nonces: &[AggNonce; 2], + ) -> Result<[PartialSignature; 2], SigningError> { let input_0_sig = self.sign_input_0_musig2(context, &sec_nonces[0], &agg_nonces[0]); - match [input_0_sig] + let input_3_sig = self.sign_input_3_musig2(context, &sec_nonces[1], &agg_nonces[1]); + match [input_0_sig, input_3_sig] .into_iter() .collect::, SigningError>>() { @@ -200,37 +245,63 @@ impl Take1Transaction { pub fn aggregate_pre_sigs( &self, context: &dyn BaseContext, - partial_signatures: &[Vec; 1], - agg_nonces: &[AggNonce; 1], - ) -> Result<[bitcoin::taproot::Signature; 1], Error> { - let input_index = 0; - let sig_index = 0; - let sighash_type = TapSighashType::All; - let input_0_sig = match generate_taproot_aggregated_signature( - context, - self.tx(), - &agg_nonces[sig_index], - input_index, - self.prev_outs(), - &self.prev_scripts()[input_index], - sighash_type, - partial_signatures[sig_index].clone(), - ) { - Ok(sig) => bitcoin::taproot::Signature { - signature: sig.into(), + partial_signatures: &[Vec; 2], + agg_nonces: &[AggNonce; 2], + ) -> Result<[bitcoin::taproot::Signature; 2], Error> { + let (input_0_sig, input_3_sig); + { + let input_index = 0; + let sig_index = 0; + let sighash_type = TapSighashType::All; + input_0_sig = match generate_taproot_aggregated_signature( + context, + self.tx(), + &agg_nonces[sig_index], + input_index, + self.prev_outs(), + &self.prev_scripts()[input_index], sighash_type, - }, - Err(_) => return Err(Error::Other("Failed to aggregate signatures")), - }; - Ok([input_0_sig]) + partial_signatures[sig_index].clone(), + ) { + Ok(sig) => bitcoin::taproot::Signature { + signature: sig.into(), + sighash_type, + }, + Err(_) => return Err(Error::Other("Failed to aggregate signatures")), + }; + } + { + let input_index = 3; + let sig_index = 1; + let sighash_type = TapSighashType::All; + input_3_sig = match generate_taproot_aggregated_signature( + context, + self.tx(), + &agg_nonces[sig_index], + input_index, + self.prev_outs(), + &self.prev_scripts()[input_index], + sighash_type, + partial_signatures[sig_index].clone(), + ) { + Ok(sig) => bitcoin::taproot::Signature { + signature: sig.into(), + sighash_type, + }, + Err(_) => return Err(Error::Other("Failed to aggregate signatures")), + }; + } + Ok([input_0_sig, input_3_sig]) } pub fn push_pre_sigs( &mut self, connector_0: &Connector0, - pre_sigs: [bitcoin::taproot::Signature; 1], + connector_c: &ConnectorC, + pre_sigs: [bitcoin::taproot::Signature; 2], ) { - self.push_input_0_signature(connector_0, pre_sigs[0].clone()); + self.push_input_0_signature(connector_0, pre_sigs[0]); + self.push_input_3_signature(connector_c, pre_sigs[1]); } pub fn sign_input_1(&mut self, context: &OperatorContext, connector_a: &ConnectorA) { @@ -255,17 +326,6 @@ impl Take1Transaction { ); } - pub fn sign_input_3(&mut self, context: &OperatorContext, connector_c: &ConnectorC) { - let input_index = 3; - pre_sign_taproot_input_default( - self, - input_index, - TapSighashType::All, - connector_c.generate_taproot_spend_info(), - &vec![&context.operator_keypair], - ); - } - pub fn sign_input_4( &mut self, context: &OperatorContext, diff --git a/goat/src/transactions/take2.rs b/goat/src/transactions/take2.rs index 597e637fa..c1098a97c 100644 --- a/goat/src/transactions/take2.rs +++ b/goat/src/transactions/take2.rs @@ -12,10 +12,10 @@ use crate::{ use super::{ super::{ connectors::{ - base::*, connector_0::Connector0, connector_d::ConnectorD, connector_e::ConnectorE, - connector_f::ConnectorF, kickoff_connectors::GuardianConnector, + base::*, connector_0::Connector0, connector_d::ConnectorD, + kickoff_connectors::GuardianConnector, }, - contexts::{operator::OperatorContext, verifier::VerifierContext}, + contexts::{committee::CommitteeContext, operator::OperatorContext}, error::{Error, TransactionError::InsufficientInputAmount}, scripts::*, transactions::signing_musig2::generate_taproot_partial_signature, @@ -56,14 +56,10 @@ impl Take2Transaction { pub fn new_for_validation( connector_0: &Connector0, connector_d: &ConnectorD, - connector_e: &ConnectorE, - connector_f: &ConnectorF, guardian_connector: &GuardianConnector, input_0: Input, input_1: Input, input_2: Input, - input_3: Input, - input_4: Input, operator_address: &Address, ) -> Result { let input_0_leaf = 0; @@ -72,16 +68,10 @@ impl Take2Transaction { let input_1_leaf = 0; let _input_1 = connector_d.generate_taproot_leaf_tx_in(input_1_leaf, &input_1); - let _input_2 = generate_default_tx_in(&input_2); + let input_2_leaf = 0; + let _input_2 = guardian_connector.generate_taproot_leaf_tx_in(input_2_leaf, &input_2); - let input_3_leaf = 0; - let _input_3 = connector_f.generate_taproot_leaf_tx_in(input_3_leaf, &input_3); - - let input_4_leaf = 0; - let _input_4 = guardian_connector.generate_taproot_leaf_tx_in(input_4_leaf, &input_4); - - let total_input_amount = - input_0.amount + input_1.amount + input_2.amount + input_3.amount + input_4.amount; + let total_input_amount = input_0.amount + input_1.amount + input_2.amount; if total_input_amount < (Amount::from_sat(MIN_RELAY_FEE_TAKE_2 + 2 * DUST_AMOUNT)) { return Err(Error::Transaction(InsufficientInputAmount)); @@ -100,7 +90,7 @@ impl Take2Transaction { tx: Transaction { version: bitcoin::transaction::Version(2), lock_time: absolute::LockTime::ZERO, - input: vec![_input_0, _input_1, _input_2, _input_3, _input_4], + input: vec![_input_0, _input_1, _input_2], output: vec![output_0, output_1], }, prev_outs: vec![ @@ -114,14 +104,6 @@ impl Take2Transaction { }, TxOut { value: input_2.amount, - script_pubkey: connector_e.generate_taproot_address().script_pubkey(), - }, - TxOut { - value: input_3.amount, - script_pubkey: connector_f.generate_taproot_address().script_pubkey(), - }, - TxOut { - value: input_4.amount, script_pubkey: guardian_connector .generate_taproot_address() .script_pubkey(), @@ -130,23 +112,21 @@ impl Take2Transaction { prev_scripts: vec![ connector_0.generate_taproot_leaf_script(input_0_leaf), connector_d.generate_taproot_leaf_script(input_1_leaf), - ScriptBuf::default(), // No `input_2` script - key spend path is used - connector_f.generate_taproot_leaf_script(input_3_leaf), - guardian_connector.generate_taproot_leaf_script(input_4_leaf), + guardian_connector.generate_taproot_leaf_script(input_2_leaf), ], }) } fn sign_input_0_musig2( &mut self, - context: &VerifierContext, + context: &CommitteeContext, sec_nonce: &SecNonce, agg_nonce: &AggNonce, ) -> Result { let input_index = 0; let sighash_type = TapSighashType::All; generate_taproot_partial_signature( - &context, + context, self.tx(), sec_nonce, agg_nonce, @@ -182,7 +162,7 @@ impl Take2Transaction { pub fn pre_sign( &mut self, - context: &VerifierContext, + context: &CommitteeContext, sec_nonces: &[SecNonce; 1], agg_nonces: &[AggNonce; 1], ) -> Result<[PartialSignature; 1], SigningError> { @@ -229,7 +209,7 @@ impl Take2Transaction { connector_0: &Connector0, pre_sigs: [bitcoin::taproot::Signature; 1], ) { - self.push_input_0_signature(connector_0, pre_sigs[0].clone()); + self.push_input_0_signature(connector_0, pre_sigs[0]); } pub fn sign_input_1(&mut self, context: &OperatorContext, connector_d: &ConnectorD) { @@ -243,38 +223,12 @@ impl Take2Transaction { ); } - pub fn sign_input_2(&mut self, context: &OperatorContext, connector_e: &ConnectorE) { - let input_index = 2; - let prev_outs = &self.prev_outs().clone(); - let merkle_root = connector_e.taproot_merkle_root; - - populate_p2tr_key_spend_witness( - self.tx_mut(), - input_index, - prev_outs, - TapSighashType::All, - merkle_root, - &context.operator_keypair, - ); - } - - pub fn sign_input_3(&mut self, context: &OperatorContext, connector_f: &ConnectorF) { - let input_index = 3; - pre_sign_taproot_input_default( - self, - input_index, - TapSighashType::All, - connector_f.generate_taproot_spend_info(), - &vec![&context.operator_keypair], - ); - } - - pub fn sign_input_4( + pub fn sign_input_2( &mut self, context: &OperatorContext, guardian_connector: &GuardianConnector, ) { - let input_index = 4; + let input_index = 2; pre_sign_taproot_input_default( self, input_index, diff --git a/goat/src/transactions/watchtower_challenge.rs b/goat/src/transactions/watchtower_challenge.rs index 153d51bf3..9d178f405 100644 --- a/goat/src/transactions/watchtower_challenge.rs +++ b/goat/src/transactions/watchtower_challenge.rs @@ -2,100 +2,26 @@ use bitcoin::{ absolute, consensus, key::Keypair, Address, Amount, ScriptBuf, TapSighashType, Transaction, TxIn, TxOut, }; -use bitvm::signatures::WinternitzSecret; -use musig2::{errors::SigningError, AggNonce, PartialSignature, SecNonce}; use serde::{Deserialize, Serialize}; use crate::{ connectors::{ base::{generate_default_tx_in, TaprootConnector}, connector_b::ConnectorB, - connector_f::ConnectorF, - connector_g::ConnectorG, - watchtower_connectors::WatchctowerConnectors, + watchtower_connectors::WatchtowerChallengeConnector, }, - contexts::{base::BaseContext, operator::OperatorContext, verifier::VerifierContext}, + contexts::operator::OperatorContext, error::{Error, TransactionError::InsufficientInputAmount}, scripts::{generate_data_commitment_outputs, p2a_output}, - transactions::{ - signing::{ - populate_taproot_input_witness_default, populate_taproot_txin_witness, - push_taproot_leaf_script_and_control_block_to_witness, - }, - signing_musig2::{ - generate_taproot_aggregated_signature, generate_taproot_partial_signature, - }, - }, + transactions::signing::populate_taproot_input_witness_default, }; use super::{base::*, pre_signed::*}; -// return BlockhashCommitTransaction's necessary txin(Connector-G) and set wots sig for blockhash as witness for it -pub fn operator_commit_blockhash( - connector_g: &ConnectorG, - latest_blockhash: &[u8; 32], - wots_secret_key: &WinternitzSecret, - input_0: Input, -) -> Result { - let input_0_leaf = 0; - let mut _input_0 = connector_g.generate_taproot_leaf_tx_in(input_0_leaf, &input_0); - match connector_g.generate_leaf_0_unlock_data(wots_secret_key, latest_blockhash) { - Ok(unlock_data) => { - populate_taproot_txin_witness( - &mut _input_0, - &connector_g.generate_taproot_spend_info(), - &connector_g.generate_taproot_leaf_script(input_0_leaf), - unlock_data, - ); - Ok(_input_0) - } - Err(e) => return Err(e), - } -} - -// return AckTransaction's necessary txin(AckConnector) and set preimage as witness for it -pub fn operator_ack( - watchtower_connectors: &WatchctowerConnectors, - preimage: &[u8], - input_0: Input, -) -> Result { - let input_0_leaf = 1; - let mut _input_0 = watchtower_connectors - .1 - .generate_taproot_leaf_tx_in(input_0_leaf, &input_0); - match watchtower_connectors - .1 - .generate_leaf_1_unlock_data(preimage) - { - Ok(unlock_data) => { - populate_taproot_txin_witness( - &mut _input_0, - &watchtower_connectors.1.generate_taproot_spend_info(), - &watchtower_connectors - .1 - .generate_taproot_leaf_script(input_0_leaf), - unlock_data, - ); - Ok(_input_0) - } - Err(e) => return Err(e), - } -} - -pub fn extract_operator_preimage_from_ack_txin(ack_txin: &TxIn) -> Result, Error> { - ack_txin - .witness - .nth(0) - .map(|w| w.to_vec()) - .ok_or(Error::Other( - "Unable to extract preimage from ack txin witness", - )) -} - // Build WatchtowerChallenge transaction and sign ChallengeConnector(txin[0]) pub fn watchtower_challenge( watchtower_keypair: &Keypair, - watchtower_connectors: &WatchctowerConnectors, + watchtower_challenge_connector: &WatchtowerChallengeConnector, commitment: &[u8], input_0: Input, payer_inputs: Vec, @@ -103,9 +29,8 @@ pub fn watchtower_challenge( fee_amount: Amount, ) -> Result { let input_0_leaf = 0; - let mut _input_0 = watchtower_connectors - .0 - .generate_taproot_leaf_tx_in(input_0_leaf, &input_0); + let mut _input_0 = + watchtower_challenge_connector.generate_taproot_leaf_tx_in(input_0_leaf, &input_0); let mut txins = vec![_input_0]; let mut total_input_amount = input_0.amount; @@ -151,20 +76,17 @@ pub fn watchtower_challenge( let input_index = 0; let prev_outs = vec![TxOut { value: input_0.amount, - script_pubkey: watchtower_connectors - .0 + script_pubkey: watchtower_challenge_connector .generate_taproot_address() .script_pubkey(), }]; - let script = watchtower_connectors - .0 - .generate_taproot_leaf_script(input_0_leaf); + let script = watchtower_challenge_connector.generate_taproot_leaf_script(input_0_leaf); populate_taproot_input_witness_default( &mut tx, &prev_outs, input_index, TapSighashType::AllPlusAnyoneCanPay, - &watchtower_connectors.0.generate_taproot_spend_info(), + &watchtower_challenge_connector.generate_taproot_spend_info(), &script, &vec![watchtower_keypair], ); @@ -200,61 +122,32 @@ impl PreSignedTransaction for WatchtowerChallengeInitTransaction { impl WatchtowerChallengeInitTransaction { pub fn new_for_validation( connector_b: &ConnectorB, - connector_g: &ConnectorG, - connector_f: &ConnectorF, - watchtower_connectors_array: &Vec, + watchtower_challenge_connectors: &Vec, input_0: Input, ) -> Result { let input_0_leaf = 0; let _input_0 = connector_b.generate_taproot_leaf_tx_in(input_0_leaf, &input_0); if input_0.amount - < Amount::from_sat( - min_relay_fee_watchtower_challenge_init(watchtower_connectors_array.len()) - + (watchtower_connectors_array.len() * 2 + 3) as u64 * DUST_AMOUNT, - ) + < Amount::from_sat(max_watchtower_challenge_cost( + watchtower_challenge_connectors.len(), + )) { return Err(Error::Transaction(InsufficientInputAmount)); } - let mut total_output_amount = input_0.amount - - Amount::from_sat(min_relay_fee_watchtower_challenge_init( - watchtower_connectors_array.len(), - )); let mut txouts = vec![]; - for watchtower_connectors in watchtower_connectors_array { + for watchtower_challenge_connector in watchtower_challenge_connectors { let challenge_output = TxOut { value: Amount::from_sat(DUST_AMOUNT), - script_pubkey: watchtower_connectors - .0 - .generate_taproot_address() - .script_pubkey(), - }; - let ack_output = TxOut { - value: Amount::from_sat(DUST_AMOUNT), - script_pubkey: watchtower_connectors - .1 + script_pubkey: watchtower_challenge_connector .generate_taproot_address() .script_pubkey(), }; - total_output_amount = total_output_amount - challenge_output.value - ack_output.value; txouts.push(challenge_output); - txouts.push(ack_output); } - let output_connector_g = TxOut { - value: Amount::from_sat(DUST_AMOUNT), - script_pubkey: connector_g.generate_taproot_address().script_pubkey(), - }; let anchor_output = p2a_output(); - total_output_amount = total_output_amount - output_connector_g.value; - total_output_amount = total_output_amount - anchor_output.value; - let output_connector_f = TxOut { - value: total_output_amount, - script_pubkey: connector_f.generate_taproot_address().script_pubkey(), - }; - txouts.push(output_connector_g); - txouts.push(output_connector_f); txouts.push(anchor_output); Ok(WatchtowerChallengeInitTransaction { @@ -291,684 +184,3 @@ impl BaseTransaction for WatchtowerChallengeInitTransaction { "WatchtowerChallengeInit" } } - -#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] -pub struct WatchtowerChallengeTimeoutTransaction { - #[serde(with = "consensus::serde::With::")] - tx: Transaction, - #[serde(with = "consensus::serde::With::")] - prev_outs: Vec, - prev_scripts: Vec, -} -impl PreSignedTransaction for WatchtowerChallengeTimeoutTransaction { - fn tx(&self) -> &Transaction { - &self.tx - } - - fn tx_mut(&mut self) -> &mut Transaction { - &mut self.tx - } - - fn prev_outs(&self) -> &Vec { - &self.prev_outs - } - - fn prev_scripts(&self) -> &Vec { - &self.prev_scripts - } -} -impl WatchtowerChallengeTimeoutTransaction { - pub fn new_for_validation( - watchtower_connectors: &WatchctowerConnectors, - input_0: Input, - input_1: Input, - ) -> Self { - let input_0_leaf = 1; - let _input_0 = watchtower_connectors - .0 - .generate_taproot_leaf_tx_in(input_0_leaf, &input_0); - - let input_1_leaf = 0; - let _input_1 = watchtower_connectors - .1 - .generate_taproot_leaf_tx_in(input_1_leaf, &input_1); - - let output_0 = p2a_output(); - - WatchtowerChallengeTimeoutTransaction { - tx: Transaction { - version: bitcoin::transaction::Version(2), - lock_time: absolute::LockTime::ZERO, - input: vec![_input_0, _input_1], - output: vec![output_0], - }, - prev_outs: vec![ - TxOut { - value: input_0.amount, - script_pubkey: watchtower_connectors - .0 - .generate_taproot_address() - .script_pubkey(), - }, - TxOut { - value: input_1.amount, - script_pubkey: watchtower_connectors - .1 - .generate_taproot_address() - .script_pubkey(), - }, - ], - prev_scripts: vec![ - watchtower_connectors - .0 - .generate_taproot_leaf_script(input_0_leaf), - watchtower_connectors - .1 - .generate_taproot_leaf_script(input_1_leaf), - ], - } - } - - pub fn sign_input_0( - &mut self, - context: &OperatorContext, - watchtower_connectors: &WatchctowerConnectors, - ) { - let input_index = 0; - pre_sign_taproot_input_default( - self, - input_index, - TapSighashType::All, - watchtower_connectors.0.generate_taproot_spend_info(), - &vec![&context.operator_keypair], - ); - } - - fn sign_input_1_musig2( - &mut self, - context: &VerifierContext, - sec_nonce: &SecNonce, - agg_nonce: &AggNonce, - ) -> Result { - let input_index = 1; - // We choose SIGHASH_NONE here, so if the fee_rate is low enough, the operator can replace the - // anchor output with an OP_RETURN output carrying 0 amount, eliminating the need for additional CPFP. - let sighash_type = TapSighashType::None; - generate_taproot_partial_signature( - &context, - self.tx(), - sec_nonce, - agg_nonce, - input_index, - self.prev_outs(), - &self.prev_scripts()[input_index], - sighash_type, - ) - } - - fn push_input_1_signature( - &mut self, - watchtower_connectors: &WatchctowerConnectors, - input_1_sig: bitcoin::taproot::Signature, - ) { - let input_index = 1; - let script = self.prev_scripts()[input_index].clone(); - let spend_info = watchtower_connectors.1.generate_taproot_spend_info(); - let tx_mut = self.tx_mut(); - // Push signature to witness - tx_mut.input[input_index] - .witness - .push(input_1_sig.serialize()); - - // Push script + control block - push_taproot_leaf_script_and_control_block_to_witness( - tx_mut, - input_index, - &spend_info, - &script, - ); - } - - pub fn pre_sign( - &mut self, - context: &VerifierContext, - sec_nonces: &[SecNonce; 1], - agg_nonces: &[AggNonce; 1], - ) -> Result<[PartialSignature; 1], SigningError> { - let input_0_sig = self.sign_input_1_musig2(context, &sec_nonces[0], &agg_nonces[0]); - match [input_0_sig] - .into_iter() - .collect::, SigningError>>() - { - Ok(sigs) => Ok(sigs.try_into().unwrap()), - Err(e) => Err(e), - } - } - - pub fn aggregate_pre_sigs( - &self, - context: &dyn BaseContext, - partial_signatures: &[Vec; 1], - agg_nonces: &[AggNonce; 1], - ) -> Result<[bitcoin::taproot::Signature; 1], Error> { - let input_index = 1; - let sig_index = 0; - let sighash_type = TapSighashType::None; - let input_0_sig = match generate_taproot_aggregated_signature( - context, - self.tx(), - &agg_nonces[sig_index], - input_index, - self.prev_outs(), - &self.prev_scripts()[input_index], - sighash_type, - partial_signatures[sig_index].clone(), - ) { - Ok(sig) => bitcoin::taproot::Signature { - signature: sig.into(), - sighash_type, - }, - Err(_) => return Err(Error::Other("Failed to aggregate signatures")), - }; - Ok([input_0_sig]) - } - - pub fn push_pre_sigs( - &mut self, - watchtower_connectors: &WatchctowerConnectors, - pre_sigs: [bitcoin::taproot::Signature; 1], - ) { - self.push_input_1_signature(watchtower_connectors, pre_sigs[0].clone()); - } -} -impl BaseTransaction for WatchtowerChallengeTimeoutTransaction { - fn finalize(&self) -> Transaction { - self.tx.clone() - } - fn name(&self) -> &'static str { - "WatchtowerChallengeTimeout" - } -} - -#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] -pub struct NackTransaction { - #[serde(with = "consensus::serde::With::")] - tx: Transaction, - #[serde(with = "consensus::serde::With::")] - prev_outs: Vec, - prev_scripts: Vec, -} -impl PreSignedTransaction for NackTransaction { - fn tx(&self) -> &Transaction { - &self.tx - } - - fn tx_mut(&mut self) -> &mut Transaction { - &mut self.tx - } - - fn prev_outs(&self) -> &Vec { - &self.prev_outs - } - - fn prev_scripts(&self) -> &Vec { - &self.prev_scripts - } -} -impl NackTransaction { - pub fn new_for_validation( - watchtower_connectors: &WatchctowerConnectors, - connector_f: &ConnectorF, - input_0: Input, - input_1: Input, - ) -> Self { - let input_0_leaf = 2; - let _input_0 = watchtower_connectors - .1 - .generate_taproot_leaf_tx_in(input_0_leaf, &input_0); - - let input_1_leaf = 1; - let _input_1 = connector_f.generate_taproot_leaf_tx_in(input_1_leaf, &input_1); - - let output_0 = p2a_output(); - - NackTransaction { - tx: Transaction { - version: bitcoin::transaction::Version(2), - lock_time: absolute::LockTime::ZERO, - input: vec![_input_0, _input_1], - output: vec![output_0], - }, - prev_outs: vec![ - TxOut { - value: input_0.amount, - script_pubkey: watchtower_connectors - .1 - .generate_taproot_address() - .script_pubkey(), - }, - TxOut { - value: input_1.amount, - script_pubkey: connector_f.generate_taproot_address().script_pubkey(), - }, - ], - prev_scripts: vec![ - watchtower_connectors - .1 - .generate_taproot_leaf_script(input_0_leaf), - connector_f.generate_taproot_leaf_script(input_1_leaf), - ], - } - } - - fn sign_input_0_musig2( - &mut self, - context: &VerifierContext, - sec_nonce: &SecNonce, - agg_nonce: &AggNonce, - ) -> Result { - let input_index = 0; - let sighash_type = TapSighashType::All; - generate_taproot_partial_signature( - &context, - self.tx(), - sec_nonce, - agg_nonce, - input_index, - self.prev_outs(), - &self.prev_scripts()[input_index], - sighash_type, - ) - } - - fn push_input_0_signature( - &mut self, - watchtower_connectors: &WatchctowerConnectors, - input_0_sig: bitcoin::taproot::Signature, - ) { - let input_index = 0; - let script = self.prev_scripts()[input_index].clone(); - let spend_info = watchtower_connectors.1.generate_taproot_spend_info(); - let tx_mut = self.tx_mut(); - // Push signature to witness - tx_mut.input[input_index] - .witness - .push(input_0_sig.serialize()); - - // Push script + control block - push_taproot_leaf_script_and_control_block_to_witness( - tx_mut, - input_index, - &spend_info, - &script, - ); - } - - fn sign_input_1_musig2( - &mut self, - context: &VerifierContext, - sec_nonce: &SecNonce, - agg_nonce: &AggNonce, - ) -> Result { - let input_index = 1; - let sighash_type = TapSighashType::All; - generate_taproot_partial_signature( - &context, - self.tx(), - sec_nonce, - agg_nonce, - input_index, - self.prev_outs(), - &self.prev_scripts()[input_index], - sighash_type, - ) - } - - fn push_input_1_signature( - &mut self, - connector_f: &ConnectorF, - input_1_sig: bitcoin::taproot::Signature, - ) { - let input_index = 1; - let script = self.prev_scripts()[input_index].clone(); - let spend_info = connector_f.generate_taproot_spend_info(); - let tx_mut = self.tx_mut(); - // Push signature to witness - tx_mut.input[input_index] - .witness - .push(input_1_sig.serialize()); - - // Push script + control block - push_taproot_leaf_script_and_control_block_to_witness( - tx_mut, - input_index, - &spend_info, - &script, - ); - } - - pub fn pre_sign( - &mut self, - context: &VerifierContext, - sec_nonces: &[SecNonce; 2], - agg_nonces: &[AggNonce; 2], - ) -> Result<[PartialSignature; 2], SigningError> { - let input_0_sig = self.sign_input_0_musig2(context, &sec_nonces[0], &agg_nonces[0]); - let input_1_sig = self.sign_input_1_musig2(context, &sec_nonces[1], &agg_nonces[1]); - match [input_0_sig, input_1_sig] - .into_iter() - .collect::, SigningError>>() - { - Ok(sigs) => Ok(sigs.try_into().unwrap()), - Err(e) => Err(e), - } - } - - pub fn aggregate_pre_sigs( - &self, - context: &dyn BaseContext, - partial_signatures: &[Vec; 2], - agg_nonces: &[AggNonce; 2], - ) -> Result<[bitcoin::taproot::Signature; 2], Error> { - let (input_0_sig, input_1_sig); - { - let input_index = 0; - let sig_index = 0; - let sighash_type = TapSighashType::All; - input_0_sig = match generate_taproot_aggregated_signature( - context, - self.tx(), - &agg_nonces[sig_index], - input_index, - self.prev_outs(), - &self.prev_scripts()[input_index], - sighash_type, - partial_signatures[sig_index].clone(), - ) { - Ok(sig) => bitcoin::taproot::Signature { - signature: sig.into(), - sighash_type, - }, - Err(_) => return Err(Error::Other("Failed to aggregate signatures")), - }; - } - { - let input_index = 1; - let sig_index = 1; - let sighash_type = TapSighashType::All; - input_1_sig = match generate_taproot_aggregated_signature( - context, - self.tx(), - &agg_nonces[sig_index], - input_index, - self.prev_outs(), - &self.prev_scripts()[input_index], - sighash_type, - partial_signatures[sig_index].clone(), - ) { - Ok(sig) => bitcoin::taproot::Signature { - signature: sig.into(), - sighash_type, - }, - Err(_) => return Err(Error::Other("Failed to aggregate signatures")), - }; - } - Ok([input_0_sig, input_1_sig]) - } - - pub fn push_pre_sigs( - &mut self, - watchtower_connectors: &WatchctowerConnectors, - connector_f: &ConnectorF, - pre_sigs: [bitcoin::taproot::Signature; 2], - ) { - self.push_input_0_signature(watchtower_connectors, pre_sigs[0].clone()); - self.push_input_1_signature(connector_f, pre_sigs[1].clone()); - } -} -impl BaseTransaction for NackTransaction { - fn finalize(&self) -> Transaction { - self.tx.clone() - } - fn name(&self) -> &'static str { - "Nack" - } -} - -#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] -pub struct BlockhashCommitTimeoutTransaction { - #[serde(with = "consensus::serde::With::")] - tx: Transaction, - #[serde(with = "consensus::serde::With::")] - prev_outs: Vec, - prev_scripts: Vec, -} -impl PreSignedTransaction for BlockhashCommitTimeoutTransaction { - fn tx(&self) -> &Transaction { - &self.tx - } - - fn tx_mut(&mut self) -> &mut Transaction { - &mut self.tx - } - - fn prev_outs(&self) -> &Vec { - &self.prev_outs - } - - fn prev_scripts(&self) -> &Vec { - &self.prev_scripts - } -} -impl BlockhashCommitTimeoutTransaction { - pub fn new_for_validation( - connector_g: &ConnectorG, - connector_f: &ConnectorF, - input_0: Input, - input_1: Input, - ) -> Self { - let input_0_leaf = 1; - let _input_0 = connector_g.generate_taproot_leaf_tx_in(input_0_leaf, &input_0); - - let input_1_leaf = 1; - let _input_1 = connector_f.generate_taproot_leaf_tx_in(input_1_leaf, &input_1); - - let output_0 = p2a_output(); - - BlockhashCommitTimeoutTransaction { - tx: Transaction { - version: bitcoin::transaction::Version(2), - lock_time: absolute::LockTime::ZERO, - input: vec![_input_0, _input_1], - output: vec![output_0], - }, - prev_outs: vec![ - TxOut { - value: input_0.amount, - script_pubkey: connector_g.generate_taproot_address().script_pubkey(), - }, - TxOut { - value: input_1.amount, - script_pubkey: connector_f.generate_taproot_address().script_pubkey(), - }, - ], - prev_scripts: vec![ - connector_g.generate_taproot_leaf_script(input_0_leaf), - connector_f.generate_taproot_leaf_script(input_1_leaf), - ], - } - } - - fn sign_input_0_musig2( - &mut self, - context: &VerifierContext, - sec_nonce: &SecNonce, - agg_nonce: &AggNonce, - ) -> Result { - let input_index = 0; - let sighash_type = TapSighashType::All; - generate_taproot_partial_signature( - &context, - self.tx(), - sec_nonce, - agg_nonce, - input_index, - self.prev_outs(), - &self.prev_scripts()[input_index], - sighash_type, - ) - } - - fn push_input_0_signature( - &mut self, - connector_g: &ConnectorG, - input_0_sig: bitcoin::taproot::Signature, - ) { - let input_index = 0; - let script = self.prev_scripts()[input_index].clone(); - let spend_info = connector_g.generate_taproot_spend_info(); - let tx_mut = self.tx_mut(); - // Push signature to witness - tx_mut.input[input_index] - .witness - .push(input_0_sig.serialize()); - - // Push script + control block - push_taproot_leaf_script_and_control_block_to_witness( - tx_mut, - input_index, - &spend_info, - &script, - ); - } - - fn sign_input_1_musig2( - &mut self, - context: &VerifierContext, - sec_nonce: &SecNonce, - agg_nonce: &AggNonce, - ) -> Result { - let input_index = 1; - let sighash_type = TapSighashType::All; - generate_taproot_partial_signature( - &context, - self.tx(), - sec_nonce, - agg_nonce, - input_index, - self.prev_outs(), - &self.prev_scripts()[input_index], - sighash_type, - ) - } - - fn push_input_1_signature( - &mut self, - connector_f: &ConnectorF, - input_1_sig: bitcoin::taproot::Signature, - ) { - let input_index = 1; - let script = self.prev_scripts()[input_index].clone(); - let spend_info = connector_f.generate_taproot_spend_info(); - let tx_mut = self.tx_mut(); - // Push signature to witness - tx_mut.input[input_index] - .witness - .push(input_1_sig.serialize()); - - // Push script + control block - push_taproot_leaf_script_and_control_block_to_witness( - tx_mut, - input_index, - &spend_info, - &script, - ); - } - - pub fn pre_sign( - &mut self, - context: &VerifierContext, - sec_nonces: &[SecNonce; 2], - agg_nonces: &[AggNonce; 2], - ) -> Result<[PartialSignature; 2], SigningError> { - let input_0_sig = self.sign_input_0_musig2(context, &sec_nonces[0], &agg_nonces[0]); - let input_1_sig = self.sign_input_1_musig2(context, &sec_nonces[1], &agg_nonces[1]); - match [input_0_sig, input_1_sig] - .into_iter() - .collect::, SigningError>>() - { - Ok(sigs) => Ok(sigs.try_into().unwrap()), - Err(e) => Err(e), - } - } - - pub fn aggregate_pre_sigs( - &self, - context: &dyn BaseContext, - partial_signatures: &[Vec; 2], - agg_nonces: &[AggNonce; 2], - ) -> Result<[bitcoin::taproot::Signature; 2], Error> { - let (input_0_sig, input_1_sig); - { - let input_index = 0; - let sig_index = 0; - let sighash_type = TapSighashType::All; - input_0_sig = match generate_taproot_aggregated_signature( - context, - self.tx(), - &agg_nonces[sig_index], - input_index, - self.prev_outs(), - &self.prev_scripts()[input_index], - sighash_type, - partial_signatures[sig_index].clone(), - ) { - Ok(sig) => bitcoin::taproot::Signature { - signature: sig.into(), - sighash_type, - }, - Err(_) => return Err(Error::Other("Failed to aggregate signatures")), - }; - } - { - let input_index = 1; - let sig_index = 1; - let sighash_type = TapSighashType::All; - input_1_sig = match generate_taproot_aggregated_signature( - context, - self.tx(), - &agg_nonces[sig_index], - input_index, - self.prev_outs(), - &self.prev_scripts()[input_index], - sighash_type, - partial_signatures[sig_index].clone(), - ) { - Ok(sig) => bitcoin::taproot::Signature { - signature: sig.into(), - sighash_type, - }, - Err(_) => return Err(Error::Other("Failed to aggregate signatures")), - }; - } - Ok([input_0_sig, input_1_sig]) - } - - pub fn push_pre_sigs( - &mut self, - connector_g: &ConnectorG, - connector_f: &ConnectorF, - pre_sigs: [bitcoin::taproot::Signature; 2], - ) { - self.push_input_0_signature(connector_g, pre_sigs[0].clone()); - self.push_input_1_signature(connector_f, pre_sigs[1].clone()); - } -} -impl BaseTransaction for BlockhashCommitTimeoutTransaction { - fn finalize(&self) -> Transaction { - self.tx.clone() - } - fn name(&self) -> &'static str { - "BlockhashCommitTimeout" - } -} diff --git a/goat/src/wots.rs b/goat/src/wots.rs new file mode 100644 index 000000000..3496fdb8e --- /dev/null +++ b/goat/src/wots.rs @@ -0,0 +1,109 @@ +use bitcoin::script::read_scriptint; +use bitcoin::Witness; +use bitvm::signatures::utils::bitcoin_representation; +pub use bitvm::signatures::winternitz::{Parameters, VoidConverter}; +pub use bitvm::signatures::{ + CompactWots, GenericWinternitzPublicKey, WinternitzSecret, WinternitzSigningInputs, Wots, + Wots16, Wots32, Wots4, Wots64, Wots80, +}; + +pub const WOTS96_LOG2_BASE: u32 = 8; +pub const WOTS96_BASE: u32 = 1 << WOTS96_LOG2_BASE; + +pub struct Wots96; + +impl Wots for Wots96 { + type Converter = VoidConverter; + type PublicKey = [[u8; 20]; Self::TOTAL_DIGIT_LEN as usize]; + type Message = [u8; Self::MSG_BYTE_LEN as usize]; + type Signature = [[u8; 21]; Self::TOTAL_DIGIT_LEN as usize]; + + const MSG_BYTE_LEN: u32 = 96; + const PARAMETERS: Parameters = + Parameters::new_by_bit_length(Self::MSG_BYTE_LEN * 8, WOTS96_LOG2_BASE); + + fn raw_witness_to_signature(witness: &Witness) -> Self::Signature { + assert_eq!(witness.len(), 2 * Self::TOTAL_DIGIT_LEN as usize); + + let mut digit_signatures = Vec::with_capacity(Self::TOTAL_DIGIT_LEN as usize); + for i in (0..witness.len()).step_by(2) { + assert_eq!( + witness[i].len(), + 20, + "the digit signature should be constant 20 bytes" + ); + assert!( + witness[i + 1].len() <= 2, + "the base256 digit should fit in minimally encoded script bytes" + ); + + let digit_value = read_scriptint(&witness[i + 1]).unwrap(); + assert!( + (0..WOTS96_BASE as i64).contains(&digit_value), + "the digit should be in the valid Wots96 base range" + ); + + let mut digit_signature = [0u8; 21]; + digit_signature[..20].copy_from_slice(&witness[i]); + digit_signature[20] = digit_value as u8; + digit_signatures.push(digit_signature); + } + + Self::Signature::try_from(digit_signatures).unwrap() + } + + fn signature_to_raw_witness(signature: &Self::Signature) -> Witness { + let mut witness = Witness::new(); + + for digit_signature in signature.as_ref() { + witness.push(&digit_signature[..20]); + witness.push(bitcoin_representation(i32::from(digit_signature[20]))); + } + + witness + } + + fn signature_to_message(signature: &Self::Signature) -> Self::Message { + if WOTS96_LOG2_BASE == 8 { + let bytes = signature + .as_ref() + .iter() + .map(|digit_sig| digit_sig[20]) + .take(Self::MSG_BYTE_LEN as usize) + .rev() + .collect::>(); + + return Self::Message::try_from(bytes).unwrap(); + } + + let digits = signature + .as_ref() + .iter() + .map(|digit_sig| digit_sig[20]) + .take(((Self::MSG_BYTE_LEN * 8).div_ceil(WOTS96_LOG2_BASE)) as usize) + .rev() + .collect::>(); + + let mut bytes = Vec::with_capacity(Self::MSG_BYTE_LEN as usize); + let mut byte = 0u8; + let mut used_bits = 0u32; + for digit in digits { + byte |= digit << used_bits; + used_bits += WOTS96_LOG2_BASE; + if used_bits >= 8 { + bytes.push(byte); + byte = digit >> (8 - (used_bits - WOTS96_LOG2_BASE)); + used_bits -= 8; + } + } + bytes.truncate(Self::MSG_BYTE_LEN as usize); + + Self::Message::try_from(bytes).unwrap() + } +} + +impl CompactWots for Wots96 { + type CompactSignature = [[u8; 20]; Self::TOTAL_DIGIT_LEN as usize]; +} + +pub type Wots96Secret = WinternitzSecret;