ウォレットの操作

概要

ここでは、ウォレットクラスを使用してトランザクションをカスタムする方法を学びます。
Walletクラスは、キーとトランザクションを格納したbitcoinjで最も重要なクラスの1つです。UTXOを使い新しいトランザクションを作成し、ウォレットの内容が変更された時に通知を行います。

ウォレットを使用してさまざまな種類のアプリを構築する方法を学ぶ必要があります。

この記事では、ホワイトペーパーとWorkingWithTransactionsの記事を読んだことを前提としています。

セットアップ

ウォレットを最適に操作をするために、BlockChain/Peer/PeerGroupに接続する必要があります。ブロックチェーンのデータは、コンストラクタ内のWalletに渡すことができます。Walletは受け取ったデータを送信して、このウォレットに関連するトランザクション(つまり、ウォレットに格納されている鍵にBitcoinを送受信するトランザクション)だけを抽出します。Peer/Groupは、ブロックに表示される前にネットワークを介してブロードキャストされるウォレットトランザクションを送信します。

Walletは、ブロックチェーンに含まれているものとは無関係に、トランザクションがない状態、つまり残高が0の状態で開始します。これを使用するには、ブロックチェーンをダウンロードする必要があります。ブロックチェーンを使用すると、分析および使用可能なトランザクションがウォレットに読み込まれます。

Wallet wallet = new Wallet(params);
BlockChain chain = new BlockChain(params, wallet, ...);
PeerGroup peerGroup = new PeerGroup(params, chain);
peerGroup.addWallet(wallet);
peerGroup.startAndWait();

アドレスの取得

ウォレットからキーとアドレスを取得するには、次のAPI呼び出しを使用します。

Address a = wallet.currentReceiveAddress();
ECKey b = wallet.currentReceiveKey();
Address c = wallet.freshReceiveAddress();

assert b.toAddress(wallet.getParams()).equals(a);
assert !c.equals(a);

これらは支払いを受け取るために渡されます。ウォレットには「現在の」アドレスの概念があります。これは、常にアドレスを表示したいGUIウォレットを対象としています。現在のアドレスが使用されると、新しいアドレスに変更されます。一方、freshReceiveKey / Addressメソッドは、常に新しいアドレスを返します。

シードとニーモニックコード

これらのメソッドによって返されるキーとアドレスは、BIP32とBIP39に定義されたアルゴリズムにより、シードから決定論的に導出されます。キーの導出は次のようになります。

1.新しいWalletオブジェクトは、SecureRandomを使用して、十分なエントロピーのある128ビットのシードを生成します。

2.この乱数は「ニーモニックコード」に変換されます。BIP39に定義された辞書を使用し12個の単語に変換されます。

3.12個の文字列は、鍵の導出アルゴリズム(PBKDF2)の入力として使用され、「シード」を得るために何度も繰り返し計算されます。シードはニーモニックコードから導き出された乱数ではなく、シードは単語を導き出すためのものであることに注意して下さい。

4.次に、ルートシードからHMAC-SHA512を使い、マスター秘密鍵(256bit)とマスターチェーンコード(256bit)に分割します。これにより、BIP32で指定されたアルゴリズムを使用したキーツリーの導出が可能になります。このアルゴリズムは、楕円曲線の数学特性を利用し、秘密鍵に触れることなく公開鍵を導出することが出来るようになります。これはマスタ公開鍵を配布している場合に、ウォレットとそのアカウントのみでアドレスを自動的に生成することが出来るので非常に便利です。bitcoinjは、BIP32のデフォルトの推奨ツリー構造を使用します。

5.ウォレットは先読み鍵のセットを事前に計算します。先読み鍵は、current/freshReceiveKey APIを介してWalletから発行されていない鍵ですが、今後はWalletから発行された鍵になります。先読み鍵を事前に計算する利点が2つあります。

1つ目は、これらのAPIの処理を高速化します。これは、遅いECサービス処理を望まないGUIアプリケーションで役立ちます。
2つ目は、ウォレットが、まだ発行されていない鍵からトランザクションを生成している事に気付くことができます。これはWalletシードが複数のデバイスに複製され、ブロックチェーンが再生されているかどうか示します。

ウォレットがロードされたときの再構築が遅くれないように、計算されたシードと鍵はディスクに保存されます。

ニーモニックコードは、秘密鍵を直接扱うよりも扱いやすく、書き留めやすいようになっています。写し間違いをする可能性が少なく、ペンや紙を使用して簡単に作業できます。バックアップとしてユーザーにその言葉を公開することをお勧めします(リストアを高速化するために日付も書き留めてください)。

次のように作業することができます:

DeterministicSeed seed = wallet.getKeyChainSeed();
println("Seed words are: " + Joiner.on(" ").join(seed.getMnemonicCode()));
println("Seed birthday is: " + seed.getCreationTimeSeconds());

String seedCode = "yard impulse luxury drive today throw farm pepper survey wreck glass federal";
long creationtime = 1409478661L;
DeterministicSeed seed = new DeterministicSeed(seedCode, null, "", creationtime);
Wallet restoredWallet = Wallet.fromSeed(params, seed);
//後で説明するように、復元されたウォレットを同期させます

ルックアヘッドゾーンは、ウォレットを同期させて維持するときに重要な役割を果たします。デフォルトゾーンは100個分の鍵のサイズがありウォレットAがウォレットBに複製される場合は、ウォレットAが50個の鍵を発行し、最後の鍵を実際に支払いを受け為に使用します。ウォレットBは引き続き支払いを確認し、ルックアヘッドゾーンを移動させ合計で150個の鍵を辿ることが出来ます。もしウォレットAが120個の鍵を渡し110回目の支払いのみを受けた場合、財布Bは何が起きているのかわかりません。ですので財布を同期する時には、決済時に未払いのアドレスがいくつあるか知ることが重要です。デフォルトは100個の鍵を持つことで消費者のウォレットととして適していますが、Bitcoinサービスを扱う業者はより大きなゾーンが必要な場合もあります。

ブロックチェーンの再構成

新しくない秘密鍵を、すでにトランザクションが含まれているウォレットにインポートする場合には、ウォレットをリセットしてブロックチェーンを再度ダウンロードし、既にあるトランザクションを削除する必要があります。

現在、既存のトランザクションが含まれているウォレットにチェーンを再構成すると、ウォレットを破損する可能性があります。これは将来変更される可能性があります。
または、ブロック・エクスプローラのような他のサイトからトランザクションデータをダウンロードし、ウォレットに直接トランザクションを入れることもできます。しかし、これは現在サポートさされていません。多くのユーザーにとって、既存の秘密鍵をインポートすることは良くありません。秘密鍵を財布に定期的にインポートする必要があると感じたら、bitcoinjの開発者に話してください。

Walletは、システム内の他のクラスと連携してブロックチェーンとの同期を高速化しますが、デフォルトでは一部の最適化のみがオンになっています。最適なパフォーマンスを得るためのWallet/PeerGroupの設定方法は、SpeedingUpChainSyncを読んでください。

支払いの作成

ブロックチェーンを同期し終えた後に、bitcoinの支払いをすることができます:

System.out.println("You have " + Coin.FRIENDLY_FORMAT.format(wallet.getBalance()));

支払いは4つのステップで構成されています。

1.送信リクエストを作成します
2.送信リクエストの作成を完了します
3.トランザクションをコミットし、ウォレットに保存します
4.生成されたトランザクションをブロードキャストします

便宜上、これらの手順を実行を補助するメソッドがあります。

最も単純な場合:

//オブジェクト形式のアドレス1RbxbA1yP2Lebauuef3cBiBho853f7jxsを取得します
Address targetAddress = new Address(params, "1RbxbA1yP2Lebauuef3cBiBho853f7jxs");
//バックグラウンドで1 BTCの送信を行います。これはInsufficientMoneyExceptionをスローする可能性があります。
Wallet.SendResult result = wallet.sendCoins(peerGroup, targetAddress, Coin.COIN);
//ウォレットをディスクに保存します。自動保存を使用する場合はオプションです(下記参照)。
wallet.saveToFile(....);
//受諾を示すトランザクションがP2Pネットワークを介して伝播するのを待ちます。
result.broadcastComplete.get();

sendCoinsメソッドは、作成されたトランザクションがネットワークに受け入れられるまで(1つのピアにトランザクションを送信し、それ以外のピアから戻って来るまで)ブロックするListenableFutureを返します。返されたfutureにコールバックを登録して、伝播が完了したことを知ることができます。トランザクションに独自のTransactionConfidence.Listenerを登録して、伝播とマイニングの進行状況を監視することもできます。

より低レベルでは、これらのステップを自分で行うことができます:

//このコードが一度に1つのスレッドで実行されることを確認してください。
SendRequest request = SendRequest.to(address, value);
//この時点でSendRequestオブジェクトをカスタマイズして、トランザクションの作成方法を変更することができます。
wallet.completeTx(request);
//これらの資金が再び使われないようにする。
wallet.commitTx(request.tx);
wallet.saveToFile(...);
//提案されたトランザクションは、request.txに座っています。バックグラウンドで送信します。
ListenableFuture<Transaction> future = peerGroup.broadcastTransaction(request.tx);

//将来的には、ネットワーク全体でトランザクションのリプルが十分に理解された時点で完了します。
//ここでは終了するのを待つだけですが、終了時にバックグラウンドスレッドで実行されるリスナーをアタッチすることもできます。 あるいは、ネットワークがトランザクションを受け入れて実行すると仮定することもできます。
future.get();

トランザクションを作成するには、最初に静的ヘルパーメソッドの1つを使用してSendRequestオブジェクトを作成します。SendRequestは、部分的に完了した(無効な)Transactionオブジェクトで構成されます。他のフィールドでは、手数料、アドレスの変更、通貨の選択方法などのプライバシー保護機能のようなものはカスタマイズできません。必要に応じて部分的なトランザクションを変更することができます。あるいは、独自のトランザクションを最初から構築することもできます。SendRequestの静的ヘルパーメソッドは、部分トランザクションを構築するための単純な方法です。

次に、リクエストを完了します。つまり、送信リクエストのトランザクションに、トランザクションを有効にするために入出力が追加され、署名されることを意味します。これで、Bitcoinネットワークで受け入れられるようになります。

completeTxとcommitTxの間が固定されていないことに注意してください。
例えば、ウォレットAの鍵をウォレットBにエクスポートし、ウォレットA,Bで同じUTXOを選択し作成したトランザクションが呼び出された場合、コードが競合して失敗する可能性があります。
よりシンプルな構成を使用すると、両方の操作が実行されている間はウォレットがロックされ、二重払いをコミットしなくなります。

トランザクションをコミットするタイミング

トランザクションのコミットとは、ウォレット内のトランザクションが使用済であるか更新し、再度使用されないようにすることです。適切なタイミングでトランザクションをコミットすることは重要です。これを行う方法は様々です。

sendCoins()の初期動作は、コミットしてからブロードキャストすることです。これは非常に重要な役割です。なぜなら、ネットワークの問題が発生した場合や、複数のスレッドが同時にトランザクションを作成してブロードキャストしようとしている場合に、誤って二重で支払ってしまう可能性がなくなるからです。一方、ネットワークが何らかの理由(手数料が不十分/標準的な形式でない)で取引を受け入れない場合、ウォレットはトランザクションがない状態でBitcoinが費やされたと考えます。この場合は自分で修正する必要があります。

wallet.commitTxを呼び出さずに、代わりにpeerGroup.broadcastTransactionを使用することもできます。一度トランザクションが複数のピアによって確認されると、そこでウォレットに取り込まれコミットされます。ブロードキャストが成功した後にコミットをしたい主な理由は、新しいコードを試しており、必ずしも受け入れられるトランザクションを作成できるか分からない場合です。この場合、ウォレットを常にロールバックしなければならないのは面倒です。ネットワークが常にトランザクションを受け入れることがわかったら、送信要求を作成して完了します。そして、その結果のトランザクションをすべて1つのロックの下にコミットすることができるので、複数のスレッドが間違って二重払いすことはありません.

残高とコイン選択の理解

Wallet.getBalance()をデフォルトで呼び出した場合、あなたを驚かせるかもしれません。なぜなら送金した時のトランザクションをブロードキャストしてすぐにチェックすると、期待しているものよりも残高は少ないかもしれないからです(あるいはゼロかもしれません)。

その理由は、bitcoinjがやや複雑な残高に対する概念を持っているからです。堅牢なアプリケーションを作成するには、これを理解する必要があります。getBalance()メソッドには2つの選択肢があります。
1つ目は、Wallet.BalanceType列挙型(定数値を一つ一つ列挙する)を渡します。AVAILABLE,ESTIMATED,AVAILABLE_SPENDABLE,ESTIMATED_SPENDABLEの4つの値があります。多くの場合、これらは同じになりますが、時には変わることもあります。
2つ目はgetBalance()のオーバーロードは、CoinSelectorオブジェクトを受け取ります。

ESTIMATED残高は,おそらく残高として認識しているかもしれませんが、これはウォレット内のUTXOの事です。しかし、そのUTXOを使うのが本当に安全だということではありません。 Bitcoinはグローバルなコンセンサスシステム(POW)なので、本当にBitcoinを受け取ったと信じることができ、トランザクションロールバックされないことを決めるには微妙です。

安全の概念はAVAILABLEバランスタイプとCoinSelector抽象化によって晒されています。コインセレクタは、CoinSelectorインターフェイスを実装するオブジェクトです。CoinSelectorインタフェースには、すべてのUTXOとターゲット値のリストを指定した単一のメソッドがあり、少なくともターゲットを合計した小さな出力リストを返します。コインの選択は、安全性、プライバシー、料金効率などの要因を排除する複雑なプロセスになる可能性があります。そのため、差し替えが出来ます。

bitcoinj提供するデフォルトのコインセレクタ(DefaultCoinSelector)は、比較的安全なポリシーを実装しています。トランザクションを選択する基準として少なくとも1確認が必要であり、ウォレット自体が作成したトランザクションについては、ネットワークを介して伝搬されている限り消費可能とみなされます。これは、getBalanceかgetBalance()を使用したときに返される残高です。

1.自分が既に知っているから信頼できると言う理屈で、自分のトランザクションを暗黙的に信頼してしまいます。しかし、例えば十分な手数料を払っていないなど、バグやその他のことによって確認できないトランザクションを作成する可能性はあります。したがって、ネットワークのノードがトランザクションをリレーした事を確認しない限り、アウトプットを使う事は望ましくありません。それがマイニングされる事で信頼性を増します。

2.他のトランザクションでは、SPVモードでトランザクションが有効であることを自分で確認できないため、少なくとも1つのブロックが確認されるまで待機します。あなたのネットの接続がハッキングされた場合、存在しないBitcoinを消費する偽物のトランザクションを提供する偽物のBitcoinネットワークと繋がっている可能性があります。詳しくはSecurityModelの記事を読んでください。トランザクションがブロック内に表示されるのを待っているので、トランザクションが本物であることを確信できます。Bitcoinクライアントでは6回の確認を待ちますが、これはマイニングハッシュレートがはるかに低い時代に決まったもので、その時代は小さな偽のチェーンを作成する方がはるかに簡単でした。現代のハッシュレートでは今の所、無効なブロックを使って騙された報告がないので、1回の確認で十分です。

デフォルトのコインセレクタは、次のブロックでトランザクションが確定する確率を最大にするために、出力の経過時間も考慮に入れます。

デフォルトのコインセレクタは、サブクラス化によっていくらかカスタマイズ可能です。通常、唯一のカスタマイズする可能性があるのは、未確認のトランザクションを使うことです。例えば、アプリが受け取っている金額が有効である(例えばあなたが信頼できるノードに接続されているなど)ことを知っている場合や、扱う金額が非常に低くく詐欺の被害を気にしない場合は、Wallet.allowSpendingUnconfirmedTransactions()を使用することで、ウォレットに1回の確認なしにする別のセレクタを使用させることができます。

また、SendRequest.coinSelectorフィールドを使用して、支払いごとにコインセレクターを選択することもできます。

以上の結果からPeerGroup.broadcastTransactionを実行した直後にAVAILABLE残高を照会すると、ブロードキャストしたトランザクションがネットワーク全体に広がるまでに確認作業があるため、予想している金額より低い金額が表示されます。返されたListenableFuture が正常に完了した後でgetBalance()を呼び出すと、残高がちゃんと反映されています。

”AVAILABLE_SPENDABLE”と”AVAILABLE”の違いは、ウォレットがキーを持たない出力を考慮するかどうかです。”ウォレットを見ている”場合は、ウォレットは公開鍵を見て別のウォレットの残高を追跡している可能性があります。デフォルトでは残高を確認する為に出力(アドレス・スクリプト・キー)を使用すると失敗するにもかかわらず、残高の一部としてみなされてしまいます。”AVAILABLE_SPENDABLE”と”AVAILABLE”の2つの残高が異なるのは、秘密鍵と公開鍵だけがウォレットに混在している時です。これは高度な契約ベースのアプリを作る時に発生する可能性があります。

手数料を使う

ウォレットによって取引が完了した時に、取引に付随して手数料を付けることができます。これを制御するために、SendRequestオブジェクトには使用できるいくつかのフィールドがあります。
最も単純なのはSendRequest.feeで、これは絶対値のオーバーライドです。設定されているものが添付される手数料です。
他の便利なフィールドはSendRequest.feePerKbです。完成したトランザクションのサイズで最終的な手数料を調整することができます。ブロックスペースが限られている場合、マイナーは1000バイトあたりの手数料でランク付けを決定します。したがって通常、大きなデータスペースを使う取引はより多くの手数料を支払う必要があります。そうしないと、マイナーの手数料の閾値を下回る可能性があるからです。

また、flooding攻撃を避けるためのいくつかの手数料ルールが定められています。中でも注目すべきは、0.01BTC未満の取引は、現在10,000satoshiの手数料が必要です。デフォルトでは、ノードはこの手数料のルールに合致しないトランザクションをリレーを拒否しますが、それらをマイニングすることはもちろん許容されています。SendRequest.ensureMinRequiredFeeをfalseに変更することで、リレーできないトランザクションを作成することができます。

bitcoinjは、技術的に必要かどうかにかかわらず、デフォルトで各トランザクションに1キロバイトごとの小さな手数料を付けることを保証します。使用される手数料の額は、取引の規模(UTXOのサイズ)によって異なります。完了後にSendRequestの手数料の欄を読んで、どの程度手数料が添付されたかを知ることができます。bitcoinjがデフォルトで手数料を設定する理由はいくつかあります。

・ほとんどのアプリはすでに固定料金を設定されている
・ハイプライオリティスペース(無料で優先度の高くトランザクションを受け入れるスペース)が各ブロックにわずか27kバイトしかなく、直ぐに使い果たされる非常に少ないスペース
SPVクライアントは、次のブロックで既に27kバイトのフリーハイプライオリティスペースがいっぱいであるかどうかを知る方法がない
・デフォルトの手数料はかなり低い
・以上より取引が迅速に確認されることが多少なりとも保証される

時間の経過とともに、ほとんどのトランザクションの手数料が無料でなっても良いですが、注意深く調整されたロールアウトとともに、C++やマイニングサイドでいくつかの変更が必要になります。

変更について学ぶ

ウォレットの内容の変更について学習するためのWalletEventListenerインターフェースを提供します。これらのメソッドのデフォルト実装を取得するには、AbstractWalletEventListenerから求めることができます。

コールバックを取得する:

・お金を受け取る
・ウォレットからBitcoinが送金される(送金が送金されたかどうかに関係なく)
トランザクションのコンフィデンスの変化。
※これについてはWorkingWithTransactionsを参照してください。


適切な時間にウォレットを保存する

ウォレットはメモリ内のオブジェクトにすぎないので、単独で保存されません。ウォレットを永続的に使用する場合は、saveToFile()かsaveToOutputStream()を使用します。可能であれば、saveToFile()を使用することが最善です。これは、一時ファイルに書き込み名前を変更し、保存プロセスの途中で何か問題が生じた場合にウォレットが断片的に破損したりすることがないようにするためです。

ウォレットをどのタイミングで保存するのかは難しく、あまり頻繁に行うとアプリのパフォーマンスに悪影響を与える可能性があります。これを解決するために、Walletは名前付きファイルで自動的に保存されます。これを設定するには、autoSaveToFile()メソッドを使用します。任意でバックアップ期間、例えば遅延時間を数百ミリ秒などにすることができます。これにより、保存が必要な場合にNミリ秒ごとにウォレットを保存するバックグラウンドスレッドが作成されます。秘密鍵を追加するなどの重要な操作は、即時に自動保存を開始されます。ウォレットの書き込みを遅らせることは、トランザクションが多く出入りしてるような場合に、パフォーマンスを向上させます。自動保存リスナーを登録すると、ウォレットがいつ保存されたかを知ることができます。

ウォレットのメンテナンスと鍵のローテーション

ウォレットにはメンテナンスという概念があり、現在はキーローテーションをサポートしています。キーローテーションは、一部のキーが弱く危険にさらされている可能性があり使用を停止したいと考えている場合に便利です。
ウォレットは、新しくHDキー階層を作成し、Bitcoinをローテーションキーから新しいキーに自動的に移動させるための支払いを作成する事が出来ます。このプロセスを開始するには、既存のキーが弱くなったであろう時刻をウォレットに通知し、doMaintenance(KeyParameter、boolean)メソッドを使用してBitcoinを新しいキーに移動するトランザクションを取得します。個々の弱くなったキーだけをマークることは出来ず、作成時間に基づいてグループ管理します。

有用な例:
・あなたが複数の鍵を作成するのに使う乱数ジェネレータが予測可能であったことを知る場合
・確実に削除できないバックアップがあり、キーが盗難に合う可能性があることを心配していいる場合
※たとえば、フラッシュ/ソリッドステートディスクに書き込まれたウォレットは確実に消去するのが難しいからです

doMaintenanceメソッドは、ユーザーのパスワードキーがある場合はパスワードキーを取り、メンテナンス必要なトランザクションが実際にネットワーク上でブロードキャストされるかどうかを制御するブール値を取ります。すぐに完了するfutureを返すか、すべてのメンテナンストランザクションがブロードキャストされ、futureが作成されたトランザクションのリストを提供します。このメソッドは、たとえbool引数がfalseであってもユーザーのパスワードキーが必要な場合は例外をスローする可能性があるので、それをキャッチしてユーザーにパスワードの入力を求める必要があります。例外がスローされず、引数がfalseの場合、返されたfutureについてget()を使用してリストを取得し、それが空であるかどうかを確認することができます。それが空であれば、メンテナンスは必要ありません。そうでない場合は、何が起こっているのかをユーザーに伝え、トランザクションをブロードキャストさせる必要があります。

メンテナンスの概念は一般的です。将来、ウォレットはウォレットの最適化やユーザーのプライバシーの向上などのメンテナンス・トランザクションを生成する可能性があります。しかし、現在、キーローテーションを使用しない場合、このメソッドは何も行いません。

複数への送信と他の契約の作成

デフォルトのSendRequest静的メソッドは、一般的な形式のトランザクションを構築するのに役立ちますが、さらに高度なものを必要とする場合はどうすればよいでしょうか?独自のトランザクションをカスタマイズまたは構築し、それを自分でSendRequestに入れることができます。

ここでは、一度に複数のアドレスに送信する簡単な例を示します。新しいトランザクションオブジェクトが最初に作成され、さまざまな額のコインを送信する場所を指定します。その後、不完全な無効なトランザクションが完了します。つまり、ウォレットは、トランザクションを有効にするために必要な入力(および変更出力)を追加します。

Address target1 = new Address(params, "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL");
Address target2 = new Address(params, "1RbxbA1yP2Lebauuef3cBiBho853f7jxs");
Transaction tx = new Transaction(params);
tx.addOutput(Utils.toNanoCoins(1, 10), target1);
tx.addOutput(Utils.toNanoCoins(2, 20), target2);
SendRequest request = SendRequest.forTx(tx);
if (!wallet.completeTx(request)) {
  // Cannot afford this!
} else {
  wallet.commitTx(request.tx);
  peerGroup.broadcastTransaction(request.tx).get();
}

任意のTransactionOutputオブジェクトを追加することができ、このようにして変わっている再利用条件を持つトランザクションを構築できます。実現可能な例については、契約を参照してください。

現時点では、SendRequestはSIGHASH_ANYONECANPAYのような特殊な形式の署名を要求することはできません。これを行うには、下位レベルのAPIを使用する必要があります。幸いにもそれほど難しいことではありません。WorkWithContractsの記事でそのやり方の例を見ることができます。

秘密鍵の暗号化

もしウォレットからあまりBitcoinを支払うことがない場合、秘密鍵を暗号化することをお勧めします。Wallet.encrypt("password")メソッドは、指定されたパスワード文字列のScryptハッシュからAESキーを生成し、それを使用してウォレット内の秘密鍵を暗号化します。トランザクションに署名する時やウォレットを完全に解読する時にパスワードを入力する事が出来ます。パスワードから生成させたくない場合は、独自のAES鍵を提供することもできます。また、Scryptハッシュパラメータをカスタマイズすることもできます。

Scryptは、代替のアルゴリズムよりでブルートフォースに耐えうるよう設計されたハッシュ関数です。複雑なパラメータを選択することにより、パスワードを数秒間かけて鍵に変えることができます。WalletTemplateアプリケーションでは、ホストコンピュータの速度を測定し、scryptパラメータを選択して数秒の暗号化/復号化時間を与える方法を示すコードを見つけることができます。

暗号化が完了したら、トランザクションに署名して署名するたびにAESキーを入力する必要があります。これは、SendRequestオブジェクトを介して行います。

Address a = new Address(params, "1RbxbA1yP2Lebauuef3cBiBho853f7jxs");
SendRequest req = SendRequest.to(a, Coin.parseCoin("0.12"));
req.aesKey = wallet.getKeyCrypter().deriveKey("password");
wallet.sendCoins(req);

ウォレットは、テキスト・パスワードまたはKeyParameterのいずれかをとるwallet.decryptメソッドを使用して復号化できます。

bitcoinjは一時ファイルを作成して名前を変更することでウォレットを保存するため、暗号化しても秘密鍵を生成する為の元ネタがディスクに残っている可能性があることに注意してください。特に現代のSSDベースのシステムでは、ディスク上のデータを削除することはほとんど不可能です。暗号化は、非常に高度でない攻撃者に対してハードルを上げるための合理的な方法と見なすべきです。しかし、誰かが既にウォレットファイルにアクセスした場合、理論的には、ユーザーがパスワードを入力してこの方法で秘密鍵を取得することができます。暗号化は便利ですが、万全なセキュリティーに対する解決策と見なすべきではありません。

ウォッチングウォレット

ウォレットはP2Pネットワークから複製できますが、この複製されたウォレットには支払いに必要な秘密鍵をを保有していません。これは非常に便利で、オンラインウェブショップでは大変役立ちます。Webサーバーはウェブショップのウォレットへの支払いを全て監視することができるため、顧客が支払った時点を知ることができます。しかし、そのウォレット自体から引き出しを許可することはできない為、そのウォレットからハッキングされBitcoinを盗られるリスクは大幅に減少します。

他のHD BIP32/bitcoinjウォレットからウォッチングウォレットを作成するには:

Wallet toWatch = ....;

DeterministicKey watchingKey = toWatch.getWatchingKey();

//標準化されたbase58でエンコードされたシリアル化を取得します。
System.out.println("Watching key data: " + watchingKey.serializePubB58());
System.out.println("Watching key birthday: " + watchingKey.getCreationTimeSeconds());

//////////////////////////////////////////////////////////////////////////////////////////

DeterministicKey key = DeterministicKey.deserializeB58(null, "key data goes here");
long keyBirthday = 12345678L;
Wallet watchingWallet = Wallet.fromWatchingKey(params, key, keyBirthday);
//今度は "watchingWallet"を添付してチェーンを通常通り再生してください。

この場合のウォッチングキーは、BIP32(階層的決定性ウォレット)に定義されたアカウント番号が0のキーの事で、マスターキーから派生した最初のキーを意味します。BIP32の仕組みにより、ウォッチングキーはマスター公開鍵自体になることはできません。
内部(おつり用)チェーンと外部(支払い用)チェーンは両方ともマスターキーの下にあり、したがって各ウォレットによって生成される全てのキーを同期します。 Webサーバーの場合、サーバーは通常どおりwallet.freshReceiveAddress()を呼び出すことができます。 元のウォレットは、受信したすべての支払いを見てウォレットに表示し、同期したときにそれらを使用することができます。 ただし、セキュリティを強化するために、オフラインで保存することができます(いわゆるこれがコールドウォレット)。

HDウォッチングキーがない場合でも、任意のアドレスまたはスクリプトを監視するウォレットを作成することもできます。

wallet.addWatchedAddress(...);
wallet.addWatchedScripts(List.of(script, script, script));

明らかに、この場合、新しい鍵やアドレスやスクリプトの自動同期されません。

マルチシグウォレットとプラガブル署名者

bitcoinj0.12から、トランザクションに署名する為に複数の署名者が必要なマルチシグウォレットを採用しています。これにより、追加の権限が得られた場合や取引のリスクが低いと判断された場合にのみ、遠隔でトランザクションに複数人で署名することができる”リスク分析サービス”を利用することができます。

財布と別の財布を結合させるには、配偶者はデフォルトのBIP32ツリーを使用してHDウォレットにする必要があります。外部プロトコルを使用して、他のウォレットのウォッチングキーを取得する必要があります(前述のように取得できます)。

DeterministicKey spouseKey = ....;
wallet.addFollowingAccountKeys(Lists.newArrayList(spouseKey), 2);   // threshold of 2 keys, i.e. both are needed to spend.

Address a = wallet.freshReceiveAddress();
assert a.isP2SHAddress();

ウォレットに「フォローイングキー」があると、結合してAPIが変更されます。 freshReceiveKeyまたはcurrentReceiveKeyは、シングルキーを返すように指定されており、結合したウォレットは、pay-to-script-hash(P2SH)アドレスを使用して送金できます。代わりに、currentReceiveAddressとfreshReceiveAddressだけを使用する必要があります。

HD階層は、ウォレット自体が使用するキーの階層と一致する必要があります。つまり、リモートサーバーがすべてのユーザー間で共有される単一のHDキー階層を持つことはできません。各ユーザーはそれぞれ独自のキーツリーを持つ必要があります。

Bitcoinの支払い方法も変わります。高レベルのAPIは同じですが、プラグイン可能なトランザクション署名者をインストールせずにBitcoinを送信しようとすると、Walletは例外をスローします。その理由は、自身の秘密鍵で署名する方法を知っていても、リモートサービスと通信して関連する署名を取得して終了する方法を知らないからです。

プラグイン可能なトランザクションシグナーズとは、TransactionSignerインタフェースを実装するオブジェクトのことです。引数なしのコンストラクタを持つ必要があり、認証資格情報を保存時にウォレットにシリアル化されるデータとして提供する事ができます。
設定中に作成する必要がありますが、遠隔サービスとの通信がに必要な情報が設定されていれば、認証を行い署名を得ることが出来ます。

設定後、isReadyメソッドはtrueを返すようになります。Wallet.addTransactionSignerメソッドを使用してウォレットに追加できるようになりました。これは、デシリアルライズがリフレクションを使用してオブジェクトを再作成する時に一度だけ実行する必要があります。TransactionSigner.deserialize(byte [])メソッドを使用してリロードします。

Walletクラスは、トランザクションシグナーズを順番に実行します。 WalletのKeyBagで秘密鍵を使用する方法を知っているLocalTransactionSignerがまず登場します。TransactionSigner.signInputs(ProposedTransaction、KeyBag)メソッドが呼び出され、ProposedTransactionオブジェクト内のトランザクションは、遠隔サービスからフェッチされ、適切に挿入される署名を持つ必要があります。使用するHDキーパスは、ProposedTransactionオブジェクトから取得できます。

マリードウォレットのサポートは比較的新しいもので実験的です。あなたがそれで何かをする、または問題に遭遇した場合は、私たちに教えてください!

ウォレットをUTXOストアに接続する

デフォルトでは、ウォレットは、Bloomフィルタを使用して通常のSPV方式でブロックチェーンをスキャンすることによってUTXOを見つけることを想定しています。
いくつかのユースケースにおいて、すでにUTXOのデータを別のソースから取得している場合があります。例えば完全にブロックストアが取り除かれた一つによって構成されたデータベースから取得出来ます。独自のデータの代わりにこのようなソースを使用するようにWalletクラスを設定することができます。そのため、ブロックチェーンを直接使用する必要はありません。これは、ウォレットをすばやくロードして破棄する必要のあるWebウォレットのようなものを構築しようとするときに便利です。

この方法でウォレットを構成するには、UTXOProviderインタフェースの実装が必要です。このインタフェースはかなりシンプルで、2つのコアメソッドがあります。
1つはプロバイダーが認識しているブロック高を返すメソッド。もう1つはAddressオブジェクトのリストがあるUTXOオブジェクトのリストを返すメソッドです。

public interface UTXOProvider {
    List<UTXO> getOpenTransactionOutputs(List<Address> addresses) throws UTXOProviderException;
    ....
 }

このAPIは将来、アドレスのリストではなくスクリプトのリストを取るように変更されることに注意してください。すべての出力タイプをアドレスで表現できるわけではありません。

便利なことに、bitcoinjがFullPrunedBlockStoreの実装によるデータベースのバックアップを使用する事によって、ウォレットを直接データベースに接続できます。

UTXOプロバイダを取得したら、Wallet.setUTXOProviderを使用してプラグインします。残高が計算されるか、または支出が完了すると、プロバイダーは消費可能な出力を照会されます。

UTXOプロバイダの関連付けはシリアル化されていないため、Walletがロードされるたびにリセットする必要があります。 しかし、典型的なWebウォレットのシナリオでは、これは大したことではありません。ウォレットは、明示的にシリアル化されるのではなく、必要になるたびにランダムなシードから再構築されます。