From 3600f31227c09cb384aee708c7b85fa7ab0ba1b3 Mon Sep 17 00:00:00 2001 From: Lyndon Date: Wed, 22 Jul 2026 09:21:07 +0800 Subject: [PATCH 1/2] fix: enhance comparison logic for zero-length inputs --- build.rs | 2 +- c/utils.h | 8 ++ src/tests/secp256k1_blake160_multisig_all.rs | 93 ++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 349dd79..9132124 100644 --- a/build.rs +++ b/build.rs @@ -27,7 +27,7 @@ const BINARIES: &[(&str, &str)] = &[ ), ( "secp256k1_blake160_multisig_all", - "36c971b8d41fbd94aabca77dc75e826729ac98447b46f91e00796155dddb0d29", + "4ce11c0d86324813c3844acacaede12bf586497e672ebcd89c4c5cf6c1d46c3c", ), ]; diff --git a/c/utils.h b/c/utils.h index 4a9a73b..219158f 100644 --- a/c/utils.h +++ b/c/utils.h @@ -29,11 +29,19 @@ int epoch_number_with_fraction_cmp(uint64_t a, uint64_t b) { uint64_t a_epoch = (a >> NUMBER_OFFSET) & NUMBER_MASK; uint64_t a_index = (a >> INDEX_OFFSET) & INDEX_MASK; uint64_t a_len = (a >> LENGTH_OFFSET) & LENGTH_MASK; + if (a_len == 0) { + a_index = 0; + a_len = 1; + } /* extract b epoch */ uint64_t b_epoch = (b >> NUMBER_OFFSET) & NUMBER_MASK; uint64_t b_index = (b >> INDEX_OFFSET) & INDEX_MASK; uint64_t b_len = (b >> LENGTH_OFFSET) & LENGTH_MASK; + if (b_len == 0) { + b_index = 0; + b_len = 1; + } if (a_epoch < b_epoch) { return -1; diff --git a/src/tests/secp256k1_blake160_multisig_all.rs b/src/tests/secp256k1_blake160_multisig_all.rs index 38a3286..8743d48 100644 --- a/src/tests/secp256k1_blake160_multisig_all.rs +++ b/src/tests/secp256k1_blake160_multisig_all.rs @@ -646,6 +646,99 @@ fn test_multisig_0_2_3_unlock_with_since_epoch() { } } +#[test] +fn test_multisig_0_2_3_unlock_with_since_epoch_length_zero() { + let mut data_loader = DummyDataLoader::new(); + let keys = generate_keys(3); + let since_epoch = EpochNumberWithFraction::new(200, 5, 100); + let since = 0x2000_0000_0000_0000u64 + since_epoch.full_value(); + let multi_sign_script = gen_multi_sign_script(&keys, 2, 0); + let args = { + let mut buf = blake160(&multi_sign_script).to_vec(); + buf.extend(since.to_le_bytes().iter()); + Bytes::from(buf) + }; + let lock_script = gen_multi_sign_lock_script(args.clone()); + let raw_tx = gen_tx(&mut data_loader, args); + + { + let input_since = 0x2000_0000_0000_0000u64 | (100u64 << 24) | 200u64; + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| i.as_builder().since(input_since.pack()).build()) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + let verify_result = verify(&data_loader, &tx); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::validation_failure(&lock_script, ERROR_INCORRECT_SINCE_VALUE) + .input_lock_script(0), + ); + } + + { + let input_since = 0x2000_0000_0000_0000u64 | 201u64; + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| i.as_builder().since(input_since.pack()).build()) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + verify(&data_loader, &tx).expect("pass verification"); + } + + { + let epoch = EpochNumberWithFraction::new(200, 4, 100); + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| { + i.as_builder() + .since((0x2000_0000_0000_0000u64 + epoch.full_value()).pack()) + .build() + }) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + let verify_result = verify(&data_loader, &tx); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::validation_failure(&lock_script, ERROR_INCORRECT_SINCE_VALUE) + .input_lock_script(0), + ); + } + + { + let epoch = EpochNumberWithFraction::new(200, 6, 100); + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| { + i.as_builder() + .since((0x2000_0000_0000_0000u64 + epoch.full_value()).pack()) + .build() + }) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + verify(&data_loader, &tx).expect("pass verification"); + } + + { + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| i.as_builder().since(since.pack()).build()) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + verify(&data_loader, &tx).expect("pass verification"); + } +} + #[test] fn test_genesis_time_locked_cell() { // Test against ckb1qyqxs3hhwx2ttqcrk2yk2nsgqteglvfjt4hsjpzgxs,5200,2020-02-09 From 5125cb1f724fc869f6d83e5402082d43a99e46c4 Mon Sep 17 00:00:00 2001 From: Lyndon Date: Tue, 28 Jul 2026 10:56:22 +0800 Subject: [PATCH 2/2] fix: update multisig handling for since flags and add tests for relative epoch --- build.rs | 2 +- c/common.h | 6 +- src/tests/secp256k1_blake160_multisig_all.rs | 157 +++++++++++++++++++ 3 files changed, 162 insertions(+), 3 deletions(-) diff --git a/build.rs b/build.rs index 9132124..67bb8bf 100644 --- a/build.rs +++ b/build.rs @@ -27,7 +27,7 @@ const BINARIES: &[(&str, &str)] = &[ ), ( "secp256k1_blake160_multisig_all", - "4ce11c0d86324813c3844acacaede12bf586497e672ebcd89c4c5cf6c1d46c3c", + "fcc633b5dae45217eaea21f3283c5433683bc556a668c327a1f7f003edaafe63", ), ]; diff --git a/c/common.h b/c/common.h index 0586e98..03721f2 100644 --- a/c/common.h +++ b/c/common.h @@ -26,7 +26,8 @@ Defines commonly used high level functions and constants. /* since */ #define SINCE_VALUE_BITS 56 #define SINCE_VALUE_MASK 0x00ffffffffffffff -#define SINCE_EPOCH_FRACTION_FLAG 0b00100000 +#define SINCE_EPOCH_FRACTION_ABSOLUTE_FLAG 0b00100000 +#define SINCE_EPOCH_FRACTION_RELATIVE_FLAG 0b10100000 /* calculate inputs length */ int calculate_inputs_len() { @@ -111,7 +112,8 @@ int check_since(uint64_t since) { if (since_flags != input_since_flags) { return ERROR_INCORRECT_SINCE_FLAGS; } - if (input_since_flags == SINCE_EPOCH_FRACTION_FLAG) { + if (input_since_flags == SINCE_EPOCH_FRACTION_ABSOLUTE_FLAG || + input_since_flags == SINCE_EPOCH_FRACTION_RELATIVE_FLAG) { ret = epoch_number_with_fraction_cmp(input_since_value, since_value); if (ret < 0) { return ERROR_INCORRECT_SINCE_VALUE; diff --git a/src/tests/secp256k1_blake160_multisig_all.rs b/src/tests/secp256k1_blake160_multisig_all.rs index 8743d48..f12adb4 100644 --- a/src/tests/secp256k1_blake160_multisig_all.rs +++ b/src/tests/secp256k1_blake160_multisig_all.rs @@ -646,6 +646,163 @@ fn test_multisig_0_2_3_unlock_with_since_epoch() { } } +#[test] +fn test_multisig_0_2_3_unlock_with_since_relative_epoch() { + let mut data_loader = DummyDataLoader::new(); + let keys = generate_keys(3); + let since_epoch = EpochNumberWithFraction::new(200, 5, 100); + // relative epoch flag: 0b10100000 << 56 + let since = 0xa000_0000_0000_0000u64 + since_epoch.full_value(); + let multi_sign_script = gen_multi_sign_script(&keys, 2, 0); + let args = { + let mut buf = blake160(&multi_sign_script).to_vec(); + buf.extend(since.to_le_bytes().iter()); + Bytes::from(buf) + }; + let lock_script = gen_multi_sign_lock_script(args.clone()); + let raw_tx = gen_tx(&mut data_loader, args); + { + // default since is 0, flags mismatch + let tx = multi_sign_tx(raw_tx.clone(), &multi_sign_script, &[&keys[0], &keys[1]]); + let verify_result = verify(&data_loader, &tx); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::validation_failure(&lock_script, ERROR_INCORRECT_SINCE_FLAG) + .input_lock_script(0), + ); + } + { + // absolute epoch flag instead of relative + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| { + i.as_builder() + .since((0x2000_0000_0000_0000u64 + since_epoch.full_value()).pack()) + .build() + }) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + let verify_result = verify(&data_loader, &tx); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::validation_failure(&lock_script, ERROR_INCORRECT_SINCE_FLAG) + .input_lock_script(0), + ); + } + { + // smaller fraction: 200 + 2/200 < 200 + 5/100 + let epoch = EpochNumberWithFraction::new(200, 2, 200); + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| { + i.as_builder() + .since((0xa000_0000_0000_0000u64 + epoch.full_value()).pack()) + .build() + }) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + let verify_result = verify(&data_loader, &tx); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::validation_failure(&lock_script, ERROR_INCORRECT_SINCE_VALUE) + .input_lock_script(0), + ); + } + { + // smaller fraction: 200 + 1/600 < 200 + 5/100 + let epoch = EpochNumberWithFraction::new(200, 1, 600); + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| { + i.as_builder() + .since((0xa000_0000_0000_0000u64 + epoch.full_value()).pack()) + .build() + }) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + let verify_result = verify(&data_loader, &tx); + assert_error_eq!( + verify_result.unwrap_err(), + ScriptError::validation_failure(&lock_script, ERROR_INCORRECT_SINCE_VALUE) + .input_lock_script(0), + ); + } + { + // larger fraction with different encoding: 200 + 6/50 > 200 + 5/100 + let epoch = EpochNumberWithFraction::new(200, 6, 50); + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| { + i.as_builder() + .since((0xa000_0000_0000_0000u64 + epoch.full_value()).pack()) + .build() + }) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + verify(&data_loader, &tx).expect("pass verification"); + } + { + // larger fraction: 200 + 1/2 > 200 + 5/100 + // packed integer is smaller (length in high bits), so integer compare would wrongly fail + let epoch = EpochNumberWithFraction::new(200, 1, 2); + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| { + i.as_builder() + .since((0xa000_0000_0000_0000u64 + epoch.full_value()).pack()) + .build() + }) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + verify(&data_loader, &tx).expect("pass verification"); + } + { + let epoch = EpochNumberWithFraction::new(200, 6, 100); + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| { + i.as_builder() + .since((0xa000_0000_0000_0000u64 + epoch.full_value()).pack()) + .build() + }) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + verify(&data_loader, &tx).expect("pass verification"); + } + { + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| i.as_builder().since(since.pack()).build()) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + verify(&data_loader, &tx).expect("pass verification"); + } + { + let inputs: Vec = raw_tx + .inputs() + .into_iter() + .map(|i| i.as_builder().since((since + 1).pack()).build()) + .collect(); + let raw_tx = raw_tx.as_advanced_builder().set_inputs(inputs).build(); + let tx = multi_sign_tx(raw_tx, &multi_sign_script, &[&keys[1], &keys[2]]); + verify(&data_loader, &tx).expect("pass verification"); + } +} + #[test] fn test_multisig_0_2_3_unlock_with_since_epoch_length_zero() { let mut data_loader = DummyDataLoader::new();