Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions examples/merkle_branch_boundary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use bitcoin::consensus::encode::serialize;
use bitcoin::script::Instruction;
use bitcoin::Witness;
use bitcoin_lab::hashes::sha256::sha2_u32::sha256;
use bitcoin_lab::support::script::ScriptCompilation;

fn main() {
let script = sha256(64).compile_with_policy();
let static_non_push_opcodes = script
.instructions()
.map(Result::unwrap)
.filter(
|instruction| matches!(instruction, Instruction::Op(opcode) if opcode.to_u8() > 0x60),
)
.count();
let fixture_witness = Witness::from_slice(&vec![vec![1u8]; 64]);
let maximum_witness = Witness::from_slice(&vec![vec![0xff, 0x00]; 64]);
let fixture_witness_bytes = serialize(&fixture_witness).len();
let maximum_witness_bytes = serialize(&maximum_witness).len();
assert_eq!(script.len(), 1_060_200);
assert_eq!(static_non_push_opcodes, 770_481);
assert_eq!(fixture_witness_bytes, 129);
assert_eq!(maximum_witness_bytes, 193);
println!(
"sha256_64_bytes_unoptimized={} witness_fixture_bytes={} witness_max_bytes={} data_items=64 hints=0 static_non_push_opcodes={}",
script.len(),
fixture_witness_bytes,
maximum_witness_bytes,
static_non_push_opcodes
);
}
7 changes: 7 additions & 0 deletions knowledge/comparisons/commitments.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,10 @@ cannot bind two hostile 32-byte nodes into the tagged `TapBranch` SHA256
preimage without an enabled concatenation/splitting operation. See
[NR-057](../negative-results/index.md#nr-057-native-taproot-merkle-branch-adapter-is-not-available)
and [OP-021](../open-problems.md#op-021--taproot-merkle-path-verifier).

Ordinary `HASH256(left || right)` Merkle composition is a separate negative
result. The current byte-oriented SHA-256 backend measures one 64-byte layer
at 1,060,200 unoptimized script bytes and 770,481 static non-push opcodes,
with a 129-byte one-byte fixture witness or 193-byte canonical maximum for 64
data items and zero hints. This is a backend-specific compile-only profile,
not a universal lower bound or a complete branch verifier; see [NR-064](../negative-results/merkle-branch-composition.md).
15 changes: 15 additions & 0 deletions knowledge/negative-results/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1586,3 +1586,18 @@ peaking at 5 instead of 7, with the same one-item witness. The construction is
retained as a stack-shape primitive and a complete-width correctness result,
not as a general script-byte optimization. Evidence is `locally-reproduced`;
deployment is `unclassified`; OP-026 remains open.

## NR-064: Standard Merkle branch composition without `OP_CAT`

The conventional Bitcoin Merkle step is `HASH256(left || right)`, while the
current Script opcode set has no enabled native concatenation. The existing
mixed-hash path is unary and is not a Merkle proof. A compile-only probe of the
byte-oriented `sha2_u32::sha256(64)` backend measures 1,060,200 unoptimized
script bytes and 770,481 static non-push opcodes before double hashing or
routing; 64 one-byte items serialize to a 129-byte fixture witness, while
64 canonical two-byte payloads serialize to 193 bytes. This is a
`locally-reproduced`, backend-specific profile, not a universal lower bound,
complete verifier, consensus result, or policy result. Ordinary Merkle
composition is distinct from Taproot `TapBranch`; see [NR-057](#nr-057-native-taproot-merkle-branch-adapter-is-not-available),
[OP-021](../open-problems.md#op-021--taproot-merkle-path-verifier), and the
[full record](merkle-branch-composition.md).
61 changes: 61 additions & 0 deletions knowledge/negative-results/merkle-branch-composition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# NR-064: Standard Merkle branch composition without `OP_CAT`

## Question

Can the repository's existing commitment primitives verify a conventional
Bitcoin Merkle branch, whose step hashes `HASH256(left || right)`, while
keeping the current Script opcode set and a useful cost boundary?

## Result

Not with the compact unary hash-path interface. Bitcoin Script hashes one
stack element at a time, while `OP_CAT` is disabled. Two sibling stack items
therefore cannot be assembled into the 64-byte preimage required by a standard
Merkle step. The existing mixed-hash path is not a substitute: it repeatedly
hashes one state item with unary SHA-256 or RIPEMD-160 and adds a terminal
RIPEMD-160, rather than authenticating a left/right concatenation.

The measured profile is the existing byte-oriented `sha2_u32::sha256(64)`
generator over 64 one-byte stack items. A deterministic compile-only probe
measured:

| Boundary | Script bytes | Static non-push opcodes | Maximum witness bytes | Hints |
| --- | ---: | ---: | ---: | ---: |
| One 64-byte SHA-256 layer | 1,060,200 unoptimized | 770,481 | 129 fixture / 193 max | 0 |

The 129-byte witness is the one-byte fixture; canonical two-byte ScriptNum
payloads such as `[ff,00]` make the 64-item data witness 193 bytes. Both rows
exclude orientation, script and control-block items. This is only the first
SHA-256 layer; standard Bitcoin Merkle branches require double SHA-256,
per-level left/right routing, and conversion of the 32-byte result into the
next 64-byte input. Those costs are not included, so this is a
backend-specific compile-only profile, not a universal cost lower bound or a
complete verifier metric.

The existing 31-bit mixed-hash path is a different unary hash-state function,
not a Merkle proof. Taproot `TapBranch` is also a distinct tagged-SHA256
construction over lexicographically ordered nodes; see [NR-057](index.md#nr-057-native-taproot-merkle-branch-adapter-is-not-available),
[OP-021](../open-problems.md#op-021--taproot-merkle-path-verifier), and the
ordered-node work from PR #17.

Evidence is `locally-reproduced` for the serialization profile and
`inspected` for the opcode/semantic distinction. No consensus, policy, or
cryptographic deployment claim is made.

Reproduce the pinned boundary with:

```sh
cargo run --locked --example merkle_branch_boundary
```

Primary references: `bip-342` and `fips-180-4` in
`knowledge/references/sources.json`; the disabled-opcode rule is detailed in
`knowledge/bitcoin-script-reference.md`.

## Follow-up

Close this result only after a standard `HASH256(left || right)` branch is
implemented with an enabled concatenation primitive or a purpose-built bounded
compression circuit, then measured with direction bits, malformed sibling
encodings, extra witness items, double hashing, and a strict combined-stack
execution test.
15 changes: 15 additions & 0 deletions knowledge/primitives/hash-path-integer.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,21 @@ Script:
separation. Pin every checkpoint, initial state, participant order, bit width,
and round boundary externally. Script and witness cost remain linear.

## Merkle distinction

The path is deliberately unary. A conventional Bitcoin Merkle branch requires
`HASH256(left || right)` at each level, but current Script hashes one stack item
and has no enabled `OP_CAT` to build that 64-byte preimage from two items. The
existing `sha2_u32::sha256(64)` backend has a 1,060,200-byte unoptimized,
compile-only profile and 770,481 static non-push opcodes before double hashing
or branch routing. Its 129-byte one-byte fixture witness becomes 193 bytes for
64 canonical two-byte payloads; both have 64 data items and zero hints. This is
a backend-specific workaround profile, not a Merkle verifier or universal cost
lower bound. Ordinary Merkle branches also differ from BIP341 TapBranch's
tagged, ordered-node construction; see [NR-064](../negative-results/merkle-branch-composition.md),
[NR-057](../negative-results/index.md#nr-057-native-taproot-merkle-branch-adapter-is-not-available),
and [OP-021](../open-problems.md#op-021--taproot-merkle-path-verifier).

## Joint-randomness protocols

Nested paths can authenticate a commit–reveal transcript for a game or
Expand Down
16 changes: 8 additions & 8 deletions src/arithmetic/u4/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ pub mod bit_transitions;
pub mod bits;
pub mod centered;
pub mod compare;
pub mod interleave;
pub mod gray;
pub mod leading_zeros;
pub mod gray_inverse;
pub mod interleave;
pub mod leading_zeros;
pub mod logic;
pub mod lowbit;
pub mod lsb;
pub mod mirror;
pub mod mod3;
pub mod mul_constant;
pub mod nondecreasing;
pub mod pack;
pub mod one_hot;
pub mod mirror;
pub mod mod3;
pub mod pack;
pub mod parity;
pub mod popcount;
pub mod power_of_two;
Expand All @@ -29,9 +29,9 @@ pub mod stack;
pub mod stack_add;
pub mod stack_logic;
pub mod stack_shift;
pub mod zero;
pub mod sum;
pub mod trailing_zeros;
pub mod vector_rotate;
pub mod xor_reduce;
pub mod zero;
pub mod zero_bitmask;
pub mod vector_rotate;
pub mod trailing_zeros;
1 change: 0 additions & 1 deletion src/arithmetic/u4/sum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ mod tests {
}
}


/// Largest standalone batch before accounting for unrelated live stack state.
pub const U4_EXACT_SUM_MAX_BATCH: u32 = 997;

Expand Down
11 changes: 8 additions & 3 deletions src/ciphers/aes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@

use bitcoin::{
opcodes::{
all::{OP_2DROP, OP_2DUP, OP_2OVER, OP_3DUP, OP_ADD, OP_DUP, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_GREATERTHAN, OP_OVER, OP_PICK, OP_ROLL, OP_SUB, OP_SWAP, OP_TOALTSTACK, OP_VERIFY, OP_WITHIN},
all::{
OP_2DROP, OP_2DUP, OP_2OVER, OP_3DUP, OP_ADD, OP_DUP, OP_EQUALVERIFY, OP_FROMALTSTACK,
OP_GREATERTHAN, OP_OVER, OP_PICK, OP_ROLL, OP_SUB, OP_SWAP, OP_TOALTSTACK, OP_VERIFY,
OP_WITHIN,
},
Opcode,
},
script::Builder,
Expand Down Expand Up @@ -763,7 +767,9 @@ mod tests {
use super::*;
use crate::support::{
execution::execute_raw_script_with_inputs_strict,
execution::{execute_script, execute_script_with_inputs, execute_script_with_inputs_strict},
execution::{
execute_script, execute_script_with_inputs, execute_script_with_inputs_strict,
},
script::{script, ScriptCompilation},
};

Expand All @@ -783,7 +789,6 @@ mod tests {
}
}


fn sub_bytes_witness(bytes: [u8; 16]) -> Vec<Vec<u8>> {
bytes_to_nibbles(bytes)
.into_iter()
Expand Down
5 changes: 5 additions & 0 deletions src/commitments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,10 @@ Each construction has its own implementation, tests, and parameter documentation
- [Preimage length](preimage_length/README.md): authenticate a SHA-256 preimage
and return its length minus an offset.

The binary hash path is unary, not a conventional binary Merkle branch;
`HASH256(left || right)` would require concatenation or a separate 64-byte
compression circuit because `OP_CAT` is disabled. See the [measured negative
result](../../knowledge/negative-results/merkle-branch-composition.md).

These experimental primitives authenticate values; they are not complete
protocols. See the [comparison](../../knowledge/comparisons/commitments.md).
11 changes: 11 additions & 0 deletions tests/primitive_metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5971,6 +5971,17 @@ fn hash_path_chain_metrics_are_current() {
check_readme_metrics(hash_path_chain_metrics());
}

#[test]
fn merkle_branch_boundary_metrics_are_current() {
let script = sha256::sha2_u32::sha256(64);
let fixture_witness = vec![vec![1u8]; 64];
let maximum_witness = vec![vec![0xff, 0x00]; 64];
assert_eq!(script_len(script.clone()), 1_060_200);
assert_eq!(static_non_push_opcodes(script), 770_481);
assert_eq!(witness_size(&fixture_witness), 129);
assert_eq!(witness_size(&maximum_witness), 193);
}

fn hash160_composition_metrics() -> Vec<Metric> {
let shared = hash160::hash160_shared_table(32);
vec![
Expand Down
Loading