Skip to content

Commit 2724156

Browse files
committed
Lex fixup
Add build_scriptint from rust-bitcoin
1 parent 763af66 commit 2724156

File tree

3 files changed

+73
-21
lines changed

3 files changed

+73
-21
lines changed

src/miniscript/decode.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ pub fn parse<Ctx: ScriptContext>(
282282
))?
283283
},
284284
),
285-
Tk::Push4(ver), Tk::Pick, Tk::Sub, Tk::Depth => match_token!(
285+
Tk::PickPush4(ver), Tk::Sub, Tk::Depth => match_token!(
286286
tokens,
287287
Tk::Num(2) => {
288288
non_term.push(NonTerm::Verify);
@@ -360,7 +360,7 @@ pub fn parse<Ctx: ScriptContext>(
360360
hash160::Hash::from_inner(hash)
361361
))?,
362362
),
363-
Tk::Push4(ver), Tk::Pick, Tk::Sub, Tk::Depth => match_token!(
363+
Tk::PickPush4(ver), Tk::Sub, Tk::Depth => match_token!(
364364
tokens,
365365
Tk::Num(2) => term.reduce0(Terminal::Version(ver))?,
366366
),

src/miniscript/lex.rs

+43-19
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use elements::{opcodes, script};
2323
use std::fmt;
2424

2525
use super::Error;
26-
use util::slice_to_u32_le;
26+
use util::{build_scriptint, slice_to_u32_le};
2727
/// Atom of a tokenized version of a script
2828
#[derive(Debug, Clone, PartialEq, Eq)]
2929
#[allow(missing_docs)]
@@ -65,8 +65,10 @@ pub enum Token {
6565
Hash20([u8; 20]),
6666
Hash32([u8; 32]),
6767
Pubkey(PublicKey),
68-
Push4(u32),
69-
Push(Vec<u8>), // <pref> <swap> <cat> /* prefix is catted */
68+
Push(Vec<u8>), // Num or a
69+
PickPush4(u32), // Pick followed by a 4 byte push
70+
PickPush32([u8; 32]), // Pick followed by a 32 byte push
71+
PickPush(Vec<u8>), // Pick followed by a push
7072
}
7173

7274
impl fmt::Display for Token {
@@ -131,6 +133,23 @@ impl Iterator for TokenIter {
131133
pub fn lex(script: &script::Script) -> Result<Vec<Token>, Error> {
132134
let mut ret = Vec::with_capacity(script.len());
133135

136+
fn process_candidate_push(ret: &mut Vec<Token>) -> Result<(), Error> {
137+
let ret_len = ret.len();
138+
139+
if ret_len < 2 || ret[ret_len - 1] != Token::Swap {
140+
return Ok(());
141+
}
142+
let token = match &ret[ret_len - 2] {
143+
Token::Hash20(x) => Token::Push(x.to_vec()),
144+
Token::Hash32(x) => Token::Push(x.to_vec()),
145+
Token::Pubkey(pk) => Token::Push(pk.to_bytes()),
146+
Token::Num(k) => Token::Push(build_scriptint(*k as i64)),
147+
_x => return Ok(()), // no change required
148+
};
149+
ret[ret_len - 2] = token;
150+
Ok(())
151+
}
152+
134153
for ins in script.instructions_minimal() {
135154
match ins.map_err(Error::Script)? {
136155
script::Instruction::Op(opcodes::all::OP_BOOLAND) => {
@@ -179,6 +198,7 @@ pub fn lex(script: &script::Script) -> Result<Vec<Token>, Error> {
179198
ret.push(Token::Left);
180199
}
181200
script::Instruction::Op(opcodes::all::OP_CAT) => {
201+
process_candidate_push(&mut ret)?;
182202
ret.push(Token::Cat);
183203
}
184204
script::Instruction::Op(opcodes::all::OP_CODESEPARATOR) => {
@@ -253,36 +273,41 @@ pub fn lex(script: &script::Script) -> Result<Vec<Token>, Error> {
253273
ret.push(Token::Hash256);
254274
}
255275
script::Instruction::PushBytes(bytes) => {
256-
// Check for Push
257-
let ret_len = ret.len();
258-
// See that last five elements are CAT
259-
// Checking only two here
260-
if ret_len >= 5
261-
&& ret.last() == Some(&Token::Cat)
262-
&& ret.get(ret_len - 2) == Some(&Token::Cat)
263-
{
264-
ret.push(Token::Push(bytes.to_owned()));
265-
}
276+
// Check for Pick Push
266277
// Special handling of tokens for Covenants
267278
// To determine whether some Token is actually
268279
// 4 bytes push or a script int of 4 bytes,
269280
// we need additional script context
270-
else if ret.last() == Some(&Token::Pick) {
281+
if ret.last() == Some(&Token::Pick) {
282+
ret.pop().unwrap();
271283
match bytes.len() {
272-
4 => ret.push(Token::Push4(slice_to_u32_le(bytes))),
284+
// All other sighash elements are 32 bytes. And the script code
285+
// is 24 bytes
286+
4 => ret.push(Token::PickPush4(slice_to_u32_le(bytes))),
287+
32 => {
288+
let mut x = [0u8; 32];
289+
x.copy_from_slice(bytes);
290+
ret.push(Token::PickPush32(x));
291+
}
292+
// Other pushes should be err. This will change
293+
// once we add script introspection
273294
_ => return Err(Error::InvalidPush(bytes.to_owned())),
274295
}
275296
} else {
297+
// Create the most specific type possible out of the
298+
// Push. When we later encounter CAT, revisit and
299+
// reconvert these to pushes.
300+
// See [process_candidate_push]
276301
match bytes.len() {
277302
20 => {
278303
let mut x = [0; 20];
279304
x.copy_from_slice(bytes);
280-
ret.push(Token::Hash20(x))
305+
ret.push(Token::Hash20(x));
281306
}
282307
32 => {
283308
let mut x = [0; 32];
284309
x.copy_from_slice(bytes);
285-
ret.push(Token::Hash32(x))
310+
ret.push(Token::Hash32(x));
286311
}
287312
33 | 65 => {
288313
ret.push(Token::Pubkey(
@@ -300,8 +325,7 @@ pub fn lex(script: &script::Script) -> Result<Vec<Token>, Error> {
300325
}
301326
ret.push(Token::Num(v as u32));
302327
}
303-
Ok(_) => return Err(Error::InvalidPush(bytes.to_owned())),
304-
Err(e) => return Err(Error::Script(e)),
328+
_ => ret.push(Token::Push(bytes.to_owned())),
305329
}
306330
}
307331
}

src/util.rs

+28
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,34 @@ macro_rules! define_slice_to_le {
3737

3838
define_slice_to_le!(slice_to_u32_le, u32);
3939

40+
/// Helper to encode an integer in script format
41+
/// Copied from rust-bitcoin
42+
pub(crate) fn build_scriptint(n: i64) -> Vec<u8> {
43+
if n == 0 {
44+
return vec![];
45+
}
46+
47+
let neg = n < 0;
48+
49+
let mut abs = if neg { -n } else { n } as usize;
50+
let mut v = vec![];
51+
while abs > 0xFF {
52+
v.push((abs & 0xFF) as u8);
53+
abs >>= 8;
54+
}
55+
// If the number's value causes the sign bit to be set, we need an extra
56+
// byte to get the correct value and correct sign bit
57+
if abs & 0x80 != 0 {
58+
v.push(abs as u8);
59+
v.push(if neg { 0x80u8 } else { 0u8 });
60+
}
61+
// Otherwise we just set the sign bit ourselves
62+
else {
63+
abs |= if neg { 0x80 } else { 0 };
64+
v.push(abs as u8);
65+
}
66+
v
67+
}
4068
/// Get the count of non-push opcodes
4169
// Export to upstream
4270
#[cfg(test)]

0 commit comments

Comments
 (0)