├── _config.yml ├── LICENSE ├── 01_introduction.md ├── 02_setting.md ├── 12_offline_signature.md ├── README.md ├── 07_metadata.md ├── 03_account.md ├── 05_mosaic.md ├── 06_namespace.md ├── 08_lock.md ├── 10_observer.md ├── 11_restriction.md ├── 09_multisig.md ├── 04_transaction.md └── 13_verify.md /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 XEMBook 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /01_introduction.md: -------------------------------------------------------------------------------- 1 | ### 今日から現場で使える 2 | # 速習Symbolブロックチェーン 3 | 4 | # はじめに 5 | 6 | ## 本ドキュメントのねらい 7 | 8 | 網羅的に技術全般を扱う一般的な公式ドキュメントとは異なり、本ドキュメントは、素早くSymbolブロックチェーンを活用する技術を身に着けていただくための解説書です。最初から読み進めていただければ、Symbolを活用したアプリ開発のための全体像を無理なく系統立てて学習できるように構成しています。 9 | なお、ノードの構築方法やコンセンサスアルゴリズム、インセンティブ報酬などの説明はありません。 10 | 11 | ## 本書の対象者 12 | 13 | - ブロックチェーンを試してみたいけど、なにから勉強したらいいのかわからない方。 14 | - ブロックチェーンを覚えても、なにに使えるのかピンと来ない方。 15 | - Symbolの教材を作ろうとしてるけどどういう手順で教えたらいいのか悩んでいる方。 16 | - Symbolって簡単っていうけど、どういう感じで簡単なのかさくっと知りたい方。 17 | 18 | ## 現場で使えるとは? 19 | 20 | ブロックチェーンを構成するデータの最小単位はお金ではなくタイムスタンプつき存在証明です。ここに注目すればブロックチェーンは認証やトレーサビリティなど、さらにその用途を広げることができます。 世の中を見渡せば、あらゆることが **信頼の数珠繋ぎ** で成り立っていることが分かります。その信頼を形にすることが困難なために、多くのことがお金に変換されて構築されてきました。 21 | 22 | 今、ブロックチェーン技術によりその信頼をお金に変換することなく改ざん不可能な形で記録することができるようになりました。本ドキュメントは、金融だけではなくさらに多くのビジネスや文化の **「現場」** で活躍する人にブロックチェーンの力を活かすヒントを得てもらうために執筆しました。 23 | 24 | ## 「明日から」じゃなくて「今日から」使えるの意味 25 | 昨今、IoTなどの開発現場では **「もはやPoCすら必要ない」** と表現されることがあります。ITを構成する部品化が進み、試しに作ってみた装置がそのまま実運用を始めてしまえるほどにモジュール化されつつあります。Symbolブロックチェーンもアプリケーションを構築するまでもなく、そして自分でノードを建てる必要もなく、設定したアカウントやトークンがコミュニティの提供するツール群でそのまま高セキュリティな情報基盤として活用できるプラットフォームとなっています。 26 | 27 | ぜひ、本ドキュメントでその可能性を感じていただければと思います。なお、各章の最後に載せた「現場で使えるヒント」は各機能の横断的な知識が必要になりますので最初は読み飛ばしていただいても大丈夫です。(いくつかの章の現場で使えるヒントは「現在執筆中」とさせていただいております。) 28 | 29 | ## Symbolブロックチェーンでできないこと 30 | 31 | 他のチェーンで開発されてきた方のために、すこしだけ説明しておきます。 32 | 33 | Symbolブロックチェーンにはコントラクトアカウントが存在しません。したがって、スマートコントラクトをデプロイするという発想がなく、すべてのスマートコントラクトは1回だけ実行されて、その効力を失います。そのため、デプロイレス・ワンタイムスマートコントラクトと表現されることもあります。 34 | 35 | デプロイ不要なため、任意の言語でスマートコントラクトを記述することができ、また、1回限りの実行のため、不適切なループ制御などでネットワークのリソースを大量に消費したり、コントラクト内のロジックを再利用して悪用されるといった脆弱性を狙われることもありません。 36 | -------------------------------------------------------------------------------- /02_setting.md: -------------------------------------------------------------------------------- 1 | # 2.環境構築 2 | 3 | 本書の読み進め方について解説します。 4 | 5 | ## 2.1 使用言語 6 | 7 | JavaScriptを使用します。 8 | 9 | ### SDK 10 | symbol-sdk-typescript-javascript v2.0.4 11 | https://github.com/symbol/symbol-sdk-typescript-javascript 12 | 13 | 上記SDKをbrowserify化したものをブラウザの開発者コンソールに読み込ませて使用します。 14 | https://github.com/xembook/nem2-browserify 15 | 16 | ##### 注意 17 | 現在 symbol-sdk v3.0.0がアルファ版としてリリースされており、v 2.0.4はdeprecatedです。 18 | v3ではrxjsに依存した多くの機能が削除されるため、REST APIへの直接アクセスが推奨されます。 19 | 20 | ### リファレンス 21 | Symbol SDK for TypeScript and JavaScript 22 | https://symbol.github.io/symbol-sdk-typescript-javascript/1.0.3/ 23 | 24 | Catapult REST Endpoints (1.0.3) 25 | https://symbol.github.io/symbol-openapi/v1.0.3/ 26 | 27 | ## 2.2 サンプルソースコード 28 | 29 | ### 変数宣言 30 | console上で何度も書き直して動作検証をして欲しいため、あえてconst宣言を行いません。 31 | アプリケーション開発時はconst宣言するなどしてセキュリティを確保してください。 32 | 33 | ### 出力値確認 34 | console.log()を変数の内容を出力します。好みに応じた出力関数に読み替えてお試しください。 35 | 出力内容は `>` 以下に記述しています。サンプルを実行する場合はこの部分を含まずに試してください。 36 | 37 | ### 同期・非同期 38 | 他言語に慣れた開発者の方には非同期処理の書き方に抵抗がある人もいると思うので、特に問題が無い限り非同期処理を使わずに解説します。 39 | 40 | 41 | ### アカウント 42 | #### Alice 43 | 本書では主にAliceアカウントを中心として解説します。 44 | 3章で作成したAliceをその後の章でも引き続き使いますので、十分なXYMを送信した状態でお読みください。 45 | 46 | #### Bob 47 | Aliceとの送受信用のアカウントとして各章で必要に応じて作成します。その他、マルチシグの章などでCarolなどを使用します。 48 | 49 | ### 手数料 50 | 本書で紹介するトランザクションの手数料乗数は100でトランザクションを作成します。 51 | 52 | 53 | ## 2.3 事前準備 54 | ノード一覧より任意のノードのページをChromeブラウザなどで開きます。本書ではテストネットを前提として解説しています。 55 | 56 | - テストネット 57 | - https://symbolnodes.org/nodes_testnet/ 58 | - メインネット 59 | - https://symbolnodes.org/nodes/ 60 | 61 | F12キーを押して開発者コンソールを開き、以下のスクリプトを入力します。 62 | 63 | ```js 64 | (script = document.createElement('script')).src = 'https://xembook.github.io/nem2-browserify/symbol-sdk-pack-2.0.4.js'; 65 | document.getElementsByTagName('head')[0].appendChild(script); 66 | ``` 67 | 68 | 続いて、ほぼすべての章で利用する共通ロジック部分を実行しておきます。 69 | 70 | ```js 71 | NODE = window.origin; //現在開いているページのURLがここに入ります 72 | sym = require("/node_modules/symbol-sdk"); 73 | repo = new sym.RepositoryFactoryHttp(NODE); 74 | txRepo = repo.createTransactionRepository(); 75 | (async() =>{ 76 | networkType = await repo.getNetworkType().toPromise(); 77 | generationHash = await repo.getGenerationHash().toPromise(); 78 | epochAdjustment = await repo.getEpochAdjustment().toPromise(); 79 | })(); 80 | 81 | function clog(signedTx){ 82 | console.log(NODE + "/transactionStatus/" + signedTx.hash); 83 | console.log(NODE + "/transactions/confirmed/" + signedTx.hash); 84 | console.log("https://symbol.fyi/transactions/" + signedTx.hash); 85 | console.log("https://testnet.symbol.fyi/transactions/" + signedTx.hash); 86 | } 87 | ``` 88 | 89 | これで準備完了です。 90 | 91 | 本ドキュメントの内容が少し分かりにくい場合はQiita等の記事もご参考ください。 92 | 93 | [Symbolブロックチェーンのテストネットで送金を体験する](https://qiita.com/nem_takanobu/items/e2b1f0aafe7a2df0fe1b) 94 | -------------------------------------------------------------------------------- /12_offline_signature.md: -------------------------------------------------------------------------------- 1 | # 12.オフライン署名 2 | 3 | ロック機構の章で、アナウンスしたトランザクションをハッシュ値指定でロックして、 4 | 複数の署名(オンライン署名)を集めるアグリゲートトランザクションを紹介しました。 5 | この章では、トランザクションを事前に署名を集めてノードにアナウンスするオフライン署名について説明します。 6 | 7 | ## 手順 8 | 9 | Aliceが起案者となりトランザクションを作成し、署名します。 10 | 次にBobが署名してAliceに返します。 11 | 最後にAliceがトランザクションを結合してネットワークにアナウンスします。 12 | 13 | 14 | ## 12.1 トランザクション作成 15 | ```js 16 | bob = sym.Account.generateNewAccount(networkType); 17 | 18 | innerTx1 = sym.TransferTransaction.create( 19 | undefined, 20 | bob.address, 21 | [], 22 | sym.PlainMessage.create("tx1"), 23 | networkType 24 | ); 25 | 26 | innerTx2 = sym.TransferTransaction.create( 27 | undefined, 28 | alice.address, 29 | [], 30 | sym.PlainMessage.create("tx2"), 31 | networkType 32 | ); 33 | 34 | aggregateTx = sym.AggregateTransaction.createComplete( 35 | sym.Deadline.create(epochAdjustment), 36 | [ 37 | innerTx1.toAggregate(alice.publicAccount), 38 | innerTx2.toAggregate(bob.publicAccount) 39 | ], 40 | networkType, 41 | [], 42 | ).setMaxFeeForAggregate(100, 1); 43 | 44 | signedTx = alice.sign(aggregateTx,generationHash); 45 | signedHash = signedTx.hash; 46 | signedPayload = signedTx.payload; 47 | 48 | console.log(signedPayload); 49 | ``` 50 | ###### 出力例 51 | ```js 52 | >580100000000000039A6555133357524A8F4A832E1E596BDBA39297BC94CD1D0728572EE14F66AA71ACF5088DB6F0D1031FF65F2BBA7DA9EE3A8ECF242C2A0FE41B6A00A2EF4B9020E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26000000000198414100AF000000000000D4641CD902000000306771D758886F1529F9B61664B0450ED138B27CC5E3AE579C16D550EDEE5791B00000000000000054000000000000000E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26000000000198544198A1BE13194C0D18897DD88FE3BC4860B8EEF79C6BC8C8720400000000000000007478310000000054000000000000003C4ADF83264FF73B4EC1DD05B490723A8CFFAE1ABBD4D4190AC4CAC1E6505A5900000000019854419850BF0FD1A45FCEE211B57D0FE2B6421EB81979814F629204000000000000000074783200000000 53 | ``` 54 | 55 | 署名を行い、signedHash,signedPayloadを出力します。 56 | signedPayloadをBobに渡して署名を促します。 57 | 58 | ## 12.2 Bobによる連署 59 | 60 | 61 | Aliceから受け取ったsignedPayloadでトランザクションを復元します。 62 | 63 | ```js 64 | tx = sym.TransactionMapping.createFromPayload(signedPayload); 65 | console.log(tx); 66 | ``` 67 | ###### 出力例 68 | ```js 69 | > AggregateTransaction 70 | cosignatures: [] 71 | deadline: Deadline {adjustedValue: 12197090355} 72 | > innerTransactions: Array(2) 73 | 0: TransferTransaction {type: 16724, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64, …} 74 | 1: TransferTransaction {type: 16724, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64, …} 75 | maxFee: UInt64 {lower: 44800, higher: 0} 76 | networkType: 152 77 | payloadSize: undefined 78 | signature: "4999A8437DA1C339280ED19BE0814965B73D60A1A6AF2F3856F69FBFF9C7123427757247A231EB89BB8844F37AC6F7559F859E2FDE39B8FA58A57F36DDB3B505" 79 | signer: PublicAccount 80 | address: Address {address: 'TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ', networkType: 152} 81 | publicKey: "D4933FC1E4C56F9DF9314E9E0533173E1AB727BDB2A04B59F048124E93BEFBD2" 82 | transactionInfo: undefined 83 | type: 16705 84 | version: 1 85 | ``` 86 | 87 | 念のため、Aliceがすでに署名したトランザクション(ペイロード)かどうかを検証します。 88 | ```js 89 | Buffer = require("/node_modules/buffer").Buffer; 90 | res = tx.signer.verifySignature( 91 | tx.getSigningBytes([...Buffer.from(signedPayload,'hex')],[...Buffer.from(generationHash,'hex')]), 92 | tx.signature 93 | ); 94 | console.log(res); 95 | ``` 96 | ###### 出力例 97 | ```js 98 | > true 99 | ``` 100 | 101 | ペイロードがsigner、つまりAliceによって署名されたものであることが確認できました。 102 | 次にBobが連署します。 103 | ```js 104 | bobSignedTx = sym.CosignatureTransaction.signTransactionPayload(bob, signedPayload, generationHash); 105 | bobSignedTxSignature = bobSignedTx.signature; 106 | bobSignedTxSignerPublicKey = bobSignedTx.signerPublicKey; 107 | ``` 108 | 109 | CosignatureTransactionで署名を行い、bobSignedTxSignature,bobSignedTxSignerPublicKeyを出力しAliceに返却します。 110 | Bobが全ての署名を揃えられる場合は、Aliceに返却しなくてもBobがアナウンスすることも可能です。 111 | 112 | ## 12.3 Aliceによるアナウンス 113 | 114 | AliceはBobからbobSignedTxSignature,bobSignedTxSignerPublicKeyを受け取ります。 115 | また事前にAlice自身で作成したsignedPayloadを用意します。 116 | 117 | ```js 118 | signedHash = sym.Transaction.createTransactionHash(signedPayload,Buffer.from(generationHash, 'hex')); 119 | cosignSignedTxs = [ 120 | new sym.CosignatureSignedTransaction(signedHash,bobSignedTxSignature,bobSignedTxSignerPublicKey) 121 | ]; 122 | 123 | recreatedTx = sym.TransactionMapping.createFromPayload(signedPayload); 124 | 125 | cosignSignedTxs.forEach((cosignedTx) => { 126 | signedPayload += cosignedTx.version.toHex() + cosignedTx.signerPublicKey + cosignedTx.signature; 127 | }); 128 | 129 | size = `00000000${(signedPayload.length / 2).toString(16)}`; 130 | formatedSize = size.substr(size.length - 8, size.length); 131 | littleEndianSize = formatedSize.substr(6, 2) + formatedSize.substr(4, 2) + formatedSize.substr(2, 2) + formatedSize.substr(0, 2); 132 | 133 | signedPayload = littleEndianSize + signedPayload.substr(8, signedPayload.length - 8); 134 | signedTx = new sym.SignedTransaction(signedPayload, signedHash, alice.publicKey, recreatedTx.type, recreatedTx.networkType); 135 | 136 | await txRepo.announce(signedTx).toPromise(); 137 | ``` 138 | 139 | 後半部分の連署を追加する部分がPayload(サイズ値)を直接操作しているので少し難しいかもしれません。 140 | Aliceの秘密鍵で再度署名できる場合はcosignSignedTxsを生成した後、以下のように連署済みトランザクションを生成することも可能です。 141 | 142 | ```js 143 | resignedTx = recreatedTx.signTransactionGivenSignatures(alice, cosignSignedTxs, generationHash); 144 | await txRepo.announce(resignedTx).toPromise(); 145 | ``` 146 | 147 | ## 12.4 現場で使えるヒント 148 | 149 | ### マーケットプレイスレス 150 | ボンデッドトランザクションと異なりハッシュロックのための手数料(10XYM)を気にする必要がありません。 151 | ペイロードを共有できる場が存在する場合、売り手は考えられるすべての買い手候補に対してペイロードを作成して交渉開始を待つことができます。 152 | (複数のトランザクションが個別に実行されないように、1つしか存在しない領収書NFTをアグリゲートトランザクションに混ぜ込むなどして排他制御をしてください)。 153 | この交渉に専用のマーケットプレイスを構築する必要はありません。 154 | SNSのタイムラインをマーケットプレイスにしたり、必要に応じて任意の時間や空間でワンタイムマーケットプレイスを展開することができます。 155 | 156 | ただ、オフラインで署名を交換するため、なりすましのハッシュ署名要求には気を付けましょう。 157 | (必ず検証可能なペイロードからハッシュを生成して署名するようにしてください) 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ### 今日から現場で使える 3 | # 速習Symbolブロックチェーン 4 | ## 著作者 5 | ### XEMBook 6 | ## 目次 7 | ### [1.はじめに](./01_introduction.md) 8 | ### [2.環境構築](./02_setting.md) 9 | ### [3.アカウント](./03_account.md) 10 | - [3.1 アカウント作成](03_account.md#31-アカウント生成) 11 | - [3.2 アカウントへの送信](03_account.md#32-アカウントへの送信) 12 | - [3.3 アカウント情報の確認](03_account.md#33-アカウント情報の確認) 13 | - [3.4 現場で使えるヒント](https://github.com/xembook/quick_learning_symbol/blob/main/03_account.md#34-%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B%E3%83%92%E3%83%B3%E3%83%88) 14 | 15 | ### [4.トランザクション](./04_transaction.md) 16 | - [4.1 トランザクションのライフサイクル](04_transaction.md#41-%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB) 17 | - [4.2 トランザクション作成](04_transaction.md#42-%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E4%BD%9C%E6%88%90) 18 | - [4.3 署名とアナウンス](04_transaction.md#43-%E7%BD%B2%E5%90%8D%E3%81%A8%E3%82%A2%E3%83%8A%E3%82%A6%E3%83%B3%E3%82%B9) 19 | - [4.4 確認](04_transaction.md#44-%E7%A2%BA%E8%AA%8D) 20 | - [4.5トランザクション履歴](04_transaction.md#45%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E5%B1%A5%E6%AD%B4) 21 | - [4.6 アグリゲートトランザクション](04_transaction.md#46-%E3%82%A2%E3%82%B0%E3%83%AA%E3%82%B2%E3%83%BC%E3%83%88%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3) 22 | - [4.7 現場で使えるヒント](04_transaction.md#47-%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B%E3%83%92%E3%83%B3%E3%83%88) 23 | ### [5.モザイク](./05_mosaic.md) 24 | - [5.1 モザイク生成](05_mosaic.md#51-%E3%83%A2%E3%82%B6%E3%82%A4%E3%82%AF%E7%94%9F%E6%88%90) 25 | - [5.2 モザイク送信](05_mosaic.md#52-%E3%83%A2%E3%82%B6%E3%82%A4%E3%82%AF%E9%80%81%E4%BF%A1) 26 | - [5.3 現場で使えるヒント](05_mosaic.md#53-%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B%E3%83%92%E3%83%B3%E3%83%88) 27 | ### [6.ネームスペース](./06_namespace.md) 28 | - [6.1 手数料の計算](06_namespace.md#61-%E6%89%8B%E6%95%B0%E6%96%99%E3%81%AE%E8%A8%88%E7%AE%97) 29 | - [6.2 レンタル](06_namespace.md#62-%E3%83%AC%E3%83%B3%E3%82%BF%E3%83%AB) 30 | - [6.3 リンク](06_namespace.md#63-%E3%83%AA%E3%83%B3%E3%82%AF) 31 | - [6.4 未解決で使用](06_namespace.md#64-%E6%9C%AA%E8%A7%A3%E6%B1%BA%E3%81%A7%E4%BD%BF%E7%94%A8) 32 | - [6.5 参照](06_namespace.md#65-%E5%8F%82%E7%85%A7) 33 | - [6.6 現場で使えるヒント](06_namespace.md#66-%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B%E3%83%92%E3%83%B3%E3%83%88) 34 | ### [7.メタデータ](./07_metadata.md) 35 | - [7.1 アカウントに登録](07_metadata.md#71-%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AB%E7%99%BB%E9%8C%B2) 36 | - [7.2 モザイクに登録](07_metadata.md#72-%E3%83%A2%E3%82%B6%E3%82%A4%E3%82%AF%E3%81%AB%E7%99%BB%E9%8C%B2) 37 | - [7.3 ネームスペースに登録](07_metadata.md#73-%E3%83%8D%E3%83%BC%E3%83%A0%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%81%AB%E7%99%BB%E9%8C%B2) 38 | - [7.4 確認](07_metadata.md#74-%E7%A2%BA%E8%AA%8D) 39 | - [7.5 現場で使えるヒント](07_metadata.md#75-%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B%E3%83%92%E3%83%B3%E3%83%88) 40 | ### [8.ロック](./08_lock.md) 41 | - [8.1 ハッシュロック](08_lock.md#81-%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%AD%E3%83%83%E3%82%AF) 42 | - [8.2 シークレットロック・シークレットプルーフ](08_lock.md#82-%E3%82%B7%E3%83%BC%E3%82%AF%E3%83%AC%E3%83%83%E3%83%88%E3%83%AD%E3%83%83%E3%82%AF%E3%82%B7%E3%83%BC%E3%82%AF%E3%83%AC%E3%83%83%E3%83%88%E3%83%97%E3%83%AB%E3%83%BC%E3%83%95) 43 | - [8.3 現場で使えるヒント](08_lock.md#83-%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B%E3%83%92%E3%83%B3%E3%83%88) 44 | 45 | ### [9.マルチシグ化](./09_multisig.md) 46 | - [9.0 アカウントの準備](09_multisig.md#90-%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E6%BA%96%E5%82%99) 47 | - [9.1 マルチシグの登録](09_multisig.md#91-%E3%83%9E%E3%83%AB%E3%83%81%E3%82%B7%E3%82%B0%E3%81%AE%E7%99%BB%E9%8C%B2) 48 | - [9.2 確認](09_multisig.md#92-%E7%A2%BA%E8%AA%8D) 49 | - [9.3 マルチシグ署名](09_multisig.md#93-%E3%83%9E%E3%83%AB%E3%83%81%E3%82%B7%E3%82%B0%E7%BD%B2%E5%90%8D) 50 | - [9.4 マルチシグ送信の確認](09_multisig.md#94-%E3%83%9E%E3%83%AB%E3%83%81%E3%82%B7%E3%82%B0%E9%80%81%E4%BF%A1%E3%81%AE%E7%A2%BA%E8%AA%8D) 51 | - [9.5 マルチシグ構成変更](09_multisig.md#95-%E3%83%9E%E3%83%AB%E3%83%81%E3%82%B7%E3%82%B0%E6%A7%8B%E6%88%90%E5%A4%89%E6%9B%B4) 52 | - [9.6 現場で使えるヒント](09_multisig.md#96-%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B%E3%83%92%E3%83%B3%E3%83%88) 53 | ### [10.監視](./10_observer.md) 54 | - [10.1 リスナー設定](10_observer.md#101-%E3%83%AA%E3%82%B9%E3%83%8A%E3%83%BC%E8%A8%AD%E5%AE%9A) 55 | - [10.2 受信検知](10_observer.md#102-%E5%8F%97%E4%BF%A1%E6%A4%9C%E7%9F%A5) 56 | - [10.3 ブロック監視](10_observer.md#103-%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E7%9B%A3%E8%A6%96) 57 | - [10.4 署名要求](10_observer.md#104-%E7%BD%B2%E5%90%8D%E8%A6%81%E6%B1%82) 58 | - [10.5 現場で使えるヒント](10_observer.md#105-%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B%E3%83%92%E3%83%B3%E3%83%88) 59 | ### [11.制限](./11_restriction.md) 60 | - [11.1 アカウント制限](11_restriction.md#111-%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E5%88%B6%E9%99%90) 61 | - [11.2 グローバルモザイク制限](11_restriction.md#112-%E3%82%B0%E3%83%AD%E3%83%BC%E3%83%90%E3%83%AB%E3%83%A2%E3%82%B6%E3%82%A4%E3%82%AF%E5%88%B6%E9%99%90) 62 | - [11.3 現場で使えるヒント](11_restriction.md#113-%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B%E3%83%92%E3%83%B3%E3%83%88) 63 | ### [12.オフライン署名](./12_offline_signature.md) 64 | - [12.1 トランザクション作成](12_offline_signature.md#121-%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E4%BD%9C%E6%88%90) 65 | - [12.2 Bobによる連署](12_offline_signature.md#122-bob%E3%81%AB%E3%82%88%E3%82%8B%E9%80%A3%E7%BD%B2) 66 | - [12.3 Aliceによるアナウンス](12_offline_signature.md#123-alice%E3%81%AB%E3%82%88%E3%82%8B%E3%82%A2%E3%83%8A%E3%82%A6%E3%83%B3%E3%82%B9) 67 | - [12.4 現場で使えるヒント](12_offline_signature.md#124-%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B%E3%83%92%E3%83%B3%E3%83%88) 68 | 69 | ### [13.検証](./13_verify.md) 70 | - [13.1 トランザクションの検証](13_verify.md#131-%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E6%A4%9C%E8%A8%BC) 71 | - [13.2 ブロックヘッダーの検証](13_verify.md#132-%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%83%98%E3%83%83%E3%83%80%E3%83%BC%E3%81%AE%E6%A4%9C%E8%A8%BC) 72 | - [13.3 アカウント・メタデータの検証](13_verify.md#133-%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%83%A1%E3%82%BF%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E6%A4%9C%E8%A8%BC) 73 | - [13.4 現場で使えるヒント](https://github.com/xembook/quick_learning_symbol/blob/main/13_verify.md#134-%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B%E3%83%92%E3%83%B3%E3%83%88) 74 | 75 | -------------------------------------------------------------------------------- /07_metadata.md: -------------------------------------------------------------------------------- 1 | # 7.メタデータ 2 | 3 | アカウント・モザイク・ネームスペースに対してKey-Value形式のデータを登録することができます。 4 | Valueの最大値は1024バイトです。 5 | 本章ではモザイク・ネームスペースの作成アカウントとメタデータの作成アカウントがどちらもAliceであることを前提に説明します。 6 | 7 | 本章のサンプルスクリプトを実行する前に以下を実行して必要ライブラリを読み込んでおいてください。 8 | ```js 9 | metaRepo = repo.createMetadataRepository(); 10 | mosaicRepo = repo.createMosaicRepository(); 11 | metaService = new sym.MetadataTransactionService(metaRepo); 12 | ``` 13 | ## 7.1 アカウントに登録 14 | 15 | アカウントに対して、Key-Value値を登録します。 16 | 17 | ```js 18 | key = sym.KeyGenerator.generateUInt64Key("key_account"); 19 | value = "test"; 20 | 21 | tx = await metaService.createAccountMetadataTransaction( 22 | undefined, 23 | networkType, 24 | alice.address, //メタデータ記録先アドレス 25 | key,value, //Key-Value値 26 | alice.address //メタデータ作成者アドレス 27 | ).toPromise(); 28 | 29 | aggregateTx = sym.AggregateTransaction.createComplete( 30 | sym.Deadline.create(epochAdjustment), 31 | [tx.toAggregate(alice.publicAccount)], 32 | networkType,[] 33 | ).setMaxFeeForAggregate(100, 0); 34 | 35 | signedTx = alice.sign(aggregateTx,generationHash); 36 | await txRepo.announce(signedTx).toPromise(); 37 | ``` 38 | 39 | メタデータの登録には記録先アカウントが承諾を示す署名が必要です。 40 | また、記録先アカウントと記録者アカウントが同一でもアグリゲートトランザクションにする必要があります。 41 | 42 | 異なるアカウントのメタデータに登録する場合は署名時に 43 | signTransactionWithCosignatoriesを使用します。 44 | 45 | ```js 46 | tx = await metaService.createAccountMetadataTransaction( 47 | undefined, 48 | networkType, 49 | bob.address, //メタデータ記録先アドレス 50 | key,value, //Key-Value値 51 | alice.address //メタデータ作成者アドレス 52 | ).toPromise(); 53 | 54 | aggregateTx = sym.AggregateTransaction.createComplete( 55 | sym.Deadline.create(epochAdjustment), 56 | [tx.toAggregate(alice.publicAccount)], 57 | networkType,[] 58 | ).setMaxFeeForAggregate(100, 1); // 第二引数に連署者の数:1 59 | 60 | signedTx = aggregateTx.signTransactionWithCosignatories( 61 | alice,[bob],generationHash,// 第二引数に連署者 62 | ); 63 | await txRepo.announce(signedTx).toPromise(); 64 | ``` 65 | 66 | bobの秘密鍵が分からない場合はこの後の章で説明する 67 | アグリゲートボンデッドトランザクション、あるいはオフライン署名を使用する必要があります。 68 | 69 | ## 7.2 モザイクに登録 70 | 71 | ターゲットとなるモザイクに対して、Key値・ソースアカウントの複合キーでValue値を登録します。 72 | 登録・更新にはモザイクを作成したアカウントの署名が必要です。 73 | 74 | ```js 75 | mosaicId = new sym.MosaicId("1275B0B7511D9161"); 76 | mosaicInfo = await mosaicRepo.getMosaic(mosaicId).toPromise(); 77 | 78 | key = sym.KeyGenerator.generateUInt64Key('key_mosaic'); 79 | value = 'test'; 80 | 81 | tx = await metaService.createMosaicMetadataTransaction( 82 | undefined, 83 | networkType, 84 | mosaicInfo.ownerAddress, //モザイク作成者アドレス 85 | mosaicId, 86 | key,value, //Key-Value値 87 | alice.address 88 | ).toPromise(); 89 | 90 | aggregateTx = sym.AggregateTransaction.createComplete( 91 | sym.Deadline.create(epochAdjustment), 92 | [tx.toAggregate(alice.publicAccount)], 93 | networkType,[] 94 | ).setMaxFeeForAggregate(100, 0); 95 | 96 | signedTx = alice.sign(aggregateTx,generationHash); 97 | await txRepo.announce(signedTx).toPromise(); 98 | ``` 99 | 100 | ## 7.3 ネームスペースに登録 101 | 102 | ネームスペースに対して、Key-Value値を登録します。 103 | 登録・更新にはネームスペースを作成したアカウントの署名が必要です。 104 | 105 | ```js 106 | nsRepo = repo.createNamespaceRepository(); 107 | namespaceId = new sym.NamespaceId("xembook"); 108 | namespaceInfo = await nsRepo.getNamespace(namespaceId).toPromise(); 109 | 110 | key = sym.KeyGenerator.generateUInt64Key('key_namespace'); 111 | value = 'test'; 112 | 113 | tx = await metaService.createNamespaceMetadataTransaction( 114 | undefined,networkType, 115 | namespaceInfo.ownerAddress, //ネームスペースの作成者アドレス 116 | namespaceId, 117 | key,value, //Key-Value値 118 | alice.address //メタデータの登録者 119 | ).toPromise(); 120 | 121 | aggregateTx = sym.AggregateTransaction.createComplete( 122 | sym.Deadline.create(epochAdjustment), 123 | [tx.toAggregate(alice.publicAccount)], 124 | networkType,[] 125 | ).setMaxFeeForAggregate(100, 0); 126 | 127 | signedTx = alice.sign(aggregateTx,generationHash); 128 | await txRepo.announce(signedTx).toPromise(); 129 | ``` 130 | 131 | ## 7.4 確認 132 | 登録したメタデータを確認します。 133 | 134 | ```js 135 | res = await metaRepo.search({ 136 | targetAddress:alice.address, 137 | sourceAddress:alice.address} 138 | ).toPromise(); 139 | console.log(res); 140 | ``` 141 | ###### 出力例 142 | ```js 143 | data: Array(3) 144 | 0: Metadata 145 | id: "62471DD2BF42F221DFD309D9" 146 | metadataEntry: MetadataEntry 147 | compositeHash: "617B0F9208753A1080F93C1CEE1A35ED740603CE7CFC21FBAE3859B7707A9063" 148 | metadataType: 0 149 | scopedMetadataKey: UInt64 {lower: 92350423, higher: 2540877595} 150 | sourceAddress: Address {address: 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ', networkType: 152} 151 | targetAddress: Address {address: 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ', networkType: 152} 152 | targetId: undefined 153 | value: "test" 154 | 1: Metadata 155 | id: "62471F87BF42F221DFD30CC8" 156 | metadataEntry: MetadataEntry 157 | compositeHash: "D9E2019D7BD5BA58245320392A68B51752E35A35DA349B08E141DCE99AC3655A" 158 | metadataType: 1 159 | scopedMetadataKey: UInt64 {lower: 1789141730, higher: 3475078673} 160 | sourceAddress: Address {address: 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ', networkType: 152} 161 | targetAddress: Address {address: 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ', networkType: 152} 162 | targetId: MosaicId 163 | id: Id {lower: 1360892257, higher: 309702839} 164 | value: "test" 165 | 3: Metadata 166 | id: "62616372BF42F221DF00A88C" 167 | metadataEntry: MetadataEntry 168 | compositeHash: "D8E597C7B491BF7F9990367C1798B5C993E1D893222F6FC199F98915339D92D5" 169 | metadataType: 2 170 | scopedMetadataKey: UInt64 {lower: 141807833, higher: 2339015223} 171 | sourceAddress: Address {address: 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ', networkType: 152} 172 | targetAddress: Address {address: 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ', networkType: 152} 173 | targetId: NamespaceId 174 | id: Id {lower: 646738821, higher: 2754876907} 175 | value: "test" 176 | ``` 177 | metadataTypeは以下の通りです。 178 | ```js 179 | sym.MetadataType 180 | {0: 'Account', 1: 'Mosaic', 2: 'Namespace'} 181 | ``` 182 | 183 | ### 注意事項 184 | メタデータはキー値で素早く情報にアクセスできるというメリットがある一方で更新可能であることに注意しておく必要があります。 185 | 更新には、発行者アカウントと登録先アカウントの署名が必要のため、それらのアカウントの管理状態が信用できる場合のみ使用するようにしてください。 186 | 187 | 188 | ## 7.5 現場で使えるヒント 189 | 190 | ### 有資格証明 191 | 192 | モザイクの章で所有証明、ネームスペースの章でドメインリンクの説明をしました。 193 | 実社会で信頼性の高いドメインからリンクされたアカウントが発行したメタデータの付与を受けることで 194 | そのドメイン内での有資格情報の所有を証明することができます。 195 | 196 | #### DID 197 | 198 | 分散型アイデンティティと呼ばれます。 199 | エコシステムは発行者、所有者、検証者に分かれ、例えば大学が発行した卒業証書を学生が所有し、 200 | 企業は学生から提示された証明書を大学が公表している公開鍵をもとに検証します。 201 | このやりとりにプラットフォームに依存する情報はありません。 202 | メタデータを活用することで、大学は学生の所有するアカウントにメタデータを発行することができ、 203 | 企業は大学の公開鍵と学生のモザイク(アカウント)所有証明でメタデータに記載された卒業証明を検証することができます。 204 | 205 | 206 | -------------------------------------------------------------------------------- /03_account.md: -------------------------------------------------------------------------------- 1 | # 3.アカウント 2 | 3 | アカウントは秘密鍵に紐づく情報が記録されたデータ構造体です。アカウントと関連づいた秘密鍵を使って署名することでのみブロックチェーンのデータを更新することができます。 4 | 5 | ## 3.1 アカウント生成 6 | 7 | アカウントには秘密鍵と公開鍵をセットにしたキーペア、アドレスなどの情報が含まれています。まずはランダムにアカウントを作成して、それらの情報を確認してみましょう。 8 | 9 | ### 新規生成 10 | ```js 11 | alice = sym.Account.generateNewAccount(networkType); 12 | console.log(alice); 13 | ``` 14 | ###### 出力例 15 | ```js 16 | > Account 17 | address: Address {address: 'TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ', networkType: 152} 18 | keyPair: {privateKey: Uint8Array(32), publicKey: Uint8Array(32)} 19 | ``` 20 | 21 | networkTypeは以下の通りです。 22 | ```js 23 | {104: 'MAIN_NET', 152: 'TEST_NET'} 24 | ``` 25 | 26 | ### 秘密鍵と公開鍵の導出 27 | ```js 28 | console.log(alice.privateKey); 29 | console.log(alice.publicKey); 30 | ``` 31 | ``` 32 | > 1E9139CC1580B4AED6A1FE110085281D4982ED0D89CE07F3380EB83069B1**** 33 | > D4933FC1E4C56F9DF9314E9E0533173E1AB727BDB2A04B59F048124E93BEFBD2 34 | ``` 35 | 36 | #### 注意事項 37 | 秘密鍵を紛失するとそのアカウントに紐づけられたデータを操作することが出来なくなります。また、他人は知らないという秘密鍵の性質を利用してデータ操作の署名を行うので、秘密鍵を他人に教えてはいけません。組織のなかで秘密鍵を譲り受けて運用を続けるといった行為も控えましょう。 38 | 一般的なWebサービスでは「アカウントID」に対してパスワードが割り振られるため、パスワードの変更が可能ですが、ブロックチェーンではパスワードにあたる秘密鍵に対して一意に決まるID(アドレス)が割り振られるため、アカウントに紐づく秘密鍵を変更するということはできません。 39 | 40 | 41 | ### アドレスの導出 42 | ```js 43 | aliceRawAddress = alice.address.plain(); 44 | console.log(aliceRawAddress); 45 | ``` 46 | ```js 47 | > TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ 48 | ``` 49 | 50 | これらがブロックチェーンを操作するための最も基本的な情報となります。また、秘密鍵からアカウントを生成したり、公開鍵やアドレスのみを扱うクラスの生成方法も確認しておきましょう。 51 | 52 | ### 秘密鍵からアカウント生成 53 | ```js 54 | alice = sym.Account.createFromPrivateKey( 55 | "1E9139CC1580B4AED6A1FE110085281D4982ED0D89CE07F3380EB83069B1****", 56 | networkType 57 | ); 58 | ``` 59 | 60 | ### 公開鍵クラスの生成 61 | ```js 62 | alicePublicAccount = sym.PublicAccount.createFromPublicKey( 63 | "D4933FC1E4C56F9DF9314E9E0533173E1AB727BDB2A04B59F048124E93BEFBD2", 64 | networkType 65 | ); 66 | console.log(alicePublicAccount); 67 | ``` 68 | ###### 出力例 69 | ```js 70 | > PublicAccount 71 | address: Address {address: 'TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ', networkType: 152} 72 | publicKey: "D4933FC1E4C56F9DF9314E9E0533173E1AB727BDB2A04B59F048124E93BEFBD2" 73 | 74 | ``` 75 | 76 | ### アドレスクラスの生成 77 | ```js 78 | aliceAddress = sym.Address.createFromRawAddress( 79 | "TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ" 80 | ); 81 | console.log(aliceAddress); 82 | ``` 83 | ###### 出力例 84 | ```js 85 | > Address 86 | address: "TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ" 87 | networkType: 152 88 | ``` 89 | 90 | ## 3.2 アカウントへの送信 91 | 92 | アカウントを作成しただけでは、ブロックチェーンにデータを送信することはできません。 93 | パブリックブロックチェーンはリソースを有効活用するためにデータ送信時に手数料を要求します。 94 | Symbolブロックチェーンでは、この手数料をXYMという共通トークンで支払うことになります。 95 | アカウントを生成したら、この後の章から説明するトランザクションを実行するために必要な手数料を送信しておきます。 96 | 97 | ### フォーセットから送信 98 | 99 | テストネットではフォーセット(蛇口)サービスから検証用のXYMを入手することができます。 100 | メインネットの場合は取引所などでXYMを購入するか、投げ銭サービス(NEMLOG,QUEST)などを利用して寄付を募りましょう。 101 | 102 | テストネット 103 | - FAUCET(蛇口) 104 | - https://testnet.symbol.tools/ 105 | 106 | メインネット 107 | - NEMLOG 108 | - https://nemlog.nem.social/ 109 | - QUEST 110 | - https://quest-bc.com/ 111 | 112 | 113 | 114 | ### エクスプローラーで確認 115 | 116 | フォーセットから作成したアカウントへ送信が成功したらエクスプローラーで確認してみましょう。 117 | 118 | - テストネット 119 | - https://testnet.symbol.fyi/ 120 | - メインネット 121 | - https://symbol.fyi/ 122 | 123 | ## 3.3 アカウント情報の確認 124 | 125 | ノードに保存されているアカウント情報を取得します。 126 | 127 | ### 所有モザイク一覧の取得 128 | 129 | ```js 130 | accountRepo = repo.createAccountRepository(); 131 | accountInfo = await accountRepo.getAccountInfo(aliceAddress).toPromise(); 132 | console.log(accountInfo); 133 | ``` 134 | ###### 出力例 135 | ```js 136 | > AccountInfo 137 | address: Address {address: 'TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ', networkType: 152} 138 | publicKey: "0000000000000000000000000000000000000000000000000000000000000000" 139 | > mosaics: Array(1) 140 | 0: Mosaic 141 | amount: UInt64 {lower: 10000000, higher: 0} 142 | id: MosaicId 143 | id: Id {lower: 760461000, higher: 981735131} 144 | ``` 145 | 146 | #### publicKey 147 | クライアント側で作成しただけで、ブロックチェーンでまだ利用されていないアカウント情報は記録されていません。宛先として指定されて受信することで初めてアカウント情報が記録され、署名したトランザクションを送信することで公開鍵の情報が記録されます。そのため、publicKeyは現在`00000...`表記となっています。 148 | 149 | #### UInt64 150 | JavaScriptでは大きすぎる数値はあふれてしまうため、idやamountはUInt64というsdkの独自フォーマットで管理されています。文字列に変換する場合は toString()、数値に変換する場合は compact()、16進数にする場合は toHex() で変換してください。 151 | 152 | ```js 153 | console.log("addressHeight:"); //アドレスが記録されたブロック高 154 | console.log(accountInfo.addressHeight.compact()); //数値 155 | accountInfo.mosaics.forEach(mosaic => { 156 | console.log("id:" + mosaic.id.toHex()); //16進数 157 | console.log("amount:" + mosaic.amount.toString()); //文字列 158 | }); 159 | ``` 160 | 161 | 大きすぎるid値をcompactで数値変換するとエラーが発生することがあります。 162 | `Compacted value is greater than Number.Max_Value.` 163 | 164 | 165 | #### 表示桁数の調整 166 | 167 | 所有するトークンの量は誤差の発生を防ぐため、整数値で扱います。トークンの定義から可分性を取得することができるので、その値を使って正確な所有量を表示してみます。 168 | 169 | ```js 170 | mosaicRepo = repo.createMosaicRepository(); 171 | mosaicAmount = accountInfo.mosaics[0].amount.toString(); 172 | mosaicInfo = await mosaicRepo.getMosaic(accountInfo.mosaics[0].id).toPromise(); 173 | divisibility = mosaicInfo.divisibility; //可分性 174 | if(divisibility > 0){ 175 | displayAmount = mosaicAmount.slice(0,mosaicAmount.length-divisibility) 176 | + "." + mosaicAmount.slice(-divisibility); 177 | }else{ 178 | displayAmount = mosaicAmount; 179 | } 180 | console.log(displayAmount); 181 | ``` 182 | 183 | ## 3.4 現場で使えるヒント 184 | ### 暗号化と署名 185 | 186 | アカウントとして生成した秘密鍵や公開鍵は、そのまま従来の暗号化や電子署名として活用することができます。信頼性に問題点があるアプリケーションを使用する必要がある場合も、個人間(エンドツーエンド)でデータの秘匿性・正当性を検証することができます。 187 | 188 | #### 事前準備:対話のためのBobアカウントを生成 189 | ```js 190 | bob = sym.Account.generateNewAccount(networkType); 191 | bobPublicAccount = bob.publicAccount; 192 | ``` 193 | 194 | #### 暗号化 195 | 196 | Aliceの秘密鍵・Bobの公開鍵で暗号化し、Aliceの公開鍵・Bobの秘密鍵で復号します(AES-GCM形式)。 197 | 198 | ```js 199 | message = 'Hello Symol!'; 200 | encryptedMessage = alice.encryptMessage(message ,bob.publicAccount); 201 | console.log(encryptedMessage); 202 | ``` 203 | ```js 204 | > 294C8979156C0D941270BAC191F7C689E93371EDBC36ADD8B920CF494012A97BA2D1A3759F9A6D55D5957E9D 205 | ``` 206 | 207 | #### 復号化 208 | ```js 209 | decryptMessage = bob.decryptMessage( 210 | new sym.EncryptedMessage( 211 | "294C8979156C0D941270BAC191F7C689E93371EDBC36ADD8B920CF494012A97BA2D1A3759F9A6D55D5957E9D" 212 | ), 213 | alice.publicAccount 214 | ).payload 215 | console.log(decryptMessage); 216 | ``` 217 | ```js 218 | > "Hello Symol!" 219 | ``` 220 | 221 | #### 署名 222 | 223 | Aliceの秘密鍵でメッセージを署名し、Aliceの公開鍵と署名でメッセージを検証します。 224 | 225 | ```js 226 | Buffer = require("/node_modules/buffer").Buffer; 227 | payload = Buffer.from("Hello Symol!", 'utf-8'); 228 | signature = Buffer.from(sym.KeyPair.sign(alice.keyPair, payload)).toString("hex").toUpperCase(); 229 | console.log(signature); 230 | ``` 231 | ``` 232 | > B8A9BCDE9246BB5780A8DED0F4D5DFC80020BBB7360B863EC1F9C62CAFA8686049F39A9F403CB4E66104754A6AEDEF8F6B4AC79E9416DEEDC176FDD24AFEC60E 233 | ``` 234 | 235 | #### 検証 236 | ```js 237 | isVerified = sym.KeyPair.verify( 238 | alice.keyPair.publicKey, 239 | Buffer.from("Hello Symol!", 'utf-8'), 240 | Buffer.from(signature, 'hex') 241 | ) 242 | console.log(isVerified); 243 | ``` 244 | ```js 245 | > true 246 | ``` 247 | 248 | ブロックチェーンを使用しない署名は何度も再利用される可能性があることにご注意ください。 249 | 250 | ### アカウントの保管 251 | 252 | アカウントの管理方法について説明しておきます。 253 | 秘密鍵はそのままで保存しないようにしてください。symbol-qr-libraryを利用して秘密鍵をパスフレーズで暗号化して保存する方法を紹介します。 254 | 255 | #### 秘密鍵の暗号化 256 | 257 | ```js 258 | qr = require("/node_modules/symbol-qr-library"); 259 | 260 | //パスフレーズでロックされたアカウント生成 261 | signerQR = qr.QRCodeGenerator.createExportAccount( 262 | alice.privateKey, networkType, generationHash, "パスフレーズ" 263 | ); 264 | 265 | //QRコード表示 266 | signerQR.toBase64().subscribe(x =>{ 267 | 268 | //HTML body上にQRコードを表示する例 269 | (tag= document.createElement('img')).src = x; 270 | document.getElementsByTagName('body')[0].appendChild(tag); 271 | }); 272 | 273 | //アカウントを暗号化したJSONデータとして表示 274 | jsonSignerQR = signerQR.toJSON(); 275 | console.log(jsonSignerQR); 276 | ``` 277 | ###### 出力例 278 | ```js 279 | > {"v":3,"type":2,"network_id":152,"chain_id":"7FCCD304802016BEBBCD342A332F91FF1F3BB5E902988B352697BE245F48E836","data":{"ciphertext":"e9e2f76cb482fd054bc13b7ca7c9d086E7VxeGS/N8n1WGTc5MwshNMxUiOpSV2CNagtc6dDZ7rVZcnHXrrESS06CtDTLdD7qrNZEZAi166ucDUgk4Yst0P/XJfesCpXRxlzzNgcK8Q=","salt":"54de9318a44cc8990e01baba1bcb92fa111d5bcc0b02ffc6544d2816989dc0e9"}} 280 | ``` 281 | このjsonSignerQRで出力されるQRコード、あるいはテキストを保存しておけばいつでも秘密鍵を復元することができます。 282 | 283 | #### 暗号化された秘密鍵の復号 284 | 285 | ```js 286 | //保存しておいたテキスト、あるいはQRコードスキャンで得られたテキストをjsonSignerQRに代入 287 | jsonSignerQR = '{"v":3,"type":2,"network_id":152,"chain_id":"7FCCD304802016BEBBCD342A332F91FF1F3BB5E902988B352697BE245F48E836","data":{"ciphertext":"e9e2f76cb482fd054bc13b7ca7c9d086E7VxeGS/N8n1WGTc5MwshNMxUiOpSV2CNagtc6dDZ7rVZcnHXrrESS06CtDTLdD7qrNZEZAi166ucDUgk4Yst0P/XJfesCpXRxlzzNgcK8Q=","salt":"54de9318a44cc8990e01baba1bcb92fa111d5bcc0b02ffc6544d2816989dc0e9"}}'; 288 | 289 | qr = require("/node_modules/symbol-qr-library"); 290 | signerQR = qr.AccountQR.fromJSON(jsonSignerQR,"パスフレーズ"); 291 | console.log(signerQR.accountPrivateKey); 292 | ``` 293 | ###### 出力例 294 | ```js 295 | > 1E9139CC1580B4AED6A1FE110085281D4982ED0D89CE07F3380EB83069B1**** 296 | ``` 297 | 298 | -------------------------------------------------------------------------------- /05_mosaic.md: -------------------------------------------------------------------------------- 1 | # 5.モザイク 2 | 3 | 本章ではモザイクの設定とその生成方法について解説します。 4 | Symbolではトークンのことをモザイクと表現します。 5 | 6 | > Wikipediaによると、トークンとは「紀元前8000年頃から紀元前3000年までのメソポタミアの地層から出土する直径が1cm前後の粘土で作られたさまざまな形状の物体」のことを指します。一方でモザイクとは「小片を寄せあわせ埋め込んで、絵(図像)や模様を表す装飾美術の技法。石、陶磁器(モザイクタイル)、有色無色のガラス、貝殻、木などが使用され、建築物の床や壁面、あるいは工芸品の装飾のために施される。」とあります。SymbolにおいてモザイクとはSymbolが作りなすエコシステムの様相を表すさまざまな構成要素、と考えることができます。 7 | 8 | ## 5.1 モザイク生成 9 | 10 | モザイク生成には 11 | 作成するモザイクを定義します。 12 | ```js 13 | supplyMutable = true; //供給量変更の可否 14 | transferable = false; //第三者への譲渡可否 15 | restrictable = true; //制限設定の可否 16 | revokable = true; //発行者からの還収可否 17 | 18 | //モザイク定義 19 | nonce = sym.MosaicNonce.createRandom(); 20 | mosaicDefTx = sym.MosaicDefinitionTransaction.create( 21 | undefined, 22 | nonce, 23 | sym.MosaicId.createFromNonce(nonce, alice.address), //モザイクID 24 | sym.MosaicFlags.create(supplyMutable, transferable, restrictable, revokable), 25 | 2,//divisibility:可分性 26 | sym.UInt64.fromUint(0), //duration:有効期限 27 | networkType 28 | ); 29 | ``` 30 | 31 | MosaicFlagsは以下の通りです。 32 | 33 | ```js 34 | MosaicFlags { 35 | supplyMutable: false, transferable: false, restrictable: false, revokable: false 36 | } 37 | ``` 38 | 数量変更、第三者への譲渡、モザイクグローバル制限の適用、発行者からの還収の可否について指定します。 39 | この項目は後で変更することはできません。 40 | 41 | #### divisibility:可分性 42 | 43 | 可分性は小数点第何位まで数量の単位とするかを決めます。データは整数値として保持されます。 44 | 45 | divisibility:0 = 1 46 | divisibility:1 = 1.0 47 | divisibility:2 = 1.00 48 | 49 | #### duration:有効期限 50 | 51 | 0を指定した場合、無期限に使用することができます。 52 | モザイク有効期限を設定した場合、期限が切れた後も消滅することはなくデータとしては残ります。 53 | アカウント1つにつき1000までしか所有することはできませんのでご注意ください。 54 | 55 | 56 | 次に数量を変更します 57 | ```js 58 | //モザイク変更 59 | mosaicChangeTx = sym.MosaicSupplyChangeTransaction.create( 60 | undefined, 61 | mosaicDefTx.mosaicId, 62 | sym.MosaicSupplyChangeAction.Increase, 63 | sym.UInt64.fromUint(1000000), //数量 64 | networkType 65 | ); 66 | ``` 67 | supplyMutable:falseの場合、全モザイクが発行者にある場合だけ数量の変更が可能です。 68 | divisibility > 0 の場合は、最小単位を1として整数値で定義してください。 69 | (divisibility:2 で 1.00 作成したい場合は100と指定) 70 | 71 | MosaicSupplyChangeActionは以下の通りです。 72 | ```js 73 | {0: 'Decrease', 1: 'Increase'} 74 | ``` 75 | 増やしたい場合はIncreaseを指定します。 76 | 上記2つのトランザクションをまとめてアグリゲートトランザクションを作成します。 77 | 78 | ```js 79 | aggregateTx = sym.AggregateTransaction.createComplete( 80 | sym.Deadline.create(epochAdjustment), 81 | [ 82 | mosaicDefTx.toAggregate(alice.publicAccount), 83 | mosaicChangeTx.toAggregate(alice.publicAccount), 84 | ], 85 | networkType,[], 86 | ).setMaxFeeForAggregate(100, 0); 87 | 88 | signedTx = alice.sign(aggregateTx,generationHash); 89 | await txRepo.announce(signedTx).toPromise(); 90 | ``` 91 | 92 | アグリゲートトランザクションの特徴として、 93 | まだ存在していないモザイクの数量を変更しようとしている点に注目してください。 94 | 配列化した時に、矛盾点がなければ1つのブロック内で問題なく処理することができます。 95 | 96 | 97 | ### 確認 98 | モザイク作成したアカウントが持つモザイク情報を確認します。 99 | 100 | ```js 101 | mosaicRepo = repo.createMosaicRepository(); 102 | accountInfo.mosaics.forEach(async mosaic => { 103 | mosaicInfo = await mosaicRepo.getMosaic(mosaic.id).toPromise(); 104 | console.log(mosaicInfo); 105 | }); 106 | ``` 107 | ###### 出力例 108 | ```js 109 | > MosaicInfo {version: 1, recordId: '622988B12A6128903FC10496', id: MosaicId, supply: UInt64, startHeight: UInt64, …} 110 | > MosaicInfo 111 | divisibility: 2 //可分性 112 | duration: UInt64 {lower: 0, higher: 0} //有効期限 113 | > flags: MosaicFlags 114 | restrictable: true //制限設定の可否 115 | revokable: true //発行者からの還収可否 116 | supplyMutable: true //供給量変更の可否 117 | transferable: false //第三者への譲渡可否 118 | > id: MosaicId 119 | id: Id {lower: 207493124, higher: 890137608} //モザイクID 120 | ownerAddress: Address {address: 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ', networkType: 152} //作成者アドレス 121 | recordId: "62626E3C741381859AFAD4D5" 122 | supply: UInt64 {lower: 1000000, higher: 0} //供給量 123 | ``` 124 | 125 | ## 5.2 モザイク送信 126 | 127 | 作成したモザイクを送信します。 128 | よく、ブロックチェーンに初めて触れる方は、 129 | モザイク送信について「クライアント端末に保存されたモザイクを別のクライアント端末へ送信」することとイメージされている人がいますが、 130 | モザイク情報はすべてのノードで常に共有・同期化されており、送信先に未知のモザイク情報を届けることではありません。 131 | 正確にはブロックチェーンへ「トランザクションを送信」することにより、アカウント間でのトークン残量を組み替える操作のことを言います。 132 | 133 | ```js 134 | //受信アカウント作成 135 | bob = sym.Account.generateNewAccount(networkType); 136 | 137 | tx = sym.TransferTransaction.create( 138 | sym.Deadline.create(epochAdjustment), 139 | bob.address, //送信先アドレス 140 | // 送信モザイクリスト 141 | [ 142 | new sym.Mosaic( 143 | new sym.MosaicId("72C0212E67A08BCE"), //テストネットXYM 144 | sym.UInt64.fromUint(1000000) //1XYM(divisibility:6) 145 | ), 146 | new sym.Mosaic( 147 | mosaicDefTx.mosaicId, // 5.1 で作成したモザイク 148 | sym.UInt64.fromUint(1) // 数量:0.01(divisibility:2 の場合) 149 | ) 150 | ], 151 | sym.EmptyMessage, 152 | networkType 153 | ).setMaxFee(100); 154 | signedTx = alice.sign(tx,generationHash); 155 | await txRepo.announce(signedTx).toPromise(); 156 | 157 | ``` 158 | 159 | 160 | 161 | ##### 送信モザイクリスト 162 | 163 | 複数のモザイクを一度に送信できます。 164 | XYMを送信するには以下のモザイクIDを指定します。 165 | - メインネット:6BED913FA20223F8 166 | - テストネット:72C0212E67A08BCE 167 | 168 | #### 送信量 169 | 小数点もすべて整数にして指定します。 170 | XYMは可分性6なので、1XYM=1000000で指定します。 171 | 172 | ### 送信確認 173 | 174 | ```js 175 | txInfo = await txRepo.getTransaction(signedTx.hash,sym.TransactionGroup.Confirmed).toPromise(); 176 | console.log(txInfo); 177 | ``` 178 | ###### 出力例 179 | ```js 180 | > TransferTransaction 181 | deadline: Deadline {adjustedValue: 12776690385} 182 | maxFee: UInt64 {lower: 19200, higher: 0} 183 | message: RawMessage {type: -1, payload: ''} 184 | > mosaics: Array(2) 185 | > 0: Mosaic 186 | amount: UInt64 {lower: 1, higher: 0} 187 | > id: MosaicId 188 | id: Id {lower: 207493124, higher: 890137608} 189 | > 1: Mosaic 190 | amount: UInt64 {lower: 1000000, higher: 0} 191 | > id: MosaicId 192 | id: Id {lower: 760461000, higher: 981735131} 193 | networkType: 152 194 | payloadSize: 192 195 | recipientAddress: Address {address: 'TAR6ERCSTDJJ7KCN4BJNJTK7LBBL5JPPVSHUNGY', networkType: 152} 196 | signature: "7C4E9E80D250C6D09352FB8EC80175719D59787DE67446896A73AABCFE6C420AF7DD707E6D4D2B2987B8BAD775F2989DCB6F738D39C48C1239FC8CC900A6740D" 197 | signer: PublicAccount {publicKey: '0E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26', address: Address} 198 | > transactionInfo: TransactionInfo 199 | hash: "DE479C001E9736976BDA55E560AB1A5DE526236D9E1BCE24941CF8ED8884289E" 200 | height: UInt64 {lower: 326922, higher: 0} 201 | id: "626270069F1D5202A10AE93E" 202 | index: 0 203 | merkleComponentHash: "DE479C001E9736976BDA55E560AB1A5DE526236D9E1BCE24941CF8ED8884289E" 204 | type: 16724 205 | version: 1 206 | ``` 207 | TransferTransactionのmosaicsに2種類のモザイクが送信されていることが確認できます。また、TransactionInfoに承認されたブロックの情報が記載されています。 208 | 209 | ## 5.3 現場で使えるヒント 210 | 211 | ### 所有証明 212 | 213 | 前章でトランザクションによる存在証明について説明しました。 214 | アカウントの作成した送信指示が消せない形で残せるので、絶対につじつまの合う台帳を作ることができます。 215 | すべてのアカウントの「絶対に消せない送信指示」の蓄積結果として、各アカウントは自分のモザイク所有を証明することができます。 216 | (本ドキュメントでは所有を「自分の意思で手放すことができる状態」とします。少し話題がそれますが、法律的にはデジタルデータに所有権が認められていないのも、一度知ってしまったデータは自分の意志では忘れたことを他人に証明することができない点に注目すると「手放すことができる状態」の意味に納得がいくかもしれません。ブロックチェーンによりそのデータの放棄を明確に示すことができるのですが、詳しくは法律の専門の方にお任せします。) 217 | 218 | #### NFT(non fungible token) 219 | 220 | 発行枚数を1に限定し、supplyMutableをfalseに設定することで、1つだけしか存在しないトークンを発行できます。 221 | モザイクは作成したアカウントアドレスを改ざんできない情報として保有しているので、 222 | そのアカウントの送信トランザクションをメタ情報として利用できます。 223 | 7章で説明するメタデータをモザイクに登録する方法もありますが、その方法は登録アカウントとモザイク作成者の連署によって更新可能なことにご注意ください。 224 | 225 | NFTの実現方法はいろいろありますが、その一例の処理概要を以下に例示します(実行するためにはnonceやフラグ情報を適切に設定してください)。 226 | ```js 227 | supplyMutable = false; //供給量変更の可否 228 | 229 | //モザイク定義 230 | mosaicDefTx = sym.MosaicDefinitionTransaction.create( 231 | undefined, nonce,mosaicId, 232 | sym.MosaicFlags.create(supplyMutable, transferable, restrictable, revokable), 233 | 0,//divisibility:可分性 234 | sym.UInt64.fromUint(0), //duration:無期限 235 | networkType 236 | ); 237 | 238 | //モザイク数量固定 239 | mosaicChangeTx = sym.MosaicSupplyChangeTransaction.create( 240 | undefined,mosaicId, 241 | sym.MosaicSupplyChangeAction.Increase, //増やす 242 | sym.UInt64.fromUint(1), //数量1 243 | networkType 244 | ); 245 | 246 | //NFTデータ 247 | nftTx = sym.TransferTransaction.create( 248 | undefined, //Deadline:有効期限 249 | alice.address, 250 | [], 251 | sym.PlainMessage.create("Hello Symbol!"), //NFTデータ実体 252 | networkType 253 | ) 254 | 255 | //モザイクの生成とNFTデータをアグリゲートしてブロックに登録 256 | aggregateTx = sym.AggregateTransaction.createComplete( 257 | sym.Deadline.create(epochAdjustment), 258 | [ 259 | mosaicDefTx.toAggregate(alice.publicAccount), 260 | mosaicChangeTx.toAggregate(alice.publicAccount), 261 | nftTx.toAggregate(alice.publicAccount) 262 | ], 263 | networkType,[], 264 | ).setMaxFeeForAggregate(100, 0); 265 | ``` 266 | 267 | モザイク生成時のブロック高と作成アカウントがモザイク情報に含まれているので同ブロック内のトランザクションを検索することにより、 268 | 紐づけられたNFTデータを取得することができます。 269 | 270 | ##### 注意事項 271 | モザイクの作成者が全数量を所有している場合、供給量を変更することが可能です。 272 | またトランザクションに分割してデータを記録した場合、改ざんできませんがデータの追記は可能です。 273 | NFTを運用する場合はモザイク作成者の秘密鍵を厳重に管理・あるいは破棄するなど、適切な運用にご注意ください。 274 | 275 | 276 | #### 回収可能なポイント運用 277 | 278 | transferableをfalseに設定することで転売が制限されるため、資金決済法の影響を受けにくいポイントを定義することができます。 279 | またrevokableをtrueに設定することで、ユーザ側が秘密鍵を管理しなくても使用分を回収できるような中央管理型のポイント運用を行うことができます。 280 | 281 | ```js 282 | transferable = false; //第三者への譲渡可否 283 | revokable = true; //発行者からの還収可否 284 | ``` 285 | 286 | トランザクションは以下のように記述します。 287 | 288 | ```js 289 | revocationTx = sym.MosaicSupplyRevocationTransaction.create( 290 | sym.Deadline.create(epochAdjustment), 291 | bob.address, //回収先アドレス 292 | new sym.Mosaic(mosaicId, sym.UInt64.fromUint(3)), //回収モザイクIDと数量 293 | networkType 294 | ).setMaxFee(100); 295 | ``` 296 | 297 | 298 | 299 | 300 | -------------------------------------------------------------------------------- /06_namespace.md: -------------------------------------------------------------------------------- 1 | # 6.ネームスペース 2 | 3 | Symbolブロックチェーンではネームスペースをレンタルしてアドレスやモザイクに視認性の高い単語をリンクさせることができます。 4 | ネームスペースは最大64文字、利用可能な文字は a, b, c, …, z, 0, 1, 2, …, 9, _ , - です。 5 | 6 | ## 6.1 手数料の計算 7 | 8 | ネームスペースのレンタルにはネットワーク手数料とは別にレンタル手数料が発生します。 9 | ネットワークの活性度に比例して価格が変動しますので、取得前に確認するようにしてください。 10 | 11 | ルートネームスペースを365日レンタルする場合の手数料を計算します。 12 | 13 | ```js 14 | nwRepo = repo.createNetworkRepository(); 15 | 16 | rentalFees = await nwRepo.getRentalFees().toPromise(); 17 | rootNsperBlock = rentalFees.effectiveRootNamespaceRentalFeePerBlock.compact(); 18 | rentalDays = 365; 19 | rentalBlock = rentalDays * 24 * 60 * 60 / 30; 20 | rootNsRenatalFeeTotal = rentalBlock * rootNsperBlock; 21 | console.log("rentalBlock:" + rentalBlock); 22 | console.log("rootNsRenatalFeeTotal:" + rootNsRenatalFeeTotal); 23 | ``` 24 | ###### 出力例 25 | ```js 26 | > rentalBlock:1051200 27 | > rootNsRenatalFeeTotal:210240000 //約210XYM 28 | ``` 29 | 30 | 期間はブロック数で指定します。1ブロックを30秒として計算しました。 31 | 最低で30日分はレンタルする必要があります(最大で1825日分)。 32 | 33 | サブネームスペースの取得手数料を計算します。 34 | 35 | ```js 36 | childNamespaceRentalFee = rentalFees.effectiveChildNamespaceRentalFee.compact() 37 | console.log(childNamespaceRentalFee); 38 | ``` 39 | ###### 出力例 40 | ```js 41 | > 10000000 //10XYM 42 | ``` 43 | 44 | サブネームスペースに期間指定はありません。ルートネームスペースをレンタルしている限り使用できます。 45 | 46 | ## 6.2 レンタル 47 | 48 | ルートネームスペースをレンタルします(例:xembook) 49 | ```js 50 | 51 | tx = sym.NamespaceRegistrationTransaction.createRootNamespace( 52 | sym.Deadline.create(epochAdjustment), 53 | "xembook", 54 | sym.UInt64.fromUint(86400), 55 | networkType 56 | ).setMaxFee(100); 57 | signedTx = alice.sign(tx,generationHash); 58 | await txRepo.announce(signedTx).toPromise(); 59 | ``` 60 | 61 | サブネームスペースをレンタルします(例:xembook.tomato) 62 | ```js 63 | subNamespaceTx = sym.NamespaceRegistrationTransaction.createSubNamespace( 64 | sym.Deadline.create(epochAdjustment), 65 | "tomato", //作成するサブネームスペース 66 | "xembook", //紐づけたいルートネームスペース 67 | networkType, 68 | ).setMaxFee(100); 69 | signedTx = alice.sign(subNamespaceTx,generationHash); 70 | await txRepo.announce(signedTx).toPromise(); 71 | ``` 72 | 73 | 2階層目のサブネームスペースを作成したい場合は 74 | 例えば、xembook.tomato.morningを定義したい場合は以下のようにします。 75 | 76 | ```js 77 | subNamespaceTx = sym.NamespaceRegistrationTransaction.createSubNamespace( 78 | , 79 | "morning", //作成するサブネームスペース 80 | "xembook.tomato", //紐づけたいルートネームスペース 81 | , 82 | ) 83 | ``` 84 | 85 | 86 | ### 有効期限の計算 87 | 88 | レンタル済みルートネームスペースの有効期限を計算します。 89 | 90 | ```js 91 | nsRepo = repo.createNamespaceRepository(); 92 | chainRepo = repo.createChainRepository(); 93 | blockRepo = repo.createBlockRepository(); 94 | 95 | namespaceId = new sym.NamespaceId("xembook"); 96 | nsInfo = await nsRepo.getNamespace(namespaceId).toPromise(); 97 | lastHeight = (await chainRepo.getChainInfo().toPromise()).height; 98 | lastBlock = await blockRepo.getBlockByHeight(lastHeight).toPromise(); 99 | remainHeight = nsInfo.endHeight.compact() - lastHeight.compact(); 100 | 101 | endDate = new Date(lastBlock.timestamp.compact() + remainHeight * 30000 + epochAdjustment * 1000) 102 | console.log(endDate); 103 | ``` 104 | 105 | ネームスペース情報の終了ブロックを取得し、現在のブロック高から差し引いた残ブロック数に30秒(平均ブロック生成間隔)を掛け合わせた日時を出力します。 106 | テストネットでは設定した有効期限よりも1日程度更新期限が猶予されます。メインネットはこの値が30日となっていますのでご留意ください 107 | 108 | ###### 出力例 109 | ```js 110 | > Tue Mar 29 2022 18:17:06 GMT+0900 (日本標準時) 111 | ``` 112 | ## 6.3 リンク 113 | 114 | ### アカウントへのリンク 115 | ```js 116 | namespaceId = new sym.NamespaceId("xembook"); 117 | address = sym.Address.createFromRawAddress("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ"); 118 | tx = sym.AliasTransaction.createForAddress( 119 | sym.Deadline.create(epochAdjustment), 120 | sym.AliasAction.Link, 121 | namespaceId, 122 | address, 123 | networkType 124 | ).setMaxFee(100); 125 | signedTx = alice.sign(tx,generationHash); 126 | await txRepo.announce(signedTx).toPromise(); 127 | ``` 128 | リンク先のアドレスは自分が所有していなくても問題ありません。 129 | 130 | ### モザイクへリンク 131 | ```js 132 | namespaceId = new sym.NamespaceId("xembook.tomato"); 133 | mosaicId = new sym.MosaicId("3A8416DB2D53xxxx"); 134 | tx = sym.AliasTransaction.createForMosaic( 135 | sym.Deadline.create(epochAdjustment), 136 | sym.AliasAction.Link, 137 | namespaceId, 138 | mosaicId, 139 | networkType 140 | ).setMaxFee(100); 141 | signedTx = alice.sign(tx,generationHash); 142 | await txRepo.announce(signedTx).toPromise(); 143 | ``` 144 | 145 | モザイクを作成したアドレスと同一の場合のみリンクできるようです。 146 | 147 | 148 | ## 6.4 未解決で使用 149 | 150 | 送信先にUnresolvedAccountとして指定して、アドレスを特定しないままトランザクションを署名・アナウンスします。 151 | チェーン側で解決されたアカウントに対しての送信が実施されます。 152 | ```js 153 | namespaceId = new sym.NamespaceId("xembook"); 154 | tx = sym.TransferTransaction.create( 155 | sym.Deadline.create(epochAdjustment), 156 | namespaceId, //UnresolvedAccount:未解決アカウントアドレス 157 | [], 158 | sym.EmptyMessage, 159 | networkType 160 | ).setMaxFee(100); 161 | signedTx = alice.sign(tx,generationHash); 162 | await txRepo.announce(signedTx).toPromise(); 163 | ``` 164 | 送信モザイクにUnresolvedMosaicとして指定して、モザイクIDを特定しないままトランザクションを署名・アナウンスします。 165 | 166 | ```js 167 | namespaceId = new sym.NamespaceId("xembook.tomato"); 168 | tx = sym.TransferTransaction.create( 169 | sym.Deadline.create(epochAdjustment), 170 | address, 171 | [ 172 | new sym.Mosaic( 173 | namespaceId,//UnresolvedMosaic:未解決モザイク 174 | sym.UInt64.fromUint(1) //送信量 175 | ) 176 | ], 177 | sym.EmptyMessage, 178 | networkType 179 | ).setMaxFee(100); 180 | signedTx = alice.sign(tx,generationHash); 181 | await txRepo.announce(signedTx).toPromise(); 182 | ``` 183 | 184 | XYMをネームスペースで使用する場合は以下のように指定します。 185 | 186 | ```js 187 | namespaceId = new sym.NamespaceId("symbol.xym"); 188 | ``` 189 | ```js 190 | > NamespaceId {fullName: 'symbol.xym', id: Id} 191 | fullName: "symbol.xym" 192 | id: Id {lower: 1106554862, higher: 3880491450} 193 | ``` 194 | 195 | Idは内部ではUint64と呼ばれる数値 `{lower: 1106554862, higher: 3880491450}` で保持されています。 196 | 197 | ## 6.5 参照 198 | 199 | アドレスへリンクしたネームスペースの参照します 200 | ```js 201 | nsRepo = repo.createNamespaceRepository(); 202 | 203 | namespaceInfo = await nsRepo.getNamespace(new sym.NamespaceId("xembook")).toPromise(); 204 | console.log(namespaceInfo); 205 | ``` 206 | ###### 出力例 207 | ```js 208 | NamespaceInfo 209 | active: true 210 | > alias: AddressAlias 211 | address: Address {address: 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ', networkType: 152} 212 | mosaicId: undefined 213 | type: 2 //AliasType 214 | depth: 1 215 | endHeight: UInt64 {lower: 500545, higher: 0} 216 | index: 1 217 | levels: [NamespaceId] 218 | ownerAddress: Address {address: 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ', networkType: 152} 219 | parentId: NamespaceId {id: Id} 220 | registrationType: 0 //NamespaceRegistrationType 221 | startHeight: UInt64 {lower: 324865, higher: 0} 222 | ``` 223 | 224 | AliasTypeは以下の通りです。 225 | ```js 226 | {0: 'None', 1: 'Mosaic', 2: 'Address'} 227 | ``` 228 | 229 | NamespaceRegistrationTypeは以下の通りです。 230 | ```js 231 | {0: 'RootNamespace', 1: 'SubNamespace'} 232 | ``` 233 | 234 | モザイクへリンクしたネームスペースを参照します。 235 | ```js 236 | nsRepo = repo.createNamespaceRepository(); 237 | 238 | namespaceInfo = await nsRepo.getNamespace(new sym.NamespaceId("xembook.tomato")).toPromise(); 239 | console.log(namespaceInfo); 240 | ``` 241 | ###### 出力例 242 | ```js 243 | NamespaceInfo 244 | > active: true 245 | alias: MosaicAlias 246 | address: undefined 247 | mosaicId: MosaicId 248 | id: Id {lower: 1360892257, higher: 309702839} 249 | type: 1 //AliasType 250 | depth: 2 251 | endHeight: UInt64 {lower: 500545, higher: 0} 252 | index: 1 253 | levels: (2) [NamespaceId, NamespaceId] 254 | ownerAddress: Address {address: 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ', networkType: 152} 255 | parentId: NamespaceId {id: Id} 256 | registrationType: 1 //NamespaceRegistrationType 257 | startHeight: UInt64 {lower: 324865, higher: 0} 258 | ``` 259 | 260 | ### 逆引き 261 | 262 | アドレスに紐づけられたネームスペースを全て調べます。 263 | ```js 264 | nsRepo = repo.createNamespaceRepository(); 265 | 266 | accountNames = await nsRepo.getAccountsNames( 267 | [sym.Address.createFromRawAddress("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ")] 268 | ).toPromise(); 269 | 270 | namespaceIds = accountNames[0].names.map(name=>{ 271 | return name.namespaceId; 272 | }); 273 | console.log(namespaceIds); 274 | ``` 275 | 276 | モザイクに紐づけられたネームスペースを全て調べます。 277 | ```js 278 | nsRepo = repo.createNamespaceRepository(); 279 | 280 | mosaicNames = await nsRepo.getMosaicsNames( 281 | [new sym.MosaicId("72C0212E67A08BCE")] 282 | ).toPromise(); 283 | 284 | namespaceIds = mosaicNames[0].names.map(name=>{ 285 | return name.namespaceId; 286 | }); 287 | console.log(namespaceIds); 288 | ``` 289 | 290 | 291 | ### レシートの参照 292 | 293 | トランザクションに使用されたネームスペースをブロックチェーン側がどう解決したかを確認します。 294 | 295 | ```js 296 | receiptRepo = repo.createReceiptRepository(); 297 | state = await receiptRepo.searchAddressResolutionStatements({height:179401}).toPromise(); 298 | ``` 299 | ###### 出力例 300 | ```js 301 | data: Array(1) 302 | 0: ResolutionStatement 303 | height: UInt64 {lower: 179401, higher: 0} 304 | resolutionEntries: Array(1) 305 | 0: ResolutionEntry 306 | resolved: Address {address: 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ', networkType: 152} 307 | source: ReceiptSource {primaryId: 1, secondaryId: 0} 308 | resolutionType: 0 //ResolutionType 309 | unresolved: NamespaceId 310 | id: Id {lower: 646738821, higher: 2754876907} 311 | ``` 312 | 313 | ResolutionTypeは以下の通りです。 314 | ```js 315 | {0: 'Address', 1: 'Mosaic'} 316 | ``` 317 | 318 | #### 注意事項 319 | ネームスペースはレンタル制のため、過去のトランザクションで使用したネームスペースのリンク先と 320 | 現在のネームスペースのリンク先が異なる可能性があります。 321 | 過去のデータを参照する際などに、その時どのアカウントにリンクしていたかなどを知りたい場合は 322 | 必ずレシートを参照するようにしてください。 323 | 324 | ## 6.6 現場で使えるヒント 325 | 326 | ### 外部ドメインとの相互リンク 327 | 328 | ネームスペースは重複取得がプロトコル上制限されているため、 329 | インターネットドメインや実世界で周知されている商標名と同一のネームスペースを取得し、 330 | 外部(公式サイトや印刷物など)からネームスペース存在の認知を公表することで、 331 | Symbol上のアカウントのブランド価値を構築することができます 332 | (法的な効力については調整が必要です)。 333 | 外部ドメイン側のハッキングあるいは、Symbol側でのネームスペース更新忘れにはご注意ください。 334 | 335 | 336 | #### ネームスペースを取得するアカウントについての注意 337 | ネームスペースはレンタル期限という概念をもつ機能です。 338 | 今のところ、取得したネームスペースは放棄か延長の選択肢しかありません。 339 | 運用譲渡などが発生する可能性のあるシステムでネームスペース活用を検討する場合は 340 | マルチシグ化(9章)したアカウントでネームスペースを取得することをおすすめします。 341 | 342 | -------------------------------------------------------------------------------- /08_lock.md: -------------------------------------------------------------------------------- 1 | # 8.ロック 2 | 3 | Symbolブロックチェーンにはハッシュロックとシークレットロックの2種類のロック機構があります。 4 | 5 | ## 8.1 ハッシュロック 6 | 7 | ハッシュロックは後でアナウンスされる予定のトランザクションを事前にハッシュ値で登録しておくことで、 8 | 該当トランザクションがアナウンスされた場合に、そのトランザクションをAPIノード上で処理せずにロックさせて、署名が集まってから処理を行うことができます。 9 | アカウントが所有するモザイクを操作できないようにロックするわけではなく、ロックされるのはハッシュ値の対象となるトランザクションとなります。 10 | ハッシュロックにかかる費用は10XYM、有効期限は最大約48時間です。ロックしたトランザクションが承認されれば10XYMは返却されます。 11 | 12 | ### アグリゲートボンデッドトランザクションの作成 13 | 14 | ```js 15 | bob = sym.Account.generateNewAccount(networkType); 16 | 17 | tx1 = sym.TransferTransaction.create( 18 | undefined, 19 | bob.address, //Bobへの送信 20 | [ //1XYM 21 | new sym.Mosaic( 22 | new sym.NamespaceId("symbol.xym"), 23 | sym.UInt64.fromUint(1000000) 24 | ) 25 | ], 26 | sym.EmptyMessage, //メッセージ無し 27 | networkType 28 | ); 29 | 30 | tx2 = sym.TransferTransaction.create( 31 | undefined, 32 | alice.address, // Aliceへの送信 33 | [], 34 | sym.PlainMessage.create('thank you!'), //メッセージ 35 | networkType 36 | ); 37 | 38 | aggregateArray = [ 39 | tx1.toAggregate(alice.publicAccount), //Aliceからの送信 40 | tx2.toAggregate(bob.publicAccount), // Bobからの送信 41 | ] 42 | 43 | //アグリゲートボンデッドトランザクション 44 | aggregateTx = sym.AggregateTransaction.createBonded( 45 | sym.Deadline.create(epochAdjustment), 46 | aggregateArray, 47 | networkType, 48 | [], 49 | ).setMaxFeeForAggregate(100, 1); 50 | 51 | //署名 52 | signedAggregateTx = alice.sign(aggregateTx, generationHash); 53 | ``` 54 | 55 | tx1,tx2の2つのトランザクションをaggregateArrayで配列にする時に、送信元アカウントの公開鍵を指定します。 56 | 公開鍵はアカウントの章を参考に事前にAPIで取得しておきましょう。 57 | 配列化されたトランザクションはブロック承認時にその順序で整合性を検証されます。 58 | 例えば、tx1でNFTをAliceからBobへ送信した後、tx2でBobからCarolへ同じNFTを送信することは可能ですが、tx2,tx1の順序でアグリゲートトランザクションを通知するとエラーになります。 59 | また、アグリゲートトランザクションの中に1つでも整合性の合わないトランザクションが存在していると、アグリゲートトランザクション全体がエラーとなってチェーンに承認されることはありません。 60 | 61 | ### ハッシュロックトランザクションの作成と署名、アナウンス 62 | ```js 63 | //ハッシュロックTX作成 64 | hashLockTx = sym.HashLockTransaction.create( 65 | sym.Deadline.create(epochAdjustment), 66 | new sym.Mosaic(new sym.NamespaceId("symbol.xym"),sym.UInt64.fromUint(10 * 1000000)), //10xym固定値 67 | sym.UInt64.fromUint(480), // ロック有効期限 68 | signedAggregateTx,// このハッシュ値を登録 69 | networkType 70 | ).setMaxFee(100); 71 | 72 | //署名 73 | signedLockTx = alice.sign(hashLockTx, generationHash); 74 | 75 | //ハッシュロックTXをアナウンス 76 | await txRepo.announce(signedLockTx).toPromise(); 77 | ``` 78 | 79 | ### アグリゲートボンデッドトランザクションのアナウンス 80 | 81 | エクスプローラーなどで確認した後、ボンデッドトランザクションをネットワークにアナウンスします。 82 | ```js 83 | await txRepo.announceAggregateBonded(signedAggregateTx).toPromise(); 84 | ``` 85 | 86 | 87 | ### 連署 88 | ロックされたトランザクションを指定されたアカウント(Bob)で連署します。 89 | 90 | ```js 91 | txInfo = await txRepo.getTransaction(signedAggregateTx.hash,sym.TransactionGroup.Partial).toPromise(); 92 | cosignatureTx = sym.CosignatureTransaction.create(txInfo); 93 | signedCosTx = bob.signCosignatureTransaction(cosignatureTx); 94 | await txRepo.announceAggregateBondedCosignature(signedCosTx).toPromise(); 95 | ``` 96 | 97 | ### 注意点 98 | ハッシュロックトランザクションは起案者(トランザクションを作成し最初に署名するアカウント)に限らず、誰が作成してアナウンスしても大丈夫ですが、 99 | アグリゲートトランザクションにそのアカウントがsignerとなるトランザクションを含めるようにしてください。 100 | モザイク送信無し&メッセージ無しのダミートランザクションでも問題ありません(パフォーマンスに影響が出るための仕様とのことです) 101 | また、ハッシュロックトランザクションが承認された直後にボンデッドトランザクションをアナウンスした場合、 102 | ハッシュロックの承認がネットワーク全体に伝播する前にボンデッドトランザクションを受け取ってしまうノードが出てくる可能性があります。 103 | そのような状態を防ぐために、ボンデッドトランザクションはハッシュロックトランザクションが承認された後しばらく待ってからアナウンスするようにしてください。 104 | 105 | ## 8.2 シークレットロック・シークレットプルーフ 106 | 107 | シークレットロックは事前に共通パスワードを作成しておき、指定モザイクをロックします。 108 | 受信者が有効期限内にパスワードの所有を証明することができればロックされたモザイクを受け取ることができる仕組みです。 109 | 110 | ここではAliceが1XYMをロックしてBobが解除することで受信する方法を説明します。 111 | 112 | まずはAliceとやり取りするBobアカウントを作成します。 113 | ロック解除にBob側からトランザクションをアナウンスする必要があるのでFAUCETで10XYMほど受信しておきます。 114 | 115 | ```js 116 | bob = sym.Account.generateNewAccount(networkType); 117 | console.log(bob.address); 118 | 119 | //FAUCET URL出力 120 | console.log("https://testnet.symbol.tools/?recipient=" + bob.address.plain() +"&amount=10"); 121 | ``` 122 | 123 | ### シークレットロック 124 | 125 | ロック・解除にかかわる共通暗号を作成します。 126 | 127 | ```js 128 | sha3_256 = require('/node_modules/js-sha3').sha3_256; 129 | 130 | random = sym.Crypto.randomBytes(20); 131 | hash = sha3_256.create(); 132 | secret = hash.update(random).hex(); //ロック用キーワード 133 | proof = random.toString('hex'); //解除用キーワード 134 | console.log("secret:" + secret); 135 | console.log("proof:" + proof); 136 | ``` 137 | 138 | ###### 出力例 139 | ```js 140 | > secret:f260bfb53478f163ee61ee3e5fb7cfcaf7f0b663bc9dd4c537b958d4ce00e240 141 | proof:7944496ac0f572173c2549baf9ac18f893aab6d0 142 | ``` 143 | 144 | トランザクションを作成・署名・アナウンスします 145 | ```js 146 | lockTx = sym.SecretLockTransaction.create( 147 | sym.Deadline.create(epochAdjustment), 148 | new sym.Mosaic( 149 | new sym.NamespaceId("symbol.xym"), 150 | sym.UInt64.fromUint(1000000) //1XYM 151 | ), //ロックするモザイク 152 | sym.UInt64.fromUint(480), //ロック期間(ブロック数) 153 | sym.LockHashAlgorithm.Op_Sha3_256, //ロックキーワード生成に使用したアルゴリズム 154 | secret, //ロック用キーワード 155 | bob.address, //解除時の転送先:Bob 156 | networkType 157 | ).setMaxFee(100); 158 | 159 | signedLockTx = alice.sign(lockTx,generationHash); 160 | await txRepo.announce(signedLockTx).toPromise(); 161 | ``` 162 | 163 | LockHashAlgorithmは以下の通りです。 164 | ```js 165 | {0: 'Op_Sha3_256', 1: 'Op_Hash_160', 2: 'Op_Hash_256'} 166 | ``` 167 | 168 | ロック時に解除先を指定するのでBob以外のアカウントが解除しても転送先(Bob)を変更することはできません。 169 | ロック期間は最長で365日(ブロック数を日換算)までです。 170 | 171 | 承認されたトランザクションを確認します。 172 | ```js 173 | slRepo = repo.createSecretLockRepository(); 174 | res = await slRepo.search({secret:secret}).toPromise(); 175 | console.log(res.data[0]); 176 | ``` 177 | ###### 出力例 178 | ```js 179 | > SecretLockInfo 180 | amount: UInt64 {lower: 1000000, higher: 0} 181 | compositeHash: "770F65CB0CC0CA17370DE961B2AA5B48B8D86D6DB422171AB00DF34D19DEE2F1" 182 | endHeight: UInt64 {lower: 323495, higher: 0} 183 | hashAlgorithm: 0 184 | mosaicId: MosaicId {id: Id} 185 | ownerAddress: Address {address: 'TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ', networkType: 152} 186 | recipientAddress: Address {address: 'TBTWKXCNROT65CJHEBPL7F6DRHX7UKSUPD7EUGA', networkType: 152} 187 | recordId: "6260A1D3205E94BEA3D9E3E9" 188 | secret: "F260BFB53478F163EE61EE3E5FB7CFCAF7F0B663BC9DD4C537B958D4CE00E240" 189 | status: 0 190 | version: 1 191 | ``` 192 | ロックしたAliceがownerAddress、受信予定のBobがrecipientAddressに記録されています。 193 | secret情報が公開されていて、これに対応するproofをBobがネットワークに通知します。 194 | 195 | 196 | ### シークレットプルーフ 197 | 198 | 解除用キーワードを使用してロック解除します。 199 | Bobは事前に解除用キーワードを入手しておく必要があります。 200 | 201 | ```js 202 | proofTx = sym.SecretProofTransaction.create( 203 | sym.Deadline.create(epochAdjustment), 204 | sym.LockHashAlgorithm.Op_Sha3_256, //ロック作成に使用したアルゴリズム 205 | secret, //ロックキーワード 206 | bob.address, //解除アカウント(受信アカウント) 207 | proof, //解除用キーワード 208 | networkType 209 | ).setMaxFee(100); 210 | 211 | signedProofTx = bob.sign(proofTx,generationHash); 212 | await txRepo.announce(signedProofTx).toPromise(); 213 | ``` 214 | 215 | 承認結果を確認します。 216 | ```js 217 | txInfo = await txRepo.getTransaction(signedProofTx.hash,sym.TransactionGroup.Confirmed).toPromise(); 218 | console.log(txInfo); 219 | ``` 220 | ###### 出力例 221 | ```js 222 | > SecretProofTransaction 223 | > deadline: Deadline {adjustedValue: 12669305546} 224 | hashAlgorithm: 0 225 | maxFee: UInt64 {lower: 20700, higher: 0} 226 | networkType: 152 227 | payloadSize: 207 228 | proof: "A6431E74005585779AD5343E2AC5E9DC4FB1C69E" 229 | recipientAddress: Address {address: 'TBTWKXCNROT65CJHEBPL7F6DRHX7UKSUPD7EUGA', networkType: 152} 230 | secret: "4C116F32D986371D6BCC44CE64C970B6567686E79850E4A4112AF869580B7C3C" 231 | signature: "951F440860E8F24F6F3AB8EC670A3D448B12D75AB954012D9DB70030E31DA00B965003D88B7B94381761234D5A66BE989B5A8009BB234716CA3E5847C33F7005" 232 | signer: PublicAccount {publicKey: '9DC9AE081DF2E76554084DFBCCF2BC992042AA81E8893F26F8504FCED3692CFB', address: Address} 233 | > transactionInfo: TransactionInfo 234 | hash: "85044FF702A6966AB13D05DBE4AC4C3A13520C7381F32540429987C207B2056B" 235 | height: UInt64 {lower: 323805, higher: 0} 236 | id: "6260CC7F60EE2B0EA10CCEDA" 237 | merkleComponentHash: "85044FF702A6966AB13D05DBE4AC4C3A13520C7381F32540429987C207B2056B" 238 | type: 16978 239 | ``` 240 | 241 | SecretProofTransactionにはモザイクの受信量の情報は含まれていません。 242 | ブロック生成時に作成されるレシートで受信量を確認します。 243 | レシートタイプ:LockSecret_Completed でBob宛のレシートを検索してみます。 244 | 245 | ```js 246 | receiptRepo = repo.createReceiptRepository(); 247 | 248 | receiptInfo = await receiptRepo.searchReceipts({ 249 | receiptType:sym.LockSecret_Completed, 250 | targetAddress:bob.address 251 | }).toPromise(); 252 | console.log(receiptInfo.data); 253 | ``` 254 | ###### 出力例 255 | ```js 256 | > data: Array(1) 257 | > 0: TransactionStatement 258 | height: UInt64 {lower: 323805, higher: 0} 259 | > receipts: Array(1) 260 | > 0: BalanceChangeReceipt 261 | amount: UInt64 {lower: 1000000, higher: 0} 262 | > mosaicId: MosaicId 263 | id: Id {lower: 760461000, higher: 981735131} 264 | targetAddress: Address {address: 'TBTWKXCNROT65CJHEBPL7F6DRHX7UKSUPD7EUGA', networkType: 152} 265 | type: 8786 266 | ``` 267 | 268 | ReceiptTypeは以下の通りです。 269 | 270 | ```js 271 | {4685: 'Mosaic_Rental_Fee', 4942: 'Namespace_Rental_Fee', 8515: 'Harvest_Fee', 8776: 'LockHash_Completed', 8786: 'LockSecret_Completed', 9032: 'LockHash_Expired', 9042: 'LockSecret_Expired', 12616: 'LockHash_Created', 12626: 'LockSecret_Created', 16717: 'Mosaic_Expired', 16718: 'Namespace_Expired', 16974: 'Namespace_Deleted', 20803: 'Inflation', 57667: 'Transaction_Group', 61763: 'Address_Alias_Resolution', 62019: 'Mosaic_Alias_Resolution'} 272 | 273 | 8786: 'LockSecret_Completed' :ロック解除完了 274 | 9042: 'LockSecret_Expired' :ロック期限切れ 275 | ``` 276 | 277 | ## 8.3 現場で使えるヒント 278 | 279 | 280 | ### 手数料代払い 281 | 282 | 一般的にブロックチェーンはトランザクション送信に手数料を必要とします。 283 | そのため、ブロックチェーンを利用しようとするユーザは事前に手数料を取引所から入手しておく必要があります。 284 | このユーザが企業である場合はその管理方法も加えてさらにハードルの高い問題となります。 285 | アグリゲートトランザクションを使用することでハッシュロック費用とネットワーク手数料をサービス提供者が代理で負担することができます。 286 | 287 | ### タイマー送信 288 | 289 | シークレットロックは指定ブロック数を経過すると元のアカウントへ払い戻されます。 290 | この原理を利用して、シークレットロックしたアカウントにたいしてロック分の費用をサービス提供者が充足しておけば、 291 | 期限が過ぎた後ユーザ側がロック分のトークン所有量が増加することになります。 292 | 一方で、期限が過ぎる前にシークレット証明トランザクションをアナウンスすると、送信が完了し、サービス提供者に充当戻るためキャンセル扱いとなります。 293 | 294 | ### アトミックスワップ 295 | シークレットロックを使用して、他のチェーンとのトークン・モザイクの交換を行うことができます。 296 | 他のチェーンではハッシュタイムロックコントラクト(HTLC)と呼ばれているためハッシュロックと間違えないようにご注意ください。 297 | 298 | 299 | -------------------------------------------------------------------------------- /10_observer.md: -------------------------------------------------------------------------------- 1 | # 10.監視 2 | SymbolのノードはWebSocket通信でブロックチェーンの状態変化を監視することが可能です。 3 | 4 | ## 10.1 リスナー設定 5 | 6 | WebSocketを生成してリスナーの設定を行います。 7 | 8 | ```js 9 | nsRepo = repo.createNamespaceRepository(); 10 | wsEndpoint = NODE.replace('http', 'ws') + "/ws"; 11 | listener = new sym.Listener(wsEndpoint,nsRepo,WebSocket); 12 | listener.open(); 13 | ``` 14 | 15 | エンドポイントのフォーマットは以下の通りです。 16 | - wss://{node url}:3001/ws 17 | 18 | 何も通信が無ければ、listenerは1分で切断されます。 19 | 20 | ## 10.2 受信検知 21 | 22 | アカウントが受信したトランザクションを検知します。 23 | 24 | ```js 25 | listener.open().then(() => { 26 | 27 | //承認トランザクションの検知 28 | listener.confirmed(alice.address) 29 | .subscribe(tx=>{ 30 | //受信後の処理を記述 31 | console.log(tx); 32 | }); 33 | 34 | //未承認トランザクションの検知 35 | listener.unconfirmedAdded(alice.address) 36 | .subscribe(tx=>{ 37 | //受信後の処理を記述 38 | console.log(tx); 39 | }); 40 | }); 41 | ``` 42 | 上記リスナーを実行後、aliceへの送信トランザクションをアナウンスしてください。 43 | 44 | ###### 出力例 45 | ```js 46 | > Promise {} 47 | > TransferTransaction {type: 16724, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64, …} 48 | deadline: Deadline {adjustedValue: 12449258375} 49 | maxFee: UInt64 {lower: 32000, higher: 0} 50 | message: RawMessage {type: -1, payload: ''} 51 | mosaics: [] 52 | networkType: 152 53 | payloadSize: undefined 54 | recipientAddress: Address {address: 'TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ', networkType: 152} 55 | signature: "914B625F3013635FA9C99B2F138C47CD75F6E1DF7BDDA291E449390178EB461AA389522FA126D506405163CC8BA51FA9019E0522E3FA9FED7C2F857F11FBCC09" 56 | signer: PublicAccount {publicKey: 'D4933FC1E4C56F9DF9314E9E0533173E1AB727BDB2A04B59F048124E93BEFBD2', address: Address} 57 | transactionInfo: TransactionInfo 58 | hash: "3B21D8842EB70A780A662CCA19B8B030E2D5C7FB4C54BDA8B3C3760F0B35FECE" 59 | height: UInt64 {lower: 316771, higher: 0} 60 | id: undefined 61 | index: undefined 62 | merkleComponentHash: "3B21D8842EB70A780A662CCA19B8B030E2D5C7FB4C54BDA8B3C3760F0B35FECE" 63 | type: 16724 64 | version: 1 65 | ``` 66 | 67 | 未承認トランザクションは transactionInfo.height=0 で受信します。 68 | 69 | ##### 注意事項 70 | 受信先アドレスやモザイクIDで受信検知をする場合は送信者がネームスペースを利用して送信している場合もあるのでご注意ください。 71 | たとえば、メインネットでXYMのモザイクIDは`6BED913FA20223F8`ですが、ユーザーがネームスペースID(symbol.xym)で送信した場合はトランザクションには 72 | `E74B99BA41F4AFEE`というIDが記録されています。 73 | 74 | 75 | ## 10.3 ブロック監視 76 | 77 | 新規に生成されたブロックを検知します。 78 | 79 | ```js 80 | listener.open().then(() => { 81 | 82 | //ブロック生成の検知 83 | listener.newBlock() 84 | .subscribe(block=>console.log(block)); 85 | }); 86 | ``` 87 | ###### 出力例 88 | ```js 89 | > Promise {} 90 | > NewBlock 91 | beneficiaryAddress: Address {address: 'TAKATV2VSYBH3RX4JVCCILITWANT6JRANZI2AUQ', networkType: 152} 92 | blockReceiptsHash: "ABDDB66A03A270E4815C256A8125B70FC3B7EFC4B95FF5ECAD517CB1AB5F5334" 93 | blockTransactionsHash: "0000000000000000000000000000000000000000000000000000000000000000" 94 | difficulty: UInt64 {lower: 1316134912, higher: 2328} 95 | feeMultiplier: 0 96 | generationHash: "5B4F32D3F2CDD17917D530A6A967927D93F73F2B52CC590A64E3E94408D8CE96" 97 | hash: "E8294BDDDAE32E17242DF655805EC0FCAB3B628A331824B87A3CA7578683B09C" 98 | height: UInt64 {lower: 316759, higher: 0} 99 | networkType: 152 100 | previousBlockHash: "38382D616772682321D58046511DD942F36A463155C5B7FB0A2CBEE8E29B253C" 101 | proofGamma: "37187F1C8BD8C87CB4F000F353ACE5717D988BC220EFBCC25E2F40B1FB9B7D7A" 102 | proofScalar: "AD91A572E5D81EA92FE313CA00915E5A497F60315C63023A52E292E55345F705" 103 | proofVerificationHash: "EF58228B3EB3C422289626935DADEF11" 104 | signature: "A9481E5976EDA86B74433E8BCC8495788BA2B9BE0A50F9435AD90A14D1E362D934BA26069182C373783F835E55D7F3681817716295EC1EFB5F2375B6DE302801" 105 | signer: PublicAccount {publicKey: 'F2195B3FAFBA3DF8C31CFBD9D5BE95BB3F3A04BDB877C59EFB9D1C54ED2DC50E', address: Address} 106 | stateHash: "4A1C828B34DE47759C2D717845830BA14287A4EC7220B75494BDC31E9539FCB5" 107 | timestamp: UInt64 {lower: 3851456497, higher: 2} 108 | type: 33091 109 | version: 1 110 | ``` 111 | 112 | listener.newBlock()をしておくと、約30秒ごとに通信が発生するのでWebSocketの切断が起こりにくくなります。 113 | まれに、ブロック生成が1分を超える場合があるのでその場合はリスナーを再接続する必要があります。 114 | (その他の事象で切断される可能性もあるので、万全を期したい場合は後述するoncloseで補足しましょう) 115 | 116 | ## 10.4 署名要求 117 | 118 | 署名が必要なトランザクションが発生すると検知します。 119 | 120 | ```js 121 | listener.open().then(() => { 122 | //署名が必要なアグリゲートボンデッドトランザクション発生の検知 123 | listener.aggregateBondedAdded(alice.address) 124 | .subscribe(async tx=>console.log(tx)); 125 | }); 126 | ``` 127 | ###### 出力例 128 | ```js 129 | 130 | > AggregateTransaction 131 | cosignatures: [] 132 | deadline: Deadline {adjustedValue: 12450154608} 133 | > innerTransactions: Array(2) 134 | 0: TransferTransaction {type: 16724, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64, …} 135 | 1: TransferTransaction {type: 16724, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64, …} 136 | maxFee: UInt64 {lower: 94400, higher: 0} 137 | networkType: 152 138 | signature: "972968C5A2FB70C1D644BE206A190C4FCFDA98976F371DBB70D66A3AAEBCFC4B26E7833BCB86C407879C07927F6882C752C7012C265C2357CAA52C29834EFD0F" 139 | signer: PublicAccount {publicKey: '0E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26', address: Address} 140 | > transactionInfo: TransactionInfo 141 | hash: "44B2CD891DA0B788F1DD5D5AB24866A9A172C80C1749DCB6EB62255A2497EA08" 142 | height: UInt64 {lower: 0, higher: 0} 143 | id: undefined 144 | index: undefined 145 | merkleComponentHash: "0000000000000000000000000000000000000000000000000000000000000000" 146 | type: 16961 147 | version: 1 148 | 149 | ``` 150 | 151 | 指定アドレスが関係するすべてのアグリゲートトランザクションが検知されます。 152 | 連署が必要かどうかは別途フィルターして判断します。 153 | 154 | 155 | ## 10.5 現場で使えるヒント 156 | ### 常時コネクション 157 | 158 | 一覧からランダムに選択し、接続を試みます。 159 | 160 | ##### ノードへの接続 161 | ```js 162 | //ノード一覧 163 | NODES = ["https://node.com:3001",...]; 164 | 165 | function connectNode(nodes) { 166 | const node = nodes[Math.floor(Math.random() * nodes.length)] ; 167 | console.log("try:" + node); 168 | 169 | return new Promise((resolve, reject) => { 170 | let req = new XMLHttpRequest(); 171 | req.timeout = 2000; //タイムアウト値:2秒(=2000ms) 172 | req.open('GET', node + "/node/health", true); 173 | req.onload = function() { 174 | if (req.status === 200) { 175 | const status = JSON.parse(req.responseText).status; 176 | if(status.apiNode == "up" && status.db == "up"){ 177 | return resolve(node); 178 | }else{ 179 | console.log("fail node status:" + status); 180 | return connectNode(nodes).then(node => resolve(node)); 181 | } 182 | } else { 183 | console.log("fail request status:" + req.status) 184 | return connectNode(nodes).then(node => resolve(node)); 185 | } 186 | }; 187 | 188 | req.onerror = function(e) { 189 | console.log("onerror:" + e) 190 | return connectNode(nodes).then(node => resolve(node)); 191 | }; 192 | 193 | req.ontimeout = function (e) { 194 | console.log("ontimeout") 195 | return connectNode(nodes).then(node => resolve(node)); 196 | }; 197 | 198 | req.send(); 199 | }); 200 | } 201 | ``` 202 | 203 | タイムアウト値を設定しておき、応答の悪いノードに接続した場合は選びなおします。 204 | エンドポイント /node/health を確認してステータス異常の場合はノードを選びなおします。 205 | 206 | 207 | ##### レポジトリの作成 208 | ```js 209 | function createRepo(nodes){ 210 | 211 | return connectNode(nodes).then(async function onFulfilled(node) { 212 | 213 | const repo = new sym.RepositoryFactoryHttp(node); 214 | 215 | try{ 216 | epochAdjustment = await repo.getEpochAdjustment().toPromise(); 217 | }catch(error){ 218 | console.log("fail createRepo"); 219 | return await createRepo(nodes); 220 | } 221 | return await repo; 222 | }); 223 | } 224 | ``` 225 | まれに /network/properties のエンドポイントが解放されていないノードが存在するため、 226 | getEpochAdjustment() の情報を取得してチェックを行います。取得できない場合は再帰的にcreateRepoを読み込みます。 227 | 228 | 229 | ##### リスナーの常時接続 230 | ```js 231 | async function listenerKeepOpening(nodes){ 232 | 233 | const repo = await createRepo(NODES); 234 | let wsEndpoint = repo.url.replace('http', 'ws') + "/ws"; 235 | const nsRepo = repo.createNamespaceRepository(); 236 | const lner = new sym.Listener(wsEndpoint,nsRepo,WebSocket); 237 | try{ 238 | await lner.open(); 239 | lner.newBlock(); 240 | }catch(e){ 241 | console.log("fail websocket"); 242 | return await listenerKeepOpening(nodes); 243 | } 244 | 245 | lner.webSocket.onclose = async function(){ 246 | console.log("listener onclose"); 247 | return await listenerKeepOpening(nodes); 248 | } 249 | return lner; 250 | } 251 | ``` 252 | 253 | リスナーがcloseした場合は再接続します。 254 | 255 | ##### リスナー開始 256 | ```js 257 | listener = await listenerKeepOpening(NODES); 258 | ``` 259 | 260 | ### 未署名トランザクション自動連署 261 | 262 | 未署名のトランザクションを検知して、署名&ネットワークにアナウンスします。 263 | 初期画面表示時と画面閲覧中の受信と2パターンの検知が必要です。 264 | 265 | ```js 266 | //rxjsの読み込み 267 | op = require("/node_modules/rxjs/operators"); 268 | rxjs = require("/node_modules/rxjs"); 269 | 270 | //アグリゲートトランザクション検知 271 | bondedListener = listener.aggregateBondedAdded(bob.address); 272 | bondedHttp = txRepo.search({address:bob.address,group:sym.TransactionGroup.Partial}) 273 | .pipe( 274 | op.delay(2000), 275 | op.mergeMap(page => page.data) 276 | ); 277 | 278 | //選択中アカウントの完了トランザクション検知リスナー 279 | const statusChanged = function(address,hash){ 280 | 281 | const transactionObservable = listener.confirmed(address); 282 | const errorObservable = listener.status(address, hash); 283 | return rxjs.merge(transactionObservable, errorObservable).pipe( 284 | op.first(), 285 | op.map((errorOrTransaction) => { 286 | if (errorOrTransaction.constructor.name === "TransactionStatusError") { 287 | throw new Error(errorOrTransaction.code); 288 | } else { 289 | return errorOrTransaction; 290 | } 291 | }), 292 | ); 293 | } 294 | 295 | //連署実行 296 | function exeAggregateBondedCosignature(tx){ 297 | 298 | txRepo.getTransactionsById([tx.transactionInfo.hash],sym.TransactionGroup.Partial) 299 | .pipe( 300 | //トランザクションが抽出された場合のみ 301 | op.filter(aggTx => aggTx.length > 0) 302 | ) 303 | .subscribe(async aggTx =>{ 304 | 305 | //インナートランザクションの署名者に自分が指定されている場合 306 | if(aggTx[0].innerTransactions.find((inTx) => inTx.signer.equals(bob.publicAccount))!= undefined){ 307 | //Aliceのトランザクションで署名 308 | const cosignatureTx = sym.CosignatureTransaction.create(aggTx[0]); 309 | const signedTx = bob.signCosignatureTransaction(cosignatureTx); 310 | const cosignedAggTx = await txRepo.announceAggregateBondedCosignature(signedTx).toPromise(); 311 | statusChanged(bob.address,signedTx.parentHash).subscribe(res=>{ 312 | console.log(res); 313 | }); 314 | } 315 | }); 316 | } 317 | 318 | bondedSubscribe = function(observer){ 319 | observer.pipe( 320 | 321 | //すでに署名済みでない場合 322 | op.filter(tx => { 323 | return !tx.signedByAccount(sym.PublicAccount.createFromPublicKey(bob.publicKey ,networkType)); 324 | }) 325 | ).subscribe(tx=>{ 326 | console.log(tx); 327 | exeAggregateBondedCosignature(tx); 328 | }); 329 | } 330 | 331 | bondedSubscribe(bondedListener); 332 | bondedSubscribe(bondedHttp); 333 | ``` 334 | 335 | ##### 注意事項 336 | スキャムトランザクションを自動署名しないように、 337 | 送信元のアカウントを確認するなどのチェック処理を必ず実施するようにしてください。 338 | -------------------------------------------------------------------------------- /11_restriction.md: -------------------------------------------------------------------------------- 1 | # 11.制限 2 | 3 | アカウントに対する制限とモザイクのグローバル制限についての方法を紹介します。 4 | 本章では、既存アカウントの権限を制限してしまうので、使い捨てのアカウントを新規に作成してお試しください。 5 | 6 | ```js 7 | //使い捨てアカウントCarolの生成 8 | carol = sym.Account.generateNewAccount(networkType); 9 | console.log(carol.address); 10 | 11 | //FAUCET URL出力 12 | console.log("https://testnet.symbol.tools/?recipient=" + carol.address.plain() +"&amount=100"); 13 | ``` 14 | ## 11.1 アカウント制限 15 | 16 | ### 指定アドレスからの受信制限・指定アドレスへの送信制限 17 | ```js 18 | 19 | bob = sym.Account.generateNewAccount(networkType); 20 | 21 | tx = sym.AccountRestrictionTransaction.createAddressRestrictionModificationTransaction( 22 | sym.Deadline.create(epochAdjustment), 23 | sym.AddressRestrictionFlag.BlockIncomingAddress, //アドレス制限フラグ 24 | [bob.address],//設定アドレス 25 | [],      //解除アドレス 26 | networkType 27 | ).setMaxFee(100); 28 | signedTx = carol.sign(tx,generationHash); 29 | await txRepo.announce(signedTx).toPromise(); 30 | ``` 31 | 32 | AddressRestrictionFlagについては以下の通りです。 33 | ```js 34 | {1: 'AllowIncomingAddress', 16385: 'AllowOutgoingAddress', 32769: 'BlockIncomingAddress', 49153: 'BlockOutgoingAddress'} 35 | ``` 36 | 37 | AddressRestrictionFlagにはBlockIncomingAddressのほか、上記のようなフラグが使用できます。 38 | - AllowIncomingAddress:指定アドレスからのみ受信許可 39 | - AllowOutgoingAddress:指定アドレス宛のみ送信許可 40 | - BlockIncomingAddress:指定アドレスからの受信受拒否 41 | - BlockOutgoingAddress:指定アドレス宛への送信禁止 42 | 43 | ### 指定モザイクの受信制限 44 | ```js 45 | mosaicId = new sym.MosaicId("72C0212E67A08BCE"); //テストネット XYM 46 | tx = sym.AccountRestrictionTransaction.createMosaicRestrictionModificationTransaction( 47 | sym.Deadline.create(epochAdjustment), 48 | sym.MosaicRestrictionFlag.BlockMosaic, //モザイク制限フラグ 49 | [mosaicId],//設定モザイク 50 | [],//解除モザイク 51 | networkType 52 | ).setMaxFee(100); 53 | signedTx = carol.sign(tx,generationHash); 54 | await txRepo.announce(signedTx).toPromise(); 55 | ``` 56 | 57 | MosaicRestrictionFlagについては以下の通りです。 58 | ```js 59 | {2: 'AllowMosaic', 32770: 'BlockMosaic'} 60 | ``` 61 | 62 | - AllowMosaic:指定モザイクを含むトランザクションのみ受信許可 63 | - BlockMosaic:指定モザイクを含むトランザクションを受信拒否 64 | 65 | モザイク送信の制限機能はありません。 66 | また、後述するモザイクのふるまいを制限するグローバルモザイク制限と混同しないようにご注意ください。 67 | 68 | ### 指定トランザクションの送信制限 69 | 70 | ```js 71 | tx = sym.AccountRestrictionTransaction.createOperationRestrictionModificationTransaction( 72 | sym.Deadline.create(epochAdjustment), 73 | sym.OperationRestrictionFlag.AllowOutgoingTransactionType, 74 | [sym.TransactionType.ACCOUNT_OPERATION_RESTRICTION],//設定トランザクション 75 | [],//解除トランザクション 76 | networkType 77 | ).setMaxFee(100); 78 | signedTx = carol.sign(tx,generationHash); 79 | await txRepo.announce(signedTx).toPromise(); 80 | ``` 81 | 82 | OperationRestrictionFlagについては以下の通りです。 83 | ```js 84 | {16388: 'AllowOutgoingTransactionType', 49156: 'BlockOutgoingTransactionType'} 85 | ``` 86 | 87 | - AllowOutgoingTransactionType:指定トランザクションの送信のみ許可 88 | - BlockOutgoingTransactionType:指定トランザクションの送信を禁止 89 | 90 | トランザクション受信の制限機能はありません。指定できるオペレーションは以下の通りです。 91 | 92 | TransactionTypeについては以下の通りです。 93 | ```js 94 | {16705: 'AGGREGATE_COMPLETE', 16707: 'VOTING_KEY_LINK', 16708: 'ACCOUNT_METADATA', 16712: 'HASH_LOCK', 16716: 'ACCOUNT_KEY_LINK', 16717: 'MOSAIC_DEFINITION', 16718: 'NAMESPACE_REGISTRATION', 16720: 'ACCOUNT_ADDRESS_RESTRICTION', 16721: 'MOSAIC_GLOBAL_RESTRICTION', 16722: 'SECRET_LOCK', 16724: 'TRANSFER', 16725: 'MULTISIG_ACCOUNT_MODIFICATION', 16961: 'AGGREGATE_BONDED', 16963: 'VRF_KEY_LINK', 16964: 'MOSAIC_METADATA', 16972: 'NODE_KEY_LINK', 16973: 'MOSAIC_SUPPLY_CHANGE', 16974: 'ADDRESS_ALIAS', 16976: 'ACCOUNT_MOSAIC_RESTRICTION', 16977: 'MOSAIC_ADDRESS_RESTRICTION', 16978: 'SECRET_PROOF', 17220: 'NAMESPACE_METADATA', 17229: 'MOSAIC_SUPPLY_REVOCATION', 17230: 'MOSAIC_ALIAS'} 95 | ``` 96 | 97 | ##### 注意事項 98 | 17232: 'ACCOUNT_OPERATION_RESTRICTION' の制限は許可されていません。 99 | つまり、AllowOutgoingTransactionTypeを指定する場合は、ACCOUNT_OPERATION_RESTRICTIONを必ず含める必要があり、 100 | BlockOutgoingTransactionTypeを指定する場合は、ACCOUNT_OPERATION_RESTRICTIONを含めることはできません。 101 | 102 | 103 | ### 確認 104 | 105 | 設定した制限情報を確認します 106 | 107 | ```js 108 | resAccountRepo = repo.createRestrictionAccountRepository(); 109 | 110 | res = await resAccountRepo.getAccountRestrictions(carol.address).toPromise(); 111 | console.log(res); 112 | ``` 113 | ###### 出力例 114 | ```js 115 | > AccountRestrictions 116 | address: Address {address: 'TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ', networkType: 152} 117 | > restrictions: Array(2) 118 | 0: AccountRestriction 119 | restrictionFlags: 32770 120 | values: Array(1) 121 | 0: MosaicId 122 | id: Id {lower: 1360892257, higher: 309702839} 123 | 1: AccountRestriction 124 | restrictionFlags: 49153 125 | values: Array(1) 126 | 0: Address {address: 'TCW2ZW7LVJMS4LWUQ7W6NROASRE2G2QKSBVCIQY', networkType: 152} 127 | ``` 128 | 129 | ## 11.2 グローバルモザイク制限 130 | 131 | グローバルモザイク制限はモザイクに対して送信可能な条件を設定します。 132 | その後、各アカウントに対してグローバルモザイク制限専用の数値メタデータを付与します。 133 | 送信アカウント・受信アカウントの両方が条件を満たした場合のみ、該当モザイクを送信することができます。 134 | 135 | 最初に必要ライブラリの設定を行います。 136 | ```js 137 | nsRepo = repo.createNamespaceRepository(); 138 | resMosaicRepo = repo.createRestrictionMosaicRepository(); 139 | mosaicResService = new sym.MosaicRestrictionTransactionService(resMosaicRepo,nsRepo); 140 | ``` 141 | 142 | 143 | ### グローバル制限機能つきモザイクの作成 144 | restrictableをtrueにしてCarolでモザイクを作成します。 145 | 146 | ```js 147 | supplyMutable = true; //供給量変更の可否 148 | transferable = true; //第三者への譲渡可否 149 | restrictable = true; //グローバル制限設定の可否 150 | revokable = true; //発行者からの還収可否 151 | 152 | nonce = sym.MosaicNonce.createRandom(); 153 | mosaicDefTx = sym.MosaicDefinitionTransaction.create( 154 | undefined, 155 | nonce, 156 | sym.MosaicId.createFromNonce(nonce, carol.address), 157 | sym.MosaicFlags.create(supplyMutable, transferable, restrictable, revokable), 158 | 0,//divisibility 159 | sym.UInt64.fromUint(0), //duration 160 | networkType 161 | ); 162 | 163 | //モザイク変更 164 | mosaicChangeTx = sym.MosaicSupplyChangeTransaction.create( 165 | undefined, 166 | mosaicDefTx.mosaicId, 167 | sym.MosaicSupplyChangeAction.Increase, 168 | sym.UInt64.fromUint(1000000), 169 | networkType 170 | ); 171 | 172 | //グローバルモザイク制限 173 | key = sym.KeyGenerator.generateUInt64Key("KYC") // restrictionKey 174 | mosaicGlobalResTx = await mosaicResService.createMosaicGlobalRestrictionTransaction( 175 | undefined, 176 | networkType, 177 | mosaicDefTx.mosaicId, 178 | key, 179 | '1', 180 | sym.MosaicRestrictionType.EQ, 181 | ).toPromise(); 182 | 183 | aggregateTx = sym.AggregateTransaction.createComplete( 184 | sym.Deadline.create(epochAdjustment), 185 | [ 186 | mosaicDefTx.toAggregate(carol.publicAccount), 187 | mosaicChangeTx.toAggregate(carol.publicAccount), 188 | mosaicGlobalResTx.toAggregate(carol.publicAccount) 189 | ], 190 | networkType,[], 191 | ).setMaxFeeForAggregate(100, 0); 192 | 193 | signedTx = carol.sign(aggregateTx,generationHash); 194 | await txRepo.announce(signedTx).toPromise(); 195 | ``` 196 | 197 | MosaicRestrictionTypeについては以下の通りです。 198 | 199 | ```js 200 | {0: 'NONE', 1: 'EQ', 2: 'NE', 3: 'LT', 4: 'LE', 5: 'GT', 6: 'GE'} 201 | ``` 202 | 203 | | 演算子 | 略称 | 英語 | 204 | |---|---|---| 205 | | = | EQ | equal to | 206 | | != | NE | not equal to | 207 | | < | LT | less than | 208 | | <= | LE | less than or equal to | 209 | | > | GT | greater than | 210 | | <= | GE | greater than or equal to | 211 | 212 | 213 | ### アカウントへのモザイク制限適用 214 | 215 | Carol,Bobに対してグローバル制限モザイクに対しての適格情報を追加します。 216 | 送信・受信についてかかる制限なので、すでに所有しているモザイクについての制限はありません。 217 | 送信を成功させるためには、送信者・受信者双方が条件をクリアしている必要があります。 218 | モザイク作成者の秘密鍵があればどのアカウントに対しても承諾の署名を必要とせずに制限をつけることができます。 219 | 220 | ```js 221 | //Carolに適用 222 | carolMosaicAddressResTx = sym.MosaicAddressRestrictionTransaction.create( 223 | sym.Deadline.create(epochAdjustment), 224 | mosaicDefTx.mosaicId, // mosaicId 225 | sym.KeyGenerator.generateUInt64Key("KYC"), // restrictionKey 226 | carol.address, // address 227 | sym.UInt64.fromUint(1), // newRestrictionValue 228 | networkType, 229 | sym.UInt64.fromHex('FFFFFFFFFFFFFFFF') //previousRestrictionValue 230 | ).setMaxFee(100); 231 | signedTx = carol.sign(carolMosaicAddressResTx,generationHash); 232 | await txRepo.announce(signedTx).toPromise(); 233 | 234 | //Bobに適用 235 | bob = sym.Account.generateNewAccount(networkType); 236 | bobMosaicAddressResTx = sym.MosaicAddressRestrictionTransaction.create( 237 | sym.Deadline.create(epochAdjustment), 238 | mosaicDefTx.mosaicId, // mosaicId 239 | sym.KeyGenerator.generateUInt64Key("KYC"), // restrictionKey 240 | bob.address, // address 241 | sym.UInt64.fromUint(1), // newRestrictionValue 242 | networkType, 243 | sym.UInt64.fromHex('FFFFFFFFFFFFFFFF') //previousRestrictionValue 244 | ).setMaxFee(100); 245 | signedTx = carol.sign(bobMosaicAddressResTx,generationHash); 246 | await txRepo.announce(signedTx).toPromise(); 247 | ``` 248 | 249 | ### 制限状態確認 250 | 251 | ノードに問い合わせて制限状態を確認します。 252 | 253 | ```js 254 | res = await resMosaicRepo.search({mosaicId:mosaicDefTx.mosaicId}).toPromise(); 255 | console.log(res); 256 | ``` 257 | 258 | ###### 出力例 259 | ```js 260 | > data 261 | > 0: MosaicGlobalRestriction 262 | compositeHash: "68FBADBAFBD098C157D42A61A7D82E8AF730D3B8C3937B1088456432CDDB8373" 263 | entryType: 1 264 | > mosaicId: MosaicId 265 | id: Id {lower: 2467167064, higher: 973862467} 266 | > restrictions: Array(1) 267 | 0: MosaicGlobalRestrictionItem 268 | key: UInt64 {lower: 2424036727, higher: 2165465980} 269 | restrictionType: 1 270 | restrictionValue: UInt64 {lower: 1, higher: 0} 271 | > 1: MosaicAddressRestriction 272 | compositeHash: "920BFD041B6D30C0799E06585EC5F3916489E2DDF47FF6C30C569B102DB39F4E" 273 | entryType: 0 274 | > mosaicId: MosaicId 275 | id: Id {lower: 2467167064, higher: 973862467} 276 | > restrictions: Array(1) 277 | 0: MosaicAddressRestrictionItem 278 | key: UInt64 {lower: 2424036727, higher: 2165465980} 279 | restrictionValue: UInt64 {lower: 1, higher: 0} 280 | targetAddress: Address {address: 'TAZCST2RBXDSD3227Y4A6ZP3QHFUB2P7JQVRYEI', networkType: 152} 281 | > 2: MosaicAddressRestriction 282 | ... 283 | ``` 284 | 285 | ### 送信確認 286 | 287 | 実際にモザイクを送信してみて、制限状態を確認します。 288 | 289 | ```js 290 | //成功(CarolからBobに送信) 291 | trTx = sym.TransferTransaction.create( 292 | sym.Deadline.create(epochAdjustment), 293 | bob.address, 294 | [new sym.Mosaic(mosaicDefTx.mosaicId, sym.UInt64.fromUint(1))], 295 | sym.PlainMessage.create(""), 296 | networkType 297 | ).setMaxFee(100); 298 | signedTx = carol.sign(trTx,generationHash); 299 | await txRepo.announce(signedTx).toPromise(); 300 | 301 | //失敗(CarolからDaveに送信) 302 | dave = sym.Account.generateNewAccount(networkType); 303 | trTx = sym.TransferTransaction.create( 304 | sym.Deadline.create(epochAdjustment), 305 | dave.address, 306 | [new sym.Mosaic(mosaicDefTx.mosaicId, sym.UInt64.fromUint(1))], 307 | sym.PlainMessage.create(""), 308 | networkType 309 | ).setMaxFee(100); 310 | signedTx = carol.sign(trTx,generationHash); 311 | await txRepo.announce(signedTx).toPromise(); 312 | ``` 313 | 314 | 失敗した場合以下のようなエラーステータスになります。 315 | 316 | ```js 317 | {"hash":"E3402FB7AE21A6A64838DDD0722420EC67E61206C148A73B0DFD7F8C098062FA","code":"Failure_RestrictionMosaic_Account_Unauthorized","deadline":"12371602742","group":"failed"} 318 | ``` 319 | 320 | ## 11.3 現場で使えるヒント 321 | 322 | ブロックチェーンの社会実装などを考えたときに、法律や信頼性の見地から 323 | 一つの役割のみを持たせたいアカウント、関係ないアカウントを巻き込みたくないと思うことがあります。 324 | そんな場合にアカウント制限とグローバルモザイク制限を使いこなすことで、 325 | モザイクのふるまいを柔軟にコントロールすることができます。 326 | 327 | ### アカウントバーン 328 | 329 | AllowIncomingAddressによって指定アドレスからのみ受信可能にしておいて、 330 | XYMを全量送信すると、秘密鍵を持っていても自力では操作困難なアカウントを明示的に作成することができます。 331 | (最小手数料を0に設定したノードによって承認されることもあり、その可能性はゼロではありません) 332 | 333 | ### モザイクロック 334 | 譲渡不可設定のモザイクを配布し、配布者側のアカウントで受け取り拒否を行うとモザイクをロックさせることができます。 335 | 336 | ### 所属証明 337 | モザイクの章で所有の証明について説明しました。グローバルモザイク制限を活用することで、 338 | KYCが済んだアカウント間でのみ所有・流通させることが可能なモザイクを作り、所有者のみが所属できる独自経済圏を構築することが可能です。 339 | 340 | 341 | -------------------------------------------------------------------------------- /09_multisig.md: -------------------------------------------------------------------------------- 1 | # 9.マルチシグ化 2 | アカウントのマルチシグ化について説明します。 3 | 4 | 5 | ### 注意事項 6 | 7 | 一つのマルチシグアカウントに登録できる連署者の数は25個です。 8 | 一つのアカウントは最大25個のマルチシグの連署者になれます。 9 | マルチシグは最大3階層まで構成できます。 10 | 本書では1階層のマルチシグのみ解説します。 11 | 12 | ## 9.0 アカウントの準備 13 | この章のサンプルソースコードで使用するアカウントを作成し、それぞれの秘密鍵を出力しておきます。 14 | 本章でマルチシグ化したアカウントBobは、Carolの秘密鍵を紛失すると使えなくなってしまうのでご注意ください。 15 | 16 | ```js 17 | bob = sym.Account.generateNewAccount(networkType); 18 | carol1 = sym.Account.generateNewAccount(networkType); 19 | carol2 = sym.Account.generateNewAccount(networkType); 20 | carol3 = sym.Account.generateNewAccount(networkType); 21 | carol4 = sym.Account.generateNewAccount(networkType); 22 | carol5 = sym.Account.generateNewAccount(networkType); 23 | 24 | console.log(bob.privateKey); 25 | console.log(carol1.privateKey); 26 | console.log(carol2.privateKey); 27 | console.log(carol3.privateKey); 28 | console.log(carol4.privateKey); 29 | console.log(carol5.privateKey); 30 | ``` 31 | 32 | テストネットの場合はFAUCETでネットワーク手数料分をbobとcarol1に補給しておきます。 33 | 34 | - Faucet 35 | - https://testnet.symbol.tools/ 36 | 37 | ##### URL出力 38 | ```js 39 | console.log("https://testnet.symbol.tools/?recipient=" + bob.address.plain() +"&amount=20"); 40 | console.log("https://testnet.symbol.tools/?recipient=" + carol1.address.plain() +"&amount=20"); 41 | ``` 42 | 43 | ## 9.1 マルチシグの登録 44 | 45 | Symbolではマルチシグアカウントを新規に作成するのではなく、既存アカウントについて連署者を指定してマルチシグ化します。 46 | マルチシグ化には連署者に指定されたアカウントの承諾署名(オプトイン)が必要なため、アグリゲートトランザクションを使用します。 47 | 48 | ```js 49 | multisigTx = sym.MultisigAccountModificationTransaction.create( 50 | undefined, 51 | 3, //minApproval:承認のために必要な最小署名者数増分 52 | 3, //minRemoval:除名のために必要な最小署名者数増分 53 | [ 54 | carol1.address,carol2.address,carol3.address,carol4.address 55 | ], //追加対象アドレスリスト 56 | [],//除名対象アドレスリスト 57 | networkType 58 | ); 59 | 60 | aggregateTx = sym.AggregateTransaction.createComplete( 61 | sym.Deadline.create(epochAdjustment), 62 | [//マルチシグ化したいアカウントの公開鍵を指定 63 | multisigTx.toAggregate(bob.publicAccount), 64 | ], 65 | networkType,[] 66 | ).setMaxFeeForAggregate(100, 4); // 第二引数に連署者の数:4 67 | 68 | signedTx = aggregateTx.signTransactionWithCosignatories( 69 | bob, //マルチシグ化したいアカウント 70 | [carol1,carol2,carol3,carol4], //追加・除外対象として指定したアカウント 71 | generationHash, 72 | ); 73 | await txRepo.announce(signedTx).toPromise(); 74 | ``` 75 | 76 | ## 9.2 確認 77 | 78 | ### マルチシグ化したアカウントの確認 79 | ```js 80 | msigRepo = repo.createMultisigRepository(); 81 | 82 | multisigInfo = await msigRepo.getMultisigAccountInfo(bob.address).toPromise(); 83 | console.log(multisigInfo); 84 | ``` 85 | ###### 出力例 86 | ```js 87 | > MultisigAccountInfo 88 | accountAddress: Address {address: 'TCOMA5VG67TZH4X55HGZOXOFP7S232CYEQMOS7Q', networkType: 152} 89 | > cosignatoryAddresses: Array(4) 90 | 0: Address {address: 'TBAFGZOCB7OHZCCYYV64F2IFZL7SOOXNDHFS5NY', networkType: 152} 91 | 1: Address {address: 'TB3XP4GQK6XH2SSA2E2U6UWCESNACK566DS4COY', networkType: 152} 92 | 2: Address {address: 'TCV67BMTD2JMDQOJUDQHBFJHQPG4DAKVKST3YJI', networkType: 152} 93 | 3: Address {address: 'TDWGG6ZWCGS5AHFTF5FDB347HIMII57PK46AIDA', networkType: 152} 94 | minApproval: 3 95 | minRemoval: 3 96 | multisigAddresses: [] 97 | ``` 98 | 99 | cosignatoryAddressesが連署者として登録されていることがわかります。 100 | また、minApproval:3 によりトランザクションが成立するために必要な署名数3 101 | minRemoval: 3により連署者を取り外すために必要な署名者数は3であることがわかります。 102 | 103 | ### 連署者アカウントの確認 104 | ```js 105 | msigRepo = repo.createMultisigRepository(); 106 | 107 | multisigInfo = await msigRepo.getMultisigAccountInfo(carol1.address).toPromise(); 108 | console.log(multisigInfo); 109 | ``` 110 | ###### 出力例 111 | ``` 112 | > MultisigAccountInfo 113 | accountAddress: Address {address: 'TCV67BMTD2JMDQOJUDQHBFJHQPG4DAKVKST3YJI', networkType: 152} 114 | cosignatoryAddresses: [] 115 | minApproval: 0 116 | minRemoval: 0 117 | > multisigAddresses: Array(1) 118 | 0: Address {address: 'TCOMA5VG67TZH4X55HGZOXOFP7S232CYEQMOS7Q', networkType: 152} 119 | ``` 120 | 121 | multisigAddresses に対して連署する権利を持っていることが分かります。 122 | 123 | ## 9.3 マルチシグ署名 124 | 125 | マルチシグ化したアカウントからモザイクを送信します。 126 | 127 | ### アグリゲートコンプリートトランザクションで送信 128 | 129 | アグリゲートコンプリートトランザクションの場合、ノードにアナウンスする前に連署者の署名を全て集めてからトランザクションを作成します。 130 | 131 | ```js 132 | tx = sym.TransferTransaction.create( 133 | undefined, 134 | alice.address, 135 | [new sym.Mosaic(new sym.NamespaceId("symbol.xym"),sym.UInt64.fromUint(1000000))], 136 | sym.PlainMessage.create('test'), 137 | networkType 138 | ); 139 | 140 | aggregateTx = sym.AggregateTransaction.createComplete( 141 | sym.Deadline.create(epochAdjustment), 142 | [//マルチシグ化したアカウントの公開鍵を指定 143 | tx.toAggregate(bob.publicAccount) 144 | ], 145 | networkType,[], 146 | ).setMaxFeeForAggregate(100, 2); // 第二引数に連署者の数:2 147 | 148 | signedTx = aggregateTx.signTransactionWithCosignatories( 149 | carol1, //起案者 150 | [carol2,carol3], //連署者 151 | generationHash, 152 | ); 153 | await txRepo.announce(signedTx).toPromise(); 154 | ``` 155 | 156 | ### アグリゲートボンデッドトランザクションで送信 157 | 158 | アグリゲートボンデッドトランザクションの場合は連署者を指定せずにアナウンスできます。 159 | 事前にハッシュロックでトランザクションを留め置きしておくことを宣言しておき、連署者がネットワーク上に留め置きされたトランザクションに追加署名することで完成となります。 160 | 161 | ```js 162 | tx = sym.TransferTransaction.create( 163 | undefined, 164 | alice.address, //Aliceへの送信 165 | [new sym.Mosaic(new sym.NamespaceId("symbol.xym"),sym.UInt64.fromUint(1000000))], //1XYM 166 | sym.PlainMessage.create('test'), 167 | networkType 168 | ); 169 | 170 | aggregateTx = sym.AggregateTransaction.createBonded( 171 | sym.Deadline.create(epochAdjustment), 172 | [ //マルチシグ化したアカウントの公開鍵を指定 173 | tx.toAggregate(bob.publicAccount) 174 | ], 175 | networkType,[], 176 | ).setMaxFeeForAggregate(100, 2); // 第二引数に連署者の数:2 177 | 178 | signedAggregateTx = carol1.sign(aggregateTx, generationHash); 179 | 180 | hashLockTx = sym.HashLockTransaction.create( 181 | sym.Deadline.create(epochAdjustment), 182 | new sym.Mosaic(new sym.NamespaceId("symbol.xym"),sym.UInt64.fromUint(10 * 1000000)), //固定値:10XYM 183 | sym.UInt64.fromUint(480), 184 | signedAggregateTx, 185 | networkType 186 | ).setMaxFee(100); 187 | 188 | signedLockTx = carol1.sign(hashLockTx, generationHash); 189 | 190 | //ハッシュロックTXをアナウンス 191 | await txRepo.announce(signedLockTx).toPromise(); 192 | ``` 193 | 194 | ```js 195 | //ハッシュロックの承認を確認した後、ボンデッドTXをアナウンス 196 | await txRepo.announceAggregateBonded(signedAggregateTx).toPromise(); 197 | ``` 198 | ボンデッドトランザクションがノードに取り込まれるとパーシャル署名状態となるので、8.ロックで紹介した連署を使用して、マルチシグアカウントで連署します。 199 | 連署をサポートするウォレットで承認することもできます。 200 | 201 | 202 | ## 9.4 マルチシグ送信の確認 203 | 204 | マルチシグで行った送信トランザクションの結果を確認してみます。 205 | 206 | ```js 207 | txInfo = await txRepo.getTransaction(signedTx.hash,sym.TransactionGroup.Confirmed).toPromise(); 208 | console.log(txInfo); 209 | ``` 210 | ###### 出力例 211 | ```js 212 | > AggregateTransaction 213 | > cosignatures: Array(2) 214 | 0: AggregateTransactionCosignature 215 | signature: "554F3C7017C32FD4FE67C1E5E35DD21D395D44742B43BD1EF99BC8E9576845CDC087B923C69DB2D86680279253F2C8A450F97CC7D3BCD6E86FE4E70135D44B06" 216 | signer: PublicAccount 217 | address: Address {address: 'TB3XP4GQK6XH2SSA2E2U6UWCESNACK566DS4COY', networkType: 152} 218 | publicKey: "A1BA266B56B21DC997D637BCC539CCFFA563ABCB34EAA52CF90005429F5CB39C" 219 | 1: AggregateTransactionCosignature 220 | signature: "AD753E23D3D3A4150092C13A410D5AB373B871CA74D1A723798332D70AD4598EC656F580CB281DB3EB5B9A7A1826BAAA6E060EEA3CC5F93644136E9B52006C05" 221 | signer: PublicAccount 222 | address: Address {address: 'TBAFGZOCB7OHZCCYYV64F2IFZL7SOOXNDHFS5NY', networkType: 152} 223 | publicKey: "B00721EDD76B24E3DDCA13555F86FC4BDA89D413625465B1BD7F347F74B82FF0" 224 | deadline: Deadline {adjustedValue: 12619660047} 225 | > innerTransactions: Array(1) 226 | > 0: TransferTransaction 227 | deadline: Deadline {adjustedValue: 12619660047} 228 | maxFee: UInt64 {lower: 48000, higher: 0} 229 | message: PlainMessage {type: 0, payload: 'test'} 230 | mosaics: [Mosaic] 231 | networkType: 152 232 | payloadSize: undefined 233 | recipientAddress: Address {address: 'TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ', networkType: 152} 234 | signature: "670EA8CFA4E35604DEE20877A6FC95C2786D748A8449CE7EEA7CB941FE5EC181175B0D6A08AF9E99955640C872DAD0AA68A37065C866EE1B651C3CE28BA95404" 235 | signer: PublicAccount 236 | address: Address {address: 'TCOMA5VG67TZH4X55HGZOXOFP7S232CYEQMOS7Q', networkType: 152} 237 | publicKey: "4667BC99B68B6CA0878CD499CE89CDEB7AAE2EE8EB96E0E8656386DECF0AD657" 238 | transactionInfo: AggregateTransactionInfo {height: UInt64, index: 0, id: '62600A8C0A21EB5CD28679A4', hash: undefined, merkleComponentHash: undefined, …} 239 | type: 16724 240 | maxFee: UInt64 {lower: 48000, higher: 0} 241 | networkType: 152 242 | payloadSize: 480 243 | signature: "670EA8CFA4E35604DEE20877A6FC95C2786D748A8449CE7EEA7CB941FE5EC181175B0D6A08AF9E99955640C872DAD0AA68A37065C866EE1B651C3CE28BA95404" 244 | > signer: PublicAccount 245 | address: Address {address: 'TCV67BMTD2JMDQOJUDQHBFJHQPG4DAKVKST3YJI', networkType: 152} 246 | publicKey: "FF9595FDCD983F46FF9AE0F7D86D94E9B164E385BD125202CF16528F53298656" 247 | > transactionInfo: 248 | hash: "AA99F8F4000F989E6F135228829DB66AEB3B3C4B1F06BA77D373D042EAA4C8DA" 249 | height: UInt64 {lower: 322376, higher: 0} 250 | id: "62600A8C0A21EB5CD28679A3" 251 | merkleComponentHash: "1FD6340BCFEEA138CC6305137566B0B1E98DEDE70E79CC933665FE93E10E0E3E" 252 | type: 16705 253 | ``` 254 | 255 | - マルチシグアカウント 256 | - Bob 257 | - AggregateTransaction.innerTransactions[0].signer.address 258 | - TCOMA5VG67TZH4X55HGZOXOFP7S232CYEQMOS7Q 259 | - 起案者アカウント 260 | - Carol1 261 | - AggregateTransaction.signer.address 262 | - TCV67BMTD2JMDQOJUDQHBFJHQPG4DAKVKST3YJI 263 | - 連署者アカウント 264 | - Carol2 265 | - AggregateTransaction.cosignatures[0].signer.address 266 | - TB3XP4GQK6XH2SSA2E2U6UWCESNACK566DS4COY 267 | - Carol3 268 | - AggregateTransaction.cosignatures[1].signer.address 269 | - TBAFGZOCB7OHZCCYYV64F2IFZL7SOOXNDHFS5NY 270 | 271 | ## 9.5 マルチシグ構成変更 272 | 273 | ### マルチシグ構成の縮小 274 | 275 | 連署者を減らすには除名対象アドレスに指定するとともに最小署名者数を連署者数が超えてしまわないように調整してトランザクションをアナウンスします。 276 | 除名対象者を連署者に含む必要はありません。 277 | 278 | ```js 279 | multisigTx = sym.MultisigAccountModificationTransaction.create( 280 | undefined, 281 | -1, //承認のために必要な最小署名者数増分 282 | -1, //除名のために必要な最小署名者数増分 283 | [], //追加対象アドレス 284 | [carol3.address],//除名対象アドレス 285 | networkType 286 | ); 287 | 288 | aggregateTx = sym.AggregateTransaction.createComplete( 289 | sym.Deadline.create(epochAdjustment), 290 | [ //構成変更したいマルチシグアカウントの公開鍵を指定 291 | multisigTx.toAggregate(bob.publicAccount), 292 | ], 293 | networkType,[] 294 | ).setMaxFeeForAggregate(100, 2); // 第二引数に連署者の数:2 295 | 296 | signedTx = aggregateTx.signTransactionWithCosignatories( 297 | carol1, 298 | [carol2,carol4], 299 | generationHash, 300 | ); 301 | await txRepo.announce(signedTx).toPromise(); 302 | ``` 303 | 304 | ### 連署者構成の差替え 305 | 306 | 連署者を差し替えるには、追加対象アドレスと除名対象アドレスを指定します。 307 | 新たに追加指定するアカウントの連署は必ず必要です。 308 | 309 | ```js 310 | multisigTx = sym.MultisigAccountModificationTransaction.create( 311 | undefined, 312 | 0, //承認のために必要な最小署名者数増分 313 | 0, //除名のために必要な最小署名者数増分 314 | [carol5.address], //追加対象アドレス 315 | [carol4.address], //除名対象アドレス 316 | networkType 317 | ); 318 | 319 | aggregateTx = sym.AggregateTransaction.createComplete( 320 | sym.Deadline.create(epochAdjustment), 321 | [ //構成変更したいマルチシグアカウントの公開鍵を指定 322 | multisigTx.toAggregate(bob.publicAccount), 323 | ], 324 | networkType,[] 325 | ).setMaxFeeForAggregate(100, 2); // 第二引数に連署者の数:2 326 | 327 | signedTx = aggregateTx.signTransactionWithCosignatories( 328 | carol1, //起案者 329 | [carol2,carol5], //連署者+承諾アカウント 330 | generationHash, 331 | ); 332 | await txRepo.announce(signedTx).toPromise(); 333 | ``` 334 | 335 | ## 9.6 現場で使えるヒント 336 | 337 | ### 多要素認証 338 | 339 | 秘密鍵の管理を複数の端末に分散させることができます。 340 | セキュリティ用の鍵を用意しておけば、紛失・漏洩時にも安全に回復することができます。 341 | また、マルチシグの安全運用については盗難時と紛失時の2パターンを検討しておく必要があるのでご注意ください。 342 | - 盗難時:ほかにも秘密鍵を使える人がいる。 343 | - 紛失時:だれもその秘密鍵を使えなくなる。 344 | 345 | 346 | ### アカウントの所有 347 | 348 | マルチシグ化したアカウントの秘密鍵は無効化し、マルチシグを解除しない限りたとえ秘密鍵を知っていたとしても 349 | モザイク送信などはできなくなります。 350 | モザイクの章で説明した通り、所有を「自分の意思で手放すことができる状態」だとすると、 351 | マルチシグ化したアカウントがもつモザイク等の所有者は連署者になります。 352 | また、Symbolではマルチシグの構成変更が可能ですのでアカウントの所有を他の連署者に安全に移転することができます。 353 | 354 | ### ワークフロー 355 | 356 | Symbolではマルチシグを3階層まで構成することができます(マルチレベルマルチシグ)。 357 | マルチレベルマルチシグを用いることで、バックアップ鍵を不正に持ち出して連署を完成させたり、承認者と監査役だけで署名を完成させるといったことを防ぐことができます。 358 | これによって、ブロックチェーン上にトランザクションが存在することが現実社会のワークフローなどの条件を満たした証拠として提示することができます。 359 | -------------------------------------------------------------------------------- /04_transaction.md: -------------------------------------------------------------------------------- 1 | # 4.トランザクション 2 | ブロックチェーン上のデータ更新はトランザクションをネットワークにアナウンスすることによって行います。 3 | 4 | ## 4.1 トランザクションのライフサイクル 5 | 6 | トランザクションを作成してから、改ざんが困難なデータとなるまでを順に説明します。 7 | 8 | - トランザクション作成 9 | - ブロックチェーンが受理できるフォーマットでトランザクションを作成します。 10 | - 署名 11 | - アカウントの秘密鍵でトランザクションを署名します。 12 | - アナウンス 13 | - 任意のノードに署名済みトランザクションを通知します。 14 | - 未承認トランザクション 15 | - ノードに受理されたトランザクションは、未承認トランザクションとして全ノードに伝播します 16 | - トランザクションに設定した最大手数料が、各ノード毎に設定されている最低手数料を満たさない場合はそのノードへは伝播しません。 17 | - 承認済みトランザクション 18 | - 約30秒に1度ごとに生成されるブロックに未承認トランザクションが取り込まれると、承認済みトランザクションとなります。 19 | - ロールバック 20 | - ノード間の合意に達することができずロールバックされたブロックに含まれていたトランザクションは、未承認トランザクションに差し戻されます。 21 | - 有効期限切れや、キャッシュからあふれたトランザクションは切り捨てられます。 22 | - ファイナライズ 23 | - 投票ノードによるファイナライズプロセスによりブロックが確定するとトランザクションはロールバック不可なデータとして扱うことができます。 24 | 25 | ### ブロックとは 26 | 27 | ブロックは約30秒ごとに生成され、高い手数料を支払ったトランザクションから優先に取り込まれ、ブロック単位で他のノードと同期します。 28 | 同期に失敗するとロールバックして、ネットワークが全体で合意が取れるまでこの作業を繰り返します。 29 | 30 | ## 4.2 トランザクション作成 31 | 32 | まずは最も基本的な転送トランザクションを作成してみます。 33 | 34 | ### Bobへの転送トランザクション 35 | 36 | 送信先のBobアドレスを作成しておきます。 37 | ```js 38 | bob = sym.Account.generateNewAccount(networkType); 39 | console.log(bob.address); 40 | ``` 41 | ```js 42 | > Address {address: 'TDWBA6L3CZ6VTZAZPAISL3RWM5VKMHM6J6IM3LY', networkType: 152} 43 | ``` 44 | 45 | トランザクションを作成します。 46 | ```js 47 | tx = sym.TransferTransaction.create( 48 | sym.Deadline.create(epochAdjustment), //Deadline:有効期限 49 | sym.Address.createFromRawAddress("TDWBA6L3CZ6VTZAZPAISL3RWM5VKMHM6J6IM3LY"), 50 | [], 51 | sym.PlainMessage.create("Hello Symbol!"), //メッセージ 52 | networkType //テストネット・メインネット区分 53 | ).setMaxFee(100); //手数料 54 | ``` 55 | 56 | 各設定項目について説明します。 57 | 58 | #### 有効期限 59 | sdkではデフォルトで2時間後に設定されます。 60 | 最大6時間まで指定可能です。 61 | ```js 62 | sym.Deadline.create(epochAdjustment,6) 63 | ``` 64 | 65 | #### メッセージ 66 | トランザクションに最大1023バイトのメッセージを添付することができます。 67 | バイナリデータであってもrawdataとして送信することが可能です。 68 | 69 | ##### 空メッセージ 70 | ```js 71 | sym.EmptyMessage 72 | ``` 73 | 74 | ##### 平文メッセージ 75 | ```js 76 | sym.PlainMessage.create("Hello Symbol!") 77 | ``` 78 | 79 | ##### 暗号文メッセージ 80 | ```js 81 | sym.EncryptedMessage('294C8979156C0D941270BAC191F7C689E93371EDBC36ADD8B920CF494012A97BA2D1A3759F9A6D55D5957E9D'); 82 | ``` 83 | 84 | EncryptedMessageを使用すると、「指定したメッセージが暗号化されています」という意味のフラグ(目印)がつきます。 85 | エクスプローラーやウォレットはそのフラグを参考にメッセージを無用にデコードしなかったり、非表示にしたりなどの処理を行います。 86 | このメソッドが暗号化をするわけではありません。 87 | 88 | ##### 生データ 89 | ```js 90 | sym.RawMessage.create(uint8Arrays[i]) 91 | ``` 92 | 93 | #### 最大手数料 94 | 95 | ネットワーク手数料については、常に少し多めに払っておけば問題はないのですが、最低限の知識は持っておく必要があります。 96 | アカウントはトランザクションを作成するときに、ここまでは手数料として払ってもいいという最大手数料を指定します。 97 | 一方で、ノードはその時々で最も高い手数料となるトランザクションのみブロックにまとめて収穫しようとします。 98 | つまり、多く払ってもいいというトランザクションが他に多く存在すると承認されるまでの時間が長くなります。 99 | 逆に、より少なく払いたいというトランザクションが多く存在し、その総額が大きい場合は、設定した最大額に満たない手数料額で送信が実現します。 100 | 101 | トランザクションサイズ x feeMultiprilerというもので決定されます。 102 | 176バイトだった場合 maxFee を100で設定すると 17600μXYM = 0.0176XYMを手数料として支払うことを許容します。 103 | feeMultiprier = 100として指定する方法とmaxFee = 17600 として指定する方法があります。 104 | 105 | ##### feeMultiprier = 100として指定する方法 106 | ```js 107 | tx = sym.TransferTransaction.create( 108 | ,,,, 109 | networkType 110 | ).setMaxFee(100); 111 | ``` 112 | 113 | ##### maxFee = 17600 として指定する方法 114 | ```js 115 | tx = sym.TransferTransaction.create( 116 | ,,,, 117 | networkType, 118 | sym.UInt64.fromUint(17600) 119 | ); 120 | ``` 121 | 122 | 本書では以後、feeMultiprier = 100として指定する方法で統一して説明します。 123 | 124 | ## 4.3 署名とアナウンス 125 | 126 | 作成したトランザクションを秘密鍵で署名して、任意のノードを通じてアナウンスします。 127 | 128 | ### 署名 129 | ```js 130 | signedTx = alice.sign(tx,generationHash); 131 | console.log(signedTx); 132 | ``` 133 | ###### 出力例 134 | ```js 135 | > SignedTransaction 136 | hash: "3BD00B0AF24DE70C7F1763B3FD64983C9668A370CB96258768B715B117D703C2" 137 | networkType: 152 138 | payload: 139 | "AE00000000000000CFC7A36C17060A937AFE1191BC7D77E33D81F3CC48DF9A0FFE892858DFC08C9911221543D687813ECE3D36836458D2569084298C09223F9899DF6ABD41028D0AD4933FC1E4C56F9DF9314E9E0533173E1AB727BDB2A04B59F048124E93BEFBD20000000001985441F843000000000000879E76C702000000986F4982FE77894ABC3EBFDC16DFD4A5C2C7BC05BFD44ECE0E000000000000000048656C6C6F2053796D626F6C21" 140 | signerPublicKey: "D4933FC1E4C56F9DF9314E9E0533173E1AB727BDB2A04B59F048124E93BEFBD2" 141 | type: 16724 142 | ``` 143 | 144 | トランザクションの署名にはAccountクラスとgenerationHash値が必要です。 145 | 146 | generationHash 147 | - テストネット 148 | - 49D6E1CE276A85B70EAFE52349AACCA389302E7A9754BCF1221E79494FC665A4 149 | - メインネット 150 | - 57F7DA205008026C776CB6AED843393F04CD458E0AA2D9F1D5F31A402072B2D6 151 | 152 | generationHash値はそのブロックチェーンネットワークを一意に識別するための値です。 153 | 同じ秘密鍵をもつ他のネットワークに使いまわされないようにそのネットワーク個別のハッシュ値を織り交ぜて署名済みトランザクションを作成します。 154 | 155 | 156 | ### アナウンス 157 | ```js 158 | res = await txRepo.announce(signedTx).toPromise(); 159 | console.log(res); 160 | ``` 161 | ```js 162 | > TransactionAnnounceResponse {message: 'packet 9 was pushed to the network via /transactions'} 163 | ``` 164 | 165 | 上記のスクリプトのように `packet n was pushed to the network` というレスポンスがあれば、トランザクションはノードに受理されたことになります。 166 | これはトランザクションのフォーマット等に異常が無かった程度の意味しかありません。 167 | Symbolではノードの応答速度を極限に高めるため、トランザクションの内容を検証するまえに受信結果の応答を返し接続を切断します。 168 | レスポンス値はこの情報を受け取ったにすぎません。フォーマットに異常があった場合は以下のようなメッセージ応答があります。 169 | 170 | ##### アナウンスに失敗した場合の応答例 171 | ```js 172 | Uncaught Error: {"statusCode":409,"statusMessage":"Unknown Error","body":"{\"code\":\"InvalidArgument\",\"message\":\"payload has an invalid format\"}"} 173 | ``` 174 | 175 | ## 4.4 確認 176 | 177 | 178 | ### ステータスの確認 179 | 180 | ノードに受理されたトランザクションのステータスを確認 181 | 182 | ```js 183 | tsRepo = repo.createTransactionStatusRepository(); 184 | transactionStatus = await tsRepo.getTransactionStatus(signedTx.hash).toPromise(); 185 | console.log(transactionStatus); 186 | ``` 187 | ###### 出力例 188 | ```js 189 | > TransactionStatus 190 | group: "confirmed" 191 | code: "Success" 192 | deadline: Deadline {adjustedValue: 11989512431} 193 | hash: "661360E61C37E156B0BE18E52C9F3ED1022DCE846A4609D72DF9FA8A5B667747" 194 | height: undefined 195 | ``` 196 | 197 | 承認されると ` group: "confirmed"`となっています。 198 | 199 | 受理されたものの、エラーが発生していた場合は以下のような出力となります。トランザクションを書き直して再度アナウンスしてみてください。 200 | 201 | ```js 202 | > TransactionStatus 203 | group: "failed" 204 | code: "Failure_Core_Insufficient_Balance" 205 | deadline: Deadline {adjustedValue: 11990156766} 206 | hash: "A82507C6C46DF444E36AC94391EA2D0D7DD1A218948DED465A7A4F9D1B53CA0E" 207 | height: undefined 208 | ``` 209 | 210 | 以下のようにResourceNotFoundエラーが発生した場合はトランザクションが受理されていません。 211 | ```js 212 | Uncaught Error: {"statusCode":404,"statusMessage":"Unknown Error","body":"{\"code\":\"ResourceNotFound\",\"message\":\"no resource exists with id '18AEBC9866CD1C15270F18738D577CB1BD4B2DF3EFB28F270B528E3FE583F42D'\"}"} 213 | ``` 214 | 215 | 考えられる可能性としては、トランザクションで指定した最大手数料が、ノードで設定された最低手数料に満たない場合や、 216 | アグリゲートトランザクションとしてアナウンスすることが求められているトランザクションを単体のトランザクションでアナウンスした場合に発生するようです。 217 | 218 | ### 承認確認 219 | 220 | トランザクションがブロックに承認されるまでに30秒程度かかります。 221 | 222 | #### エクスプローラーで確認 223 | signedTx.hash で取得できるハッシュ値を使ってエクスプローラーで検索してみましょう。 224 | 225 | ```js 226 | console.log(signedTx.hash); 227 | ``` 228 | ```js 229 | > "661360E61C37E156B0BE18E52C9F3ED1022DCE846A4609D72DF9FA8A5B667747" 230 | ``` 231 | 232 | - メインネット  233 | - https://symbol.fyi/transactions/661360E61C37E156B0BE18E52C9F3ED1022DCE846A4609D72DF9FA8A5B667747 234 | - テストネット  235 | - https://testnet.symbol.fyi/transactions/661360E61C37E156B0BE18E52C9F3ED1022DCE846A4609D72DF9FA8A5B667747 236 | 237 | #### SDKで確認 238 | 239 | ```js 240 | txInfo = await txRepo.getTransaction(signedTx.hash,sym.TransactionGroup.Confirmed).toPromise(); 241 | console.log(txInfo); 242 | ``` 243 | ###### 出力例 244 | ```js 245 | > TransferTransaction 246 | deadline: Deadline {adjustedValue: 12883929118} 247 | maxFee: UInt64 {lower: 17400, higher: 0} 248 | message: PlainMessage {type: 0, payload: 'Hello Symbol!'} 249 | mosaics: [] 250 | networkType: 152 251 | payloadSize: 174 252 | recipientAddress: Address {address: 'TDWBA6L3CZ6VTZAZPAISL3RWM5VKMHM6J6IM3LY', networkType: 152} 253 | signature: "7A3562DCD7FEE4EE9CB456E48EFEEC687647119DC053DE63581FD46CA9D16A829FA421B39179AABBF4DE0C1D987B58490E3F95C37327358E6E461832E3B3A60D" 254 | signer: PublicAccount {publicKey: '0E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26', address: Address} 255 | > transactionInfo: TransactionInfo 256 | hash: "DA4B672E68E6561EAE560FB89B144AFE1EF75D2BE0D9B6755D90388F8BCC4709" 257 | height: UInt64 {lower: 330012, higher: 0} 258 | id: "626413050A21EB5CD286E17D" 259 | index: 1 260 | merkleComponentHash: "DA4B672E68E6561EAE560FB89B144AFE1EF75D2BE0D9B6755D90388F8BCC4709" 261 | type: 16724 262 | version: 1 263 | ``` 264 | ##### 注意点 265 | 266 | トランザクションはブロックで承認されたとしても、ロールバックが発生するとトランザクションの承認が取り消される場合があります。 267 | ブロックが承認された後、数ブロックの承認が進むと、ロールバックの発生する確率は減少していきます。 268 | また、Votingノードの投票で実施されるファイナライズブロックを待つことで、記録されたデータは確実なものとなります。 269 | 270 | ##### スクリプト例 271 | トランザクションをアナウンスした後は以下のようなスクリプトを流すと、チェーンの状態を把握しやすくて便利です。 272 | ```js 273 | hash = signedTx.hash; 274 | tsRepo = repo.createTransactionStatusRepository(); 275 | transactionStatus = await tsRepo.getTransactionStatus(hash).toPromise(); 276 | console.log(transactionStatus); 277 | txInfo = await txRepo.getTransaction(hash,sym.TransactionGroup.Confirmed).toPromise(); 278 | console.log(txInfo); 279 | ``` 280 | 281 | ## 4.5トランザクション履歴 282 | 283 | Aliceが送受信したトランザクション履歴を一覧で取得します。 284 | ```js 285 | result = await txRepo.search( 286 | { 287 | group:sym.TransactionGroup.Confirmed, 288 | embedded:true, 289 | address:alice.address 290 | } 291 | ).toPromise(); 292 | 293 | txes = result.data; 294 | txes.forEach(tx => { 295 | console.log(tx); 296 | }) 297 | ``` 298 | ###### 出力例 299 | ```js 300 | > TransferTransaction 301 | type: 16724 302 | networkType: 152 303 | payloadSize: 176 304 | deadline: Deadline {adjustedValue: 11905303680} 305 | maxFee: UInt64 {lower: 200000000, higher: 0} 306 | recipientAddress: Address {address: 'TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ', networkType: 152} 307 | signature: "E5924A1EB653240A7220405A4DD4E221E71E43327B3BA691D267326FEE3F57458E8721907188DB33A3F2A9CB1D0293845B4D0F1D7A93C8A3389262D1603C7108" 308 | signer: PublicAccount {publicKey: 'BDFAF3B090270920A30460AA943F9D8D4FCFF6741C2CB58798DBF7A2ED6B75AB', address: Address} 309 | > message: RawMessage 310 | payload: "" 311 | type: -1 312 | > mosaics: Array(1) 313 | 0: Mosaic 314 | amount: UInt64 {lower: 10000000, higher: 0} 315 | id: MosaicId 316 | id: Id {lower: 760461000, higher: 981735131} 317 | > transactionInfo: TransactionInfo 318 | hash: "308472D34BE1A58B15A83B9684278010F2D69B59E39127518BE38A4D22EEF31D" 319 | height: UInt64 {lower: 301717, higher: 0} 320 | id: "6255242053E0E706653116F9" 321 | index: 0 322 | merkleComponentHash: "308472D34BE1A58B15A83B9684278010F2D69B59E39127518BE38A4D22EEF31D" 323 | ``` 324 | 325 | TransactionTypeは以下の通りです。 326 | ```js 327 | {0: 'RESERVED', 16705: 'AGGREGATE_COMPLETE', 16707: 'VOTING_KEY_LINK', 16708: 'ACCOUNT_METADATA', 16712: 'HASH_LOCK', 16716: 'ACCOUNT_KEY_LINK', 16717: 'MOSAIC_DEFINITION', 16718: 'NAMESPACE_REGISTRATION', 16720: 'ACCOUNT_ADDRESS_RESTRICTION', 16721: 'MOSAIC_GLOBAL_RESTRICTION', 16722: 'SECRET_LOCK', 16724: 'TRANSFER', 16725: 'MULTISIG_ACCOUNT_MODIFICATION', 16961: 'AGGREGATE_BONDED', 16963: 'VRF_KEY_LINK', 16964: 'MOSAIC_METADATA', 16972: 'NODE_KEY_LINK', 16973: 'MOSAIC_SUPPLY_CHANGE', 16974: 'ADDRESS_ALIAS', 16976: 'ACCOUNT_MOSAIC_RESTRICTION', 16977: 'MOSAIC_ADDRESS_RESTRICTION', 16978: 'SECRET_PROOF', 17220: 'NAMESPACE_METADATA', 17229: 'MOSAIC_SUPPLY_REVOCATION', 17230: 'MOSAIC_ALIAS', 17232: 'ACCOUNT_OPERATION_RESTRICTION' 328 | ``` 329 | 330 | MessageTypeは以下の通りです。 331 | ```js 332 | {0: 'PlainMessage', 1: 'EncryptedMessage', 254: 'PersistentHarvestingDelegationMessage', -1: 'RawMessage'} 333 | ``` 334 | ## 4.6 アグリゲートトランザクション 335 | 336 | Symbolでは複数のトランザクションを1ブロックにまとめてアナウンスすることができます。 337 | 最大で100件のトランザクションをまとめることができます(連署者が異なる場合は25アカウントまでを連署指定可能)。 338 | 以降の章で扱う内容にアグリゲートトランザクションへの理解が必要な機能が含まれますので、 339 | 本章ではアグリゲートトランザクションのうち、簡単なものだけを紹介します。 340 | ### 起案者の署名だけが必要な場合 341 | 342 | ```js 343 | bob = sym.Account.generateNewAccount(networkType); 344 | carol = sym.Account.generateNewAccount(networkType); 345 | 346 | innerTx1 = sym.TransferTransaction.create( 347 | undefined, //Deadline 348 | bob.address, //送信先 349 | [], 350 | sym.PlainMessage.create("tx1"), 351 | networkType 352 | ); 353 | 354 | innerTx2 = sym.TransferTransaction.create( 355 | undefined, //Deadline 356 | carol.address, //送信先 357 | [], 358 | sym.PlainMessage.create("tx2"), 359 | networkType 360 | ); 361 | 362 | aggregateTx = sym.AggregateTransaction.createComplete( 363 | sym.Deadline.create(epochAdjustment), 364 | [ 365 | innerTx1.toAggregate(alice.publicAccount), //送信元アカウントの公開鍵 366 | innerTx2.toAggregate(alice.publicAccount) //送信元アカウントの公開鍵 367 | ], 368 | networkType, 369 | [], 370 | sym.UInt64.fromUint(1000000) 371 | ); 372 | signedTx = alice.sign(aggregateTx,generationHash); 373 | await txRepo.announce(signedTx).toPromise(); 374 | ``` 375 | 376 | まず、アグリゲートトランザクションに含めるトランザクションを作成します。 377 | このときDeadlineを指定する必要はありません。 378 | リスト化するときに、生成したトランザクションにtoAggregateを追加して送信元アカウントの公開鍵を指定します。 379 | ちなみに送信元アカウントと署名アカウントが **必ずしも一致するとは限りません** 。 380 | 後の章での解説で「Bobの送信トランザクションをAliceが署名する」といった事が起こり得るためこのような書き方をします。 381 | これはSymbolブロックチェーンでトランザクションを扱ううえで最も重要な概念になります。 382 | なお、本章で扱うトランザクションは同じAliceですので、アグリゲートボンデッドトランザクションへの署名もAliceを指定します。 383 | 384 | ## 4.7 現場で使えるヒント 385 | 386 | ### 存在証明 387 | 388 | アカウントの章でアカウントによるデータの署名と検証する方法について説明しました。 389 | このデータをトランザクションに載せてブロックチェーンが承認することで、 390 | アカウントがある時刻にあるデータの存在を認知したことを消すことができなくなります。 391 | タイムスタンプの刻印された電子署名を利害関係者間で所有することと同じ意味があると考えることもできます。 392 | (法律的な判断は他の方にお任せします) 393 | 394 | ブロックチェーンは、この消せない「アカウントが認知したという事実」の存在をもって送信などのデータ更新を行います。 395 | また、誰もがまだ知らないはずの事実を知っていたことの証明としてブロックチェーンを利用することもできます。 396 | ここでは、その存在が証明されたデータをトランザクションに載せる2つの方法について説明します。 397 | 398 | #### デジタルデータのハッシュ値(SHA256)出力方法 399 | 400 | ファイルの要約値をブロックチェーンに記録することでそのファイルの存在を証明することができます。 401 | 402 | 各OSごとのファイルのSHA256でハッシュ値を計算する方法は下記の通りです。 403 | ```sh 404 | #Windows 405 | certutil -hashfile WINファイルパス SHA256 406 | #Mac 407 | shasum -a 256 MACファイルパス 408 | #Linux 409 | sha256sum Linuxファイルパス 410 | ``` 411 | 412 | #### 大きなデータの分割 413 | 414 | トランザクションのペイロードには1023バイトしか格納できないため、 415 | 大きなデータは分割してペイロードに詰め込んでアグリゲートトランザクションにします。 416 | 417 | ```js 418 | bigdata = 'C00200000000000093B0B985101C1BDD1BC2BF30D72F35E34265B3F381ECA464733E147A4F0A6B9353547E2E08189EF37E50D271BEB5F09B81CE5816BB34A153D2268520AF630A0A0E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26000000000198414140770200000000002A769FB40000000076B455CFAE2CCDA9C282BF8556D3E9C9C0DE18B0CBE6660ACCF86EB54AC51B33B001000000000000DB000000000000000E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26000000000198544198205C1A4CE06C45B3A896B1B2360E03633B9F36BF7F22338B000000000000000066653465353435393833444430383935303645394533424446434235313637433046394232384135344536463032413837364535303734423641303337414643414233303344383841303630353343353345354235413835323835443639434132364235343233343032364244444331443133343139464435353438323930334242453038423832304100000000006800000000000000B2D4FD84B2B63A96AA37C35FC6E0A2341CEC1FD19C8FFC8D93CCCA2B028D1E9D000000000198444198205C1A4CE06C45B3A896B1B2360E03633B9F36BF7F2233BC089179EBBE01A81400140035383435344434373631364336433635373237396800000000000000B2D4FD84B2B63A96AA37C35FC6E0A2341CEC1FD19C8FFC8D93CCCA2B028D1E9D000000000198444198205C1A4CE06C45B3A896B1B2360E03633B9F36BF7F223345ECB996EDDB9BEB1400140035383435344434373631364336433635373237390000000000000000B2D4FD84B2B63A96AA37C35FC6E0A2341CEC1FD19C8FFC8D93CCCA2B028D1E9D5A71EBA9C924EFA146897BE6C9BB3DACEFA26A07D687AC4A83C9B03087640E2D1DDAE952E9DDBC33312E2C8D021B4CC0435852C0756B1EBD983FCE221A981D02'; 419 | 420 | let payloads = []; 421 | for (let i = 0; i < bigdata.length / 1023; i++) { 422 | payloads.push(bigdata.substr(i * 1023, 1023)); 423 | } 424 | console.log(payloads); 425 | ``` 426 | -------------------------------------------------------------------------------- /13_verify.md: -------------------------------------------------------------------------------- 1 | # 13.検証 2 | ブロックチェーン上に記録されたさまざまな情報を検証します。 3 | ブロックチェーンへのデータ記録は全ノードの合意を持って行われますが、 4 | ブロックチェーンへの**データ参照**はノード単体からの情報取得であるため、 5 | 信用できないノードの情報を元にして新たな取引を行いたい場合は、ノードから取得したデータに対して検証を行う必要があります。 6 | 7 | 8 | ## 13.1 トランザクションの検証 9 | 10 | トランザクションがブロックヘッダーに含まれていることを検証します。この検証が成功すれば、トランザクションがブロックチェーンの合意によって承認されたものとみなすことができます。 11 | 12 | 本章のサンプルスクリプトを実行する前に以下を実行して必要ライブラリを読み込んでおいてください。 13 | ```js 14 | Buffer = require("/node_modules/buffer").Buffer; 15 | cat = require("/node_modules/catbuffer-typescript"); 16 | sha3_256 = require('/node_modules/js-sha3').sha3_256; 17 | 18 | accountRepo = repo.createAccountRepository(); 19 | blockRepo = repo.createBlockRepository(); 20 | stateProofService = new sym.StateProofService(repo); 21 | ``` 22 | 23 | ### 検証するペイロード 24 | 25 | 今回検証するトランザクションペイロードとそのトランザクションが記録されているとされるブロック高です。 26 | 27 | ```js 28 | payload = 'C00200000000000093B0B985101C1BDD1BC2BF30D72F35E34265B3F381ECA464733E147A4F0A6B9353547E2E08189EF37E50D271BEB5F09B81CE5816BB34A153D2268520AF630A0A0E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26000000000198414140770200000000002A769FB40000000076B455CFAE2CCDA9C282BF8556D3E9C9C0DE18B0CBE6660ACCF86EB54AC51B33B001000000000000DB000000000000000E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26000000000198544198205C1A4CE06C45B3A896B1B2360E03633B9F36BF7F22338B000000000000000066653465353435393833444430383935303645394533424446434235313637433046394232384135344536463032413837364535303734423641303337414643414233303344383841303630353343353345354235413835323835443639434132364235343233343032364244444331443133343139464435353438323930334242453038423832304100000000006800000000000000B2D4FD84B2B63A96AA37C35FC6E0A2341CEC1FD19C8FFC8D93CCCA2B028D1E9D000000000198444198205C1A4CE06C45B3A896B1B2360E03633B9F36BF7F2233BC089179EBBE01A81400140035383435344434373631364336433635373237396800000000000000B2D4FD84B2B63A96AA37C35FC6E0A2341CEC1FD19C8FFC8D93CCCA2B028D1E9D000000000198444198205C1A4CE06C45B3A896B1B2360E03633B9F36BF7F223345ECB996EDDB9BEB1400140035383435344434373631364336433635373237390000000000000000B2D4FD84B2B63A96AA37C35FC6E0A2341CEC1FD19C8FFC8D93CCCA2B028D1E9D5A71EBA9C924EFA146897BE6C9BB3DACEFA26A07D687AC4A83C9B03087640E2D1DDAE952E9DDBC33312E2C8D021B4CC0435852C0756B1EBD983FCE221A981D02'; 29 | height = 59639; 30 | ``` 31 | 32 | 33 | ### payload確認 34 | 35 | トランザクションの内容を確認します。 36 | 37 | ```js 38 | tx = sym.TransactionMapping.createFromPayload(payload); 39 | hash = sym.Transaction.createTransactionHash(payload,Buffer.from(generationHash, 'hex')); 40 | console.log(hash); 41 | console.log(tx); 42 | ``` 43 | ###### 出力例 44 | ```js 45 | > 257E2CAECF4B477235CA93C37090E8BE58B7D3812A012E39B7B55BA7D7FFCB20 46 | > AggregateTransaction 47 | > cosignatures: Array(1) 48 | 0: AggregateTransactionCosignature 49 | signature: "5A71EBA9C924EFA146897BE6C9BB3DACEFA26A07D687AC4A83C9B03087640E2D1DDAE952E9DDBC33312E2C8D021B4CC0435852C0756B1EBD983FCE221A981D02" 50 | signer: PublicAccount 51 | address: Address {address: 'TAQFYGSM4BWELM5IS2Y3ENQOANRTXHZWX57SEMY', networkType: 152} 52 | publicKey: "B2D4FD84B2B63A96AA37C35FC6E0A2341CEC1FD19C8FFC8D93CCCA2B028D1E9D" 53 | deadline: Deadline {adjustedValue: 3030349354} 54 | > innerTransactions: Array(3) 55 | 0: TransferTransaction {type: 16724, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64, …} 56 | 1: AccountMetadataTransaction {type: 16708, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64, …} 57 | 2: AccountMetadataTransaction {type: 16708, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64, …} 58 | maxFee: UInt64 {lower: 161600, higher: 0} 59 | networkType: 152 60 | signature: "93B0B985101C1BDD1BC2BF30D72F35E34265B3F381ECA464733E147A4F0A6B9353547E2E08189EF37E50D271BEB5F09B81CE5816BB34A153D2268520AF630A0A" 61 | > signer: PublicAccount 62 | address: Address {address: 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ', networkType: 152} 63 | publicKey: "0E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26" 64 | transactionInfo: undefined 65 | type: 16705 66 | ``` 67 | 68 | ### 署名者の検証 69 | 70 | トランザクションがブロックに含まれていることが確認できれば自明ですが、 71 | 念のため、アカウントの公開鍵でトランザクションの署名を検証しておきます。 72 | 73 | ```js 74 | res = alice.publicAccount.verifySignature( 75 | tx.getSigningBytes([...Buffer.from(payload,'hex')],[...Buffer.from(generationHash,'hex')]), 76 | "93B0B985101C1BDD1BC2BF30D72F35E34265B3F381ECA464733E147A4F0A6B9353547E2E08189EF37E50D271BEB5F09B81CE5816BB34A153D2268520AF630A0A" 77 | ); 78 | console.log(res); 79 | ``` 80 | ```js 81 | > true 82 | ``` 83 | 84 | getSigningBytesで署名の対象となる部分だけを取り出しています。 85 | 通常のトランザクションとアグリゲートトランザクションでは取り出す部分が異なるので注意が必要です。 86 | 87 | ### マークルコンポーネントハッシュの計算 88 | 89 | トランザクションのハッシュ値には連署者の情報が含まれていません。 90 | 一方でブロックヘッダーに格納されるマークルルートはトランザクションのハッシュに連署者の情報が含めたものが格納されます。 91 | そのためトランザクションがブロック内部に存在しているかどうかを検証する場合は、トランザクションハッシュをマークルコンポーネントハッシュに変換しておく必要があります。 92 | 93 | ```js 94 | merkleComponentHash = hash; 95 | if( tx.cosignatures !== undefined && tx.cosignatures.length > 0){ 96 | 97 | const hasher = sha3_256.create(); 98 | hasher.update(Buffer.from(hash, 'hex')); 99 | for (cosignature of tx.cosignatures ){ 100 | hasher.update(Buffer.from(cosignature.signer.publicKey, 'hex')); 101 | } 102 | merkleComponentHash = hasher.hex().toUpperCase(); 103 | } 104 | console.log(merkleComponentHash); 105 | ``` 106 | ```js 107 | > C8D1335F07DE05832B702CACB85B8EDAC2F3086543C76C9F56F99A0861E8F235 108 | ``` 109 | 110 | ### InBlockの検証 111 | 112 | ノードからマークルツリーを取得し、先ほど計算したmerkleComponentHashからブロックヘッダーのマークルルートが導出できることを確認します。 113 | 114 | ```js 115 | function validateTransactionInBlock(leaf,HRoot,merkleProof){ 116 | 117 | if (merkleProof.length === 0) { 118 | // There is a single item in the tree, so HRoot' = leaf. 119 | return leaf.toUpperCase() === HRoot.toUpperCase(); 120 | } 121 | 122 | const HRoot0 = merkleProof.reduce((proofHash, pathItem) => { 123 | const hasher = sha3_256.create(); 124 | if (pathItem.position === sym.MerklePosition.Left) { 125 | return hasher.update(Buffer.from(pathItem.hash + proofHash, 'hex')).hex(); 126 | } else { 127 | return hasher.update(Buffer.from(proofHash + pathItem.hash, 'hex')).hex(); 128 | } 129 | }, leaf); 130 | return HRoot.toUpperCase() === HRoot0.toUpperCase(); 131 | } 132 | 133 | //トランザクションから計算 134 | leaf = merkleComponentHash.toLowerCase();//merkleComponentHash 135 | 136 | //ノードから取得 137 | HRoot = (await blockRepo.getBlockByHeight(height).toPromise()).blockTransactionsHash; 138 | merkleProof = (await blockRepo.getMerkleTransaction(height, leaf).toPromise()).merklePath; 139 | 140 | result = validateTransactionInBlock(leaf,HRoot,merkleProof); 141 | console.log(result); 142 | ``` 143 | ```js 144 | > true 145 | ``` 146 | 147 | トランザクションの情報がブロックヘッダーに含まれていることが確認できました。 148 | 149 | ## 13.2 ブロックヘッダーの検証 150 | 151 | 既知のブロックハッシュ値(例:ファイナライズブロック)から、検証中のブロックヘッダーまでたどれることを検証します。 152 | 153 | 154 | ### normalブロックの検証 155 | 156 | ```js 157 | block = await blockRepo.getBlockByHeight(height).toPromise(); 158 | previousBlock = await blockRepo.getBlockByHeight(height - 1).toPromise(); 159 | if(block.type === sym.BlockType.NormalBlock){ 160 | 161 | hasher = sha3_256.create(); 162 | hasher.update(Buffer.from(block.signature,'hex')); //signature 163 | hasher.update(Buffer.from(block.signer.publicKey,'hex')); //publicKey 164 | hasher.update(cat.GeneratorUtils.uintToBuffer( block.version, 1)); 165 | hasher.update(cat.GeneratorUtils.uintToBuffer( block.networkType, 1)); 166 | hasher.update(cat.GeneratorUtils.uintToBuffer( block.type, 2)); 167 | hasher.update(cat.GeneratorUtils.uint64ToBuffer([block.height.lower ,block.height.higher])); 168 | hasher.update(cat.GeneratorUtils.uint64ToBuffer([block.timestamp.lower ,block.timestamp.higher])); 169 | hasher.update(cat.GeneratorUtils.uint64ToBuffer([block.difficulty.lower,block.difficulty.higher])); 170 | hasher.update(Buffer.from(block.proofGamma,'hex')); 171 | hasher.update(Buffer.from(block.proofVerificationHash,'hex')); 172 | hasher.update(Buffer.from(block.proofScalar,'hex')); 173 | hasher.update(Buffer.from(previousBlock.hash,'hex')); 174 | hasher.update(Buffer.from(block.blockTransactionsHash,'hex')); 175 | hasher.update(Buffer.from(block.blockReceiptsHash,'hex')); 176 | hasher.update(Buffer.from(block.stateHash,'hex')); 177 | hasher.update(sym.RawAddress.stringToAddress(block.beneficiaryAddress.address)); 178 | hasher.update(cat.GeneratorUtils.uintToBuffer( block.feeMultiplier, 4)); 179 | hash = hasher.hex().toUpperCase(); 180 | console.log(hash === block.hash); 181 | } 182 | ``` 183 | 184 | true が出力されればこのブロックハッシュは前ブロックハッシュ値の存在を認知していることになります。 185 | 同様にしてn番目のブロックがn-1番目のブロックを存在を確認し、最後に検証中のブロックにたどり着きます。 186 | 187 | これで、どのノードに問い合わせても確認可能な既知のファイナライズブロックが、 188 | 検証したいブロックの存在に支えられていることが分かりました。 189 | 190 | ### importanceブロックの検証 191 | 192 | importanceBlockは、importance値の再計算が行われるブロック(720ブロック毎、テストネットは180ブロック毎)です。 193 | NormalBlockに加えて以下の情報が追加されています。 194 | 195 | - votingEligibleAccountsCount 196 | - harvestingEligibleAccountsCount 197 | - totalVotingBalance 198 | - previousImportanceBlockHash 199 | 200 | ```js 201 | block = await blockRepo.getBlockByHeight(height).toPromise(); 202 | previousBlock = await blockRepo.getBlockByHeight(height - 1).toPromise(); 203 | if(block.type === sym.BlockType.ImportanceBlock){ 204 | 205 | hasher = sha3_256.create(); 206 | hasher.update(Buffer.from(block.signature,'hex')); //signature 207 | hasher.update(Buffer.from(block.signer.publicKey,'hex')); //publicKey 208 | hasher.update(cat.GeneratorUtils.uintToBuffer( block.version, 1)); 209 | hasher.update(cat.GeneratorUtils.uintToBuffer( block.networkType, 1)); 210 | hasher.update(cat.GeneratorUtils.uintToBuffer( block.type, 2)); 211 | hasher.update(cat.GeneratorUtils.uint64ToBuffer([block.height.lower ,block.height.higher])); 212 | hasher.update(cat.GeneratorUtils.uint64ToBuffer([block.timestamp.lower ,block.timestamp.higher])); 213 | hasher.update(cat.GeneratorUtils.uint64ToBuffer([block.difficulty.lower,block.difficulty.higher])); 214 | hasher.update(Buffer.from(block.proofGamma,'hex')); 215 | hasher.update(Buffer.from(block.proofVerificationHash,'hex')); 216 | hasher.update(Buffer.from(block.proofScalar,'hex')); 217 | hasher.update(Buffer.from(previousBlock.hash,'hex')); 218 | hasher.update(Buffer.from(block.blockTransactionsHash,'hex')); 219 | hasher.update(Buffer.from(block.blockReceiptsHash,'hex')); 220 | hasher.update(Buffer.from(block.stateHash,'hex')); 221 | hasher.update(sym.RawAddress.stringToAddress(block.beneficiaryAddress.address)); 222 | hasher.update(cat.GeneratorUtils.uintToBuffer( block.feeMultiplier, 4)); 223 | hasher.update(cat.GeneratorUtils.uintToBuffer(block.votingEligibleAccountsCount,4)); 224 | hasher.update(cat.GeneratorUtils.uint64ToBuffer([block.harvestingEligibleAccountsCount.lower,block.harvestingEligibleAccountsCount.higher])); 225 | hasher.update(cat.GeneratorUtils.uint64ToBuffer([block.totalVotingBalance.lower,block.totalVotingBalance.higher])); 226 | hasher.update(Buffer.from(block.previousImportanceBlockHash,'hex')); 227 | 228 | hash = hasher.hex().toUpperCase(); 229 | console.log(hash === block.hash); 230 | } 231 | ``` 232 | 233 | 後述するアカウントやメタデータの検証のために、stateHashSubCacheMerkleRootsを検証しておきます。 234 | 235 | ### stateHashの検証 236 | ```js 237 | console.log(block); 238 | ``` 239 | ```js 240 | > NormalBlockInfo 241 | height: UInt64 {lower: 59639, higher: 0} 242 | hash: "B5F765D388B5381AC93659F501D5C68C00A2EE7DF4548C988E97F809B279839B" 243 | stateHash: "9D6801C49FE0C31ADE5C1BB71019883378016FA35230B9813CA6BB98F7572758" 244 | > stateHashSubCacheMerkleRoots: Array(9) 245 | 0: "4578D33DD0ED5B8563440DA88F627BBC95A174C183191C15EE1672C5033E0572" 246 | 1: "2C76DAD84E4830021BE7D4CF661218973BA467741A1FC4663B54B5982053C606" 247 | 2: "259FB9565C546BAD0833AD2B5249AA54FE3BC45C9A0C64101888AC123A156D04" 248 | 3: "58D777F0AA670440D71FA859FB51F8981AF1164474840C71C1BEB4F7801F1B27" 249 | 4: "C9092F0652273166991FA24E8B115ACCBBD39814B8820A94BFBBE3C433E01733" 250 | 5: "4B53B8B0E5EE1EEAD6C1498CCC1D839044B3AE5F85DD8C522A4376C2C92D8324" 251 | 6: "132324AF5536EC9AA85B2C1697F6B357F05EAFC130894B210946567E4D4E9519" 252 | 7: "8374F46FBC759049F73667265394BD47642577F16E0076CBB7B0B9A92AAE0F8E" 253 | 8: "45F6AC48E072992343254F440450EF4E840D8386102AD161B817E9791ABC6F7F" 254 | ``` 255 | ```js 256 | hasher = sha3_256.create(); 257 | hasher.update(Buffer.from(block.stateHashSubCacheMerkleRoots[0],'hex')); //AccountState 258 | hasher.update(Buffer.from(block.stateHashSubCacheMerkleRoots[1],'hex')); //Namespace 259 | hasher.update(Buffer.from(block.stateHashSubCacheMerkleRoots[2],'hex')); //Mosaic 260 | hasher.update(Buffer.from(block.stateHashSubCacheMerkleRoots[3],'hex')); //Multisig 261 | hasher.update(Buffer.from(block.stateHashSubCacheMerkleRoots[4],'hex')); //HashLockInfo 262 | hasher.update(Buffer.from(block.stateHashSubCacheMerkleRoots[5],'hex')); //SecretLockInfo 263 | hasher.update(Buffer.from(block.stateHashSubCacheMerkleRoots[6],'hex')); //AccountRestriction 264 | hasher.update(Buffer.from(block.stateHashSubCacheMerkleRoots[7],'hex')); //MosaicRestriction 265 | hasher.update(Buffer.from(block.stateHashSubCacheMerkleRoots[8],'hex')); //Metadata 266 | hash = hasher.hex().toUpperCase(); 267 | console.log(block.stateHash === hash); 268 | ``` 269 | ```js 270 | > true 271 | ``` 272 | 273 | ブロックヘッダーの検証に利用した9個のstateがstateHashSubCacheMerkleRootsから構成されていることがわかります。 274 | 275 | 276 | ## 13.3 アカウント・メタデータの検証 277 | 278 | マークルパトリシアツリーを利用して、トランザクションに紐づくアカウントやメタデータの存在を検証します。 279 | サービス提供者がマークルパトリシアツリーを提供すれば、利用者は自分の意志で選択したノードを使ってその真偽を検証することができます。 280 | 281 | ### 検証用共通関数 282 | 283 | ```js 284 | //葉のハッシュ値取得関数 285 | function getLeafHash(encodedPath, leafValue){ 286 | const hasher = sha3_256.create(); 287 | return hasher.update(sym.Convert.hexToUint8(encodedPath + leafValue)).hex().toUpperCase(); 288 | } 289 | 290 | //枝のハッシュ値取得関数 291 | function getBranchHash(encodedPath, links){ 292 | const branchLinks = Array(16).fill(sym.Convert.uint8ToHex(new Uint8Array(32))); 293 | links.forEach((link) => { 294 | branchLinks[parseInt(`0x${link.bit}`, 16)] = link.link; 295 | }); 296 | const hasher = sha3_256.create(); 297 | const bHash = hasher.update(sym.Convert.hexToUint8(encodedPath + branchLinks.join(''))).hex().toUpperCase(); 298 | return bHash; 299 | } 300 | 301 | //ワールドステートの検証 302 | function checkState(stateProof,stateHash,pathHash,rootHash){ 303 | 304 | const merkleLeaf = stateProof.merkleTree.leaf; 305 | const merkleBranches = stateProof.merkleTree.branches.reverse(); 306 | const leafHash = getLeafHash(merkleLeaf.encodedPath,stateHash); 307 | 308 | let linkHash = leafHash; //最初のlinkHashはleafHash 309 | let bit=""; 310 | for(let i = 0; i < merkleBranches.length; i++){ 311 | const branch = merkleBranches[i]; 312 | const branchLink = branch.links.find(x=>x.link === linkHash) 313 | linkHash = getBranchHash(branch.encodedPath,branch.links); 314 | bit = merkleBranches[i].path.slice(0,merkleBranches[i].nibbleCount) + branchLink.bit + bit ; 315 | } 316 | 317 | const treeRootHash = linkHash; //最後のlinkHashはrootHash 318 | let treePathHash = bit + merkleLeaf.path; 319 | 320 | if(treePathHash.length % 2 == 1){ 321 | treePathHash = treePathHash.slice( 0, -1 ); 322 | } 323 | 324 | //検証 325 | console.log(treeRootHash === rootHash); 326 | console.log(treePathHash === pathHash); 327 | } 328 | ``` 329 | 330 | 331 | ### 13.3.1 アカウント情報の検証 332 | 333 | アカウント情報を葉として、 334 | マークルツリー上の分岐する枝をアドレスでたどり、 335 | ルートに到着できるかを確認します。 336 | 337 | ```js 338 | stateProofService = new sym.StateProofService(repo); 339 | 340 | aliceAddress = sym.Address.createFromRawAddress("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ"); 341 | 342 | hasher = sha3_256.create(); 343 | alicePathHash = hasher.update( 344 | sym.RawAddress.stringToAddress(aliceAddress.plain()) 345 | ).hex().toUpperCase(); 346 | 347 | hasher = sha3_256.create(); 348 | aliceInfo = await accountRepo.getAccountInfo(aliceAddress).toPromise(); 349 | aliceStateHash = hasher.update(aliceInfo.serialize()).hex().toUpperCase(); 350 | 351 | //サービス提供者以外のノードから最新のブロックヘッダー情報を取得 352 | blockInfo = await blockRepo.search({order:"desc"}).toPromise(); 353 | rootHash = blockInfo.data[0].stateHashSubCacheMerkleRoots[0]; 354 | 355 | //サービス提供者を含む任意のノードからマークル情報を取得 356 | stateProof = await stateProofService.accountById(aliceAddress).toPromise(); 357 | 358 | //検証 359 | checkState(stateProof,aliceStateHash,alicePathHash,rootHash); 360 | ``` 361 | 362 | 363 | ### 13.3.2 モザイクへ登録したメタデータの検証 364 | 365 | モザイクに登録したメタデータValue値を葉として、 366 | マークルツリー上の分岐する枝をメタデータキーで構成されるハッシュ値でたどり、 367 | ルートに到着できるかを確認します。 368 | 369 | ```js 370 | srcAddress = Buffer.from( 371 | sym.Address.createFromRawAddress("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ").encoded(), 372 | 'hex' 373 | ) 374 | 375 | targetAddress = Buffer.from( 376 | sym.Address.createFromRawAddress("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ").encoded(), 377 | 'hex' 378 | ) 379 | 380 | hasher = sha3_256.create(); 381 | hasher.update(srcAddress); 382 | hasher.update(targetAddress); 383 | hasher.update(sym.Convert.hexToUint8Reverse("CF217E116AA422E2")); // scopeKey 384 | hasher.update(sym.Convert.hexToUint8Reverse("1275B0B7511D9161")); // targetId 385 | hasher.update(Uint8Array.from([sym.MetadataType.Mosaic])); // type: Mosaic 1 386 | compositeHash = hasher.hex(); 387 | 388 | hasher = sha3_256.create(); 389 | hasher.update( Buffer.from(compositeHash,'hex')); 390 | 391 | pathHash = hasher.hex().toUpperCase(); 392 | 393 | //stateHash(Value値) 394 | hasher = sha3_256.create(); 395 | hasher.update(cat.GeneratorUtils.uintToBuffer(1, 2)); //version 396 | hasher.update(srcAddress); 397 | hasher.update(targetAddress); 398 | hasher.update(sym.Convert.hexToUint8Reverse("CF217E116AA422E2")); // scopeKey 399 | hasher.update(sym.Convert.hexToUint8Reverse("1275B0B7511D9161")); // targetId 400 | hasher.update(Uint8Array.from([sym.MetadataType.Mosaic])); //mosaic 401 | 402 | value = Buffer.from("test"); 403 | 404 | hasher.update(cat.GeneratorUtils.uintToBuffer(value.length, 2)); 405 | hasher.update(value); 406 | stateHash = hasher.hex(); 407 | 408 | //サービス提供者以外のノードから最新のブロックヘッダー情報を取得 409 | blockInfo = await blockRepo.search({order:"desc"}).toPromise(); 410 | rootHash = blockInfo.data[0].stateHashSubCacheMerkleRoots[8]; 411 | 412 | //サービス提供者を含む任意のノードからマークル情報を取得 413 | stateProof = await stateProofService.metadataById(compositeHash).toPromise(); 414 | 415 | //検証 416 | checkState(stateProof,stateHash,pathHash,rootHash); 417 | ``` 418 | 419 | ### 13.3.3 アカウントへ登録したメタデータの検証 420 | 421 | アカウントに登録したメタデータValue値を葉として、 422 | マークルツリー上の分岐する枝をメタデータキーで構成されるハッシュ値でたどり、 423 | ルートに到着できるかを確認します。 424 | 425 | ```js 426 | srcAddress = Buffer.from( 427 | sym.Address.createFromRawAddress("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ").encoded(), 428 | 'hex' 429 | ) 430 | 431 | targetAddress = Buffer.from( 432 | sym.Address.createFromRawAddress("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ").encoded(), 433 | 'hex' 434 | ) 435 | 436 | //compositePathHash(Key値) 437 | hasher = sha3_256.create(); 438 | hasher.update(srcAddress); 439 | hasher.update(targetAddress); 440 | hasher.update(sym.Convert.hexToUint8Reverse("9772B71B058127D7")); // scopeKey 441 | hasher.update(sym.Convert.hexToUint8Reverse("0000000000000000")); // targetId 442 | hasher.update(Uint8Array.from([sym.MetadataType.Account])); // type: Account 0 443 | compositeHash = hasher.hex(); 444 | 445 | hasher = sha3_256.create(); 446 | hasher.update( Buffer.from(compositeHash,'hex')); 447 | 448 | pathHash = hasher.hex().toUpperCase(); 449 | 450 | //stateHash(Value値) 451 | hasher = sha3_256.create(); 452 | hasher.update(cat.GeneratorUtils.uintToBuffer(1, 2)); //version 453 | hasher.update(srcAddress); 454 | hasher.update(targetAddress); 455 | hasher.update(sym.Convert.hexToUint8Reverse("9772B71B058127D7")); // scopeKey 456 | hasher.update(sym.Convert.hexToUint8Reverse("0000000000000000")); // targetId 457 | hasher.update(Uint8Array.from([sym.MetadataType.Account])); //account 458 | value = Buffer.from("test"); 459 | hasher.update(cat.GeneratorUtils.uintToBuffer(value.length, 2)); 460 | hasher.update(value); 461 | stateHash = hasher.hex(); 462 | 463 | //サービス提供者以外のノードから最新のブロックヘッダー情報を取得 464 | blockInfo = await blockRepo.search({order:"desc"}).toPromise(); 465 | rootHash = blockInfo.data[0].stateHashSubCacheMerkleRoots[8]; 466 | 467 | //サービス提供者を含む任意のノードからマークル情報を取得 468 | stateProof = await stateProofService.metadataById(compositeHash).toPromise(); 469 | 470 | //検証 471 | checkState(stateProof,stateHash,pathHash,rootHash); 472 | ``` 473 | 474 | ## 13.4 現場で使えるヒント 475 | 476 | ### トラステッドウェブ 477 | 478 | トラステッドウェブを簡単に説明すると、全てをプラットフォーマーに依存せず、かつ全てを検証せずに済むWebの実現です。 479 | 480 | 本章の検証で分かることは、ブロックチェーンが持つすべての情報はブロックヘッダーのハッシュ値によって検証可能ということです。 481 | ブロックチェーンはみんなが認め合うブロックヘッダーの共有とそれを再現できるフルノードの存在で成り立っています。 482 | しかし、ブロックチェーンを活用したいあらゆるシーンでこれらを検証するための環境を維持しておくことは非常に困難です。 483 | 最新のブロックヘッダーが複数の信頼できる機関から常時ブロードキャストされていれば、検証の手間を大きく省くことができます 484 | このようなインフラが整えば、都会などの数千万人が密集する超過密地帯、あるいは基地局が十分に配置できない僻地や災害時の広域ネットワーク遮断時など 485 | ブロックチェーンの能力を超えた場所においても信頼できる情報にアクセスできるようになります。 486 | --------------------------------------------------------------------------------