membridge

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 11, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

internal/membridge/capacity.go — PRD 4 §3.4 capacity enforcement.

EnforceHardCap fails closed at k>500 per §3.4.1. GenesisAssign samples pod_salt up to R_max=8 attempts seeking a pairwise-distinct index assignment for the initial thought set (§3.4.2). The caller provides a sampler closure so callers can inject CSPRNG, deterministic test fixtures, or a brute-force search. AssignCapture handles the intra-lineage capture path (§3.4.3): a derived-index collision fails closed without relocation.

internal/membridge/constants.go

internal/membridge/derive.go — PRD 4 §3.2 (B.1) byte-exact derivation.

Chain:

logical_key = DTAG_KEY || ULEB128(32) || pod_salt
            || ULEB128(len(tid_bytes)) || tid_bytes
index       = be_u32(SHA-256(IndexDomainSep || logical_key)[0:4]) & 0x0FFFFF
payload     = DTAG_CELL || ULEB128(len(logical_key)) || logical_key
            || content_hash   (32 bytes, no length prefix)
value_hash  = SHA-256(payload)
leaf_hash   = SHA-256(LEAF_TAG || value_hash)

internal/membridge/disclosure.go — PRD 4 §B.2 + §B.2.1 disclosure controls.

PolicyTypeC: bundle-disclosed salt; external re-derivation enabled. PolicyTypeD: salt committed via h_manifest but redacted from the

bundle. Audit trails are witness-confidential and MUST
NOT travel on the public channel.

Package membridge implements PRD 4 Part B byte-exactly: the bridge from the Phase-0 set-semantic MemorySnapshot model to the PRD 1 § 5.5 depth-20 sparse address-indexed ZK memory tree.

Public surface (B.1 derivation, B.5 import, B.6 lineage):

  • LogicalKey, CellIndex, CanonicalCellPayload, ValueHash, LeafHash
  • SparseRoot, BuildAuthPath, VerifyAuthPath
  • GenesisAssign, AssignCapture, EnforceHardCap
  • ImportSnapshot, AuditTrailEntry
  • Clone, Fork, Migrate, NewLineage
  • DisclosurePolicy, PublicBundleView

Every rejection is fatal and returns a *MembridgeError whose Code is a stable PRD 4 § 6 identifier (e.g. ERR_CELL_SALT_LEN). Callers branch on the code via errors.Is(err, ErrCellSaltLen).

internal/membridge/errors.go — stable sentinels matching PRD 4 §6.

Every named code in PRD 4 §6 has a sentinel here. The Code() helper extracts the string for golden-vector comparison; errors.Is matches even when the sentinel is wrapped with fmt.Errorf("...: %w", e).

internal/membridge/import.go — PRD 4 §B.5 snapshot import + audit trail.

Procedure (ordered):

  1. Enumerate MemorySnapshotLeaf records by leaf_index ASC.
  2. Derive (index, value_hash) per B.1 for each leaf.
  3. Assemble the depth-20 sparse Merkle tree.
  4. Read off the root → r_init.
  5. Verify r_init == manifest.r_init; mismatch → ErrRInitMismatch.
  6. Any structural failure → ErrSnapshotImportFailed.

internal/membridge/leb128.go — minimal unsigned LEB128 (PRD 4 §3.2.3).

EncodeThoughtID emits the minimal encoding for any uint64. The runtime owns the integer value at derivation time so the output is always valid.

ParseThoughtIDBytes consumes externally-supplied bytes (snapshots, conformance vectors, on-wire messages, audit trails) and rejects non-minimal encodings per the WI-6 normative separation. The encoder path is tautological; the parser path is the real validation surface.

internal/membridge/lineage.go — PRD 4 §B.6 lineage operations.

Salt-handling matrix:

Clone      → preserve salt (read-only copy)
Fork       → preserve salt for both branches
Migrate    → preserve salt (same lineage, new runtime)
NewLineage → reseed via genesis assign (R_max=8)

Mid-lineage reseed is prohibited in all non-NewLineage operations.

internal/membridge/provisional.go — PRD 4 §B.6.1 (WI-5) normative.

provisional_id = be_u64(SHA-256(

DTAG_PROV || content_hash || be_u64(task_id) || be_u32(ordinal)

)[0:8])

Reconciliation is byte-exact equality against the eventual persistent thought_id; mismatch fails closed.

internal/membridge/sparse_merkle.go — PRD 1 §5.5 depth-20 sparse tree.

Construction: bottom-up, only populated subtrees are computed explicitly; empty siblings reuse EmptyRoots[k]. Authentication path is leaf→root ordered with LSB-first direction (bit j of index at level j).

Fail-closed validation (Hermes review on MR !7): callers MUST supply indices < 2^D. Out-of-range indices return ErrSparseIndexOutOfRange. Masking belongs only at the normative CellIndex derivation boundary (PRD 4 §3.2.5 specifies `& 0x0FFFFF` happens at index derivation, not inside the tree). Two callers differing only in bits >= 20 would otherwise collide silently.

Index

Constants

View Source
const (
	DTAG_KEY  byte = 0x20 // logical-key domain separator
	DTAG_CELL byte = 0x21 // canonical-cell-payload domain separator
	DTAG_PROV byte = 0x22 // provisional-id domain separator (B.6.1)
	LEAF_TAG  byte = 0x10 // PRD 1 §5.5 leaf hashing tag
	NODE_TAG  byte = 0x11 // PRD 1 §5.5 internal-node hashing tag
)

Domain-tag constants. DTAG_KEY/DTAG_CELL/DTAG_PROV are Part-B only (PRD 4 §3.2.1 + §3.7.1). LEAF_TAG/NODE_TAG are PRD 1 §5.5 tree tags. All five are byte-disjoint from PRD 1 §5.4 T_log tags (0x00, 0x01, 0x03).

View Source
const D = 20

D is the fixed sparse Merkle tree depth per PRD 1 §5.5.

View Source
const HardCapK = 500

HardCapK is the v1 per-pod cell cap (PRD 4 §3.4.1). Fail-closed.

View Source
const RMax = 8

RMax is the genesis salt-resample bound (PRD 4 §3.4.2).

Variables

View Source
var (
	ErrCellSaltLen            = &MembridgeError{CodeStr: "ERR_CELL_SALT_LEN"}
	ErrLeb128NonMinimal       = &MembridgeError{CodeStr: "ERR_LEB128_NONMINIMAL"}
	ErrCellCapExceeded        = &MembridgeError{CodeStr: "ERR_CELL_CAP_EXCEEDED"}
	ErrGenesisReseedCollision = &MembridgeError{CodeStr: "ERR_GENESIS_RESEED_COLLISION"}
	ErrCellIndexCollision     = &MembridgeError{CodeStr: "ERR_CELL_INDEX_COLLISION"}
	ErrRInitMismatch          = &MembridgeError{CodeStr: "ERR_RINIT_MISMATCH"}
	ErrSnapshotImportFailed   = &MembridgeError{CodeStr: "ERR_SNAPSHOT_IMPORT_FAILED"}
	ErrProvisionalIDMismatch  = &MembridgeError{CodeStr: "ERR_PROVISIONAL_ID_MISMATCH"}
	ErrTypeDConfidentialLeak  = &MembridgeError{CodeStr: "ERR_TYPE_D_CONFIDENTIAL_LEAK"}
	// ErrSparseIndexOutOfRange is raised when a caller passes a tree
	// index >= 2^D into the sparse Merkle API. Masking belongs only at
	// the normative CellIndex derivation boundary; the tree itself is
	// fail-closed on out-of-range caller input (Hermes review on MR !7).
	ErrSparseIndexOutOfRange = &MembridgeError{CodeStr: "ERR_SPARSE_INDEX_OUT_OF_RANGE"}
)

Sentinel values. The string field equals the stable PRD 4 §6 code.

View Source
var EmptyRoots = func() [21][32]byte {
	var out [21][32]byte
	h := sha256.New()
	h.Write([]byte{LEAF_TAG})
	h.Write(EmptyValueHash[:])
	copy(out[0][:], h.Sum(nil))
	for k := 1; k <= D; k++ {
		h.Reset()
		h.Write([]byte{NODE_TAG})
		h.Write(out[k-1][:])
		h.Write(out[k-1][:])
		copy(out[k][:], h.Sum(nil))
	}
	return out
}()

EmptyRoots is the precomputed PRD 1 §5.5 empty-subtree root table. EmptyRoots[0] = SHA-256(LEAF_TAG || 0x00*32) EmptyRoots[k] = SHA-256(NODE_TAG || EmptyRoots[k-1] || EmptyRoots[k-1]) EmptyRoots[20] is the root of the all-empty depth-20 tree.

View Source
var EmptyValueHash = [32]byte{}

EmptyValueHash is the reserved sentinel for an unpopulated cell (PRD 1 §5.5). Distinct from SHA-256 of any well-formed payload.

View Source
var IndexDomainSep = []byte("konareef-mem-idx/v1")

IndexDomainSep is the literal 19 ASCII bytes prefixed to logical_key before SHA-256 to derive the cell index (PRD 4 §3.2.5). No null terminator, no length prefix — raw bytes only.

Functions

func AssignCapture

func AssignCapture(occupied map[uint32]uint64, podSalt []byte, thoughtID uint64) (uint32, error)

AssignCapture derives the index for thoughtID under the fixed lineage salt and rejects with ErrCellIndexCollision when the slot is occupied. On success, returns the index; the caller is responsible for inserting into `occupied`. occupied is not mutated on failure.

func BuildAuthPath

func BuildAuthPath(cells map[uint32][32]byte, index uint32) ([D][32]byte, error)

BuildAuthPath returns the 20 sibling hashes (leaf→root order) needed to authenticate the leaf at index against the root of cells. The returned slice always has length D; siblings at empty subtrees use the corresponding EmptyRoots[k]. Out-of-range index or leaf-map key returns ErrSparseIndexOutOfRange.

func CanonicalCellPayload

func CanonicalCellPayload(podSalt []byte, thoughtID uint64, contentHash []byte) ([]byte, error)

CanonicalCellPayload constructs the §3.2.6 payload. contentHash MUST be exactly 32 bytes.

func CellIndex

func CellIndex(podSalt []byte, thoughtID uint64) (uint32, error)

CellIndex computes the 20-bit Merkle address per §3.2.5.

func Clone

func Clone(src []byte) []byte

Clone returns a defensive copy of src — preserves the pod_salt bytes without aliasing the underlying buffer.

func Code

func Code(err error) string

Code extracts the stable error string. Returns "" if err is nil or not (a wrap of) a *MembridgeError.

func EncodeThoughtID

func EncodeThoughtID(n uint64) []byte

EncodeThoughtID emits the minimal unsigned LEB128 encoding of n.

func EnforceHardCap

func EnforceHardCap(k int) error

EnforceHardCap returns ErrCellCapExceeded when k > HardCapK.

func Fork

func Fork(src []byte) ([]byte, []byte)

Fork returns two independent copies of src — both branches preserve the original salt; each may evolve r_init independently.

func GenesisAssign

func GenesisAssign(sampler SaltSampler, thoughtIDs []uint64) ([]byte, int, error)

GenesisAssign performs the §3.4.2 reseed loop. Returns the accepted salt, the number of attempts taken (1-indexed for the success attempt), or ErrGenesisReseedCollision after RMax exhaustion.

Per §3.4.1 the input set size is also bounded; callers MUST gate k ≤ HardCapK before invoking GenesisAssign.

func LeafHash

func LeafHash(valueHash [32]byte) [32]byte

LeafHash returns SHA-256(LEAF_TAG || valueHash) per PRD 1 §5.5.

func LogicalKey

func LogicalKey(podSalt []byte, thoughtID uint64) ([]byte, error)

LogicalKey constructs the canonical logical-key byte string for (podSalt, thoughtID). podSalt MUST be exactly 32 bytes; otherwise ErrCellSaltLen is returned and no other field is computed.

func Migrate

func Migrate(src []byte) []byte

Migrate returns a defensive copy of src — same lineage, new runtime host; salt invariant.

func NewLineage

func NewLineage(sampler SaltSampler, initialThoughtIDs []uint64) ([]byte, int, error)

NewLineage runs §3.4.2 genesis assign to produce a fresh salt for a deliberately-new lineage. Use the runtime CSPRNG-backed sampler in production; tests inject deterministic sequences.

func ParseThoughtIDBytes

func ParseThoughtIDBytes(b []byte) (uint64, error)

ParseThoughtIDBytes decodes b as standard minimal unsigned LEB128 per PRD 4 §3.2.3.

Minimality rule (post-Hermes-review correction): the parser performs a standard LSB-first ULEB128 decode and then re-encodes the decoded value via EncodeThoughtID. Input bytes are minimal iff they equal the canonical encoding. This is the textbook ULEB128 minimality check.

This rule correctly accepts encodings such as 0x80 0x80 0x01 (canonical minimal encoding of 16384, well within the v1 thought_id range of 2^21-1 = 2097151) and correctly rejects encodings such as 0x80 0x00 (non-minimal 0; canonical minimal encoding of 0 is 0x00).

The earlier interior-0x80 rejection rule was incorrect: it would fail-closed on legitimate 16384 thought_ids. See paygate-zk issue #1 for the upstream conformance-vector text mismatch.

Rejects with ErrLeb128NonMinimal when:

  • b is empty,
  • b is truncated (final byte still has the continuation bit set),
  • trailing bytes follow the terminator,
  • the decoded value's canonical encoding differs from the input,
  • decode would consume more than 10 bytes (64-bit overflow guard).

func ProvisionalID

func ProvisionalID(contentHash []byte, taskID uint64, ordinal uint32) (uint64, error)

ProvisionalID computes the deterministic capture-time provisional thought_id per §B.6.1. contentHash MUST be exactly 32 bytes.

func ReconcileProvisional

func ReconcileProvisional(provisional, persistent uint64) error

ReconcileProvisional asserts the persistent thought_id matches the provisional id byte-for-byte. Mismatch is the §B.6.1 fail-closed gate.

func SparseRoot

func SparseRoot(cells map[uint32][32]byte) ([32]byte, error)

SparseRoot computes the depth-20 sparse Merkle root for the set of (index, value_hash) pairs in cells. nil or empty cells returns EmptyRoots[20]. Any index >= 2^D is rejected fail-closed with ErrSparseIndexOutOfRange — callers MUST mask at the CellIndex derivation boundary, not rely on the tree to mask.

func ValueHash

func ValueHash(podSalt []byte, thoughtID uint64, contentHash []byte) ([32]byte, error)

ValueHash returns SHA-256(canonical_cell_payload).

func VerifyAuthPath

func VerifyAuthPath(index uint32, leafHash [32]byte, path [D][32]byte) [32]byte

VerifyAuthPath recomputes the root by walking leafHash up to the root using LSB-first direction bits from index. Indices >= 2^D produce undefined output; callers MUST validate range upstream (the normative CellIndex boundary).

Types

type AuditTrailEntry

type AuditTrailEntry struct {
	ThoughtID   uint64
	LogicalKey  []byte
	Index       uint32
	ValueHash   [32]byte
	ContentHash []byte
}

AuditTrailEntry is one row of the §B.5 ordered audit record. Salt is the runtime's responsibility to keep alongside (Type-C: bundled; Type-D: out-of-band confidential).

func ImportSnapshot

func ImportSnapshot(podSalt []byte, leaves []MemorySnapshotLeaf, manifestRInit []byte) ([32]byte, []AuditTrailEntry, error)

ImportSnapshot performs §B.5 steps 1-5.

Returns the computed r_init and the audit trail in leaf_index ASC order. On r_init mismatch the trail is still returned so callers can diagnose; the error fully unwraps to ErrRInitMismatch.

type DisclosurePolicy

type DisclosurePolicy uint8

DisclosurePolicy is the bundle-side confidentiality posture.

const (
	PolicyUnspecified DisclosurePolicy = 0
	PolicyTypeC       DisclosurePolicy = 1
	PolicyTypeD       DisclosurePolicy = 2
)

type MembridgeError

type MembridgeError struct {
	CodeStr string
}

MembridgeError is the concrete sentinel type. Each exported Err* variable is a *MembridgeError, so wrapped errors compare equal to the sentinel via errors.Is (the default *T pointer-equality unwrap path).

func (*MembridgeError) Code

func (e *MembridgeError) Code() string

Code returns the stable PRD 4 §6 identifier string for the sentinel. Equivalent to e.CodeStr; provided so callers can read .Code on *MembridgeError values surfaced through errors.As without naming the field directly.

func (*MembridgeError) Error

func (e *MembridgeError) Error() string

type MemorySnapshotLeaf

type MemorySnapshotLeaf struct {
	LeafIndex   uint64 // PRD 4 §B.5 step-1 sort key
	ThoughtID   uint64
	ContentHash []byte // 32 bytes
}

MemorySnapshotLeaf is one row from the Phase-0 MemorySnapshot.

type PublicBundleView

type PublicBundleView struct {
	Policy     DisclosurePolicy
	PodSalt    []byte
	RInit      [32]byte
	AuditTrail []AuditTrailEntry // §B.5 — Type-D MUST be empty here.

}

PublicBundleView is the raw input to the public-bundle serializer. Serialize emits a confidentiality-aware SerializedView; passing a Type-D bundle with any confidential field populated (audit trail, per-cell logical_key, per-cell index) returns ErrTypeDConfidentialLeak.

func (PublicBundleView) Serialize

func (v PublicBundleView) Serialize() (SerializedView, error)

Serialize applies PRD 4 §B.2.1 redaction rules.

type SaltSampler

type SaltSampler func(attempt int) ([]byte, error)

SaltSampler is the runtime hook that produces a candidate pod_salt. attempt is zero-based (first call: 0). Returning a salt of length != 32 causes GenesisAssign to surface ErrCellSaltLen.

type SerializedView

type SerializedView struct {
	PolicyStr  string `json:"policy"`
	PodSaltHex string `json:"pod_salt,omitempty"`
	RInitHex   string `json:"r_init"`
}

SerializedView is the post-redaction view safe to publish.

func (SerializedView) MarshalJSON

func (s SerializedView) MarshalJSON() ([]byte, error)

MarshalJSON emits the public bundle JSON. Wrapper exists so tests can scan for leaked bytes in the canonical serialized form.

Jump to

Keyboard shortcuts

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