Pebble Coding

ソフトウェアエンジニアによるIT技術、数学の備忘録

ECDSA(secp256k1)での署名と検証をswiftで行う(secp256k1swiftライブラリ使用)

前回、pythonで行なったECDSA(secp256k1)での署名と検証をswiftで行なってみます。

拙作 secp256k1swift

GitHub - pebble8888/secp256k1swift: secp256k1 by pure swift

を用いてみます。
ちなみに、このライブラリはbitcoinのC言語で書かれたライブラリ

GitHub - bitcoin-core/secp256k1: Optimized C library for EC operations on curve secp256k1

をそのままswiftに書き直しただけ(と言いつつめちゃ大変でしたが)なので、使い方の参考になると思います。

ではソースです。

guard var ctx: secp256k1_context = secp256k1_context_create([.SECP256K1_CONTEXT_SIGN, .SECP256K1_CONTEXT_VERIFY]) else { fatalError() }
let seckey: [UInt8] = [0,0,0,0,0,0,0,0,
                       0,0,0,0,0,0,0,0,
                       0,0,0,0,0,0,0,0,
                       0,0,0,0,0,0,0,3]
var pubkey = secp256k1_pubkey()
let result = secp256k1_ec_pubkey_create(ctx, &pubkey, seckey)
XCTAssert(result)
print("\(pubkey)")

let msg32:[UInt8] = [0x9a, 0xf1, 0x5b, 0x33, 0x6e, 0x6a, 0x96, 0x19,
                     0x92, 0x85, 0x37, 0xdf, 0x30, 0xb2, 0xe6, 0xa2,
                     0x37, 0x65, 0x69, 0xfc, 0xf9, 0xd7, 0xe7, 0x73,
                     0xec, 0xce, 0xde, 0x65, 0x60, 0x65, 0x29, 0xa0]
var sig = secp256k1_ecdsa_signature()

let nonce_func: secp256k1_nonce_function = { (
    _ nonce32: inout [UInt8],
    _ msg32: [UInt8],
    _ key32:[UInt8],
    _ algo16:[UInt8]?,
    _ data: [UInt8]?,
    _ counter: UInt
    ) -> Bool in
    guard let data = data else { return false }
    nonce32 = data
    return true
}
// noncedata: BigEndian
let noncedata: [UInt8] = [0,0,0,0,0,0,0,0,
                          0,0,0,0,0,0,0,0,
                          0,0,0,0,0,0,0,0,
                          0,0,0,0,0,0,0,2]
let ret2 = secp256k1_ecdsa_sign(ctx, &sig, msg32, seckey, nonce_func, noncedata)
XCTAssert(ret2)
print("\(sig)")

let ret3 = secp256k1_ecdsa_verify(ctx, sig, msg32, pubkey)
XCTAssert(ret3)

secp256k1_context_destroy(&ctx)
f9 36 e0 bc 13 f1 01 86 b0 99 6f 83 45 c8 31 b5 29 52 9d f8 85 4f 34 49 10 c3 58 92 01 8a 30 f9 72 e6 b8 84 75 fd b9 6c 1b 23 c2 34 99 a9 00 65 56 f3 37 2a e6 37 e3 0f 14 e8 2d 63 0f 7b 8f 38
c6 04 7f 94 41 ed 7d 6d 30 45 40 6e 95 c0 7c d8 5c 77 8e 4b 8c ef 3c a7 ab ac 09 b9 5c 70 9e e5 09 80 93 07 e5 e6 78 cf 6e 55 83 6a 87 05 d1 68 71 a0 40 ea 36 9a 21 a4 27 d2 10 0a 7d 75 de ba

出力を32バイトで区切ったものがこちらです。

f9 36 e0 bc 13 f1 01 86 b0 99 6f 83 45 c8 31 b5 29 52 9d f8 85 4f 34 49 10 c3 58 92 01 8a 30 f9
72 e6 b8 84 75 fd b9 6c 1b 23 c2 34 99 a9 00 65 56 f3 37 2a e6 37 e3 0f 14 e8 2d 63 0f 7b 8f 38
c6 04 7f 94 41 ed 7d 6d 30 45 40 6e 95 c0 7c d8 5c 77 8e 4b 8c ef 3c a7 ab ac 09 b9 5c 70 9e e5
09 80 93 07 e5 e6 78 cf 6e 55 83 6a 87 05 d1 68 71 a0 40 ea 36 9a 21 a4 27 d2 10 0a 7d 75 de ba

seckey=3はbigendianの32バイトで設定しています。
nonce=2もbigendianで設定していますが、
このecdsaライブラリではnonceを生成するクロージャと元となるデータを指定するようになっています。
明示的にnonce=2を設定するには、noncedata引数にnonce=2を与え、
クロージャではデータをそのままセットするようにします。
最初に表示されているのが、公開鍵ですが、このecdsaライブラリでは内部的にbigendianで持っているので、
python版での
pk = (f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9, 388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672)
とは表示が逆になっていますが、一致しています。

次に表示されているのが、署名のrとsです。
bigendianでrが先頭32バイト、次の32バイトがsです。
rはpython版のr = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
と一致していますが、
sはpython版のs = 0xf67f6cf81a19873091aa7c9578fa2e96490e9bfc78ae7e9798004e8252c06287
と一致していないように見えます。
これはこのecdsaライブラリでは、rは必ず正の値を返すが、sは大きさが0に近い方の値を返すという実装になっているためです。
0に近いというのは群の位数lのモジュラで考えてということです。
pythonを使って確認してみます。

>>> py_s = 0xf67f6cf81a19873091aa7c9578fa2e96490e9bfc78ae7e9798004e8252c06287
>>> l = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
>>> print("%x" % (py_s-l))
-9809307e5e678cf6e55836a8705d16871a040ea369a21a427d2100a7d75deba

符号を除き一致していることが分かります。
このライブラリではrは正の値を返しますが、sでは値が群の位数の半分より大きい場合、sの代わりに正の値 s' = l-s を返します。
符号が一致していないけど、こんなことしても大丈夫なのか?と気になりますが、結論からいうと署名の検証はパスしますので大丈夫 です。
s'を使って検証してみます。
 u_1' = m {s'} ^ {-1} (\mod n)
 = m \frac {1} {n-s} (\mod n)
 = m {s} ^ {-1} \frac {s} {n-s} (\mod n)
 = m {s} ^ {-1} \frac {s -n} {n-s} (\mod n)
 = -m {s} ^ {-1} (\mod n)となり、値に負号がついただけです。
 u_2'も同じように計算すると結局、
 u_1' G + u_2' A = -1 \cdot nonce G となりますが、楕円曲線の点のマイナスはy座標点の正負を反転させるだけなので、xの値は変わりません。
そのため、検証はパスするというわけです。
ただし、このように使うライブラリ間で、選択する値にいくつか自由度があると、複数ライブラリを使った
際に違いが生じるので気をつける必要があります。
例えば、bitcoinのsecp256k1実装ではrの値が、群の位数の半分より大きな値だと検証を失敗させる実装になっているので、
これと同じ実装になっていないecdsaライブラリを使うと、まともに動かないものになります。