|
|
|
|
|
// Package ed25519strict verifies Ed25519 signatures with the strict profile
|
|
|
|
|
|
// of the author signature (spec v0.11, §29.9): the equation of RFC 8032
|
|
|
|
|
|
// without the cofactor, with the public key A and R in their canonical
|
|
|
|
|
|
// encodings, S below ℓ, and A not of small order.
|
|
|
|
|
|
//
|
|
|
|
|
|
// crypto/ed25519 checks S and R, and computes the equation without the
|
|
|
|
|
|
// cofactor, but it accepts a non-canonical A and an A of small order: with A
|
|
|
|
|
|
// = 01 00…00, R the identity and S = 0 it accepts any message. Verify checks A
|
|
|
|
|
|
// first, with an encoding check and the table of the eight points of small
|
|
|
|
|
|
// order, so that no arithmetic on points is written here.
|
|
|
|
|
|
package ed25519strict
|
|
|
|
|
|
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
|
import (
|
|
|
|
|
|
"crypto/ed25519"
|
|
|
|
|
|
"math/big"
|
|
|
|
|
|
"slices"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// smallOrder are the canonical encodings of the eight points of small order
|
|
|
|
|
|
// of edwards25519: the identity, the point of order 2, the two of order 4 and
|
|
|
|
|
|
// the four of order 8. A canonical A of small order is one of them; the tests
|
|
|
|
|
|
// compute them again.
|
|
|
|
|
|
var smallOrder = [8][32]byte{
|
|
|
|
|
|
{0x00},
|
|
|
|
|
|
{31: 0x80},
|
|
|
|
|
|
{0x01},
|
|
|
|
|
|
{0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05},
|
|
|
|
|
|
{0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85},
|
|
|
|
|
|
{0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a},
|
|
|
|
|
|
{0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa},
|
|
|
|
|
|
{0xec, 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},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Verify reports whether sig is a valid signature of msg by the public key
|
|
|
|
|
|
// pub under the strict profile (spec §29.9). A key or a signature of another
|
|
|
|
|
|
// length is not valid either; the caller tells that case apart (F1).
|
|
|
|
|
|
func Verify(pub, msg, sig []byte) bool {
|
|
|
|
|
|
if len(pub) != ed25519.PublicKeySize || len(sig) != ed25519.SignatureSize {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
if !Canonical(pub) || SmallOrder(pub) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
// crypto/ed25519 rejects sig[63] & 0xE0 != 0 and S >= ℓ, and compares the
|
|
|
|
|
|
// encoding of [S]B − [k]A with R, which therefore must be canonical.
|
|
|
|
|
|
return ed25519.Verify(ed25519.PublicKey(pub), msg, sig)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Canonical reports whether the 32 bytes a are a canonical encoding: their y,
|
|
|
|
|
|
// the low 255 bits, is below p = 2^255 − 19, and their sign bit is clear when
|
|
|
|
|
|
// y is 1 or p − 1, the two values whose x is 0 (spec §29.9, rule 1). It does
|
|
|
|
|
|
// not tell whether y belongs to a point of the curve.
|
|
|
|
|
|
func Canonical(a []byte) bool {
|
|
|
|
|
|
if len(a) != 32 {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
high := a[31] & 0x7f
|
|
|
|
|
|
ones := true
|
|
|
|
|
|
for _, b := range a[1:31] {
|
|
|
|
|
|
if b != 0xff {
|
|
|
|
|
|
ones = false
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// y >= p: 7f ff…ff and a first byte of 0xed or more.
|
|
|
|
|
|
if high == 0x7f && ones && a[0] >= 0xed {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
if a[31]&0x80 == 0 {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
// x = 0: y = 1, 01 00…00, or y = p − 1, ec ff…ff 7f.
|
|
|
|
|
|
zeros := high == 0
|
|
|
|
|
|
for _, b := range a[1:31] {
|
|
|
|
|
|
if b != 0 {
|
|
|
|
|
|
zeros = false
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
isOne := zeros && a[0] == 0x01
|
|
|
|
|
|
isMinusOne := high == 0x7f && ones && a[0] == 0xec
|
|
|
|
|
|
return !isOne && !isMinusOne
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
|
// The field and the curve of edwards25519: p = 2^255 − 19, d = −121665/121666
|
|
|
|
|
|
// mod p, and the exponent (p − 1)/2 of Euler's criterion.
|
|
|
|
|
|
var curveP, curveD, halfP = func() (p, d, h *big.Int) {
|
|
|
|
|
|
p = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 255), big.NewInt(19))
|
|
|
|
|
|
d = new(big.Int).ModInverse(big.NewInt(121666), p)
|
|
|
|
|
|
d.Mul(d, big.NewInt(-121665)).Mod(d, p)
|
|
|
|
|
|
h = new(big.Int).Rsh(new(big.Int).Sub(p, big.NewInt(1)), 1)
|
|
|
|
|
|
return p, d, h
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
// OnCurve reports whether the canonical encoding a is a point of the curve:
|
|
|
|
|
|
// whether x² = (y² − 1)/(d·y² + 1) has a solution modulo p (RFC 8032, 5.1.3).
|
|
|
|
|
|
// Verify leaves that check to crypto/ed25519; a parser of keys uses it to
|
|
|
|
|
|
// refuse a key that no signature could verify (spec §29.9, rule 2). d·y² + 1
|
|
|
|
|
|
// is never 0, because −1/d is not a square.
|
|
|
|
|
|
func OnCurve(a []byte) bool {
|
|
|
|
|
|
if len(a) != 32 {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
be := slices.Clone(a)
|
|
|
|
|
|
be[31] &= 0x7f
|
|
|
|
|
|
slices.Reverse(be)
|
|
|
|
|
|
y := new(big.Int).SetBytes(be)
|
|
|
|
|
|
y2 := new(big.Int).Mul(y, y)
|
|
|
|
|
|
u := new(big.Int).Sub(y2, big.NewInt(1))
|
|
|
|
|
|
v := new(big.Int).Mul(curveD, y2)
|
|
|
|
|
|
v.Add(v, big.NewInt(1)).Mod(v, curveP)
|
|
|
|
|
|
x2 := u.Mul(u, v.ModInverse(v, curveP))
|
|
|
|
|
|
x2.Mod(x2, curveP)
|
|
|
|
|
|
return x2.Sign() == 0 || new(big.Int).Exp(x2, halfP, curveP).Cmp(big.NewInt(1)) == 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SmallOrder reports whether the canonical encoding a is one of the eight
|
|
|
|
|
|
// points of small order (spec §29.9, rule 2).
|
|
|
|
|
|
func SmallOrder(a []byte) bool {
|
|
|
|
|
|
if len(a) != 32 {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, s := range smallOrder {
|
|
|
|
|
|
if [32]byte(a) == s {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SmallOrderPoints returns the canonical encodings of the eight points of
|
|
|
|
|
|
// small order, for the tests and the vectors.
|
|
|
|
|
|
func SmallOrderPoints() [8][32]byte { return smallOrder }
|