refactor(quartz): FsLayout.sha256Hex routes through Quartz utils

Replace the inline MessageDigest + hand-rolled hex converter with
quartz.utils.sha256.sha256 + quartz.utils.Hex.encode. Same output
(deterministic algorithm), but we stop duplicating primitives that
already exist in the codebase. One fewer JCA dependency, one fewer
hex-conversion implementation to audit.

All 113 fs tests still green, including FsParityTest vs the SQLite
reference — the d-tag slot paths are byte-identical to before.
This commit is contained in:
Claude
2026-04-25 02:20:30 +00:00
parent 22a418bea2
commit fd9f70536f
@@ -22,11 +22,12 @@ package com.vitorpamplona.quartz.nip01Core.store.fs
import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Kind import com.vitorpamplona.quartz.nip01Core.core.Kind
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.sha256.sha256
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
import java.security.MessageDigest
import java.security.SecureRandom import java.security.SecureRandom
import kotlin.io.path.exists import kotlin.io.path.exists
@@ -195,20 +196,15 @@ internal class FsLayout(
const val SEED_FILE = ".seed" const val SEED_FILE = ".seed"
const val JSON_EXT = ".json" const val JSON_EXT = ".json"
/** Lowercase hex SHA-256 of the given UTF-8 string. Used for d-tag slots. */ /**
fun sha256Hex(s: String): String { * Lowercase hex SHA-256 of the given UTF-8 string. Used for
val md = MessageDigest.getInstance("SHA-256") * addressable d-tag slots and d-tag-scoped tombstones — needs
val bytes = md.digest(s.encodeToByteArray()) * cryptographic collision resistance because the filename IS the
val sb = StringBuilder(bytes.size * 2) * NIP-01 `UNIQUE(kind, pubkey, d)` constraint. Reuses the shared
for (b in bytes) { * Quartz primitives ([sha256], [Hex.encode]) so behaviour matches
val v = b.toInt() and 0xff * the rest of the codebase on every KMP target.
sb.append(HEX[v ushr 4]) */
sb.append(HEX[v and 0x0f]) fun sha256Hex(s: String): String = Hex.encode(sha256(s.encodeToByteArray()))
}
return sb.toString()
}
private val HEX = "0123456789abcdef".toCharArray()
/** zero-padded to 10 digits so lex order == chronological order through year 2286. */ /** zero-padded to 10 digits so lex order == chronological order through year 2286. */
fun tsPad(ts: Long): String = ts.toString().padStart(TS_PAD_WIDTH, '0') fun tsPad(ts: Long): String = ts.toString().padStart(TS_PAD_WIDTH, '0')