package org.ripple.power.txns.btc; import java.io.EOFException; import java.math.BigInteger; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.Iterator; import java.util.List; import org.ripple.power.Helper; /** * Transaction script parser * * A script is a small program contained in the transaction which determines whether or not * an output can be spent. The first half of the script is provided by the transaction input * and the second half of the script is provided by the connected transaction output. */ public class ScriptParser { /** Verbose debug flag */ private static final boolean verboseDebug = false; /** * Processes a transaction script to determine if the spending transaction * is authorized to spend the output coins * * @param txInput Transaction input spending the coins * @param txOutput Transaction output providing the coins * @param chainHeight Current block chain height * @return The script result * @throws ScriptException Unable to process the transaction script */ public static boolean process(TransactionInput txInput, TransactionOutput txOutput, int chainHeight) throws ScriptException { Transaction tx = txInput.getTransaction(); boolean txValid = true; boolean pay2ScriptHash = false; List<byte[]> scriptStack = new ArrayList<>(5); List<StackElement> elemStack = new ArrayList<>(25); List<StackElement> altStack = new ArrayList<>(5); byte[] inputScriptBytes = txInput.getScriptBytes(); if (inputScriptBytes.length != 0){ scriptStack.add(inputScriptBytes); } byte[] outputScriptBytes = txOutput.getScriptBytes(); if (outputScriptBytes.length != 0){ scriptStack.add(outputScriptBytes); } if (verboseDebug){ System.out.printf("Processing scripts for transaction %s\n", tx.getHashAsString()); } // // The result is FALSE if there are no scripts to process since an empty stack // is the same as FALSE // if (scriptStack.isEmpty()) return false; // // Check for a pay-to-script-hash output (BIP0016) // // The output script: OP_HASH160 <20-byte hash> OP_EQUAL // The inputs script: can contain only data elements and must have at least two elements // The block height must be greater than 175,000 // if (chainHeight > 175000 && scriptStack.size() == 2 && outputScriptBytes.length == 23 && outputScriptBytes[0] == (byte)ScriptOpCodes.OP_HASH160 && outputScriptBytes[1] == 20 && outputScriptBytes[22] == (byte)ScriptOpCodes.OP_EQUAL) { int offset = 0; int count = 0; pay2ScriptHash = true; try { while (offset < inputScriptBytes.length) { int opcode = (int)inputScriptBytes[offset++]&0xff; if (opcode <= ScriptOpCodes.OP_PUSHDATA4) { int[] result = Script.getDataLength(opcode, inputScriptBytes, offset); offset = result[0] + result[1]; count++; } else { pay2ScriptHash = false; break; } } if (count < 2) pay2ScriptHash = false; } catch (EOFException exc) { throw new ScriptException(String.format("End-of-data while scanning input script\n Tx %s", tx.getHash().toString()), exc); } } // // Process the script segments // try { boolean p2sh = pay2ScriptHash; while (txValid && !scriptStack.isEmpty()) { txValid = processScript(txInput, scriptStack, elemStack, altStack, p2sh); scriptStack.remove(0); if (pay2ScriptHash && !scriptStack.isEmpty()) { byte[] scriptBytes = scriptStack.get(0); p2sh = (scriptBytes.length == 23 && scriptBytes[0] == (byte)ScriptOpCodes.OP_HASH160 && scriptBytes[1] == 20 && scriptBytes[22] == (byte)ScriptOpCodes.OP_EQUAL); } } } catch (Throwable exc) { throw new ScriptException(String.format("%s: %s\n Tx %s\n Index %d", exc.getClass().getName(), exc.getMessage(), tx.getHash().toString(), txInput.getIndex()), exc); } // // The script is successful if a non-zero value is on the top of the stack. An // empty stack is the same as a FALSE value. // if (txValid) txValid = (!elemStack.isEmpty() ? popStack(elemStack).isTrue() : false); return txValid; } /** * Processes the current script * * @param txInput The current transaction input * @param scriptStack Script stack * @param elemStack Element stack * @param altStack Alternate stack * @param p2sh TRUE if this is a pay-to-script-hash * @return Script result * @throws EOFException End-of-data processing script * @throws ScriptException Unable to process script */ private static boolean processScript(TransactionInput txInput, List<byte[]> scriptStack, List<StackElement> elemStack, List<StackElement> altStack, boolean p2sh) throws EOFException, ScriptException { boolean txValid = true; boolean skipping = false; byte[] scriptBytes = scriptStack.get(0); int offset = 0; int lastSeparator = 0; Deque<Boolean> ifStack = new ArrayDeque<>(); if (verboseDebug) System.out.println("Processing script segment"); // // Process the script opcodes // while (txValid && offset<scriptBytes.length) { StackElement elem, elem1, elem2, elem3, elem4; BigInteger big1, big2, big3; byte[] bytes; int dataToRead, size, index; int opcode = (int)scriptBytes[offset++]&0xff; if (verboseDebug) System.out.printf("Processing OpCode %02X\n", opcode); skipping = !ifStack.isEmpty() && !ifStack.peek(); if (opcode <= ScriptOpCodes.OP_PUSHDATA4) { // Data push opcodes int[] result = Script.getDataLength(opcode, scriptBytes, offset); dataToRead = result[0]; offset = result[1]; if (offset+dataToRead > scriptBytes.length) throw new EOFException("End-of-data while processing script"); if (!skipping) { bytes = new byte[dataToRead]; if (dataToRead > 0) System.arraycopy(scriptBytes, offset, bytes, 0, dataToRead); elemStack.add(new StackElement(bytes)); } offset += dataToRead; } else if (opcode == ScriptOpCodes.OP_IF) { // IF clause processed if top stack element is true ifStack.push(skipping ? false : popStack(elemStack).isTrue()); if (verboseDebug) System.out.printf("OP_IF(%d) status %s\n", ifStack.size(), ifStack.peek()); } else if (opcode == ScriptOpCodes.OP_NOTIF) { // IF clause process if top stack element is false ifStack.push(skipping ? false : !popStack(elemStack).isTrue()); if (verboseDebug) System.out.printf("OP_NOTIF(%d) status %s\n", ifStack.size(), ifStack.peek()); } else if (opcode == ScriptOpCodes.OP_ENDIF) { // IF processing completed if (ifStack.isEmpty()) throw new ScriptException("OP_ENDIF without matching OP_IF"); if (verboseDebug) System.out.printf("IF(%d) ended\n", ifStack.size()); ifStack.pop(); } else if (opcode == ScriptOpCodes.OP_ELSE) { if (ifStack.isEmpty()) throw new ScriptException("OP_ELSE without matching OP_IF"); ifStack.pop(); if (ifStack.isEmpty() || ifStack.peek()) ifStack.push(skipping); else ifStack.push(false); if (verboseDebug) System.out.printf("OP_ELSE status %s\n", ifStack.peek()); } else if (skipping) { // We are skipping, so don't execute opcodes if (verboseDebug) System.out.println("Skipping OpCode"); } else if (opcode >= ScriptOpCodes.OP_1 && opcode <= ScriptOpCodes.OP_16) { // Push 1 to 16 onto the stack based on the opcode (0x51-0x60) bytes = new byte[1]; bytes[0] = (byte)(opcode&0x0f); if (bytes[0] == 0) bytes[0] = (byte)16; elemStack.add(new StackElement(bytes)); } else if (opcode >= ScriptOpCodes.OP_NOP1 && opcode <= ScriptOpCodes.OP_NOP10) { // Reserved for future expansion } else { switch (opcode) { case ScriptOpCodes.OP_NOP: // Do nothing break; case ScriptOpCodes.OP_RETURN: // Mark transaction invalid txValid = false; break; case ScriptOpCodes.OP_CODESEPARATOR: // Signature operations ignore data before the separator lastSeparator = offset; break; case ScriptOpCodes.OP_1NEGATE: // Push -1 onto the stack elemStack.add(new StackElement(BigInteger.ONE.negate())); break; case ScriptOpCodes.OP_NOT: // Reverse the top stack element (TRUE->FALSE, FALSE->TRUE) elemStack.add(new StackElement(!popStack(elemStack).isTrue())); break; case ScriptOpCodes.OP_0NOTEQUAL: // Returns 0 if the input is 0, otherwise returns 1 elemStack.add(new StackElement(popStack(elemStack).isTrue())); break; case ScriptOpCodes.OP_GREATERTHAN: // Returns 1 if element A is greater than element B big1 = popStack(elemStack).getBigInteger(); // B big2 = popStack(elemStack).getBigInteger(); // A elemStack.add(new StackElement(big2.compareTo(big1)>0)); break; case ScriptOpCodes.OP_LESSTHAN: // Returns 1 if element A is less than element B big1 = popStack(elemStack).getBigInteger(); // B big2 = popStack(elemStack).getBigInteger(); // A elemStack.add(new StackElement(big2.compareTo(big1)<0)); break; case ScriptOpCodes.OP_GREATERTHANOREQUAL: // Returns 1 if element A is greater than or equal to element B big1 = popStack(elemStack).getBigInteger(); // B big2 = popStack(elemStack).getBigInteger(); // A elemStack.add(new StackElement(big2.compareTo(big1)>=0)); break; case ScriptOpCodes.OP_LESSTHANOREQUAL: // Returns 1 if element A is less than or equal to element B big1 = popStack(elemStack).getBigInteger(); // B big2 = popStack(elemStack).getBigInteger(); // A elemStack.add(new StackElement(big2.compareTo(big1)<=0)); break; case ScriptOpCodes.OP_ADD: // Add the top stack elements big1 = popStack(elemStack).getBigInteger(); big2 = popStack(elemStack).getBigInteger(); elemStack.add(new StackElement(big2.add(big1))); break; case ScriptOpCodes.OP_1ADD: // Add one to the top stack element elemStack.add(new StackElement(popStack(elemStack).getBigInteger().add(BigInteger.ONE))); break; case ScriptOpCodes.OP_SUB: // Subtract the top stack elements big1 = popStack(elemStack).getBigInteger(); big2 = popStack(elemStack).getBigInteger(); elemStack.add(new StackElement(big2.subtract(big1))); break; case ScriptOpCodes.OP_1SUB: // Subtract one from the top stack element elemStack.add(new StackElement(popStack(elemStack).getBigInteger().subtract(BigInteger.ONE))); break; case ScriptOpCodes.OP_MUL: // Multiply the top stack elements big1 = popStack(elemStack).getBigInteger(); big2 = popStack(elemStack).getBigInteger(); elemStack.add(new StackElement(big2.multiply(big1))); break; case ScriptOpCodes.OP_DIV: // Divide the top stack elements big1 = popStack(elemStack).getBigInteger(); big2 = popStack(elemStack).getBigInteger(); elemStack.add(new StackElement(big2.divide(big1))); break; case ScriptOpCodes.OP_NEGATE: // Reverse the sign of the top stack element elemStack.add(new StackElement(popStack(elemStack).getBigInteger().negate())); break; case ScriptOpCodes.OP_ABS: // Get the absolute value of the top stack element elemStack.add(new StackElement(popStack(elemStack).getBigInteger().abs())); break; case ScriptOpCodes.OP_MIN: // Compare top 2 stack elements and replace with the smaller elem1 = popStack(elemStack); elem2 = popStack(elemStack); elemStack.add(elem1.compareTo(elem2)<=0 ? elem1 : elem2); break; case ScriptOpCodes.OP_MAX: // Compare top 2 stack elements and replace with the larger elem1 = popStack(elemStack); elem2 = popStack(elemStack); elemStack.add(elem1.compareTo(elem2)>=0 ? elem1 : elem2); break; case ScriptOpCodes.OP_WITHIN: // Return TRUE if the value is within the min/max values big1 = popStack(elemStack).getBigInteger(); // MAX big2 = popStack(elemStack).getBigInteger(); // MIN big3 = popStack(elemStack).getBigInteger(); // VALUE elemStack.add(new StackElement(big3.compareTo(big2)>=0 && big3.compareTo(big1)<0)); break; case ScriptOpCodes.OP_NUMEQUAL: case ScriptOpCodes.OP_NUMEQUALVERIFY: // Compare the two top stack elements and return TRUE if they are equal big1 = popStack(elemStack).getBigInteger(); big2 = popStack(elemStack).getBigInteger(); elemStack.add(new StackElement(big1.compareTo(big2)==0)); if (opcode == ScriptOpCodes.OP_NUMEQUALVERIFY) txValid = processVerify(elemStack); break; case ScriptOpCodes.OP_NUMNOTEQUAL: // Compare the two top stack elements and return TRUE if they are not equal big1 = popStack(elemStack).getBigInteger(); big2 = popStack(elemStack).getBigInteger(); elemStack.add(new StackElement(big1.compareTo(big2)!=0)); break; case ScriptOpCodes.OP_BOOLAND: // Result is TRUE if two top elements are TRUE elem1 = popStack(elemStack); elem2 = popStack(elemStack); elemStack.add(new StackElement(elem1.isTrue() && elem2.isTrue())); break; case ScriptOpCodes.OP_BOOLOR: // Result is TRUE if at least one of the two top elements is TRUE elem1 = popStack(elemStack); elem2 = popStack(elemStack); elemStack.add(new StackElement(elem1.isTrue() || elem2.isTrue())); break; case ScriptOpCodes.OP_TOALTSTACK: // Move from main stack to alternate stack altStack.add(popStack(elemStack)); break; case ScriptOpCodes.OP_FROMALTSTACK: // Move from alternate stack to main stack elemStack.add(popStack(altStack)); break; case ScriptOpCodes.OP_DROP: // Remove the top stack element popStack(elemStack); break; case ScriptOpCodes.OP_2DROP: // Remove the top two stack elements popStack(elemStack); popStack(elemStack); break; case ScriptOpCodes.OP_NIP: // Remove the second-from-top stack element size = elemStack.size(); if (size < 2) throw new ScriptException("Stack underrun on OP_NIP"); elemStack.remove(size-2); break; case ScriptOpCodes.OP_OVER: // Copy the second-from-top stack element to the top of the stack size = elemStack.size(); if (size<2) throw new ScriptException("Stack underrun on OP_OVER"); elemStack.add(elemStack.get(size-2)); break; case ScriptOpCodes.OP_2OVER: // Copy the pair of elements behind the top pair to the top of the stack size = elemStack.size(); if (size < 4) throw new ScriptException("Stack underrun on OP_2OVER"); elem1 = elemStack.get(size-4); elem2 = elemStack.get(size-3); elemStack.add(elem1); elemStack.add(elem2); break; case ScriptOpCodes.OP_PICK: // Copy the nth-from-top stack element to the top of the stack index = popStack(elemStack).getBigInteger().intValue(); size = elemStack.size(); if (index >= size) throw new ScriptException("Stack underrun on OP_PICK"); elemStack.add(elemStack.get(size-index-1)); break; case ScriptOpCodes.OP_ROLL: // Move the nth-from-top stack element to the top of the stack index = popStack(elemStack).getBigInteger().intValue(); size = elemStack.size(); if (index >= size) throw new ScriptException("Stack underrun on OP_ROLL"); elemStack.add(elemStack.remove(size-index-1)); break; case ScriptOpCodes.OP_ROT: // Rotate the top three stack elements size = elemStack.size(); if (size < 3) throw new ScriptException("Stack underrun on OP_ROT"); elemStack.add(elemStack.remove(size-3)); break; case ScriptOpCodes.OP_2ROT: // Rotate the top three pairs of stack elements size = elemStack.size(); if (size < 6) throw new ScriptException("Stack overrun on OP2ROT"); elemStack.add(elemStack.remove(size-6)); elemStack.add(elemStack.remove(size-6)); break; case ScriptOpCodes.OP_TUCK: // Copy the top stack element before the second-from-top element size = elemStack.size(); if (size < 2) throw new ScriptException("Stack underrun on OP_TUCK"); elemStack.add(size-2, elemStack.get(size-1)); break; case ScriptOpCodes.OP_DUP: // Duplicate the top stack element elemStack.add(new StackElement(peekStack(elemStack))); break; case ScriptOpCodes.OP_2DUP: // Duplicate the top two stack elements size = elemStack.size(); if (size < 2) throw new ScriptException("Stack underron on OP_2DUP"); elemStack.add(elemStack.get(size-2)); elemStack.add(elemStack.get(size-1)); break; case ScriptOpCodes.OP_3DUP: // Duplicate the top three stack elements size = elemStack.size(); if (size < 3) throw new ScriptException("Stack underron on OP_3DUP"); elemStack.add(elemStack.get(size-3)); elemStack.add(elemStack.get(size-2)); elemStack.add(elemStack.get(size-1)); break; case ScriptOpCodes.OP_IFDUP: // Duplicate top stack element if it is not zero elem = peekStack(elemStack); if (elem.isTrue()) elemStack.add(new StackElement(elem)); break; case ScriptOpCodes.OP_SWAP: // Swap the top two elements elem1 = popStack(elemStack); elem2 = popStack(elemStack); elemStack.add(elem1); elemStack.add(elem2); break; case ScriptOpCodes.OP_2SWAP: // Swap the top two pairs of elements elem1 = popStack(elemStack); elem2 = popStack(elemStack); elem3 = popStack(elemStack); elem4 = popStack(elemStack); elemStack.add(elem2); elemStack.add(elem1); elemStack.add(elem4); elemStack.add(elem3); break; case ScriptOpCodes.OP_DEPTH: // Push the stack depth elemStack.add(new StackElement(BigInteger.valueOf(elemStack.size()))); break; case ScriptOpCodes.OP_SIZE: // Push the size of the top stack element elemStack.add(new StackElement(BigInteger.valueOf(peekStack(elemStack).getBytes().length))); break; case ScriptOpCodes.OP_VERIFY: // Verify the top stack element txValid = processVerify(elemStack); break; case ScriptOpCodes.OP_EQUAL: case ScriptOpCodes.OP_EQUALVERIFY: // Push 1 (TRUE) if top two stack elements are equal, else push 0 (FALSE) bytes = new byte[1]; elem1 = popStack(elemStack); elem2 = popStack(elemStack); if (elem1.equals(elem2)) bytes[0] = (byte)1; elemStack.add(new StackElement(bytes)); if (opcode == ScriptOpCodes.OP_EQUAL) { if (p2sh && scriptStack.size()>1 && bytes[0]==1) { // Remove TRUE from the stack so that we are left with just the remaining // data elements from the input script (OP_EQUAL is the last opcode // in the output script) popStack(elemStack); } } else { txValid = processVerify(elemStack); } break; case ScriptOpCodes.OP_RIPEMD160: // RIPEMD160 hash of the top stack element elemStack.add(new StackElement(Helper.hash160(popStack(elemStack).getBytes()))); break; case ScriptOpCodes.OP_SHA1: // SHA-1 hash of top stack element elemStack.add(new StackElement(Helper.sha1Hash(popStack(elemStack).getBytes()))); break; case ScriptOpCodes.OP_SHA256: // SHA-256 hash of top stack element elemStack.add(new StackElement(Helper.singleDigest(popStack(elemStack).getBytes()))); break; case ScriptOpCodes.OP_HASH160: // SHA-256 hash followed by RIPEMD160 hash of top stack element elem = popStack(elemStack); elemStack.add(new StackElement(Helper.sha256Hash160(elem.getBytes()))); // Save the deserialized script for pay-to-hash-script processing if (p2sh && elem.getBytes().length>0) scriptStack.add(elem.getBytes()); break; case ScriptOpCodes.OP_HASH256: // Double SHA-256 hash of top stack element elemStack.add(new StackElement(Helper.doubleDigest(popStack(elemStack).getBytes()))); break; case ScriptOpCodes.OP_CHECKSIG: case ScriptOpCodes.OP_CHECKSIGVERIFY: // Check single signature processCheckSig(txInput, elemStack, scriptBytes, lastSeparator); if (opcode == ScriptOpCodes.OP_CHECKSIGVERIFY) txValid = processVerify(elemStack); break; case ScriptOpCodes.OP_CHECKMULTISIG: case ScriptOpCodes.OP_CHECKMULTISIGVERIFY: // Check multiple signatures processMultiSig(txInput, elemStack, scriptBytes, lastSeparator); if (opcode == ScriptOpCodes.OP_CHECKMULTISIGVERIFY) txValid = processVerify(elemStack); break; case ScriptOpCodes.OP_CAT: case ScriptOpCodes.OP_SUBSTR: case ScriptOpCodes.OP_LEFT: case ScriptOpCodes.OP_RIGHT: case ScriptOpCodes.OP_INVERT: case ScriptOpCodes.OP_AND: case ScriptOpCodes.OP_OR: case ScriptOpCodes.OP_XOR: case ScriptOpCodes.OP_2MUL: case ScriptOpCodes.OP_2DIV: case ScriptOpCodes.OP_MOD: case ScriptOpCodes.OP_LSHIFT: case ScriptOpCodes.OP_RSHIFT: // Disabled opcode throw new ScriptException(String.format("Disabled script opcode %s (%d)", ScriptOpCodes.getOpCodeName(opcode), opcode)); case ScriptOpCodes.OP_RESERVED: case ScriptOpCodes.OP_RESERVED1: case ScriptOpCodes.OP_RESERVED2: case ScriptOpCodes.OP_VER: case ScriptOpCodes.OP_VERIF: case ScriptOpCodes.OP_VERNOTIF: // Reserved opcodes allowed only when skipping if (ifStack.isEmpty() || ifStack.peek()) { throw new ScriptException(String.format("Reserved script opcode %s (%d)", ScriptOpCodes.getOpCodeName(opcode), opcode)); } break; default: throw new ScriptException(String.format("Unsupported script opcode %s(%d)", ScriptOpCodes.getOpCodeName(opcode), opcode)); } } } return txValid; } /** * Returns the top element from the stack but does not remove it from the stack * * @param elemStack The element stack * @return The top stack element * @throws ScriptException The stack is empty */ private static StackElement peekStack(List<StackElement> elemStack) throws ScriptException { if (elemStack.isEmpty()) throw new ScriptException("Stack underrun"); return elemStack.get(elemStack.size()-1); } /** * Pop the top element from the stack and return it * * @param elemStack The element stack * @return The top stack element * @throws ScriptException The stack is empty */ private static StackElement popStack(List<StackElement> elemStack) throws ScriptException { if (elemStack.isEmpty()) throw new ScriptException("Stack underrun"); return elemStack.remove(elemStack.size()-1); } /** * Process OP_VERIFY * * Checks the top element on the stack and removes it if it is non-zero. The return value * is TRUE if the top element is non-zero and FALSE otherwise. * * @param elemStack The element stack * @return TRUE if the top stack element is non-zero */ private static boolean processVerify(List<StackElement> elemStack) { boolean txValid; int index = elemStack.size()-1; if (index < 0) { txValid = false; } else if (elemStack.get(index).isTrue()) { txValid = true; elemStack.remove(index); } else { txValid = false; } return txValid; } /** * Process OP_CHECKSIG * * The stack must contain the signature and the public key. The public key is * used to verify the signature. TRUE is pushed on the stack if the signature * is valid, otherwise FALSE is pushed on the stack. * * @param txInput The current transaction input * @param elemStack The element stack * @param scriptBytes The current script program * @param lastSeparator The last code separator offset or zero * @throws ScriptException Unable to verify signature */ private static void processCheckSig(TransactionInput txInput, List<StackElement> elemStack, byte[] scriptBytes, int lastSeparator) throws ScriptException { byte[] bytes; boolean result; // // Check the signature // // Make sure the public key starts with x'02', x'03' or x'04'. Otherwise, // Bouncycastle throws an illegal argument exception. We will return FALSE // if we find an invalid public key. // StackElement pubKey = popStack(elemStack); StackElement sig = popStack(elemStack); // // The reference client returns FALSE for a zero-length signature // if (sig.getBytes().length == 0) { elemStack.add(new StackElement(false)); return; } // // Check the signature // // The script subprogram starts following the last code separator and all instances // of the signature are removed // bytes = pubKey.getBytes(); if (bytes.length == 0) { result = false; } else if (!ECKey.isPubKeyCanonical(bytes)) { result = false; } else { List<StackElement> pubKeys = new ArrayList<>(); pubKeys.add(pubKey); byte[] subProgram = Script.removeDataElement(sig.getBytes(), scriptBytes, lastSeparator); result = checkSig(txInput, sig, pubKeys, subProgram); } // // Push the result on the stack // elemStack.add(new StackElement(result)); } /** * Process OP_MULTISIG * * The stack must contain at least one signature and at least one public key. * Each public key is tested against each signature until a valid signature is * found. All signatures must be verified but all public keys do not need to * be used. A public key is removed from the list once it has been used to * verify a signature. * * TRUE is pushed on the stack if all signatures have been verified, * otherwise FALSE is pushed on the stack. * * @param txInput The current transaction input * @param elemStack The element stack * @param scriptBytes The current script program * @param lastSeparator The last code separator offset or zero * @throws ScriptException Unable to verify signature */ private static void processMultiSig(TransactionInput txInput, List<StackElement> elemStack, byte[] scriptBytes, int lastSeparator) throws ScriptException { List<StackElement> keys = new ArrayList<>(20); List<StackElement> sigs = new ArrayList<>(20); boolean isValid = true; StackElement elem; byte[] bytes; // // Get the public keys // // Some transactions are storing application data as one of the public // keys. So we need to check for a valid initial byte (02, 03, 04). // The garbage key will be ignored and the transaction will be valid as long // as the signature is verified using one of the valid keys. // int pubKeyCount = popStack(elemStack).getBigInteger().intValue(); for (int i=0; i<pubKeyCount; i++) { elem = popStack(elemStack); bytes = elem.getBytes(); if (bytes.length != 0 && ECKey.isPubKeyCanonical(bytes)) keys.add(elem); } // // Get the signatures // int sigCount = popStack(elemStack).getBigInteger().intValue(); for (int i=0; i<sigCount; i++) sigs.add(popStack(elemStack)); // // Due to a bug in the reference client, an extra element is removed from the stack // popStack(elemStack); // // The script subprogram starts following the last code separator and all instances // of all signature are removed // byte[] subProgram = Arrays.copyOfRange(scriptBytes, lastSeparator, scriptBytes.length); for (StackElement sig : sigs) subProgram = Script.removeDataElement(sig.getBytes(), subProgram, 0); // // Verify each signature and stop if we have a verification failure // // We will stop when all signatures have been verified or there are no more // public keys available // for (StackElement sig : sigs) { if (keys.isEmpty()) { isValid = false; break; } isValid = checkSig(txInput, sig, keys, subProgram); if (!isValid) break; } // // Push the result on the stack // elemStack.add(new StackElement(isValid)); } /** * Checks the transaction signature * * The signature is valid if it is signed by one of the supplied public keys. * * @param txInput The current transaction input * @param sig The signature to be verified * @param pubKeys The public keys to be checked * @param subProgram The script subprogram * @return TRUE if the signature is valid, FALSE otherwise * @throw ScriptException Unable to verify signature */ private static boolean checkSig(TransactionInput txInput, StackElement sig, List<StackElement> pubKeys, byte[] subProgram) throws ScriptException { byte[] sigBytes = sig.getBytes(); boolean isValid = false; if (sigBytes.length < 9) throw new ScriptException("Signature is too short"); // // The hash type is the last byte of the signature. Remove it and create a new // byte array containing the DER-encoded signature. // int hashType = (int)sigBytes[sigBytes.length-1]&0x00ff; byte[] encodedSig = new byte[sigBytes.length-1]; System.arraycopy(sigBytes, 0, encodedSig, 0, encodedSig.length); // // Serialize the transaction and then add the hash type to the end of the data // // The reference client has a bug for SIGHASH_SINGLE when the input index is // greater than or equal to the number of outputs. In this case, it doesn't // detect an error and instead uses the error code as the transaction hash. // To handle this, we will set the serialized transaction data to null. ECKey.verify() // will detect this and use the error hash when verifying the signature. // Transaction tx = txInput.getTransaction(); byte[] txData; if ((hashType&0x7f) != ScriptOpCodes.SIGHASH_SINGLE || txInput.getIndex() < tx.getOutputs().size()) { SerializedBuffer outBuffer = new SerializedBuffer(1024); tx.serializeForSignature(txInput.getIndex(), hashType, subProgram, outBuffer); outBuffer.putInt(hashType); txData = outBuffer.toByteArray(); } else { txData = null; } // // Use the public keys to verify the signature for the hashed data. Stop as // soon as we have a verified signature. The public key will be removed from // the list if it verifies a signature to prevent one person from signing the // transaction multiple times. // Iterator<StackElement> it = pubKeys.iterator(); while (it.hasNext()) { StackElement pubKey = it.next(); ECKey ecKey = new ECKey(pubKey.getBytes()); try { isValid = ecKey.verifySignature(txData, encodedSig); } catch (ECException exc) { it.remove(); } // // Remove the public key from the list if the verification is successful // if (isValid) { it.remove(); break; } } return isValid; } }