为什么使用 ecdsa Python 库创建的签名对 coincurve 无效?

Why are signatures created with ecdsa Python library not valid with coincurve?

我正在从纯 Python ecdsa 库切换到更快的 coincurve 库来签署数据。我还想切换到 coincurve 来验证签名(包括由 ecdsa 库创建的旧签名)。

看来使用 ecdsa 创建的签名在 coincurve 中并不(总是?)有效。有人可以解释为什么这不起作用吗?此外,cryptography 库似乎能够一致地验证 ecdsa 签名和 coincurve 签名而没有问题。

更令人困惑的是,如果您 运行 在下面编写几次脚本,有时它会打印点 3 而其他时候则不会。为什么 coincurve 只是偶尔会发现签名有效?

pip install ecdsa cryptography coincurve
import ecdsa
import hashlib
import coincurve
from coincurve.ecdsa import deserialize_compact, cdata_to_der
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed

ecdsa_private_key = ecdsa.SigningKey.generate(ecdsa.SECP256k1, None, hashlib.sha256)
ecdsa_pub = ecdsa_private_key.get_verifying_key()
message = b"Hello world!"
digest = hashlib.sha256(message).digest()
serialized_signature = ecdsa_private_key.sign_digest_deterministic(digest, hashfunc=hashlib.sha256)

signature = cdata_to_der(deserialize_compact(serialized_signature))
cc_private_key = coincurve.PrivateKey(ecdsa_private_key.to_string())
cc_pub = cc_private_key.public_key
crypto_pub = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), cc_pub.format(True))

if ecdsa_pub.verify_digest(serialized_signature, digest) is True:
    print("1. ecdsa can validate its own signature")

crypto_pub.verify(signature, digest, ec.ECDSA(Prehashed(hashes.SHA256())))
print("2. cryptography can validate ecdsa signature (raises exception if not valid)")

if cc_pub.verify(signature, digest, None) is False:
    print("3. coincurve will not validate ecdsa signature")

signature = cc_private_key.sign(digest, None)

crypto_pub.verify(signature, digest, ec.ECDSA(Prehashed(hashes.SHA256())))
print("4. cryptography can validate coincurve signature (raises exception if not valid)")

if cc_pub.verify(signature, digest, None) is True:
    print("5. coincurve will validate its own signature")

比特币和 coincurve 库使用规范签名,而 ecdsa 库并非如此。

规范 签名是什么意思?
一般来说,如果(r,s)是一个有效的签名,那么(r,s') := (r,-s mod n)也是一个有效的签名(n是基点的顺序)。
canonical 签名使用值 s' = -s mod n = n - s 而不是 s,即签名 (r, n-s),如果 s > n/2,s。例如here.

所有来自 ecdsa 库但未被测试程序中的 coincurve 库成功验证的签名都有一个 s > n/2,因此不是规范的,而那些成功验证的是规范的。

所以修复只是将 ecdsa 库的签名规范化,例如:

def canonize(s_bytes):
  n = 115792089237316195423570985008687907852837564279074904382605163141518161494337
  s = int.from_bytes(s_bytes, byteorder='big')
  if s > n//2:
    s = n - s
  return s.to_bytes(32, byteorder='big')

...  
serialized_signature = serialized_signature[:32] + canonize(serialized_signature[32:]) # Fix: canonize
signature = cdata_to_der(deserialize_compact(serialized_signature))
...

通过此修复,coincurve 库成功验证了 all 来自 ecdsa 库的签名在你的测试程序中。