C言語で多次元拡張ブロックチェーン chainmail

記事
IT・テクノロジー
ブロックチェーンとはなにか
さて、ココナラブログ、初回のものからはじめて、その6まで来ました。
前回のその5では、メモリー管理についてお話しました。

今回は、NFTでもっとも重要なブロックチェーンについてのお話です。ただし、ここでご紹介するのは、仮想通貨(暗号資産)などで使われているブロックチェーンではありません。むしろ、NFTに特化した多次元拡張ブロックチェーンで、名前は、chainmail (鎖かたびら)です。
そこで、まずは、ブロックチェーンとはなにか、ということを、さらっとおさらいしておきます。

まずは、ブロックチェーンが最初に脚光を浴びた仮想通貨ビットコイン(Bitcoin)のお話から。

簡単にいうと、通貨(お金)は、誰がどれだけ持っているか、ということが重要です。Aさんが10ビットコイン持っている、Bさんが50ビットコイン持っている、という話です。ですから、BさんがAさんに10ビットコイン渡し(送金し)たら、Aさんは20ビットコイン、Bさんは40ビットコインになりますね。だから、参加している人みんなの相互の送金情報(たとえば、BさんがAさんに10ビットコイン渡したという情報)を記録しておけば、だれがビットコインをどれだけ持っているか常に分かるわけですね。

送金情報は送金が行われるごとに、溜まっていくので、それが一定の量溜まったら、それを一つのブロック(送金情報の塊)にして、鍵をかけて中身が改竄されないようにして、前のブロックとしっかり繋ぐ、ってことです。で、このブロックの繋がったものがブロックチェーンと呼ばれるもので、それだけなんです。で、ブロックに鍵をかけるときに、重要な整数をみんなでコンピュータ回して探して、見つけた人には、(初期には)1ビットコイン(今では1/4だか1/8ビットコインだか)貰える、というのがマイニングと呼ばれるものです。で、このマイニングでビットコインが貰えるというのは、あまり本質的ではないわけです。重要なことは、送金情報の塊(ブロック)に鍵をかけて、塊(ブロック)を鎖(チェーン)のように繋げる、ということですね。

ということは、ビットコインについては、ブロックチェーンは一本だけしかありません。他のいろいろな仮想通貨(暗号資産)というものも、それぞれブロックチェーンは一本だけです。ただし、その一本のブロックチェーンがネットワーク上に多数コピーされて、みんなが常にみられるようになっています。
ということで、一般的なブロックチェーンの話はおしまいです。

NFTにおけるブロックチェーンとはなにか
このビットコインなどの仮想通貨で使われるブロックチェーン、べつに仮想通貨以外で使ってもいいじゃないか、というのが、NFTの発想です。
たとえば、Xという名前の仮想通貨があって、その仮想通貨のブロックチェーンは、Xブロックチェーンというのがあるわけですが、そのXブロックチェーンに、たとえば、あるデジタルコンテンツ(デジタルアートとか)の持ち主情報を入れてもいいじゃないか、という話。たとえば、デジタル絵師のCさんは、自分が描いたデジタル画像作品(これを画像Yとしましょう)に、特定のIDを与えたりします。で、このデジタルコンテンツのIDのことを、NFTというのです。もちろん、NFTに制作者(ここではCさん)のブロックチェーンXで使われているID(ウォレットのIDですね)も組込むことができます。

で、そのNFT(これを画像YのIDなので、NFT-Yとします)番号を基に、Dさんにデジタル画像を送った(売った/譲渡した)とします。その際、Dさんから仮想通貨Xで10Xだけ支払われたとしますね。そうしたら、Xブロックチェーンに、
「Cさんは、IDYの画像Y(NFT-Y)を、10XでDさんに売った」
という情報を書き込むわけです。
つまり、これによって、画像Yの所有権が、絵師のCさんからDさんに移動したということになります。

もちろん、デジタル画像Xは、コピーできますから、デジタル絵師のCさんは、所有権や著作権は保持したまま、画像Xのコピーを、Dさん、Eさん、Fさん、Gさんに渡すこともできるわけで、その場合は、仮想通貨で1Xで売ることもできます。そうすると、
「画像Yのコピーを1XでD、E、F、Gの四人に売った。」
ということをブロックチェーンXに書き込めば、その後、画像Yのコピーを誰がもっているのか、などの情報が記録されます。そして、コピーを買ったFさんが、Hさんに、0.5Xで画像Yのコピーのコピーを売って、ロイヤリティとして、原作者のCさんに0.5X支払ったとしたら、その内容をブロックチェーンXに書き込めばよいですね。
ここまでのポイントをまとめます。

1)デジタルコンテンツ(画像とか動画とかなんでも)にコンテンツ自体と制作者などの情報を合わせたID番号を与え、これをNFTと呼ぶ。
2)デジタルコンテンツの所有権や利用する権利などが移動したら、その権利が移動したことを、ブロックチェーンに書き込む。
3)その際に、権利の移動に伴い対価が支払われたなら、その対価もブロックチェーンに書き込む。

だいたいの場合、NFTの権利の移動(移譲)で対価の支払が発生するときは、使っているブロックチェーンの仮想通貨で支払うのが普通です。そもそもそのブロックチェーンはその仮想通貨の送金情報を記録するためのものですからね。

ということで、デジタルコンテンツをNFT化すると、その権利や所有権の人から人への移動が、ブロックチェーン内に書き込まれて、誰が権利をもっているかなどがトレース可能だから、不正コピーされず、また、転売などの場合に、もとの制作者にロイヤリティが支払われるようにできる、とかいう話なんですが、これ、全部、真っ赤なウソです。

仮想通貨については、その仮想通貨を誰がどれだけ持っているか、という情報が、その仮想通貨の専用ブロックチェーンに書かれているわけで、通貨を使って何かを買う、という場合、通貨の移動(送金)情報がブロックチェーンに書きこまれるわけですから、ブロックチェーンを離れて、通貨を使うことは絶対にできません。それは、銀行の預金通帳のコピーを通貨として使うことができない、というのと同じ事です。

でも、デジタルコンテンツは、別にブロックチェーン内に記録されたNFTから切り離しても、普通に流通します。たんにコピーして転送すればよいだけ。
たとえば、上の例のCさんが作った画像Yについて、そのコピーをNFTとして購入したEさんが、その画像Yをどんどん(不正に)コピーして、これをXブロックチェーンに書き込まない形で多数の人に配ってしまったら(不正配布)、画像Yのコピーがブロックチェーンに記録されずに、どんどん拡散してしまい、もとの制作者である絵師のCさんがそのコピーの持ち主をトレースしたり、コントロールしたり、ロイヤリティを得たりすることはできないわけです。
もちろん、Eさんは、Cさんとの契約違反になるから、Cさんは訴えることができるかもしれませんが、Eさんは「いや、そんなことしてません」とか言って逃げられるわけです。

というわけで、NFTというのは、ただ、参加する人がの善意があれば、デジタルコンテンツの流通が、ブロックチェーンに記録されて、あとからトレースできる、というものであって、べつにNFT化したことで不正コピーや裏取引が抑制できるわけでもないわけです。

多次元拡張ブロックチェーン chainmail
ええと、別に chainmailという多次元ブロックチェーンの仕組みが、上で述べたNFTの問題を解決する、とかいう話ではないです。ただし、chainmailは、基本的に一つのデジタルコンテンツが、いろいろな人に拡散していき、そこで、相場を形成して、取引が株式市場みたいに活発に取引されることを想定しているので、NFT化して、chainmailにしっかり取引履歴が記録されている状況で、chainmailから切り離して流通させようとすると、相場より安く流通するので、たぶん儲からないんで、損したくないなら、ちゃんとchainmail に取引を書き込んで不正をやめようぜ、という方向になるだろうなあ、という予感はします(あくまでも予感です)。

これからの説明でハッシュ値というのが出てきます。そこでハッシュ値について、簡単に説明します。ハッシュ値とは、ある長さのデジタルデータがあった場合、そのデジタルデータの中のいろいろな文字や数値などを、相互に足したり引いたり掛けたり割ったりして、計算した整数値のことです。だから、そのデジタルデータの中身がちょっとでも違うと、ハッシュ値も違ってきます。ハッシュ値の計算方法はいろいろありますが、一つのシステムでは、同じ計算方法を使うことにします。ですから、デジタルデータの中身からハッシュ値を計算して、デジタルデータにくっつけておくと、あとからデジタルデータを見た時に、ハッシュ値を再計算して、データにくっついているハッシュ値と比べて、同じならデータは改竄されていないし、違っていたら、データは改竄されている、ということがわかります。

では、chainmailについて書いていきます。
ちょっと複雑なので、図で説明しましょう。
Chainmail.jpg
ここでは、User AとUser Bという取引参加者(以降、Aさん、Bさんとします)が、Product X(以降、商品X)という商品を取引する話になります。そして、取引に必要な伝票の関係が示されています。
Aさんは、いまま発行した注文伝票すべてを、Aさんの注文伝票のチェーンに蓄積しています。Bさんも同じです。
1)Aさんは商品Xを買いたいので、Xの買い注文を発行し、Aさんの注文伝票チェーンに書き込みます。
→買い注文伝票には、発行者AさんのIDと、商品XのIDと、商品Xの希望購入価格と、買う数が書かれています。
2)Bさんは商品Xを売りたいので、売り注文を発行し、Bさんの注文伝票チェーンに書き込みます。
→売り注文伝票には、発行者BさんのIDと、商品XのIDと、商品Xの希望販売価格と、売る数が書かれています。
3)双方の主張する取引価格が一致したので、商品Xは約定伝票を発行し、商品Xの約定伝票チェーンに書き込みます。そして、その約定伝票を、Aさん、Bさん双方に送付します。
→約定伝票には、商品XのIDと、取引された数と、取引総額が書かれ、かつ買い注文伝票、売り注文伝票の写しが書かれています。
4)Aさんは、取引(支払)伝票を発行して、Aさんの発行済み取引伝票チェーンに書き込み、かつ、その取引(支払)伝票をBさんに送付します。
→取引(支払)伝票には、伝票発行者AさんのIDと、約定伝票の写しが書かれています。
5)Bさんは、取引(受領)伝票を発行して、発行済み取引伝票チェーンに書き込み、かつその取引(受領)伝票をAさんに送付します。
→取引(受領)伝票には、、伝票発行者BさんのIDと、約定伝票の写しが書かれています。
6)そして、Aさん、Bさんそれぞれが、相手から送られてきた取引伝票を、自分の受け取り取引伝票チェーンに書き込みます。これで取引完了です。

複雑なようですが、これは企業間で商品の取引をする際の一般的な流れそのものですよね。買う側は発注伝票をつくって発注し、売る側は受注伝票をつくって受注し、それで約定伝票を作って、売る側はそれを請求書として渡し、買う側は請求書の分を支払って、決済完了という感じです。
重要なのは、伝票チェーンがたくさんあることです。で、チェーンは、図にあるように、ハッシュ値で連鎖しています。

さて、上の画像の左端にあるように、伝票それぞれには、前の伝票のハッシュ値と、(その前の伝票のハッシュ値込みの)伝票の内容から計算されるハッシュ値の二つがあります。個々の伝票では、前のハッシュ値と内容からハッシュ値を計算して、それが伝票にあるハッシュ値と同じ値であれば、伝票は改竄されていないことになります。もしハッシュ値が合わなければ、伝票は改竄されていることになります。そして、ハッシュ値は伝票の発行時に計算されて、それが明示されています。さて、約定伝票には売り注文伝票と買い注文伝票の写しが含まれます。それぞれの注文伝票には中に前のハッシュ値とハッシュ値が両方書き込まれていますから、そのハッシュ値と注文伝票の内容がこみこみで約定伝票のハッシュ値が計算されます。よって、あとから注文伝票を改竄したら、約定伝票のハッシュ値も変化し、合わなくなりますよね。

そして、取引伝票には約定伝票と約定伝票に入っている売り買い注文伝票二つがこみこみなので、取引伝票のハッシュ値も約定伝票、注文伝票の中身に依存します。つまり、ハッシュ値が鎖のようになって伝票と伝票をつないでいて、どれか一つの伝票を改竄したら、それに関係する全ての伝票のハッシュ値をすべて変更しないと、いけなくなります。

さて、通常のNFTでのブロックチェーンは、もっと単純で、この chainmailの仕組みからすると、約定伝票がブロックチェーンに書き込まれているだけです。それも、NFTがらみだけでなく、そのブロックチェーンでの仮想通貨の送金に関するデータも込み込みで大きなブロックをつくり、それに鍵をかけていますが、例えば、NFTの取引をだれかが架空でつくって、それをブロックチェーンに書き込んでしまうと、知らないうちに、NFTで買った筈のコンテンツが盗まれる事もありえます。しかし、chainmailでは、その架空の取引は作れません。
からなず、取引参加者が、注文伝票を発行し、自分の管理下の注文伝票チェーンにハッシュ値こみで書き込んでありますから、それをチェックできます。

chainmailは「鎖かたびら」という意味の英語です。鉄などのリングをただ繋いだだけでなく、縦にも横にもつないで、鎖で網込まれたもので作った鎧(よろい)ですね。日本の戦国時代も甲冑の下に鎖かたびらを着ていたりしましたし、ヨーロッパの中世の騎士たちも、鎖かたびらを着ていました。ちなみに、電子メール(mail)が連鎖(chain)することもchain-mailといいますが、これは別ものです。

さて、これで、chainmailの基本は、ご理解いただけたかと思います。取引参加者それぞれが、発行した注文伝票チェーン、発行した取引伝票チェーン、そして、受け取り取引伝票チェーンを持っていて、かつ、商品ごとに約定伝票チェーンがあり、約定伝票には売り買いの注文伝票が入れ込まれ、取引伝票には約定伝票が入れ込まれ、ハッシュ値が縦にも横にも組み込まれているので、多次元ブロックチェーン(実際は多次元伝票チェーン)になっています。だから、これを、鎖かたびら(chainmail)、と呼ぶわけです。鎖かたびらで、各種の伝票をしっかり守っている感じです。ネーミングとしては、まあ、よかったかなと思います。

chainmailの実装 1)各種伝票の構造体
ということで、いよいよC言語で実装します。
まずは、伝票の構造体です。注文伝票も、約定伝票も、取引伝票も、すべて同じようにハッシュ値が二つ(前の伝票のハッシュ値と、自身のハッシュ値)ありますので、まず、全ての伝票に共通の部分を form型構造体で表現します。
----ここから----
typedef struct          form
{
//伝票のハッシュ値
  uint64                Hash;
//前の伝票のハッシュ値
  uint64                HashPrev;
//伝票を発行した時間
  int64                 TimeStamp;
//伝票のタイプ
  int32                 FormType;
//伝票発行の通し番号
  int32                 SerialNumber;
};
----ここまで----
これは、そのまんまですね。分かり易いと思います。TimeStampは、UNIX時間を使っています。FormTypeは、注文伝票、約定伝票、取引伝票といった違いを enum型で表したものです。
続いて、注文伝票です。
----ここから----
typedef struct          order
{
//まず先頭に、上のform構造体の実体をいれます。
  form                  Form;
//次に、商品のID(128ビット整数)です。
  uint128               ProductId128; 
//発行者のID(128ビット整数)です。
  uint128               IssuerId128;  
//発注する数です。
//買い注文なら、正の数(>0.0)で、売り注文なら負の数(<0.0)です。
  float64               Quantity;
//希望取引価格です。
  float64               BidPrice;
};
----ここまで----
これも、分かり易いですね。実際は、chainmailでは、売買の取引だけでなく、注文をキャンセルする注文や、手数料徴収注文、売り買いを伴わない物品の移譲注文、さらには、購入後の商品の発送注文などもあり、BidPriceの部分は、ユニオンで定義されています。ただ、今回は、簡略版で希望価格だけにします。

次に、約定伝票です。
----ここから----
typedef struct          contract
{
//注文伝票と同じく、form構造体の実体です。
  form                  Form;
//取引された商品の数です。
  float64               Quantity;
//取引総額です。
  float64               Cost;
//約定の元になった注文伝票の写しが二つあります。
//後行注文
  order                 OrderAccept;
/先行注文
  order                 OrderOffer;
};
----ここまで----
ここで、先行注文と後行注文の二つがありますが、これは、どっちの注文が先にあったか、ということです。商品販売では、先ず売る側が、売り注文をだし、その売り注文をうけて、買い注文が発生しますから、普通は売り注文が先行注文で、買い注文が後行注文になります。ただし、商品が品切れになっているとき(つまり、売り注文がないとき)、買い注文が先に出て来て、それを持っている人が売りに出す場合は、買い注文が先行注文で、売り注文が後行注文です。
すると、取引された商品の数Quantityは、後行注文の立場で考えます。後行注文が買い注文であれば、Quantityは正の数値。一方、後行注文が売り注文であれば、Quantityは負の数値になります。
取引総額 Costも同じように、後行注文が買い注文であれば、総額は支払なので、残高が減るから、負の数値、後行注文が売り注文ならば、総額は受け取りなので、残高が増えるから、正の数値になります。

最後に、取引伝票です。
----ここから----
struct          statement
{
// form 構造体
  form                  Form;
//伝票発行者 ID
  uint128               IssuerId128;
//約定伝票の写し
  contract              Contract;
};
----ここまで----
これだけですね。

chainmail の実装 2)ハッシュ値計算
さて、次に、ハッシュ値の計算をする部分を見ていきます。
ここで一つやっかいな問題があります。伝票の構造体には、128ビット整数、64ビット整数、64ビット浮動小数点数など、ビット幅の異る数値があります。ハッシュ値の計算では、これらの違いをきっちり行わないといけません。
構造体の中身を単なるバイト配列と見なして、頭からハッシュ値を計算すると、計算機システムによって、ハッシュ値がまるで違うことが起こるのです。
これは、エンディアン問題ですね。1バイトの整数、つまり、charとかunsigned charの場合(HOTPortでは、char, int8, uint8 などとしています)は、問題ありませんが、たとえば、2バイトの整数 short や usigned short (HOTPort では、in16, uint16としています)では、十六進数で表現した場合、0x1F2Aという数値の場合、メモリー上に配列されるときに、0x1F 0x2Aと配置するか(ビッグエンディアン)、0x2A 0x1Fと配置するか(リトルエンディアン)の二通りがあり、これは、コンピュータのCPUによって違います。そこで、バイト幅の異る整数は、それぞれのバイト幅でハッシュ値を計算するようにしないと、CPUによってハッシュ値が異なります。
通常のブロックチェーンでは、ブロックの中は文字列として扱うので、文字は char型で1バイトですから、全く問題ありません。しかし、HOTPortでは、文字列でなく、いくつかのバイト幅の数値からハッシュ値を計算するので、エンディアンの問題は避けて通れません。その代わり、HOTPortでは、伝票は種類によって、その構造体のサイズがしっかり決まってくるので、メモリー効率などはかなりよく、メモリー消費量は少く、またハッシュ値計算は速いです。

では、例として、64ビットの整数値のハッシュ計算をする部分と、128ビットの整数値のハッシュを計算する部分を見ていきます。
----ここから----
//引数の、Key64は、数値で64ビット幅の正整数です(uint64) です。
//次の HashPrev は、その前のデータをふくめたハッシュ値です。
uint64                          calcHash_uint64                (uint64          Key64,          uint64          HashPrev)
{
//一旦128ビットの整数に変換します。
  uint128       Hash128        =HashPrev;
//Hash128に、63ビット長で最大の素数(MaxPrimeU2PN[63])を掛け、
//そこに、Key64を足し、その全体を64ビット長で最大の素数で割った
//余りを求めます。
  Hash128                     =(Hash128*MaxPrimeU2PN   [63]    +Key64) %MaxPrimeU2PN   [64];
//その数値の下64ビットを値として返します。
  uint64        RetVal        =(0xFFFFFFFFFFFFFFFFUL   &Hash128); 
  return  RetVal;
}
----ここまで----

次に、128ビットの整数値のハッシュを計算する部分です。
----ここから----
//今度は、引数のKey128は、128ビットの正整数です。
uint64                          calcHash_uint128               (uint128         Key128,         uint64          HashPrev)
{
  uint128       Hash128        =HashPrev;
//まず、Key128の上位64ビットのハッシュ値を計算します。
  uint64        U64           =(0xFFFFFFFFFFFFFFFFUL  &(Key128>>64));
  Hash128                     =(Hash128*MaxPrimeU2PN   [63]    +U64)  %MaxPrimeU2PN   [64];
//続いて、Key128の、下位64ビットのハッシュ値を計算します。
  uint64        L64           =(0xFFFFFFFFFFFFFFFFUL   &Key128);
  Hash128                     =(Hash128*MaxPrimeU2PN   [63]    +L64)  %MaxPrimeU2PN   [64];
  uint64        RetVal        =(0xFFFFFFFFFFFFFFFFUL   &Hash128);                               return  RetVal;
}
----ここまで----
重要なことは、演算子>>で表されるシフト演算は、エンディアンとは無関係ですから、このコードであれば、CPUでエンディアンが違っていても、同じハッシュ値が計算できます。

それでは、伝票の頭についている、form構造体の部分のハッシュ値計算です。
----ここから----
//計算するformの構造体へのポインタがThis
//一つ前のハッシュ値がHashPrevです。
uint64    form_hashCalc (form*           This,           uint64          HashPrev)
{
 //前のハッシュ値のハッシュ値を計算
  uint64        Hash64 =calcHash_uint64        (This  ->TimeStamp,      HashPrev);
//伝票のタイプのハッシュ値を計算
  Hash64                   =calcHash_uint64        (This  ->FormType,       Hash64);
//伝票の連番のハッシュ値を計算して、それを返します。
  Hash64                    =calcHash_uint64        (This  ->SerialNumber,   Hash64);
  return  Hash64;
}
----ここまで-----
This->FormTypeは、実は32ビットですが、数値として送られるときに、64ビット整数に変換されるので、エンディアンの問題はありません。

つぎに、注文伝票 order の場合のハッシュ値計算です。
----ここから----
//order型の伝票ですが、引数はform型ポインタになっています。
//それは、関数テーブルに登録する際の関数の型との関係です。
uint64     order_hashCalc  (form*           This,           uint64          HashPrev)
{
//まず、頭のform構造体のハッシュ値を計算
  uint64        Hash64         =form_hashCalc          (This,   HashPrev);
//Thisを、order*に型変換します。
  order*        OrderPtr       =(order*)                This;
//その後、順次、要素の数値のハッシュ値を計算していきます。
  Hash64     =calcHash_uint128       (OrderPtr      ->ProductId128,   Hash64);
  Hash64     =calcHash_uint128       (OrderPtr      ->IssuerId128,    Hash64);
  Hash64     =calcHash_float64       (OrderPtr      ->Quantity,       Hash64);
  Hash64     =calcHash_float64       (OrderPtr      ->BidPriceBS, Hash64); 
return  Hash64;
}
----ここまで----

calcHash_float64は、64ビット浮動小数点数(double型)に対応する関数です。これも、整数型に変換するときに、注意が必要なので、別の関数で用意しています。
さて、こうして、伝票のハッシュ値が計算できるようなったので、あとは、これらの伝票の配列を作ったら、それが伝票チェーンになります。まあ、伝票チェーンをブロックチェーンと呼んでよいかどうかは、別として、通常の文字列ベースの大きいブロックチェーンよりは、遥にガチガチに堅いチェーンを作り、しかも、複数の伝票チェーンと横にもハッシュ値で繋がっているので、まず改竄はできない伝票保管方法です。これが chainmailなのです。

まとめに変えて
さて、これでブログも、六回目ですね。
長かったですが、最後まで読んでいただき、ありがとうございます。
ここでご紹介したコードもふくめて、必要なコードを切り出して御提供するサービスをしております。
実際は、ブログでご紹介しているコードよりは複雑でいろいろ絡まっているのですが、サービスのときは、うまいこと切り出して行こうと思いますので、サービスともども宜しくお願いいたします。

で、次回は、HOTPortのCMSの話になるかと思います。投稿されたタイトル(デジタルコンテンツ、文書、などなど)を管理する部分です。実際のコードでは、この部分だけで1万行を肥えていて、chainmailの部分よりも遥に大変なんで、たぶん、数回にわけて連載になるとおもいます。
では、またの連載まで。













サービス数40万件のスキルマーケット、あなたにぴったりのサービスを探す