package ledger import ( "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "os" "time" ) type Entry struct { Timestamp int64 `json:"timestamp"` Type string `json:"type"` Payload json.RawMessage `json:"payload"` PrevHash string `json:"prev_hash"` Hash string `json:"hash"` } type Ledger struct { path string file *os.File last string } func sha256Hex(b []byte) string { h := sha256.Sum256(b) return hex.EncodeToString(h[:]) } func Open(path string) (*Ledger, error) { f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600) if err != nil { return nil, err } l := &Ledger{path: path, file: f} if err := l.loadLastHash(); err != nil { return nil, err } return l, nil } func (l *Ledger) loadLastHash() error { // read file line by line, keep last hash f, err := os.Open(l.path) if err != nil { return err } defer f.Close() dec := json.NewDecoder(f) var last string for { var e Entry if err := dec.Decode(&e); err != nil { break } last = e.Hash } l.last = last return nil } func (l *Ledger) Append(eventType string, payload interface{}) (string, error) { pbytes, err := json.Marshal(payload) if err != nil { return "", err } e := Entry{ Timestamp: time.Now().UnixNano(), Type: eventType, Payload: json.RawMessage(pbytes), PrevHash: l.last, } // compute hash hb, _ := json.Marshal(struct { Timestamp int64 `json:"timestamp"` Type string `json:"type"` Payload json.RawMessage `json:"payload"` PrevHash string `json:"prev_hash"` }{e.Timestamp, e.Type, e.Payload, e.PrevHash}) e.Hash = sha256Hex(hb) enc, err := json.Marshal(e) if err != nil { return "", err } if _, err := l.file.Write(append(enc, '\n')); err != nil { return "", err } l.last = e.Hash return e.Hash, nil } func (l *Ledger) Verify() error { f, err := os.Open(l.path) if err != nil { return err } defer f.Close() dec := json.NewDecoder(f) var prev string for { var e Entry if err := dec.Decode(&e); err != nil { break } if e.PrevHash != prev { return fmt.Errorf("broken chain: prev %s != entry.PrevHash %s", prev, e.PrevHash) } // recompute hb, _ := json.Marshal(struct { Timestamp int64 `json:"timestamp"` Type string `json:"type"` Payload json.RawMessage `json:"payload"` PrevHash string `json:"prev_hash"` }{e.Timestamp, e.Type, e.Payload, e.PrevHash}) if sha256Hex(hb) != e.Hash { return fmt.Errorf("hash mismatch for entry at %d", e.Timestamp) } prev = e.Hash } return nil } func (l *Ledger) Close() error { return l.file.Close() }