An ECDSA signature consists of two numbers, r and s which are numbers in the range [1..n-1] where n is the order of the curve. n is a (known) number in the range [2^(k-1)..2^k-1] where k is the key size. So the size of r and s are generally the same and sometimes somewhat smaller as the key size in bytes.
Now r and s can be encoded in multiple ways, of which two are common:
- r and s are DER encoded as two ASN.1 signed INTEGER types within an ASN.1 SEQUENCE.
- r and s are encoded as two statically sized, unsigned integers with the same size as the key size (or order) in octets or bytes.
So the difference in size is just because the values r and s are encoded differently. Of course, you need to know the type of encoding before you can verify the signature.
As r and s are exactly the same independents of the encoding it is relatively simple to convert between the two versions (if you can call anything that requires generation or parsing of DER-encoded ASN.1 structures "simple").
Type 1 has been standardized in ANSI X9.62 and type 2, often called a flat encoding, is commonly used on embedded platforms or smart cards.
r and s are just very likely the same size as n / the key size, but in principle, they could be e.g. a number 3. The chance of that happening is abysmally small. You should, however, not perform any tests on the size of r and s. If either of them is more than 8 bytes smaller then you may start to scratch your head because the chance of that happening is between 1/2^63 and 1/2^64, i.e. extremely unlikely.
So:
- I am misunderstanding the wiki article.
No, the wiki article assumes the standardized encoding of ANSI X9.62.
- I am using python-ecdsa incorrectly
No, the python-ecdsa package just uses a different encoding and you are surprised.
- the bitcoin wiki is incorrect
No, the bitcoin wiki assumed a particular encoding chosen for their protocol.
- python-ecdsa is not implemented correctly
Definitely not; at least not with regards to the size of the signature.
Now for the implementation details; the following is in the documentation:
There are also multiple ways to represent a signature. The default sk.sign()
and vk.verify()
methods present it as a short string, for simplicity and minimal overhead. To use a different scheme, use the sk.sign(sigencode=) and vk.verify(sigdecode=) arguments. There are helper functions in the "ecdsa.util" module that can be useful here.
So try to use sigencode=sigencode_der
to get the format expected by the wiki article. The util.py
source has all the conversions you are likely to need. It uses number_to_string
to create statically sized numbers. This function is also known as I2OSP or Integer to Octet String primitive in PKCS#1 (RSA). Note that "strings" in the code refer to octet strings, also known as byte arrays - not text strings.