読者です 読者をやめる 読者になる 読者になる

Develop with pleasure!

福岡でCloudとかBlockchainとか。

トランザクションのmalleabilityを利用した攻撃を受けた話

最近testnetでブロードキャストしたトランザクションが、消えて無くなる現象が続いたので調べてみた。

消えたといっても実際に消えてるわけではなく、ブロードキャストした際のTXIDとは別のTXIDでブロックに格納されている。

実際にブロードキャストした際のTXIDは

16693caa1fa4a9eb5792063138d36c4c12a02287426b8c1fd759efb35199b5ef

で、その内容は↓

{
  "hex": "010000000201e4a0f1fa83c642b91feafae36a0f8fded4158dfa6fd650e046b4364b805684000000006a473044022045c65646abc12c71352335dbec2824b2dbdef9253366b4b83439b2190ce098d20220112f48fc8e2c76d079a4bc1d448ec134d22737d9765421b7a9b050dd4dc9368f01210259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520ffffffff31137db564a7fad07c9db5b6b862786589977c68d1270819030a9079941ca6c9010000006a47304402204354565632eedd30fb9ca5c22bb70ef848afd74f7bed354d267705a6e71ea885022019159daf2d623ef634a65399ce70e34c4422c8eba19bf877e6b822ffc6a4607601210259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520ffffffff02801d2c04000000001976a914322653c91d6038e08b6d971e4560842c155c8a8888ac80248706000000001976a9143b9722f91a2e50d913dadc3a6a8a88a58a7b859788ac00000000",
  "txid": "16693caa1fa4a9eb5792063138d36c4c12a02287426b8c1fd759efb35199b5ef",
  "hash": "16693caa1fa4a9eb5792063138d36c4c12a02287426b8c1fd759efb35199b5ef",
  "size": 372,
  "vsize": 372,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "8456804b36b446e050d66ffa8d15d4de8f0f6ae3faea1fb942c683faf1a0e401",
      "vout": 0,
      "scriptSig": {
        "asm": "3044022045c65646abc12c71352335dbec2824b2dbdef9253366b4b83439b2190ce098d20220112f48fc8e2c76d079a4bc1d448ec134d22737d9765421b7a9b050dd4dc9368f[ALL] 0259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520",
        "hex": "473044022045c65646abc12c71352335dbec2824b2dbdef9253366b4b83439b2190ce098d20220112f48fc8e2c76d079a4bc1d448ec134d22737d9765421b7a9b050dd4dc9368f01210259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520"
      },
      "sequence": 4294967295
    }, 
    {
      "txid": "c9a61c9479900a03190827d1687c9789657862b8b6b59d7cd0faa764b57d1331",
      "vout": 1,
      "scriptSig": {
        "asm": "304402204354565632eedd30fb9ca5c22bb70ef848afd74f7bed354d267705a6e71ea885022019159daf2d623ef634a65399ce70e34c4422c8eba19bf877e6b822ffc6a46076[ALL] 0259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520",
        "hex": "47304402204354565632eedd30fb9ca5c22bb70ef848afd74f7bed354d267705a6e71ea885022019159daf2d623ef634a65399ce70e34c4422c8eba19bf877e6b822ffc6a4607601210259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520"
      },
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.70000000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 322653c91d6038e08b6d971e4560842c155c8a88 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914322653c91d6038e08b6d971e4560842c155c8a8888ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mk67zfwR4f5UZg2Lfa7nuvbdsm7mdmUpcV"
        ]
      }
    }, 
    {
      "value": 1.09520000,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 3b9722f91a2e50d913dadc3a6a8a88a58a7b8597 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a9143b9722f91a2e50d913dadc3a6a8a88a58a7b859788ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mkx3DXaWZt1GGaXLFh7cGPvW3NC4ZoHnMR"
        ]
      }
    }
  ]
}

ブロードキャストして、外部のブロックチェーンエクスプローラにもそのトランザクションが伝播したことは確認した。しばらくして、このTXIDのトランザクションが承認されたか確認しようとしたら、

No such mempool or blockchain transaction. Use gettransaction for wallet transactions.

となり、ブロックチェーンエクスプローラ側でもそんなトランザクションは無いと言われる。(さっきまであったのに)

ただ実際にトランザクションが消えてなくなった訳ではなく、

59af5c2f932b23dc19e06dd92e1ead1fd2a2546df7cbb834f47e2c1355272cc6

という別のTXIDでブロックに格納されていた。このトランザクションの内容は↓

{
  "hex": "010000000201e4a0f1fa83c642b91feafae36a0f8fded4158dfa6fd650e046b4364b805684000000006b483045022045c65646abc12c71352335dbec2824b2dbdef9253366b4b83439b2190ce098d2022100eed0b70371d3892f865b43e2bb713ec9e887a50d38f47e8416220daf826d0ab201210259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520ffffffff31137db564a7fad07c9db5b6b862786589977c68d1270819030a9079941ca6c9010000006b48304502204354565632eedd30fb9ca5c22bb70ef848afd74f7bed354d267705a6e71ea885022100e6ea6250d29dc109cb59ac66318f1cb2768c13fb0daca7c3d91a3b8d0991e0cb01210259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520ffffffff02801d2c04000000001976a914322653c91d6038e08b6d971e4560842c155c8a8888ac80248706000000001976a9143b9722f91a2e50d913dadc3a6a8a88a58a7b859788ac00000000",
  "txid": "59af5c2f932b23dc19e06dd92e1ead1fd2a2546df7cbb834f47e2c1355272cc6",
  "hash": "59af5c2f932b23dc19e06dd92e1ead1fd2a2546df7cbb834f47e2c1355272cc6",
  "size": 374,
  "vsize": 374,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "8456804b36b446e050d66ffa8d15d4de8f0f6ae3faea1fb942c683faf1a0e401",
      "vout": 0,
      "scriptSig": {
        "asm": "3045022045c65646abc12c71352335dbec2824b2dbdef9253366b4b83439b2190ce098d2022100eed0b70371d3892f865b43e2bb713ec9e887a50d38f47e8416220daf826d0ab2[ALL] 0259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520",
        "hex": "483045022045c65646abc12c71352335dbec2824b2dbdef9253366b4b83439b2190ce098d2022100eed0b70371d3892f865b43e2bb713ec9e887a50d38f47e8416220daf826d0ab201210259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520"
      },
      "sequence": 4294967295
    }, 
    {
      "txid": "c9a61c9479900a03190827d1687c9789657862b8b6b59d7cd0faa764b57d1331",
      "vout": 1,
      "scriptSig": {
        "asm": "304502204354565632eedd30fb9ca5c22bb70ef848afd74f7bed354d267705a6e71ea885022100e6ea6250d29dc109cb59ac66318f1cb2768c13fb0daca7c3d91a3b8d0991e0cb[ALL] 0259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520",
        "hex": "48304502204354565632eedd30fb9ca5c22bb70ef848afd74f7bed354d267705a6e71ea885022100e6ea6250d29dc109cb59ac66318f1cb2768c13fb0daca7c3d91a3b8d0991e0cb01210259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520"
      },
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.70000000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 322653c91d6038e08b6d971e4560842c155c8a88 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914322653c91d6038e08b6d971e4560842c155c8a8888ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mk67zfwR4f5UZg2Lfa7nuvbdsm7mdmUpcV"
        ]
      }
    }, 
    {
      "value": 1.09520000,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 3b9722f91a2e50d913dadc3a6a8a88a58a7b8597 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a9143b9722f91a2e50d913dadc3a6a8a88a58a7b859788ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mkx3DXaWZt1GGaXLFh7cGPvW3NC4ZoHnMR"
        ]
      }
    }
  ],
  "blockhash": "00000000000022e3612bb0d79705417ff2cd77212d778a87156cc29769fbc83c",
  "confirmations": 5,
  "time": 1491551887,
  "blocktime": 1491551887
}

ブロードキャストしたトランザクションの各入力はSIGHASH_ALLで署名しているので、出力等のデータは全く変わっておらず、Bitcoinが盗まれた訳ではない。入力も当然OutPoint(参照先のTXIDと出力インデックス)は変わってないが、scriptSigが変わっていることが分かる。2つある入力の全てのscriptSigがブロードキャスト時のトランザクションと変わっている。

入力の参照先のscriptPubkeyはP2PKHなので、入力のscriptSig

<署名>[SIGHASH_TYPE] 公開鍵

という構成になる。これがどう改竄されたのか最初の入力データで確認してみる。

ブロードキャスト時のデータ
473044022045c65646abc12c71352335dbec2824b2dbdef9253366b4b83439b2190ce098d20220112f48fc8e2c76d079a4bc1d448ec134d22737d9765421b7a9b050dd4dc9368f01210259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520

署名データは

3044022045c65646abc12c71352335dbec2824b2dbdef9253366b4b83439b2190ce098d20220112f48fc8e2c76d079a4bc1d448ec134d22737d9765421b7a9b050dd4dc9368f01
ブロックに取り込まれたデータ
483045022045c65646abc12c71352335dbec2824b2dbdef9253366b4b83439b2190ce098d2022100eed0b70371d3892f865b43e2bb713ec9e887a50d38f47e8416220daf826d0ab201210259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520

署名データは

3045022045c65646abc12c71352335dbec2824b2dbdef9253366b4b83439b2190ce098d2022100eed0b70371d3892f865b43e2bb713ec9e887a50d38f47e8416220daf826d0ab201

攻撃方法

上記のデータから改竄されているのは署名部分であることが分かる。

各署名は、DER形式のECDSA署名で、以下の形式でシリアライズされている。

0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash-type]
  • total-length
    この後に続くデータの長さ( sighash-typeを除く)
  • R-length
    Rの値の長さを指す1バイトデータ。
  • R
    任意のデータ長で、署名作成時に生成した乱数値。ビッグエンディアンエンコードされている。
  • S-length
    Sの値の長さを指す1バイトデータ。
  • S
    Rと署名対象のメッセージのハッシュ値秘密鍵から計算した値。任意のデータ長で、ビッグエンディアンエンコードされている。
  • sighash-type
    署名時に使用したハッシュタイプのフラグ(0x01, 0x02, 0x03, 0x81, 0x82, 0x83を指定可能)

↑のブロードキャスト時とブロックに入れれた際の署名を見ると、Rの値(45c65646abc12c71352335dbec2824b2dbdef9253366b4b83439b2190ce098d2)は同じであることが分かる。変わっているのはSの値。

これは署名値(r,s)のsの値をマイナスにした、(r, -s (mod N))の場合もその署名は有効と判断するECDSAの署名のmalleabilityを利用したものだ。

ブロードキャスト時のS値は

112f48fc8e2c76d079a4bc1d448ec134d22737d9765421b7a9b050dd4dc9368f

だが、この-s (mod N)の値を計算すると↓

require 'bitcoin'

origin_s = '112f48fc8e2c76d079a4bc1d448ec134d22737d9765421b7a9b050dd4dc9368f'
s = OpenSSL::BN.from_hex(origin_s).to_i

n = Bitcoin.bitcoin_elliptic_curve.group.order.to_i

modified_s = OpenSSL::BN.new((s * -1) % n).to_hex
> 'eed0b70371d3892f865b43e2bb713ec9e887a50d38f47e8416220daf826d0ab2'

となり、改竄後の↓の値に合致する。

eed0b70371d3892f865b43e2bb713ec9e887a50d38f47e8416220daf826d0ab2

改竄されたデータにはさらに先頭に00が付与されている。

影響

今回の署名の改竄は出力先や量が変更できるものではないので、Bitcoinを盗まれるという影響は無い。ただ双方向のペイメントチャネル等のまだブロードキャストされていないトランザクションのTXIDを元にトランザクションを作るようなケースでは、確定したTXIDが必要になる。これはSegwitを導入により回避できる。

そういう用途で使用する際は事前にP2WPKH宛にコインを送って送る必要がありが、その際のトランザクションは今回のようなmalleabilityを利用した攻撃によりTXIDが書き換わる可能性があることを想定しておく必要がある。

また一般に公開されているtestnetのFaucetやウォレットアプリではまだP2WPKHやP2WSHを使った送金ができる機能は見かけないので、そういったアプリ側が早くWitnessなトランザクションを作れるようになる必要がある。そのためにもP2WPKHやP2WSHのアドレスフォーマットを定義したBech32↓のBIP登録が待たれる。

github.com

あと、mainnetでのSegwitのアクティベートも。

他にもtestnetでmalleabilityを利用した改竄被害にあった人いるみたい↓

www.reddit.com

誰が改竄してる?

ブロードキャストしたトランザクションはブロックに入るまでは、外部のブロックチェーンエクスプローラでも確認できたので中間者による改竄ではなくブロックをマイニングしたマイナーの可能性が高い。

通常ブロックをマイニングしたのがどのマイニングプールかは、コインベーストランザクションのcoinbase scriptに記載されていることが多い。 ↑トランザクションはブロックheightが1116497のブロックに入っていて、そのcoinbaseは

0351091100048f46e75804a17031160cdfffe558d87db95884fb08001d2f4542f09f92a92f4144f09f98b12f434b506f6f6c5072696d6172792f

BIP-034によりこの先頭はblock heightで、その後が任意のスクリプトになっており、マイニングプールの情報が記載されていることが多い。block heightを除いたデータをデコードすると↓のようになる。

 �F�X�p1���X�}�X� /EB💩/AD😱/CKPoolPrimary/

CKPoolPrimaryがそうなのか?著名なマイニングプールではなさそう。

それにしても、malleabilityの問題自体は認識していたものの、今まで実際にやられたことは無かったので良い経験になった。