pythonnode.jsstm32ecdsasecp256k1

ECDSA signature verification mismatch


I see a strange behaviour on ECDSA signature verification from the nodejs's secp256k1 package that sometimes fails the signature check. I use the following public key:

33 2E 16 0F 4C 24 1F 50 0B 5A 67 13 EB E1 52 52 
D1 E2 BA A0 0A B9 7B 54 6E 5C CD 32 E4 FE 26 2A 
B5 51 5A BF CA EF D5 9D FD 35 AA 3A 4B 23 1C 7C 
1A 2E 3B 4A B7 84 7C 49 89 66 66 98 E6 4F FA B4

Now, given the message hash

0C 8D 6D 12 60 93 2B 13 04 DA 48 56 F5 DB 14 DE 
E6 51 69 97 5D 04 89 1F 5E F3 56 A5 77 12 31 10

and the signature

989EFF3505B719017F9DC0CB1D46CBC305940CA458742357BABC0E81C306704FE4F1CD5921E42FEC1CD184FBF0D09E82BCCF3B7F8706D15E4B331302F9845A1F

with both Python's ecdsa and ST's X-CUBE-CRYPTOLIB it verifies successfully, instead with nodejs's secp256k1 the signature gets rejected. Any ideas? On nodejs I have to add 0x04 before the publickey and it works perfectly in most of the cases. The following signatures/hash couple for example is accepted:

hash

43 82 6b bf 48 61 77 e7 c9 3e 47 b3 ad cf 80 c2 51 46 29 a1 97 15 13 3b 8c b5 bb a0 89 c5 cb bc

Signature

D5FA95C2B66DA7ECB294E9C677495BC24425076C6C9DE42DAB9C4F0FD25AE854649E6F3042611F8441DAE82A14D6145E3C3EB8504A8F673FADDF94702CF641C3

Thanks


Solution

  • Bitcoin and the secp256k1 library use canonical signatures, while this constraint does not apply to the ecdsa library (and presumably not to X-CUBE-CRYPTOLIB).

    Canonical signature: In general, if (r, s) is a valid signature, then (r, -s) = (r, n - s) is also a valid signature (n: order of the base point). A canonical signature uses the value s' = n - s if s > n/2, see here.

    Therefore, signatures with s > n/2 are always validated as invalid by the secp256k1 library, while this does not apply to the ecdsa library. Signatures with smaller s are validated identically by both libraries. This is the reason for the sporadic occurrence of the issue.

    For the secp256k1 library to verify the posted signature as valid, it must be normalized. The secp256k1 library provides the signatureNormalize() function for this purpose:

    const crypto = require('crypto')
    const secp256k1 = require('secp256k1')
    var signature = Buffer.from('989EFF3505B719017F9DC0CB1D46CBC305940CA458742357BABC0E81C306704FE4F1CD5921E42FEC1CD184FBF0D09E82BCCF3B7F8706D15E4B331302F9845A1F', 'hex');
    signature = secp256k1.signatureNormalize(signature); // FIX!
    var publicKey = Buffer.from('04332E160F4C241F500B5A6713EBE15252D1E2BAA00AB97B546E5CCD32E4FE262AB5515ABFCAEFD59DFD35AA3A4B231C7C1A2E3B4AB7847C4989666698E64FFAB4', 'hex');
    var messageHash = Buffer.from("0C8D6D1260932B1304DA4856F5DB14DEE65169975D04891F5EF356A577123110", 'hex');
    console.log(secp256k1.ecdsaVerify(signature, messageHash, publicKey)); // true