Skip to content

Commit 98238e4

Browse files
committed
Merge rust-bitcoin#4978: primitives: Implement Encodable for &Script<T>
c2c567b Implement new Encodable trait for Script<T> (Tobin C. Harding) 816cd96 consenus_encoding: Make encoding functions use unsized (Tobin C. Harding) 9d5ff8a Add prefix support to BytesEncoder (Tobin C. Harding) e0b9d39 internals: Make compact_size::MAX_ENCODING_SIZE public (Tobin C. Harding) 3c01d81 Add units tests for the BytesEncoder (Tobin C. Harding) 7d2c670 Remove lifetime from encoder_newtype macro (Tobin C. Harding) Pull request description: Includes a bunch of preparatory clean up as well as a patch to add support to `BytesEncoder` to encode with length prefix. ACKs for top commit: apoelstra: ACK c2c567b; successfully ran local tests nyonson: ACK c2c567b Tree-SHA512: bdea310d2de42ed51897e1c4737c7ceb6d141ae7028e938d052beb2d8b48ff222824549417aa739719b1086f434b5e0b48dd3eaef0973a93da654c06beb60147
2 parents 39d427f + c2c567b commit 98238e4

File tree

8 files changed

+142
-15
lines changed

8 files changed

+142
-15
lines changed

Cargo-minimal.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ dependencies = [
209209
name = "consensus-encoding"
210210
version = "0.0.0"
211211
dependencies = [
212+
"bitcoin-internals",
212213
"bitcoin_hashes 0.16.0",
213214
]
214215

Cargo-recent.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ dependencies = [
211211
name = "consensus-encoding"
212212
version = "0.0.0"
213213
dependencies = [
214+
"bitcoin-internals",
214215
"bitcoin_hashes 0.16.0",
215216
]
216217

consensus_encoding/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ exclude = ["tests", "contrib"]
1414

1515
[features]
1616
default = ["std"]
17-
std = ["alloc"]
18-
alloc = []
17+
std = ["alloc", "internals/std"]
18+
alloc = ["internals/alloc"]
1919

2020
[dependencies]
2121
hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = false }
22+
internals = { package = "bitcoin-internals", path = "../internals" }
2223

2324
[package.metadata.docs.rs]
2425
all-features = true

consensus_encoding/src/encode/encoders.rs

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,49 @@
1111
//! For implementing these newtypes, we provide the [`encoder_newtype`] macro.
1212
//!
1313
14+
use internals::array_vec::ArrayVec;
15+
use internals::compact_size;
16+
1417
use super::Encoder;
1518

19+
/// The maximum length of a compact size encoding.
20+
const SIZE: usize = compact_size::MAX_ENCODING_SIZE;
21+
1622
/// An encoder for a single byte slice.
1723
pub struct BytesEncoder<'sl> {
1824
sl: Option<&'sl [u8]>,
25+
compact_size: Option<ArrayVec<u8, SIZE>>,
1926
}
2027

2128
impl<'sl> BytesEncoder<'sl> {
2229
/// Constructs a byte encoder which encodes the given byte slice, with no length prefix.
23-
pub fn without_length_prefix(sl: &'sl [u8]) -> Self { Self { sl: Some(sl) } }
30+
pub fn without_length_prefix(sl: &'sl [u8]) -> Self {
31+
Self { sl: Some(sl), compact_size: None }
32+
}
33+
34+
/// Constructs a byte encoder which encodes the given byte slice, with the length prefix.
35+
pub fn with_length_prefix(sl: &'sl [u8]) -> Self {
36+
Self { sl: Some(sl), compact_size: Some(compact_size::encode(sl.len())) }
37+
}
2438
}
2539

2640
impl<'e, 'sl> Encoder<'e> for BytesEncoder<'sl> {
27-
fn current_chunk(&self) -> Option<&[u8]> { self.sl }
41+
fn current_chunk(&self) -> Option<&[u8]> {
42+
if let Some(compact_size) = self.compact_size.as_ref() {
43+
Some(compact_size)
44+
} else {
45+
self.sl
46+
}
47+
}
2848

2949
fn advance(&mut self) -> bool {
30-
self.sl = None;
31-
false
50+
if self.compact_size.is_some() {
51+
self.compact_size = None;
52+
true
53+
} else {
54+
self.sl = None;
55+
false
56+
}
3257
}
3358
}
3459

@@ -155,3 +180,63 @@ impl<
155180
fn current_chunk(&self) -> Option<&[u8]> { self.inner.current_chunk() }
156181
fn advance(&mut self) -> bool { self.inner.advance() }
157182
}
183+
184+
#[cfg(test)]
185+
#[cfg(feature = "alloc")]
186+
mod tests {
187+
use alloc::vec::Vec;
188+
189+
use super::*;
190+
191+
// Run the encoder i.e., use it to encode into a vector.
192+
fn run_encoder<'e>(mut encoder: impl Encoder<'e>) -> Vec<u8> {
193+
let mut vec = Vec::new();
194+
while let Some(chunk) = encoder.current_chunk() {
195+
vec.extend_from_slice(chunk);
196+
encoder.advance();
197+
}
198+
vec
199+
}
200+
201+
#[test]
202+
fn encode_byte_slice_without_prefix() {
203+
let obj = [1u8, 2, 3];
204+
205+
let encoder = BytesEncoder::without_length_prefix(&obj);
206+
let got = run_encoder(encoder);
207+
208+
assert_eq!(got, obj);
209+
}
210+
211+
#[test]
212+
fn encode_empty_byte_slice_without_prefix() {
213+
let obj = [];
214+
215+
let encoder = BytesEncoder::without_length_prefix(&obj);
216+
let got = run_encoder(encoder);
217+
218+
assert_eq!(got, obj);
219+
}
220+
221+
#[test]
222+
fn encode_byte_slice_with_prefix() {
223+
let obj = [1u8, 2, 3];
224+
225+
let encoder = BytesEncoder::with_length_prefix(&obj);
226+
let got = run_encoder(encoder);
227+
228+
let want = [3u8, 1, 2, 3];
229+
assert_eq!(got, want);
230+
}
231+
232+
#[test]
233+
fn encode_empty_byte_slice_with_prefix() {
234+
let obj = [];
235+
236+
let encoder = BytesEncoder::with_length_prefix(&obj);
237+
let got = run_encoder(encoder);
238+
239+
let want = [0u8];
240+
assert_eq!(got, want);
241+
}
242+
}

consensus_encoding/src/encode/mod.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ macro_rules! encoder_newtype{
5555
$(#[$($struct_attr)*])*
5656
pub struct $name$(<$lt>)?($encoder);
5757

58-
impl<'e $(, $lt)?> $crate::Encoder<'e> for $name$(<$lt>)? {
58+
impl<'e> $crate::Encoder<'e> for $name$(<$lt>)? {
5959
#[inline]
6060
fn current_chunk(&self) -> Option<&[u8]> { self.0.current_chunk() }
6161

@@ -69,7 +69,11 @@ macro_rules! encoder_newtype{
6969
///
7070
/// Consumes and returns the hash engine to make it easier to call
7171
/// [`hashes::HashEngine::finalize`] directly on the result.
72-
pub fn encode_to_hash_engine<T: Encodable, H: hashes::HashEngine>(object: &T, mut engine: H) -> H {
72+
pub fn encode_to_hash_engine<T, H>(object: &T, mut engine: H) -> H
73+
where
74+
T: Encodable + ?Sized,
75+
H: hashes::HashEngine,
76+
{
7377
let mut encoder = object.encoder();
7478
while let Some(sl) = encoder.current_chunk() {
7579
engine.input(sl);
@@ -80,7 +84,10 @@ pub fn encode_to_hash_engine<T: Encodable, H: hashes::HashEngine>(object: &T, mu
8084

8185
/// Encodes an object into a vector.
8286
#[cfg(feature = "alloc")]
83-
pub fn encode_to_vec<T: Encodable>(object: &T) -> Vec<u8> {
87+
pub fn encode_to_vec<T>(object: &T) -> Vec<u8>
88+
where
89+
T: Encodable + ?Sized,
90+
{
8491
let mut encoder = object.encoder();
8592
let mut vec = Vec::new();
8693
while let Some(chunk) = encoder.current_chunk() {
@@ -103,10 +110,11 @@ pub fn encode_to_vec<T: Encodable>(object: &T) -> Vec<u8> {
103110
///
104111
/// Returns any I/O error encountered while writing to the writer.
105112
#[cfg(feature = "std")]
106-
pub fn encode_to_writer<T: Encodable, W: std::io::Write>(
107-
object: &T,
108-
mut writer: W,
109-
) -> Result<(), std::io::Error> {
113+
pub fn encode_to_writer<T, W>(object: &T, mut writer: W) -> Result<(), std::io::Error>
114+
where
115+
T: Encodable + ?Sized,
116+
W: std::io::Write,
117+
{
110118
let mut encoder = object.encoder();
111119
while let Some(chunk) = encoder.current_chunk() {
112120
writer.write_all(chunk)?;

internals/src/compact_size.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::ToU64;
1818
pub const MAX_ENCODABLE_VALUE: u64 = 0x0200_0000;
1919

2020
/// The maximum length of an encoding.
21-
const MAX_ENCODING_SIZE: usize = 9;
21+
pub const MAX_ENCODING_SIZE: usize = 9;
2222

2323
/// Returns the number of bytes used to encode this `CompactSize` value.
2424
///

primitives/src/script/borrowed.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use core::ops::{
77

88
#[cfg(feature = "arbitrary")]
99
use arbitrary::{Arbitrary, Unstructured};
10+
use encoding::{BytesEncoder, Encodable};
1011

1112
use super::ScriptBuf;
1213
use crate::prelude::{Box, ToOwned, Vec};
@@ -152,6 +153,22 @@ impl<T> Script<T> {
152153
pub fn to_hex(&self) -> alloc::string::String { alloc::format!("{:x}", self) }
153154
}
154155

156+
encoding::encoder_newtype! {
157+
/// The encoder for the [`Script<T>`] type.
158+
pub struct ScriptEncoder<'e>(BytesEncoder<'e>);
159+
}
160+
161+
impl<T> Encodable for Script<T> {
162+
type Encoder<'a>
163+
= ScriptEncoder<'a>
164+
where
165+
Self: 'a;
166+
167+
fn encoder(&self) -> Self::Encoder<'_> {
168+
ScriptEncoder(BytesEncoder::with_length_prefix(self.as_bytes()))
169+
}
170+
}
171+
155172
#[cfg(feature = "arbitrary")]
156173
impl<'a, T> Arbitrary<'a> for &'a Script<T> {
157174
#[inline]
@@ -248,4 +265,18 @@ mod tests {
248265
assert_eq!(script[1..=3].as_bytes(), &[2, 3, 4]);
249266
assert_eq!(script[..=2].as_bytes(), &[1, 2, 3]);
250267
}
268+
269+
#[test]
270+
#[cfg(feature = "alloc")]
271+
fn encode() {
272+
// Consensus encoding includes the length of the encoded data
273+
// (compact size encoded length prefix).
274+
let consensus_encoded: [u8; 6] = [0x05, 1, 2, 3, 4, 5];
275+
276+
// `from_bytes` does not expect the prefix.
277+
let script = Script::from_bytes(&consensus_encoded[1..]);
278+
279+
let got = encoding::encode_to_vec(script);
280+
assert_eq!(got, consensus_encoded);
281+
}
251282
}

primitives/src/script/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use crate::prelude::{Borrow, BorrowMut, Box, Cow, ToOwned, Vec};
2626
#[rustfmt::skip] // Keep public re-exports separate.
2727
#[doc(inline)]
2828
pub use self::{
29-
borrowed::Script,
29+
borrowed::{Script, ScriptEncoder},
3030
owned::ScriptBuf,
3131
tag::{Tag, RedeemScriptTag, ScriptPubKeyTag, ScriptSigTag, TapScriptTag, WitnessScriptTag},
3232
};

0 commit comments

Comments
 (0)