summaryrefslogtreecommitdiff
path: root/7b/8bbd067229c0f4c1a6a1f0735f797caddec5ff
diff options
context:
space:
mode:
authorRusty Russell <rusty@rustcorp.com.au>2015-01-22 11:02:35 +1030
committerbitcoindev <bitcoindev@gnusha.org>2015-01-22 01:05:09 +0000
commit127b366f3290561dd741d6a8f75416f81762beb0 (patch)
tree7359159fea81f9349a13fe04a3fac443de7175b1 /7b/8bbd067229c0f4c1a6a1f0735f797caddec5ff
parent43eaef622aba32a14f0209565fc201190fdd9dc4 (diff)
downloadpi-bitcoindev-127b366f3290561dd741d6a8f75416f81762beb0.tar.gz
pi-bitcoindev-127b366f3290561dd741d6a8f75416f81762beb0.zip
Re: [Bitcoin-development] [softfork proposal] Strict DER signatures
Diffstat (limited to '7b/8bbd067229c0f4c1a6a1f0735f797caddec5ff')
-rw-r--r--7b/8bbd067229c0f4c1a6a1f0735f797caddec5ff452
1 files changed, 452 insertions, 0 deletions
diff --git a/7b/8bbd067229c0f4c1a6a1f0735f797caddec5ff b/7b/8bbd067229c0f4c1a6a1f0735f797caddec5ff
new file mode 100644
index 000000000..8160dfbfc
--- /dev/null
+++ b/7b/8bbd067229c0f4c1a6a1f0735f797caddec5ff
@@ -0,0 +1,452 @@
+Received: from sog-mx-1.v43.ch3.sourceforge.com ([172.29.43.191]
+ helo=mx.sourceforge.net)
+ by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.76)
+ (envelope-from <rusty@ozlabs.org>) id 1YE6Cr-0006vy-3n
+ for bitcoin-development@lists.sourceforge.net;
+ Thu, 22 Jan 2015 01:05:09 +0000
+X-ACL-Warn:
+Received: from ozlabs.org ([103.22.144.67])
+ by sog-mx-1.v43.ch3.sourceforge.com with esmtps (TLSv1:AES256-SHA:256)
+ (Exim 4.76) id 1YE6Co-0002EW-Pk
+ for bitcoin-development@lists.sourceforge.net;
+ Thu, 22 Jan 2015 01:05:09 +0000
+Received: by ozlabs.org (Postfix, from userid 1011)
+ id 7E3261402A7; Thu, 22 Jan 2015 12:04:59 +1100 (AEDT)
+From: Rusty Russell <rusty@rustcorp.com.au>
+To: Pieter Wuille <pieter.wuille@gmail.com>,
+ Bitcoin Dev <bitcoin-development@lists.sourceforge.net>
+In-Reply-To: <CAPg+sBhk7F2OHT64i2LNSjv8DR5tD3RJkLJGzPGZW8OPQTCjQw@mail.gmail.com>
+References: <CAPg+sBhk7F2OHT64i2LNSjv8DR5tD3RJkLJGzPGZW8OPQTCjQw@mail.gmail.com>
+User-Agent: Notmuch/0.17 (http://notmuchmail.org) Emacs/24.3.1
+ (x86_64-pc-linux-gnu)
+Date: Thu, 22 Jan 2015 11:02:35 +1030
+Message-ID: <87egqnwt7g.fsf@rustcorp.com.au>
+MIME-Version: 1.0
+Content-Type: text/plain
+X-Spam-Score: 0.1 (/)
+X-Spam-Report: Spam Filtering performed by mx.sourceforge.net.
+ See http://spamassassin.org/tag/ for more details.
+ -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay
+ domain
+ 0.1 AWL AWL: Adjusted score from AWL reputation of From: address
+X-Headers-End: 1YE6Co-0002EW-Pk
+Subject: Re: [Bitcoin-development] [softfork proposal] Strict DER signatures
+X-BeenThere: bitcoin-development@lists.sourceforge.net
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: <bitcoin-development.lists.sourceforge.net>
+List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/bitcoin-development>,
+ <mailto:bitcoin-development-request@lists.sourceforge.net?subject=unsubscribe>
+List-Archive: <http://sourceforge.net/mailarchive/forum.php?forum_name=bitcoin-development>
+List-Post: <mailto:bitcoin-development@lists.sourceforge.net>
+List-Help: <mailto:bitcoin-development-request@lists.sourceforge.net?subject=help>
+List-Subscribe: <https://lists.sourceforge.net/lists/listinfo/bitcoin-development>,
+ <mailto:bitcoin-development-request@lists.sourceforge.net?subject=subscribe>
+X-List-Received-Date: Thu, 22 Jan 2015 01:05:09 -0000
+
+Pieter Wuille <pieter.wuille@gmail.com> writes:
+> Hello everyone,
+>
+> We've been aware of the risk of depending on OpenSSL for consensus
+> rules for a while, and were trying to get rid of this as part of BIP
+> 62 (malleability protection), which was however postponed due to
+> unforeseen complexities. The recent evens (see the thread titled
+> "OpenSSL 1.0.0p / 1.0.1k incompatible, causes blockchain rejection."
+> on this mailing list) have made it clear that the problem is very
+> real, however, and I would prefer to have a fundamental solution for
+> it sooner rather than later.
+
+OK, I worked up a clearer (but more verbose) version with fewer
+magic numbers. More importantly, feel free to steal the test cases.
+
+One weirdness is the restriction on maximum total length, rather than a
+32 byte (33 with 0-prepad) limit on signatures themselves.
+
+Apologies for my babytalk C++. Am sure there's a neater way.
+
+/* Licensed under Creative Commons zero (public domain). */
+#include <vector>
+#include <cstdlib>
+#include <cassert>
+
+#ifdef CLARIFY
+bool ConsumeByte(const std::vector<unsigned char> &sig, size_t &off,
+ unsigned int &val)
+{
+ if (off >= sig.size()) return false;
+
+ val = sig[off++];
+ return true;
+}
+
+bool ConsumeTypeByte(const std::vector<unsigned char> &sig, size_t &off,
+ unsigned int t)
+{
+ unsigned int type;
+ if (!ConsumeByte(sig, off, type)) return false;
+
+ return (type == t);
+}
+
+bool ConsumeNonZeroLength(const std::vector<unsigned char> &sig, size_t &off,
+ unsigned int &len)
+{
+ if (!ConsumeByte(sig, off, len)) return false;
+
+ // Zero-length integers are not allowed.
+ return (len != 0);
+}
+
+bool ConsumeNumber(const std::vector<unsigned char> &sig, size_t &off,
+ unsigned int len)
+{
+ // Length of number should be within signature.
+ if (off + len > sig.size()) return false;
+
+ // Negative numbers are not allowed.
+ if (sig[off] & 0x80) return false;
+
+ // Zero bytes at the start are not allowed, unless it would
+ // otherwise be interpreted as a negative number.
+ if (len > 1 && (sig[off] == 0x00) && !(sig[off+1] & 0x80)) return false;
+
+ // Consume number itself.
+ off += len;
+ return true;
+}
+
+// Consume a DER encoded integer, update off if successful.
+bool ConsumeDERInteger(const std::vector<unsigned char> &sig, size_t &off) {
+ unsigned int len;
+
+ // Type byte must be "integer"
+ if (!ConsumeTypeByte(sig, off, 0x02)) return false;
+ if (!ConsumeNonZeroLength(sig, off, len)) return false;
+ // Now the BE encoded value itself.
+ if (!ConsumeNumber(sig, off, len)) return false;
+
+ return true;
+}
+
+bool IsValidSignatureEncoding(const std::vector<unsigned char> &sig) {
+ // 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 cannot start with any
+ // null bytes, unless the first byte that follows is 0x80 or higher, in which
+ // case a single null byte is required.
+ // * 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.
+
+ // Accept empty signature as correctly encoded (but invalid) signature,
+ // even though it is not strictly DER.
+ if (sig.size() == 0) return true;
+
+ // Maximum size constraint.
+ if (sig.size() > 73) return false;
+
+ size_t off = 0;
+
+ // A signature is of type "compound".
+ if (!ConsumeTypeByte(sig, off, 0x30)) return false;
+
+ unsigned int len;
+ if (!ConsumeNonZeroLength(sig, off, len)) return false;
+
+ // Make sure the length covers the rest (except sighash).
+ if (len + 1 != sig.size() - off) return false;
+
+ // Check R value.
+ if (!ConsumeDERInteger(sig, off)) return false;
+
+ // Check S value.
+ if (!ConsumeDERInteger(sig, off)) return false;
+
+ // There should exactly one byte left (the sighash).
+ return off + 1 == sig.size() ? true : false;
+}
+#else
+bool IsValidSignatureEncoding(const std::vector<unsigned char> &sig) {
+ // 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)
+
+ // Accept empty signature as correctly encoded (but invalid) signature,
+ // even though it is not strictly DER. This avoids needing full DER signatures
+ // in places where any invalid signature would do. Given that the empty string is
+ // always invalid as signature, this is safe.
+ if (sig.size() == 0) return true;
+
+ // Minimum and maximum size constraints.
+ if (sig.size() < 9) return false;
+ if (sig.size() > 73) return false;
+
+ // A signature is of type 0x30 (compound).
+ if (sig[0] != 0x30) return false;
+
+ // Make sure the length covers the entire signature.
+ if (sig[1] != sig.size() - 3) return false;
+
+ // Extract the length of the R element.
+ unsigned int lenR = sig[3];
+
+ // Make sure the length of the S element is still inside the signature.
+ if (5 + lenR >= sig.size()) return false;
+
+ // Extract the length of the S element.
+ unsigned int lenS = sig[5 + lenR];
+
+ // Verify that the length of the signature matches the sum of the length
+ // of the elements.
+ if ((size_t)(lenR + lenS + 7) != sig.size()) return false;
+
+ // Check whether the R element is an integer.
+ if (sig[2] != 0x02) return false;
+
+ // Zero-length integers are not allowed for R.
+ if (lenR == 0) return false;
+
+ // Negative numbers are not allowed for R.
+ if (sig[4] & 0x80) return false;
+
+ // Null bytes at the start of R are not allowed, unless R would
+ // otherwise be interpreted as a negative number.
+ if (lenR > 1 && (sig[4] == 0x00) && !(sig[5] & 0x80)) return false;
+
+ // Check whether the S element is an integer.
+ if (sig[lenR + 4] != 0x02) return false;
+
+ // Zero-length integers are not allowed for S.
+ if (lenS == 0) return false;
+
+ // Negative numbers are not allowed for S.
+ if (sig[lenR + 6] & 0x80) return false;
+
+ // Null bytes at the start of S are not allowed, unless S would otherwise be
+ // interpreted as a negative number.
+ if (lenS > 1 && (sig[lenR + 6] == 0x00) && !(sig[lenR + 7] & 0x80)) return false;
+
+ return true;
+}
+#endif
+
+#define COMPOUND 0x30
+#define NOT_COMPOUND 0x31
+
+// Len gets adjusted by check() to be actual length with this offset.
+#define LEN_OK 0
+#define LEN_TOO_BIG 1
+#define LEN_TOO_SMALL 0xff
+
+#define INT 0x02
+#define NOT_INT 0x03
+
+#define MINIMAL_SIGLEN 1
+#define MINIMAL_SIGVAL 0x0
+
+#define NORMAL_SIGLEN 32
+#define NORMAL_SIGVAL(S) S, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, \
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, \
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, \
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
+
+// 33 bytes is possible, with 0 prepended.
+#define MAXIMAL_SIGLEN 33
+#define MAXIMAL_SIGVAL(S) NORMAL_SIGVAL(S), 0x20
+
+#define OVERSIZE_SIGLEN 34
+#define OVERSIZE_SIGVAL(S) MAXIMAL_SIGVAL(S), 0x21
+
+#define ZEROPAD_SIGLEN (1 + NORMAL_SIGLEN)
+#define ZEROPAD_SIGVAL(S) 00, NORMAL_SIGVAL(S)
+
+#define SIGHASH 0xf0
+
+static bool check(const std::vector<unsigned char> &sig)
+{
+ std::vector<unsigned char> fixed = sig;
+
+ // Fixup length
+ if (fixed.size() > 1)
+ fixed[1] += fixed.size() - 3;
+ return IsValidSignatureEncoding(fixed);
+}
+
+#define good(arr) assert(check(std::vector<unsigned char>(arr, arr+sizeof(arr))))
+#define bad(arr) assert(!check(std::vector<unsigned char>(arr, arr+sizeof(arr))))
+
+// The OK cases.
+static unsigned char zerolen[] = { };
+static unsigned char normal[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char min_r[] = { COMPOUND, LEN_OK,
+ INT, MINIMAL_SIGLEN, MINIMAL_SIGVAL,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char min_s[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, MINIMAL_SIGLEN, MINIMAL_SIGVAL,
+ SIGHASH };
+static unsigned char max_r[] = { COMPOUND, LEN_OK,
+ INT, MAXIMAL_SIGLEN, MAXIMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char max_s[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, MAXIMAL_SIGLEN, MAXIMAL_SIGVAL(0x2),
+ SIGHASH };
+// As long as total size doesn't go over, a single sig is allowed > 33 bytes
+static unsigned char wierd_s_len[] = { COMPOUND, LEN_OK,
+ INT, OVERSIZE_SIGLEN, OVERSIZE_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char wierd_r_len[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, OVERSIZE_SIGLEN, OVERSIZE_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char zeropad_s[] = { COMPOUND, LEN_OK,
+ INT, ZEROPAD_SIGLEN, ZEROPAD_SIGVAL(0x81),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char zeropad_r[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, ZEROPAD_SIGLEN, ZEROPAD_SIGVAL(0x82),
+ SIGHASH };
+
+
+// The fail cases.
+static unsigned char not_compound[] = { NOT_COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char short_len[] = { COMPOUND, LEN_TOO_SMALL,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char long_len[] = { COMPOUND, LEN_TOO_BIG,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char r_notint[] = { COMPOUND, LEN_OK,
+ NOT_INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char s_notint[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ NOT_INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char s_oversig[] = { COMPOUND, LEN_OK,
+ INT, OVERSIZE_SIGLEN, OVERSIZE_SIGVAL(0x1),
+ INT, MAXIMAL_SIGLEN, MAXIMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char r_oversig[] = { COMPOUND, LEN_OK,
+ INT, MAXIMAL_SIGLEN, MAXIMAL_SIGVAL(0x1),
+ INT, OVERSIZE_SIGLEN, OVERSIZE_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char s_negative[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x81),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char r_negative[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x82),
+ SIGHASH };
+static unsigned char zeropad_bad_s[] = { COMPOUND, LEN_OK,
+ INT, ZEROPAD_SIGLEN, ZEROPAD_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char zeropad_bad_r[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, ZEROPAD_SIGLEN, ZEROPAD_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char missing_sighash[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2) };
+static unsigned char extra_byte[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH, 0 };
+
+// Bad signature lengths
+static unsigned char zerolen_r[] = { COMPOUND, LEN_OK,
+ INT, 0,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char zerolen_s[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, 0,
+ SIGHASH };
+static unsigned char overlen_r_by_1[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN + 1 + 1 + NORMAL_SIGLEN + 1 + 1, NORMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char overlen_s_by_1[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN+1+1, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char underlen_r_by_1[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN-1, NORMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+static unsigned char underlen_s_by_1[] = { COMPOUND, LEN_OK,
+ INT, NORMAL_SIGLEN, NORMAL_SIGVAL(0x1),
+ INT, NORMAL_SIGLEN-1, NORMAL_SIGVAL(0x2),
+ SIGHASH };
+
+int main()
+{
+ good(zerolen);
+ good(normal);
+ good(min_r);
+ good(min_s);
+ good(max_r);
+ good(max_s);
+ good(wierd_s_len);
+ good(wierd_r_len);
+ good(zeropad_s);
+ good(zeropad_r);
+
+ // Try different amounts of truncation.
+ for (size_t i = 1; i < sizeof(normal)-1; i++)
+ assert(!check(std::vector<unsigned char>(normal, normal+i)));
+
+ bad(not_compound);
+ bad(short_len);
+ bad(long_len);
+ bad(r_notint);
+ bad(s_notint);
+ bad(s_oversig);
+ bad(r_oversig);
+ bad(s_negative);
+ bad(r_negative);
+ bad(s_negative);
+ bad(r_negative);
+ bad(zeropad_bad_s);
+ bad(zeropad_bad_r);
+ bad(zerolen_r);
+ bad(zerolen_s);
+ bad(overlen_r_by_1);
+ bad(overlen_s_by_1);
+ bad(underlen_r_by_1);
+ bad(underlen_s_by_1);
+ bad(missing_sighash);
+ bad(extra_byte);
+
+ return 0;
+}
+
+
+
+