/// <summary> /// <exception cref="ScriptException"></exception> /// </summary> /// <param name="txContainingThis"></param> /// <param name="index"></param> /// <param name="script"></param> /// <param name="stack"></param> private static void executeScript(Transaction txContainingThis,long index,Script script,Stack<byte[]> stack) { int opCount=0; int lastCodeSepLocation=0; Stack<byte[]> altstack=new Stack<byte[]>(); Stack<bool> ifStack=new Stack<bool>(); foreach(ScriptChunk chunk in script.chunks) { bool shouldExecute=!ifStack.Contains(false); if(!chunk.IsOpCode) { if(chunk.Data.Length>520) { throw new ScriptException("Attempted to push a data string larger than 520 bytes"); } if(!shouldExecute) { continue; } stack.Push(chunk.Data); } else { int opcode=0xFF & chunk.Data[0]; if(opcode>OP_16) { opCount++; if(opCount>201) { throw new ScriptException("More script operations than is allowed"); } } if(opcode==OP_VERIF || opcode==OP_VERNOTIF) { throw new ScriptException("Script included OP_VERIF or OP_VERNOTIF"); } if(opcode==OP_CAT || opcode==OP_SUBSTR || opcode==OP_LEFT || opcode==OP_RIGHT || opcode==OP_INVERT || opcode==OP_AND || opcode==OP_OR || opcode==OP_XOR || opcode==OP_2MUL || opcode==OP_2DIV || opcode==OP_MUL || opcode==OP_DIV || opcode==OP_MOD || opcode==OP_LSHIFT || opcode==OP_RSHIFT) { throw new ScriptException("Script included a disabled Script Op."); } switch (opcode) { case OP_IF: if(!shouldExecute) { ifStack.Push(false); continue; } if(stack.Count<1) { throw new ScriptException("Attempted OP_IF on an empty stack"); } ifStack.Push(castToBool(stack.Pop())); continue; case OP_NOTIF: if(!shouldExecute) { ifStack.Push(false); continue; } if(stack.Count<1) { throw new ScriptException("Attempted OP_NOTIF on an empty stack"); } ifStack.Push(!castToBool(stack.Pop())); continue; case OP_ELSE: if(ifStack.Count==0) { throw new ScriptException("Attempted OP_ELSE without OP_IF/NOTIF"); } ifStack.Push(!ifStack.Pop()); continue; case OP_ENDIF: if(ifStack.Count==0) { throw new ScriptException("Attempted OP_ENDIF without OP_IF/NOTIF"); } ifStack.Pop(); continue; } if(!shouldExecute) { continue; } switch(opcode) { //case OP_0: dont know why this isnt also here in the reference client case OP_1NEGATE: stack.Push(MPIHelper.Encode(BigInteger.One.Negate(),false).ReverseBytes()); break; case OP_1: case OP_2: case OP_3: case OP_4: case OP_5: case OP_6: case OP_7: case OP_8: case OP_9: case OP_10: case OP_11: case OP_12: case OP_13: case OP_14: case OP_15: case OP_16: stack.Push(MPIHelper.Encode(BigInteger.ValueOf(getOpNValue(opcode)),false).ReverseBytes()); break; case OP_NOP: break; case OP_VERIFY: if(stack.Count<1) { throw new ScriptException("Attempted OP_VERIFY on an empty stack"); } if(!castToBool(stack.Pop())) { throw new ScriptException("OP_VERIFY failed"); } break; case OP_RETURN: throw new ScriptException("Script called OP_RETURN"); case OP_TOALTSTACK: if(stack.Count<1) { throw new ScriptException("Attempted OP_TOALTSTACK on an empty stack"); } altstack.Push(stack.Pop()); break; case OP_FROMALTSTACK: if(altstack.Count<1) { throw new ScriptException("Attempted OP_TOALTSTACK on an empty altstack"); } stack.Push(altstack.Pop()); break; case OP_2DROP: if(stack.Count<2) { throw new ScriptException("Attempted OP_2DROP on a stack with size<2"); } stack.Pop(); stack.Pop(); break; case OP_2DUP: if(stack.Count<2) { throw new ScriptException("Attempted OP_2DUP on a stack with size<2"); } Iterator<byte[]> it2DUP=stack.descendingIterator(); byte[] OP2DUPtmpChunk2=it2DUP.next(); stack.Push(it2DUP.next()); stack.Push(OP2DUPtmpChunk2); break; case OP_3DUP: if(stack.Count<3) { throw new ScriptException("Attempted OP_3DUP on a stack with size<3"); } Iterator<byte[]> it3DUP=stack.descendingIterator(); byte[] OP3DUPtmpChunk3=it3DUP.next(); byte[] OP3DUPtmpChunk2=it3DUP.next(); stack.Push(it3DUP.next()); stack.Push(OP3DUPtmpChunk2); stack.Push(OP3DUPtmpChunk3); break; case OP_2OVER: if(stack.Count<4) { throw new ScriptException("Attempted OP_2OVER on a stack with size<4"); } Iterator<byte[]> it2OVER=stack.descendingIterator(); it2OVER.next(); it2OVER.next(); byte[] OP2OVERtmpChunk2=it2OVER.next(); stack.Push(it2OVER.next()); stack.Push(OP2OVERtmpChunk2); break; case OP_2ROT: if(stack.Count<6) { throw new ScriptException("Attempted OP_2ROT on a stack with size<6"); } byte[] OP2ROTtmpChunk6=stack.Pop(); byte[] OP2ROTtmpChunk5=stack.Pop(); byte[] OP2ROTtmpChunk4=stack.Pop(); byte[] OP2ROTtmpChunk3=stack.Pop(); byte[] OP2ROTtmpChunk2=stack.Pop(); byte[] OP2ROTtmpChunk1=stack.Pop(); stack.Push(OP2ROTtmpChunk3); stack.Push(OP2ROTtmpChunk4); stack.Push(OP2ROTtmpChunk5); stack.Push(OP2ROTtmpChunk6); stack.Push(OP2ROTtmpChunk1); stack.Push(OP2ROTtmpChunk2); break; case OP_2SWAP: if(stack.Count<4) { throw new ScriptException("Attempted OP_2SWAP on a stack with size<4"); } byte[] OP2SWAPtmpChunk4=stack.Pop(); byte[] OP2SWAPtmpChunk3=stack.Pop(); byte[] OP2SWAPtmpChunk2=stack.Pop(); byte[] OP2SWAPtmpChunk1=stack.Pop(); stack.Push(OP2SWAPtmpChunk3); stack.Push(OP2SWAPtmpChunk4); stack.Push(OP2SWAPtmpChunk1); stack.Push(OP2SWAPtmpChunk2); break; case OP_IFDUP: if(stack.Count<1) { throw new ScriptException("Attempted OP_IFDUP on an empty stack"); } if(castToBool(stack.getLast())) { stack.Push(stack.getLast()); } break; case OP_DEPTH: stack.Push(Utils.reverseBytes(Utils.encodeMPI(BigInteger.valueOf(stack.Count), false))); break; case OP_DROP: if(stack.Count<1) throw new ScriptException("Attempted OP_DROP on an empty stack"); stack.Pop(); break; case OP_DUP: if(stack.Count<1) throw new ScriptException("Attempted OP_DUP on an empty stack"); stack.Push(stack.getLast()); break; case OP_NIP: if(stack.Count<2) throw new ScriptException("Attempted OP_NIP on a stack with size < 2"); byte[] OPNIPtmpChunk=stack.Pop(); stack.Pop(); stack.Push(OPNIPtmpChunk); break; case OP_OVER: if(stack.Count<2) throw new ScriptException("Attempted OP_OVER on a stack with size < 2"); Iterator<byte[]> itOVER=stack.descendingIterator(); itOVER.next(); stack.Push(itOVER.next()); break; case OP_PICK: case OP_ROLL: if(stack.Count<1) { throw new ScriptException("Attempted OP_PICK/OP_ROLL on an empty stack"); } long val=castToBigInteger(stack.Pop()).longValue(); if(val<0 || val >= stack.Count) { throw new ScriptException("OP_PICK/OP_ROLL attempted to get data deeper than stack size"); } Iterator<byte[]> itPICK=stack.descendingIterator(); for(long i=0; i<val; i++) { itPICK.next(); } byte[] OPROLLtmpChunk=itPICK.next(); if(opcode == OP_ROLL) { itPICK.remove(); } stack.Push(OPROLLtmpChunk); break; case OP_ROT: if(stack.Count<3) { throw new ScriptException("Attempted OP_ROT on a stack with size<3"); } byte[] OPROTtmpChunk3=stack.Pop(); byte[] OPROTtmpChunk2=stack.Pop(); byte[] OPROTtmpChunk1=stack.Pop(); stack.Push(OPROTtmpChunk2); stack.Push(OPROTtmpChunk3); stack.Push(OPROTtmpChunk1); break; case OP_SWAP: case OP_TUCK: if(stack.Count<2) { throw new ScriptException("Attempted OP_SWAP on a stack with size<2"); } byte[] OPSWAPtmpChunk2=stack.Pop(); byte[] OPSWAPtmpChunk1=stack.Pop(); stack.Push(OPSWAPtmpChunk2); stack.Push(OPSWAPtmpChunk1); if(opcode == OP_TUCK) { stack.Push(OPSWAPtmpChunk2); } break; case OP_CAT: case OP_SUBSTR: case OP_LEFT: case OP_RIGHT: throw new ScriptException("Attempted to use disabled Script Op"); case OP_SIZE: if(stack.Count<1) { throw new ScriptException("Attempted OP_SIZE on an empty stack"); } stack.Push(Utils.reverseBytes(Utils.encodeMPI(BigInteger.valueOf(stack.getLast().Length), false))); break; case OP_INVERT: case OP_AND: case OP_OR: case OP_XOR: throw new ScriptException("Attempted to use disabled Script Op."); case OP_EQUAL: if(stack.Count<2) throw new ScriptException("Attempted OP_EQUALVERIFY on a stack with size<2"); stack.Push(Arrays.Equals(stack.Pop(), stack.Pop()) ? new byte[] {1} : new byte[] {0}); break; case OP_EQUALVERIFY: if(stack.Count<2) throw new ScriptException("Attempted OP_EQUALVERIFY on a stack with size<2"); if(!Arrays.Equals(stack.Pop(), stack.Pop())) throw new ScriptException("OP_EQUALVERIFY: non-equal data"); break; case OP_1ADD: case OP_1SUB: case OP_NEGATE: case OP_ABS: case OP_NOT: case OP_0NOTEQUAL: if(stack.Count<1) throw new ScriptException("Attempted a numeric op on an empty stack"); BigInteger numericOPnum=castToBigInteger(stack.Pop()); switch (opcode) { case OP_1ADD: numericOPnum=numericOPnum.Add(BigInteger.One); break; case OP_1SUB: numericOPnum=numericOPnum.Subtract(BigInteger.One); break; case OP_NEGATE: numericOPnum=numericOPnum.negate(); break; case OP_ABS: if(numericOPnum.CompareTo(BigInteger.Zero)<0) numericOPnum=numericOPnum.negate(); break; case OP_NOT: if(numericOPnum.Equals(BigInteger.Zero)) numericOPnum=BigInteger.One; else numericOPnum=BigInteger.Zero; break; case OP_0NOTEQUAL: if(numericOPnum.Equals(BigInteger.Zero)) numericOPnum=BigInteger.Zero; else numericOPnum=BigInteger.One; break; } stack.Push(Utils.reverseBytes(Utils.encodeMPI(numericOPnum, false))); break; case OP_2MUL: case OP_2DIV: throw new ScriptException("Attempted to use disabled Script Op."); case OP_ADD: case OP_SUB: case OP_BOOLAND: case OP_BOOLOR: case OP_NUMEQUAL: case OP_NUMNOTEQUAL: case OP_LESSTHAN: case OP_GREATERTHAN: case OP_LESSTHANOREQUAL: case OP_GREATERTHANOREQUAL: case OP_MIN: case OP_MAX: if(stack.Count<2) { throw new ScriptException("Attempted a numeric op on a stack with size<2"); } BigInteger numericOPnum2=castToBigInteger(stack.Pop()); BigInteger numericOPnum1=castToBigInteger(stack.Pop()); BigInteger numericOPresult; switch (opcode) { case OP_ADD: numericOPresult=numericOPnum1.Add(numericOPnum2); break; case OP_SUB: numericOPresult=numericOPnum1.Subtract(numericOPnum2); break; case OP_BOOLAND: if(!numericOPnum1.Equals(BigInteger.Zero) && !numericOPnum2.Equals(BigInteger.Zero)) { numericOPresult=BigInteger.One; } else { numericOPresult=BigInteger.Zero; } break; case OP_BOOLOR: if(!numericOPnum1.Equals(BigInteger.Zero) || !numericOPnum2.Equals(BigInteger.Zero)) { numericOPresult=BigInteger.One; } else { numericOPresult=BigInteger.Zero; } break; case OP_NUMEQUAL: if(numericOPnum1.Equals(numericOPnum2)) { numericOPresult=BigInteger.One; } else { numericOPresult=BigInteger.Zero; } break; case OP_NUMNOTEQUAL: if(!numericOPnum1.Equals(numericOPnum2)) { numericOPresult=BigInteger.One; } else { numericOPresult=BigInteger.Zero; } break; case OP_LESSTHAN: if(numericOPnum1.CompareTo(numericOPnum2)<0) { numericOPresult=BigInteger.One; } else { numericOPresult=BigInteger.Zero; } break; case OP_GREATERTHAN: if(numericOPnum1.CompareTo(numericOPnum2)>0) { numericOPresult=BigInteger.One; } else { numericOPresult=BigInteger.Zero; } break; case OP_LESSTHANOREQUAL: if(numericOPnum1.CompareTo(numericOPnum2)<=0) numericOPresult=BigInteger.One; else numericOPresult=BigInteger.Zero; break; case OP_GREATERTHANOREQUAL: if(numericOPnum1.CompareTo(numericOPnum2)>=0) { numericOPresult=BigInteger.One; } else { numericOPresult=BigInteger.Zero; } break; case OP_MIN: if(numericOPnum1.CompareTo(numericOPnum2)<0) { numericOPresult=numericOPnum1; } else { numericOPresult=numericOPnum2; } break; case OP_MAX: if(numericOPnum1.CompareTo(numericOPnum2) > 0) { numericOPresult=numericOPnum1; } else { numericOPresult=numericOPnum2; } break; default: throw new RuntimeException("Opcode switched at runtime?"); } stack.Push(Utils.reverseBytes(Utils.encodeMPI(numericOPresult, false))); break; case OP_MUL: case OP_DIV: case OP_MOD: case OP_LSHIFT: case OP_RSHIFT: throw new ScriptException("Attempted to use disabled Script Op."); case OP_NUMEQUALVERIFY: if(stack.Count<2) { throw new ScriptException("Attempted OP_NUMEQUALVERIFY on a stack with size<2"); } BigInteger OPNUMEQUALVERIFYnum2=castToBigInteger(stack.Pop()); BigInteger OPNUMEQUALVERIFYnum1=castToBigInteger(stack.Pop()); if(!OPNUMEQUALVERIFYnum1.Equals(OPNUMEQUALVERIFYnum2)) { throw new ScriptException("OP_NUMEQUALVERIFY failed"); } break; case OP_WITHIN: if(stack.Count<3) { throw new ScriptException("Attempted OP_WITHIN on a stack with size<3"); } BigInteger OPWITHINnum3=castToBigInteger(stack.Pop()); BigInteger OPWITHINnum2=castToBigInteger(stack.Pop()); BigInteger OPWITHINnum1=castToBigInteger(stack.Pop()); if(OPWITHINnum2.CompareTo(OPWITHINnum1) <= 0 && OPWITHINnum1.CompareTo(OPWITHINnum3)<0) { stack.Push(Utils.reverseBytes(Utils.encodeMPI(BigInteger.One, false))); } else { stack.Push(Utils.reverseBytes(Utils.encodeMPI(BigInteger.Zero, false))); } break; case OP_RIPEMD160: if(stack.Count<1) { throw new ScriptException("Attempted OP_RIPEMD160 on an empty stack"); } RIPEMD160Digest digest=new RIPEMD160Digest(); byte[] dataToHash=stack.Pop(); digest.update(dataToHash, 0, dataToHash.Length); byte[] ripmemdHash=new byte[20]; digest.doFinal(ripmemdHash, 0); stack.Push(ripmemdHash); break; case OP_SHA1: if(stack.Count<1) { throw new ScriptException("Attempted OP_SHA1 on an empty stack"); } stack.Push(MessageDigest.getInstance("SHA-1").digest(stack.Pop())); break; case OP_SHA256: if(stack.Count<1) { throw new ScriptException("Attempted OP_SHA256 on an empty stack"); } stack.Push(MessageDigest.getInstance("SHA-256").digest(stack.Pop())); break; case OP_HASH160: if(stack.Count<1) { throw new ScriptException("Attempted OP_HASH160 on an empty stack"); } stack.Push(Utils.sha256hash160(stack.Pop())); break; case OP_HASH256: if(stack.Count<1) { throw new ScriptException("Attempted OP_SHA256 on an empty stack"); } stack.Push(Utils.doubleDigest(stack.Pop())); break; case OP_CODESEPARATOR: lastCodeSepLocation=chunk.startLocationInProgram + 1; break; case OP_CHECKSIG: case OP_CHECKSIGVERIFY: if(stack.Count<2) { throw new ScriptException("Attempted OP_CHECKSIG(VERIFY) on a stack with size<2"); } byte[] CHECKSIGpubKey=stack.Pop(); byte[] CHECKSIGsig=stack.Pop(); byte[] CHECKSIGconnectedScript=Arrays.copyOfRange(script.program, lastCodeSepLocation, script.program.Length); UnsafeByteArrayOutputStream OPCHECKSIGOutStream=new UnsafeByteArrayOutputStream(CHECKSIGsig.Length + 1); writeBytes(OPCHECKSIGOutStream,CHECKSIGsig); CHECKSIGconnectedScript=removeAllInstancesOf(CHECKSIGconnectedScript, OPCHECKSIGOutStream.toByteArray()); // TODO: Use int for indexes everywhere, we can't have that many inputs/outputs Sha256Hash CHECKSIGhash=txContainingThis.hashTransactionForSignature((int)index,CHECKSIGconnectedScript,CHECKSIGsig[CHECKSIGsig.Length-1]); bool CHECKSIGsigValid; try { CHECKSIGsigValid=ECKey.verify(CHECKSIGhash.getBytes(), Arrays.copyOf(CHECKSIGsig, CHECKSIGsig.Length - 1), CHECKSIGpubKey); } catch(Exception) { // There is (at least) one exception that could be hit here (EOFException, if the sig is too short) // Because I can't verify there aren't more, we use a very generic Exception catch CHECKSIGsigValid=false; } if(opcode==OP_CHECKSIG) { stack.Push(CHECKSIGsigValid?new byte[] {1}:new byte[] {0}); } else if(opcode==OP_CHECKSIGVERIFY) { if(!CHECKSIGsigValid) { throw new ScriptException("Script failed OP_CHECKSIGVERIFY"); } } break; case OP_CHECKMULTISIG: case OP_CHECKMULTISIGVERIFY: if(stack.Count<2) { throw new ScriptException("Attempted OP_CHECKMULTISIG(VERIFY) on a stack with size<2"); } int CHECKMULTISIGpubKeyCount=castToBigInteger(stack.Pop()).intValue(); if(CHECKMULTISIGpubKeyCount<0 || CHECKMULTISIGpubKeyCount > 20) { throw new ScriptException("OP_CHECKMULTISIG(VERIFY) with pubkey count out of range"); } opCount+=CHECKMULTISIGpubKeyCount; if(opCount>201) { throw new ScriptException("Total op count > 201 during OP_CHECKMULTISIG(VERIFY)"); } if(stack.Count<CHECKMULTISIGpubKeyCount+1) { throw new ScriptException("Attempted OP_CHECKMULTISIG(VERIFY) on a stack with size<num_of_pubkeys + 2"); } LinkedList<byte[]> CHECKMULTISIGpubkeys=new LinkedList<byte[]>(); for(int i=0; i<CHECKMULTISIGpubKeyCount; i++) CHECKMULTISIGpubkeys.Add(stack.Pop()); int CHECKMULTISIGsigCount=castToBigInteger(stack.Pop()).intValue(); if(CHECKMULTISIGsigCount<0 || CHECKMULTISIGsigCount > CHECKMULTISIGpubKeyCount) throw new ScriptException("OP_CHECKMULTISIG(VERIFY) with sig count out of range"); if(stack.Count<CHECKMULTISIGsigCount + 1) throw new ScriptException("Attempted OP_CHECKMULTISIG(VERIFY) on a stack with size<num_of_pubkeys + num_of_signatures + 3"); LinkedList<byte[]> CHECKMULTISIGsigs=new LinkedList<byte[]>(); for(int i=0; i<CHECKMULTISIGsigCount; i++) CHECKMULTISIGsigs.Add(stack.Pop()); byte[] CHECKMULTISIGconnectedScript=Arrays.copyOfRange(script.program, lastCodeSepLocation, script.program.Length); foreach(byte[] CHECKMULTISIGsig in CHECKMULTISIGsigs) { UnsafeByteArrayOutputStream OPCHECKMULTISIGOutStream=new UnsafeByteArrayOutputStream(CHECKMULTISIGsig.Length + 1); writeBytes(OPCHECKMULTISIGOutStream,CHECKMULTISIGsig); CHECKMULTISIGconnectedScript=removeAllInstancesOf(CHECKMULTISIGconnectedScript, OPCHECKMULTISIGOutStream.toByteArray()); } bool CHECKMULTISIGValid=true; while (CHECKMULTISIGsigs.Count > 0) { byte[] CHECKMULTISIGsig=CHECKMULTISIGsigs.getFirst(); byte[] CHECKMULTISIGpubKey=CHECKMULTISIGpubkeys.pollFirst(); // We could reasonably move this out of the loop, // but because signature verification is significantly more expensive than hashing, its not a big deal Sha256Hash CHECKMULTISIGhash=txContainingThis.hashTransactionForSignature((int)index, CHECKMULTISIGconnectedScript, CHECKMULTISIGsig[CHECKMULTISIGsig.Length - 1]); try { if(ECKey.verify(CHECKMULTISIGhash.getBytes(), Arrays.copyOf(CHECKMULTISIGsig, CHECKMULTISIGsig.Length - 1), CHECKMULTISIGpubKey)) CHECKMULTISIGsigs.pollFirst(); } catch (Exception e) { // There is (at least) one exception that could be hit here (EOFException, if the sig is too short) // Because I can't verify there aren't more, we use a very generic Exception catch } if(CHECKMULTISIGsigs.Count > CHECKMULTISIGpubkeys.Count) { CHECKMULTISIGValid=false; break; } } // We uselessly remove a stack object to emulate a reference client bug stack.Pop(); if(opcode == OP_CHECKMULTISIG) { stack.Push(CHECKMULTISIGValid?new byte[] {1}:new byte[] {0}); } else if(opcode==OP_CHECKMULTISIGVERIFY) { if(!CHECKMULTISIGValid) { throw new ScriptException("Script failed OP_CHECKMULTISIGVERIFY"); } } break; case OP_NOP1: case OP_NOP2: case OP_NOP3: case OP_NOP4: case OP_NOP5: case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10: break; default: throw new ScriptException("Script used a reserved Op Code"); } } if(stack.Count+altstack.Count>1000 || stack.Count+altstack.Count<0) { throw new ScriptException("Stack size exceeded range"); } } if(ifStack.Count!=0) { throw new ScriptException("OP_IF/OP_NOTIF without OP_ENDIF"); } }