A tamper-evident, hash-chained log; altering, reordering, or dropping a record breaks verification. One capability of pamoja, one memory-safe Rust core with bindings for TypeScript, Python, and C#.
npm install @pamoja/audit
This pulls in @pamoja/native, the compiled engine, and @pamoja/security. npm install pamoja is the whole framework in one package.
The test that runs in CI, spliced here as it ran.
From bindings/node/guides/audit.ts:
import { AuditEntry, AuditLog, verifyChain } from '@pamoja/audit'
import { DeviceIdentity } from '@pamoja/security'
// The controller signs its own log with a provisioned seed and an auditor holds only the
// public half, so a log can be checked anywhere without the device present.
const keeper = DeviceIdentity.fromSeed(Buffer.alloc(32, 7))
const auditor = keeper.publicKey()
const log = new AuditLog(keeper)
const lit = log.append(Buffer.from('burner=on'))
const stopped = log.append(Buffer.from('burner=off'))
console.log(`recorded ${lit.index} then ${stopped.index}`)
// Each record hashes its own index, the digest of the record before it, and what it
// carries, so the chain fixes the order as well as the contents.
console.log(`chained ${stopped.previous.equals(lit.digest)}`)
verifyChain(auditor, [lit, stopped])
console.log('verified the whole log is authentic and in order')
// Editing a stored record changes the digest its signature covers.
const edited = Buffer.from(stopped.toBytes())
edited[edited.length - 1] ^= 0xff
const tampered = AuditEntry.fromBytes(edited)
try {
verifyChain(auditor, [lit, tampered])
console.log('an edited record verified, which should never happen')
} catch (error) {
console.log(`edited caught: ${(error as Error).message}`)
}
// Dropping the first record leaves the survivor chained to a link that is no longer there,
// so a shortened log is caught as readily as an edited one.
try {
verifyChain(auditor, [stopped])
console.log('a shortened log verified, which should never happen')
} catch (error) {
console.log(`shortened caught: ${(error as Error).message}`)
}
| Language | Package | Reference |
|---|---|---|
| Rust | pamoja-audit |
reference, docs.rs, install |
| TypeScript | @pamoja/audit |
reference, install |
| Python | pamoja-audit |
reference, install |
| C# | Pamoja.Audit |
reference, install |
@pamoja/audit reference, every class, function, and type this package exports.MIT
Ergonomic facade over the generated audit binding.
A log that can be edited after the fact proves nothing. Each record here is signed and carries the hash of the one before it, so altering a record, dropping one, or reordering two breaks the chain at that point and at every point after it. The index is part of what is signed, which is what makes a record removed from the end detectable too.