9. マルチシグ化

速習Symbolブロックチェーン9章、マルチシグ化編です。

URLとしてはこちら

それではやっていきます。

実践編

注意事項に1つのマルチシグアカウントに登録できる連署者の数は25となります。つまり、1つの子アカウントに対し、親になれるのは25ということなんでしょう。

また、最大3階層までとのことです。

この値については、apiノードの/network/propertiesにアクセスすると、確認できます。

こういう値はおそらく、ハードフォークすることで変更できる値なんでしょう。

9.0 アカウントの準備

マルチシグを構成するためのアカウントを用意します。

var takashi = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(takashiPrivateKey), sym.NetworkTypeEnum.testnet);

var carol1 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol1PrivateKey), sym.NetworkTypeEnum.testnet);
var carol2 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol2PrivateKey), sym.NetworkTypeEnum.testnet);
var carol3 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol3PrivateKey), sym.NetworkTypeEnum.testnet);
var carol4 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol4PrivateKey), sym.NetworkTypeEnum.testnet);
var carol5 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol5PrivateKey), sym.NetworkTypeEnum.testnet);

print("takashi: ${takashi.address.toString()}");
print("carol1: ${carol1.address.toString()}");
print("carol2: ${carol2.address.toString()}");
print("carol3: ${carol3.address.toString()}");
print("carol4: ${carol4.address.toString()}");
print("carol5: ${carol5.address.toString()}");

こんな感じで。速習Symbolではbobとcarol1-5でやっていました。

秘密鍵をランダムでやると(1日で終わるとは限らない僕のペースでは)困ってしまうので、定数化しています。

過去の分も似たような形でやっていたため、名前の重複すると(自分で)混乱する可能性が高いため、bobの代わりにtakashiとしました。よろしくtakashi。

9.1 マルチシグの登録

説明の通り、Symbolのマルチシグアカウントは新規で生まれるわけではなく、既存のアカウントを利用します。つまり、先ほどのtakashiをそのままマルチシグにするような感じです。

var takashi = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(takashiPrivateKey), sym.NetworkTypeEnum.testnet);

var carol1 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol1PrivateKey), sym.NetworkTypeEnum.testnet);
var carol2 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol2PrivateKey), sym.NetworkTypeEnum.testnet);          
var carol3 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol3PrivateKey), sym.NetworkTypeEnum.testnet);          
var carol4 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol4PrivateKey), sym.NetworkTypeEnum.testnet);          

// マルチシグの追加アカウントたち。
var cosigners = [carol1, carol2, carol3, carol4];

// マルチシグの追加アカウントたち(アドレス)
var cosignersAddr = List.generate(cosigners.length, (index) => cosigners[index].address);

// 速習では承認/除名で必要な署名者数が同数となっているが、値の判別のため、それぞれを別とする。
// また、(元SDKの引数順を見ていないが)速習では承認、除名という引数順であった。
// 理由は不明だが自作では除名、承認となっている。
var multisigTx = sym.MultisigAccountModificationInfoV1.create(
  takashi.publicAccount,
  Int8(2),    // 除名のために必要な最小署名者数増分
  Int8(3),    // 承認のために必要な最小署名者数増分
  cosignersAddr,  // 追加アドレス。
  []
);

// Transaction設定を行う。  
var transSetting = sym.TransactionSetting(
  signer: takashi, // マルチシグ化したいアカウント(発行者)
  generationHash: generationHashSeed, 
  networkType: sym.NetworkTypeEnum.testnet, 
  deadline: sym.Deadline.create(epochAdjustment),
  fee:sym.FeeMultiplier(100),
  cosignatoriesQ: cosigners.length);     // 手数料は100%。

// トランザクションの通知。アグリゲートである必要があるらしい。         
var transactionSender = TransactionSender();

await transactionSender.sendAggregateComplete(transSetting, [multisigTx], cosigners);

MultisigAccountModificationInfoV1を作っているところで、ちょっと変更している部分はありますが、速習と同じ感じかなと思います。

なお、アグリゲートコンプリートトランザクションで実行しています。

長くなってしまいましたが承認されました。

9.2 確認

takashiがマルチシグ化されているのか? を確認するようです。

同様にcarol1から見て、takashiが子となっているかを確認します。

// アドレスを取得するため、takashiとcarol1を生成する。
var takashi = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(takashiPrivateKey), sym.NetworkTypeEnum.testnet);

var carol1 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol1PrivateKey), sym.NetworkTypeEnum.testnet);


// 使用するネットワークホストの取得と、RestAPIのアクセス用クラスをインスタンス化。
var networkHostInfo = await TransactionSender.getNetworkHost();

// マルチシグ情報を取得する。
var httpMultisig = sym.MultisigRoutesHttp(networkHostInfo.networkHost);


print("マルチシグ化したアカウントの確認(takashi)");

print((await httpMultisig.getMultisigInformation(takashi.address)).baseMap.toString());

print("連署者アカウントの確認");

print((await httpMultisig.getMultisigInformation(carol1.address)).baseMap.toString());

マルチシグ化したアカウントの確認(takashi)

takashi側でマルチシグ化されているのが確認できます。

minApprovalが3、minRemovalが2と、先ほどの設定どおりです。

cosignatoryAddressesにはcarol1-4が(おそらく)並んでいます。

multisigAddressesは自分が親となる、子どものアドレスが設定されるようです。takashiは誰の親でもないため、空となっているのが確認できます。

連署者アカウントの確認

こちらはcarol1で確認しています。こちらはマルチシグアカウントではないため、minApprovalおよびminRemovalは0となっています。同様に親が存在しないため、cosignatoryAddressesは空になります。

子どもとなるtakashiのアドレスがmultisigAddressesに登録されています。

9.3 マルチシグ署名

マルチシグ化されたアカウントは自身でやるのではなく、親アカウントが操作する必要があります。

そのため、この先の例ではtakashiのアカウントから送信するが、実際にそのトリガとしてアクションを起こすのはcarol1となっています。

アグリゲートコンプリートトランザクションで送信

アグリゲートコンプリートトランザクションで実行するコードは以下。

// 必要アカウントを作成する。
var takashi = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(takashiPrivateKey), sym.NetworkTypeEnum.testnet);
var carol1 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol1PrivateKey), sym.NetworkTypeEnum.testnet);
var carol2 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol2PrivateKey), sym.NetworkTypeEnum.testnet);
var carol3 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol3PrivateKey), sym.NetworkTypeEnum.testnet);

// carol1は起案者になるため、連署はcarol2、carol3のみ。
// (takashiのminApprovalが3のため、3名が署名すれば、連署は成立する)
var cosigners = [carol2, carol3];

// aliceが送信を受ける。
var alice = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(alicePrivateKey), sym.NetworkTypeEnum.testnet);

// 転送トランザクション作成。
var transTx = sym.TransferInfoV1.create(
  takashi.publicAccount,      // 実行する(送信元)アカウントをtakashiとする。
  alice.address,
  [sym.Mosaic(id: sym.NamespaceId("symbol.xym").getUnresolvedMosaicId(), amount: sym.Amount(1000000))],
  sym.PlainMessage.create('konichiha!!'),
);

// Transaction設定を行う。
// 署名者はcarol1。takashiのアカウントから送信するが、takashiは子のため、自身で起案を行わない。
var transSetting = sym.TransactionSetting(
  signer: carol1,
  generationHash: generationHashSeed, 
  networkType: sym.NetworkTypeEnum.testnet, 
  deadline: sym.Deadline.create(epochAdjustment),
  fee:sym.FeeMultiplier(100),
  cosignatoriesQ: cosigners.length);     // 手数料は100%。

// トランザクションの通知。
print('アグリゲートコンプリートトランザクションで送信');
var transactionSender = TransactionSender();

await transactionSender.sendAggregateComplete(transSetting, [transTx], cosigners);

僕の作り上、 transactionSender.sendAggregateComplete()でアグリゲートコンプリートトランザクション実行後、承認を待って承認内容を出力しています。

アグリゲートトランザクション内にて、実際の転送トランザクション部分は以下。

ウォレットで確認すると下記の形。

takashiのアドレスは末尾HDY。carol1のアドレスは末尾JBI。

署名者(Signer起案者)はcarol1となっているが、トランザクション詳細の送信者は末尾HDYのtakashiとなっています。

takashiの署名は関係なく、carol1がtakashiのトークンを転送できたことが確認できます。

アグリゲートボンデッドトランザクションで送信

takashiの親アカウントの連署を集めるのに、全員がトランザクションに即時署名ができるとは限らないため、アグリゲートボンデッドトランザクションでも実行可能となっています。

ちょっと冗長ではありますが、takashiがaliceに転送トランザクションを実行するアグリゲートボンデッドトランザクションをcarol1が起案として実行します。

なお、過去のアグリゲートボンデッドトランザクションの連署と同じく、トランザクションハッシュをチェーンから取得してきて、carol2、carol3が連署するようにします。

アグリゲートボンデッドトランザクションを通知してから、30秒待機しているので、ちょっと長いですね。

// 必要アカウントを作成する。
var takashi = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(takashiPrivateKey), sym.NetworkTypeEnum.testnet);
var carol1 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol1PrivateKey), sym.NetworkTypeEnum.testnet);

// aliceが送信を受ける。
var alice = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(alicePrivateKey), sym.NetworkTypeEnum.testnet);

// 転送トランザクション作成。
var transTx = sym.TransferInfoV1.create(
  takashi.publicAccount,      // 実行する(送信元)アカウントをtakashiとする。
  alice.address,
  [sym.Mosaic(id: sym.NamespaceId("symbol.xym").getUnresolvedMosaicId(), amount: sym.Amount(1000000))],
  sym.PlainMessage.create('zuttomo dayo!!'),
);

// Transaction設定を行う。
// 署名者はcarol1。carol1以外の連署者が署名すればよいため、連署者数はcarol2,carol3の2が必要。
var transSetting = sym.TransactionSetting(
  signer: carol1,
  generationHash: generationHashSeed, 
  networkType: sym.NetworkTypeEnum.testnet, 
  deadline: sym.Deadline.create(epochAdjustment),
  fee:sym.FeeMultiplier(100),
  cosignatoriesQ: 2);     // 手数料は100%。

// トランザクションの通知。
print('アグリゲートボンデッドトランザクションで送信');
var transactionSender = TransactionSender();

var transHash = await transactionSender.sendAggregateBonded(transSetting, [transTx]);


// ここから連署を行う。
var carol2 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol2PrivateKey), sym.NetworkTypeEnum.testnet);
var carol3 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol3PrivateKey), sym.NetworkTypeEnum.testnet);

var cosigners = [carol2, carol3];

var networkHostInfo = await TransactionSender.getNetworkHost();

var transHttp = sym.TransactionRoutesHttp(networkHostInfo.networkHost);

var transInfo = await transHttp.getPartialInformation(transHash.value);

transInfo as sym.TransactionInfoDTO;

var aggTrans = transInfo.transaction;

aggTrans as sym.AggregateTransactionDTO;

print('署名対象ハッシュ');
print(transInfo.meta.hash.value);

print("連署を行う");
for (var element in cosigners) {
  
  var signature = await element.sign(transInfo.meta.hash.serialize());

  var cosignature = sym.DetachedCosignature(element.publicKey, signature, transInfo.meta.hash);

  await transHttp.announceCosignature(cosignature);
}

実行結果は以下の通り。

連署関係は過去の章と同じ処理で。

ウォレットで確認すると同様にトランザクションが承認されていることが確認できました。

9.5 マルチシグ構成変更

現在、takashiは承認のために3アカウント、除名には2アカウントとなっています。

この数値をいじれたり、また親アカウントの構成を変えることができるようです。

マルチシグ構成の縮小。

除名および承認に必要な連署者数を-1とするようです。

また、carol3についてもマルチシグ構成から除名するようです。

// 必要アカウントを作成する。
var takashi = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(takashiPrivateKey), sym.NetworkTypeEnum.testnet);
var carol1 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol1PrivateKey), sym.NetworkTypeEnum.testnet);
var carol2 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol2PrivateKey), sym.NetworkTypeEnum.testnet);
var carol3 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol3PrivateKey), sym.NetworkTypeEnum.testnet);

// carol1は起案者になるため、連署はcarol2、carol3のみ。
// (takashiのminApprovalが3のため、3名が署名すれば、連署は成立する)
var cosigners = [carol2, carol3];

// マルチシグアカウント情報変更設定を行う。
// takashiの必要な最小署名者を-1にし、carol3を除名する。
var multisigTx = sym.MultisigAccountModificationInfoV1.create(
  takashi.publicAccount, 
  Int8(-1),   // 除名のために必要な最小署名者数増分
  Int8(-1),   // 承認のために必要な最小署名者数増分
  [], 
  [carol3.address]);

// Transaction設定を行う。
// 署名者はcarol1。takashiのアカウントから送信するが、takashiは子のため、自身で起案を行わない。
var transSetting = sym.TransactionSetting(
  signer: carol1,
  generationHash: generationHashSeed, 
  networkType: sym.NetworkTypeEnum.testnet, 
  deadline: sym.Deadline.create(epochAdjustment),
  fee:sym.FeeMultiplier(100),
  cosignatoriesQ: cosigners.length);     // 手数料は100%。

// トランザクションの通知。
print('アグリゲートコンプリートトランザクションで送信');
var transactionSender = TransactionSender();

await transactionSender.sendAggregateComplete(transSetting, [multisigTx], cosigners);

完了した。

9.2 確認で行った動作を再度実行します。

takashiのminApprovalが2、minRemovalが1となっているのが確認できます。

また、cosignatoryAddressesもcarol3が除名されたことで、3件となっていることが確認できます。

これでtakashiのマルチシグ構成を減らすことを確認しました。

連署者構成の差し替え

連署者構成の差し替えを行います。

現在、takashiの親アカウントはcarol1、carol2となっています。

これをcarol2をout、carol5をinとしていきます。minApprovalおよびminRemovalについては変動させません。


// 必要アカウントを作成する。
var takashi = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(takashiPrivateKey), sym.NetworkTypeEnum.testnet);
var carol1 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol1PrivateKey), sym.NetworkTypeEnum.testnet);
var carol2 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol2PrivateKey), sym.NetworkTypeEnum.testnet);
var carol5 = await sym.Account.createFromPrivateKey(
  sym.PrivateKey(carol5PrivateKey), sym.NetworkTypeEnum.testnet);

// carol1は起案者になるため、連署はcarol2のみ。
// (takashiのminApprovalが2のため、2名が署名すれば、連署は成立する)
// ただしcarol5を追加するため、carol5の連署も必要となる。
var cosigners = [carol2, carol5];

// マルチシグアカウント情報変更設定を行う。
// 署名者数増分を0にして、変更なし。
// carol2を削除し、carol5を設定する。
var multisigTx = sym.MultisigAccountModificationInfoV1.create(
  takashi.publicAccount, 
  Int8(0),   // 除名のために必要な最小署名者数増分
  Int8(0),   // 承認のために必要な最小署名者数増分
  [carol5.address], 
  [carol2.address]);

// Transaction設定を行う。
// 署名者はcarol1。takashiのアカウントから送信するが、takashiは子のため、自身で起案を行わない。
var transSetting = sym.TransactionSetting(
  signer: carol1,
  generationHash: generationHashSeed, 
  networkType: sym.NetworkTypeEnum.testnet, 
  deadline: sym.Deadline.create(epochAdjustment),
  fee:sym.FeeMultiplier(100),
  cosignatoriesQ: cosigners.length);     // 手数料は100%。

// トランザクションの通知。
print('アグリゲートコンプリートトランザクションで送信');
var transactionSender = TransactionSender();

await transactionSender.sendAggregateComplete(transSetting, [multisigTx], cosigners);

とりあえず実行されました。

再度、9.2の確認を実行します。

takashiのminApprovalが2、minRemovalが1で変化はありません。

cosignatoryAddressesに3名が並んでいますが、この状態では冒頭のアドレスとは一致しないため、わかりづらいですね。。。

ということで、エンコード前アドレスも出力してきました。

アッパーにしてないのであれですが、carol1は9812…f485、carol4が987c…991e、carol5が9800…3257で親アドレスになっていることが確認できます。

感想編

現場で使えるヒントもこちらで。

多要素認証

ちゃんとやればセキュリティは安全に。ただ、変にやると漏れてしまうねっていうところでしょうか。

複数人で1アカウントの運用もできてしまうので便利ではありますが、一度ミスをした人がいれば全体にも波及する恐れがありますね。

MAGIシステムじゃないですが、複数人の承認がないと実行されないのは面白いですよね。
別に複数人でなくても、例えば金庫用のアカウントなんかがある場合は、金庫用のアカウントの親アカウントを2つ用意し、それぞれを普段使いのデスクトップPC、スマホと導入しておけば、片方だけでは送金とかされない安全性はありますよね。
……故障などで取り出せなくなることもあるため、秘密鍵の管理は別途、大事になるところだと思いますが。。。

アカウントの所有

親アカウントを所持していることで、子アカウントのアカウントも(それが発行したモザイクなども)所持しているということですね。親アカウントの構成を自由に組み替えられるため、誰かに子アカウントの譲渡もできる、という感じでしょうか。

秘密鍵は無効(というより、子アカウントでのトランザクション発行が無効なのかな?)とありました。
ふと思ったのは、子アカウントに向けて暗号化メッセージを送信した場合、メッセージの復号には子アカウントの秘密鍵が必要になるのでは? と思いました。

これは言い換えると、子アカウントに対しては暗号化メッセージを送らないほうがよさそうな気もしますね。もちろん運用次第ですけど。
子アカウントを譲渡した際に子アカウントの秘密鍵の譲渡がされない場合、子アカウントへの暗号メッセージは譲渡された側が確認することができませんし、子アカウントの元の持ち主が見ることが可能かなと思います。そうだとしたらちょっと怖い部分かもしれないですね。

ワークフロー

あーそうですよね、ワークフロー。できそうな気もします。あんまりちょっとしっくり来ていませんが。。。複数階層のマルチシグアカウントで実験してないからかもしれません。

どこまで署名が集まったか? っていうのは恒久的に確認できるんでしょうか。。。どこかで見れるのかも知れませんが、ちょっとまだ追えてないです。

その他

僕がもともとSymbolの機能で「ああ、こういうのあるのよねー」と思っていたのはネームスペース、モザイク、アグリゲートトランザクション、マルチシグのであったため、ここまでで一通りの? 「僕が思うSymbolの機能たち」というところまできました。長かった。

また次回。あと4章。ひとつきに2章やれればなんとか、12月には終えそうですね。