Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
// Package codec implements the Deterministic CBOR rules of spec §58 and §58.1.
|
|
|
|
|
//
|
|
|
|
|
// Encoding uses RFC 8949 §4.2.1 Core Deterministic Encoding. Decoding is
|
|
|
|
|
// strict (no indefinite lengths, no tags, no duplicate keys, bounded depth and
|
|
|
|
|
// sizes, valid UTF-8, no unknown struct fields) and is always followed by a
|
|
|
|
|
// re-encoding that must reproduce the input byte for byte. Any difference is
|
|
|
|
|
// ErrNonCanonicalCBOR. The same principle as dk1_ canonicality (spec §19):
|
|
|
|
|
// canonicality does not depend on a library promising to reject every
|
|
|
|
|
// non-canonical form.
|
|
|
|
|
//
|
|
|
|
|
// The CBOR profile of the protocol (spec §58: major types 0, 2, 3, 4 and 5
|
|
|
|
|
// only, unsigned integer map keys) is enforced by decoding into the typed
|
|
|
|
|
// schemas of each package: their fields are unsigned integers, byte strings,
|
|
|
|
|
// text strings, arrays and maps, so negative integers, floats and simple
|
|
|
|
|
// values fail to decode, and null fails the re-encoding check.
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
package codec
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"github.com/fxamacker/cbor/v2"
|
|
|
|
|
|
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Decoding limits. Structural sizes are additionally bounded by the framing
|
|
|
|
|
// limits of spec §57 before any CBOR is decoded.
|
|
|
|
|
const (
|
|
|
|
|
MaxNestedLevels = 16
|
|
|
|
|
MaxArrayElements = 65536
|
|
|
|
|
MaxMapPairs = 65536
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// MaxSafeUint is 2^53-1, the largest unsigned integer any schema of the
|
|
|
|
|
// protocol allows, so that every integer is exact as an IEEE 754 double
|
|
|
|
|
// (spec §58).
|
|
|
|
|
const MaxSafeUint = 1<<53 - 1
|
|
|
|
|
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
var (
|
|
|
|
|
encMode = must(encOptions().EncMode())
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
decMode = must(decOptions(true).DecMode())
|
|
|
|
|
peekMode = must(decOptions(false).DecMode())
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// encOptions returns Core Deterministic Encoding options in which a nil byte
|
|
|
|
|
// string, array or map encodes as an empty one, never as null: null is outside
|
|
|
|
|
// the profile of spec §58, so an input null never survives the re-encoding
|
|
|
|
|
// check.
|
|
|
|
|
func encOptions() cbor.EncOptions {
|
|
|
|
|
o := cbor.CoreDetEncOptions()
|
|
|
|
|
o.NilContainers = cbor.NilContainerAsEmpty
|
|
|
|
|
return o
|
|
|
|
|
}
|
|
|
|
|
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
// decOptions returns the strict decoding options. Peek mode ignores unknown
|
|
|
|
|
// map keys; the canonical mode reports them.
|
|
|
|
|
func decOptions(strict bool) cbor.DecOptions {
|
|
|
|
|
o := cbor.DecOptions{
|
|
|
|
|
DupMapKey: cbor.DupMapKeyEnforcedAPF,
|
|
|
|
|
IndefLength: cbor.IndefLengthForbidden,
|
|
|
|
|
TagsMd: cbor.TagsForbidden,
|
|
|
|
|
MaxNestedLevels: MaxNestedLevels,
|
|
|
|
|
MaxArrayElements: MaxArrayElements,
|
|
|
|
|
MaxMapPairs: MaxMapPairs,
|
|
|
|
|
UTF8: cbor.UTF8RejectInvalid,
|
|
|
|
|
MapKeyByteString: cbor.MapKeyByteStringAllowed,
|
|
|
|
|
}
|
|
|
|
|
if strict {
|
|
|
|
|
o.ExtraReturnErrors = cbor.ExtraDecErrorUnknownField
|
|
|
|
|
}
|
|
|
|
|
return o
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// must accepts only the static options above, which cannot be invalid.
|
|
|
|
|
func must[T any](m T, err error) T {
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic("codec: invalid static options: " + err.Error())
|
|
|
|
|
}
|
|
|
|
|
return m
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Marshal returns the core deterministic CBOR encoding of v.
|
|
|
|
|
func Marshal(v any) ([]byte, error) {
|
|
|
|
|
b, err := encMode.Marshal(v)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("codec: encode: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return b, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unmarshal decodes exactly one CBOR data item from data into v, which must be
|
|
|
|
|
// a pointer, and then requires that re-encoding v reproduces data exactly.
|
|
|
|
|
// Every failure wraps datekeys.ErrNonCanonicalCBOR.
|
|
|
|
|
//
|
|
|
|
|
// Fields of type cbor.RawMessage are copied verbatim and are NOT covered by the
|
|
|
|
|
// re-encoding check; the caller must validate them.
|
|
|
|
|
//
|
|
|
|
|
// The re-encoding equals data on success, so it may hold secrets such as
|
|
|
|
|
// I_PAYLOAD or access_material; it is wiped on every path. This is best
|
|
|
|
|
// effort: the encoder's internal buffer may keep a copy.
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
func Unmarshal(data []byte, v any) error {
|
|
|
|
|
if err := decMode.Unmarshal(data, v); err != nil {
|
|
|
|
|
return fmt.Errorf("codec: decode: %v: %w", err, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
re, err := encMode.Marshal(v)
|
|
|
|
|
defer clear(re)
|
|
|
|
|
if err != nil || !bytes.Equal(re, data) {
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
return fmt.Errorf("codec: input is not the deterministic encoding of its value: %w", datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Peek decodes selected fields of a CBOR map, ignoring every other key and
|
|
|
|
|
// without the canonicality check. It exists only to read a type tag and a
|
|
|
|
|
// schema version before strict decoding, so that an unknown major version is
|
|
|
|
|
// reported as such (spec §70). Its result must never be used as the decoded
|
|
|
|
|
// object.
|
|
|
|
|
func Peek(data []byte, v any) error {
|
|
|
|
|
if err := peekMode.Unmarshal(data, v); err != nil {
|
|
|
|
|
return fmt.Errorf("codec: decode: %v: %w", err, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CheckSchema reads key 0 (type tag) and key 1 (schema version) of a CBOR map
|
|
|
|
|
// and requires the expected values. A different type tag is
|
|
|
|
|
// ErrNonCanonicalCBOR; a different version is ErrUnsupportedVersion.
|
|
|
|
|
func CheckSchema(data []byte, typeTag string, version uint64) error {
|
|
|
|
|
var h struct {
|
|
|
|
|
Type string `cbor:"0,keyasint"`
|
|
|
|
|
Version uint64 `cbor:"1,keyasint"`
|
|
|
|
|
}
|
|
|
|
|
if err := Peek(data, &h); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if h.Type != typeTag {
|
|
|
|
|
return fmt.Errorf("codec: type %q, want %q: %w", h.Type, typeTag, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
if h.Version != version {
|
|
|
|
|
return fmt.Errorf("codec: %s schema version %d, want %d: %w", typeTag, h.Version, version, datekeys.ErrUnsupportedVersion)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|