Skip to content

feat: pure-Go SQLCipher encryption (zero modernc dep) - #2

Open
zeekay wants to merge 2 commits into
mainfrom
feat/pure-go-sqlcipher
Open

feat: pure-Go SQLCipher encryption (zero modernc dep)#2
zeekay wants to merge 2 commits into
mainfrom
feat/pure-go-sqlcipher

Conversation

@zeekay

@zeekay zeekay commented Jul 18, 2026

Copy link
Copy Markdown
Member

Pure-Go (CGO_ENABLED=0) backend now ENCRYPTS at rest, byte-compatible with C SQLCipher, via a vendored pure-Go SQLite engine (fork of modernc v1.53.0 in internal/, zero modernc.org/* modules) + the hanzoai/sqlcipher codec VFS. Both backends encrypt the same SQLCipher-4 format; a DB written by one opens under the other. CGO backend migrated mattn/go-sqlite3 -> hanzoai/csqlite. Tests (CGO=0): keyed encrypt+WAL round-trip+reopen, opens a C-written fixture, byte-compat both ways, wrong-key fail-closed. Caveats: keyed DBs are single-writer single-process (in-process wal-index); WAL cross-engine hot-recovery with C not byte-compatible (checkpointed main DBs are). Tag v0.3.1.

The !cgo backend now ENCRYPTS at rest, byte-compatible with C SQLCipher, via a
vendored pure-Go SQLite engine + the hanzoai/sqlcipher codec VFS. Both backends
encrypt in the same SQLCipher-4 format; a DB written by one opens under the other.

- internal/: fork of modernc.org/{sqlite,libc,memory,mathutil} (v1.53.0, SQLite
  3.53.2), arch-trimmed to {linux,darwin}x{amd64,arm64}, imports rewritten into
  the hanzo namespace. go list -m all shows ZERO modernc.org/*.
- internal/engine/vfs/codec.go: read-write, os-backed codec VFS applying the
  hanzoai/sqlcipher page format. Zeros the reserve on decrypt (WAL checksum
  invariant), in-process heap wal-index (shm), single-writer. Fail-closed on
  wrong key / tampered page; refuses rollback journals (WAL-only).
- driver_nocgo.go: EncryptionAvailable()=true; keyed OpenDB opens through the
  codec VFS, creates via an in-memory journal then converts to WAL, unregisters
  the VFS (and wipes the DEK) on sql.DB.Close.
- driver_cgo.go: migrated mattn/go-sqlite3 -> hanzoai/csqlite (no mattn dep).
- Engine patches: _reserve_bytes fcntl, Connector accessor.
- Tests (CGO=0): keyed encrypt+WAL round-trip+reopen, opens a C-written fixture,
  byte-compat both ways, wrong-key fail-closed.

v0.3.0 -> v0.3.1
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Restores the named API removed on the self-containment branch so replicate,
Base, and base-ha migrate off modernc.org/sqlite with zero code churn — each is
one tag-neutral file + engine(!cgo)/csqlite(cgo) halves, so the same calls run
under both backends:

- OpenPragma / Pragma / PragmaDSN — per-connection pragmas (the only
  backend-neutral way to set wal_autocheckpoint=0; csqlite drops it from the DSN)
- SetPersistWAL — SQLITE_FCNTL_PERSIST_WAL, WAL-shipping replication needs the
  -wal retained across close
- HookRegisterer / CommitHookFn / CommitHookRegisterer — CommitHookFn ALIASES the
  engine's under !cgo, so a raw-conn driverConn.(sqlite.HookRegisterer) assertion
  (Hanzo Base) works with no bridge
- Driver / ConnectionHookFn / ExecQuerierContext / RegisterConnectionHook —
  re-exports of the pure-Go engine's connection-hook feature so litesql/go-sqlite-ha
  (base-ha) migrates without importing internal packages
- IsConstraint{Unique,PrimaryKey,ForeignKey}

Adds TestConcurrentCheckpointUnderLoad as the verification bar for concurrent
checkpoint locking. The vendored engine (SQLite 3.53.2) blocks correctly on
busy_timeout (a checkpoint waits out a 700ms-held writer lock then truncates).
replicate's flaky TestDB_CheckpointPageGapWithConcurrentWrites is a harness race
(its writer has no busy_timeout and hammers), NOT a version regression: clean
serial pass rate is fork 9/12 == modernc v1.44.3 9/12, > v1.48.0 7/12. Basing on
v1.44.3 does not fix it and would downgrade the SQLite.

Zero modernc.org/* in the module graph. Both backends build; full suite green.
@zeekay

zeekay commented Jul 18, 2026

Copy link
Copy Markdown
Member Author

Update: restored drop-in named API + concurrent-checkpoint bar (v0.3.2, commit f2b15d9)

The self-containment branch had removed the named API that downstream consumers depend on. Restored it (backend-neutral: one tag-neutral file + engine(!cgo)/csqlite(cgo) halves) so replicate, Base, and base-ha migrate off modernc.org/sqlite with zero code churn:

  • OpenPragma / Pragma / PragmaDSN — per-connection pragmas (only backend-neutral way to set wal_autocheckpoint=0; csqlite drops it from the DSN)
  • SetPersistWALSQLITE_FCNTL_PERSIST_WAL (WAL-shipping replication)
  • HookRegisterer / CommitHookFn / CommitHookRegistererCommitHookFn aliases the engine's under !cgo, so Base's raw-conn driverConn.(sqlite.HookRegisterer) assertion works with no bridge (verified)
  • Driver / ConnectionHookFn / ExecQuerierContext / RegisterConnectionHook — re-exports so litesql/go-sqlite-ha (base-ha) migrates without importing internal packages
  • IsConstraint{Unique,PrimaryKey,ForeignKey}

go list -m all still shows zero modernc.org/*. Both backends build; full suite green.

Concurrent-checkpoint regression — root-caused, evidence-backed

Added TestConcurrentCheckpointUnderLoad (10/10 reliable) as the verification bar.

Finding: replicate's flaky TestDB_CheckpointPageGapWithConcurrentWrites is a harness race, not a version regression.

  • The vendored engine (SQLite 3.53.2) blocks correctly on busy_timeout — a checkpoint waits out a 700ms-held writer lock then truncates (measured).
  • Clean serial pass rate: fork 9/12 == modernc v1.44.3 9/12 > v1.48.0 7/12. v1.44.3 does NOT deterministically fix it (still 9/12) and would downgrade the SQLite from 3.53.2.
  • The remaining flake is replicate's test writer having no busy_timeout (it hammers and starves the checkpoint) — a well-behaved writer (busy_timeout set) is reliable.

Recommend replicate set busy_timeout on its concurrent-writer connection (sqldb) to de-flake its own test; no engine change is warranted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant