経緯

先日仮想通貨取引所のCoin CheckのNEM約600億円が盗まれるという事件が起きました。

その後の対応として日本の17歳JKが追跡用プログラムを作成したということで話題に話題になりました。(実際はJKではないようですが)

【コインチェック事件】NEM財団所属17歳の天才女子校生ハッカーみなりん氏が流出資金の自動追跡プログラムの開発をして犯人捜査

そこで私も17歳のJKになるために、あるNEMアドレスからの取引を監視し、そのアドレスから送信されたアドレスにもモザイクを送りつけ監視するbotを作成してみました。

環境

Arch Linux
Node.js v9.4.0
nem-sdk 1.6.2
動作確認はテストネットのみで行っています。

はじめに 追跡の仕方

NEMには(お金があれば)誰にでも自由に独自にトークン(NEM用語ではモザイク)を発行することができます。
今回盗まれたと話題になっているNEMも実際はxemという名前のNEMブロックチェーンプラットフォーム上に作られたモザイクの一つです。
(xemはNEMプラットフォームの基軸通貨として扱われているため、私達が作れるモザイクと若干の違いはあります)

モザイクを発行する時の設定次第ではモザイクの移動権限を発行者のみにしたり、後から発行枚数を変更したりすることができます。
またモザイクは受取手によって受取を拒否することができず一方的に送りつけることができ、モザイクを持っているという情報はブロックチェーン上に公開されているため隠したり改ざんすることができません。
そこで、移動権限を発行者のみにしたモザイクを発行し監視対象のアドレスに対してモザイクを送りつけることでそのモザイクを持っているアドレスと危険であると示すことができます。(○○という名前のモザイクが識別子として使われているという情報は他の手段によって共有されていなければなりません)

追跡の方法について知っておくべきことをまとめると
* 誰でもモザイクというものを作れる
* モザイクは一方的に送りつけることができる
* モザイクを持っていることは隠すことができない
* 監視対象の送信先にもモザイクを送り監視対象を広げていく

イメージ画像です
image.png

手順1 モザイクの作成

Botを作る前に追跡用モザイクを作成する必要があります。
追跡用モザイクを作るためには120xem程度が必要になります。
NanoWalletに120xemを準備してください。
Testnetの場合はFaucetやフォーラムでもらいましょう。

NEM Testnet faucet

Paste you address here for beta NEM (Testnet XEM) - Technical Discussion - NEM Forum

xemを手に入れたら、モザイクを作成する前にネームスペースを取得します。
ネームスペースはドメインのようなものだと思ってください。

NanoWalletのサービスからネームスペースの作成を選び、適当にネームスペースを作成します。
Screenshot from 2018-01-27 20-00-37.png
Screenshot from 2018-01-27 20-01-03.png

その後、サービスからモザイクの作成を選びモザイクを作成します。
この時譲渡許可のチェックを外すのを忘れないようにしてください。

Screenshot from 2018-01-27 20-02-22.png

Screenshot from 2018-01-27 20-20-53.png

手順2 botを作る

まずはソースコードを
行数の関係からエラー処理等は省略しています

// PRIVATE_KEYを設定しています
require('dotenv').config()
const nem = require('nem-sdk').default
let network, networkId;

const testnet = true
if (testnet) {
  network = nem.model.nodes.defaultTestnet
  networkId = nem.model.network.data.testnet.id
}else{
  network = nem.model.nodes.defaultMainnet
  networkId = nem.model.network.data.mainnet.id
}

const endpointSocket = nem.model.objects.create('endpoint')(network, nem.model.nodes.websocketPort)
const connector = nem.com.websockets.connector.create(endpointSocket, '')
const endpoint = nem.model.objects.create('endpoint')(network, nem.model.nodes.defaultPort)

const common = nem.model.objects.create('common')('', process.env.PRIVATE_KEY)
const trackerMosaic = nem.model.objects.create('mosaicAttachment')('tohu', 'tracker_mosaic', 1)
let trackerMosaicDefinition

nem.com.requests.namespace.mosaicDefinitions(endpoint, 'tohu')
  .then(res => {
    // 作成したモザイクの定義情報を取得します
    trackerMosaicDefinition = res.data[0]
  })

connector.connect().then(() => {
  nem.com.websockets.subscribe.chain.blocks(connector, res => {
    console.log('new block added! \nsignature: ' +  res.signature, '\n\n')

    res.transactions.forEach(transaction => {
      if (transaction.amount === 0) {
        return
      }

      const sender = nem.model.address.toAddress(transaction.signer, networkId)
      nem.com.requests.account.mosaics.owned(endpoint, sender)
        .then(data => {
          const mosaics = data.data
          mosaics.forEach(mosaic => {
            if(mosaic.mosaicId.namespaceId === 'tohu' && mosaic.mosaicId.name === 'tracker_mosaic') {
              console.log('発見!!!!!')
              sendTrackerMosaic(transaction.recipient)
            }
          })
        })
    })
  })
})

const sendTrackerMosaic = address => {
  const transferTransaction = nem.model.objects.create('transferTransaction')(address, 0, 'You are being chased!!')
  transferTransaction.mosaics = [trackerMosaic]
  const transactionEntity = nem.model.transactions.prepare('mosaicTransferTransaction')(common, transferTransaction, trackerMosaicDefinition, networkId)
  transactionEntity.fee = 100000
  nem.model.transactions.send(common, transactionEntity, endpoint)
    .then(res => {
      console.log('モザイクを送信しました')
    })
}

詳しいAPIの詳細やオブジェクトのプロパティは下の参考サイトからご確認ください。

ブロックの追加を検知する

nem-sdkではWebSocketを使ってブロックチェーンにブロックが追加されたということを購読することができます。

const endpointSocket = nem.model.objects.create('endpoint')(network, nem.model.nodes.websocketPort)
const connector = nem.com.websockets.connector.create(endpointSocket, '')

connector.connect().then(() => {
  nem.com.websockets.subscribe.chain.blocks(connector, res => {
    // ブロックが追加されたときの処理
  })
})

resの中身は https://nemproject.github.io/#block を参照

送信者が追跡用モザイクを持っているかを判断する

transaction.amout === 0としているのはnem以外の移動のときはモザイクを送信しないということと、モザイク発行者のモザイク送信に対して無限ループが発生するのを抑えるためです。

手順1で取得したネームスペースが「tohu」、モザイクの名前が「tracker_mosaic」なので以下のようなif文になっています。

if (transaction.amount === 0) {
  return
}

// transactionの情報から送り主のアドレスを取得する
const sender = nem.model.address.toAddress(transaction.signer, networkId)
// 指定したアドレスが持っているモザイクとその量の一覧を取得する
nem.com.requests.account.mosaics.owned(endpoint, sender)
  .then(data => {
    const mosaics = data.data
    mosaics.forEach(mosaic => {
      if(mosaic.mosaicId.namespaceId === 'tohu' && mosaic.mosaicId.name === 'tracker_mosaic') {
        // 追跡用モザイクを持っていたときの処理
      }
    })
  })

モザイクを送信する

ここが一番苦労したところです。
モザイク送信の最小サンプルが以下になると思います。

大切なのはモザイク送信のときはそのモザイクの定義オブジェクトが必要だということです。
モザイクの定義情報は不変なので、一度手動で取得してソースコードにハードコーディングするのもいいかもしれません。

またハマった点としては手数料の部分です。
普通にトランザクションをcreateで作ってprepareで加工すると手数料(transactionEntity.fee)は50000(0.05xem)となっているのですが、メッセージを追加しているため実際に必要な手数料は100000となりそれは手動で設定する必要がありました。
送信するメッセージのサイズが大きくなると更に必要になりそうです。

message: 'FAILURE_INSUFFICIENT_FEE', というメッセージがエラーで出る場合は手数料が足りていません。

const endpoint = nem.model.objects.create('endpoint')(network, nem.model.nodes.defaultPort)
const common = nem.model.objects.create('common')('', process.env.PRIVATE_KEY)
const trackerMosaic = nem.model.objects.create('mosaicAttachment')('tohu', 'tracker_mosaic', 1)
let trackerMosaicDefinition

nem.com.requests.namespace.mosaicDefinitions(endpoint, 'tohu')
  .then(res => {
    // 作成したモザイクの定義情報を取得します
    trackerMosaicDefinition = res.data[0]
  })

const sendTrackerBadge = address => {
  const transferTransaction = nem.model.objects.create('transferTransaction')(address, 0, 'You are being chased!!')
  transferTransaction.mosaics = [trackerMosaic]
  const transactionEntity = nem.model.transactions.prepare('mosaicTransferTransaction')(common, transferTransaction, trackerMosaicDefinition, networkId)
  transactionEntity.fee = 100000
  nem.model.transactions.send(common, transactionEntity, endpoint)
    .then(res => {
      console.log('モザイクを送信しました')
    })
}

手順3 テストしてみる

できたBotを動かして早速テストしてみます。
先程の追跡用モザイクを持っているアカウントから持っていないアカウントに対して、xemを送信してみます。

Screenshot from 2018-01-27 21-11-15.png

送信が承認されるとコマンドラインに
image.png
と出ました。

送信先のアカウントのウォレットを見てみると
image.png

image.png

送金されてきた1xemの後に追跡用コインが送られてきたことがわかります。

最後に

いかがでしたでしょうか。
かなり長くなってしまいましたが、コーディング自体は2時間ほどでできました。
使いやすいAPIが提供されている点は素晴らしいなと思いました。

追跡BotだけでなくXEMやモザイクを用いたプロダクトの参考になれば幸いです。

参考サイト

QuantumMechanics/NEM-sdk: NEM Developer Kit for Node.js and the browser

New Economy Movement(NEM) APIマニュアル和訳 | PR1SM

NEM-sdkを使ってみる - tadajam's blog

Node.jsとNEM-sdkを利用して仮想通貨XEMのばらまきサイトを超簡単に作る。 - Qiita

NEM のトランザクションを作る - Qiita

私のNEM アドレス
NAWSMV-EGRMSI-IGUENL-E7OCI4-AWI3CE-72BPVP-6X5L

完全な素人で混乱しているのですが、わざわざモザイクをする意味があるのでしょうか。
NEMのブロックチェーンのすべてのブロックとその中にあるトランザクションを辿っていけば資金の流れを追うことは可能ではないのでしょうか。ビットコインしかわからなくてごめんなさい。

37contribution

@cupnoodlegirl さん
コメントありがとうございます。
@cupnoodlegirl さんの仰るとおり、すべてのトランザクションをたどることで資金の流れを完全に把握し追っていくことも技術的には可能です。
それでもなお、モザイクを使う理由としては実装が簡単かつ誰からでも容易に対象者を判別できるという点があります。

ビットコインのウォレットを使ったことがあるならご存知かもしれませんが、膨大な数のブロックをダウンロードしトランザクションを追っていくのは非常に時間がかかります。しかし、nemではAPIを使うことで(NEMのスーパーノードを使わせてもらうことで)即座にあるアドレスがどれだけモザイクを持っているかを知ることができるのです。

また、トランザクションを追う形の追跡方法ではある程度技術のある人しか対象者であることを知ることができませんが、モザイクを用いれば現時点でも取引相手のアドレスが分かればどのモザイクを持っているかということを知ることができるので追跡対象になっているかを簡単に判別することができ、取引を避けることができます。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.