Symbolブロックチェーンのネームスペースを完全に理解する。

今回はSymbolブロックチェーンのネームスペースについて解説します。


公式ドキュメントはこちらです。

https://docs.symbol.dev/ja/concepts/namespace.html

まずはSymbolで定義されるネームスペースの特徴について、初心者と上級者にわけてざっくりと説明します。

###### 初心者の方
アドレスやモザイクトークンに分かりやすい名前を付けることができ、**送金ミスを減らす**ことができます。
ネームスペースはインターネットのドメインと同様に期間でレンタルします。
XYMには**symbol.xym**という分かりやすいネームスペースがあらかじめ定義されています。

###### 上級者の方
ウォレットはネームスペースを**名前解決することなく**そのままトランザクションに署名できます。
トランザクションが承認されたときの**レシートを参照する**ことで、そのときのネームスペース所有者を特定できます。
改ざん不可能（検証可能）なDNSが実現可能です。


# 基本操作

ネームスペースの作成、更新、変換まわりを抑えておきましょう。

### 生成

```js
nsXembookId = new sym.NamespaceId("xembook");

>NamespaceId {fullName: 'xembook', id: Id}
>  fullName: "xembook"
>  id: Id {lower: 646738821, higher: 2754876907}
```

### 更新

##### 有効期限の確認

```js
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 (日本標準時)
```

##### 生成更新トランザクション

###### ルートネームスペース
```js
namespaceTx = nem.NamespaceRegistrationTransaction.createRootNamespace(
    nem.Deadline.create(epochAdjustment),
    "xembook",
    nem.UInt64.fromUint(180000),
    networkType
);
```

###### サブネームスペース
```js
subNamespaceTx = nem.NamespaceRegistrationTransaction.createSubNamespace(
	nem.Deadline.create(epochAdjustment),
	"tomato",
	"xembook",
	networkType,
);
```


### 変換

ネームスペース文字列からIDが一意に決定されます。その値をHexやUInt値に変換することで、署名や参照のためのキー値として利用することもできます。

##### HEX値

```js
nsXembookId.toHex();
>'A43415EB268C7385'

nsId = sym.NamespaceId.createFromEncoded("A43415EB268C7385");
>NamespaceId {id: Id}
```

##### UInt値

```js
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
                - ルートネームスペースを指定
        - AliasType
            - Address = 2
            - Mosaic = 1
            - one = 0
        - registrationType
            - RootNamespace = 0
            - SubNamespace = 1
        - pageNumber,pageSize,order,ownerAddress
            - 共通

sdkドキュメントはこちら

https://symbol.github.io/symbol-sdk-typescript-javascript/1.0.3/classes/NamespaceHttp.html


# リンク

作成したネームスペースはアカウントアドレスやモザイクトークンに割り当てることができます。

### Mosaicにリンク

```js
mosaicId = new sym.MosaicId("6BED913FA20223F8");
mosaicAliasTx = sym.AliasTransaction.createForMosaic(
    sym.Deadline.create(epochAdjustment),
    sym.AliasAction.Link,
    namespaceId,
    mosaicId,
    networkType
).setMaxFee(200);
```

### Addressにリンク

```js
address = nem.Address.createFromRawAddress("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ");
addressAliasTx = sym.AliasTransaction.createForAddress(
	sym.Deadline.create(epochAdjustment),
	sym.AliasAction.Link,
	namespaceId,
	address,
	networkType
).setMaxFee(200);
```


# ネームスペースをトランザクションに使用する

例えば、TransferTransaction.createは以下のように定義されます。

```js
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のままでトランザクションを署名できます。

```js
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ままでトランザクションを署名できます。


```js
nsXembookId = new sym.NamespaceId("xembook");
tx = sym.TransferTransaction.create(
	sym.Deadline.create(epochAdjustment),
	nsXembook, 
	[],
	sym.EmptyMessage,
	networkType
).setMaxFee(200);

```

# メタデータの付与

アカウントやモザイクと同様にネームスペースにもメタデータを付与することができます。

```js
createNamespaceMetaTx = await metaService.createNamespaceMetadataTransaction(
    nem.Deadline.create(epochAdjustment),
    networkType,
    publicAccount.address,//ネームスペースの作成者
    namespaceId,
    key,value,
    publicAccount.address //メタデータの登録者
).toPromise();

```

企業であればドメイン登録に付随したデータ、あるいは認証アカウント(Issuer)からの署名、
ビットコインやイーサリアムのアドレスを記録しておけば面白いかもしれませんね。


# 署名時のネームスペースを解決する

現在ネームスペースがリンクしているアドレスやモザイクではなく、トランザクションがブロックチェーンに記録されたときに何とリンクされていたかを調べる方法です。

```js
state = await receiptRepo.searchAddressResolutionStatements({height:179401}).toPromise();
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29187/e68ae533-33ca-56ab-db62-9aaa5d06a4b0.png)

```js
nsBallId = new sym.NamespaceId("xembook.game.ball");
state = await receiptRepo.searchMosaicResolutionStatements({height:181769}).toPromise();
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29187/a6881659-1395-e110-58eb-1e4d072883c2.png)

ネームスペースを利用するときに、注意しないといけないことに、
トランザクションを署名した時のネームスペースの所有者と、現在のネームスペースの所有者が異なる可能性がある、という点があります。
サービス提供者にとって、ネームスペースを使えば気軽にアドレスを乗り換えられる利点がありますが、サービスが廃止された後、スキャマーによってネームスペースを取得された場合は要注意が必要です。

# マークルパトリシアツリーでネームスペースを検証する

署名を必要とせず、ネームスペースの参照のみ行いたい場合は問い合わせ先ノードが悪意ある第三者に乗っ取られている可能性を考慮する必要があります。そういった場合は、マークルパトリシアツリーを使うことで（ファイナライズブロック情報が他所から取り寄せて）ネームスペースの正当性を検証することができます。

##### ブロックヘッダーのハッシュ値とマークルパス情報を取得
```js
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);
})
```

##### ネームスペース情報からハッシュ値生成

```js
nsInfo = await nsRepo.search({level0:nsXembookId}).toPromise();
hasher = sha3_256.create();
nsInfoHash = hasher.update(nsInfo.data[0].serialize(nsInfo.data)).hex().toUpperCase();

//>'38CC8173E8387ADE8198ED5BC3C8A0E391D38F943062F85F8606798C62F1F898'
```

##### ネームスペースIDからパスハッシュ値生成

```js
hasher = sha3_256.create();    
hasher.update(sym.Convert.hexToUint8Reverse(nsXembookId.toHex()));
nsIdHash = hasher.hex().toUpperCase();
//> '64AF1132744506286E37EF66E6109279979B03B85BA2184ADB47061ED98FB647'
```


##### ステートハッシュとネームスペース情報のシリアライズ値のハッシュを比較
```js
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を作成する必要があります。

マークルパトリシアツリーを使った検証についてはこちらもご参考ください。

https://qiita.com/nem_takanobu/items/83e05553e2450c09c84f


# さいごに

Symbolのネームスペースを実現するための技術は意外と多岐にわたり、ネームスペースを理解することができれば、
Symbolブロックチェーンを扱うアプリケーション開発に必要な技術の大部分を理解することができます。

ぜひチャレンジしてみてください。