Edited at
nemDay 10

NEMDNSについて。

なぜかツイッターアカウントが凍結されてしまったそらりすです。

とりあえずTwitterアカウント連携していたものに軒並みログインできなくなって大変困っております。

QiitaもTwitter連携しておりメールアドレスがどれに紐づいているかわからなかったのでとりあえず再作成しました。

その結果アドベントカレンダーで書こうと思っていたNEM-DNS周りのお話ができなくなってしまったのですが自分から枠を奪って書かせていただきます。


NEM DNSとは

どのバージョンからはあまり覚えていないのですが、Nanowalletのサービス欄にいつの間にか増えていました。

image.png

https://github.com/aenima86/NEM-DNS

どうやら「namespaceを使ってDNSみたいなことをしよう。」というもののようです。

Namespaceをもとにポインターアドレスというものを作成し、そこに対して書き込まれたメッセージの中で、Namespaceの所有者が書き込んだ最新のメッセージをDNSの情報としています。


実装

なぜかNanowalletにあるNEM DNSの実装とGithubにあるNEM DNSの実装は挙動が少し違いました。コードを見る限りは同じなんですけどね。

なので少しコードを変えています。

また、コードはNEM-SDKを使用しています。

まず、Namespaceの所有者を調べます。

async function getNamespaceOwner(namespace) {

let endpoint = nem.model.objects.create("endpoint")("http://xxx.xxx.xxx.xxx", 7890);
let res = await nem.com.requests.namespace.info(endpoint, namespace);
return res.owner;
}

{

"owner": "NCWIGGBM5JJB24JI4GYNDP2XNKDPK2GMLMWSBGYD",
"fqn": "soralis",
"height": 1751425
}

soralisの所有者はNCWIGGBM5JJB24JI4GYNDP2XNKDPK2GMLMWSBGYDです。

次に、ポインターアドレスを取得します。

function getPointerAddress(namespace){

let passphrase = sha256(namespace);

//githubにはない部分
passphrase = nem.utils.convert.hex2ua(passphrase);
passphrase = nem.utils.convert.ua2words(passphrase, 32);
//ここまで

let privateKey = nem.crypto.helpers.derivePassSha(passphrase, 1).priv;
let keyPair = nem.crypto.keyPair.create(privateKey);
let publicKey = keyPair.publicKey.toString();
let address = nem.model.address.toAddress(publicKey, nem.model.network.data.testnet.id);
return address;
}

soralisのポインターアドレスはNCDV4PXSQ6JM5E3QDQD3FZY7OKS2W3LLXDVGINASです。

(上の2行がないとNBZJ2S57SUCOCQ7OVEWFFCUWFANEZXLCYUHJPFHNになりました。)

次に、ポインターアドレスに対して送信されたトランザクションを取得します。


async function getPointerTransactions(namespace) {
const endpoint = nem.model.objects.create("endpoint")("http://xxx.xxx.xxx.xxx", 7890);
const address = getPointerAccount(namespace);
let res = await nem.com.requests.account.transactions.incoming(endpoint, address);
return res;
}

{

"data": [
{
"meta": {
"innerHash": {},
"id": 2505688,
"hash": {
"data": "a483c347b6527a17188dfd4128b24f8ee56fe5c44150b31a64a42d6db68bfd82"
},
"height": 1751440
},
"transaction": {
"timeStamp": 106027303,
"amount": 0,
"signature": "0b72b4197707980114c1e8d5d981a2af2ffc5bbd874a4453fd3a143999c107a62a52f8c749fe07842e77490950ae9ae6ce52d94315ef04142783b6cfec92be05",
"fee": 200000,
"recipient": "NCDV4PXSQ6JM5E3QDQD3FZY7OKS2W3LLXDVGINAS",
"type": 257,
"deadline": 106113703,
"message": {
"payload": "7b22646e73223a22796573222c22697031223a22736f72616c69732e6f7267222c2022656d61696c223a22736f72616c69732e6e656d40676d61696c2e636f6d227d",
"type": 1
},
"version": 1744830465,
"signer": "c47171b8feeaa6f8938182f9ac287f26cb1cbe828fac2726112c010b70465f7e"
}
},
{
"meta": {
"innerHash": {},
"id": 2505648,
"hash": {
"data": "f6e4bb00710e312e779160207ff2d0cfa2bce3ed17d5e494247e6d3c0be825f6"
},
"height": 1751429
},
"transaction": {
"timeStamp": 106026701,
"amount": 0,
"signature": "724e76401f73a8497597de1aaf8ff86e6e05f41d2bdb50b1ad4eb7a7701733af9d2a5f791970e407fb37e33db56dcb4f53fa24210cb820118d2b23ff86d0e601",
"fee": 150000,
"recipient": "NCDV4PXSQ6JM5E3QDQD3FZY7OKS2W3LLXDVGINAS",
"type": 257,
"deadline": 106113101,
"message": {
"payload": "7b22646e73223a22796573222c22697031223a223132372e302e302e31222c20226f74686572223a2254657374227d",
"type": 1
},
"version": 1744830465,
"signer": "c47171b8feeaa6f8938182f9ac287f26cb1cbe828fac2726112c010b70465f7e"
}
}
]
}

この中から最新のDNSデータを取得していきます。

function convertFromHex(hex) {

let hex = hex.toString(); //force conversion
let str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}

function getAccountFromPublickey(publicKey) {
const res = nem.model.address.toAddress(publicKey, nem.model.network.data.mainnet.id)
return res;
}

function isJSON(arg) {
arg = (typeof arg === "function") ? arg() : arg;
if (typeof arg !== "string") {
return false;
}
try {
arg = (!JSON) ? eval("(" + arg + ")") : JSON.parse(arg);
return true;
} catch (e) {
return false;
}
}

async function getDNSdata(){
const ownerAcc = await getNamespaceOwner(namespace)
const incomingTransactions = await getPointerTransactions(namespace);
if (incomingTransactions.data.length <= 0) {
return "not found";
}
for (var i in incomingTransactions.data) {
let senderAcc = getAccountFromPublickey(incomingTransactions.data[i].transaction.signer);
if (senderAcc == ownerAcc) {
break;
}
}
let senderAcc = getAccountFromPublickey(incomingTransactions.data[i].transaction.signer);
if (senderAcc != ownerAcc) {
return "not found";
}

let payload = convertFromHex(incomingTransactions.data[i].transaction.message.payload);

if (!isJSON(payload)) {
return "not found";
}
payload = JSON.parse(payload);
return payload;
}

メッセージが正しい形式じゃない場合や持ち主以外がメッセージを送ってきている場合があるのでしっかりチェックしましょう。

また、書いてありませんがページング処理も必要になってくるかもしれません。

{"dns":"yes","ip1":"soralis.org", "email":"soralis.nem@gmail.com"}

これで書き込まれたDNSのデータが取得できます。

あとはこのデータを煮るなり焼くなり隙にすればOKです。

ipXにIPアドレスかドメインが入るのでそこにリダイレクトしろ~みたいな感じですね。


疑問点や今後やってほしいこと

DNS名乗るならAとかCNAMEとかちゃんとしてほしいです。ip1にCNAMEっぽいのとIPが入る仕様はやめてほしい。

メッセージに平文で入っているのでポインターアドレスが分からなくてもDNSの中身が分かってしまう。

DNSとしてこれってどうなのって思っていまいます。

→メッセージをPAの秘密鍵で暗号化すればいいのでは?

へんなChrome拡張作るんじゃなくてDNSサーバーを公開してほしい。そうじゃないと実用性がないです・・・

私もとりあえずDNSサーバーもどきを書いたんですが動かないので誰か助けてほしい。

https://github.com/soralis-nem/nem-dns-proxy

とりあえず以上です。