仮想通貨ウォレットやトランザクション周りを実装していると、
ほぼ必ず通る関門があります。
ECDSA署名
今回は、
ECDSA署名を「たった1バイト」間違えただけで、すべてが動かなくなった話を書きます。
何をしていたのか
やっていたこと自体は、よくある処理です。
- raw transaction を組み立てる
- 署名対象データ(sighash)を作る
- 秘密鍵で ECDSA 署名
- 署名を transaction に埋め込む
- ノードに送信
理論上は、これで通るはずでした。
起きた現象
結果はこうでした。
- rawtransaction が通らない
- ノードから reject される
- エラー内容が曖昧
- 何が間違っているのか分からない
署名自体は「生成されている」し、
形式もそれっぽい。
でも、絶対に通らない
という最悪の状態でした。
原因:たった1バイトのズレ
結論から言うと、原因はこれでした。
ECDSA署名の1バイトが違っていた
- r と s のどちらか
- DERエンコードの長さ
- sighash type の付け方
この 1バイトのズレ によって、
- 署名検証が必ず失敗
- トランザクションは即 reject
されていました。
なぜ見つからなかったのか
1. 見た目では分からない
- 署名の長さは合っている
- DER形式っぽい
- 16進数で見ても違和感がない
しかし内部的には、
完全に別の署名
として扱われます。
2. エラーが親切ではない
多くの場合、
- invalid signature
- non-mandatory-script-verify-flag
- signature error
など、
具体的な原因を教えてくれません。
具体的にハマりやすいポイント
- r / s の leading zero の扱い
- DER長の計算ミス
- sighash type を署名対象に含め忘れる
- sighash type を末尾に付け忘れる
- Big Endian / Little Endian の混同
どれか1つでも間違えると、
署名は即死します。
検証して分かったこと
- 公式実装と1バイト単位で比較
- 既存ウォレットの署名結果と比較
- 自前実装同士でクロスチェック
特に、
署名結果をそのまま比較する
のが一番確実でした。
ECDSA署名で学んだ教訓
- 「だいたい合ってる」は存在しない
- 1ビットでも違えば完全に無効
- 実装より検証が重要
- 自前実装は必ず疑う
まとめ
- ECDSA署名は極端にシビア
- 1バイトの違いで全てが壊れる
- エラーは原因を教えてくれない
- 比較検証が最短ルート
この話が、
これから ECDSA やウォレット実装に挑戦する人の
地雷回避になれば幸いです。