From 859915ea74297007f389d9fd42e654aaeefe774f Mon Sep 17 00:00:00 2001 From: KSlashh <48985735+KSlashh@users.noreply.github.com> Date: Wed, 10 Dec 2025 18:26:22 +0800 Subject: [PATCH 1/2] remove length-prefix when get groth16 pubin --- goat/src/disprove_scripts.rs | 166 +++++++++++++++++++++++++++++++++-- goat/src/scripts.rs | 3 + goat/ziren/proof.bin | 1 + goat/ziren/public_inputs.bin | Bin 0 -> 64 bytes goat/ziren/vk.bin | Bin 0 -> 34138 bytes 5 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 goat/ziren/proof.bin create mode 100644 goat/ziren/public_inputs.bin create mode 100644 goat/ziren/vk.bin diff --git a/goat/src/disprove_scripts.rs b/goat/src/disprove_scripts.rs index b69396ff..5cafd399 100644 --- a/goat/src/disprove_scripts.rs +++ b/goat/src/disprove_scripts.rs @@ -54,22 +54,34 @@ pub fn validate_guest_assertions( } 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) } + { lift_and_reverse_bytes_u4(64 * i, 32) } } - { sha256_u4(guest_pubin_num * 40) } + { 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], @@ -102,7 +114,7 @@ pub fn verify_guest_pubin( { roll_n(zipped_wots32_msg_stack_items_num, wots32_msg_stack_items_num * 3) } - { generate_guest_pubin_commitment(NUM_GUEST as u32) } + { generate_guest_pubin_commitment(NUM_GUEST as u32) } // guest-pubin-commitment = hash(blockhash || constant || included_watchtowers_map) { zip_nibbles_bytes32() } @@ -761,3 +773,139 @@ fn test_verify_guest_pubin() { assert_eq!(result.final_stack.len(), 1); } } + +#[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_COMMITMENT_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], &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 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], &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/scripts.rs b/goat/src/scripts.rs index ccd1811c..de7dc9d7 100644 --- a/goat/src/scripts.rs +++ b/goat/src/scripts.rs @@ -243,6 +243,9 @@ pub fn extract_data_from_commitment_outputs(txouts: &[TxOut]) -> Vec { if let bitcoin::blockdata::script::Instruction::PushBytes(bytes) = &instructions[1] { data.extend_from_slice(bytes.as_bytes()); } + if script.is_op_return() { + break; + } } data } diff --git a/goat/ziren/proof.bin b/goat/ziren/proof.bin new file mode 100644 index 00000000..2716c496 --- /dev/null +++ b/goat/ziren/proof.bin @@ -0,0 +1 @@ +^Rju_mD"H oSR^ 9󧠡U6 cY̠R@3jfF{iLe/qʪhI?E.xju$Q \ No newline at end of file diff --git a/goat/ziren/public_inputs.bin b/goat/ziren/public_inputs.bin new file mode 100644 index 0000000000000000000000000000000000000000..a496a6f7aa0c4ca4c80c9a97407d35382ab61053 GIT binary patch literal 64 zcmV-G0Kflwla#cmDF^s$MmsXQ{|SXq8nzzm&bR?I4v+swwZQ;bVHw)dEEtygSNDw1 WrCve?uGt1w1rW>m`eaCh1y&l)^c`pb literal 0 HcmV?d00001 diff --git a/goat/ziren/vk.bin b/goat/ziren/vk.bin new file mode 100644 index 0000000000000000000000000000000000000000..858944da2033ae45c32069b2fd738b5164e40b34 GIT binary patch literal 34138 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 literal 0 HcmV?d00001 From 0f784f376cec4f6e69a81cf1fbfdaf3df55bd13e Mon Sep 17 00:00:00 2001 From: KSlashh <48985735+KSlashh@users.noreply.github.com> Date: Wed, 14 Jan 2026 02:14:53 +0800 Subject: [PATCH 2/2] fix guest inputs verify script --- goat/src/disprove_scripts.rs | 218 +++++++++-------------------------- 1 file changed, 55 insertions(+), 163 deletions(-) diff --git a/goat/src/disprove_scripts.rs b/goat/src/disprove_scripts.rs index 5cafd399..fbfa8b4f 100644 --- a/goat/src/disprove_scripts.rs +++ b/goat/src/disprove_scripts.rs @@ -7,12 +7,14 @@ use bitvm::chunk::api::{ }; 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_COMMITMENT_INDEX: usize = 1; +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; @@ -110,7 +112,7 @@ pub fn verify_guest_pubin( { Wots32::checksig_verify(&groth16_pubin_wots_pubkeys[GUEST_PUBIN_COMMITMENT_INDEX]) } - { zip_nibbles_bytes32() } + { reverse_zip_nibbles_bytes32() } { roll_n(zipped_wots32_msg_stack_items_num, wots32_msg_stack_items_num * 3) } @@ -366,6 +368,16 @@ fn zip_nibbles_bytes32() -> Script { } } +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); @@ -454,6 +466,17 @@ pub fn parse_u4_stack(start_index: usize, num_bytes: usize, stack: &FmtStack) -> 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(); @@ -608,61 +631,7 @@ fn test_verify_hashlock_pubin_script() { } #[test] -fn test_generate_guest_pubin_commitment() { - 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("395bf727f9aac5e80911591073fcf9c826f428804131ca089beba3869421749a") - .unwrap() - .try_into() - .unwrap(), - hex::decode("7517017f1ed26c3a0eeeeff8cd8415a0f90f4f8ece83f639a625e62f399c9ac1") - .unwrap() - .try_into() - .unwrap(), - hex::decode("0fd7e7b3810d822c9804b48d0e1334ed98690f3a78c3baceeefb88fb416b49b9") - .unwrap() - .try_into() - .unwrap(), - ]; - let pubin_bytes = hex::decode("0000000000000020395bf727f9aac5e80911591073fcf9c826f428804131ca089beba3869421749a00000000000000207517017f1ed26c3a0eeeeff8cd8415a0f90f4f8ece83f639a625e62f399c9ac100000000000000200fd7e7b3810d822c9804b48d0e1334ed98690f3a78c3baceeefb88fb416b49b9").unwrap(); - let _sha256_digest: [u8; 32] = - hex::decode("7702d98383c9971311fc63deed6edb20a1a7d19a01c7587fdd9c8b76a98ae798") - .unwrap() - .try_into() - .unwrap(); - let commit_value: [u8; 32] = - hex::decode("1702d98383c9971311fc63deed6edb20a1a7d19a01c7587fdd9c8b76a98ae798") - .unwrap() - .try_into() - .unwrap(); - let _pubin_bytes_rev = { - let mut v = pubin_bytes.clone(); - v.reverse(); - v - }; - - 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); - assert_eq!( - commit_value.to_vec(), - parse_u4_stack(0, 32, &result.final_stack) - ); -} - -#[test] -fn test_verify_guest_pubin() { +fn test_verify_guest_pubin_ziren() { use hex::FromHex; let secrets = std::iter::repeat(Wots32::generate_secret_key()) .take(NUM_GUEST + NUM_PUBS) @@ -672,25 +641,41 @@ fn test_verify_guest_pubin() { .map(|s| Wots32::generate_public_key(s)) .collect::>(); let blockhash = - <[u8; 32]>::from_hex("395bf727f9aac5e80911591073fcf9c826f428804131ca089beba3869421749a") + <[u8; 32]>::from_hex("5a690bba0ba076d621f77665398f4b1ddbfc2349bbb3e8880307625ac5cfa900") .unwrap(); let constant = - <[u8; 32]>::from_hex("7517017f1ed26c3a0eeeeff8cd8415a0f90f4f8ece83f639a625e62f399c9ac1") + <[u8; 32]>::from_hex("2df7bde0605973f5809a1338094cb47a309bc38363dd35ce178c088cd3cda79f") .unwrap(); - // 1011010100011010 let included_bitmap = - <[u8; 32]>::from_hex("b51a000000000000000000000000000000000000000000000000000000000000") - .unwrap(); - let groth16_pubin = - <[u8; 32]>::from_hex("1351297318a8febcdabb450df8e3ea4c61c1c2218d36a3df3ed887ca548d38a9") + <[u8; 32]>::from_hex("0100000000000000000000000000000000000000000000000000000000000000") .unwrap(); - let hashes_len = 16; + // 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())); - // println!("hash {}: {:?}", i, hex::encode(hashes[i].iter().rev().cloned().collect::>())); } let lock_scr = script! { @@ -708,7 +693,7 @@ fn test_verify_guest_pubin() { 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], &groth16_pubin) } + { 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) } @@ -727,7 +712,7 @@ fn test_verify_guest_pubin() { 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], &groth16_pubin) } + { 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) } @@ -745,23 +730,7 @@ fn test_verify_guest_pubin() { 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], &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 input_preimages = vec![vec![]; hashes_len]; - let full_scr = script! { - { Wots32::sign_to_raw_witness(&secrets[NUM_PUBS + GUEST_PUBIN_COMMITMENT_INDEX], &groth16_pubin) } + { 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) } @@ -769,83 +738,6 @@ fn test_verify_guest_pubin() { { 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_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_COMMITMENT_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], &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); } @@ -855,7 +747,7 @@ fn test_verify_guest_pubin_ziren() { 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], &groth16_pubin) } + { 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) }