From 6b790ea462c9e9219e3b2b61680b6ad96dc2b460 Mon Sep 17 00:00:00 2001 From: kumar-miskin <34426044+kumar-miskin@users.noreply.github.com> Date: Sat, 10 Oct 2026 12:54:22 -0500 Subject: [PATCH 1/2] Fix DER signature sequence length and second integer tag Related: #295. Prior art: #190 identified the missing sequence length. AI-assisted patch preparation and testing. --- bitcoin/signature.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bitcoin/signature.py b/bitcoin/signature.py index 89434b7a..1a0532dc 100644 --- a/bitcoin/signature.py +++ b/bitcoin/signature.py @@ -38,11 +38,13 @@ def stream_deserialize(cls, f): return cls(r, s, len(r + s)) def stream_serialize(self, f): + rs = BytesIO() + rs.write(b"\x02") + BytesSerializer.stream_serialize(self.r, rs) + rs.write(b"\x02") + BytesSerializer.stream_serialize(self.s, rs) f.write(b"\x30") - f.write(b"\x02") - BytesSerializer.stream_serialize(self.r, f) - f.write(b"\x30") - BytesSerializer.stream_serialize(self.s, f) + BytesSerializer.stream_serialize(rs.getvalue(), f) def __repr__(self): return 'DERSignature(%s, %s)' % (self.r, self.s) From 24039a0bda84ba4c9043f484aa583c9ca77ee2d8 Mon Sep 17 00:00:00 2001 From: kumar-miskin <34426044+kumar-miskin@users.noreply.github.com> Date: Sat, 10 Oct 2026 12:54:58 -0500 Subject: [PATCH 2/2] Add DER serialization regression and interoperability tests Cover exact encodings, leading-zero integer payloads, stream serialization and locally generated signatures that still verify after round-trip. AI-assisted patch preparation and testing. --- bitcoin/tests/test_signature.py | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 bitcoin/tests/test_signature.py diff --git a/bitcoin/tests/test_signature.py b/bitcoin/tests/test_signature.py new file mode 100644 index 00000000..b64624ac --- /dev/null +++ b/bitcoin/tests/test_signature.py @@ -0,0 +1,42 @@ +# Copyright (C) The python-bitcoinlib developers +# This file is subject to the license terms in the LICENSE file. + +import unittest +from io import BytesIO + +from bitcoin.core.key import CECKey +from bitcoin.signature import DERSignature + + +class Test_DERSignature(unittest.TestCase): + def test_serialize_known_vectors(self): + # ECDSA integers can require a leading zero to preserve their sign. + for r, s in [(b'\x01', b'\x01'), + (b'\x7f' * 32, b'\x01'), + (b'\x00\x80' + b'\x00' * 31, b'\x7f' * 32), + (b'\x00\xff' + b'\x00' * 31, + b'\x00\x80' + b'\x00' * 31)]: + with self.subTest(r=r, s=s): + body = b'\x02' + bytes([len(r)]) + r + body += b'\x02' + bytes([len(s)]) + s + expected = b'\x30' + bytes([len(body)]) + body + signature = DERSignature.deserialize(expected) + self.assertEqual(signature.serialize(), expected) + restored = DERSignature.deserialize(signature.serialize()) + self.assertEqual((restored.r, restored.s), (r, s)) + + def test_stream_serialize(self): + stream = BytesIO() + signature = DERSignature(b'\x01', b'\x02', 2) + signature.stream_serialize(stream) + self.assertEqual(stream.getvalue(), bytes.fromhex('3006020101020102')) + + def test_generated_signatures_verify_after_roundtrip(self): + key = CECKey() + key.set_secretbytes(b'\x01' * 32) + for digest in (b'\x02' * 32, b'\x03' * 32, b'\xff' * 32): + with self.subTest(digest=digest): + encoded = key.sign(digest) + restored = DERSignature.deserialize(encoded).serialize() + self.assertEqual(restored, encoded) + self.assertTrue(key.verify(digest, restored))