Files
Claude c37f5291e8 Store encrypt/decrypt payloads as ciphertext, decrypt on demand
History/activity rows previously persisted the plaintext of every
NIP-04/NIP-44(/v3) encrypt and decrypt operation (encrypt = plaintext
input, decrypt = plaintext output) and the decrypted private zap. That
left sensitive cleartext at rest in history_db_$npub.

Now the encrypted form is stored instead and the plaintext is recovered
on demand only when the activity/history screen renders a row:

- Encrypt requests store their ciphertext output; decrypt requests store
  the ciphertext input that arrived in the request. Rejected encrypts
  store nothing (no ciphertext exists, so no plaintext is leaked).
- HistoryEntity gains encryptionPubKey + encryptionScope; the existing
  kind column carries the NIP-44 v3 kind. ActivityRow decrypts via
  account.decrypt / nip44v3Decrypt / decryptZapEvent, falling back to the
  stored value for pre-migration rows or missing context.
- Ciphertext rows are exempted from content truncation so they remain
  decryptable.

Covers all three ingestion paths (ContentProvider/relay via
SignerProviderQuery, nostrsigner:// intents, NIP-46 bunker approvals) and
both accept and reject flows. Adds HistoryDatabase migration 3->4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WABMihCLr9XN51uLWj93oX
2026-06-19 10:21:23 +00:00

123 lines
3.7 KiB
JSON

{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "9c7e0cf73a7c502f89cfc6dc97b0b56c",
"entities": [
{
"tableName": "history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `pkKey` TEXT NOT NULL, `type` TEXT NOT NULL, `kind` INTEGER, `time` INTEGER NOT NULL, `accepted` INTEGER NOT NULL, `translatedPermission` TEXT NOT NULL, `content` TEXT NOT NULL, `encryptionPubKey` TEXT NOT NULL, `encryptionScope` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pkKey",
"columnName": "pkKey",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "kind",
"columnName": "kind",
"affinity": "INTEGER"
},
{
"fieldPath": "time",
"columnName": "time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accepted",
"columnName": "accepted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "translatedPermission",
"columnName": "translatedPermission",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "encryptionPubKey",
"columnName": "encryptionPubKey",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "encryptionScope",
"columnName": "encryptionScope",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "history_by_pk_key",
"unique": false,
"columnNames": [
"pkKey"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `history_by_pk_key` ON `${TABLE_NAME}` (`pkKey`)"
},
{
"name": "history_by_id",
"unique": false,
"columnNames": [
"id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `history_by_id` ON `${TABLE_NAME}` (`id`)"
},
{
"name": "history_by_time",
"unique": false,
"columnNames": [
"time"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `history_by_time` ON `${TABLE_NAME}` (`time`)"
},
{
"name": "history_by_key_and_time",
"unique": false,
"columnNames": [
"pkKey",
"time"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `history_by_key_and_time` ON `${TABLE_NAME}` (`pkKey`, `time`)"
}
]
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9c7e0cf73a7c502f89cfc6dc97b0b56c')"
]
}
}