2020-04-17 秘密鍵とダイジェストを固定しても secp256k1 による署名の結果が毎回異なる場合がある
secp256k1 において同一の秘密鍵とダイジェストで署名をしても、結果が異なることがある。
例えば openssl で試すと以下の通りになる。
code:bash
$ openssl ecparam -genkey -name secp256k1 -out ./out/key-pair.pem
$ openssl ec -in ./out/key-pair.pem -outform PEM -out ./out/private.pem
$ echo abcde > ./out/plain.txt
$ openssl dgst -sha1 -sign ./out/private.pem ./out/plain.txt > ./out/signature1.dat
$ openssl dgst -sha1 -sign ./out/private.pem ./out/plain.txt > ./out/signature2.dat
$ openssl asn1parse -inform DER -in ./out/signature1.dat
$ openssl asn1parse -inform DER -in ./out/signature2.dat
0:d=0 hl=2 l= 68 cons: SEQUENCE
2:d=1 hl=2 l= 32 prim: INTEGER :229F6018C7E782507F6149354D37C9973472B207A5AAA97492A475B768CFC5B1
36:d=1 hl=2 l= 32 prim: INTEGER :276ED774EA9D7F5223F2FEE447D16C4BFA0C306CBE1F359A87B43FC2B05B66C2
0:d=0 hl=2 l= 70 cons: SEQUENCE
2:d=1 hl=2 l= 33 prim: INTEGER :94152AE30E0D86867E74B752AA8CA995F9FD2F235D174BF3DDA535B45154B241
37:d=1 hl=2 l= 33 prim: INTEGER :E774D1A493BB54A8E5AFD97FE7EE7BA299B0D29728083E7723AED2474951FEB7
AWS KMS に用意されている asymmetric customer master keys の secp256k1 についても同様に署名の結果が毎回異なる。
もちろん、署名の結果が異なっても、ecrecover したときに得られる公開鍵は同一である。
一方で geth や web3.js に用意されている署名関数を使用すると、同じダイジェストと秘密鍵に対して常に同じ署名の結果を返す。
code:main.go
package main
import "encoding/hex"
import "fmt"
import "github.com/ethereum/go-ethereum/crypto/secp256k1"
func main() {
private_key, _ := hex.DecodeString("29a1ad7fe945e254474891bb530114b8706cdf89844bce9445570743182a69b1")
digest, _ := hex.DecodeString("e28f5ff58ff3f1b24d6ba6e3b3e95e49589e8dd59b91296e76189d6ad2857b22")
signature, _ := secp256k1.Sign(digest, private_key)
fmt.Printf("%s\n%s", hex.EncodeToString(digest), hex.EncodeToString(signature))
}
code:index.js
const Web3 = require("web3")
const web3 = new Web3()
const account = web3.eth.accounts.privateKeyToAccount("29a1ad7fe945e254474891bb530114b8706cdf89844bce9445570743182a69b1")
console.log(account.sign("abc"))
geth についてはコードを追えば理由がわかる。RFC 6979 Deterministic DSA and ECDSA と呼ばれる規格がある。
これにより、ダイジェストと秘密鍵から決定的に nonce を生成し、これを元に署名を生成しているらしい。なので、常に署名の結果が一致すると考えられる。
特にYellow Paper には RFC 6979 について言及がないけどこんなもんなんですかね
web3.js については eth-lib を見ればわかる気がするが、読んでない。
bitcoin についても同様らしい
openssl には RFC 6979 に対応するプルリクが立てられている。
結論
geth で署名の結果が同じになるのは RFC 6979 を実装しているから
geth に RFC 6979 を実装していない署名を投げていいかは未検証
geth は秘密鍵から nonce を計算しているので、署名の受け取り手には RFC 6979 によって nonce を生成されたかは知ることができない
そのため問題なさそう