diff --git a/src/main/scala/org/scalacoin/crypto/DERSignatureUtil.scala b/src/main/scala/org/scalacoin/crypto/DERSignatureUtil.scala index 6a5cd95932..39525f9e8f 100644 --- a/src/main/scala/org/scalacoin/crypto/DERSignatureUtil.scala +++ b/src/main/scala/org/scalacoin/crypto/DERSignatureUtil.scala @@ -82,6 +82,90 @@ trait DERSignatureUtil extends BitcoinSLogger { (r.getPositiveValue, s.getPositiveValue) } else throw new RuntimeException("The given sequence of bytes was not a DER signature: " + BitcoinSUtil.encodeHex(bytes)) } + + /** + * This functions implements the strict der encoding rules that were created in BIP66 + * https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki + * @param signature the signature to check if they are strictly der encoded + * @return boolean indicating whether the signature was der encoded or not + */ + def isStrictDEREncoding(signature : ECDigitalSignature) : Boolean = isStrictDEREncoding(signature.bytes) + + + /** + * This functions implements the strict der encoding rules that were created in BIP66 + * https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki + * @param bytes the bytes to check if they are strictly der encoded + * @return boolean indicating whether the bytes were der encoded or not + */ + def isStrictDEREncoding(bytes : Seq[Byte]) : Boolean = { + // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash] + // * total-length: 1-byte length descriptor of everything that follows, + // excluding the sighash byte. + // * R-length: 1-byte length descriptor of the R value that follows. + // * R: arbitrary-length big-endian encoded R value. It must use the shortest + // possible encoding for a positive integers (which means no null bytes at + // the start, except a single one when the next byte has its highest bit set). + // * S-length: 1-byte length descriptor of the S value that follows. + // * S: arbitrary-length big-endian encoded S value. The same rules apply. + // * sighash: 1-byte value indicating what data is hashed (not part of the DER + // signature) + + //check if the bytes are ATLEAST der encoded + val isDerEncoded = isDEREncoded(bytes) + if (!isDerEncoded) return false + + + if (bytes.size < 9) return false + if (bytes.size > 73) return false + + // A signature is of type 0x30 (compound) + if (bytes.head != 0x30) return false + + // Make sure the length covers the entire signature. + if (bytes(1) != bytes.size - 3) return false + + val rSize = bytes(3) + + // Make sure the length of the S element is still inside the signature. + if (5 + rSize >= bytes.size) return false + + // Extract the length of the S element. + val sSize = bytes(5 + rSize) + + // Verify that the length of the signature matches the sum of the length + // of the elements. + if ((rSize + sSize + 7) != bytes.size) return false + + // Check whether the R element is an integer. + if (bytes(2) != 0x02) return false + + // Zero-length integers are not allowed for R. + if (rSize == 0) return false + + // Negative numbers are not allowed for R. + if ((bytes(4) & 0x80) != 0) return false + + // Null bytes at the start of R are not allowed, unless R would + // otherwise be interpreted as a negative number. + if (rSize > 1 && (bytes(4) == 0x00) && !((bytes(5) & 0x80) != 0 )) return false + + // Check whether the S element is an integer. + if (bytes(rSize + 4) != 0x02) return false + + // Zero-length integers are not allowed for S. + if (rSize == 0) return false + + // Negative numbers are not allowed for S. + if ((bytes(rSize + 6) & 0x80) != 0) return false + + // Null bytes at the start of S are not allowed, unless S would otherwise be + // interpreted as a negative number. + if (sSize > 1 && (bytes(rSize + 6) == 0x00) && !((bytes(rSize + 7) & 0x80) != 0)) return false + + //if we made it to this point without returning false this must be a valid strictly encoded der sig + true + } } object DERSignatureUtil extends DERSignatureUtil diff --git a/src/main/scala/org/scalacoin/script/crypto/CryptoInterpreter.scala b/src/main/scala/org/scalacoin/script/crypto/CryptoInterpreter.scala index 623bb53ee0..9d535176c5 100644 --- a/src/main/scala/org/scalacoin/script/crypto/CryptoInterpreter.scala +++ b/src/main/scala/org/scalacoin/script/crypto/CryptoInterpreter.scala @@ -1,9 +1,10 @@ package org.scalacoin.script.crypto -import org.scalacoin.crypto.{TransactionSignatureChecker, ECFactory, TransactionSignatureSerializer} +import org.scalacoin.crypto.{DERSignatureUtil, TransactionSignatureChecker, ECFactory, TransactionSignatureSerializer} import org.scalacoin.protocol.script._ import org.scalacoin.protocol.transaction.Transaction import org.scalacoin.script.control.{ControlOperationsInterpreter, OP_VERIFY} +import org.scalacoin.script.flag.ScriptVerifyDerSig import org.scalacoin.script.{ScriptProgramFactory, ScriptProgramImpl, ScriptProgram} import org.scalacoin.script.constant._ import org.scalacoin.util.{BitcoinSLogger, BitcoinSUtil, CryptoUtil} @@ -94,7 +95,13 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger throw new RuntimeException("We do not know how to evaluate a nonstandard or EmptyScriptSignature for an OP_CHECKSIG operation") } - + //TODO: Check if strict dersig flag + if (program.flags.contains(ScriptVerifyDerSig)) { + //this means all of the signatures must encoded according to BIP66 strict dersig + //https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki + require(DERSignatureUtil.isDEREncoded(signature), "Since the ScriptVerifyDerSig flag is set the signature being checked must be a strict dersig signature as per BIP 66\n" + + "Sig: " + signature.hex) + } val restOfStack = program.stack.tail.tail val hashType = (signature.bytes.size == 0) match { @@ -157,6 +164,8 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger def opCheckMultiSig(program : ScriptProgram) : ScriptProgram = { require(program.script.headOption.isDefined && program.script.head == OP_CHECKMULTISIG, "Script top must be OP_CHECKMULTISIG") require(program.stack.size > 2, "Stack must contain at least 3 items for OP_CHECKMULTISIG") + + //TODO: Check if strict dersig flag //head should be n for m/n val nPossibleSignatures : Int = program.stack.head match { case s : ScriptNumber => s.num.toInt diff --git a/src/main/scala/org/scalacoin/script/flag/ScriptFlagFactory.scala b/src/main/scala/org/scalacoin/script/flag/ScriptFlagFactory.scala index 6a7eaed6d9..3db8c0b701 100644 --- a/src/main/scala/org/scalacoin/script/flag/ScriptFlagFactory.scala +++ b/src/main/scala/org/scalacoin/script/flag/ScriptFlagFactory.scala @@ -2,15 +2,21 @@ package org.scalacoin.script.flag /** * Created by chris on 3/23/16. - * Trait used to create a script flag used to evalaute scripts in a + * Trait used to create a script flag used to evaluate scripts in a * certain way */ trait ScriptFlagFactory { + /** + * All the script flags found inside of bitcoin core + * https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.h#L31 + * @return + */ private def flags = Seq(ScriptVerifyNone, ScriptVerifyP2SH, ScriptVerifyStrictEnc, ScriptVerifyDerSig, ScriptVerifyLowS, ScriptVerifySigPushOnly, ScriptVerifyMinimalData, ScriptVerifyNullDummy, ScriptVerifyDiscourageUpgradableNOPs, ScriptVerifyCleanStack, ScriptVerifyCheckLocktimeVerify, ScriptVerifyCheckSequenceVerify) + /** * Takes in a string and tries to match it with a script flag * @param str the string to try and match with a script flag diff --git a/src/test/scala/org/scalacoin/crypto/DERSignatureUtilTest.scala b/src/test/scala/org/scalacoin/crypto/DERSignatureUtilTest.scala index 0b8e244e3f..f0e4d4cdb3 100644 --- a/src/test/scala/org/scalacoin/crypto/DERSignatureUtilTest.scala +++ b/src/test/scala/org/scalacoin/crypto/DERSignatureUtilTest.scala @@ -41,5 +41,18 @@ class DERSignatureUtilTest extends FlatSpec with MustMatchers { r must be (BitcoinSUtil.hexToBigInt("0a5c6163f07b8d3b013c4d1d6dba25e780b39658d79ba37af7057a3b7f15ffa1")) s must be (BitcoinSUtil.hexToBigInt("1fd9b4eaa9943f734928b99a83592c2e7bf342ea2680f6a2bb705167966b7420")) } + + it must "say that a signature taken from a p2sh transaction is a valid stirctly DER encoded signature" in { + DERSignatureUtil.isStrictDEREncoding(p2shSignature) must be (true) + } + + it must "say that signature taken from a p2pkh transaction is a valid strictly DER encoded signature" in { + DERSignatureUtil.isStrictDEREncoding(p2pkhSignature) must be (true) + } + + it must "say that a signature taken from a p2pk transaction is a valid strictly DER encoded signature" in { + + DERSignatureUtil.isStrictDEREncoding(p2pkSignature) must be (true) + } }