chain

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 12, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package chain implements the hash-chaining and Merkle tree primitives for the audit-chain plugin.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Canonicalize

func Canonicalize(data []byte) ([]byte, error)

Canonicalize returns the RFC 8785 (JSON Canonicalization Scheme) encoding of data: all object keys sorted lexicographically at every nesting level, whitespace removed, array element order preserved.

Numbers are decoded via json.Number (not float64) to prevent precision loss for large integers that cannot be represented exactly as float64 (e.g. integers > 2^53). The number bytes are re-emitted as-is by encoding/json.

The resulting bytes are deterministic: two JSON documents with the same logical value always produce identical output.

func EntryHash

func EntryHash(seq int64, ledger, eventType, payloadHash, prevEntryHash string) string

EntryHash computes the chain-integrity hash for an audit log entry.

Preimage: RFC 8785 canonical JSON of

{"event_type":<et>,"ledger":<l>,"payload_hash":<ph>,"prev_entry_hash":<prev>,"sequence":<seq>}

Keys are sorted lexicographically by encoding/json's map serialization. created_at, actor, and metadata are intentionally excluded from the preimage — they are stored for audit purposes but are not load-bearing for chain integrity. This matches the design doc: SHA256(sequence||ledger||event_type||payload_hash||prev_entry_hash).

func InclusionProof

func InclusionProof(leaves []string, idx int) ([]string, error)

InclusionProof returns the Merkle sibling path for the leaf at idx. Each element of the returned slice is a direction-prefixed hex-encoded node:

  • "L" + 64 hex chars: sibling is to the LEFT — combine as SHA256(0x01||sibling||current)
  • "R" + 64 hex chars: sibling is to the RIGHT — combine as SHA256(0x01||current||sibling)

The returned proof can be verified with VerifyInclusion.

func MerkleRoot

func MerkleRoot(leaves []string) (string, error)

MerkleRoot builds a binary Merkle tree over the given leaf strings and returns the hex-encoded root (64 lowercase hex chars).

Leaf hashing: SHA-256(0x00 || leaf_bytes). Node combination: SHA-256(0x01 || left || right). When a level has an odd count, the last node is paired with itself. Returns an error if leaves is empty.

func PayloadHash

func PayloadHash(data []byte) (string, error)

PayloadHash returns (SHA-256(Canonicalize(data)), nil) as a lowercase 64-char hex string. The input is canonicalized per RFC 8785 before hashing so that key order and whitespace in the caller's JSON do not affect the hash value. Returns an error if data is not valid JSON — callers (e.g. the append step) must validate the payload before appending rather than relying on a panic.

func VerifyInclusion

func VerifyInclusion(leaf string, proof []string, root string) bool

VerifyInclusion returns true if leaf is a member of the Merkle tree with the given root, as attested by proof (as produced by InclusionProof). The final root comparison uses crypto/subtle.ConstantTimeCompare.

Types

type Appender

type Appender struct {
	// contains filtered or unexported fields
}

Appender writes hash-chained audit entries to Postgres. It serialises concurrent appends to the same ledger via a SELECT … FOR UPDATE on the audit_ledgers row, which guarantees gap-free monotonic sequences without application-level locking.

func NewAppender

func NewAppender(db *sql.DB) *Appender

NewAppender returns an Appender backed by db.

func (*Appender) Append

func (a *Appender) Append(ctx context.Context, ledger, eventType string, payload, metadata []byte, actor string) (int64, string, time.Time, error)

Append opens its own transaction, appends one entry to ledger, and commits. metadata is stored as-is in audit_log.metadata (JSONB); pass nil if not needed. Returns (sequence, entryHash, dbCreatedAt, error). createdAt is the server-side timestamp assigned by the DB (via NOW()), not the application clock, so it is accurate regardless of NTP skew across nodes.

func (*Appender) AppendTx

func (a *Appender) AppendTx(ctx context.Context, tx *sql.Tx, ledger, eventType string, payload, metadata []byte, actor string) (int64, string, time.Time, error)

AppendTx appends one entry within the caller-supplied transaction tx. The caller is responsible for commit/rollback. This is the primitive used by BMW PR 11 Task 47 (step.bmw.audit_append_with_map) so that the audit entry and the business record land in a single atomic transaction. metadata is stored as-is in audit_log.metadata (JSONB); pass nil if not needed. Returns (sequence, entryHash, dbCreatedAt, error).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL