niko_trust/pkg/tce/id.go
Niko Marmeladkov 3bf13fa488 Public SDK packages, proxy-aware rate limits, service login recipe
- internal/{address,identity,protocol,tce,transport,verify} -> pkg/ so
  external Go projects can import the verified core; invariant tests
  updated for the new paths
- Config.TrustProxy: key rate limiting by X-Forwarded-For when the relay
  sits behind a reverse proxy (off by default, header never trusted
  otherwise)
- examples/service + examples/approve: complete passwordless login round
  trip (mint request -> wallet approves -> local verify), run live in CI
- docs/SERVICE-GUIDE.md: the integration recipe
2026-08-26 12:49:54 +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
}