- 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
94 lines
2.4 KiB
Go
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
|
|
}
|