前回はNEMはんこをW3Cによって定義されたDIDという分散環型識別子と比較して考察を行いました。
今回はさらに広い範囲で、ブロックチェーンを社会実装するにあたり抑えておくべき設計テクニックをデザインパターンとして考案しました。本記事は大きく分けて基礎編と実践編の2部構成としています。基礎編では今回の記事で使用する図の見方の説明します。実践編で実際のシステムに組み込んでいく設計パターンを説明していきたいと思います。使用する用語などはDIDに寄せている箇所がありますので、この記事を読む前にぜひ前回の「分散型識別子DID的に見たNEMはんこの技術的な解説」を読んでおいてください。
WHY デザインパターン?
どうしてブロックチェーンのデザインパターンを提唱する必要があるの?そう思われるかもしれません。ブロックチェーンは従来のシステムとは異なり、システム開発者や運用者にすべてのデータを操作する権限がありません。ひとたびブロックチェーンが動き出すとお互いを監視しあう仕組みにより、アカウントのデータはその秘密鍵を知るものしか操作できなくなります。つまり従来システムであれば、簡単に定義できていた**「状態」のキャンセルや譲渡・延長・共有**などの表現が非常に難しくなってしまう場合があります。これが「WHY BLOCKCHAIN?」と呼ばれる、ブロックチェーンで実装しようとすると余計に工数が増えてしまうのではないか?という論説の大きな根拠となってしまっています。しかし、最近のブロックチェーンは電子署名にタイムスタンプ機能を実装したのみならず、マルチシグやメタデータなブロックチェーンを社会実装するにおいて必要不可欠な機能がメインレイヤーで提供されているものも現れ始めました。これらをうまく組み合わせることで従来システムにブロックチェーンをプラスして社会実装することが可能になります。そのために実装ノウハウをデザインパターンとして技術者がイメージを共有しておくことがより重要になると考えます。
疑似コードについて
今回、デザインパターンの紹介にあたり理解を進めるために疑似コードを付記しています。簡単に説明します。
署名アナウンス
Alice.TransactionType{
[option]
}->Bob
AliceからBobへのトランザクションです。トランザクション個別の内容をoptionに記述します。もう少し複雑なパターンを紹介します。
AssetAccount<-(2-of-3){CosignerAccount1,CosignerAccount2,...}
.TransactionType{}
->RecipientAccount
CosignerAccount1,CosignerAccount2,...によって2-of-3でマルチシグ署名されたAssetAccountでRecipientAccountに対してトランザクションを送信します。
オプトイン署名
listener.aggregateBondedAdded(Bob)
.subscribe(tx => Bob.signCosignature(tx));
トランザクション発行者ではなくて、署名を求められたアカウントが連署するためのコードです。
listenerでネットワークを監視し、署名要求があればsignCosignatureで連署します。
データアクセス
TransactionRepo.search({id:hash})
.subscribe(tx=> Alice.publicKey.verify(tx.hash))
ブロックチェーンに記録されたデータを抽出します。上記コードでトランザクションをhash値で検索し、Aliceの公開鍵で検証を行うという意味になります。
基礎編
アカウント
アカウントは四角枠の中にロール名を書いて表現します。マルチシグは連署者と線で結びます。
トランザクション
トランザクションは紙をめくるような図で表現することにします。枠の内部にトランザクション生成時に指定したパラメータを表記します(モデルを説明するために必要最低限のパラメータのみ記載)。
以下にパラメータ指定の例を記します。
Message:"Hello",
Mosaic:{xym:10}
メッセージ"Hello"を添えて、10XYMを送金します。
Endpoint:http://...
Publickey:asdfada
アカウントのメタデータとしてEndpointとPublickeyのキーバリュー値を作成・更新します。
Transferトランザクションはアカウントに対してリスト的にデータを追加(append)していきます。削除はできません。Metadataはアカウントに対してディクショナリ(マップ)的にデータを更新していきます。キーバリューは更新されていくので、いつ更新されたデータなのかを注意しなければいけないケースも発生します。特徴を把握し、要件に応じて使い分けましょう。
トランザクションのアナウンス
アカウントのトランザクション発行を図で表現してみましょう。
左側はAliceからBobへの送金トランザクション。右側はAliceが自身に対してのMetadata更新トランザクションです。矢印の開始点がトランザクションの発信者、受信側アカウントの内部にトランザクション図を内包して表現します。
Alice.Transfer{
Message:"Hello",
Mosaic:{xym:10}
}
-> Bob
Alice.AccountMetadata{
Endpoint:http://...,
PublicKey:asdfada
}->Alice
検証者のデータアクセス
ブロックチェーンに記録されたデータへのアクセスは各パラメータ値へ直接矢印を引いて表現します。
上図ではVictor(Verifier:検証者)がAlice(Holder:所有者)のデータへアクセスしています。
TransferのMessageに記録した場合はトランザクションのハッシュ値でダイレクトに取得できます。Metadataの場合は一度アドレスなどからアカウント情報を取得した後、アカウントに紐づくキー値からバリュー値を取得します。
//アカウント情報の取得
AccountRepo.search({publicKey:Alice})
.subscribe(accountInfo=> getPublicKey(publicKey))
//メタデータの取得
MetadataRepo.search({
sourceAddress:Alice,
targetAddress:Alice,
scopedMetadataKey:canceled
})
.subscribe(meta=> getCancelInfo(meta.value))
実践編
図の見方を説明したところでいよいよ実践編です。ブロックチェーンはSymbol from NEMを想定していますが、上記基礎編を実現できるブロックチェーンであれば大部分は実装可能です。
1.公証・アポスティーユ
2.トレーサビリティ
3.所有・共有
4.譲渡・回復
5.キャンセル
6.有効期限
7.名前解決
8.タイマー送金
9.認証
10.分配
11.確定的存在証明
1.公証・アポスティーユ
証明書や作品のデジタル情報をハッシュ化してブロックチェーンに記録するパターンです。存在証明として使用します。
//公証
Issuer.Transfer{
Message:hash(Artwork)
}-> Alice
//検証
TransactionRepo.search({id:hash(Artwork)})
.subscribe(tx=> Alice.publicKey.verify(tx.hash))
issuerによる公証
- 証明書、および作品(Artwork)のデジタル情報(予測不能なID値を含む証書や作品を撮影した写真)のハッシュ値を取得
- ハッシュ値をTransferトランザクションのメッセージに含めて作品固有のアカウント(Alice)に送信
- Issuer(発行者)のアドレスを社会的影響力のある媒体にて公表する
Issuer(発行者)の秘密鍵によるトランザクション署名により、そのブロックを生成された時点でのコンテンツの存在が証明されます。
verifierによる検証
- Aliceアカウント所有者より公証されたTransaction hashをVictor(検証者)に提示
- Registryに登録された証明書・作品あるいは実際のArtworkにタグ付けされたデジタル情報からハッシュ値を取得
- トランザクションハッシュと自身の手でハッシュ化した値が一致することを確認
2.トレーサビリティ
複数の公証を実施して、その順序までも検証可能にすることがトレーサビリティと考えることができます。リスト構造を持つTransferトランザクションを利用します。
- トレーサビリティを行いたい製品・作業の単位一つ一つにユニークなアカウントを割り当てる
- アカウント(Alice)に対して、例えば製造工程ごとに完了や信頼性検証済みを証明するトランザクションを送信する
Alice.Transfer{
Message:hash(work1),
}-> Alice
Bob.Transfer{
Message:hash(work2),
}-> Alice
Carol.Transfer{
Message:hash(work3),
}-> Alice
3.所有・共有
マルチシグを用いて所有と譲渡を表現します。マルチシグ化したアカウント(Alice)は連署者(Bob)により自由にトランザクションが生成でき、また連署者の署名無しではマルチシグの解除もできません。ですので、実質連署者に「所有」されている状態になります。複数人の連署者がいれば、そのマルチシグ化したアカウントは「共有」されている状態と言えるでしょう。共有アカウントを物理的なシールで封印すれば、銀行が運用しているようなセキュリティキーとしてアカウントを運用することもできます。
- Aliceがマルチシグ作成トランザクション(BobとSecurityを追加)を実行
- BobとSecurityがオプトインしてマルチシグ化完了
- Securityアカウントを物理的安全な場所に保管
//create multisig
Alice.MultisigAccountModification{
Add:[Bob,Security]
}
//optin
listener.aggregateBondedAdded(Bob)
.subscribe(tx => Bob.signCosignature(tx));
listener.aggregateBondedAdded(Security)
.subscribe(tx => Security.signCosignature(tx));
4.譲渡・回復
マルチシグで表現された所有を他のアカウントに引き継ぎます。
BobからCarolへ譲渡する場合、Bobが署名できる状況にあるかどうかの2通りが考えられます。Bobが署名できない状況とはBobが何らかの事情により署名を拒否したり、秘密鍵を紛失したりした場合のことを指します。
- Bobが署名できる場合
- AliceのMultisig変更トランザクション(Bobを削除してCarolを追加)をBobが連署実行
- Carolがオプトイン署名してMultisig変更完了
- Bobが署名できない場合
- AliceのMultisig変更トランザクション(Bobを削除してCarolを追加)を
Security
アカウントで連署実行 - Carolがオプトイン署名してMultisig変更完了
- AliceのMultisig変更トランザクション(Bobを削除してCarolを追加)を
Alice<-(2-of-3){Bob,Security}
.MultisigAccountModification{
Delete:[Bob],
Add:[Carol]
}
//optin
listener.aggregateBondedAdded(Bob)
.subscribe(tx => Bob.signCosignature(tx));
listener.aggregateBondedAdded(Carol)
.subscribe(tx => Carol.signCosignature(tx));
5.キャンセル
Aliceみずから、そのアカウントの有効性を放棄したい場合に使用します。
- メタデータ上にキャンセル区分のキーバリュー値を設定し、キャンセルを表現
- Victor(検証者)はAlice(所有者)のAliceのMetadataからキャンセルの意思を確認
Alice以外のアカウント(例えば発行者:Issuer)がMetadataにキャンセルを記録したい場合は、Aliceのオプトイン(同意)が必要です。
Alice.AccountMetadata{
canceled:True
}->Alice
6.有効期限
アカウントに発行した証明書などに対しての制限を外部から付与するためのパターンです。
キャンセルなどで使用したMetadataは受信者の許諾(オプトイン)署名が必要なため、うまく機能しません。また、キャンセルの意味を持たせたトークンを付与しても投げ返されてしまう可能性があります。
そのため、有効期限の設定と期限更新のトークン付与という形で表現します。
-
更新
- IssuerがAliceに対してMetadataを作成し、証明書の有効期限の開始ブロックを明示
- 期限を更新するたびに延長トークンを送付
-
検証
- 検証者はMetadataに記載された有効期限の発生時と更新された回数を残高から計算して有効期限の妥当性を検証
Ivan.AccountMetadata{
StartBlock:234566
}-> Alice
//Aliceへのメタデータ挿入承認
listener.aggregateBondedAdded(Alice)
.subscribe(tx => Alice.signCosignature(tx));
//有効期限更新トークン送付
Ivan.Transfer{
Mosaic:{expired:1}
}-> Alice
//有効期限開始ブロックの取得
MetadataRepo.search({
sourceAddress:Ivan,
targetAddress:Alice,
scopedMetadataKey:StartBlock
})
.subscribe(meta=> getStartBlock(meta.value))
//有効期限更新回数の取得
TransactionRepo.search({transferMosaicId:expired})
.subscribe(tx=> getAmount(tx.mosaics))
7.名前解決
第三者への信頼に頼らずにDNSのような名前解決を行うパターンです。
- 事前準備
- Namespaceトランザクションを作成して、@AliceとAliceアカウントをリンク
- Metadataトランザクションを実行して Endpoint:https://....(解決後のアドレス) のキーバリュー値を登録
- 名前解決
- Clientが@Aliceへのアクセス要求
- Namespace@AliceからAliceアカウントを解決
- Alice内のメタデータからアクセス先エンドポイントを解決
- Server https://... に接続
/* Alice による名前登録 */
//ネームスペースの登録
Alice.NamespaceRegistration{
name:alice_name
}
//エンドポイントの登録
Alice.AccountMetadata{
Endpoint:http://aaa.com
}-> Alice
/* Clientによる解決 */
//ネームスペースから所有アカウントの取得
NamespaceRepo.getNamespace(alice_name)
.subscribe(
(ns) => getOwnerAddress(ns)
);
//所有アカウントからエンドポイントの取得
MetadataRepo.search({
sourceAddress:OwnerAddress,
targetAddress:OwnerAddress,
scopedMetadataKey:Endpoint
})
.subscribe(meta=> getEndpoint(meta.value))
8.タイマー送金
(これはplanethoukiさんに教えていただきました。まだまだ奥が深いですね。ありがとうございます)。
アグリゲートトランザクションとシークレットプルーフトランザクションを組みわせて、指定時刻(ブロック高)で送金するパターンです。実際にはシークレットプルーフトランザクションの有効時間切れ時のプロトコルによる返金処理をうまく利用します。
- 以下の処理をアグリゲートトランザクションで集約して実行
- BobからのAliceへの入金
- Alice内で同額の金額をシークレットトランザクションでロック
- Aliceからロックされた分の入金がBobに入るので、Aliceの所有残高は変化しない
- シークレットロックの指定時間が過ぎると、ロックしていた資産がAliceに戻る
- 結果、Bobから受け取っていた分の金額がAlice内で増加する(BobからAliceへのタイマー送金となる)
//タイマー設定(BobからAliceへ)
Aggregete[
Alice.SecletLock{
duration:420
Mosaic:{xym:10}
}->Bob ,
Bob.Transfer{
Mosaic:{xym:10}
}->Alice
]
//タイマーキャンセル
Bob->SecletProof->LockedTx
9.認証
Issuerから証明書を発行してもらったAliceが"今も"アカウントを所有していることを証明し、Verifierが認証するパターンです。IssuerとVerifierが同一人物である場合もあります。
- Issuer(Ivan:発行者)がAliceに対してAuthorizedのメッセージを送信
- Alice(所有者)がVerifier(Victor:検証者)に対して認証申請のトランザクションを送信します。
- Verifier(Victor:検証者)がAliceの提示するトランザクションハッシュ値とRegistryからIssuerの承認を確認します。
- Verifier(Victor:検証者)がAliceの提示するトランザクションハッシュ値から秘密鍵の所有を確認します。
//発行
txhash = Ivan.Transfer{
Message:authorized
}->Alice
//ログイン
loginHash = Alice.Transfer{
Message:txhash
Mosaic:{xym:login fee}
}->Victor
//認証
TransactionRepo.search({id:loginHash })
.subscribe(tx=> Alice.publicKey.verify(tx.hash))
10.分配
資産を分配するトランザクションを複数作成しておき、Oracleのオプトインによって、どの分配方法を採用するかを決定するパターンです。Oracleは全てのパターンにおいて参加者が署名したのを確認してからoptinを実施します。採用されなかったトランザクションは期限切れによりデポジット分の手数料が没収されます。このモデルで記述すれば、スマートコントラクトでは記述困難な可変する複数種類の資産を三人以上の関係者で分配することが簡単に実現可能です。
Aggregate[
Alice->Transfer{xym:0}->Bob,
Bob->Transfer{xym:50}->Carol,
Carol->Transfer{xym:100}->Alice,
Oracle.AccountMetadata{
Message:Alice is Correct
}->Alice
]
Aggregate[
Bob->Transfer{xym:0}->Alice,
Alice->Transfer{xym:50}->Carol,
Carol->Transfer{xym:100}->Bob,
Oracle.AccountMetadata{
Message:Bob is Correct
}->Bob
]
Aggregate[
Carol->Transfer{xym:0}->Bob,
Bob->Transfer{xym:50}->Alice,
Alice->Transfer{xym:100}->Carol,
Oracle.AccountMetadata{
Message:Carol is Correct
}->Carol
]
listener.aggregateBondedAdded(Oracle)
.subscribe(tx => Oracle.signCosignature(tx));
11.確定的存在証明
確定的な存在証明について説明します。メインチェーンの証拠となるファイナライズブロックからpreviousHashでblockをさかのぼっていき、たどることができるブロックのヘッダー内にマークルパトリシアツリーのルートが存在すれば、そのツリーに埋め込まれたデータはメインチェーンによってその存在が認められたとみなすことができます。
すべての人にマークルパトリシアツリーの力を参照
最後に
では前回のNEMはんこのエコシステム図をもう一度見てみましょう。
だいぶ理解できるようになってきたのではないでしょうか?
次回は実際に身近にあるユースケースを用いてデータの流れを追ってみたいと思います。
いろいろな図を紹介しましたが、これらの図の意味を理解しようとする人が増えてくるのはおそらく3年~5年後だと思います。トレンドを追いかけて目新しいスキームばかりに飛びついている人も、システムのライフサイクルが一巡してようやく必要に迫られて議論が始まるころだと思います。他のプラットフォームがこれらの重要性に気付き、規格化を伴う実装の検討を始めるころ、NEMはさらに進んだ課題解決に取り組んでいることを期待しています。