- 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
125 lines
4.5 KiB
Go
125 lines
4.5 KiB
Go
package address
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ed25519"
|
|
"errors"
|
|
|
|
"filippo.io/edwards25519"
|
|
)
|
|
|
|
// Public key validation.
|
|
//
|
|
// Go's crypto/ed25519.Verify follows RFC 8032 and deliberately performs no
|
|
// checks on the public key beyond its length. That is correct for RFC 8032,
|
|
// but it is not sufficient for a protocol in which an attacker chooses the
|
|
// public key, because several classes of key make signatures meaningless:
|
|
//
|
|
// - The all-zero key encodes a point of order 4. The all-zero signature
|
|
// verifies against that key for *every* message. An attacker who
|
|
// registered such a key would own an identity whose signature verifies on
|
|
// any claim or approval anyone cares to construct, which is a direct
|
|
// break of INV-1.
|
|
//
|
|
// - The identity element (y = 1) behaves the same way.
|
|
//
|
|
// - Small-order and mixed-order points admit signatures that verify under
|
|
// more than one public key, destroying the "who said this" property that
|
|
// the entire protocol rests on.
|
|
//
|
|
// - Non-canonical encodings (field elements >= p) give two distinct byte
|
|
// strings that denote the same curve point, hence two distinct addresses
|
|
// for one identity, which violates INV-8.
|
|
//
|
|
// The protocol therefore requires every public key entering the system to be a
|
|
// canonically encoded point in the prime-order subgroup. The check is
|
|
// performed once, at address construction, so that every Address in the
|
|
// program has already been validated and no later code has to remember to do
|
|
// it.
|
|
var (
|
|
// ErrKeySize is returned when a key is not 32 bytes.
|
|
ErrKeySize = errors.New("address: public key must be 32 bytes")
|
|
|
|
// ErrKeyNotOnCurve is returned when the key does not decode to a valid
|
|
// Edwards25519 point.
|
|
ErrKeyNotOnCurve = errors.New("address: public key is not a valid curve point")
|
|
|
|
// ErrKeyNonCanonical is returned when the key is a valid point encoded in
|
|
// a non-canonical way (a field element that is not fully reduced).
|
|
ErrKeyNonCanonical = errors.New("address: public key encoding is non-canonical")
|
|
|
|
// ErrKeySmallOrder is returned when the key lies in the small-order
|
|
// torsion subgroup, for which signatures are forgeable or ambiguous.
|
|
ErrKeySmallOrder = errors.New("address: public key has small order")
|
|
)
|
|
|
|
// ValidatePubKey reports whether pub is usable as a trust identity key.
|
|
//
|
|
// It returns nil only for a 32-byte, canonically encoded Edwards25519 point
|
|
// that is not annihilated by multiplication by the cofactor. Honest keys
|
|
// produced by ed25519.GenerateKey always satisfy this.
|
|
func ValidatePubKey(pub []byte) error {
|
|
if len(pub) != ed25519.PublicKeySize {
|
|
return ErrKeySize
|
|
}
|
|
|
|
p, err := new(edwards25519.Point).SetBytes(pub)
|
|
if err != nil {
|
|
// SetBytes rejects non-canonical field encodings and points that are
|
|
// not on the curve. Both are fatal, but they are distinguishable by
|
|
// re-encoding below only for the on-curve case, so report the generic
|
|
// curve error unless we can say something more precise.
|
|
if isNonCanonicalEncoding(pub) {
|
|
return ErrKeyNonCanonical
|
|
}
|
|
return ErrKeyNotOnCurve
|
|
}
|
|
|
|
// A canonical point re-encodes to exactly the bytes it came from. Any
|
|
// difference means the input was an alternative spelling of this point.
|
|
if !bytes.Equal(p.Bytes(), pub) {
|
|
return ErrKeyNonCanonical
|
|
}
|
|
|
|
// Multiplying by the cofactor (8) maps every small-order point to the
|
|
// identity. A key in the prime-order subgroup never maps to the identity,
|
|
// because the group has prime order and the key is not itself the
|
|
// identity.
|
|
if new(edwards25519.Point).MultByCofactor(p).Equal(edwards25519.NewIdentityPoint()) == 1 {
|
|
return ErrKeySmallOrder
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// isNonCanonicalEncoding reports whether the y coordinate encoded in pub is
|
|
// greater than or equal to the field prime p = 2^255 - 19. Such an encoding is
|
|
// rejected outright; it exists only to produce a second byte string for a
|
|
// point that already has a canonical encoding.
|
|
func isNonCanonicalEncoding(pub []byte) bool {
|
|
if len(pub) != ed25519.PublicKeySize {
|
|
return false
|
|
}
|
|
// Little-endian comparison against p, ignoring the sign bit in the MSB.
|
|
var y [32]byte
|
|
copy(y[:], pub)
|
|
y[31] &= 0x7f
|
|
|
|
// p = 2^255 - 19 little-endian.
|
|
var prime = [32]byte{
|
|
0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
|
|
}
|
|
for i := 31; i >= 0; i-- {
|
|
if y[i] < prime[i] {
|
|
return false
|
|
}
|
|
if y[i] > prime[i] {
|
|
return true
|
|
}
|
|
}
|
|
// Exactly equal to p is also non-canonical.
|
|
return true
|
|
}
|