interpreter in progress
This commit is contained in:
parent
8bb9897d72
commit
cbd3a130a4
|
@ -9,8 +9,11 @@ pub enum Error {
|
||||||
// Max sizes.
|
// Max sizes.
|
||||||
ScriptSize,
|
ScriptSize,
|
||||||
PushSize,
|
PushSize,
|
||||||
|
StackSize,
|
||||||
NumberOverflow,
|
NumberOverflow,
|
||||||
NumberNotMinimallyEncoded,
|
NumberNotMinimallyEncoded,
|
||||||
|
SigCount,
|
||||||
|
PubkeyCount,
|
||||||
|
|
||||||
// Failed verify operations
|
// Failed verify operations
|
||||||
Verify,
|
Verify,
|
||||||
|
@ -34,6 +37,7 @@ pub enum Error {
|
||||||
SignatureDer,
|
SignatureDer,
|
||||||
Minimaldata,
|
Minimaldata,
|
||||||
SignatureHighS,
|
SignatureHighS,
|
||||||
|
SignatureNullDummy,
|
||||||
PubkeyType,
|
PubkeyType,
|
||||||
|
|
||||||
// Softfork safeness
|
// Softfork safeness
|
||||||
|
@ -51,10 +55,13 @@ impl fmt::Display for Error {
|
||||||
Error::EqualVerify => "Failed equal verify operation".fmt(f),
|
Error::EqualVerify => "Failed equal verify operation".fmt(f),
|
||||||
Error::CheckSigVerify => "Failed signature check".fmt(f),
|
Error::CheckSigVerify => "Failed signature check".fmt(f),
|
||||||
Error::NumEqualVerify => "Failed num equal verify operation".fmt(f),
|
Error::NumEqualVerify => "Failed num equal verify operation".fmt(f),
|
||||||
|
Error::SigCount => "Maximum number of signature exceeded".fmt(f),
|
||||||
|
Error::PubkeyCount => "Maximum number of pubkeys per multisig exceeded".fmt(f),
|
||||||
|
|
||||||
// Max sizes.
|
// Max sizes.
|
||||||
Error::ScriptSize => "Script is too long".fmt(f),
|
Error::ScriptSize => "Script is too long".fmt(f),
|
||||||
Error::PushSize => "Pushing too many bytes".fmt(f),
|
Error::PushSize => "Pushing too many bytes".fmt(f),
|
||||||
|
Error::StackSize => "Stack is too big".fmt(f),
|
||||||
Error::NumberOverflow => "Number overflow".fmt(f),
|
Error::NumberOverflow => "Number overflow".fmt(f),
|
||||||
Error::NumberNotMinimallyEncoded => "Number not minimally encoded".fmt(f),
|
Error::NumberNotMinimallyEncoded => "Number not minimally encoded".fmt(f),
|
||||||
|
|
||||||
|
@ -74,6 +81,7 @@ impl fmt::Display for Error {
|
||||||
Error::SignatureDer => "Invalid Signature".fmt(f),
|
Error::SignatureDer => "Invalid Signature".fmt(f),
|
||||||
Error::Minimaldata => "Check minimaldata failed".fmt(f),
|
Error::Minimaldata => "Check minimaldata failed".fmt(f),
|
||||||
Error::SignatureHighS => "Invalid High S in Signature".fmt(f),
|
Error::SignatureHighS => "Invalid High S in Signature".fmt(f),
|
||||||
|
Error::SignatureNullDummy => "Multisig extra stack element is not empty".fmt(f),
|
||||||
Error::PubkeyType => "Invalid Pubkey".fmt(f),
|
Error::PubkeyType => "Invalid Pubkey".fmt(f),
|
||||||
|
|
||||||
// Softfork safeness
|
// Softfork safeness
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use keys::{Signature, Public};
|
use keys::{Signature, Public};
|
||||||
use transaction::{SEQUENCE_LOCKTIME_DISABLE_FLAG};
|
use transaction::SEQUENCE_LOCKTIME_DISABLE_FLAG;
|
||||||
use crypto::{sha1, sha256, dhash160, dhash256, ripemd160};
|
use crypto::{sha1, sha256, dhash160, dhash256, ripemd160};
|
||||||
use script::{
|
use script::{
|
||||||
script, Script, Num, VerificationFlags, Opcode, Error, read_usize,
|
script, Script, Num, VerificationFlags, Opcode, Error, read_usize,
|
||||||
|
@ -10,7 +10,7 @@ use script::{
|
||||||
/// Helper function.
|
/// Helper function.
|
||||||
fn check_signature(
|
fn check_signature(
|
||||||
checker: &SignatureChecker,
|
checker: &SignatureChecker,
|
||||||
script_sig: Vec<u8>,
|
mut script_sig: Vec<u8>,
|
||||||
public: Vec<u8>,
|
public: Vec<u8>,
|
||||||
script_code: &Script,
|
script_code: &Script,
|
||||||
version: SignatureVersion
|
version: SignatureVersion
|
||||||
|
@ -24,8 +24,8 @@ fn check_signature(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let hash_type = *script_sig.last().unwrap() as u32;
|
let hash_type = script_sig.pop().unwrap() as u32;
|
||||||
let signature = script_sig[..script_sig.len() - 1].into();
|
let signature = script_sig.into();
|
||||||
|
|
||||||
checker.check_signature(&signature, &public, script_code, hash_type, version)
|
checker.check_signature(&signature, &public, script_code, hash_type, version)
|
||||||
}
|
}
|
||||||
|
@ -261,8 +261,20 @@ pub fn eval_script(
|
||||||
let mut altstack = Vec::<Vec<u8>>::new();
|
let mut altstack = Vec::<Vec<u8>>::new();
|
||||||
|
|
||||||
while pc < script.len() {
|
while pc < script.len() {
|
||||||
let fexec = exec_stack.iter().find(|&x| !x).is_some();
|
let executing = exec_stack.iter().all(|x| *x);
|
||||||
let opcode = try!(script.get_opcode(pc));
|
let opcode = try!(script.get_opcode(pc));
|
||||||
|
|
||||||
|
// TODO: verify push opcodes minimal data and size
|
||||||
|
// even if they are not executed
|
||||||
|
|
||||||
|
// TODO: verify maximum number of opcodes per script
|
||||||
|
|
||||||
|
if !(executing || (Opcode::OP_IF <= opcode && opcode <= Opcode::OP_ENDIF)) {
|
||||||
|
// TODO: instead of moving pc counter, move to next opcode
|
||||||
|
pc += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
match opcode {
|
match opcode {
|
||||||
Opcode::OP_PUSHDATA1 |
|
Opcode::OP_PUSHDATA1 |
|
||||||
Opcode::OP_PUSHDATA2 |
|
Opcode::OP_PUSHDATA2 |
|
||||||
|
@ -365,6 +377,7 @@ pub fn eval_script(
|
||||||
stack.push(bytes.to_vec());
|
stack.push(bytes.to_vec());
|
||||||
pc += opcode as usize;
|
pc += opcode as usize;
|
||||||
},
|
},
|
||||||
|
Opcode::OP_1NEGATE |
|
||||||
Opcode::OP_1 |
|
Opcode::OP_1 |
|
||||||
Opcode::OP_2 |
|
Opcode::OP_2 |
|
||||||
Opcode::OP_3 |
|
Opcode::OP_3 |
|
||||||
|
@ -448,15 +461,21 @@ pub fn eval_script(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Opcode::OP_NOP1 | Opcode::OP_NOP4 | Opcode::OP_NOP5 | Opcode::OP_NOP6 |
|
Opcode::OP_NOP1 |
|
||||||
Opcode::OP_NOP7 | Opcode::OP_NOP8 | Opcode::OP_NOP9 | Opcode::OP_NOP10 => {
|
Opcode::OP_NOP4 |
|
||||||
|
Opcode::OP_NOP5 |
|
||||||
|
Opcode::OP_NOP6 |
|
||||||
|
Opcode::OP_NOP7 |
|
||||||
|
Opcode::OP_NOP8 |
|
||||||
|
Opcode::OP_NOP9 |
|
||||||
|
Opcode::OP_NOP10 => {
|
||||||
if flags.verify_discourage_upgradable_nops {
|
if flags.verify_discourage_upgradable_nops {
|
||||||
return Err(Error::DiscourageUpgradableNops);
|
return Err(Error::DiscourageUpgradableNops);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Opcode::OP_IF | Opcode::OP_NOTIF => {
|
Opcode::OP_IF | Opcode::OP_NOTIF => {
|
||||||
let mut exec_value = false;
|
let mut exec_value = false;
|
||||||
if fexec {
|
if executing {
|
||||||
try!(require_not_empty(stack).map_err(|_| Error::UnbalancedConditional));
|
try!(require_not_empty(stack).map_err(|_| Error::UnbalancedConditional));
|
||||||
exec_value = cast_to_bool(&stack.pop().unwrap());
|
exec_value = cast_to_bool(&stack.pop().unwrap());
|
||||||
if opcode == Opcode::OP_NOTIF {
|
if opcode == Opcode::OP_NOTIF {
|
||||||
|
@ -810,11 +829,92 @@ pub fn eval_script(
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY => {
|
||||||
|
try!(require_not_empty(stack));
|
||||||
|
let keys_count = try!(Num::from_slice(&stack.pop().unwrap(), flags.verify_minimaldata, 4));
|
||||||
|
if keys_count < 0.into() || keys_count > script::MAX_PUBKEYS_PER_MULTISIG.into() {
|
||||||
|
return Err(Error::PubkeyCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
try!(require_len(stack, usize::from(keys_count) + 1));
|
||||||
|
let sigs_count = try!(Num::from_slice(&stack.pop().unwrap(), flags.verify_minimaldata, 4));
|
||||||
|
if sigs_count < 0.into() || sigs_count > keys_count {
|
||||||
|
return Err(Error::SigCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sigs_count: usize = sigs_count.into();
|
||||||
|
let keys_count: usize = keys_count.into();
|
||||||
|
|
||||||
|
try!(require_len(stack, keys_count + sigs_count));
|
||||||
|
let mut subscript = script.subscript(begincode);
|
||||||
|
|
||||||
|
let keys: Vec<_> = (0..keys_count).into_iter().map(|_| stack.pop().unwrap()).rev().collect();
|
||||||
|
let sigs: Vec<_> = (0..sigs_count).into_iter().map(|_| stack.pop().unwrap()).rev().collect();
|
||||||
|
|
||||||
|
if version == SignatureVersion::Base {
|
||||||
|
for signature in &sigs {
|
||||||
|
subscript = subscript.find_and_delete(signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut success = true;
|
||||||
|
let mut k = 0;
|
||||||
|
let mut s = 0;
|
||||||
|
while s < sigs.len() && success {
|
||||||
|
// TODO: remove redundant copying
|
||||||
|
let key = keys[k].clone();
|
||||||
|
let sig = sigs[s].clone();
|
||||||
|
|
||||||
|
try!(check_signature_encoding(&sig, flags));
|
||||||
|
try!(check_pubkey_encoding(&key, flags));
|
||||||
|
|
||||||
|
let ok = check_signature(checker, sig, key, &subscript, version);
|
||||||
|
if ok {
|
||||||
|
s += 1;
|
||||||
|
}
|
||||||
|
k += 1;
|
||||||
|
|
||||||
|
success = sigs.len() - s <= keys.len() - k;
|
||||||
|
}
|
||||||
|
|
||||||
|
try!(require_not_empty(stack));
|
||||||
|
if !stack.pop().unwrap().is_empty() && flags.verify_nulldummy {
|
||||||
|
return Err(Error::SignatureNullDummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
match opcode {
|
||||||
|
Opcode::OP_CHECKMULTISIG => {
|
||||||
|
let to_push = match success {
|
||||||
|
true => vec![1],
|
||||||
|
false => vec![0],
|
||||||
|
};
|
||||||
|
stack.push(to_push);
|
||||||
|
},
|
||||||
|
Opcode::OP_CHECKMULTISIGVERIFY if !success => {
|
||||||
|
return Err(Error::CheckSigVerify);
|
||||||
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Opcode::OP_RESERVED |
|
||||||
|
Opcode::OP_VER |
|
||||||
|
Opcode::OP_RESERVED1 |
|
||||||
|
Opcode::OP_RESERVED2 => {},
|
||||||
|
Opcode::OP_VERIF |
|
||||||
|
Opcode::OP_VERNOTIF => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if stack.len() + altstack.len() > 1000 {
|
||||||
|
return Err(Error::StackSize);
|
||||||
|
}
|
||||||
|
|
||||||
pc += 1;
|
pc += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !exec_stack.is_empty() {
|
||||||
|
return Err(Error::UnbalancedConditional);
|
||||||
|
}
|
||||||
|
|
||||||
let success = !stack.is_empty() && {
|
let success = !stack.is_empty() && {
|
||||||
let last = stack.last().unwrap();
|
let last = stack.last().unwrap();
|
||||||
cast_to_bool(last)
|
cast_to_bool(last)
|
||||||
|
|
|
@ -63,6 +63,12 @@ impl From<Num> for i64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Num> for usize {
|
||||||
|
fn from(n: Num) -> Self {
|
||||||
|
n.value as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Num {
|
impl Num {
|
||||||
pub fn from_slice(data: &[u8], require_minimal: bool, max_size: usize) -> Result<Self, Error> {
|
pub fn from_slice(data: &[u8], require_minimal: bool, max_size: usize) -> Result<Self, Error> {
|
||||||
if data.len() > max_size {
|
if data.len() > max_size {
|
||||||
|
|
|
@ -212,14 +212,6 @@ pub enum Opcode {
|
||||||
OP_NOP8 = 0xb7,
|
OP_NOP8 = 0xb7,
|
||||||
OP_NOP9 = 0xb8,
|
OP_NOP9 = 0xb8,
|
||||||
OP_NOP10 = 0xb9,
|
OP_NOP10 = 0xb9,
|
||||||
|
|
||||||
// template matching params
|
|
||||||
OP_SMALLINTEGER = 0xfa,
|
|
||||||
OP_PUBKEYS = 0xfb,
|
|
||||||
OP_PUBKEYHASH = 0xfd,
|
|
||||||
OP_PUBKEY = 0xfe,
|
|
||||||
|
|
||||||
OP_INVALIDOPCODE = 0xff,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Opcode {
|
impl fmt::Display for Opcode {
|
||||||
|
@ -437,15 +429,6 @@ impl Opcode {
|
||||||
0xb7 => Some(OP_NOP8),
|
0xb7 => Some(OP_NOP8),
|
||||||
0xb8 => Some(OP_NOP9),
|
0xb8 => Some(OP_NOP9),
|
||||||
0xb9 => Some(OP_NOP10),
|
0xb9 => Some(OP_NOP10),
|
||||||
|
|
||||||
// template matching params
|
|
||||||
0xfa => Some(OP_SMALLINTEGER),
|
|
||||||
0xfb => Some(OP_PUBKEYS),
|
|
||||||
0xfd => Some(OP_PUBKEYHASH),
|
|
||||||
0xfe => Some(OP_PUBKEY),
|
|
||||||
|
|
||||||
0xff => Some(OP_INVALIDOPCODE),
|
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -681,13 +664,5 @@ mod tests {
|
||||||
assert_eq!(Opcode::OP_NOP8, Opcode::from_u8(Opcode::OP_NOP8 as u8).unwrap());
|
assert_eq!(Opcode::OP_NOP8, Opcode::from_u8(Opcode::OP_NOP8 as u8).unwrap());
|
||||||
assert_eq!(Opcode::OP_NOP9, Opcode::from_u8(Opcode::OP_NOP9 as u8).unwrap());
|
assert_eq!(Opcode::OP_NOP9, Opcode::from_u8(Opcode::OP_NOP9 as u8).unwrap());
|
||||||
assert_eq!(Opcode::OP_NOP10, Opcode::from_u8(Opcode::OP_NOP10 as u8).unwrap());
|
assert_eq!(Opcode::OP_NOP10, Opcode::from_u8(Opcode::OP_NOP10 as u8).unwrap());
|
||||||
|
|
||||||
// template matching params
|
|
||||||
assert_eq!(Opcode::OP_SMALLINTEGER, Opcode::from_u8(Opcode::OP_SMALLINTEGER as u8).unwrap());
|
|
||||||
assert_eq!(Opcode::OP_PUBKEYS, Opcode::from_u8(Opcode::OP_PUBKEYS as u8).unwrap());
|
|
||||||
assert_eq!(Opcode::OP_PUBKEYHASH, Opcode::from_u8(Opcode::OP_PUBKEYHASH as u8).unwrap());
|
|
||||||
assert_eq!(Opcode::OP_PUBKEY, Opcode::from_u8(Opcode::OP_PUBKEY as u8).unwrap());
|
|
||||||
|
|
||||||
assert_eq!(Opcode::OP_INVALIDOPCODE, Opcode::from_u8(Opcode::OP_INVALIDOPCODE as u8).unwrap());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ const MAX_SCRIPT_ELEMENT_SIZE: usize = 520;
|
||||||
const _MAX_OPS_PER_SCRIPT: u32 = 201;
|
const _MAX_OPS_PER_SCRIPT: u32 = 201;
|
||||||
|
|
||||||
/// Maximum number of public keys per multisig
|
/// Maximum number of public keys per multisig
|
||||||
const _MAX_PUBKEYS_PER_MULTISIG: u32 = 20;
|
pub const MAX_PUBKEYS_PER_MULTISIG: usize = 20;
|
||||||
|
|
||||||
/// Maximum script length in bytes
|
/// Maximum script length in bytes
|
||||||
pub const MAX_SCRIPT_SIZE: usize = 10000;
|
pub const MAX_SCRIPT_SIZE: usize = 10000;
|
||||||
|
|
Loading…
Reference in New Issue