From bc242c271d06177d68c7bab358e766ed4a96de93 Mon Sep 17 00:00:00 2001 From: just-erray Date: Wed, 13 Aug 2025 11:45:19 +0300 Subject: [PATCH 1/3] remove one length witness/vector push special case --- src/builder.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 63d5b40..eebbd07 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -354,12 +354,7 @@ impl NotU8Pushable for usize { } impl NotU8Pushable for Vec { fn bitcoin_script_push(self, builder: StructuredScript) -> StructuredScript { - // Push the element with a minimal opcode if it is a single number. - if self.len() == 1 { - builder.push_int(self[0].into()) - } else { - builder.push_slice(PushBytesBuf::try_from(self.to_vec()).unwrap()) - } + builder.push_slice(PushBytesBuf::try_from(self.to_vec()).unwrap()) } } impl NotU8Pushable for ::bitcoin::PublicKey { @@ -375,12 +370,7 @@ impl NotU8Pushable for ::bitcoin::XOnlyPublicKey { impl NotU8Pushable for Witness { fn bitcoin_script_push(self, mut builder: StructuredScript) -> StructuredScript { for element in self.into_iter() { - // Push the element with a minimal opcode if it is a single number. - if element.len() == 1 { - builder = builder.push_int(element[0].into()); - } else { - builder = builder.push_slice(PushBytesBuf::try_from(element.to_vec()).unwrap()); - } + builder = builder.push_slice(PushBytesBuf::try_from(element.to_vec()).unwrap()); } builder } From 815193bd36cab9e7e9696f0e4f1129d95894b7f3 Mon Sep 17 00:00:00 2001 From: just-erray Date: Thu, 14 Aug 2025 18:52:03 +0300 Subject: [PATCH 2/3] fix and optimize single element case and extend tests --- src/builder.rs | 20 ++++++++++++++++++-- tests/test.rs | 44 ++++++++++++++++++++++++++++++-------------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index eebbd07..b196bca 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -354,7 +354,15 @@ impl NotU8Pushable for usize { } impl NotU8Pushable for Vec { fn bitcoin_script_push(self, builder: StructuredScript) -> StructuredScript { - builder.push_slice(PushBytesBuf::try_from(self.to_vec()).unwrap()) + if self.len() == 1 && self[0] != 128 { + if self[0] < 128 { + builder.push_int(self[0].into()) + } else { + builder.push_int((128i16 - self[0] as i16).into()) + } + } else { + builder.push_slice(PushBytesBuf::try_from(self.to_vec()).unwrap()) + } } } impl NotU8Pushable for ::bitcoin::PublicKey { @@ -370,7 +378,15 @@ impl NotU8Pushable for ::bitcoin::XOnlyPublicKey { impl NotU8Pushable for Witness { fn bitcoin_script_push(self, mut builder: StructuredScript) -> StructuredScript { for element in self.into_iter() { - builder = builder.push_slice(PushBytesBuf::try_from(element.to_vec()).unwrap()); + if element.len() == 1 && element[0] != 128 { + if element[0] < 128 { + builder = builder.push_int(element[0].into()); + } else { + builder = builder.push_int((128i16 - element[0] as i16) as i64); + } + } else { + builder = builder.push_slice(PushBytesBuf::try_from(element.to_vec()).unwrap()); + } } builder } diff --git a/tests/test.rs b/tests/test.rs index 40e2eb5..acb21b4 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -260,20 +260,36 @@ fn test_non_optimal_opcodes() { #[test] fn test_push_witness() { for i in 0..512 { - let mut witness = Witness::new(); - let vec = vec![129u8; i]; - witness.push(vec.clone()); - let script = script! { - { witness } - }; - let reference_script = script! { - { vec } - }; - assert_eq!( - script.compile().as_bytes(), - reference_script.compile().as_bytes(), - "here" - ); + for x in vec![0, 37, 42, 127, 128, 129, 211, 255] { + let mut witness = Witness::new(); + let vec = vec![x; i]; + witness.push(vec.clone()); + let script = script! { + { witness } + }; + let reference_script = script! { + { vec } + }; + assert_eq!( + script.clone().compile().as_bytes(), + reference_script.compile().as_bytes(), + "here" + ); + if i == 1 && x != 128 { + let other_reference_script = script! { + if x > 128 { + { 128i32 - x as i32 } + } else { + { x } + } + }; + assert_eq!( + script.compile().as_bytes(), + other_reference_script.compile().as_bytes(), + "here" + ); + } + } } let mut witness = Witness::new(); From 88edfe1976a8cac59443f178b5bc544dacc26d62 Mon Sep 17 00:00:00 2001 From: just-erray Date: Mon, 18 Aug 2025 12:18:38 +0300 Subject: [PATCH 3/3] fix the [0] case by adding cleaner code with comments written by sander2 --- src/builder.rs | 38 ++++++++++++++++++++++++-------------- tests/test.rs | 22 +++++----------------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index b196bca..1bc2fff 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -354,14 +354,18 @@ impl NotU8Pushable for usize { } impl NotU8Pushable for Vec { fn bitcoin_script_push(self, builder: StructuredScript) -> StructuredScript { - if self.len() == 1 && self[0] != 128 { - if self[0] < 128 { - builder.push_int(self[0].into()) - } else { - builder.push_int((128i16 - self[0] as i16).into()) + match self[..] { + [x] if (1..=16).contains(&x) => { + // use decicated opcode for pushing a single byte with value 1 <= x <= 16. + // Note that we don't use a special opcode used for pushing the value 0 - pushing this + // as an integer would push an empty array rather than a 0x00 value + builder.push_int(x.into()) } - } else { - builder.push_slice(PushBytesBuf::try_from(self.to_vec()).unwrap()) + [129] => { + // 129 is equivalent to -1 when interpreting as signed - use dedicated opcode for pushing this + builder.push_int(-1) + } + _ => builder.push_slice(PushBytesBuf::try_from(self).unwrap()), } } } @@ -378,14 +382,20 @@ impl NotU8Pushable for ::bitcoin::XOnlyPublicKey { impl NotU8Pushable for Witness { fn bitcoin_script_push(self, mut builder: StructuredScript) -> StructuredScript { for element in self.into_iter() { - if element.len() == 1 && element[0] != 128 { - if element[0] < 128 { - builder = builder.push_int(element[0].into()); - } else { - builder = builder.push_int((128i16 - element[0] as i16) as i64); + match element[..] { + [x] if (1..=16).contains(&x) => { + // use decicated opcode for pushing a single byte with value 1 <= x <= 16. + // Note that we don't use a special opcode used for pushing the value 0 - pushing this + // as an integer would push an empty array rather than a 0x00 value + builder = builder.push_int(x.into()); + } + [129] => { + // 129 is equivalent to -1 when interpreting as signed - use dedicated opcode for pushing this + builder = builder.push_int(-1); + } + _ => { + builder = builder.push_slice(PushBytesBuf::try_from(element.to_vec()).unwrap()); } - } else { - builder = builder.push_slice(PushBytesBuf::try_from(element.to_vec()).unwrap()); } } builder diff --git a/tests/test.rs b/tests/test.rs index acb21b4..db7440d 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -271,29 +271,16 @@ fn test_push_witness() { { vec } }; assert_eq!( - script.clone().compile().as_bytes(), + script.compile().as_bytes(), reference_script.compile().as_bytes(), "here" ); - if i == 1 && x != 128 { - let other_reference_script = script! { - if x > 128 { - { 128i32 - x as i32 } - } else { - { x } - } - }; - assert_eq!( - script.compile().as_bytes(), - other_reference_script.compile().as_bytes(), - "here" - ); - } } } let mut witness = Witness::new(); - for i in 0..16 { + witness.push([]); //presentation of 0 with `consensus_encode` is [0] instead of [], but `push_int` in this repository encodes 0 as [] + for i in 1..16 { let mut varint = Vec::new(); encode::VarInt(i).consensus_encode(&mut varint).unwrap(); witness.push(varint); @@ -324,7 +311,8 @@ fn test_push_witness() { fn test_push_scriptbuf() { let script_buf = script! { { 1 } - }.compile(); + } + .compile(); let script = script! { { script_buf.clone() } };