Get OP_MULTISIG working
This commit is contained in:
parent
bf09ab2754
commit
a87dd0042b
|
@ -672,8 +672,8 @@ ordinary_opcode!(
|
||||||
OP_MIN, OP_MAX, OP_WITHIN,
|
OP_MIN, OP_MAX, OP_WITHIN,
|
||||||
// crypto
|
// crypto
|
||||||
OP_RIPEMD160, OP_SHA1, OP_SHA256, OP_HASH160, OP_HASH256,
|
OP_RIPEMD160, OP_SHA1, OP_SHA256, OP_HASH160, OP_HASH256,
|
||||||
OP_CODESEPARATOR, OP_CHECKSIG, OP_CHECKSIGVERIFY
|
OP_CODESEPARATOR, OP_CHECKSIG, OP_CHECKSIGVERIFY,
|
||||||
// OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
|
OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,10 @@ pub struct Script(ThinVec<u8>);
|
||||||
pub enum ScriptError {
|
pub enum ScriptError {
|
||||||
/// OP_CHECKSIG was called with a bad public key
|
/// OP_CHECKSIG was called with a bad public key
|
||||||
BadPublicKey,
|
BadPublicKey,
|
||||||
|
/// OP_CHECKSIG was called with a bad signature
|
||||||
|
BadSignature,
|
||||||
|
/// An ECDSA error
|
||||||
|
EcdsaError(::secp256k1::Error),
|
||||||
/// An OP_ELSE happened while not in an OP_IF tree
|
/// An OP_ELSE happened while not in an OP_IF tree
|
||||||
ElseWithoutIf,
|
ElseWithoutIf,
|
||||||
/// An OP_ENDIF happened while not in an OP_IF tree
|
/// An OP_ENDIF happened while not in an OP_IF tree
|
||||||
|
@ -69,6 +73,10 @@ pub enum ScriptError {
|
||||||
EarlyEndOfScript,
|
EarlyEndOfScript,
|
||||||
/// An OP_RETURN or synonym was executed
|
/// An OP_RETURN or synonym was executed
|
||||||
ExecutedReturn,
|
ExecutedReturn,
|
||||||
|
/// A multisig tx with negative or too many keys
|
||||||
|
MultisigBadKeyCount(int),
|
||||||
|
/// A multisig tx with negative or too many signatures
|
||||||
|
MultisigBadSigCount(int),
|
||||||
/// Used OP_PICK with a negative index
|
/// Used OP_PICK with a negative index
|
||||||
NegativePick,
|
NegativePick,
|
||||||
/// Used OP_ROLL with a negative index
|
/// Used OP_ROLL with a negative index
|
||||||
|
@ -196,6 +204,90 @@ pub fn read_uint<'a, I:Iterator<(uint, &'a u8)>>(mut iter: I, size: uint)
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check a signature -- returns an error that is currently just translated
|
||||||
|
/// into a 0/1 to push onto the script stack
|
||||||
|
fn check_signature(secp: &Secp256k1, sig_slice: &[u8], pk_slice: &[u8], script: Vec<u8>,
|
||||||
|
tx: &Transaction, input_index: uint) -> Result<(), ScriptError> {
|
||||||
|
|
||||||
|
// Check public key
|
||||||
|
let pubkey = PublicKey::from_slice(pk_slice);
|
||||||
|
if pubkey.is_err() {
|
||||||
|
return Err(BadPublicKey);
|
||||||
|
}
|
||||||
|
let pubkey = pubkey.unwrap();
|
||||||
|
|
||||||
|
// Check signature and hashtype
|
||||||
|
if sig_slice.len() == 0 {
|
||||||
|
return Err(BadSignature);
|
||||||
|
}
|
||||||
|
let (hashtype, anyone_can_pay) = SignatureHashType::from_signature(sig_slice);
|
||||||
|
|
||||||
|
// Compute the transaction data to be hashed
|
||||||
|
let mut tx_copy = tx.clone();
|
||||||
|
|
||||||
|
// Put the script into an Option so that we can move it (via take_unwrap())
|
||||||
|
// in the following branch/loop without the move-checker complaining about
|
||||||
|
// multiple moves.
|
||||||
|
let mut script = Some(script);
|
||||||
|
if anyone_can_pay {
|
||||||
|
// For anyone-can-pay transactions we replace the whole input array
|
||||||
|
// with just the current input, to ensure the others have no effect.
|
||||||
|
let mut old_input = tx_copy.input[input_index].clone();
|
||||||
|
old_input.script_sig = Script(ThinVec::from_vec(script.take_unwrap()));
|
||||||
|
tx_copy.input = vec![old_input];
|
||||||
|
} else {
|
||||||
|
// Otherwise we keep all the inputs, blanking out the others and even
|
||||||
|
// resetting their sequence no. if appropriate
|
||||||
|
for (n, input) in tx_copy.input.mut_iter().enumerate() {
|
||||||
|
// Zero out the scripts of other inputs
|
||||||
|
if n == input_index {
|
||||||
|
input.script_sig = Script(ThinVec::from_vec(script.take_unwrap()));
|
||||||
|
} else {
|
||||||
|
input.script_sig = Script::new();
|
||||||
|
// If we aren't signing them, also zero out the sequence number
|
||||||
|
if hashtype == SigHashSingle || hashtype == SigHashNone {
|
||||||
|
input.sequence = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase outputs as appropriate
|
||||||
|
let mut sighash_single_bug = false;
|
||||||
|
match hashtype {
|
||||||
|
SigHashNone => { tx_copy.output = vec![]; }
|
||||||
|
SigHashSingle => {
|
||||||
|
if input_index < tx_copy.output.len() {
|
||||||
|
let mut new_outs = Vec::with_capacity(input_index + 1);
|
||||||
|
for _ in range(0, input_index) {
|
||||||
|
new_outs.push(Default::default())
|
||||||
|
}
|
||||||
|
new_outs.push(tx_copy.output.swap_remove(input_index).unwrap());
|
||||||
|
tx_copy.output = new_outs;
|
||||||
|
} else {
|
||||||
|
sighash_single_bug = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SigHashAll | SigHashUnknown => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let signature_hash = if sighash_single_bug {
|
||||||
|
vec![1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
} else {
|
||||||
|
let mut data_to_sign = serialize(&tx_copy).unwrap();
|
||||||
|
data_to_sign.push(*sig_slice.last().unwrap());
|
||||||
|
data_to_sign.push(0);
|
||||||
|
data_to_sign.push(0);
|
||||||
|
data_to_sign.push(0);
|
||||||
|
serialize(&Sha256dHash::from_data(data_to_sign.as_slice())).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
secp.verify(signature_hash.as_slice(), sig_slice, &pubkey).map_err(|e| EcdsaError(e))
|
||||||
|
}
|
||||||
|
|
||||||
// Macro to translate English stack instructions into Rust code.
|
// Macro to translate English stack instructions into Rust code.
|
||||||
// All number are references to stack positions: 1 is the top,
|
// All number are references to stack positions: 1 is the top,
|
||||||
// 2 is the second-to-top, etc. The numbers do not change within
|
// 2 is the second-to-top, etc. The numbers do not change within
|
||||||
|
@ -524,96 +616,99 @@ impl Script {
|
||||||
opcodes::OP_CHECKSIG | opcodes::OP_CHECKSIGVERIFY => {
|
opcodes::OP_CHECKSIG | opcodes::OP_CHECKSIGVERIFY => {
|
||||||
if stack.len() < 2 { return Err(PopEmptyStack); }
|
if stack.len() < 2 { return Err(PopEmptyStack); }
|
||||||
|
|
||||||
let pubkey = PublicKey::from_slice(stack.pop().unwrap().as_slice());
|
let pk = stack.pop().unwrap();
|
||||||
let signature = stack.pop().unwrap();
|
let pk_slice = pk.as_slice();
|
||||||
|
let sig = stack.pop().unwrap();
|
||||||
if pubkey.is_err() {
|
let sig_slice = sig.as_slice();
|
||||||
stack.push(build_scriptint(0));
|
|
||||||
} else if signature.len() == 0 {
|
|
||||||
stack.push(build_scriptint(0));
|
|
||||||
} else {
|
|
||||||
// This is as far as we can go without a transaction, so fail here
|
|
||||||
if input_context.is_none() { return Err(NoTransaction); }
|
|
||||||
// Otherwise unwrap it
|
|
||||||
let (tx, input_index) = input_context.unwrap();
|
|
||||||
let pubkey = pubkey.unwrap();
|
|
||||||
let (hashtype, anyone_can_pay) =
|
|
||||||
SignatureHashType::from_signature(signature.as_slice());
|
|
||||||
|
|
||||||
// Compute the section of script that needs to be hashed: everything
|
// Compute the section of script that needs to be hashed: everything
|
||||||
// from the last CODESEPARATOR, except the signature itself.
|
// from the last CODESEPARATOR, except the signature itself.
|
||||||
let mut script = Vec::from_slice(raw.slice_from(codeseparator_index));
|
let mut script = Vec::from_slice(raw.slice_from(codeseparator_index));
|
||||||
find_and_remove(&mut script, signature.as_slice());
|
find_and_remove(&mut script, sig_slice);
|
||||||
|
|
||||||
// Compute the transaction data to be hashed
|
// This is as far as we can go without a transaction, so fail here
|
||||||
let mut tx_copy = tx.clone();
|
if input_context.is_none() { return Err(NoTransaction); }
|
||||||
// Put the script into an Option so that we can move it (via take_unwrap())
|
// Otherwise unwrap it
|
||||||
// in the following branch/loop without the move-checker complaining about
|
let (tx, input_index) = input_context.unwrap();
|
||||||
// multiple moves.
|
|
||||||
let mut script = Some(script);
|
|
||||||
if anyone_can_pay {
|
|
||||||
// For anyone-can-pay transactions we replace the whole input array
|
|
||||||
// with just the current input, to ensure the others have no effect.
|
|
||||||
let mut old_input = tx_copy.input[input_index].clone();
|
|
||||||
old_input.script_sig = Script(ThinVec::from_vec(script.take_unwrap()));
|
|
||||||
tx_copy.input = vec![old_input];
|
|
||||||
} else {
|
|
||||||
// Otherwise we keep all the inputs, blanking out the others and even
|
|
||||||
// resetting their sequence no. if appropriate
|
|
||||||
for (n, input) in tx_copy.input.mut_iter().enumerate() {
|
|
||||||
// Zero out the scripts of other inputs
|
|
||||||
if n == input_index {
|
|
||||||
input.script_sig = Script(ThinVec::from_vec(script.take_unwrap()));
|
|
||||||
} else {
|
|
||||||
input.script_sig = Script::new();
|
|
||||||
// If we aren't signing them, also zero out the sequence number
|
|
||||||
if hashtype == SigHashSingle || hashtype == SigHashNone {
|
|
||||||
input.sequence = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Erase outputs as appropriate
|
match check_signature(&secp, sig_slice, pk_slice, script, tx, input_index) {
|
||||||
let mut sighash_single_bug = false;
|
|
||||||
match hashtype {
|
|
||||||
SigHashNone => { tx_copy.output = vec![]; }
|
|
||||||
SigHashSingle => {
|
|
||||||
if input_index < tx_copy.output.len() {
|
|
||||||
let mut new_outs = Vec::with_capacity(input_index + 1);
|
|
||||||
for _ in range(0, input_index) {
|
|
||||||
new_outs.push(Default::default())
|
|
||||||
}
|
|
||||||
new_outs.push(tx_copy.output.swap_remove(input_index).unwrap());
|
|
||||||
tx_copy.output = new_outs;
|
|
||||||
} else {
|
|
||||||
sighash_single_bug = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SigHashAll | SigHashUnknown => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let signature_hash = if sighash_single_bug {
|
|
||||||
vec![1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0]
|
|
||||||
} else {
|
|
||||||
let mut data_to_sign = serialize(&tx_copy).unwrap();
|
|
||||||
data_to_sign.push(*signature.last().unwrap());
|
|
||||||
data_to_sign.push(0);
|
|
||||||
data_to_sign.push(0);
|
|
||||||
data_to_sign.push(0);
|
|
||||||
serialize(&Sha256dHash::from_data(data_to_sign.as_slice())).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
match secp.verify(signature_hash.as_slice(), signature.as_slice(), &pubkey) {
|
|
||||||
Ok(()) => stack.push(build_scriptint(1)),
|
Ok(()) => stack.push(build_scriptint(1)),
|
||||||
_ => stack.push(build_scriptint(0)),
|
_ => stack.push(build_scriptint(0)),
|
||||||
}
|
}
|
||||||
|
if op == opcodes::OP_CHECKSIGVERIFY { op_verify!(stack); }
|
||||||
|
}
|
||||||
|
opcodes::OP_CHECKMULTISIG | opcodes::OP_CHECKMULTISIGVERIFY => {
|
||||||
|
// Read all the keys
|
||||||
|
if stack.len() < 1 { return Err(PopEmptyStack); }
|
||||||
|
let n_keys = try!(read_scriptint(stack.pop().unwrap().as_slice()));
|
||||||
|
if n_keys < 0 || n_keys > 20 {
|
||||||
|
return Err(MultisigBadKeyCount(n_keys as int));
|
||||||
}
|
}
|
||||||
|
|
||||||
if op == opcodes::OP_CHECKSIGVERIFY { op_verify!(stack); }
|
if (stack.len() as i64) < n_keys { return Err(PopEmptyStack); }
|
||||||
|
let mut keys = Vec::with_capacity(n_keys as uint);
|
||||||
|
for _ in range(0, n_keys) {
|
||||||
|
keys.push(stack.pop().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all the signatures
|
||||||
|
if stack.len() < 1 { return Err(PopEmptyStack); }
|
||||||
|
let n_sigs = try!(read_scriptint(stack.pop().unwrap().as_slice()));
|
||||||
|
if n_sigs < 0 || n_sigs > n_keys {
|
||||||
|
return Err(MultisigBadSigCount(n_sigs as int));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.len() as i64) < n_sigs { return Err(PopEmptyStack); }
|
||||||
|
let mut sigs = Vec::with_capacity(n_sigs as uint);
|
||||||
|
for _ in range(0, n_sigs) {
|
||||||
|
sigs.push(stack.pop().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop one more element off the stack to be replicate a consensus bug
|
||||||
|
if stack.pop().is_none() { return Err(PopEmptyStack); }
|
||||||
|
|
||||||
|
// Compute the section of script that needs to be hashed: everything
|
||||||
|
// from the last CODESEPARATOR, except the signatures themselves.
|
||||||
|
let mut script = Vec::from_slice(raw.slice_from(codeseparator_index));
|
||||||
|
for sig in sigs.iter() {
|
||||||
|
find_and_remove(&mut script, sig.as_slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is as far as we can go without a transaction, so fail here
|
||||||
|
if input_context.is_none() { return Err(NoTransaction); }
|
||||||
|
// Otherwise unwrap it
|
||||||
|
let (tx, input_index) = input_context.unwrap();
|
||||||
|
|
||||||
|
// Check signatures
|
||||||
|
let mut key_iter = keys.iter();
|
||||||
|
let mut sig_iter = sigs.iter();
|
||||||
|
let mut key = key_iter.next();
|
||||||
|
let mut sig = sig_iter.next();
|
||||||
|
loop {
|
||||||
|
match (key, sig) {
|
||||||
|
// Try to validate the signature with the given key
|
||||||
|
(Some(k), Some(s)) => {
|
||||||
|
// Move to the next signature if it is valid for the current key
|
||||||
|
if check_signature(&secp, s.as_slice(), k.as_slice(),
|
||||||
|
script.clone(), tx, input_index).is_ok() {
|
||||||
|
sig = sig_iter.next();
|
||||||
|
}
|
||||||
|
// Move to the next key in any case
|
||||||
|
key = key_iter.next();
|
||||||
|
}
|
||||||
|
// Run out of signatures, success
|
||||||
|
(_, None) => {
|
||||||
|
stack.push(build_scriptint(1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Run out of keys to match to signatures, fail
|
||||||
|
(None, Some(_)) => {
|
||||||
|
stack.push(build_scriptint(0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if op == opcodes::OP_CHECKMULTISIGVERIFY { op_verify!(stack); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -735,10 +830,8 @@ mod test {
|
||||||
// before needing a transaction
|
// before needing a transaction
|
||||||
assert_eq!(script_pk.evaluate(&mut vec![], None), Err(PopEmptyStack));
|
assert_eq!(script_pk.evaluate(&mut vec![], None), Err(PopEmptyStack));
|
||||||
assert_eq!(script_pk.evaluate(&mut vec![vec![], vec![]], None), Err(VerifyFailed));
|
assert_eq!(script_pk.evaluate(&mut vec![vec![], vec![]], None), Err(VerifyFailed));
|
||||||
// A null signature is actually Ok -- this will just push 0 onto the stack
|
|
||||||
// since the signature is guaranteed to fail.
|
|
||||||
assert_eq!(script_pk.evaluate(&mut vec![vec![], "026d5d4cfef5f3d97d2263941b4d8e7aaa82910bf8e6f7c6cf1d8f0d755b9d2d1a".from_hex().unwrap()], None), Ok(()));
|
|
||||||
// But if the signature is there, we need a tx to check it
|
// But if the signature is there, we need a tx to check it
|
||||||
|
assert_eq!(script_pk.evaluate(&mut vec![vec![], "026d5d4cfef5f3d97d2263941b4d8e7aaa82910bf8e6f7c6cf1d8f0d755b9d2d1a".from_hex().unwrap()], None), Err(NoTransaction));
|
||||||
assert_eq!(script_pk.evaluate(&mut vec![vec![0], "026d5d4cfef5f3d97d2263941b4d8e7aaa82910bf8e6f7c6cf1d8f0d755b9d2d1a".from_hex().unwrap()], None), Err(NoTransaction));
|
assert_eq!(script_pk.evaluate(&mut vec![vec![0], "026d5d4cfef5f3d97d2263941b4d8e7aaa82910bf8e6f7c6cf1d8f0d755b9d2d1a".from_hex().unwrap()], None), Err(NoTransaction));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,7 @@ pub fn consume_err<T>(s: &str, res: IoResult<T>) {
|
||||||
/// instance of it, returning the number of instances removed.
|
/// instance of it, returning the number of instances removed.
|
||||||
pub fn find_and_remove<T:Eq+::std::fmt::Show>(haystack: &mut Vec<T>, needle: &[T]) -> uint {
|
pub fn find_and_remove<T:Eq+::std::fmt::Show>(haystack: &mut Vec<T>, needle: &[T]) -> uint {
|
||||||
if needle.len() > haystack.len() { return 0; }
|
if needle.len() > haystack.len() { return 0; }
|
||||||
|
if needle.len() == 0 { return 0; }
|
||||||
|
|
||||||
let mut top = haystack.len() - needle.len();
|
let mut top = haystack.len() - needle.len();
|
||||||
let mut n_deleted = 0;
|
let mut n_deleted = 0;
|
||||||
|
@ -112,6 +113,7 @@ mod tests {
|
||||||
fn test_find_and_remove() {
|
fn test_find_and_remove() {
|
||||||
let mut v = vec![1u, 2, 3, 4, 2, 3, 4, 2, 3, 4, 5, 6, 7, 8, 9];
|
let mut v = vec![1u, 2, 3, 4, 2, 3, 4, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||||
|
|
||||||
|
assert_eq!(find_and_remove(&mut v, []), 0);
|
||||||
assert_eq!(find_and_remove(&mut v, [5, 5, 5]), 0);
|
assert_eq!(find_and_remove(&mut v, [5, 5, 5]), 0);
|
||||||
assert_eq!(v, vec![1, 2, 3, 4, 2, 3, 4, 2, 3, 4, 5, 6, 7, 8, 9]);
|
assert_eq!(v, vec![1, 2, 3, 4, 2, 3, 4, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue