如何使用 ECDSA secp256k1 曲线在 python 中以与在 Javascript 中签名相同的方式对消息进行签名?
How to sign message in python the same way it is signed in Javascript by using the ECDSA secp256k1 curve?
我正在尝试对 python 中的字节数组进行签名,其方式与使用 secp256k1 from NodeJS
在加密库中发生的方式相同
这是 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)
这是我在 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)
但由于某些原因,我在 javascript 代码中获得的签名与 python 代码中的签名不同:
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]
那么如何让python代码输出与JS代码相同的签名呢?
对于确定性 ECDSA,如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中所述。对于非确定性变体,k
是随机确定的,对于确定性变体,如 RFC6979 中所述。
RFC6979 未指定 H1
和 H2
必须不同,请参阅 RFC6979, section 3.6。尽管如此,一个实现提供了分别定义两种哈希算法的可能性是有道理的。
Python的 ECDSA 实现通常允许应用两种不同的哈希算法。在第二种情况中显示之前,以下变体对应于发布的 Python 代码,应用 same 哈希算法 H1 = H2 = SHA3-256
。 sign_deterministic
方法中指定的哈希算法同时定义了 H1
和 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())
签名是:
r = 88ecdbc6a2762e7ad1160f7c984cd61385ff07982280538dd7d2103be2dce720
s = c1487df9feab7afda6e6115bdd4d9c5316e3f917a3235a5e47aee09624491304
下一个变体使用 H1 = SHA3-256
对消息进行哈希处理,并使用 H2 = SHA256
进行 k
-确定。这可以通过将 sign_deterministic
方法替换为 sign_digest_deterministic
方法来实现,该方法允许使用 H1
单独散列消息。 sign_digest_deterministic
-method 中指定的哈希算法只定义了 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())
签名是:
r = 64b10395957b78d3bd3db279e5fa4ebee36b58dd1becace4bc2d7e3a04cf6259
s = 19f1eee7495064ac679d7b64ab7213b921b650c0a3746f2938ffeede0ff1f2e8
以下代码在功能上与发布的 NodeJS 代码相同:
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'))
并生成与第二种情况相同的 签名,即显然 H2 = SHA256
。我没费多少功夫就找到了将其更改为 SHA3-256
的方法。但是,根据文档,可以 replace the default generator 实现 RFC6979。这也应该改变 H2
,但可能会更昂贵。
总结:最简单的解决两个代码不兼容的方法
就是按照上面第二种情况中描述的那样改变Python-代码,即使用sign_digest_deterministic
-方法。然后使用 SHA3-256
对消息进行哈希处理,k
生成发生在 SHA256
中。一个更昂贵的替代方案是实现自己的生成器以在 NodeJS 代码中启用 k
和 SHA3-256
生成。或者,当然,您尝试为 NodeJS 代码找到另一个 ECDSA 库,它允许您分别定义 H1
和 H2
,类似于 Python 代码。
更新:
规范签名:如果(r,s)
是签名,那么(r, -s mod n) = (r, n - s)
也是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。
我正在尝试对 python 中的字节数组进行签名,其方式与使用 secp256k1 from NodeJS
在加密库中发生的方式相同这是 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)
这是我在 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)
但由于某些原因,我在 javascript 代码中获得的签名与 python 代码中的签名不同:
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]
那么如何让python代码输出与JS代码相同的签名呢?
对于确定性 ECDSA,如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中所述。对于非确定性变体,k
是随机确定的,对于确定性变体,如 RFC6979 中所述。
RFC6979 未指定 H1
和 H2
必须不同,请参阅 RFC6979, section 3.6。尽管如此,一个实现提供了分别定义两种哈希算法的可能性是有道理的。
Python的 ECDSA 实现通常允许应用两种不同的哈希算法。在第二种情况中显示之前,以下变体对应于发布的 Python 代码,应用 same 哈希算法
H1 = H2 = SHA3-256
。sign_deterministic
方法中指定的哈希算法同时定义了H1
和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())
签名是:
r = 88ecdbc6a2762e7ad1160f7c984cd61385ff07982280538dd7d2103be2dce720 s = c1487df9feab7afda6e6115bdd4d9c5316e3f917a3235a5e47aee09624491304
下一个变体使用
H1 = SHA3-256
对消息进行哈希处理,并使用H2 = SHA256
进行k
-确定。这可以通过将sign_deterministic
方法替换为sign_digest_deterministic
方法来实现,该方法允许使用H1
单独散列消息。sign_digest_deterministic
-method 中指定的哈希算法只定义了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())
签名是:
r = 64b10395957b78d3bd3db279e5fa4ebee36b58dd1becace4bc2d7e3a04cf6259 s = 19f1eee7495064ac679d7b64ab7213b921b650c0a3746f2938ffeede0ff1f2e8
以下代码在功能上与发布的 NodeJS 代码相同:
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'))
并生成与第二种情况相同的 签名,即显然
H2 = SHA256
。我没费多少功夫就找到了将其更改为SHA3-256
的方法。但是,根据文档,可以 replace the default generator 实现 RFC6979。这也应该改变H2
,但可能会更昂贵。
总结:最简单的解决两个代码不兼容的方法
就是按照上面第二种情况中描述的那样改变Python-代码,即使用sign_digest_deterministic
-方法。然后使用 SHA3-256
对消息进行哈希处理,k
生成发生在 SHA256
中。一个更昂贵的替代方案是实现自己的生成器以在 NodeJS 代码中启用 k
和 SHA3-256
生成。或者,当然,您尝试为 NodeJS 代码找到另一个 ECDSA 库,它允许您分别定义 H1
和 H2
,类似于 Python 代码。
更新:
规范签名:如果(r,s)
是签名,那么(r, -s mod n) = (r, n - s)
也是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。