署名前に署名デバイスが行うチェック
オンライン端末からの QR コードは、信頼できない入力です。BitTool Signer はすべてのリクエストを潜在的に悪意のあるものとして扱い、どのチェーンでも同じパイプラインを実行します。
- 1QR フレームを組み立て、リクエストを厳密にデコードします(CBOR、PSBT、トランザクションのバイト列)。
- 2署名するバイト列そのものをパースし、確認画面を作成します(金額、受取人、手数料、コントラクト呼び出し、警告)。
- 3公開鍵だけを使ってリクエストをローカルのウォレットと照合し、リクエストのハッシュに紐付いた署名計画を確定します。
- ユーザーが内容を確認し、リスクを承認して、パスワードを入力 ▶
- 4シードから鍵を再導出し、使用する前に計画と照合します。
- 5署名し、新しい署名を検証して、リクエストと同じフォーマットでレスポンスをエンコードします。
厳密なデコード
チェーン固有の処理を行う前に、トランスポートを検証します。
- CBOR は確定長を使い、マップのキーに重複がなく、末尾に余分なバイトがなく、ネストは最大 24 階層、アイテム数は最大 20,000 でなければなりません。未知のリクエストフィールドは拒否されます。
- アニメーション QR コードは 1 つのセッションから来たものでなければなりません。2 つのリクエストのフレームが混在した場合は、結合せずにエラーとします。
- サイズの上限:2,048 フレーム、フレームあたり 4,300 文字、スキャンしたテキストは合計 1 MiB、デコード後のペイロードは 128 KiB。
元の CBOR(または PSBT)のバイト列は、デコードしたリクエストに添付されたまま保持されます。署名の直前にエンジンはそれを再度デコードし、確認した内容と一致しなくなっていれば署名を拒否します。
確認画面は実際のバイト列を示す
エンジンはトランザクションやメッセージを自らパースし、オンラインのウォレットが提供する要約を信用することはありません。たとえば次のとおりです。
- Bitcoin:すべてのインプットとアウトプットの金額とアドレス、手数料、どのアウトプットがおつりか。アウトプットをおつりとみなすのは、署名デバイスが自身の公開鍵からそのスクリプトを再導出できた場合だけです。PSBT のラベルだけでアウトプットが「おつり」になることはありません。
- EVM:ネットワーク、nonce、手数料、受取人、ネイティブ通貨の金額、デコードした呼び出し(ERC-20/721/1155 の送金と承認、Permit、Permit2、Safe のトランザクションなど)。spender と operator のアドレスは、同梱されている既知の公開コントラクトのリストと照合され、未知の spender の場合はリスクレベルが上がります。
- TRON:コントラクトの種類、金額、TRC-20/721 の呼び出し、手数料上限、パーミッション ID。
- Solana:認識できるすべての命令(System、SPL Token、Token-2022、ATA、Compute Budget、Memo、Stake、NFT プログラム)、手数料支払者、署名者。
各リクエストには、互いに独立した 2 つのラベルが付きます。
| デコード | リスク |
|---|---|
| 完全にデコード · 部分的にデコード · 不透明 | 通常 · 警告 · 高 |
有効だが完全にはデコードできないリクエスト(たとえば未知のコントラクト呼び出し)も署名できますが、それは そのリクエストについて ユーザーが明示的にリスクを承認した後に限られます。オンのままになる「ブラインド署名」の設定はありません。不正な形式のバイト列、非対応のトランザクションタイプ、矛盾するネットワーク情報は即座に拒否され、ユーザーが上書きすることはできません。
特に記載がない限り、両アプリが共有するエンジンの問題です。セキュリティレビューを参照してください。
- リスクの確認(iPhone では BTS-OP5-01、Android では BTS-12、低)。 上限なしの承認や
setApprovalForAllなど、完全にデコードされた高リスクのリクエストにも明示的な確認が必要ですが、このルールを強制しているのはアプリのインターフェース(「署名」ボタンが無効のまま)です。エンジンが強制するのは、部分的にデコードされたリクエストと内容不明のリクエストだけです。QR コードから回避することはできません。 - Solana の要約(BTS-07、中)。 ごく少額の送金と一緒に送られた権限変更、委任、内容不明の命令が、その送金として要約されます。iPhone 1.0.0 では、バーンも同じように隠れることがあります。警告とリスクの確認は残ります。
- Bitcoin のおつり(BTS-08、中)。 ウォレット自身のシングルシグ構成であれば、どのインデックスの出力もおつりと見なされます。閲覧専用ウォレットが決してスキャンしないインデックスも含まれます。
- EVM と TRON の要約(BTS-09、低〜中)。 不明なコントラクト呼び出しと一緒に送るネイティブの金額が、保存される要約に含まれません。確認カードには表示されます。
正しいウォレットとの照合
どのウォレットで署名するかを、ユーザーが選ぶことはありません。エンジンはリクエストをすべてのローカルウォレットの 公開 データと比較し、一致がちょうど 1 つの場合にのみ受け付けます。
| チェーン | 使用する根拠 |
|---|---|
| Bitcoin | bip32Derivation のフィンガープリントとパスを、インプットの実際のスクリプトと照らし合わせて確認します。導出情報がない場合は、各シングルシグアカウントの /0/0 だけを試します。 |
| EVM | 導出パス(+ マスターフィンガープリント)とアドレスのいずれか、または両方。パスがない場合はインデックス 0 だけを比較します。 |
| TRON | 導出パス(任意のインデックス)、またはインデックス 0〜99 と比較されるアドレス。 |
| Solana | 3 つのプリセット鍵のいずれか。かつ、その鍵がメッセージ内の必須署名者スロットを占めている必要があります。 |
一致なし、署名者の欠落、曖昧な一致(2 つのウォレットが該当)、ウォレットデータの不足は、それぞれ別のエラーになります。署名デバイスが全ウォレットのシードを総当たりで試すことはありません。
照合の結果は、正準なリクエストハッシュ、元のバイト列、署名するバイト列そのもの、対象の鍵とインプットを紐付けた、変更不可能な 計画 になります。パスワードが入力されると、エンジンは鍵を再導出し、公開データを再構築し、照合をやり直して計画全体を比較してから、初めて秘密鍵に触れます。
導出情報は必ず含めてください。PSBT には bip32Derivation を、eth-、tron-、sol-sign-request にはパスとフィンガープリントを含めます。署名者の情報が揃ったリクエストは即座に照合されます。情報がないリクエストはインデックス 0 へのフォールバックとなり、それ以外のアカウントでは失敗する可能性があります。
署名をリクエストに紐付ける
- エンジンは正準なリクエスト全体をハッシュし、署名し、自身の署名を検証したうえで、元のリクエストハッシュとともに結果を返します。署名中にユーザーがアプリを離れたり、新しいリクエストが古いリクエストを置き換えたりした場合、遅れて届いた結果は破棄されます。
- EVM、TRON、Solana のレスポンスはリクエスト ID をそのまま返し、署名は確認したバイト列そのものを対象とします。Bitcoin のレスポンスは、同じ PSBT に署名を追加したものを返します。それ以外のフィールドは一切変更されていてはなりません。
- 1 つの PSBT に複数のローカルウォレットが必要な場合があります(同じ iPhone に 2 つの鍵があるマルチシグなど)。各ウォレットはそれぞれ独自のパスワードを持つ別々のタスクとなり、部分署名はタスク間で保持されます。
バージョン 1.0.0 に対する 2 件の セキュリティレビュー はどちらも、この一連の紐付けを確認しています。Opus 5 のレビューでは、改ざんされた計画が拒否されること、そして Release ビルドで唯一の署名の入口がこれらのチェックをすべて通ることを、実際に実行して確認しました。
オフラインでは扱えない範囲
確認画面は、知り得ない事柄についてはその旨を明示します。トークンコントラクトの挙動、現在の残高と allowance、TRON マルチシグのメンバーがまだ承認されているかどうか、Solana の blockhash がまだ有効かどうか、Blur の Merkle root が実際に何にコミットしているか、といった事柄です。オンラインであるウォレットのほうが、QR コードを作成する前にこうした情報を表示するのに適した立場にあります。