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

118 lines
3.1 KiB
Go

// Copyright (C) 2020-2026, Hanzo AI Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package vault
import (
"encoding/json"
"fmt"
"time"
)
// Bundle is a portable vault export: JSON envelope with encrypted data + oplog + metadata.
type Bundle struct {
Version int `json:"version"`
UserID string `json:"userId"`
OrgID string `json:"orgId"`
Timestamp int64 `json:"timestamp"`
Snapshot json.RawMessage `json:"snapshot"` // encrypted store entries
Oplog []Op `json:"oplog"`
Metadata BundleMetadata `json:"metadata"`
}
// BundleMetadata holds non-sensitive metadata about the bundle.
type BundleMetadata struct {
KeyCount int `json:"keyCount"`
OpCount int `json:"opCount"`
Format string `json:"format"` // "vault-bundle-v3"
}
// ExportVault exports the session's encrypted state as a portable JSON bundle.
// The bundle contains ciphertext only — the DEK is NOT included.
// The caller must supply the DEK separately to import.
func ExportVault(session *Session) ([]byte, error) {
session.mu.RLock()
defer session.mu.RUnlock()
if session.store == nil {
return nil, fmt.Errorf("vault: session is closed")
}
// Serialize the encrypted store as a map of key → hex-encoded ciphertext.
snapshot := make(map[string][]byte, len(session.store))
for k, v := range session.store {
snapshot[k] = v
}
snapshotJSON, err := json.Marshal(snapshot)
if err != nil {
return nil, fmt.Errorf("vault: marshal snapshot: %w", err)
}
oplog := make([]Op, len(session.oplog))
copy(oplog, session.oplog)
bundle := Bundle{
Version: 3,
UserID: session.userID,
OrgID: session.orgID,
Timestamp: time.Now().Unix(),
Snapshot: snapshotJSON,
Oplog: oplog,
Metadata: BundleMetadata{
KeyCount: len(snapshot),
OpCount: len(oplog),
Format: "vault-bundle-v3",
},
}
data, err := json.Marshal(bundle)
if err != nil {
return nil, fmt.Errorf("vault: marshal bundle: %w", err)
}
return data, nil
}
// ImportVault imports a vault from a portable bundle.
// The caller must provide the correct DEK to decrypt values.
func ImportVault(data []byte, dek []byte) (*Session, error) {
if len(dek) != 32 {
return nil, fmt.Errorf("vault: DEK must be 32 bytes")
}
var bundle Bundle
if err := json.Unmarshal(data, &bundle); err != nil {
return nil, fmt.Errorf("vault: unmarshal bundle: %w", err)
}
if bundle.Version != 3 {
return nil, fmt.Errorf("vault: unsupported bundle version %d", bundle.Version)
}
// Deserialize the encrypted store.
var snapshot map[string][]byte
if err := json.Unmarshal(bundle.Snapshot, &snapshot); err != nil {
return nil, fmt.Errorf("vault: unmarshal snapshot: %w", err)
}
// Rebuild version map from oplog.
version := make(map[string]uint64)
for _, op := range bundle.Oplog {
if op.Seq > version[op.NodeID] {
version[op.NodeID] = op.Seq
}
}
session := &Session{
userID: bundle.UserID,
orgID: bundle.OrgID,
dek: make([]byte, 32),
store: snapshot,
oplog: bundle.Oplog,
version: version,
}
copy(session.dek, dek)
return session, nil
}