v2.0.0: claims model - signed MMUR2 votes in a truth DB, merged view in a rebuildable cache DB; POST /api/claims, claims and merge routes, unsigned writes answer 410
Build and Push Docker Image / build (push) Successful in 5m1s

This commit is contained in:
Worph
2026-10-25 17:36:00 +00:00
parent b0adefb97b
commit 84af298792
78 changed files with 7203 additions and 5617 deletions
+267
View File
@@ -0,0 +1,267 @@
package main
// Decoding of the retired MMUR1 user-data record (meta-watch record/canonical.rs
// at meta-core v1.2.0) and the private-tier envelope (record/crypto.rs).
//
// record = canonical CBOR array(7) [cid, uid, key, value, version, ts, sig]
// bytes_to_sign = "MMUR1\n" || array(6) [cid, uid, key, value, version, ts]
// sig = DER ECDSA-secp256k1 over SHA-256(bytes_to_sign)
//
// value is CBOR null / bool / int / float / text / bytes (bytes = a private-tier
// XChaCha20-Poly1305 envelope whose plaintext is the CBOR of the typed value).
// The signed bytes are rebuilt from the record's own raw element bytes (array
// header 0x87 → 0x86, sig element cut off), so the old signature is checked
// against exactly what was signed, whatever float width the encoder chose.
import (
"encoding/binary"
"errors"
"fmt"
"math"
"strconv"
"golang.org/x/crypto/chacha20poly1305"
)
const mmur1Domain = "MMUR1\n"
type cborKind uint8
const (
cNull cborKind = iota
cBool
cInt
cFloat
cText
cBytes
)
// cborItem is one decoded scalar plus its raw encoding.
type cborItem struct {
kind cborKind
b bool
i int64
f float64
s string
bytes []byte
raw []byte
}
// wireText is the MMUR2 text form of a typed value — meta-watch's
// Value::to_wire_text: bools "true"/"false", ints in decimal, floats as Rust's
// f64 Display (shortest round-trip, never an exponent), text verbatim.
func (it cborItem) wireText() (string, bool) {
switch it.kind {
case cBool:
if it.b {
return "true", true
}
return "false", true
case cInt:
return strconv.FormatInt(it.i, 10), true
case cFloat:
return strconv.FormatFloat(it.f, 'f', -1, 64), true
case cText:
return it.s, true
}
return "", false
}
type mmur1Record struct {
CID, UID, Key string
Value cborItem
Version uint64
TS int64
Sig []byte
Signed []byte // the exact MMUR1 bytes the signature covers
}
var errCBOR = errors.New("malformed MMUR1 CBOR")
// readHead parses a CBOR initial byte + argument (definite lengths only).
func readHead(b []byte, off int) (major byte, ai byte, n uint64, next int, err error) {
if off >= len(b) {
return 0, 0, 0, 0, errCBOR
}
ib := b[off]
major, ai = ib>>5, ib&31
off++
var size int
switch {
case ai < 24:
return major, ai, uint64(ai), off, nil
case ai == 24:
size = 1
case ai == 25:
size = 2
case ai == 26:
size = 4
case ai == 27:
size = 8
default:
return 0, 0, 0, 0, fmt.Errorf("%w: indefinite or reserved length", errCBOR)
}
if off+size > len(b) {
return 0, 0, 0, 0, errCBOR
}
for _, x := range b[off : off+size] {
n = n<<8 | uint64(x)
}
return major, ai, n, off + size, nil
}
// readItem decodes one scalar item at off.
func readItem(b []byte, off int) (cborItem, int, error) {
start := off
major, ai, n, off, err := readHead(b, off)
if err != nil {
return cborItem{}, 0, err
}
var it cborItem
switch major {
case 0:
if n > math.MaxInt64 {
return it, 0, fmt.Errorf("%w: int out of range", errCBOR)
}
it = cborItem{kind: cInt, i: int64(n)}
case 1:
if n > math.MaxInt64 {
return it, 0, fmt.Errorf("%w: int out of range", errCBOR)
}
it = cborItem{kind: cInt, i: -1 - int64(n)}
case 2, 3:
if n > uint64(len(b)-off) {
return it, 0, errCBOR
}
end := off + int(n)
if major == 2 {
it = cborItem{kind: cBytes, bytes: append([]byte(nil), b[off:end]...)}
} else {
it = cborItem{kind: cText, s: string(b[off:end])}
}
off = end
case 7:
switch ai {
case 20, 21:
it = cborItem{kind: cBool, b: ai == 21}
case 22:
it = cborItem{kind: cNull}
case 25:
it = cborItem{kind: cFloat, f: halfToFloat(uint16(n))}
case 26:
it = cborItem{kind: cFloat, f: float64(math.Float32frombits(uint32(n)))}
case 27:
it = cborItem{kind: cFloat, f: math.Float64frombits(n)}
default:
return it, 0, fmt.Errorf("%w: unsupported simple value %d", errCBOR, ai)
}
default:
return it, 0, fmt.Errorf("%w: unsupported major type %d", errCBOR, major)
}
it.raw = b[start:off]
return it, off, nil
}
func halfToFloat(h uint16) float64 {
sign := 1.0
if h&0x8000 != 0 {
sign = -1
}
exp := int(h>>10) & 0x1f
frac := float64(h & 0x3ff)
switch exp {
case 0:
return sign * math.Ldexp(frac, -24)
case 31:
if frac == 0 {
return math.Inf(int(sign))
}
return math.NaN()
}
return sign * math.Ldexp(frac+1024, exp-25)
}
// decodeMMUR1 parses a stored record and rebuilds its signed bytes.
func decodeMMUR1(b []byte) (*mmur1Record, error) {
if len(b) == 0 || b[0] != 0x87 {
return nil, fmt.Errorf("%w: want array(7)", errCBOR)
}
off := 1
items := make([]cborItem, 7)
sigStart := 0
for i := range items {
if i == 6 {
sigStart = off
}
it, next, err := readItem(b, off)
if err != nil {
return nil, err
}
items[i], off = it, next
}
if off != len(b) {
return nil, fmt.Errorf("%w: trailing bytes", errCBOR)
}
for i, want := range []cborKind{cText, cText, cText} {
if items[i].kind != want {
return nil, fmt.Errorf("%w: cid/uid/key must be text", errCBOR)
}
}
if items[4].kind != cInt || items[4].i < 0 {
return nil, fmt.Errorf("%w: version must be an unsigned int", errCBOR)
}
if items[5].kind != cInt {
return nil, fmt.Errorf("%w: ts must be an int", errCBOR)
}
if items[6].kind != cBytes {
return nil, fmt.Errorf("%w: sig must be bytes", errCBOR)
}
signed := make([]byte, 0, len(mmur1Domain)+sigStart)
signed = append(signed, mmur1Domain...)
signed = append(signed, 0x86)
signed = append(signed, b[1:sigStart]...)
return &mmur1Record{
CID: items[0].s, UID: items[1].s, Key: items[2].s,
Value: items[3],
Version: uint64(items[4].i),
TS: items[5].i,
Sig: items[6].bytes,
Signed: signed,
}, nil
}
// envelopeAAD is cid ‖ 0x1F ‖ uid ‖ 0x1F ‖ key ‖ 0x1F ‖ n_be(8) — n was the
// MMUR1 version and is the MMUR2 ts.
func envelopeAAD(cid, uid, key string, n uint64) []byte {
b := make([]byte, 0, len(cid)+len(uid)+len(key)+11)
b = append(b, cid...)
b = append(b, 0x1f)
b = append(b, uid...)
b = append(b, 0x1f)
b = append(b, key...)
b = append(b, 0x1f)
return binary.BigEndian.AppendUint64(b, n)
}
// openEnvelope decrypts nonce(24) ‖ ciphertext+tag.
func openEnvelope(aeadKey, env, aad []byte) ([]byte, error) {
a, err := chacha20poly1305.NewX(aeadKey)
if err != nil {
return nil, err
}
if len(env) < chacha20poly1305.NonceSizeX+a.Overhead() {
return nil, errors.New("envelope too short")
}
return a.Open(nil, env[:chacha20poly1305.NonceSizeX], env[chacha20poly1305.NonceSizeX:], aad)
}
// sealEnvelope encrypts plain under a fresh random nonce.
func sealEnvelope(aeadKey, plain, aad []byte, nonce []byte) ([]byte, error) {
a, err := chacha20poly1305.NewX(aeadKey)
if err != nil {
return nil, err
}
out := make([]byte, 0, len(nonce)+len(plain)+a.Overhead())
out = append(out, nonce...)
return a.Seal(out, nonce, plain, aad), nil
}