Casa Tookan がリカバリーフレーズと秘密鍵をどこに保管し、誰がそれを解錠でき、署名にどう使われるか。バージョン 2.1.0 以降。
秘密はすべて署名機能が保持しています。アプリが扱うのは、送金に使えない情報だけです。
| 何を | 保管場所 | 読める者 |
|---|---|---|
| リカバリーフレーズ、秘密鍵 | 封印されたエステートのレコード(キーチェーン / アプリ専用ファイル) | 所有者の認証後のネイティブ署名機能 |
| それらを封印する鍵(DEK) | 2重にラップ:OS のロックと、ウォレットの PIN | Face ID / 指紋 / パスコード後の OS、正しい PIN の後の署名機能 |
| ウォレットの PIN | どこにもなし。PIN から導出しハードウェアの秘密と混ぜた鍵のみ | 誰も読めない |
| PIN 失敗回数カウンター | キーチェーン / 専用ファイル、HMAC で保護 | ネイティブ署名機能 |
| xpub、アドレス、エステート一覧 | 署名機能のメタデータ + アプリのストレージ | ロック解除されたスマートフォンを持つ人なら誰でも。ただし送金には使えない |
| Casa の名前、色、通貨 | アプリのストレージ(AsyncStorage) | アプリ |
アプリは署名を依頼することしかできず、署名するのは署名機能です。それも、署名される正確なバイト列からコアがデコードした内容を、所有者が署名機能自身の画面で確認した後に限られます。
signer-core.js)require なし。ひとつのランダムなデータ鍵が、すべてのエステートを封印します。その鍵は2重にラップされているので、入口をひとつ失ってもウォレットを失うことはありません。
セットアップ時に、署名機能は32バイトのランダムなデータ暗号化鍵(DEK)を生成します。各エステート(リカバリーフレーズ、またはインポートした鍵)は、DEK のもとで AES-256-GCM により封印されたレコードになります。認証付きデータがレコードをそのエステートに結び付けるため、別のエステート id に移されたレコードは復号できなくなります。
record = AES-256-GCM( DEK, iv, recordJSON,
aad = "casa-signer:v1:estate:" + estateId )
レコードには、フレーズとその事前計算済みシード(JIT のないエンジンでは BIP-39 のストレッチングが遅すぎるため)、またはインポートした鍵が入っています。
dek.os:OS のロック.userPresence、アクセス可能性 WhenPasscodeSetThisDeviceOnly のキーチェーン項目。パスコードが削除されると iOS がこれを削除します。dek.pin:PIN + ハードウェアKEK = HKDF-SHA256( PBKDF2-SHA256(PIN, salt, 600 000) ‖ hw,
info = "casa-signer:v1:pin-kek" )
PIN は保存されません。誤入力はネイティブで数えられます。最初の5回までは自由で、その後は失敗のたびに PIN が30秒ロックされ、最大1時間まで倍増します。カウンターは端末専用の鍵で HMAC 保護されているため、アプリの JavaScript からも、復元したバックアップからもリセットできません。
セットアップのたびに、封印されたレコード、続いて両方のラップを書き込み、すべてを読み戻してバイト単位で比較します。その後でようやく、ウォレットの公開情報である meta を書き込みます。meta がなければここには何もないという意味です。中断されたセットアップの残骸は、次回起動時に消去されます。
| iOS | Android | |
|---|---|---|
レコード、ラップ、カウンター、meta | キーチェーン、クラス Internet password、サーバー casa-signer.local、ThisDeviceOnly | noBackupFilesDir/casa-signer/ 内のファイル |
| ハードウェア鍵 | Secure Enclave(PIN ラップ)、キーチェーンのアクセス制御(OS ラップ) | AndroidKeyStore、あれば StrongBox |
| バックアップ、ほかの端末 | 一切なし(ThisDeviceOnly) | 一切なし(バックアップ対象外のディレクトリ、ハードウェアに結び付いた鍵) |
アプリの JavaScript には、キーチェーンやキーストアのモジュールが一切ありません。iOS では、署名機能の項目は、汎用パスワードのストア(React Native のモジュールが使う種類)からはアクセスできないキーチェーンのクラスを使っています。
エステートごとに、署名機能は公開データだけを公開します。アプリはそこからすべてのアドレスを導出します(src/signer/publicKeys.ts)。残高を表示しトランザクションを組み立てることはできても、送金はできません。
m/0'/0m/84'/0'/0'/0m/86'/0'/0'/0m/44'/60'/0'/0…/hd。forbidden.test.ts)は、アプリのコードが BIP-39 をインポートしたり、シードから導出したり、秘密鍵に触れたり、トランザクションに署名したり、秘密のストアを開いたりすると、ビルドを失敗させます。prepare が正確なバイト列をデコードします:金額、受取人、お釣り、手数料、Counterparty のメッセージ、トークンの承認、署名するアドレス。アプリが渡すコンテキスト(サイト名、トークンのシンボル、Casa の名前)には未検証のラベルが付きます。sign はリクエストを再度デコードし、署名しようとする内容が表示した内容と異なれば拒否します(E_DIGEST_MISMATCH)。ひとつの画面とひとつの認証で、まとめて処理できます(複数の PSBT、マルチチェーンのログイン)。リクエストは一度にひとつ、10秒間に最大3件までです。
設定 › 表示 は、署名機能に所有者の認証を求め、フレーズや Casa の鍵を署名機能自身の画面に表示させます。スクリーンショットと画面録画はブロックされ、60秒後に自動で隠れます。秘密がアプリに返されることはありません。
どちらもネイティブで実行されます。フレーズがアプリの JavaScript を通ることはありません。コミット前に中断された移行は、単に最初からやり直されます。古い PIN を忘れた場合は、そのウォレットを消去してリカバリーフレーズから復元します。
署名機能は古いボールトを読み、独自のパッドで古い PIN を尋ね(以前の試行回数は引き継ぎ)、メインと追加のエステートを復号し、新しい DEK のもとで封印し、検証し、コミットします。その後、古い項目をすべて削除します。
署名機能は古いキーチェーン / キーストアのレコードを読み、古い4桁の PIN を確認し(回数制限あり)、新しい PIN を尋ね、封印し、検証し、コミットし、古いレコードを削除します。Casa の名前と通貨は秘密の情報ではないため、別に読み込まれます。
| 脅威 | 結果 | |
|---|---|---|
| アプリ内の npm 依存パッケージの侵害 | 秘密は一切読めません。できるのは署名を依頼することだけで、その都度ネイティブで表示・確認されます。 | 対策済み |
| 誤解を招く内容への署名を求める dApp | 署名機能はバイト列が実際に何をするかを表示します。無制限の承認、permit、部分署名には警告が付きます。 | 対策済み |
| ロックされたスマートフォンの盗難 | レコードには OS のロック、または PIN とスマートフォンのハードウェアが必要です。PIN の推測は回数制限されます。 | 対策済み |
| バックアップやコピーされたファイル | バックアップには何も含まれず、コピーされたファイルにはハードウェア鍵がありません。 | 対策済み |
| 画面ロックの解除 | ウォレットは PIN で開き続けます。 | 対策済み |
| 端末のパスコードを知っていて、ロック解除されたスマートフォンを持つ人 | 署名できます。Apple Pay と同じく、端末のパスコードが認証だからです。 | 設計どおり |
| 侵害された OS(脱獄、root 化) | 対象外です。 | 対策なし |
| リカバリーフレーズなしでのスマートフォンの紛失 | 資産は失われます。鍵はそのスマートフォンにしか存在しません。 | 対策なし |
signer-core/src/ → npm run signer:build。signer:check はテストの前に実行され、バンドルがずれていれば失敗します。modules/casa-signer/ios/Custody.swift、…/android/…/Custody.kt。npm run signer:native-test は macOS 上でカストディとコアを実行します。modules/casa-signer/mock.ts を使います。Node VM 内の実際のバンドル済みコアと、メモリ上のカストディです。アプリには含まれません。