Skip to content

Commit

Permalink
Merge branch 'fix/46-der-encoding-issues'
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonKueltz committed Apr 8, 2020
2 parents 3fef4b1 + 033ab59 commit e8a25d7
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 142 deletions.
14 changes: 7 additions & 7 deletions fastecdsa/ecdsa.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from binascii import hexlify
from hashlib import sha256
from typing import TypeVar

Expand Down Expand Up @@ -25,19 +26,18 @@ def sign(msg: MsgTypes, d: int, curve: Curve = P256, hashfunc=sha256, prehashed:
| d (int): The ECDSA private key of the signer.
| curve (fastecdsa.curve.Curve): The curve to be used to sign the message.
| hashfunc (_hashlib.HASH): The hash function used to compress the message.
| prehashed (bool): The message being passed has already been hashed by :code:`hashfunc`.
"""
# generate a deterministic nonce per RFC6979
rfc6979 = RFC6979(msg, d, curve.q, hashfunc)
rfc6979 = RFC6979(msg, d, curve.q, hashfunc, prehashed=prehashed)
k = rfc6979.gen_nonce()

# add a prehash option
if not prehashed:
hashed = hashfunc(msg_bytes(msg)).hexdigest()
if prehashed:
hex_digest = hexlify(msg).decode()
else:
hashed = msg

hex_digest = hashfunc(msg_bytes(msg)).hexdigest()
r, s = _ecdsa.sign(
hashed,
hex_digest,
str(d),
str(k),
str(curve.p),
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from os import remove
from unittest import TestCase

from ..curve import P256
from ..keys import export_key, import_key, gen_keypair
from fastecdsa.curve import P256
from fastecdsa.keys import export_key, import_key, gen_keypair


class TestAsn1(TestCase):
Expand Down
94 changes: 94 additions & 0 deletions fastecdsa/tests/encoding/test_der.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from binascii import unhexlify
from unittest import TestCase

from fastecdsa.curve import secp256k1
from fastecdsa.ecdsa import sign
from fastecdsa.encoding.der import DEREncoder, InvalidDerSignature


class TestDEREncoder(TestCase):
def test_encode_signature(self):
self.assertEqual(
DEREncoder.encode_signature(r=1, s=2),
b"\x30" # SEQUENCE
b"\x06" # Length of Sequence
b"\x02" # INTEGER
b"\x01" # Length of r
b"\x01" # r
b"\x02" # INTEGER
b"\x01" # Length of s
b"\x02", # s
)

# Check that we add a zero byte when the number's highest bit is set
self.assertEqual(DEREncoder.encode_signature(r=128, s=128), b"0\x08\x02\x02\x00\x80\x02\x02\x00\x80")

# Check a value on a standard curve like secp256k1 works
# see https://github.com/btccom/secp256k1-go/blob/master/secp256k1/sign_vectors.yaml
secp256k1_vectors = [
(
0x31a84594060e103f5a63eb742bd46cf5f5900d8406e2726dedfc61c7cf43ebad,
unhexlify("9e5755ec2f328cc8635a55415d0e9a09c2b6f2c9b0343c945fbbfe08247a4cbe"),
unhexlify("30440220132382ca59240c2e14ee7ff61d90fc63276325f4cbe8169fc53ade4a407c2fc802204d86fbe3bde69"
"75dd5a91fdc95ad6544dcdf0dab206f02224ce7e2b151bd82ab"),
),
(
0x7177f0d04c79fa0b8c91fe90c1cf1d44772d1fba6e5eb9b281a22cd3aafb51fe,
unhexlify("2d46a712699bae19a634563d74d04cc2da497b841456da270dccb75ac2f7c4e7"),
unhexlify("3045022100d80cf7abc9ab601373780cee3733d2cb5ff69ba1452ec2d2a058adf9645c13be0220011d1213b7d"
"152f72fd8759b45276ba32d9c909602e5ec89550baf3aaa8ed950"),
),
(
0x989e500d6b1397f2c5dcdf43c58ac2f14df753eb6089654e07ff946b3f84f3d5,
unhexlify("c94f4ec84be928017cbbb447d2ab5b5d4d69e5e5fd03da7eae4378a1b1c9c402"),
unhexlify("3045022100d0f5b740cbe3ee5b098d3c5afdefa61bb0797cb4e7b596afbd38174e1c653bb602200329e9f1a09"
"632de477664814791ac31544e04715db68f4b02657ba35863e711"),
),
(
0x39dfc615f2b718397f6903b0c46c47c5687e97d3d2a5e1f2b200f459f7b1219b,
unhexlify("dfeb2092955572ce0695aa038f58df5499949e18f58785553c3e83343cd5eb93"),
unhexlify("30440220692c01edf8aeab271df3ed4e8d57a170f014f8f9d65031aac28b5e1840acfb5602205075f9d1fdbf5"
"079ee052e5f3572d518b3594ef49582899ec44d065f71a55192"),
),
]

for private_key, digest, expected in secp256k1_vectors:
r, s = sign(digest, private_key, curve=secp256k1, prehashed=True)
encoded = DEREncoder.encode_signature(r, s)
self.assertEqual(encoded, expected)

def test_decode_signature(self):
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"") # length to shot
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"\x31\x06\x02\x01\x01\x02\x01\x02") # invalid SEQUENCE marker
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"\x30\x07\x02\x01\x01\x02\x01\x02") # invalid length
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"\x30\x06\x02\x03\x01\x02\x01\x02") # invalid length of r
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x03\x01\x02") # invalid length of s
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"\x30\x06\x03\x01\x01\x02\x01\x02") # invalid INTEGER marker for r
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"\x30\x06\x02\x00\x02\x01\x02") # length of r is 0
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"\x30\x06\x02\x01\x81\x02\x01\x02") # value of r is negative
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"\x30\x07\x02\x02\x00\x01\x02\x01\x02") # value of r starts with a zero byte
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x03\x01\x02") # invalid INTEGER marker for s
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x02\x00") # value of s is 0
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x02\x01\x81") # value of s is negative
with self.assertRaises(InvalidDerSignature):
DEREncoder.decode_signature(b"\x30\x07\x02\x01\x01\x02\x02\x00\x02") # value of s starts with a zero byte

self.assertEqual(DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x02\x01\x02"), (1, 2))
self.assertEqual(
DEREncoder.decode_signature(b"0\x08\x02\x02\x00\x80\x02\x02\x00\x80"), (128, 128)
) # verify zero bytes
self.assertEqual(
DEREncoder.decode_signature(b"0\x08\x02\x02\x03\xE8\x02\x02\x03\xE8"), (1000, 1000)
) # verify byte order
100 changes: 100 additions & 0 deletions fastecdsa/tests/encoding/test_sec1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from binascii import hexlify, unhexlify
from unittest import TestCase

from fastecdsa.curve import P256, secp192k1, secp256k1
from fastecdsa.encoding.sec1 import InvalidSEC1PublicKey, SEC1Encoder
from fastecdsa.point import Point


class TestSEC1Encoder(TestCase):
def test_encode_public_key(self):
# 1/ PrivateKey generated using openssl "openssl ecparam -name secp256k1 -genkey -out ec-priv.pem"
# 2/ Printed using "openssl ec -in ec-priv.pem -text -noout" and converted to numeric using "asn1._bytes_to_int"
priv_key = 7002880736699640265110069622773736733141182416793484574964618597954446769264
pubkey_compressed = hexlify(SEC1Encoder.encode_public_key(secp256k1.G * priv_key))
pubkey_uncompressed = hexlify(SEC1Encoder.encode_public_key(secp256k1.G * priv_key, compressed=False))

# 3/ PublicKey extracted using "openssl ec -in ec-priv.pem -pubout -out ec-pub.pem"
# 4/ Encoding verified using openssl "openssl ec -in ec-pub.pem -pubin -text -noout -conv_form compressed"
self.assertEqual(pubkey_compressed, b"02e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f35835062")
self.assertEqual(
pubkey_uncompressed,
b"04e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f3583506"
b"23dad76df888abde5ed0cc5af1b83968edffcae5d70bedb24fdc18bb5f79499d0",
)

# Same with P256 Curve
priv_P256 = 807015861248675637760562792774171551137308512372870683367415858378856470633
pubkey_compressed = hexlify(SEC1Encoder.encode_public_key(P256.G * priv_P256))
pubkey_uncompressed = hexlify(SEC1Encoder.encode_public_key(P256.G * priv_P256, compressed=False))
self.assertEqual(pubkey_compressed, b"0212c9ddf64b0d1f1d91d9bd729abfb880079fa889d66604cc0b78c9cbc271824c")
self.assertEqual(
pubkey_uncompressed,
b"0412c9ddf64b0d1f1d91d9bd729abfb880079fa889d66604cc0b78c9cbc271824"
b"c9a7d581bcf2aba680b53cedbade03be62fe95869da04a168a458f369ac6a823e",
)

# And secp192k1 Curve
priv_secp192k1 = 5345863567856687638748079156318679969014620278806295592453
pubkey_compressed = hexlify(SEC1Encoder.encode_public_key(secp192k1.G * priv_secp192k1))
pubkey_uncompressed = hexlify(SEC1Encoder.encode_public_key(secp192k1.G * priv_secp192k1, compressed=False))
self.assertEqual(pubkey_compressed, b"03a3bec5fba6d13e51fb55bd88dd097cb9b04f827bc151d22d")
self.assertEqual(
pubkey_uncompressed,
b"04a3bec5fba6d13e51fb55bd88dd097cb9b04f827bc151d22df07a73819149e8d903aa983e52ab1cff38f0d381f940d361",
)

def test_decode_public_key(self):
expected_public = Point(
x=0xE5E2C01985AAFB6E2C3AD49F3DB5CCC54B2E63343AF405B521303D0F35835062,
y=0x3DAD76DF888ABDE5ED0CC5AF1B83968EDFFCAE5D70BEDB24FDC18BB5F79499D0,
curve=secp256k1,
)
public_from_compressed = SEC1Encoder.decode_public_key(
unhexlify(b"02e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f35835062"), secp256k1
)
public_from_uncompressed = SEC1Encoder.decode_public_key(
unhexlify(
b"04e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f3583506"
b"23dad76df888abde5ed0cc5af1b83968edffcae5d70bedb24fdc18bb5f79499d0"
),
secp256k1,
)

# Same values as in `test_encode_public_key`, verified using openssl
self.assertEqual(public_from_compressed, expected_public)
self.assertEqual(public_from_uncompressed, expected_public)
with self.assertRaises(InvalidSEC1PublicKey) as e:
SEC1Encoder.decode_public_key(b"\x02", secp256k1) # invalid compressed length
self.assertEqual(e.exception.args[0], "A compressed public key must be 33 bytes long")
with self.assertRaises(InvalidSEC1PublicKey) as e:
SEC1Encoder.decode_public_key(b"\x04", secp256k1) # invalid uncompressed length
self.assertEqual(e.exception.args[0], "An uncompressed public key must be 65 bytes long")
with self.assertRaises(InvalidSEC1PublicKey) as e:
# invalid prefix value
SEC1Encoder.decode_public_key(
unhexlify(b"05e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f35835062"), secp256k1
)
self.assertEqual(e.exception.args[0], "Wrong key format")

# With P256, same values as in `test_encode_public_key`, verified using openssl
expected_P256 = Point(
x=0x12C9DDF64B0D1F1D91D9BD729ABFB880079FA889D66604CC0B78C9CBC271824C,
y=0x9A7D581BCF2ABA680B53CEDBADE03BE62FE95869DA04A168A458F369AC6A823E,
curve=P256,
)
public_from_compressed = SEC1Encoder.decode_public_key(
unhexlify(b"0212c9ddf64b0d1f1d91d9bd729abfb880079fa889d66604cc0b78c9cbc271824c"), P256
)
self.assertEqual(public_from_compressed, expected_P256)

# With secp192k1, same values as in `test_encode_public_key`, verified using openssl
expected_secp192k1 = Point(
x=0xA3BEC5FBA6D13E51FB55BD88DD097CB9B04F827BC151D22D,
y=0xF07A73819149E8D903AA983E52AB1CFF38F0D381F940D361,
curve=secp192k1,
)
public_from_compressed = SEC1Encoder.decode_public_key(
unhexlify(b"03a3bec5fba6d13e51fb55bd88dd097cb9b04f827bc151d22d"), secp192k1
)
self.assertEqual(public_from_compressed, expected_secp192k1)
72 changes: 0 additions & 72 deletions fastecdsa/tests/test_key_encoding.py

This file was deleted.

Loading

0 comments on commit e8a25d7

Please sign in to comment.