Skip to content

セキュリティ仕様 ​

English

このプロジェクトを信頼しやすくするために、どこで何を守っているかを説明します。ブラウザで動かすときの守り方と、署名エンジンの切り分けの2つです。

ブラウザエンジンを信頼するときのリスクの抑え方 ​

Webアプリでは、鍵も署名エンジンもブラウザ(iPhoneではWebKit)の中にあります。ブラウザ自体は信頼するしかないので、その上で漏れる経路と、鍵がメモリに残る時間を減らしています。

対策中身防ぐもの残るリスク
通信先を自分のサイトに限るContent-Security-Policy でdefault-src 'self'、connect-src 'self'、form-action 'none'、object-src 'none'ほかのサイトへの送信、外部スクリプトの読み込み同じサイトへの送信はできる。配る側が乗っ取られたバージョンには効かない
ネットにつながると鍵を消すオンラインの間は鍵を読み込めず、読み込んだ鍵はつながった時点で消す鍵がある間の送信ブラウザが知らせる範囲でしか分からない
アプリを離れると鍵を消す画面が隠れたら鍵を消し、画面も隠す。戻るとページを読み込み直すほかのアプリを使っている間に鍵が残ることアプリ切り替えの画面に残るかはブラウザ次第
使い終わったバイト列を0で埋めるエントロピー、サイコロの目、署名、カメラの画像メモリに残った鍵の材料JavaScriptの文字列(入力した単語、パスフレーズ)は消せない。そのため読み込み直しで捨てる
シードは画面内のキーで入力ネイティブ版と同じキーパッドキーボードアプリによる記録パスフレーズはキーボードを通る。標準キーボードを使う
ネイティブと同じ署名エンジン同じWASMモジュールを、同じRustのホストごとWASMにして動かす(署名エンジンの切り分け)Web版だけの実装の違いネイティブ版のSHA-256照合は、照合するコードもサイトから届くので効かない
入れたバージョンだけを使うService Workerが全ファイルを取り込み、オフラインでもそのバージョンで動く。新しいバージョンは開き直したときだけ知らないうちにバージョンが変わることネットにつなぐと新しいバージョンが届く
バージョン名を見せる設定 › このアプリのバージョン。GitHub Actionsでビルドすれば、同じコミットは同じ名前差し替えられたバージョンに気づかないこと比べる相手(リリースノート)を信頼する
パスキーで暗号化して保存WebAuthn PRF。開くたびにFace ID、指紋、またはパスコード。iPhoneはiOS 18以降ブラウザのデータを抜かれても開けないパスコードを知る人は開ける。Appleアカウントへのサインインが要る

防げないことは次の3つです。

  • ブラウザ自体の脆弱性や改ざん: OSの更新で直すしかありません。ネットにつながないことで、突かれる機会を減らします。
  • 配る側が乗っ取られた状態で入れること: 入れる時点のサイトを信頼します。避けたいなら自分のPagesから入れます。
  • スクリーンショットと画面収録: ネイティブ版と違い、止められません。

署名エンジンの切り分け ​

署名エンジンは3つのWASMモジュールに分かれ、それぞれが受け取れるもの、返せるものを最小限にしています。3つとも同じバイト列のまま、ネイティブ版(Android・iOS)とWebアプリの wasmi ホストで動かします。

それぞれのモジュール ​

モジュール役割作りしていること
parser.wasm信頼しない入力を読む。鍵を持たないC(同じ出力を返すRust版もある)、import 0アニメーションQR(UR)をつなぎ合わせてPSBT v0を解析し、6712バイトの決まった形の「計画」にまとめる。署名をPSBTに戻し、URにして返す
signer.wasm鍵を持つC(libsecp256k1)、import 0計画をもう一度確かめて、画面に出す内容を作り、署名する。ニーモニック、SeedQR、BIP32、BIP85、BIP137、xpub、マルチシグも扱う。PSBTそのものは読まない
checker.wasm追加の照合で、署名を止めることしかできない。鍵を持たないLean 4 のモデルをCにしてWASMへ、import 0signer.wasmとは別に書いた、署名前の判断のモデル。合計、手数料、署名する入力、出力の持ち主、画面の表示を計算し直し、signer.wasmと1バイトでも違えば署名しない

parser.wasm と signer.wasm は bitwasm-signer、checker.wasm は btcsigner-spec のモデルから bitwasm-signer-lean でビルドしています。この2つのリポジトリはまだ公開していないため、今は checker.wasm を公開されたソースから作り直したり確かめたりはできません。

署名エンジンの3つのモジュールとデータの流れ

読めるものと出せるもの ​

モジュールのあいだの受け渡しはすべてホスト(Rust)が行い、モジュールには import を1つも渡しません。そのため、どのモジュールも下の表にない情報には触れられず、ネット、ファイル、時計にも触れられません。出力は決まった大きさの領域からホストが読むだけで、読んだあとは0で埋めます。

parser.wasmsigner.wasmchecker.wasm単語入力用 signer.wasm画面
シード・秘密鍵—持つ※1———
QRの生データ読む———出す※2
計画出す読む読む——
前の取引出す読む読む——
ナンス出す読む———
確認内容・画面の表示—出す読む・出す※3—読む
署名—出す——読む
xpub・ディスクリプタ—出す——読む
単語・SeedQR—出す※4—読む※5読む※4
乱数—読む※6———
  • ※1 作るときと読み込むときに受け取ります。外へは出しません。

  • ※2 カメラで読んで、ホストへ渡します。

  • ※3 signer.wasmの事実を読み、計算し直した結果を出します。

  • ※4 作ったときと、表示を選んだときだけです。

  • ※5 入力中の単語です。返すのは候補とチェックサムの結果だけです。

  • ※6 新しい鍵を作るとき、ホストから受け取ります。

  • 単語入力用の signer.wasm は、同じ signer.wasm を鍵を入れずにもう一つ動かしたものです。単語を打っている間、鍵を持つインスタンスに触れません。

  • 署名にはコーディネーターから届いたナンスを使う(anti-exfil)ので、署名の乱数に鍵を紛れ込ませて持ち出すことはできません。

  • 鍵を消すと、ホストは signer.wasm のメモリを丸ごと0で埋めます。

  • キーパッドの入力状態はまだ画面側のJavaScriptにあります。エンジンに移すのは今後の作業です。