今回はブロックチェーンの”社会実装”を念頭に、より実践的なリモート承認を想定をしてみます。
前回までの記事を先に読んでおいてください。
ブロックチェーンでリモート承認してみる(1)
ブロックチェーンでリモート承認してみる(2)
制約条件
- 申請者が手数料分のお金を持っていない
- 申請に使用するアカウントと申請者を区別したい
解決策
- 承認者が申請に必要な手数料を肩代わりする
- 申請アカウントを申請者が署名者とするマルチシグアカウントとして、申請者が申請書の所有を表現する
何がどう実践的なの?
申請をする立場の人がネットワーク利用のための手数料を持ち合わせていない状況は、今後頻繁に発生し得ることかもしれません。取引所を使用したことが無い人のことを考えておくのが重要です。
また、ブロックチェーンが扱う秘密鍵は本来自分が生成するものであって会社から貸与されるべきものではありません。一方で申請などに使用するアカウントは会社が会社が管理したアカウント上で行うことが望ましいです。そのため、今回はマルチシグを用いて会社の資産を従業員(申請者)が所有するというモデルを表現してみました。
事前準備
google chrome ブラウザよりタブを2つを開いてF12 キーを押して開発者コンソールを表示してください。
ライブラリ読み込み
(script = document.createElement('script')).src = 'https://xembook.github.io/nem2-browserify/symbol-sdk-pack-0.21.0.js';
document.getElementsByTagName('head')[0].appendChild(script);
定義
モジュールのインポート、定数宣言、ノードへアクセスするための準備を行います。
nem = require("/node_modules/symbol-sdk");
op = require("/node_modules/rxjs/operators");
NODE = "https://sym-test.opening-line.jp:3001";
GENERATION_HASH = "6C1B92391CCB41C96478471C2634C111D9E989DECD66130C0430B5B8D20117CD";
txHttp = new nem.TransactionHttp(NODE);
accountHttp = new nem.AccountHttp(NODE);
wsEndpoint = NODE.replace('http', 'ws');
listener = new nem.Listener(wsEndpoint, WebSocket);
listener.open().then(() => {
listener.newBlock();
});
初期設定
承認者アカウント
bob = nem.Account.generateNewAccount(nem.NetworkType.TEST_NET);
console.log(bob.address.plain());
console.log(bob.publicAccount);
"http://faucet-0.10.0.x-01.symboldev.network/?recipient=" + bob.address.plain() +"&amount=10"
申請者は手持ちの手数料を持たない条件なので蛇口から入金するのはこちらだけになります。
申請者と申請アカウント
aliceを申請アカウント、carolを申請者アカウントとします。
alice = nem.Account.generateNewAccount(nem.NetworkType.TEST_NET); //会社が割り振ったアカウント
carol = nem.Account.generateNewAccount(nem.NetworkType.TEST_NET); //社員が自分で生成したアカウント
console.log(alice.address.plain());
console.log(alice.publicAccount);
console.log(carol.publicAccount);
alicePublicAccount = nem.PublicAccount.createFromPublicKey("{aliceのpublicKey}",nem.NetworkType.TEST_NET);
carolPublicAccount = nem.PublicAccount.createFromPublicKey("{carolのpublicKey}",nem.NetworkType.TEST_NET);
承認者側でトランザクションを組み立てるので公開アカウント情報をbob側で生成しておきます。
トランザクション生成
feeTx = nem.TransferTransaction.create(
nem.Deadline.create(),
bob.address,
[
new nem.Mosaic(
new nem.MosaicId('5B66E76BECAD0860'),
nem.UInt64.fromUint(10000000)
)
],
nem.PlainMessage.create(''),
nem.NetworkType.TEST_NET,
);
multisigTx = nem.MultisigAccountModificationTransaction.create(
nem.Deadline.create(), 1,1,
[carolPublicAccount],
[],
nem.NetworkType.TEST_NET
);
aplicationTx = nem.TransferTransaction.create(
nem.Deadline.create(),
bob.address,[],
nem.PlainMessage.create('b7d3e3191d2d2e77ed6e455eeaec147c13e19f0c079f0ca0dcff853f3df46911'),
nem.NetworkType.TEST_NET
);
approvalTx = nem.TransferTransaction.create(
nem.Deadline.create(),
alicePublicAccount.address,[],
nem.PlainMessage.create('approved:b7d3e3191d2d2e77ed6e455eeaec147c13e19f0c079f0ca0dcff853f3df46911'),
nem.NetworkType.TEST_NET
);
- feeTx
- 申請者が送信するときに必要な手数料を承認者から送金しておきます。
- multisigTx
- 申請者アカウントを申請アカウントの署名者に設定します。
- aplicationTx
- 申請アカウントから承認アカウントへ申請ファイルのハッシュ値を刻んだメッセージを送信します。
- approvalTx
- 承認者アカウントから申請アカウントへ承認メッセージを送信します
トランザクションの集約と署名
aggregateTx = nem.AggregateTransaction.createBonded(
nem.Deadline.create(),
[
feeTx.toAggregate(bob.publicAccount),
multisigTx.toAggregate(alicePublicAccount),
aplicationTx.toAggregate(alicePublicAccount),
approvalTx.toAggregate(bob.publicAccount)
],
nem.NetworkType.TEST_NET,[],
nem.UInt64.fromUint(1000000)
);
signedTx = bob.sign(aggregateTx, GENERATION_HASH);
署名するアカウントが誰かを間違えないようにして配列に並べます。
ロックトランザクションの作成と署名
集約したトランザクションは署名が集まるまで、ブロックチェーンで仮置きしてもらうためにそれに関する手数料支払いのトランザクションを作成します。
lockTx = nem.HashLockTransaction.create(
nem.Deadline.create(),
new nem.Mosaic(
new nem.MosaicId('5B66E76BECAD0860'),
nem.UInt64.fromUint(10000000)
),
nem.UInt64.fromUint(5000),
signedTx,
nem.NetworkType.TEST_NET,
nem.UInt64.fromUint(100000)
);
lockSignedTx = bob.sign(lockTx, GENERATION_HASH);
ロック承認後のトランザクション発行処理
リスナーを利用して自動化しておきます。
listener.confirmed(bob.address).pipe(
op.filter(tx => {
console.log(tx);
return tx.transactionInfo !== undefined && tx.transactionInfo.hash === lockSignedTx.hash;
}),
op.mergeMap(_ => {
console.log(_);
return txHttp.announceAggregateBonded(signedTx);
})
).subscribe(x => console.log(x), err => console.error(err));
申請者側の署名処理
bondedListener = listener.aggregateBondedAdded(alice.address)
bondedListener.pipe(
op.filter(_ => !_.signedByAccount(alice.publicAccount)),
op.map(_ => alice.signCosignatureTransaction(nem.CosignatureTransaction.create(_))),
op.mergeMap(_ => txHttp.announceAggregateBondedCosignature(_))
).subscribe(x => console.log(x), err => console.error(err));
bondedListener.pipe(
op.filter(_ => !_.signedByAccount(carol.publicAccount)),
op.filter(_ => { //申請の確認
return _.innerTransactions[2].message.payload === "b7d3e3191d2d2e77ed6e455eeaec147c13e19f0c079f0ca0dcff853f3df46911";
}),
op.map(_ => carol.signCosignatureTransaction(nem.CosignatureTransaction.create(_))),
op.mergeMap(_ => txHttp.announceAggregateBondedCosignature(_))
).subscribe(x => console.log(x), err => console.error(err));
申請者側で必要な署名処理をリスナーで記述しておきます。
承認者が作成したトランザクションが間違いなく自分が作成したハッシュを記しているかの確認が必要です。
aliceでの署名はマルチシグ時の署名です。申請の署名は申請者であるcarolの署名を用いて行います。
実行
それでは実行してみましょう。
txHttp.announce(lockSignedTx).subscribe(x => console.log(x), err => console.error(err));
//ロックトランザクションのconfirmed確認用
"https://sym-test.opening-line.jp:3001/transactionStatus/" + lockSignedTx.hash
//全トランザクションのconfirmed確認用
"https://sym-test.opening-line.jp:3001/transactionStatus/" + signedTx.hash
手数料を持ち合わせているのが承認者であるためにBobがネットワークにアナウンスする必要があります。
ひとたび実行すれば後の処理は自動的に実行されます。
最後に
全3回にわたるリモート承認についての解説を行いました。
3回目は少し難しかったかもしれませんが、マルチシグ、アグリゲートトランザクション、WebsocketなどNEMブロックチェーンの開発しやすい特徴をフル活用した内容となっています。いつかこの記事が悩めるブロックチェーン技術者を導く光となりますように。