今回はSymbolブロックチェーンのネームスペースについて解説します。
公式ドキュメントはこちらです。
まずはSymbolで定義されるネームスペースの特徴について、初心者と上級者にわけてざっくりと説明します。
初心者の方
アドレスやモザイクトークンに分かりやすい名前を付けることができ、送金ミスを減らすことができます。
ネームスペースはインターネットのドメインと同様に期間でレンタルします。
XYMにはsymbol.xymという分かりやすいネームスペースがあらかじめ定義されています。
上級者の方
ウォレットはネームスペースを名前解決することなくそのままトランザクションに署名できます。
トランザクションが承認されたときのレシートを参照することで、そのときのネームスペース所有者を特定できます。
改ざん不可能(検証可能)なDNSが実現可能です。
基本操作
ネームスペースの作成、更新、変換まわりを抑えておきましょう。
生成
nsXembookId = new sym.NamespaceId("xembook");
>NamespaceId {fullName: 'xembook', id: Id}
> fullName: "xembook"
> id: Id {lower: 646738821, higher: 2754876907}
更新
有効期限の確認
nsInfo = await nsRepo.getNamespace(nsXembookId).toPromise();
lastHeight = (await chainRepo.getChainInfo().toPromise()).height;
lastBlock = await blockRepo.getBlockByHeight(lastHeight).toPromise();
remainHeight = nsInfo.endHeight.compact() - lastHeight.compact();
new Date(lastBlock.timestamp.compact() + remainHeight * 30000 + epochAdjustment * 1000)
//>Tue Mar 29 2022 18:17:06 GMT+0900 (日本標準時)
生成更新トランザクション
ルートネームスペース
namespaceTx = nem.NamespaceRegistrationTransaction.createRootNamespace(
nem.Deadline.create(epochAdjustment),
"xembook",
nem.UInt64.fromUint(180000),
networkType
);
サブネームスペース
subNamespaceTx = nem.NamespaceRegistrationTransaction.createSubNamespace(
nem.Deadline.create(epochAdjustment),
"tomato",
"xembook",
networkType,
);
変換
ネームスペース文字列からIDが一意に決定されます。その値をHexやUInt値に変換することで、署名や参照のためのキー値として利用することもできます。
HEX値
nsXembookId.toHex();
>'A43415EB268C7385'
nsId = sym.NamespaceId.createFromEncoded("A43415EB268C7385");
>NamespaceId {id: Id}
UInt値
nsXembookId.id
> Id {lower: 646738821, higher: 2754876907}
nsXembookId.id.toString()
>'11832106220717372293'
桁が大きすぎるためJavaScriptでは数値型で扱うことはできません。
ネームスペース関連情報の取得
NamespaceHttpを利用してネームスペースに関連したさまざまな情報をノードから取得します。
注意点として、ネームスペース情報にネームスペース名は含まれない場合があります。
アドレスやモザイクからネームスペース情報を逆引きしてネームスペース名を取得したい場合は、ネームスペース情報からさらにネームスペース名をgetNamespacesNamesで取得する必要があります。
ネームスペースIDからネームスペース名を取得
- getNamespacesNames(NamespaceId[]) => NamespaceNames[]
ネームスペースIDからネームスペース情報を取得
- getNamespace(NamespaceId) => NamespaceInfo
アドレス、モザイクなどからネームスペース名一覧を取得
- getAccountsNames(Address) => AddressNames(Address,NamespaceName[])[]
- getMosaicsNames(MosaicId) => MosaicNames(MosaicId,NamespaceName[])[]
ネームスペースからアドレスアカウント情報を取得
- getLinkedAddress(NamespaceId) => Address
- getLinkedMosaicId(NamespaceId) => MosaicId
ルートネームスペースから全ネームスペース情報の検索
- search => NamespaceInfo
- NamespaceSearchCriteria
- level0
- NamespaceId
- ルートネームスペースを指定
- NamespaceId
- AliasType
- Address = 2
- Mosaic = 1
- one = 0
- registrationType
- RootNamespace = 0
- SubNamespace = 1
- pageNumber,pageSize,order,ownerAddress
- 共通
- level0
- NamespaceSearchCriteria
sdkドキュメントはこちら
リンク
作成したネームスペースはアカウントアドレスやモザイクトークンに割り当てることができます。
Mosaicにリンク
mosaicId = new sym.MosaicId("6BED913FA20223F8");
mosaicAliasTx = sym.AliasTransaction.createForMosaic(
sym.Deadline.create(epochAdjustment),
sym.AliasAction.Link,
namespaceId,
mosaicId,
networkType
).setMaxFee(200);
Addressにリンク
address = nem.Address.createFromRawAddress("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ");
addressAliasTx = sym.AliasTransaction.createForAddress(
sym.Deadline.create(epochAdjustment),
sym.AliasAction.Link,
namespaceId,
address,
networkType
).setMaxFee(200);
ネームスペースをトランザクションに使用する
例えば、TransferTransaction.createは以下のように定義されます。
TransferTransaction.create(
deadline: Deadline,
recipientAddress: UnresolvedAddress,
mosaics: new Mosaic(id: UnresolvedMosaicId, amount: UInt64)[],
message: Message,
networkType: NetworkType,
maxFee?: UInt64,
signature?: string,
signer?: PublicAccount
)
このうち、UnresolvedAddress、UnresolvedMosaicIdと定義された箇所はネームスペースを解決せずにそのままトランザクションとして署名することができます。
UnresolvedMosaicIdとして使う
UnresolvedMosaicId: (MosaicId | NamespaceId)で定義されたパラメータは
MosaicIdの代わりにNamespaceIdのままでトランザクションを署名できます。
nsTomatoId = new sym.NamespaceId("xembook.tomato");
tx = sym.TransferTransaction.create(
sym.Deadline.create(epochAdjustment),
address,
[new sym.Mosaic(nsTomato,sym.UInt64.fromUint(1))],
sym.EmptyMessage,
networkType
).setMaxFee(200);
UnresolvedAddressとして使う
UnresolvedAddress: (Address | NamespaceId)で定義されたパラメータは
Addressの代わりにNamespaceIdままでトランザクションを署名できます。
nsXembookId = new sym.NamespaceId("xembook");
tx = sym.TransferTransaction.create(
sym.Deadline.create(epochAdjustment),
nsXembook,
[],
sym.EmptyMessage,
networkType
).setMaxFee(200);
メタデータの付与
アカウントやモザイクと同様にネームスペースにもメタデータを付与することができます。
createNamespaceMetaTx = await metaService.createNamespaceMetadataTransaction(
nem.Deadline.create(epochAdjustment),
networkType,
publicAccount.address,//ネームスペースの作成者
namespaceId,
key,value,
publicAccount.address //メタデータの登録者
).toPromise();
企業であればドメイン登録に付随したデータ、あるいは認証アカウント(Issuer)からの署名、
ビットコインやイーサリアムのアドレスを記録しておけば面白いかもしれませんね。
署名時のネームスペースを解決する
現在ネームスペースがリンクしているアドレスやモザイクではなく、トランザクションがブロックチェーンに記録されたときに何とリンクされていたかを調べる方法です。
state = await receiptRepo.searchAddressResolutionStatements({height:179401}).toPromise();
nsBallId = new sym.NamespaceId("xembook.game.ball");
state = await receiptRepo.searchMosaicResolutionStatements({height:181769}).toPromise();
ネームスペースを利用するときに、注意しないといけないことに、
トランザクションを署名した時のネームスペースの所有者と、現在のネームスペースの所有者が異なる可能性がある、という点があります。
サービス提供者にとって、ネームスペースを使えば気軽にアドレスを乗り換えられる利点がありますが、サービスが廃止された後、スキャマーによってネームスペースを取得された場合は要注意が必要です。
マークルパトリシアツリーでネームスペースを検証する
署名を必要とせず、ネームスペースの参照のみ行いたい場合は問い合わせ先ノードが悪意ある第三者に乗っ取られている可能性を考慮する必要があります。そういった場合は、マークルパトリシアツリーを使うことで(ファイナライズブロック情報が他所から取り寄せて)ネームスペースの正当性を検証することができます。
ブロックヘッダーのハッシュ値とマークルパス情報を取得
nsXembookId = new sym.NamespaceId("xembook");
rxjs.zip(
stateProofService.namespaceById(nsXembookId),
blockRepo.search({order:"desc"})
).subscribe(x=>{
state = x[0]; // マークルパス
merkleRootHash = x[1].data[0].stateHashSubCacheMerkleRoots[1];
console.log(merkleRootHash);
})
ネームスペース情報からハッシュ値生成
nsInfo = await nsRepo.search({level0:nsXembookId}).toPromise();
hasher = sha3_256.create();
nsInfoHash = hasher.update(nsInfo.data[0].serialize(nsInfo.data)).hex().toUpperCase();
//>'38CC8173E8387ADE8198ED5BC3C8A0E391D38F943062F85F8606798C62F1F898'
ネームスペースIDからパスハッシュ値生成
hasher = sha3_256.create();
hasher.update(sym.Convert.hexToUint8Reverse(nsXembookId.toHex()));
nsIdHash = hasher.hex().toUpperCase();
//> '64AF1132744506286E37EF66E6109279979B03B85BA2184ADB47061ED98FB647'
ステートハッシュとネームスペース情報のシリアライズ値のハッシュを比較
function getLeafHash(encodedPath, leafValue){
hasher = sha3_256.create();
return hasher.update(sym.Convert.hexToUint8(encodedPath + leafValue)).hex().toUpperCase();
}
function getBranchHash(encodedPath, links){
const branchLinks = Array(16).fill(sym.Convert.uint8ToHex(new Uint8Array(32)));
links.forEach((link) => {
branchLinks[parseInt(`0x${link.bit}`, 16)] = link.link;
});
hasher = sha3_256.create();
bHash = hasher.update(sym.Convert.hexToUint8(encodedPath + branchLinks.join(''))).hex().toUpperCase();
console.log(encodedPath + branchLinks.join(''));
console.log(bHash);
return bHash;
}
merkleBranches = state.merkleTree.branches.reverse();
leafHash = getLeafHash(state.merkleTree.leaf.encodedPath,nsInfoHash )
branchHash = leafHash;
bit = "";
for(let i = 0; i < merkleBranches.length; i++){
branch = merkleBranches[i];
merkleTreeBranchLink = branch.links.find(x=>x.link === branchHash)
branchHash = getBranchHash(branch.encodedPath,branch.links);
bit = merkleTreeBranchLink.bit + bit;
}
pathHash = bit + state.merkleTree.leaf.path;
stateHash = state.merkleTree.leaf.value;
rootHash = branchHash;//最後のbranchがroot
console.log(rootHash);
console.log(merkleRootHash)
console.log(pathHash);
console.log(nsIdHash);
console.log(stateHash);
console.log(nsInfoHash);
//3FAF3D1B282D7029B76C2FD9251465B1F6BF815CB69861E683F4F9F5E605C495
//3FAF3D1B282D7029B76C2FD9251465B1F6BF815CB69861E683F4F9F5E605C495
//64AF1132744506286E37EF66E6109279979B03B85BA2184ADB47061ED98FB647
//64AF1132744506286E37EF66E6109279979B03B85BA2184ADB47061ED98FB647
//38CC8173E8387ADE8198ED5BC3C8A0E391D38F943062F85F8606798C62F1F898
//38CC8173E8387ADE8198ED5BC3C8A0E391D38F943062F85F8606798C62F1F898
一部のネームスペースのみ検証することはできません。検証者はルートネームスペースに紐づくすべてのサブネームスペースを入手してstateHashを作成する必要があります。
マークルパトリシアツリーを使った検証についてはこちらもご参考ください。
さいごに
Symbolのネームスペースを実現するための技術は意外と多岐にわたり、ネームスペースを理解することができれば、
Symbolブロックチェーンを扱うアプリケーション開発に必要な技術の大部分を理解することができます。
ぜひチャレンジしてみてください。