niko_trust/internal/tce/id.go
Niko Marmeladkov 9d66003689
Initial commit: signed-object trust relay, verifier, and docs
- server: relay storing signed objects (PUT/GET), per-IP rate limiting,
  per-subject quota (1000), one-response-per-request, pagination,
  /v1/healthz /v1/readyz /v1/metrics
- verify: signature-verifying trust evaluator; every object is checked via
  env.Verify(), approvals via VerifyApprovalResponse, revocations via
  VerifyRevocationOf; k-of-n approval quorum
- docs: TRUST-MODEL.md and API.md describing issuer-anchored signatures and
  the endpoint/status-code contract
- tests: server, verify, and ratelimit packages
2026-08-12 22:36:49 +03:00

94 lines
2.4 KiB
Go

package tce
import (
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"errors"
)
// ID is the content address of a TCE object: SHA-256 over its canonical
// bytes.
//
// ID is a distinct type rather than a []byte so that a hash cannot be passed
// where a message is expected. Nothing in the tree signs or verifies an ID:
// the signature covers the TCE bytes, and the ID exists only to name and
// reference the object (see docs/IMPLEMENTATION_NOTES.md property 1).
type ID [HashSize]byte
// ErrBadID is returned when a hex string is not a valid object ID.
var ErrBadID = errors.New("tce: malformed object id")
// ComputeID returns the content address of the given canonical bytes.
func ComputeID(tceBytes []byte) ID {
return ID(sha256.Sum256(tceBytes))
}
// ParseID decodes a lowercase hex object ID.
func ParseID(s string) (ID, error) {
var id ID
if len(s) != HashSize*2 {
return id, ErrBadID
}
// Reject uppercase so that one ID has one textual form.
for i := 0; i < len(s); i++ {
c := s[i]
if !(c >= '0' && c <= '9' || c >= 'a' && c <= 'f') {
return id, ErrBadID
}
}
b, err := hex.DecodeString(s)
if err != nil {
return id, ErrBadID
}
copy(id[:], b)
return id, nil
}
// IDFromBytes builds an ID from exactly 32 bytes.
func IDFromBytes(b []byte) (ID, error) {
var id ID
if len(b) != HashSize {
return id, ErrBadID
}
copy(id[:], b)
return id, nil
}
// String renders the ID as lowercase hex.
func (id ID) String() string { return hex.EncodeToString(id[:]) }
// Bytes returns a copy of the raw hash.
func (id ID) Bytes() []byte {
out := make([]byte, HashSize)
copy(out, id[:])
return out
}
// Equal compares two IDs in constant time.
//
// The comparison is not obviously timing-sensitive, but the analysis needed to
// prove any individual case safe is not worth repeating, and the cost is
// negligible.
func (id ID) Equal(other ID) bool {
return subtle.ConstantTimeCompare(id[:], other[:]) == 1
}
// IsZero reports whether the ID is unset.
func (id ID) IsZero() bool {
var zero ID
return subtle.ConstantTimeCompare(id[:], zero[:]) == 1
}
// MarshalText implements encoding.TextMarshaler.
func (id ID) MarshalText() ([]byte, error) { return []byte(id.String()), nil }
// UnmarshalText implements encoding.TextUnmarshaler.
func (id *ID) UnmarshalText(b []byte) error {
parsed, err := ParseID(string(b))
if err != nil {
return err
}
*id = parsed
return nil
}