// Package identity defines a trust identity: an Ed25519 public key, its // canonical trust address, and signature verification over that key. // // A trust identity is self-sufficient. It is created by generating a key pair, // not by registering with anyone, and it carries no issuer, no certificate and // no server-assigned attributes. There is no account type: a person, a game // server, a daemon and a script are all just identities. // // Normative invariants enforced here: // // - INV-1: this package contains no signing capability whatsoever. It can // verify, never produce. The server links only this package, so a full // compromise of the server yields no ability to forge anything. // - INV-2: there is no distinguished "trust.n1ko.dev" identity. Nothing in // this package can mark one identity as more authoritative than another. // - INV-3: identity is the public key. There is no numeric or database // identifier anywhere in this type. // - INV-5: this package answers "who signed this", never "is this allowed". // There is deliberately no permission, role or capability concept. // - INV-6: identities have no relationships here. Nothing links one identity // to another, so no transitive trust can be derived at this layer. // - INV-7: an Alias is a display label. It is not part of Identity, does not // affect Equal, and never reaches any verification routine. // - INV-9: imports are limited to the standard library and the address // package. No storage, transport or application semantics. // // Private keys are handled exclusively by the client-only subpackage // identity/signer. Server-side code must not import it. package identity import ( "crypto/ed25519" "errors" "git.n1ko.dev/Niko/niko_trust/pkg/address" ) // ErrInvalidSignature is returned when a signature does not verify. var ErrInvalidSignature = errors.New("identity: invalid signature") // SignatureSize is the size of an Ed25519 signature in bytes. const SignatureSize = ed25519.SignatureSize // Identity is a validated public identity. // // The zero Identity is invalid. A non-zero Identity always holds a public key // that has passed curve validation, because the only ways to construct one go // through the address package. // // Identity is comparable and safe to use as a map key. type Identity struct { addr address.Address } // FromAddress builds an Identity from an already validated address. func FromAddress(a address.Address) (Identity, error) { if a.IsZero() { return Identity{}, address.ErrEmpty } return Identity{addr: a}, nil } // FromPubKey builds an Identity from an Ed25519 public key, validating the key // as a canonical prime-order curve point. func FromPubKey(pub ed25519.PublicKey) (Identity, error) { a, err := address.FromPubKey(pub) if err != nil { return Identity{}, err } return Identity{addr: a}, nil } // Parse builds an Identity from a textual trust address. func Parse(s string) (Identity, error) { a, err := address.Parse(s) if err != nil { return Identity{}, err } return Identity{addr: a}, nil } // MustParse is Parse for constants and fixtures. It panics on error. func MustParse(s string) Identity { id, err := Parse(s) if err != nil { panic("identity: MustParse: " + err.Error()) } return id } // Address returns the identity's trust address. func (i Identity) Address() address.Address { return i.addr } // String returns the canonical trust address text. func (i Identity) String() string { return i.addr.String() } // PubKey returns a copy of the identity's public key. func (i Identity) PubKey() ed25519.PublicKey { return i.addr.PubKey() } // IsZero reports whether i is the unset zero value. func (i Identity) IsZero() bool { return i.addr.IsZero() } // Equal reports whether two identities are the same key. // // Aliases are intentionally not considered (INV-7). func (i Identity) Equal(other Identity) bool { return i.addr.Equal(other.addr) } // Verify reports whether sig is a valid signature by this identity over msg. // // The message passed here must be the canonical encoding of a protocol object, // never a JSON document or any other ambiguous representation (INV-8). This // package cannot enforce that on its own; the protocol layer is responsible // for only ever calling Verify with canonical bytes. // // Verify returns a boolean rather than an error so that callers cannot // accidentally treat a non-nil error as "verified". Use VerifyErr when an // error value is more convenient. func (i Identity) Verify(msg, sig []byte) bool { if i.IsZero() { return false } if len(sig) != SignatureSize { return false } // The key was validated at construction, so ed25519.Verify cannot be fed // a small-order or non-canonical key through this path. return ed25519.Verify(i.addr.PubKey(), msg, sig) } // VerifyErr is Verify returning ErrInvalidSignature instead of false. func (i Identity) VerifyErr(msg, sig []byte) error { if !i.Verify(msg, sig) { return ErrInvalidSignature } return nil } // MarshalText implements encoding.TextMarshaler. func (i Identity) MarshalText() ([]byte, error) { return i.addr.MarshalText() } // UnmarshalText implements encoding.TextUnmarshaler, running full address // validation. func (i *Identity) UnmarshalText(b []byte) error { var a address.Address if err := a.UnmarshalText(b); err != nil { return err } i.addr = a return nil }