NBAデータツールの2020-21バージョンを公開しました。なんか瞬く間に新シーズンが始まってしまって調子狂いますよね。
ツールの取得場所は以下になります。現在の最新バージョンは1.4です。
詳しい取得方法、使用方法などは以下の記事を参考にしてください。
注意事項
この記事を書いている現時点(2020/12/25)では、まだクラッチタイムを経験していないチームがある関係でデータの取得後にエラーが出る場合があります。使用には特に問題はありませんが、ご留意ください。
NBAデータツールの2020-21バージョンを公開しました。なんか瞬く間に新シーズンが始まってしまって調子狂いますよね。
ツールの取得場所は以下になります。現在の最新バージョンは1.4です。
詳しい取得方法、使用方法などは以下の記事を参考にしてください。
この記事を書いている現時点(2020/12/25)では、まだクラッチタイムを経験していないチームがある関係でデータの取得後にエラーが出る場合があります。使用には特に問題はありませんが、ご留意ください。
2020-21シーズンのBリーグが始まってから早1か月以上ですが、ようやくbleaguerに今シーズンのデータを入れました。後述するちょっとした問題はあるのですが、一応使える状態にはなっていると思います。bleaguer(ビーリーグアール)とは何ぞやという人は以下の記事を参照してください。
今シーズンからBリーグのページに変更があり、私を含めスクレイピング勢は多少の変更を求められることになりました。コロナ禍による生活の変化、家族のスケジュールの変更によるあれこれですっかり時間がかかってしまいましたが、とりえあずこのまま今シーズンのデータは取得できそうです。
この記事では今季の対応をしていく中で気が付いた、ちょっとした四方山話をしたいと思います。特に有用な話はないと思います。
スタッツページ(だけじゃないけど)のロードがすごく遅くなった印象です。私はあまりこのページは利用していないのですが、開いてみると表示まで2-3秒はコンスタントにかかる印象です。
この現象と、色々と人から聞いた話から察するに、今季からBリーグはページ(フロントエンド)を変更しただけではなく、データベース(バックエンド)にも何かしらの変更があった可能性が高いと思います。その関連でページがデータを取得する方法が変わり、遅くなってしまったのではないでしょうか。
ページの表示速度というのは基本的には絶対正義、つまり誰しもが望むことだと思いますので、ここは改善して欲しいと思います。
これも私は最近やらなくはなったのですが、スタッツページをどかんとExcelにコピーするようなことがとてもやり辛くなりました。スタッツのテーブルを全選択するのも何かやり辛いですし、Excelにコピーしても1選手ごとに空行が2つ付いてくる構造になっているので、いちいちそれを取り除く作業をしなくてはなりません。
bleaguerなんかを使わなくても、スタッツのページの値をExcelでイジイジするだけでも相当に楽しいので、もちろん一部限定的なファンからの要望ではあるとは思いますが、スタッツのページはなるべくシンプルに保って頂くと嬉しいですね。少なくてもあまり装飾が必要なエリアではないと思っています。
これは別に私にとっては問題ではないのですが、スタッツからいつの間にかDUNKが消えております。
先日Twitterでダンクがスタッツなのか、最高記録更新を宣伝するものなのかという議論が交わされていましたが、実はページの方ではひっそりと今シーズンからダンクの項目が消えています。
先日の宣伝にあるようにBリーグとしてはダンクの数は数えているようで、それはPlay by Playなどに記録があることからも明らかです。おそらく上述の話題と関連するのですが、今季より何か新しいバックエンドがスタッツの記録に用いられていて、その中にはDUNKの項目が存在せず、既存のBリーグのデータベースからそちらに値を移すときにDUNKを捨てざるを得ないのではないでしょうか。あくまで推測ですが。
私はここまで5シーズンすべてのゲームのスクレイピングをしてきたのですが、その中でいくつか例外的な扱いをする必要があるゲームというのはありました。ただ今季はまったくスクレイピングが出来ないゲームが現れてしまいました。以下のゲームです。
GAME REPORTがページに表示されておらず、かつページの内部にも値が見つからない為にスクレイピングが出来ません。またページの構造が他のゲームと違っているのか、BOX SCOREも他のゲームと同様のスクレイピングが出来ませんでした。このゲームについては手入力でbleaguerにデータを入れる必要がありますが、GAME REPORTにしかない項目については空白としておくしかありません。
アルバルク東京のデション・トーマス、合流から早速プレーで存在感を発揮していましたね。間違いなく今季の注目選手のひとりとなりそうなのですが、実は一部のデータクラスタでも注目を集めています。理由は以下で一目瞭然なのですが、各選手に割り当てられるプレイヤーID、トーマスに唐突にすごく大きな値が割り当てられているのです。
ほんのちょっと大きいだけならさして問題ではないのですが、この数(5100000003)って2の32乗(4294967296)より大きいんですよね…こうなるとこのIDを保存する型の変更を求めれる可能性が出てきて、例えば私のbleaguerもRのinteger型を使っていたので、この値の為に変更する羽目になりました。
NBAの方でもそうだったのですが、経験上(とか言って今回全然対応はできていないですが)人様のIDなどを流用するときは数値に見えても念のため文字列型の定義としておくのが確実ですね。場合によっては頭に0が付いたりとか、今回のように唐突に大きな値が来たりとか、突然アルファベットが入ってきたとかそんなことがありえますからね。
そう言えばこれを書いていて思い出したのですが、bleaguerでは背番号もinteger型で管理してしまっており、背番号0と背番号00を区別できないというマイナーな問題も発生してしまっています(反省)。
私は以前botを制作し、以下のようなツイートで毎朝6:30頃にその日の日程をお知らせしています。
本日(2020/11/7)の #Bリーグ (B1)
北海道 – 千葉 (14:05)
A東京 – 滋賀 (15:05)
横浜 – 名古屋D (15:05)
SR渋谷 – 京都 (17:05)
新潟 – 宇都宮 (17:05)
秋田 – 大阪 (18:05)
川崎 – 琉球 (18:05)
島根 – 三遠 (18:35)#botです— 増田林太郎 Rintaro Masuda (@rintaromasuda) 2020年11月6日
これをやる為にBリーグのスケジュールページにアクセスし、第1節から最終節までのすべてのスケジュールを取得してからその日の分だけを抜き出していたのですが、今シーズンからそれが出来なくなってしまいました。
現在はある節のすべてのゲーム、もしくはあるチームのすべてのゲームを表示する事はできるようですが、すべてのチームのすべての節のゲームは表示できないようです。
とりえあず何も考えずにアクセスすると、デフォルトで今節のゲームが表示されるので、それを利用してbotは直しましたが、ざっと向こうしばらくのゲームのスケジュールを眺めることが面倒くさくなってしまった印象です。
小言みたいになってしまうのですが、Bリーグはサイトにしろアプリにしろ、『閲覧者はどこか特定のチームを応援している』という前提に依りすぎている気がしています(もちろんそういう閲覧者の方が多数派だとは思いますが。)私のように特定のチームを応援しておらずその時々で気になったゲームを見る立場だと、ときどきページやアプリのひょんな落とし穴に落ちる感じがあります。小言ですけど。
なんか文句を言っているような記事になってしまいましたが、現状は将来のシステム的な部分、IT的な部分でのステップアップの為の移行期間、痛みの期間だと信じてますので、基本的にはBリーグさんにはそのまま進んでもらいたいと思っております。頑張ってください。
恐縮ながら生まれて初めてご献本いただいてしまいました。昔からブロガーの人が献本された書籍を紹介しているのを見たりしていて「そんな世界がこの世にあるものなんだな」と思ったりしていたのですが、まさか自分が献本いただく日が来るとは。これからはプロブロガーを名乗りたいと思います(笑)
冗談はさておき、こちらの書籍は流通経済大学スポーツ健康科学部の准教授であります小谷究さんと、現在はバスケ男子日本代表のテクニカルスタッフとして活躍されている前田浩行さんによって著されたバスケットボールの戦術解説書です。日本バスケの情報を追っている方であれば、少なくても何処かでお二人の名前を目にされた方は多いと思います。
私は小谷さんが佐々木クリスさんと著された書籍を以前に読ませて頂きましたし、ツイッターのアカウントもフォローさせて頂いておりました。
またこの書籍の出版元であるベースボール・マガジン社のピックアンドロール解説書も読ませて頂いたことがあります。ちなみにこちらは元琉球ゴールデンキングスHC、現宇都宮ブレックスACの佐々宣夫さんの著書です。
こちらのバスケットボール戦術学でも佐々さんの著書同様、3Dによる分かり易い図解が使われておりますが、どちらかというと佐々さんの著書よりは文章での説明を中心に据えている印象を持ちました。
さて、ソフトウェアエンジニアならではの感想になるかもしれませんが、私がこの書籍のを一読して持った感想は「なんかオフェンスとディフェンスの攻防って、ハッカーとセキュリティ担当者の果てしない攻防みたいだな」というものです(ちなみにソフトウェアの世界では必ずしもハッカーという言葉を悪者の意味で使わないのですが、ここではそのような意味で使っております。)
ハッカーが何か悪い事をしたとします。すると、セキュリティ担当者はそれをされないように更にセキュリティを強化します。そうするとまたハッカーが新しい手を考えます。そしてセキュリティ担当者も更に新しい策を打ちます。こうして世の中のセキュリティに関する技術力は向上していきます。大学でコンピューターサイエンスを学んでいた頃、ひとりの教授が「優秀な守り手になりたいのであれば、攻める側にもなれる技術力と知識が必要」と話していたのを思い出します。
バスケットボールにおけるオフェンスとディフェンスの進化も同様の道を辿っているはずで、本書ではそれがページの流れと共に説明されています。例えばこんな感じです。
オフェンス側はインサイド選手2人を使ってカーテンスクリーンを作り、シューターにワイドオープンのスリーを打たせようとする
↓
ならばシューターのディフェンスはスクリーンとシューターの間に割って入り、シューターをノーマークにさせない
↓
ならばシューターは裏をかいて、スクリーンを使うと見せかけて逆方向に動き、ゴール下でノーマークになる
↓
ならばシューターのディフェンスはシューターを後ろから追いかけ、ノーマークの瞬間を最小限に抑えつつも裏をかかれないようにする
↓
ならばシューターはボールをもらった後にシュートにいかず、スクリーンを活かしてゴール下にドリブルでアタックする
↓
ならばスクリーナーのディフェンスのひとりが一時的にドリブルでの侵入を防ぐ役割をする
↓
ならばオフェンスは(永遠に続く…
そっちがそう来るならこっちはこうする。こっちがこう出ればあっちはああする。そんな対策のし合いのプロセスをひたすら繰り返して、バスケットボールの戦術は日々進化しているんだと思います。この書籍ではそうして磨き上げられてきた戦略の進化を追体験することが可能になっており、そこが一番のセールスポイントであると個人的には思いました。プレイヤーやコーチはもちろんの事、ただ観戦を楽しみたい私のような1ファンにも嬉しい内容だと思います。
ただひとつだけ追記しておきたいのは、ここで説明されている戦術や戦略を暗記してそして練習して、それを実際にそのまま試合で使ってみて下さいというのは著者たちのメッセージではないと思います。もちろんそれは重要な第一ステップだとは思いますが、著者たちの願いはここで説明されている知識をもとに、ひとりひとりのプレイヤーやコーチが考え、試行錯誤し、自分たちにとって最適なものを掴む、そういう過程を踏んでくれることだと思います。
生まれて初めてご献本頂いたんだからベタ褒めするしかないやろー、と思ってはみたものの、それだとあんまり面白くないと思ったのでいくつか思いついたことを書いてみたいと思います(笑)
これは読者に考えさせる為に意図的にやっていないのかもしれませんが、各箇所で何がポイントなのか、冒頭や末尾に箇条書きのまとめでもあると頭に入りやすいかと思いました。もしくはポイントとなる文章を太字にするだけでもいいかもしれません。いずれにせよ記憶への定着の向上、また書籍の検索性の向上の観点からも、まとめがあると助けになるのではと思います。
また書籍を超えた話題にはなってしまいますが、やはりこのように3Dによる絵図を活用するのであれば、動画で観たらもっと分かり易いなと思ってしまったのは正直なところです。動画となると制作のコストも上がりますし、一方でマネタイズが難しくなるなどの問題もありますが、特に若い世代においては動画コンテンツの方が好まれるのではないかと思いました。
以上になります。ご献本まことにありがとうございました。このような書籍が今後とも発売され、プレイヤーやコーチの手に渡り、日本のバスケットボールがさらに前進することを願います。
(2017年10月6日にQiitaにて執筆した記事をこちらに転載しています。)
using NBitcoin;
using QBitNinja.Client;
using QBitNinja.Client.Models;
using System;
namespace NBitcoinTest1
{
class Program
{
static void Main(string[] args)
{
QBitNinjaClient client = new QBitNinjaClient(Network.Main);
var transactionId = uint256.Parse("7008807adb713205d01d378f3d25f1bc5e06bda7d30b526c2b9d2c018cfa08b4");
GetTransactionResponse transactionResponse = client.GetTransaction(transactionId).Result;
var tx = transactionResponse.Transaction;
tx.Inputs.ForEach((txIn) =>
{
var outPoint = txIn.PrevOut;
Console.WriteLine("Transaction hash ->" + outPoint.Hash);
Console.WriteLine("The index of the transaction's outputs ->" + outPoint.N);
Console.WriteLine();
});
}
}
}
出力は以下のようになります。 前回も書いたとおりこのトランザクションには10個ものインプットが結び付けられています。そしてそのインプットはそれぞれ別のトランザクションを指すハッシュの値と、それからインデックス番号を持っています。 ビットコインのブロックチェーンとはお金の移動を記した台帳です。そしてあなたが(正確に言うとあなたのウォレットが)ビットコインを所有しているとして、その所有とはすなわちあなたが受取人になっているトランザクションアウトプットの中で、まだあなたが誰にも渡していないものということです。 このようなまだ未使用なトランザクションアウトプットのことをUTXO(Unused Transaction Output)と呼びます。あなたの所有しているビットコインの総量とは、即ちあなたが受取人になっているUTXOの合計量です。 上記の10個のインプットは、このトランザクションでビットコインを堀江さんに送った方の(その時点での)UTXOだったということになります。その方の使用したウォレットが、送信するビットコイン量に足るようにブロックチェーンから集めてきたUTXOです。 前回述べた通りひとつのトランザクションに複数のトランザクションアウトプットがある場合があるので、上記のインプットの情報にはインデックスも付いています。ちなみにこのトランザクションハッシュとアウトプットインデックスの組み合わせをアウトポイントと呼ぶようです。 確認の為に、上記10個のトランザクションアウトプットの受取人が誰だったのかをブロックチェーンに問い合わせてみましょう。すべてのトランザクションアウトプットは、堀江さんにビットコインを送ったアドレスを受取人としているはずです。 出力は以下のようになります。 すべて同じビットコインアドレスに対して送られているトランザクションアウトプットであることが分かると思います。これは確かに前回の記事で確認したこのトランザクションに送信者のところで見られたビットコインアドレスです。 ブロックチェーンとはビットコインがあるビットコインアドレスから別のビットコインアドレスに移ったという情報がひたすら書かれている台帳です。よってあなたの所有するビットコインの実体とは、ブロックチェーンに記載されたトランザクションアウトプットの中であなたが受取人に指定されていてかつあなたが使用していないものです。未使用のトランザクションアウトプットのことをUTXOと呼びます。 トランザクションのインプットをどんどんどんどん遡ってみましょう。Transaction hash ->f59a92861aee231edcfee65834b6acf48ba6f4ee624e572f9fba6a7f720499ca
The index of the transaction's outputs ->0
Transaction hash ->7507e1301b3db2e9c62e4c3f9bbfc2c2b829e01fdaf3c95651a31caf811855c0
The index of the transaction's outputs ->0
Transaction hash ->1151d6903411836c11117bfcff3e5a3a076ae2c22f88d8b5673f7123a30cd507
The index of the transaction's outputs ->0
Transaction hash ->86b8472a86dc9e0836a55171417f8b6056754de46162d635c1e70adfd452a39b
The index of the transaction's outputs ->0
Transaction hash ->35b1aaf2e486f8366440484e040fb8ee196cb2684e77e3c5bfa237f9cf9e0d2d
The index of the transaction's outputs ->0
Transaction hash ->9b8b3f718cb6b247c5855574bd1f4719eee49e99aa4016a29135b2ad1871191b
The index of the transaction's outputs ->0
Transaction hash ->ab5940ca9246aa02596058a0dc512973a5abb3575e4b3a4a57c45eda8b525861
The index of the transaction's outputs ->0
Transaction hash ->2598d0e4a9a6eb393726fc84f549186cbd08934464836b28210e9319816d50f7
The index of the transaction's outputs ->0
Transaction hash ->3924ba8a21fc1c704f0c8cce6e9ad4dd1ab6a1efa6d178c57172f429b02b039a
The index of the transaction's outputs ->0
Transaction hash ->482b50307a941d853dcf88e044ece23b29e054dd4f90312533404d02e56d4353
The index of the transaction's outputs ->0
未使用トランザクションアウトプット(UTXO)とは何か
10個のトランザクションアウトプットの受取人を見てみる
using NBitcoin;
using QBitNinja.Client;
using QBitNinja.Client.Models;
using System;
using System.Collections.Generic;
namespace NBitcoinTest1
{
class Program
{
static void Main(string[] args)
{
var outPoints = new List<Tuple<string, int>>()
{
new Tuple<string, int>("f59a92861aee231edcfee65834b6acf48ba6f4ee624e572f9fba6a7f720499ca", 0),
new Tuple<string, int>("7507e1301b3db2e9c62e4c3f9bbfc2c2b829e01fdaf3c95651a31caf811855c0", 0),
new Tuple<string, int>("1151d6903411836c11117bfcff3e5a3a076ae2c22f88d8b5673f7123a30cd507", 0),
new Tuple<string, int>("86b8472a86dc9e0836a55171417f8b6056754de46162d635c1e70adfd452a39b", 0),
new Tuple<string, int>("35b1aaf2e486f8366440484e040fb8ee196cb2684e77e3c5bfa237f9cf9e0d2d", 0),
new Tuple<string, int>("9b8b3f718cb6b247c5855574bd1f4719eee49e99aa4016a29135b2ad1871191b", 0),
new Tuple<string, int>("ab5940ca9246aa02596058a0dc512973a5abb3575e4b3a4a57c45eda8b525861", 0),
new Tuple<string, int>("2598d0e4a9a6eb393726fc84f549186cbd08934464836b28210e9319816d50f7", 0),
new Tuple<string, int>("3924ba8a21fc1c704f0c8cce6e9ad4dd1ab6a1efa6d178c57172f429b02b039a", 0),
new Tuple<string, int>("482b50307a941d853dcf88e044ece23b29e054dd4f90312533404d02e56d4353", 0)
};
outPoints.ForEach((outPoint) =>
{
QBitNinjaClient client = new QBitNinjaClient(Network.Main);
var txHash = outPoint.Item1;
var outIndex = outPoint.Item2;
var transactionId = uint256.Parse(txHash);
GetTransactionResponse transactionResponse = client.GetTransaction(transactionId).Result;
var transaction = transactionResponse.Transaction;
var addr = transaction.Outputs[outIndex].ScriptPubKey.GetDestinationAddress(Network.Main);
Console.WriteLine(addr);
}
);
}
}
}
1BdzVJ5EEWZz8zHx31F8M4c7uD5ziLVmCu
1BdzVJ5EEWZz8zHx31F8M4c7uD5ziLVmCu
1BdzVJ5EEWZz8zHx31F8M4c7uD5ziLVmCu
1BdzVJ5EEWZz8zHx31F8M4c7uD5ziLVmCu
1BdzVJ5EEWZz8zHx31F8M4c7uD5ziLVmCu
1BdzVJ5EEWZz8zHx31F8M4c7uD5ziLVmCu
1BdzVJ5EEWZz8zHx31F8M4c7uD5ziLVmCu
1BdzVJ5EEWZz8zHx31F8M4c7uD5ziLVmCu
1BdzVJ5EEWZz8zHx31F8M4c7uD5ziLVmCu
1BdzVJ5EEWZz8zHx31F8M4c7uD5ziLVmCu
まとめ
次回やること
参照
(2017年10月3日にQiitaにて執筆した記事をこちらに転載しています。)
Visual Studio Communityをインストールした後、コンソールアプリ( .NET Coreでも .NET Frameworkでもどちらでも構いません)のプロジェクトを作成し、NuGetパッケージの管理画面でNBitcoinとQBitNinjaをそれぞれ検索し、プロジェクトに追加して下さい。
前回取得した堀江さんのビットコインアドレスの一番古いトランザクションであるこちらのトランザクションを例に使いましょう。トランザクションハッシュは以下の値になっています。
7008807adb713205d01d378f3d25f1bc5e06bda7d30b526c2b9d2c018cfa08b4
ちなみにこちらは堀江さんがビットコインを受け取ったときのトランザクションです。
まずはブロックチェーンAPIにアクセスして該当のトランザクションを取得してみます。その為にQBitNinjaというクライアントライブラリを使用します。
using NBitcoin; using QBitNinja.Client; using QBitNinja.Client.Models; using System; namespace NBitcoinTest1 { class Program { static void Main(string[] args) { QBitNinjaClient client = new QBitNinjaClient(Network.Main); var transactionId = uint256.Parse("7008807adb713205d01d378f3d25f1bc5e06bda7d30b526c2b9d2c018cfa08b4"); GetTransactionResponse transactionResponse = client.GetTransaction(transactionId).Result; Console.WriteLine(transactionResponse.TransactionId); } } }
これでコンソールの出力にトランザクションハッシュが出力されるはずです。
ちなみに以下で使用しているNetwork.Mainというのは、ビットコインのテスト用ネットワークではなく、本番用のネットワークからデータを取得する為に指定しているものです。
QBitNinjaClient client = new QBitNinjaClient(Network.Main);
このトランザクションのアウトプットに含まれるビットコインの量を見てみます。これはつまり堀江さんが受け取ったビットコインの量ということになります。
今回のトランザクションにはアウトプットが一つしかありませんが、アウトプットがひとつのトランザクションにひとつとは限りません。例えばビットコインを支払いに使いおつりが発生した場合などには、おつりがひとつのアウトプットとなり、支払者宛てに送られます。
transactionResponse.ReceivedCoins.ForEach((coin) => { Money amount = (Money)coin.Amount; Console.WriteLine(amount.ToDecimal(MoneyUnit.BTC)); });
出力は以下のようになります。
0.09020979
同じ要領でインプットの量も見てみます。こちらもひとつとは限りませんし、アウトプットと同量であるとも限りません。
decimal total = 0; transactionResponse.SpentCoins.ForEach((coin) => { Money amount = (Money)coin.Amount; Console.WriteLine(amount.ToDecimal(MoneyUnit.BTC)); total += amount.ToDecimal(MoneyUnit.BTC); }); Console.WriteLine(string.Format("Total input is {0} BTC", total));
出力は以下のようになりました。
0.001 0.01914202 0.00077777 0.0001 0.01 0.01 0.001 0.001 0.04649 0.001 Total input is 0.09050979 BTC
インプットが10個存在すること。それらのビットコインが集められて、インプットのビットコインの総量の方がアウトプットのビットコインの総量(0.09020979)よりも多いことが確認できます。
このインプットとアウトプットの差はビットコインのトランザクション手数料として、所謂マイナーさんがこのトランザクションをブロックチェーンのブロックとして正式に追加したときに彼らに払われるものです。
ここで詳しくは触れませんが、ビットコインの所有権を示す為の根幹となるScriptPubKeyを見てみます。すごく簡単に言うと、このスクリプトは所有者にしか解けないパズルのようなものだと言えます。
transactionResponse.ReceivedCoins.ForEach((coin) => { Console.WriteLine(coin.TxOut.ScriptPubKey); });
出力は以下のようになります。
OP_DUP OP_HASH160 a4de1901ca3dd7167d655214c21baaa7fe554d3f OP_EQUALVERIFY OP_CHECKSIG
これは知識がないとチンプンカンプンだと思いますが、以下のようにNBitcoinを使用すると、実はここから堀江さんのビットコインアドレスである1G2jt5WeGhqWtDKEkcKY2GrZKjfYsuiVxXが抜き出せます。
using NBitcoin; using QBitNinja.Client; using QBitNinja.Client.Models; using System; namespace NBitcoinTest1 { class Program { static void Main(string[] args) { QBitNinjaClient client = new QBitNinjaClient(Network.Main); var transactionId = uint256.Parse("7008807adb713205d01d378f3d25f1bc5e06bda7d30b526c2b9d2c018cfa08b4"); GetTransactionResponse transactionResponse = client.GetTransaction(transactionId).Result; var tx = transactionResponse.Transaction; tx.Outputs.ForEach((txOut) => { var script = txOut.ScriptPubKey; Console.WriteLine(script); Console.WriteLine(script.GetDestinationAddress(Network.Main)); }); } } }
OP_DUP OP_HASH160 a4de1901ca3dd7167d655214c21baaa7fe554d3f OP_EQUALVERIFY OP_CHECKSIG 1G2jt5WeGhqWtDKEkcKY2GrZKjfYsuiVxX
ビットコインアドレスというのは公開鍵から作られているものです(正確に言うと公開鍵のハッシュから作られています。)
これでこのスクリプトは受け取り手である堀江さんの公開鍵で鍵をかけられ、彼の秘密鍵でしか解けないものであることが何となくイメージできると思います。
ちょっと長くなってきてしまったので今回はここまでにします。
次回は、上で10個あったこのトランザクションへのインプットがどこからやってきたのかを見たいと思います。
(2017年9月25日にQiitaにて執筆した記事をこちらに転載しています。)
ホリエモンこと堀江貴文さんはTwitterのプロフィール欄にて投げビットコインアドレスをひとつ公開しています。
今回はこのビットコインアドレスを例として使わせてもらい、ブロックチェーンからトランザクションの情報を取得してみます。これにより堀江さんがどれくらいのビットコインをこのアドレスで受け取ったのかが分かるはずです。
280万人ものフォロワーがいる堀江さんです。結構な数の人がこのアドレスを見て投げ銭をしたかもしれません(投げ銭目的で堀江さんがこのアドレスを載せているのかは知りませんが。)ちょっと結果が楽しみですね。
今回はBlockchain.infoのBlockchain Data APIを使いたいと思います。どうやらSingle AddressというAPIを使うと、あるひとつのビットコインアドレスに対するトランザクション情報が取得できるようです。
では実際に使ってみましょう。以下のコードはPowerShellです。
$address = "1G2jt5WeGhqWtDKEkcKY2GrZKjfYsuiVxX"; $uri = [System.String]::Format("https://blockchain.info/ja/rawaddr/{0}", $address); $response = Invoke-RestMethod -Method Get -Uri $uri; Write-Output $response.txs.Length; # output 18
このコードの実行時点では18個のトランザクションが見つかりました。思ったより少ないですね。しかもこれには堀江さんのアドレスからビットコインが送信された場合のトランザクションも含まれていますので、受信のトランザクションはもっと少ないはずです。
堀江さんのアドレスが受信先だったトランザクションのみにデータを絞るため、取得したトランザクションの送り先アドレスを確認し、堀江さんのアドレスであった場合のみ対象とします。その際にトランザクションの総数と受け取ったビットコインの総量を計算します。
$receivedCount = 0; $receivedAmount = 0; foreach ($tx in $response.txs.out) { if ($tx.addr -eq $address) { $receivedCount += 1; $receivedAmount += $tx.value; } } Write-Output $receivedCount Write-Output $receivedAmount # output 13 47715300
受信のトランザクションは13件見つかりました。一方でビットコインの総量は47,715,300という数字が返ってきました。単位を良く知らないとここで「おおおっっ!さすがホリエモン!!!」と思ってしまうかもしれませんが、これは実はsatoshiという単位ベースの数字です。
1satsohiは1/100,000,000BTC(1億分の1ビットコイン)と定義されていますので、堀江さんが受け取った投げ銭の総額は0.477153BTCということになります。本日の相場で言うと20万円と少しというところです。
これでも「20万円はすごい!さすがホリエモン!」となるかもしれませんが、ほとんどのトランザクションは2014年、2015年とビットコインがまだ現在のような価格に暴騰する前のものでした。今年に入ってからのトランザクションはありませんでした。
(2017年10月21日にQiitaにて執筆した記事をこちらに転載しています。)
この記事ではビットコインで使われているビットコインアドレスを自分の手で作る手順を紹介しています。ビットコインアドレスを作ることを通して、ビットコインに対する技術的な理解を深めることが目的です。
ビットコインアドレスを作る手順は大まかに言うと以下のようになっており、この記事では各手順を順番に説明していきます。
ビットコインアドレスとは、ビットコインを誰かが誰かに送付するときに宛先として利用するものです。例えば以下のような形をしています(ちなみにこのアドレスは堀江貴文さんがTwitterにて公開しているビットコインアドレスです。)
1G2jt5WeGhqWtDKEkcKY2GrZKjfYsuiVxX
個人間のビットコインアドレスのやり取りにせよ、実店舗やネットでのビットコインを使った買い物にせよ、ビットコインの所有権がある人から別の人に移るとき、基本的には新しい所有者のビットコインアドレスへビットコインが送付されるという形をとります。
この記事では触れませんが、このようにビットコインの所有権がある所有者から次の所有者に移されることをトランザクションと言います。トランザクションにも興味があれば、下部の参照セクションにある記事も読んでみて下さい。
まず秘密鍵を作ります。秘密鍵とは何かと言いますと、何も面白くない答えですがただの整数です。大雑把に言えばですが、から
整数のどれかであればどれでもOKです。実は
よりもわずかに小さい数が上限なのですが、それは後程詳しく述べます。
ここで大事なことを言います。あなたの秘密鍵が誰かに知られたら所有しているコインはすべて奪われますし、もし失くすなり忘れるなりしてしまったらコインは永久埋蔵金になります。
よって秘密鍵は充分に不規則な乱数で、十分に大きい数を作らなければなりません。また大切に、かつ人目に付かないところに保管しておかなければなりません。本当に大事なものです。
上でから
の間ならどれでも良いと書いたのはあくまで理論上の話で、例えばあなたの秘密鍵が5だったらあっという間に誰かにビットコインを奪われてしまうと思った方がいいです。
ちなみにとは以下のように78桁にも及ぶとてつもなく大きな数で、観測可能な宇宙にある原子の数とかそういう値に匹敵するくらいの大きさの数です。これだけ大きければ、例え全人類がビットコインを使うようになったとしても十分だと言えるでしょう。
この記事中では、今まさにこの行を書いている時点での日付と時間にちなんで2017101920171019を秘密鍵に使う整数としたいと思います。以下、この秘密鍵をkと呼びます。
次に公開鍵を秘密鍵から作ります。
公開鍵の作成手順は、この記事で紹介している一連のビットコインアドレス作成手順の中で一番複雑であり、一番面白い所です。
まずは公開鍵について、以下の大事な点を抑えておきましょう。
では、公開鍵の作成手順の説明に入ります。
ビットコインの公開鍵は、Secp256k1という楕円曲線暗号を用いたデジタル署名アルゴリズムを使い、秘密鍵から計算されて作られます。
ここで個々の理論について突っ込んで説明するスペースも力量もありませんので、あくまでビットコインで使われているSecp256k1に着目して説明したいと思います。
Secp256k1とは何かと言いますと、簡単に言ってしまえば点の集合です。どんな点の集合かと言いますと、以下の式に従う点の集合です。
なんだかすごい数字が出てきましたね。このmodの横にある一連の数字を面倒くさいのでと呼ぶことにします。この
はどうやら素数らしいのですが、上の秘密鍵の説明のところで「
よりわずかに小さい数」と書いたのがこの
です。
これがどんな点の集合になるのか図にして見てみたいところですが、あまりにも数が大きくて色々な意味で無理なので、pをちょっと小さい素数にして3パターン図にしてみました(以下はRのプログラムです。)
p <- 17 xlist <- c() ylist <- c() for (x in 1:p) { for (y in 1:p) { if (((x^3 + 7 - y^2) %% p) == 0) { xlist[length(xlist) + 1] <- x ylist[length(ylist) + 1] <- y } } } plot(xlist, ylist, type = "p", main = sprintf("(x^3 + 7 - y^2) mod %d == 0", p), xlab = "x", ylab = "y", )
どうでしょう、何となく雰囲気が掴めたでしょうか?が7919でももうかなりぐちゃぐちゃなので、Secp256k1が定義している巨大素数を使ったら大変なことになってしまいそうです。
さて計算で公開鍵を求める前にイメージしておいて欲しいのが、この膨大な点のどれかひとつがあなたの公開鍵になるということです。公開鍵は二つの整数のペアだと上で書きましたが、このどれかひとつの点の座標があなたの公開鍵になります。
上でSecp256k1は定義された式に従う点の集合だと書きましたが、実はもうひとつSecp256k1がして定義いるものがあります。それが生成元です。
生成元とは何かと言いますと、これも要するに膨大に存在する点の中のあるひとつです。この点を(まさに文字通り)起点として公開鍵の計算を行うので生成元(Generator Point)と呼ばれます。以下、この生成元をと呼びたいと思います。
以下のPythonプログラムで、生成元の具体的な値と、それがきちんと
に従っていることが確認できます。
p = 2**256 - 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 - 1 Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 Gy = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 print("p = ", p) print("Gx = ", Gx) print("Gy = ", Gy) # y^2 = x^3 + 7 mod p -> y^2 - x^3 - 7 = 0 mod p print("Gy^2 - Gx^3 - 7 mod p = ",(Gy**2 - Gx**3 - 7) % p)
出力結果です。
p = 115792089237316195423570985008687907853269984665640564039457584007908834671663 Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240 Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424 Gy^2 - Gx^3 - 7 mod p = 0
の
も
も非常に大きい数であることが分かります。またその
の
と
を
に代入してみたところ答えが
になることも確認できました。
ではいよいよ公開鍵を計算によって求めます。ここが一番の肝です。
まず単刀直入に言ってしまうと、公開鍵を求めるには生成元Gを秘密鍵kを使ってk倍してやればいいだけです。つまり求めたい公開鍵をKとすると
ということになります。なんて簡単なんでしょう。
しかし実はこれは単純な掛け算をすれば良いということではないので、の
と
にそれぞれkをかけて
と
にすれば良いということではありません。
ではどのように演算しなければならないかというと、楕円曲線上のスカラー倍算(スカラー倍算というのは、要はある整数と掛けること)をする必要があります。
この記事の中で楕円曲線上の演算の話を書きだすと記事の量がそれこそ倍算されてしまうので思い切って省略させて頂きたいと思いますが、以下のサイトを参考にしてみて下さい。特にQiitaの記事は分かり易く書かれていてお勧めです。
リンク先のサイトを読んでいただけたかとは思うのですが、一応すごく簡単に自分の言葉でも説明しておきます。
ちなみにどのサイトの説明でも楕円曲線が、楕円ではないにせよ「曲線」だと扱われて説明されていますが、あくまで我々が相手にしているのは点の集合のはずです。それをお忘れなく。
楕円曲線上の点Pと点Qの足し算した結果を点Rとすると、点Pと点Qを通る直線と(想像上の)楕円曲線が交わるもうひとつの点、それをx軸で反転させた(つまりその点のyを-1倍した)点をがRとなると定義します。
数式で説明します。ある点P(座標はとする)とある点Q(座標はtex:(x_q,y_q)])を足した結果の点R(座標は
)は以下のように求められます。
は単純に点Pと点Qを通る直線の傾きですね。続く
と
を求める式の詳細は、ここでは触れません(ごめんなさい。)
ちなみにここで注意して欲しいことがあります。の中に分数、つまり割り算が出てきますが、これは普通に割り算をしてしまっては駄目です。なぜかと言うと我々はmodの世界にいるからです。この割り算については以下の記事にまとめましたので参考にして下さい。
逆数を考慮するとを求める式は以下のように書き換えた方が良いでしょう。
割り算を逆数との掛け算に変換しているというのがポイントです。
楕円曲線上のある点Pを2倍する場合、その点における(想像上の)楕円曲線の接線と、(またまた想像上の)楕円曲線が交わるもうひとつの点、それをx軸で反転させた点がの結果だと定義します。
数式で書いてみます。の求め方が加算のときと違いますが、単に微分で直線の傾きを求めるようにしただけです。根本的にやっていることは変わりません。また、こちらでも割り算を逆数との掛け算に変換しています。
ここで突然が出てきました。これはなんでしょうか。
上でビットコインで使われる楕円暗号DSA(デジタル署名アルゴリズム)はSecp256k1で、それはからなる点の集合を定義すると説明しました。
実は楕円曲線は一般的にはで定義されており、Secp256k1は
としています。この
はその
なので、ここでは0となります。
これは加算と2倍算の組み合わせで出来ます。例えばある点Pを7倍したい場合、の様に計算します。
ちなみにこの計算式、7の2進数表記が111であることから割り出せるという不思議なものなのです。詳しくはビットコインニュースの記事を読んで欲しいのですが、要するに
を繰り返して求めます。
もちろん足し算を繰り返しても求められるのですが、そうやると効率が悪く計算時間が長くかかってしまいます。覚えていますか?秘密鍵kは安全の為にある程度大きい必要があります。
ではいよいよプログラムによって公開鍵を秘密鍵から計算します。上述の内容を踏まえて、楕円暗号上の2点の加算、ある点の2倍算、そしてある点のスカラー倍算を実装します。以下のコードはC#です。
using System; using System.Numerics; namespace MakeBitcoinAddress { class ECPoint { public BigInteger x = 0; public BigInteger y = 0; } static class Helper { public static BigInteger Mod(BigInteger value, BigInteger modulus) { var result = BigInteger.ModPow(value, 1, modulus); return (result < 0) ? result + modulus : result; } public static BigInteger ModInverse(BigInteger value, BigInteger modulus) { if (value < 0) { value = Helper.Mod(value, modulus) + modulus; } BigInteger x = 0, y = 1, gcd = modulus; BigInteger px = 1, py = 0, pgcd = value; BigInteger temp = 0; while (gcd > 0) { BigInteger quotient = pgcd / gcd; temp = x; x = px - quotient * temp; px = temp; temp = y; y = py - quotient * temp; py = temp; temp = gcd; gcd = pgcd - quotient * temp; pgcd = temp; } return px; } public static string GetBinaryString(byte[] byteArray) { string result = string.Empty; for (int i = 0; i < byteArray.Length; i++) { byte b = byteArray[i]; // If this is the last byte and 0, we should be done if ((b == 0) && (i == byteArray.Length - 1)) { break; } string binaryString = Convert.ToString(b, 2); // Fill 0 until length reaches 8 if it's not the last byte if (i < byteArray.Length - 1) { while (binaryString.Length < 8) { binaryString = "0" + binaryString; } } result = binaryString + result; } return result; } } class Program { // p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 static BigInteger ModP = BigInteger.Parse("115792089237316195423570985008687907853269984665640564039457584007908834671663"); // G (Generator Point) static ECPoint G = new ECPoint() { x = BigInteger.Parse("55066263022277343669578718895168534326250603453777594175500187360389116729240"), y = BigInteger.Parse("32670510020758816978083085130507043184471273380659243275938904335757337482424") }; static ECPoint ECAdd(ECPoint p, ECPoint q) { ECPoint result = new ECPoint(); BigInteger lambda = (q.y - p.y) * Helper.ModInverse((q.x - p.x), ModP); result.x = BigInteger.Pow(lambda, 2) - p.x - q.x; result.x = Helper.Mod(result.x, ModP); result.y = lambda * (p.x - result.x) - p.y; result.y = Helper.Mod(result.y, ModP); return result; } static ECPoint ECDouble(ECPoint p) { ECPoint result = new ECPoint(); BigInteger lambda = (3 * BigInteger.Pow(p.x, 2)) * Helper.ModInverse((2 * p.y), ModP); result.x = BigInteger.Pow(lambda, 2) - (2 * p.x); result.x = Helper.Mod(result.x, ModP); result.y = lambda * (p.x - result.x) - p.y; result.y = Helper.Mod(result.y, ModP); return result; } static ECPoint ECScalar(ECPoint p, BigInteger scalar) { ECPoint result = new ECPoint() { x = p.x, y = p.y }; string s = Helper.GetBinaryString(scalar.ToByteArray()); foreach(var c in s.Substring(1)) { result = ECDouble(result); if (c.Equals('1')) { result = ECAdd(result, p); } } return result; } static bool IsOnSecp256k1(ECPoint p) { var answer = Helper.Mod((BigInteger.Pow(p.y, 2) - BigInteger.Pow(p.x, 3) - 7), ModP); return (answer == 0); } static void Main(string[] args) { } } }
ではいよいよ上で定めた秘密鍵kから公開鍵Kを求めてみます。
static void Main(string[] args) { BigInteger k = 2017101920171019; var publicKey = ECScalar(G, k); Console.WriteLine(publicKey.x); Console.WriteLine(publicKey.y); Console.WriteLine(IsOnSecp256k1(publicKey)); }
以下が出力結果です。
36572950727085182085382801403406684418997739286809164939833038507156062366242 43641162618061335383666517243600726585787626462467298998793806674351551668099 True
これが正しいのかどうかはちょっと確かめるのが難しそうですが、とりえあえずSecp256k1で定義された式に従っている点が取得できました。私は個人的にはpを17にしてみて、手で計算できる範囲の値を使って色々とテストしてみたり、このWikiページに例として載っている値を使って計算したら合っているようだったので、ある程度は上手く動いていると見ています。
ちなみにここにきて気が付きましたが、どうやら.NETのMod計算は負の数を正の数に直さないようです。負の数のMod計算はプログラミング言語の実装によって流儀が違うので若干困りますね。今回は正の数で揃えたいと思っているので、Modの計算をHelperファンクションにて調整しています。
ようやく公開鍵を求めることが出来ました。次は公開鍵のハッシュを求めます。
ここから公開鍵のハッシュを求めます。
この文章を読んでいる方のほとんどはおそらくハッシュというものが何かということを理解していると思いますので、ハッシュの概念の説明は省きます。また個々のハッシュアルゴリズムそのものについての説明もこちらでは省きます。
公開鍵のハッシュを作る上で重要なポイントは、これも不可逆な一方向性の処理であり、公開鍵のハッシュから公開鍵を求めることは現実的には不可能ということです。
上で「公開鍵は人に見せても安全だが、見せる必要は基本的にはない」と書きましたが、ビットコインのやり取りで使われるのは、この公開鍵のハッシュであり、公開鍵ではありません。
公開鍵のハッシュはSHA256とRIPEMD-160というハッシュアルゴリズムを使って公開鍵(public key)から求められます。疑似的なプログラムで書くと、以下のように求められます。
PublicKeyHash = RIPEMD-160(SHA256(PublicKey))
簡単ですね。もちろんそれぞれのアルゴリズムの中身は多少複雑ではあると思いますが。
次に公開鍵をどのようにSHA256の入力として与えればいいかを調べてみましょう。下のWikiページで説明されています。
Technical background of version 1 Bitcoin addresses – Bitcoin Wiki
入力は65バイトのバイト列にするべきのようで、以下のような形になるようです。
[1バイト 0x04][32バイト 公開鍵のxをビッグエンディアンで][32バイト 公開鍵のyをビッグエンディアンで]
ビッグエンディアンというのは、バイト列をどのような順番で並べるのかという話です。その他は特に問題ないかと思います。
ちなみに私は公開鍵はという整数のペアだという書き方をしましたけれど、上記のSHA256への入力値を指して公開鍵と呼んでいる場合が多いかもしれません。どちらにせよ概念的には同一です。
ビットコインのトランザクションのデータ量を削減するためなどの理由で、公開鍵のxだけを使った圧縮フォーマットも存在しています。これはyは実はxさえ分かっていれば簡単に求まることを利用しています。圧縮した場合は65バイトから33バイトにサイズを減らせます。この記事では圧縮フォーマットについては触れませんが、現実的にはこちらが使われるべきでしょう。
では、以下のコードで公開鍵のハッシュを求めてみます。以下のコードはC#で、ハッシュアルゴリズムについては.NETが提供しているライブラリを用いて行っています。スペースの節約の為、プログラムは一部のみ記載しています。
using System.Security.Cryptography; using System.Text; static class Helper { public static string ByteArrayToString(byte[] ba) { StringBuilder hex = new StringBuilder(ba.Length * 2); foreach (var b in ba) { hex.AppendFormat("{0:X2}", b); } return hex.ToString(); } } class Program { static void Main(string[] args) { // Private key BigInteger k = 2017101920171019; // Public key var publicKey = ECScalar(G, k); // Public key hash byte[] inputBytes = new byte[65]; Array.Copy(new byte[1] { 0x04 }, 0, inputBytes, 0, 1); byte[] x = publicKey.x.ToByteArray(); Array.Reverse(x); // To big endian byte[] y = publicKey.y.ToByteArray(); Array.Reverse(y); // To big endian Array.Copy(x, 0, inputBytes, 1, 32); Array.Copy(y, 0, inputBytes, 33, 32); SHA256 sha256 = new SHA256CryptoServiceProvider(); RIPEMD160Managed ripemd160 = new RIPEMD160Managed(); var publicKeyHash = ripemd160.ComputeHash(sha256.ComputeHash(inputBytes)); Console.WriteLine(Helper.ByteArrayToString(publicKeyHash)); } }
以下、出力結果です。
976ED257CD5938560BA0C926AADB17927DB9E51B
RIPEMD-160は20バイトの出力を返すので当然ですが、公開鍵のハッシュはこのように20バイトとなっています。ビッグエンディアンでSHA256への入力を作る必要があることにご注意ください(私は.NETのBigIntegerのToByteArrayがリトルエンディアンを返すと知らずにトラブりました。)
ではいよいよ、最後の手順に進みたいと思います。
いよいよ最後にビットコインアドレスを作ります。
まず注意してもらいたいポイントがあります。今までの公開鍵の作成も、公開鍵のハッシュの作成も一方向的な手続きでした。つまり後戻りできないということです。おさらいになりますが、公開鍵から秘密鍵を逆に作成することも、公開鍵のハッシュから公開鍵を逆に作成することも、現実的には不可能です。
ですがこの最後の手順だけは違います。今からビットコインアドレスを公開鍵のハッシュから作成しますが、ビットコインアドレスを公開鍵のハッシュに戻すことは可能です。ビットコインアドレスを公開鍵から作成するのは単なるエンコーディングであり、暗号化もされなければハッシュ化もされていないので、デコーディングすれば元に戻せます。
別の言い方をすれば、公開鍵のハッシュとビットコインアドレスとは同じものですと言うことが出来ると思います。正直言うとこの最後の手順は、ただ利便性の為にデータの見た目を調整するだけの手順なのです。
まず公開鍵のハッシュの先頭にバージョンバイトと呼ばれる1バイトをくっつけます。
このバージョンバイトとは何かと言うと、このビットコインアドレスはビットコインの本当のネットワークで使われるのか、それともテスト用のネットワークで使われるのかを示す為のバイトです。
本当のネットワークであれば0x00を、テストのネットワークであれば0x6Fを付けることになっているようです。ここでは出来上がったビットコインアドレスを本当のネットワークで使いたいので0x00を付けます。
先頭に00が付き、現時点で以下の値が求められました。これを公開鍵のハッシュ(バージョンバイト付き)と呼びたいと思います。
00976ED257CD5938560BA0C926AADB17927DB9E51B
ここで突然チェックサムの話が出てくるのですが、仕組みや使われ方はここでは説明ず、ここでは作られ方のみ説明したいと思います。
これもご存知かと思いますが、チェックサム(Check Sum)とはネットワークの途中で値が何かしら変更されてしまったり、打ち間違いなどで値が予期せぬものになったときにその誤りを検出するための仕組みです。
ここでのチェックサムは、上で求めた公開鍵のハッシュ(バージョンバイト付き)にSHA256を2回かけた結果の、先頭の4バイトと定義されています。
上のバージョンバイトと合わせて、C#で書くとこのような感じになります。
using System.Linq; // Version byte var pubKeyHashWithVersion = new byte[21]; Array.Copy(new byte[1] { 0x00 }, 0, pubKeyHashWithVersion, 0, 1); Array.Copy(publicKeyHash, 0, pubKeyHashWithVersion, 1, 20); // Check sum var checkSum = sha256.ComputeHash(sha256.ComputeHash(pubKeyHashWithVersion)).Take(4).ToArray();
ビットコインではBase58またはBase58Checkと呼ばれるエンコーディング形式が使われています。
Base58というのは私も初耳でしたが、EメールのMIMEフォーマットの中でよく使われているBase64であれば聞いたことがある方も多いと思います。
Base64とはバイナリデータを文字列で表す為に使われるエンコーディングで、6ビットずつ64種類ある文字や記号に割り当ててエンコーディングするというものです。
Base58は言わばBase64の改良版で、以下のようなメリットがあります。
Base64はエンコーディング結果の文字列を人間が扱ったりしないので、別に見た目がどうなろうとダブルクリックで選択できなかろうと大した問題ではなかったのですが、Base58は人間に扱われることを踏まえて、上記のようなメリットを持つべくデザインされたということだと思います。
では実装していきたいと思います。ここでは以下のページを参考にしました。
Base58Check encoding – Bitcoin Wiki
Bitcoinで使われる技術要素 – Qiita
以下は、与えられたバイト列をBase58でエンコーディングした文字列にして返してくれる関数です。
public static string GetBase58EncodedString(byte[] byteArray) { var result = new StringBuilder(); string base58String = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; // Count the number of 0 in the beginning of the byte array uint countOfZero = 0; for (int i = 0; i < byteArray.Length; i++) { if (byteArray[i] == 0) countOfZero++; else break; } // Convert the byte array to a BigInteger var bl = new List<byte>(byteArray); bl.Reverse(); // To little endian as .NET wants that way var x = new BigInteger(bl.ToArray()); while (x > 0) { var r = (int)(x % 58); x = x / 58; result.Append(base58String[r]); } // Added '1' as many as the zero count for (int i = 0; i < countOfZero; i++) result.Append("1"); return new string(result.ToString().Reverse().ToArray()); // Reverse the string }
特別な解説の必要はないと思いますが、与えられたバイト列をBigIntegerに変換して、それを58で割りながら余りの数をインデックスにしてどんどん対象の文字列から文字を拾ってくる感じです。また、与えられたバイト列の先頭に0x00があった場合は、その数に合わせて1を結果に追加しています。
ビットコインでは正確に言うとBase58Checkが使われているのですが、これはBase58と違うエンコーディング形式ということではなく、上で求めたチェックサムを公開鍵のハッシュ(バージョンバイト付き)と一緒にBase58エンコーディングをして、そこに誤り検知の機能を持たせることをもってしてそう呼んでいるようです。
この誤り検知につきましては、また別の記事ででも解説できたらと思いますが、基本的にはビットコインアドレスを手で入力したときに、打ち間違って違う送付先にビットコインを送ってしまうなどの事故を防ぐためのものです。
いよいよ長かったこの記事にもついにこの瞬間がやってきました。ビットコインアドレスとなる文字列を、Base58エンコーディングによって求めます。
上述の通りチェックサムをくっつけてからBase58エンコーディングを行います。具体的に言うと、以下の25バイトのバイト列をBase58エンコーディングすることになります。
[21バイト 公開鍵のハッシュ(バージョンバイト付き)][4バイト チェックサム]
ではコードで求めてみましょう。
// Base58 encoding var base58Target = new byte[25]; Array.Copy(pubKeyHashWithVersion, 0, base58Target, 0, 21); Array.Copy(checkSum, 0, base58Target, 21, 4); string bitcoinAddress = Helper.GetBase58EncodedString(base58Target); Console.WriteLine(bitcoinAddress);
以下、出力結果です。
1EohnzQdSSSsarBSncpHnfWxCzqSVZ3uPj
ついにビットコインアドレスが求められました!
これは正真正銘のビットコインアドレスであり、当然ビットコインを送付することも可能です。もちろん冒頭で書いた通り秘密鍵がばれてしまっているので、ここに送ったコインは簡単に盗まれてしまいますが、実験的に現在の価格で500円くらいのビットコインを送付してみました。
blockchain.infoでこのアドレスに関するトランザクションなどの情報を見ることが出来ます。
秘密鍵からはじまり、公開鍵、公開鍵のハッシュのハッシュの作成、そして最後にビットコインアドレスを作るまでの過程を通してその裏にある技術的な知識を学びました。
今度続編を書く機会があれば、ではこのビットコインアドレスに送ったビットコインは、どうしてそのアドレスの持ち主(正確に言うと秘密鍵の持ち主)にだけ所有権があると保証されるのか、その部分について書きたいと思います。
あんまり記事が長くなり過ぎるのも嫌だったのと、まだ自分が勉強中のこともあってかなり色々な事を端折っています。もし「この部分が分かりづらい」などのご意見がありましたら、コメント欄ででもお知らせ下さい。
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Security.Cryptography; using System.Text; namespace MakeBitcoinAddress { class ECPoint { public BigInteger x = 0; public BigInteger y = 0; } static class Helper { public static BigInteger Mod(BigInteger value, BigInteger modulus) { var result = BigInteger.ModPow(value, 1, modulus); return (result < 0) ? result + modulus : result; } public static BigInteger ModInverse(BigInteger value, BigInteger modulus) { if (value < 0) { value = Helper.Mod(value, modulus) + modulus; } BigInteger x = 0, y = 1, gcd = modulus; BigInteger px = 1, py = 0, pgcd = value; BigInteger temp = 0; while (gcd > 0) { BigInteger quotient = pgcd / gcd; temp = x; x = px - quotient * temp; px = temp; temp = y; y = py - quotient * temp; py = temp; temp = gcd; gcd = pgcd - quotient * temp; pgcd = temp; } return px; } public static string GetBinaryString(byte[] byteArray) { string result = string.Empty; for (int i = 0; i < byteArray.Length; i++) { byte b = byteArray[i]; // If this is the last byte and 0, we should be done if ((b == 0) && (i == byteArray.Length - 1)) { break; } string binaryString = Convert.ToString(b, 2); // Fill 0 until length reaches 8 if it's not the last byte if (i < byteArray.Length - 1) { while (binaryString.Length < 8) { binaryString = "0" + binaryString; } } result = binaryString + result; } return result; } public static string ByteArrayToString(byte[] ba) { StringBuilder hex = new StringBuilder(ba.Length * 2); foreach (var b in ba) { hex.AppendFormat("{0:X2}", b); } return hex.ToString(); } public static string GetBase58EncodedString(byte[] byteArray) { var result = new StringBuilder(); string base58String = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; // Count the number of 0 in the beginning of the byte array uint countOfZero = 0; for (int i = 0; i < byteArray.Length; i++) { if (byteArray[i] == 0) countOfZero++; else break; } // Convert the byte array to a BigInteger var bl = new List<byte>(byteArray); bl.Reverse(); // To little endian as .NET wants that way var x = new BigInteger(bl.ToArray()); while (x > 0) { var r = (int)(x % 58); x = x / 58; result.Append(base58String[r]); } // Added '1' as many as the zero count for (int i = 0; i < countOfZero; i++) result.Append("1"); return new string(result.ToString().Reverse().ToArray()); // Reverse the string } } class Program { // p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 static BigInteger ModP = BigInteger.Parse("115792089237316195423570985008687907853269984665640564039457584007908834671663"); // G (Generator Point) static ECPoint G = new ECPoint() { x = BigInteger.Parse("55066263022277343669578718895168534326250603453777594175500187360389116729240"), y = BigInteger.Parse("32670510020758816978083085130507043184471273380659243275938904335757337482424") }; static ECPoint ECAdd(ECPoint p, ECPoint q) { ECPoint result = new ECPoint(); BigInteger lambda = (q.y - p.y) * Helper.ModInverse((q.x - p.x), ModP); result.x = BigInteger.Pow(lambda, 2) - p.x - q.x; result.x = Helper.Mod(result.x, ModP); result.y = lambda * (p.x - result.x) - p.y; result.y = Helper.Mod(result.y, ModP); return result; } static ECPoint ECDouble(ECPoint p) { ECPoint result = new ECPoint(); BigInteger lambda = (3 * BigInteger.Pow(p.x, 2)) * Helper.ModInverse((2 * p.y), ModP); result.x = BigInteger.Pow(lambda, 2) - (2 * p.x); result.x = Helper.Mod(result.x, ModP); result.y = lambda * (p.x - result.x) - p.y; result.y = Helper.Mod(result.y, ModP); return result; } static ECPoint ECScalar(ECPoint p, BigInteger scalar) { ECPoint result = new ECPoint() { x = p.x, y = p.y }; string s = Helper.GetBinaryString(scalar.ToByteArray()); foreach(var c in s.Substring(1)) { result = ECDouble(result); if (c.Equals('1')) { result = ECAdd(result, p); } } return result; } static bool IsOnSecp256k1(ECPoint p) { var answer = Helper.Mod((BigInteger.Pow(p.y, 2) - BigInteger.Pow(p.x, 3) - 7), ModP); return (answer == 0); } static void Main(string[] args) { // Private key BigInteger k = 2017101920171019; // Public key var publicKey = ECScalar(G, k); // Public key hash byte[] inputBytes = new byte[65]; Array.Copy(new byte[1] { 0x04 }, 0, inputBytes, 0, 1); byte[] x = publicKey.x.ToByteArray(); Array.Reverse(x); // To big endian byte[] y = publicKey.y.ToByteArray(); Array.Reverse(y); // To big endian Array.Copy(x, 0, inputBytes, 1, 32); Array.Copy(y, 0, inputBytes, 33, 32); SHA256 sha256 = new SHA256CryptoServiceProvider(); RIPEMD160Managed ripemd160 = new RIPEMD160Managed(); var publicKeyHash = ripemd160.ComputeHash(sha256.ComputeHash(inputBytes)); // Version byte var pubKeyHashWithVersion = new byte[21]; Array.Copy(new byte[1] { 0x00 }, 0, pubKeyHashWithVersion, 0, 1); Array.Copy(publicKeyHash, 0, pubKeyHashWithVersion, 1, 20); // Check sum var checkSum = sha256.ComputeHash(sha256.ComputeHash(pubKeyHashWithVersion)).Take(4).ToArray(); // Bitcoin address by doing Base58 encoding var base58Target = new byte[25]; Array.Copy(pubKeyHashWithVersion, 0, base58Target, 0, 21); Array.Copy(checkSum, 0, base58Target, 21, 4); string bitcoinAddress = Helper.GetBase58EncodedString(base58Target); Console.WriteLine(bitcoinAddress); } } }
2019年から2020年にかけての日本バスケ界、本当に色々とあり過ぎです。占星術だったら「○○星と□□星が交差する100年に1度の年」とか細木数子さんに言われそうです。知らんけど。
思いつくままに出来事を列挙してみるとこんな感じです。どれも感慨深く大きな最近の出来事ですが、最近の激動の世の中のせいか、どこか懐かしさを感じてしまうのが正直なところ。
繰り返しになりますが、どれひとつとってみても普段なら3大ニュースのひとつにでも入ろうかという内容です。やはり〇〇星か□□星の影響なのでしょうか…
シーズンも終わり、来シーズンのBリーグはB1が20チーム、B2が16チームだから地区をどうしようだとか、選手の移籍がどうなるのかなとか、1年がオリンピックが伸びたことで男子、女子とも代表の選考に影響があるのかな、とか。
まあ色々と考えたら面白そうなことはあるのですが、正直に言うと、次のシーズンが今のチームのままで普通に始まって、そしてそのまま普通に終わってくれれば、それで100点満点なんじゃないかなって思っています。
あ、まだ2020年って3か月しか経ってないんですよね。ここからもまだまだ何か起こってしまうのでしょうか…悪いことは起きないように、○○星と□□星に向かって祈りを捧げます。
(2018年2月24日にQiitaにて執筆した記事をこちらに転載しています。)
以下のようなデータがあるとします。Filesと名付けます。
各行のParentIdはそのレコードの親となるレコードのIDを指しています。つまりこのデータは、自分自身を参照しています。
この各レコードに親レコードの名前を追加したいとします。そうすれば例えば、この親レコードの名前を使ってグループ化することなど出来て便利です。もちろんParentIdでもグループ化出来ますが、可読性が落ちてしまいます。
この場合LOOKUPVALUEを使って、次のように新しい列(カラム)を定義します。
ParentName = LOOKUPVALUE(Files[Name], Files[ID], Files[ParentId])
すると以下のようになります。
ParentIdに準じたNameが表示されました。
(2017年10月17日にQiitaにて執筆した記事をこちらに転載しています。)
暗号通貨の技術に興味を持っていまして、勉強の為に1からビットコインアドレスを自分で作ってみようと思ったのですが、いきなり躓いたところがあったので共有したいと思いました。
ビットコインアドレスを作る為の最初の手順は秘密鍵から公開鍵を作ることです。
公開鍵を作る為には楕円曲線上での加法、乗法を行う必要があります(この細かい内容については別記事にしたいと思います。)
公開鍵の作成手順を実装する為にビットコインニュースのこちらのページが非常に参考になりそうだったので読んでいたのですが、突然
def inv_mod(a, p = cP):
という関数が出てきたところで躓いてしまいました。このinv_mod()とは何者で何を計算しているのでしょうか?
多分”inverse modular”かなんかの略だろうという推測のもと検索した結果、この世にはモジュラ逆数なる概念があることを知りました。
このWikipediaページや関連する情報を読んで、inv_mod()がモジュラ逆数を計算しているものだということも分かりましたし、モジュラ逆数を計算する方法も分かりました。
しかしそれでも、楕円曲線上での加法や乗法を行うとき、なぜモジュラ逆数を計算する必要があるのかがまったく分かりませんでした。
これは一言で言ってしまうと、有限体上での割り算を行うときです。私は数学の専門家じゃないので用語とか定義が間違っている可能性は高いですが、要はmod 11とか計算式の右側についている場合の割り算のときです。
詳しいことはまた別途書きたいと思いますが、楕円曲線上での加法や乗法を行う時には、まさに式の中に分数、つまり割り算が出てきてますし、式にはmodが付いています。
最初に言っておきますとQiitaに楕円曲線暗号の超簡単な理論の紹介という素晴らしい記事がありまして、以下の内容はそちらをかなり参考にさせて頂いております。是非リンク先の記事も読んで下さい。
ではまず割り算の前に足し算、引き算、掛け算については以下のように普通に出来ることを確認したいと思います。
ちなみに≡は合同を意味します。
はい、そうですね。普通に四則演算した結果の答えを11で割って、その余りを本当の答えとすればいいだけです。
次は割り算です。例えば
は一体どう計算したらよいでしょうか?、つまり0.75を11で割った余りが答えでしょうか?
ここでまず、そもそもとは何かを考えてみたいと思います。
とは4と掛け算すると答えが1になる値という定義することができると思います。数学用語で
は4の逆数と言います。
が4の逆数であることは例えmod 11のもとに計算が行われても変わりはありません。
つまり
を求めればいいことになります。この4の逆数をmod 11のもとに求めればいいわけで、これがモジュラ逆数と呼ばれるものです。「mod 11のときの4の逆数」を求めればいいわけです。
コードを書いて「mod 11のときの4の逆数」を求める前に、普通に人間の感覚を使って求めたいと思います。
上述しましたが、とは4と掛け算すると答えが1になる値という定義することができます。mod 11のとき、4に何を掛け算すると答えが1になるでしょうか?そうです。
なので、3をかけると1になります。ちなみに14や25でも掛け算すると1になります。
つまりは
と同じなのです。これで晴れて計算を行うことが出来ます。この記事のタイトルにあるように
の答えは
となります。
このようになどのもとに割り算を行う場合、分母にある値の逆数(モジュラ逆数)を求めてから、掛け算の形に直して計算を行う必要があります。これがモジュラ逆数を計算する必要がある理由です。
ちなみに割り算が割り切れるときは逆数を求めなくても計算できてしまいます。もちろん逆数を求めても計算できます。
となって答えが同じであることが確認できたと思います。
先ほどのWikipediaページに計算方法が紹介されています。
ある整数の
における逆数を求めたいとき、その逆数を
とすると以下が成り立ちます。
これは言い換えると、から1を引くと
もしくは
を整数倍した数になるということです。その整数倍を
と表現すると以下が成り立ちます。modじゃなくて普通の計算式になっていることにご注意ください。
整理するとこうなります。
我々はを求めたいわけですが、ここで拡張ユークリッドの互除法というアルゴリズムを使って求めることが出来ます。
ユークリッドの互除法という最大公約数(GCD)を求めるアルゴリズムをご存知かと思いますが、その拡張版として、
を成り立たせる整数ペア,
もついでに求めることができるというアルゴリズムです。上の
ではGCDは既に1だと分かっていますけれど、
とすれば同じアルゴリズムでと
が求まりそうです。こちらのWikipediaページに乗っているコードを参考にしてC#で書いてみたいと思います。
using System; namespace ModInverseTest { class Program { public static void ExtendedEuclid(int a, int b) { var x = 0; var y = 1; var gcd = b; var prev_x = 1; var prev_y = 0; var prev_gcd = a; var temp = 0; while (gcd > 0) { int quotient = prev_gcd / gcd; temp = x; x = prev_x - quotient * temp; prev_x = temp; temp = y; y = prev_y - quotient * temp; prev_y = temp; temp = gcd; gcd = prev_gcd - quotient * temp; prev_gcd = temp; } Console.WriteLine(string.Format("{0}*{1} + {2}*{3} = gcd({0},{2}) = {4}", a, prev_x, b, prev_y, prev_gcd)); } static void Main(string[] args) { ExtendedEuclid(1071, 1029); } } }
以下が出力結果です。
1071*-24 + 1029*25 = gcd(1071,1029) = 21
では最後にこの関数を改造して、モジュラ逆数を求めるプログラムを書いてみます。冒頭で書いた通り私はこの関数を楕円曲線上の演算にて使う予定なので、大きな数にも対応できるようにBigInteger型を使います(ちなみにJavaのBigInteger型を使うと、ModInverse()というメソッドが既に実装されているようです。)
using System; using System.Numerics; namespace ModInverseTest { class Program { public static BigInteger ModInverse(BigInteger a, BigInteger m) { if (a < 0) { a = BigInteger.ModPow(a, 1, m) + m; } BigInteger x = 0, y = 1, gcd = m; BigInteger px = 1, py = 0, pgcd = a; BigInteger temp = 0; while (gcd > 0) { BigInteger quotient = pgcd / gcd; temp = x; x = px - quotient * temp; px = temp; temp = y; y = py - quotient * temp; py = temp; temp = gcd; gcd = pgcd - quotient * temp; pgcd = temp; } return px; } static void Main(string[] args) { Console.WriteLine("a = 3, m = 11, mod_inverse = " + ModInverse(3, 11)); Console.WriteLine("a = 4, m = 11, mod_inverse = " + ModInverse(4, 11)); } } }
以下が出力結果です。
a = 3, m = 11, mod_inverse = 4 a = 4, m = 11, mod_inverse = 3
正しくモジュラ逆数が求められています。
最後になりますが、モジュラ逆数のWikipediaにもあったように、もしが素数であれば以下が成り立つようです。
.NETのBigIntegerにもModPow()という関数が用意されているので、mが素数のときであれば以下のように関数を書くこともできます。
public static BigInteger ModInverse(BigInteger a, BigInteger m) { return BigInteger.ModPow(a, m - 2, m); }
もともとはビットコインアドレスをスクラッチから自分で作ってみようと思っていたのですが、モジュラ逆数のことで躓いてしまい、色々と調べたり試したりすることになりました。
モジュラ逆数を求められないと、modが付いているとき(有限体上で)の式で割り算が出来ません。そしてこの割り算ができないと、楕円曲線上での加法や乗法ができません。そして楕円曲線上での加法や乗法ができないと、秘密鍵から公開鍵を作成できません。結果ビットコインアドレスを作ることができません。
以上です。次はビットコインアドレスの作り方を記事にします。
負の数の逆数を入力とすると、逆数が0となってしまう問題がありました。修正の為、入力が負の数だった場合、同等の正の数に変換するようにしました。