Files
zeekay f11070fcf8 feat: vault v2+v3+v4 — complete Web5 data SDK, 50 tests
v2 — Sharing & Recovery:
- Shared vaults (org/team) with member access control
- Multi-device enrollment with per-device wrapping keys
- Per-collection sharing (recipient decrypts only shared collection)
- Threshold recovery (XOR split, Shamir in v3)

v3 — Provider Marketplace:
- SyncProvider, StorageProvider, RecoveryProvider interfaces
- Local providers (filesystem, content-addressed, share storage)
- Export/Import portable vault bundles (encrypted snapshot + oplog)
- Usage metering (puts, gets, syncs, anchors per vault)

v4 — Confidential Compute:
- ConfidentialEngine interface (opt-in FHE/ZK, NOT default)
- Local engine (decrypt-compute-prove baseline)
- PolicyEngine with capability-based access control (grant/check/revoke/expiry)
- Append-only audit log with hash-chained entries + merkle root

50 tests total. Zero failures. Zero external dependencies.
Multi-instance HA: each instance runs its own SQLite shard, CRDT
sync handles consistency. Encryption at rest via DEK/KEK hierarchy.
2026-03-02 13:14:19 -07:00

135 lines
3.4 KiB
Go

// Copyright (C) 2020-2026, Hanzo AI Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package vault
import (
"crypto/sha256"
"encoding/hex"
"sync"
"time"
)
// AuditEntry records a single vault operation.
type AuditEntry struct {
Timestamp time.Time `json:"timestamp"`
VaultID string `json:"vaultId"` // org:user
Actor string `json:"actor"` // DID of who performed the action
Action string `json:"action"` // "put", "get", "delete", "sync", "anchor", "grant", "revoke"
Resource string `json:"resource"` // key, capability ID, etc.
Hash string `json:"hash"` // SHA-256 of entry for chaining
PrevHash string `json:"prevHash"` // hash of previous entry (chain link)
}
// AuditLog is an append-only log of vault operations.
// Entries are hash-chained: each entry's PrevHash points to the previous entry's Hash.
// The last hash is the audit merkle root, suitable for chain anchoring.
type AuditLog struct {
entries []AuditEntry
mu sync.RWMutex
}
// NewAuditLog creates an empty audit log.
func NewAuditLog() *AuditLog {
return &AuditLog{}
}
// Record appends an entry to the audit log.
// The entry is hash-chained to the previous entry automatically.
func (al *AuditLog) Record(vaultID, actor, action, resource string) {
al.mu.Lock()
defer al.mu.Unlock()
prevHash := ""
if len(al.entries) > 0 {
prevHash = al.entries[len(al.entries)-1].Hash
}
entry := AuditEntry{
Timestamp: time.Now(),
VaultID: vaultID,
Actor: actor,
Action: action,
Resource: resource,
PrevHash: prevHash,
}
h := sha256.New()
h.Write([]byte(entry.VaultID))
h.Write([]byte(entry.Actor))
h.Write([]byte(entry.Action))
h.Write([]byte(entry.Resource))
h.Write([]byte(entry.PrevHash))
entry.Hash = hex.EncodeToString(h.Sum(nil))
al.entries = append(al.entries, entry)
}
// GetAuditLog returns entries for a vaultID since a given time.
// Pass time.Time{} (zero) to get all entries.
func (al *AuditLog) GetAuditLog(vaultID string, since time.Time) []AuditEntry {
al.mu.RLock()
defer al.mu.RUnlock()
var result []AuditEntry
for _, e := range al.entries {
if e.VaultID != vaultID {
continue
}
if !since.IsZero() && e.Timestamp.Before(since) {
continue
}
result = append(result, e)
}
return result
}
// MerkleRoot returns the hash of the last entry, which is the root of the
// hash chain. This is the value anchored to chain.
// Returns empty string if the log is empty.
func (al *AuditLog) MerkleRoot() string {
al.mu.RLock()
defer al.mu.RUnlock()
if len(al.entries) == 0 {
return ""
}
return al.entries[len(al.entries)-1].Hash
}
// Len returns the number of entries in the audit log.
func (al *AuditLog) Len() int {
al.mu.RLock()
defer al.mu.RUnlock()
return len(al.entries)
}
// Verify checks that the hash chain is consistent.
// Returns true if every entry's PrevHash matches the previous entry's Hash.
func (al *AuditLog) Verify() bool {
al.mu.RLock()
defer al.mu.RUnlock()
for i, e := range al.entries {
// Recompute hash
h := sha256.New()
h.Write([]byte(e.VaultID))
h.Write([]byte(e.Actor))
h.Write([]byte(e.Action))
h.Write([]byte(e.Resource))
h.Write([]byte(e.PrevHash))
computed := hex.EncodeToString(h.Sum(nil))
if computed != e.Hash {
return false
}
if i > 0 && e.PrevHash != al.entries[i-1].Hash {
return false
}
if i == 0 && e.PrevHash != "" {
return false
}
}
return true
}