Reputation: 189
I am trying to sign a byte array in python by the same way as it happens on the crypto library with the secp256k1 from NodeJS
This is the code on NodeJS/Browser:
const secp256k1 = require('secp256k1')
var message = [2, 118, 145, 101, 166, 249, 149, 13, 2, 58, 65, 94, 230, 104, 184, 11, 185, 107, 92, 154, 226, 3, 93, 151, 189, 251, 68, 243, 86, 23, 90, 68, 255, 111, 3, 0, 0, 0, 0, 0, 0, 187, 226, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 84, 101, 115, 116, 105, 0, 0, 0, 0, 0, 0, 0];
var private_key_buffer = [122, 241, 114, 103, 51, 227, 157, 149, 221, 126, 157, 173, 31, 111, 43, 118, 208, 71, 123, 59, 96, 68, 57, 177, 53, 59, 151, 188, 36, 167, 40, 68]
const signature = secp256k1.sign(SHA3BUF(message), private_key_buffer)
This is my implementation in python:
import hashlib
import ecdsa
message = bytearray([2, 118, 145, 101, 166, 249, 149, 13, 2, 58, 65, 94, 230, 104, 184, 11, 185, 107, 92, 154, 226, 3, 93, 151, 189, 251, 68, 243, 86, 23, 90, 68, 255, 111, 3, 0, 0, 0, 0, 0, 0, 187, 226, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 84, 101, 115, 116, 105, 0, 0, 0, 0, 0, 0, 0])
private_key_buffer = bytearray([122, 241, 114, 103, 51, 227, 157, 149, 221, 126, 157, 173, 31, 111, 43, 118, 208, 71, 123, 59, 96, 68, 57, 177, 53, 59, 151, 188, 36, 167, 40, 68])
signinKey = ecdsa.SigningKey.from_string(private_key_buffer, curve=ecdsa.SECP256k1)
signature = signinKey.sign_deterministic(message, hashfunc=hashlib.sha3_256)
but by some reason, the signature I get in the javascript code is diferent from the python code:
java script signature: [23, 54, 64, 151, 95, 33, 200, 66, 246, 166, 144, 182, 81, 179, 124, 223, 250, 50, 137, 169, 45, 181, 197, 74, 225, 207, 116, 125, 50, 241, 38, 52, 118, 215, 252, 94, 191, 154, 200, 195, 152, 73, 1, 197, 158, 24, 72, 177, 118, 39, 241, 82, 114, 107, 25, 106, 67, 205, 202, 4, 7, 57, 82, 237]
python script signature: [213, 69, 97, 237, 85, 226, 217, 201, 51, 14, 220, 92, 105, 59, 54, 92, 87, 88, 233, 147, 191, 15, 21, 86, 134, 202, 205, 223, 83, 134, 70, 39, 10, 19, 147, 20, 181, 180, 88, 103, 79, 55, 144, 98, 84, 2, 224, 127, 192, 200, 200, 250, 170, 129, 67, 99, 163, 72, 92, 253, 109, 108, 104, 206]
So how can I make the python code output the same signature of the JS code?
Upvotes: 1
Views: 1585
Reputation: 49131
For the deterministic ECDSA as described in RFC6979, a hash algorithm is used in two places: One algorithm (H1
) is used for hashing the message, another (H2
) for determining the k
-value. k
is a parameter within the signature algorithm, whose role is described e.g. in RFC6979, section 2.4 or also here. For the non-deterministic variant k
is determined randomly, for the deterministic variant as described in RFC6979.
RFC6979 doesn't specify that H1
and H2
must be different, see RFC6979, section 3.6. Nevertheless, it makes sense that an implementation offers the possibility to define both hash algorithms separately.
The ECDSA implementation of Python generally allows two different hash algorithms to be applied. Before this is shown in the 2nd case, the following variant, which correponds to the posted Python-code, applies the same hash algorithm H1 = H2 = SHA3-256
. The hash algorithm specified in the sign_deterministic
-method defines both H1
and H2
:
import hashlib
import ecdsa
message = b'Everything should be made as simple as possible, but not simpler.'
private_key_buffer = bytearray.fromhex('0000000000000000000000000000000000000000000000000000000000000001')
sk = ecdsa.SigningKey.from_string(private_key_buffer, curve=ecdsa.SECP256k1)
signature = sk.sign_deterministic(message, hashfunc=hashlib.sha3_256)
print(signature.hex())
The signature is:
r = 88ecdbc6a2762e7ad1160f7c984cd61385ff07982280538dd7d2103be2dce720
s = c1487df9feab7afda6e6115bdd4d9c5316e3f917a3235a5e47aee09624491304
The next variant uses H1 = SHA3-256
for hashing the message and H2 = SHA256
for k
-determination. This is possible by replacing the sign_deterministic
-method with the sign_digest_deterministic
-method, which allows separate hashing of the message with H1
. The hash algorithm specified in the sign_digest_deterministic
-method then only defines H2
:
import hashlib
import ecdsa
message = b'Everything should be made as simple as possible, but not simpler.'
private_key_buffer = bytearray.fromhex('0000000000000000000000000000000000000000000000000000000000000001')
digest = hashlib.sha3_256()
digest.update(message)
hash = digest.digest()
sk = ecdsa.SigningKey.from_string(private_key_buffer, curve=ecdsa.SECP256k1)
signature = sk.sign_digest_deterministic(hash, hashfunc=hashlib.sha256)
print(signature.hex())
The signature is:
r = 64b10395957b78d3bd3db279e5fa4ebee36b58dd1becace4bc2d7e3a04cf6259
s = 19f1eee7495064ac679d7b64ab7213b921b650c0a3746f2938ffeede0ff1f2e8
The following code is functionally identical to the posted NodeJS-code:
const secp256k1 = require('secp256k1')
const sha3 = require('js-sha3')
message = 'Everything should be made as simple as possible, but not simpler.'
private_key_buffer = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001','hex')
digest = sha3.sha3_256;
hash = Buffer.from(digest(message), 'hex')
signature = secp256k1.sign(hash, private_key_buffer)
console.log(signature.signature.toString('hex'))
and generates the same signature as in the 2nd case, i.e. apparently H2 = SHA256
. I didn't find a way to change this to SHA3-256
without much effort. However, according to the documentation it is possible to replace the default generator that implements RFC6979. This should also change H2
, but could be more expensive.
In summary: The simplest way to fix the incompatibility of both codes
is to change the Python-code as described above in the 2nd case, i.e. to use the sign_digest_deterministic
-method. The message is then hashed with SHA3-256
, the k
-generation takes place with SHA256
. A more expensive alternative would be to implement an own generator to enable k
-generation with SHA3-256
in the NodeJS-code. Or of course, you try to find another ECDSA-library for the NodeJS-code that allows you to define H1
and H2
separately, analogous to the Python-code.
Update:
Canonical signature: If (r,s)
is a signature, then (r, -s mod n) = (r, n - s)
is also a valid signature. Here n
is the order of the base point. If in case s > n/2
the part -s mod n = n - s
is used instead of s
, then the result for the signature is unambiguous and is limited to the area below n/2
. This is called canonical signature, which is particularly relevant for the Bitcoin topic and also frequently used for test vectors.
Upvotes: 2