Adding function to detect if a signature is strictly der encoded according to BIP66

This commit is contained in:
Chris Stewart 2016-03-24 17:43:18 -05:00
parent 7d8699e822
commit 215cdddf20
4 changed files with 115 additions and 3 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)
}
}