Blockchain
仮想通貨
NEM

仮想通貨NEMアプリ開発時ベストプラクティスのまとめ

NEMでアプリ開発をするときに、だれもが悩む共通話題についてのベストプラクティスをまとめていきたいと思います。

こちらも参考にしてみてください。
NEMTionary
[ARCHIVES] mijin.io forum

手数料

こちらにまとまっています。スモールビジネスなどはあまり知られていないのでぜひ活用しましょう。
NEMの手数料計算

NanoWalletで自動設定される手数料はぎりぎりラインではありません。単純化された関数で少し多めに設定されています。

メッセージ

メッセージの最大数は1024バイトです。

ブロック

1ブロックに取り込めるトランザクション数の最大値は120です。
エアドロップなど行う場合はネットワークに迷惑をかけないよう、注意しましょう。

タイムスタンプ

ネットワーク時間より10秒早いとFAILURE_TIMESTAMP_TOO_FAR_IN_FUTURE エラーが発生してしまいます。
タイムスタンプは /time-sync/network-time から取得した値を1000で割った値を使います。

決済時

0CONF承認

Unconfirmedに乗ったタイミングで「支払いの意思あり」として決済完了としてしまうこと。NEM Walletの場合はマルチシグと普通のUnconfirmed状態を見分けるのが難しく、注意が必要です。マルチシグ署名が必要なトランザクションはUnconfirmedのまま署名が行われないと1日後にキャンセルされてしまいます。(キャンセルは分単位で指定可能でNanoWalletの場合1日後と設定されているようです。)

ブロックに取り込まれないトランザクション

confirmedされたトランザクションについても各ノード間での合意形成に失敗した場合など、ブロックに取り込まれずに取り消しされてしまう可能性があります。外部DBと連携させる場合は不整合が発生しないように数ブロック経過したものを取り込むようにした方が無難です。完全にロックされるのは360ブロックの約6時間後です。

参考:ブロックチェーンの同期

2ノード確認

複数のノードでconfirmedされていることで決済が完了していることを確認する方法です。

総金額別必要CONF制御

総金額に応じて、必要とする経過ブロック数の数を変更します。大きな決済ほど必要ブロック数の数を増やしてリスクを回避します。

マルチシグ

2-of-2の独自仕様

2つのアカウントで最小署名者数を2に設定したマルチシグの場合、署名者数1で相手方のマルチシグを削除することができます。プレゼントする前提での構成には便利ですが、セキュリティを確保したい場合は2-of-3あるいは2-of-4 以上のマルチシグを構成しましょう。

マルチシグされたアカウントの秘密鍵

マルチシグされたアカウント(一般的にAliceで説明されることが多いです)の秘密鍵は送金能力がないため外部に流出しても問題ありません。ただし、委任ハーベストのON,OFFをおこなう権限は残ります。

連署人

連署人は最大で32です。アルファベット順で指定します。

その他の注意点

マルチシグアカウントから暗号化メッセージを送ることはできません。また非対応の取引所やサービスに送金した場合、正しく処理されない場合があります。
あるトランザクションのマルチシグ署名中に署名構成を変更すると、そのトランザクションはキャンセルされます。この仕組みを利用して予期しない巨額の送金を確認した場合に即時に署名者情報を変更して送金をキャンセルさせる、といった監視コントラクトを書くことも可能です。

着金トリガー

WebSocketでの実装

NEMの場合WebSocketに対応しているので、アカウントの状態が変更になった場合のみトリガーを発火させるようなプログラムを記述することが可能です。ただ、ファイアウォールやルータによりWebSocketのポートが制限される可能性もあるのでそのあたりは注意しておきましょう。会社で利用しているPCなどはポート制限されている可能性が高いです。

nem-sdk
var nem = require("nem-sdk").default;

var NODES = Array(
"http://alice2.nem.ninja",
"http://alice3.nem.ninja",
"http://alice4.nem.ninja",
"http://alice5.nem.ninja",
"http://alice6.nem.ninja",
"http://alice7.nem.ninja"
);

var node_url;
function getEndpoint(){

    return NODES[Math.floor(Math.random() * NODES.length)];
}
node_url = getEndpoint();
var endpoint = nem.model.objects.create("endpoint")(node_url, nem.model.nodes.websocketPort);

// Address to subscribe
var address = "NBZNQL2JDWTGUAW237PXV4SSXSPORY43GUSWGSB7";
var connector = nem.com.websockets.connector.create(endpoint, address);

// Try to establish a connection
connect(connector);

// Connect using connector
function connect(connector){
    return connector.connect().then(function() {

        // Subscribe to confirmed transactions channel
        nem.com.websockets.subscribe.account.transactions.confirmed(connector, function(res) {
            //ここに処理を記述
        });
    });
}

なお、websocketについてのAPIドキュメントは現在こちらだけとのこと。
nis websocket channels and data

ポーリングでの実装

上記以外にも定期的にノードに対して状態を確認しにいくことをポーリングと言います。都度HTTP接続を行うのでプログラミングの知識が浅い場合は。この場合の方が確実です。理想はWebSocketで接続できない場合はポーリングに切り替えるロジックの実装です。
(後日サンプルコード記載)

トランザクション

NEM のREST APIで取得できるトランザクションjsonはその種類によって構成が異なります。
モザイクの場合やマルチシグの場合で送金されるXEMの情報が入っている場所が変わってくるので注意が必要です。

マルチシグの場合

data->transaction->otherTrans->amount:送金額

multisig
{
    "data":[
        {
            "meta":{},
            "transaction":{
                "timeStamp":94734973,
                "signature":"",
                "fee":500000,
                "type":4100,
                "deadline":94738573,
                "version":1744830465,
                "signatures":[
                    {
                        "timeStamp":94735045,
                        "otherHash"{
                            "data":"7975b33194054182915b0edca69c6dae8e3e7d03d9100ea101b91d7a21c206d9"
                        },
                        "otherAccount":"NAGJG3QFWYZ37LMI7IQPSGQNYADGSJZGJRD2DIYA",
                        "signature":"",
                        "fee":500000,
                        "type":4098,
                        "deadline":94821445,
                        "version":1744830465,
                        "signer":"ae6754c70b7e3ba0c51617c8f9efd462d0bf680d45e09c3444e817643d277826"
                    }
                ],
                "signer":"aa455d831430872feb0c6ae14265209182546c985a321c501be7fdc96ed04757",
                "otherTrans":{
                    "timeStamp":94734973,
                    "amount":7580000000,
                    "fee":50000,
                    "recipient":"NXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
                    "type":257,
                    "deadline":94738573,
                    "message":{},
                    "version":1744830465,
                    "signer":"fbae41931de6a0cc25153781321f3de0806c7ba9a191474bb9a838118c8de4d3"
                }
            }
        }
    ]
}

モザイクの場合

data->transaction->mosaics->quantity:送金額

/account/transfers/all
{
    "data":[
        {
            "meta":{},
            "transaction":{
                "timeStamp":65168770,
                "amount":1000000,
                "signature":"",
                "fee":4000000,
                "recipient":"NXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
                "mosaics":[
                    {
                        "quantity":8000,
                        "mosaicId":{
                            "namespaceId":"nem",
                            "name":"xem"
                        }
                    }
                ],
                "type":257,
                "deadline":65211970,
                "message":{
                    "payload":"",
                    "type":1
                },
                "version":1744830466,
                "signer":""
            }
        }
    ]
}

シリアライズ

jsonをシリアライズする前に、モザイク名や署名者リストがアルファベット順に並んでいるかチェックしてください。
アルファベット順に並んでいないとannounce時にノード側でエラーとなります。
おそらくノード側の負荷を減らすために、ソートなどはクライアント側でやっておいてねということだと思います。

ハッシュ検索

トランザクションは以下のAPIでハッシュ値より検索できます。
/transaction/get?hash=
ですが、これは公式ドキュメントに記載がありません。ノードによっては再起動でデータから削除できるパラメータもあるそうです。つまり、参照先はフルTXを記録できるノード(アポスティーユ用ノード等)を慎重に選ぶ必要があります。

現在確認できている全TX参照可能なノード一覧
https://github.com/5hyn3/search_nis.transactionHashRetentionTime_-_-1_node/blob/master/result.txt

ノード接続

数百あるノードの中で、中にはメンテナンスが行き届いていないノードも存在します。そういったノードに接続しないようにする方法を検討します。

pythonの読めるかたはなむやんさんのこちらが参考になるかもしれません。
https://github.com/namuyan/nem-python/blob/master/nem_python/nem_connect.py

信頼できるノードの調べ方

SNの条件に4ブロックまでの揺れが許容されているため、同期に遅れ始めたノードがギリギリ生き残っている場合があります。
/node/active-peers/max-chain-heightと/chain/heightを比較して
アプリで許容できるブロック差を判断します。
基本差異が1なら許容して連続して続くようならば除外するのが良いかと思います。

1ノードに対する負荷の目安

スーパーノードの条件に1秒あたり9アクセスというものがあるので、それを目安にするとよいでしょう。(ただし、他からのアクセスもあるので、常に占有はしないように)

ノード接続に失敗した場合の再接続方法

ノード切断を検知した場合は再接続時に各ノード固有のidパラメータを再利用しないように注意しましょう。また返答に数十秒を要するノードもざらに存在するので、タイムアウトは必ず指定しましょう。

WebSocketの場合

nem-sdk
var nem = require("nem-sdk").default;

var NODES = Array("http://alice2.nem.ninja",...);

function getEndpoint(){
        return NODES[Math.floor(Math.random() * NODES.length)];
}
var node_url = getEndpoint();
var endpoint = nem.model.objects.create("endpoint")(node_url, nem.model.nodes.websocketPort);

var address = "NBZNQL2JDWTGUAW237PXV4SSXSPORY43GUSWGSB7";
var connector = nem.com.websockets.connector.create(endpoint, address);

connect(connector);
function connect(connector){
    return connector.connect().then(function() {
    }, function(err) {reconnect();
    });
}

function reconnect() {
    node_url = getEndpoint();
    endpoint = nem.model.objects.create("endpoint")(node_url, nem.model.nodes.websocketPort);
    connector = nem.com.websockets.connector.create(endpoint, address);
    connect(connector);
}

HTTPS接続について

現在、https対応がSN条件ではなく、httpsを標準にするとSN報酬がもらえない状況のため、SNをHTTPS対応にする人は少ないです。

参考
https://docs.google.com/presentation/d/1DfzIU06TvQXbHAjvq-FP2kkBZ7JN8NnUS9PjQFDqXTs/edit#slide=id.g35a15bbd80_0_92

既知の接続先一覧

スーパーノード

イーサセキュリティ社の加門さんによってHTTPS化が進められているのでそちらの資料を参考にしてください。
https://github.com/ethersecurity/nodes/blob/master/nem/nodes.txt

テストネット

https://nis-testnet.44uk.net:7891/status
https://nistest.ttechdev.com:7891/status
https://nistest.opening-line.jp:7891/status
https://planethouki.ddns.net:7891

本人確認

NEMモザイクで作るイベントチケット売買システム

秘密鍵管理

たとえHTTPS接続であったとしても、ネットワーク上に秘密鍵を乗せないのが原則です(委任ハーベストアカウントは除く=0XEMが条件であるため)。サーバ上で秘密鍵を作成したらサーバ上だけで使用、クライアントで作成したらクライアントだけで使用、を徹底しましょう。

ハードディスクに秘密鍵を保存せず、パスワードだけで都度秘密鍵を合成する方法

(後日NanoWalletを解析予定)

Cryptographic Asset Management

ブロックチェーンサービスのセキュリティを考える p.28,p.29

マルチシグの譲渡

突然の人事異動、技術者の離脱などが起こっても大丈夫ないように、システムは修正を行わずに安定稼働させないといけません。
ここでは、開発会社から暗号通貨システムの納品を受け、運用担当者が秘密鍵を譲渡せずに権限だけを委譲するシーケンス例を挙げます。
image.png

コールドウォレットについて

参考:
NEM のコールドウォレットの自作は難しいのか検証してみた

アプリ

申請

現在iPhoneアプリの申請は企業が行わないと審査に通らないようです。
NEMにおいてはトランザクションを発行させる行為が個人アプリでは禁止されています。

連携

ログイン

1XEM送金を送金し、パスワードを暗号化したメッセージを送り返すという手法があります。

アポスティーユ

オープンアポスティーユで登録された画像を利用することができます。

tipnem

後日記載

モザイク

徴収(levy)について

【仮想通貨NEM】税金付き(levy)モザイクについて(https://cryptocurrency.muragon.com/entry/19.html)

ホワイトハッカーJK17氏が使った動かせないモザイクもこのlevyの仕組みを利用しています。

配布量について

これが正解というわけではないですが、配る量の目安についての考察です
贈るモザイクの「数」に込められる意味の考察

  • 1個:あなただけに
  • 10個:何かあったら使ってね
  • 100個:あなたが使う分だけ
  • 1000個:知り合いに配ってね
  • 10000個:うまく運用してね

トマティーナをやった実感値ですが、10万モザイクを持っていると500投げるのに抵抗が無くなります。
1万モザイクなら50ぐらい?だいたい200分の1でしょうか。

初期配布

初めてNEMを始める人にモザイクを送る場合は注意が必要です。XEMがないとモザイクを送ることができません。利用を促したい場合は少量のXEMも同時に送る必要があります。これについては、今後実装される予定のカタパルトで解決するかもしれません。

権利の証明としての利用

閲覧権とNEMモザイク

カタパルト

新機能

ざっくり分かるNEM2(Catapult) 〜アグリゲートトランザクションを知る〜
ざっくり分かるNEM2(Catapult) 〜マルチレベルマルチシグネチャ〜

ユースケース

nem2のサプライチェーンマネジメントケースのワークショップ用プロジェクトをやってみた
NEM1とNEM2のユースケース別API変更点

テストサーバ