internal/x25519ell2: Initial import

Replace agl's Elligator2 implementation with a different one, that fixes
the various distinguishers stemming from bugs in the original
implementation and "The Elligator paper is extremely hard to read".

All releases prior to this commit are trivially distinguishable with
simple math, so upgrading is strongly recommended.  The upgrade is fully
backward-compatible with existing implementations, however the
non-upgraded side will emit traffic that is trivially distinguishable
from random.

Special thanks to Loup Vaillant for his body of work on this primitive,
and for motivating me to fix it.
merge-requests/3/head
Yawning Angel 2 years ago
parent e330d1b702
commit 393aca86cc

@ -1,4 +1,6 @@
Changes in version 0.0.12 - UNRELEASED:
- Fix the long standing distinguishers associated with agl's Elligator2
implementation (Thanks to Loup Vaillant).
- Replace the extra25519 import with an internal package.
- Update the Azure TLS certificate digest (Thanks to Philipp Winter).
- Make the -unsafeLogging command line switch work.

@ -90,7 +90,9 @@ ServerTransportPlugin obfs4 exec /usr/local/bin/obfs4proxy
### Thanks
* Loup Vaillant for motivating me to replace the Elligator implementation
and a body of code I could draw on to accelerate the replacement process.
* David Fifield for goptlib.
* Adam Langley for his Elligator implementation.
* Adam Langley for his initial Elligator implementation.
* Philipp Winter for the ScrambleSuit protocol which provided much of the
design.

@ -38,6 +38,7 @@ import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"encoding/hex"
"fmt"
@ -47,7 +48,7 @@ import (
"golang.org/x/crypto/hkdf"
"gitlab.com/yawning/obfs4.git/common/csrand"
"gitlab.com/yawning/obfs4.git/internal/extra25519"
"gitlab.com/yawning/obfs4.git/internal/x25519ell2"
)
const (
@ -204,7 +205,7 @@ func (repr *Representative) Bytes() *[RepresentativeLength]byte {
func (repr *Representative) ToPublic() *PublicKey {
pub := new(PublicKey)
extra25519.UnsafeBrokenRepresentativeToPublicKey(pub.Bytes(), repr.Bytes())
x25519ell2.RepresentativeToPublicKey(pub.Bytes(), repr.Bytes())
return pub
}
@ -263,22 +264,28 @@ func NewKeypair(elligator bool) (*Keypair, error) {
for {
// Generate a Curve25519 private key. Like everyone who does this,
// run the CSPRNG output through SHA256 for extra tinfoil hattery.
// run the CSPRNG output through SHA512 for extra tinfoil hattery.
//
// Also use part of the digest that gets truncated off for the
// obfuscation tweak.
priv := keypair.private.Bytes()[:]
if err := csrand.Bytes(priv); err != nil {
return nil, err
}
digest := sha256.Sum256(priv)
digest := sha512.Sum512(priv)
digest[0] &= 248
digest[31] &= 127
digest[31] |= 64
copy(priv, digest[:])
if elligator {
tweak := digest[63]
// Apply the Elligator transform. This fails ~50% of the time.
if !extra25519.UnsafeBrokenScalarBaseMult(keypair.public.Bytes(),
if !x25519ell2.ScalarBaseMult(keypair.public.Bytes(),
keypair.representative.Bytes(),
keypair.private.Bytes()) {
keypair.private.Bytes(),
tweak) {
continue
}
} else {

@ -1,11 +1,13 @@
module gitlab.com/yawning/obfs4.git
require (
filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20
git.torproject.org/pluggable-transports/goptlib.git v1.0.0
github.com/dchest/siphash v1.2.1
gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb
gitlab.com/yawning/utls.git v0.0.12-1
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c
golang.org/x/net v0.0.0-20190328230028-74de082e2cca
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
)
go 1.13

@ -1,3 +1,5 @@
filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20 h1:iJoUgXvhagsNMrJrvavw7vu1eG8+hm6jLOxlLFcoODw=
filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
git.torproject.org/pluggable-transports/goptlib.git v1.0.0 h1:ElTwFFPKf/tA6x5nuIk9g49JZzS4T5WN+eTQTjqd00A=
git.torproject.org/pluggable-transports/goptlib.git v1.0.0/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q=
github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4=
@ -10,15 +12,24 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb h1:qRSZHsODmAP5qDvb3YsO7Qnf3TRiVbGxNG/WYnlM4/o=
gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb/go.mod h1:gvdJuZuO/tPZyhEV8K3Hmoxv/DWud5L4qEQxfYjEUTo=
gitlab.com/yawning/utls.git v0.0.12-1 h1:RL6O0MP2YI0KghuEU/uGN6+8b4183eqNWoYgx7CXD0U=
gitlab.com/yawning/utls.git v0.0.12-1/go.mod h1:3ONKiSFR9Im/c3t5RKmMJTVdmZN496FNyk3mjrY1dyo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190328230028-74de082e2cca h1:hyA6yiAgbUwuWqtscNvWAI7U1CtlaD1KilQ6iudt1aI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa h1:KIDDMLT1O0Nr7TSxp8xM5tJcdn8tgyAONntO829og1M=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

@ -1,21 +0,0 @@
This includes a copy of the edwards25519 and extra25519 packages authored
by agl, that formerly lived at github.com/agl/ed25519 as of the commit
5312a61534124124185d41f09206b9fef1d88403 with the following changes:
* Import paths fixed up.
* The unused Ed25519->X25519 key conversion routines were removed.
* `UnsafeBroken` was prefixed to the routines that are known to be
severely flawed.
The only reason this is being done (despite agl's wishes that the code
base dies, which I wanted to respect) is so people stop bothering me
about it.
Do not ask me questions about this.
Do not use it in other projects.
Do not use it in anything new.
Do not expect me to maintain this beyond ensuring it continues to build.
All I want is to be left alone.

@ -0,0 +1,37 @@
The x25519ell2 package provides X25519 obfuscated with Elligator 2, with
special care taken to handle cofactor related issues, and fixes for the
bugs in agl's original Elligator2 implementation.
All existing versions prior to the migration to the new code (anything
that uses agl's code) are fatally broken, and trivial to distinguish via
some simple math. For more details see Loup Vaillant's writings on the
subject. Any bugs in the implementation are mine, and not his.
Representatives created by this implementation will correctly be decoded
by existing implementations. Public keys created by this implementation
be it via the modified scalar basepoint multiply or via decoding a
representative will be somewhat non-standard, but will interoperate with
a standard X25519 scalar-multiply.
As the obfs4 handshake does not include the decoded representative in
any of it's authenticated handshake digest calculations, this change is
fully-backward compatible (though the non-upgraded side of the connection
will still be trivially distinguishable from random).
##### Maintainer's rant
Honestly, it is possible to create a better obfuscation protocol than
obfs4, and it's shelf-life expired years ago. No one should be using
it for anything at this point, and no one should have been using it
for anything for the past however many years since I first started
telling people to stop using it.
People should also have listened when I told them repeatedly that there
are massive issues in the protocol.
* Do not ask me questions about this.
* Do not use it in other projects.
* Do not use it in anything new.
* Use a prime order group instead of this nonsense especially if you
are doing something new.
* All I want is to be left alone.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,291 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package extra25519
import "gitlab.com/yawning/obfs4.git/internal/edwards25519"
// sqrtMinusAPlus2 is sqrt(-(486662+2))
var sqrtMinusAPlus2 = edwards25519.FieldElement{
-12222970, -8312128, -11511410, 9067497, -15300785, -241793, 25456130, 14121551, -12187136, 3972024,
}
// sqrtMinusHalf is sqrt(-1/2)
var sqrtMinusHalf = edwards25519.FieldElement{
-17256545, 3971863, 28865457, -1750208, 27359696, -16640980, 12573105, 1002827, -163343, 11073975,
}
// halfQMinus1Bytes is (2^255-20)/2 expressed in little endian form.
var halfQMinus1Bytes = [32]byte{
0xf6, 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, 0x3f,
}
// feBytesLess returns one if a <= b and zero otherwise.
func feBytesLE(a, b *[32]byte) int32 {
equalSoFar := int32(-1)
greater := int32(0)
for i := uint(31); i < 32; i-- {
x := int32(a[i])
y := int32(b[i])
greater = (^equalSoFar & greater) | (equalSoFar & ((x - y) >> 31))
equalSoFar = equalSoFar & (((x ^ y) - 1) >> 31)
}
return int32(^equalSoFar & 1 & greater)
}
// UnsafeBrokenScalarBaseMult computes a curve25519 public key from a private
// key and also a uniform representative for that public key. Note that this
// function will fail and return false for about half of private keys.
// See http://elligator.cr.yp.to/elligator-20130828.pdf.
func UnsafeBrokenScalarBaseMult(publicKey, representative, privateKey *[32]byte) bool {
var maskedPrivateKey [32]byte
copy(maskedPrivateKey[:], privateKey[:])
maskedPrivateKey[0] &= 248
maskedPrivateKey[31] &= 127
maskedPrivateKey[31] |= 64
var A edwards25519.ExtendedGroupElement
edwards25519.GeScalarMultBase(&A, &maskedPrivateKey)
var inv1 edwards25519.FieldElement
edwards25519.FeSub(&inv1, &A.Z, &A.Y)
edwards25519.FeMul(&inv1, &inv1, &A.X)
edwards25519.FeInvert(&inv1, &inv1)
var t0, u edwards25519.FieldElement
edwards25519.FeMul(&u, &inv1, &A.X)
edwards25519.FeAdd(&t0, &A.Y, &A.Z)
edwards25519.FeMul(&u, &u, &t0)
var v edwards25519.FieldElement
edwards25519.FeMul(&v, &t0, &inv1)
edwards25519.FeMul(&v, &v, &A.Z)
edwards25519.FeMul(&v, &v, &sqrtMinusAPlus2)
var b edwards25519.FieldElement
edwards25519.FeAdd(&b, &u, &edwards25519.A)
var c, b3, b7, b8 edwards25519.FieldElement
edwards25519.FeSquare(&b3, &b) // 2
edwards25519.FeMul(&b3, &b3, &b) // 3
edwards25519.FeSquare(&c, &b3) // 6
edwards25519.FeMul(&b7, &c, &b) // 7
edwards25519.FeMul(&b8, &b7, &b) // 8
edwards25519.FeMul(&c, &b7, &u)
q58(&c, &c)
var chi edwards25519.FieldElement
edwards25519.FeSquare(&chi, &c)
edwards25519.FeSquare(&chi, &chi)
edwards25519.FeSquare(&t0, &u)
edwards25519.FeMul(&chi, &chi, &t0)
edwards25519.FeSquare(&t0, &b7) // 14
edwards25519.FeMul(&chi, &chi, &t0)
edwards25519.FeNeg(&chi, &chi)
var chiBytes [32]byte
edwards25519.FeToBytes(&chiBytes, &chi)
// chi[1] is either 0 or 0xff
if chiBytes[1] == 0xff {
return false
}
// Calculate r1 = sqrt(-u/(2*(u+A)))
var r1 edwards25519.FieldElement
edwards25519.FeMul(&r1, &c, &u)
edwards25519.FeMul(&r1, &r1, &b3)
edwards25519.FeMul(&r1, &r1, &sqrtMinusHalf)
var maybeSqrtM1 edwards25519.FieldElement
edwards25519.FeSquare(&t0, &r1)
edwards25519.FeMul(&t0, &t0, &b)
edwards25519.FeAdd(&t0, &t0, &t0)
edwards25519.FeAdd(&t0, &t0, &u)
edwards25519.FeOne(&maybeSqrtM1)
edwards25519.FeCMove(&maybeSqrtM1, &edwards25519.SqrtM1, edwards25519.FeIsNonZero(&t0))
edwards25519.FeMul(&r1, &r1, &maybeSqrtM1)
// Calculate r = sqrt(-(u+A)/(2u))
var r edwards25519.FieldElement
edwards25519.FeSquare(&t0, &c) // 2
edwards25519.FeMul(&t0, &t0, &c) // 3
edwards25519.FeSquare(&t0, &t0) // 6
edwards25519.FeMul(&r, &t0, &c) // 7
edwards25519.FeSquare(&t0, &u) // 2
edwards25519.FeMul(&t0, &t0, &u) // 3
edwards25519.FeMul(&r, &r, &t0)
edwards25519.FeSquare(&t0, &b8) // 16
edwards25519.FeMul(&t0, &t0, &b8) // 24
edwards25519.FeMul(&t0, &t0, &b) // 25
edwards25519.FeMul(&r, &r, &t0)
edwards25519.FeMul(&r, &r, &sqrtMinusHalf)
edwards25519.FeSquare(&t0, &r)
edwards25519.FeMul(&t0, &t0, &u)
edwards25519.FeAdd(&t0, &t0, &t0)
edwards25519.FeAdd(&t0, &t0, &b)
edwards25519.FeOne(&maybeSqrtM1)
edwards25519.FeCMove(&maybeSqrtM1, &edwards25519.SqrtM1, edwards25519.FeIsNonZero(&t0))
edwards25519.FeMul(&r, &r, &maybeSqrtM1)
var vBytes [32]byte
edwards25519.FeToBytes(&vBytes, &v)
vInSquareRootImage := feBytesLE(&vBytes, &halfQMinus1Bytes)
edwards25519.FeCMove(&r, &r1, vInSquareRootImage)
edwards25519.FeToBytes(publicKey, &u)
edwards25519.FeToBytes(representative, &r)
return true
}
// q58 calculates out = z^((p-5)/8).
func q58(out, z *edwards25519.FieldElement) {
var t1, t2, t3 edwards25519.FieldElement
var i int
edwards25519.FeSquare(&t1, z) // 2^1
edwards25519.FeMul(&t1, &t1, z) // 2^1 + 2^0
edwards25519.FeSquare(&t1, &t1) // 2^2 + 2^1
edwards25519.FeSquare(&t2, &t1) // 2^3 + 2^2
edwards25519.FeSquare(&t2, &t2) // 2^4 + 2^3
edwards25519.FeMul(&t2, &t2, &t1) // 4,3,2,1
edwards25519.FeMul(&t1, &t2, z) // 4..0
edwards25519.FeSquare(&t2, &t1) // 5..1
for i = 1; i < 5; i++ { // 9,8,7,6,5
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t1, &t2, &t1) // 9,8,7,6,5,4,3,2,1,0
edwards25519.FeSquare(&t2, &t1) // 10..1
for i = 1; i < 10; i++ { // 19..10
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t2, &t2, &t1) // 19..0
edwards25519.FeSquare(&t3, &t2) // 20..1
for i = 1; i < 20; i++ { // 39..20
edwards25519.FeSquare(&t3, &t3)
}
edwards25519.FeMul(&t2, &t3, &t2) // 39..0
edwards25519.FeSquare(&t2, &t2) // 40..1
for i = 1; i < 10; i++ { // 49..10
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t1, &t2, &t1) // 49..0
edwards25519.FeSquare(&t2, &t1) // 50..1
for i = 1; i < 50; i++ { // 99..50
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t2, &t2, &t1) // 99..0
edwards25519.FeSquare(&t3, &t2) // 100..1
for i = 1; i < 100; i++ { // 199..100
edwards25519.FeSquare(&t3, &t3)
}
edwards25519.FeMul(&t2, &t3, &t2) // 199..0
edwards25519.FeSquare(&t2, &t2) // 200..1
for i = 1; i < 50; i++ { // 249..50
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t1, &t2, &t1) // 249..0
edwards25519.FeSquare(&t1, &t1) // 250..1
edwards25519.FeSquare(&t1, &t1) // 251..2
edwards25519.FeMul(out, &t1, z) // 251..2,0
}
// chi calculates out = z^((p-1)/2). The result is either 1, 0, or -1 depending
// on whether z is a non-zero square, zero, or a non-square.
func chi(out, z *edwards25519.FieldElement) {
var t0, t1, t2, t3 edwards25519.FieldElement
var i int
edwards25519.FeSquare(&t0, z) // 2^1
edwards25519.FeMul(&t1, &t0, z) // 2^1 + 2^0
edwards25519.FeSquare(&t0, &t1) // 2^2 + 2^1
edwards25519.FeSquare(&t2, &t0) // 2^3 + 2^2
edwards25519.FeSquare(&t2, &t2) // 4,3
edwards25519.FeMul(&t2, &t2, &t0) // 4,3,2,1
edwards25519.FeMul(&t1, &t2, z) // 4..0
edwards25519.FeSquare(&t2, &t1) // 5..1
for i = 1; i < 5; i++ { // 9,8,7,6,5
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t1, &t2, &t1) // 9,8,7,6,5,4,3,2,1,0
edwards25519.FeSquare(&t2, &t1) // 10..1
for i = 1; i < 10; i++ { // 19..10
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t2, &t2, &t1) // 19..0
edwards25519.FeSquare(&t3, &t2) // 20..1
for i = 1; i < 20; i++ { // 39..20
edwards25519.FeSquare(&t3, &t3)
}
edwards25519.FeMul(&t2, &t3, &t2) // 39..0
edwards25519.FeSquare(&t2, &t2) // 40..1
for i = 1; i < 10; i++ { // 49..10
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t1, &t2, &t1) // 49..0
edwards25519.FeSquare(&t2, &t1) // 50..1
for i = 1; i < 50; i++ { // 99..50
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t2, &t2, &t1) // 99..0
edwards25519.FeSquare(&t3, &t2) // 100..1
for i = 1; i < 100; i++ { // 199..100
edwards25519.FeSquare(&t3, &t3)
}
edwards25519.FeMul(&t2, &t3, &t2) // 199..0
edwards25519.FeSquare(&t2, &t2) // 200..1
for i = 1; i < 50; i++ { // 249..50
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t1, &t2, &t1) // 249..0
edwards25519.FeSquare(&t1, &t1) // 250..1
for i = 1; i < 4; i++ { // 253..4
edwards25519.FeSquare(&t1, &t1)
}
edwards25519.FeMul(out, &t1, &t0) // 253..4,2,1
}
// UnsafeBrokenRepresentativeToPublicKey converts a uniform representative
// value for a curve25519 public key, as produced by UnsafeBrokenScalarBaseMult,
// to a curve25519 public key.
func UnsafeBrokenRepresentativeToPublicKey(publicKey, representative *[32]byte) {
var rr2, v, e edwards25519.FieldElement
edwards25519.FeFromBytes(&rr2, representative)
edwards25519.FeSquare2(&rr2, &rr2)
rr2[0]++
edwards25519.FeInvert(&rr2, &rr2)
edwards25519.FeMul(&v, &edwards25519.A, &rr2)
edwards25519.FeNeg(&v, &v)
var v2, v3 edwards25519.FieldElement
edwards25519.FeSquare(&v2, &v)
edwards25519.FeMul(&v3, &v, &v2)
edwards25519.FeAdd(&e, &v3, &v)
edwards25519.FeMul(&v2, &v2, &edwards25519.A)
edwards25519.FeAdd(&e, &v2, &e)
chi(&e, &e)
var eBytes [32]byte
edwards25519.FeToBytes(&eBytes, &e)
// eBytes[1] is either 0 (for e = 1) or 0xff (for e = -1)
eIsMinus1 := int32(eBytes[1]) & 1
var negV edwards25519.FieldElement
edwards25519.FeNeg(&negV, &v)
edwards25519.FeCMove(&v, &negV, eIsMinus1)
edwards25519.FeZero(&v2)
edwards25519.FeCMove(&v2, &edwards25519.A, eIsMinus1)
edwards25519.FeSub(&v, &v, &v2)
edwards25519.FeToBytes(publicKey, &v)
}

@ -1,61 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package extra25519
import (
"bytes"
"crypto/rand"
"testing"
"golang.org/x/crypto/curve25519"
)
func TestElligator(t *testing.T) {
var publicKey, publicKey2, publicKey3, representative, privateKey [32]byte
for i := 0; i < 1000; i++ {
rand.Reader.Read(privateKey[:])
if !UnsafeBrokenScalarBaseMult(&publicKey, &representative, &privateKey) {
continue
}
UnsafeBrokenRepresentativeToPublicKey(&publicKey2, &representative)
if !bytes.Equal(publicKey[:], publicKey2[:]) {
t.Fatal("The resulting public key doesn't match the initial one.")
}
curve25519.ScalarBaseMult(&publicKey3, &privateKey)
if !bytes.Equal(publicKey[:], publicKey3[:]) {
t.Fatal("The public key doesn't match the value that curve25519 produced.")
}
}
}
func BenchmarkKeyGeneration(b *testing.B) {
var publicKey, representative, privateKey [32]byte
// Find the private key that results in a point that's in the image of the map.
for {
rand.Reader.Read(privateKey[:])
if UnsafeBrokenScalarBaseMult(&publicKey, &representative, &privateKey) {
break
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
UnsafeBrokenScalarBaseMult(&publicKey, &representative, &privateKey)
}
}
func BenchmarkMap(b *testing.B) {
var publicKey, representative [32]byte
rand.Reader.Read(representative[:])
b.ResetTimer()
for i := 0; i < b.N; i++ {
UnsafeBrokenRepresentativeToPublicKey(&publicKey, &representative)
}
}

@ -0,0 +1,177 @@
// Copyright (c) 2021 Yawning Angel <yawning at schwanenlied dot me>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package x25519ell2 implements obfuscated X25519 ECDH, via the Elligator2
// mapping.
package x25519ell2
import (
"encoding/binary"
"filippo.io/edwards25519"
"filippo.io/edwards25519/field"
"gitlab.com/yawning/edwards25519-extra.git/elligator2"
)
// The corrected version of this that solves the implementation errors
// present in the historical implementation by agl is derived from
// Monocypher (CC-0 or BSD-2) by Loup Vaillant. Without their efforts
// and prodding, this would likely have stayed broken forever.
var (
feOne = new(field.Element).One()
feNegTwo = mustFeFromBytes([]byte{
0xeb, 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,
})
feA = mustFeFromUint64(486662)
feSqrtM1 = mustFeFromBytes([]byte{
0xb0, 0xa0, 0x0e, 0x4a, 0x27, 0x1b, 0xee, 0xc4, 0x78, 0xe4, 0x2f, 0xad, 0x06, 0x18, 0x43, 0x2f,
0xa7, 0xd7, 0xfb, 0x3d, 0x99, 0x00, 0x4d, 0x2b, 0x0b, 0xdf, 0xc1, 0x4f, 0x80, 0x24, 0x83, 0x2b,
})
// Low order point Edwards x-coordinate `sqrt((sqrt(d + 1) + 1) / d)`.
feLopX = mustFeFromBytes([]byte{
0x4a, 0xd1, 0x45, 0xc5, 0x46, 0x46, 0xa1, 0xde, 0x38, 0xe2, 0xe5, 0x13, 0x70, 0x3c, 0x19, 0x5c,
0xbb, 0x4a, 0xde, 0x38, 0x32, 0x99, 0x33, 0xe9, 0x28, 0x4a, 0x39, 0x06, 0xa0, 0xb9, 0xd5, 0x1f,
})
// Low order point Edwards y-coordinate `-lop_x * sqrtm1`
feLopY = mustFeFromBytes([]byte{
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,
})
)
func mustFeFromBytes(b []byte) *field.Element {
fe, err := new(field.Element).SetBytes(b)
if err != nil {
panic("internal/x25519ell2: failed to deserialize constant: " + err.Error())
}
return fe
}
func mustFeFromUint64(x uint64) *field.Element {
var b [32]byte
binary.LittleEndian.PutUint64(b[:], x)
return mustFeFromBytes(b[:])
}
func selectLowOrderPoint(out, x, k *field.Element, cofactor uint8) {
out.Zero()
out.Select(k, out, int((cofactor>>1)&1)) // bit 1
out.Select(x, out, int((cofactor>>0)&1)) // bit 1
var tmp field.Element
tmp.Negate(out)
out.Select(&tmp, out, int((cofactor>>2)&1)) // bit 2
}
func scalarBaseMultDirty(privateKey *[32]byte) *field.Element {
// Compute clean scalar multiplication
scalar, err := new(edwards25519.Scalar).SetBytesWithClamping(privateKey[:])
if err != nil {
panic("internal/x25519ell2: failed to deserialize scalar: " + err.Error())
}
pk := new(edwards25519.Point).ScalarBaseMult(scalar)
// Compute low order point
var lopX, lopY, lopT field.Element
selectLowOrderPoint(&lopX, feLopX, feSqrtM1, privateKey[0])
selectLowOrderPoint(&lopY, feLopY, feOne, privateKey[0]+2)
// Z = one
lopT.Multiply(&lopX, &lopY)
lop, err := new(edwards25519.Point).SetExtendedCoordinates(&lopX, &lopY, feOne, &lopT)
if err != nil {
panic("interal/x25519ell2: failed to create edwards point from x, y: " + err.Error())
}
// Add low order point to the public key
pk.Add(pk, lop)
// Convert to Montgomery u coordinate (we ignore the sign)
_, yExt, zExt, _ := pk.ExtendedCoordinates()
var t1, t2 field.Element
t1.Add(zExt, yExt)
t2.Subtract(zExt, yExt)
t2.Invert(&t2)
t1.Multiply(&t1, &t2)
return &t1
}
func uToRepresentative(representative *[32]byte, u *field.Element, tweak byte) bool {
t1 := new(field.Element).Set(u)
t2 := new(field.Element).Add(t1, feA)
t3 := new(field.Element).Multiply(t1, t2)
t3.Multiply(t3, feNegTwo)
if _, isSquare := t3.SqrtRatio(feOne, t3); isSquare == 1 {
t1.Select(t2, t1, int(tweak&1))
t3.Multiply(t1, t3)
t1.Mult32(t3, 2)
t2.Negate(t3)
tmp := t1.Bytes()
t3.Select(t2, t3, int(tmp[0]&1))
copy(representative[:], t3.Bytes())
// Pad with two random bits
representative[31] |= tweak & 0xc0
return true
}
return false
}
// ScalarBaseMult computes a curve25519 public key from a private
// key and also a uniform representative for that public key.
// Note that this function will fail and return false for about
// half of private keys.
//
// WARNING: The underlying scalar multiply explicitly does not clear
// the cofactor, and thus the public keys will be different from
// those produced by normal implementations.
func ScalarBaseMult(publicKey, representative, privateKey *[32]byte, tweak byte) bool {
u := scalarBaseMultDirty(privateKey)
if !uToRepresentative(representative, u, tweak) {
// No representative.
return false
}
copy(publicKey[:], u.Bytes())
return true
}
// RepresentativeToPublicKey converts a uniform representative value for
// a curve25519 public key, as produced by ScalarBaseMult, to a curve25519
// public key.
func RepresentativeToPublicKey(publicKey, representative *[32]byte) {
// Representatives are encoded in 254 bits.
var clamped [32]byte
copy(clamped[:], representative[:])
clamped[31] &= 63
var fe field.Element
if _, err := fe.SetBytes(clamped[:]); err != nil {
// Panic is fine, the only way this fails is if the representative
// is not 32-bytes.
panic("internal/x25519ell2: failed to deserialize representative: " + err.Error())
}
u, _ := elligator2.MontgomeryFlavor(&fe)
copy(publicKey[:], u.Bytes())
}

@ -0,0 +1,153 @@
// Copyright (c) 2021 Yawning Angel <yawning at schwanenlied dot me>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package x25519ell2
import (
"bytes"
"crypto/rand"
"testing"
"filippo.io/edwards25519/field"
"golang.org/x/crypto/curve25519"
)
func TestX25519Ell2(t *testing.T) {
t.Run("Constants", testConstants)
t.Run("KeyExchage", testKeyExchange)
}
func testConstants(t *testing.T) {
// While the constants were calculated and serialized with a known
// correct implementation of the field arithmetic, re-derive them
// to be sure.
t.Run("NegTwo", func(t *testing.T) {
expected := new(field.Element).Add(feOne, feOne)
expected.Negate(expected)
if expected.Equal(feNegTwo) != 1 {
t.Fatalf("invalid value for -2: %x", feNegTwo.Bytes())
}
})
t.Run("LopX", func(t *testing.T) {
// d = -121665/121666
d := mustFeFromUint64(121666)
d.Invert(d)
d.Multiply(d, mustFeFromUint64(121665))
d.Negate(d)
// lop_x = sqrt((sqrt(d + 1) + 1) / d)
expected := new(field.Element).Add(d, feOne)
expected.Invert(expected)
expected.SqrtRatio(feOne, expected)
expected.Add(expected, feOne)
expected.SqrtRatio(expected, d)
if expected.Equal(feLopX) != 1 {
t.Fatalf("invalid value for low order point X: %x", feLopX.Bytes())
}
})
t.Run("LopY", func(t *testing.T) {
// lop_y = -lop_x * sqrtm1
expected := new(field.Element).Negate(feLopX)
expected.Multiply(expected, feSqrtM1)
if expected.Equal(feLopY) != 1 {
t.Fatalf("invalid value for low order point Y: %x", feLopY.Bytes())
}
})
}
func testKeyExchange(t *testing.T) {
var randSk [32]byte
_, _ = rand.Read(randSk[:])
var good, bad int
for i := 0; i < 1000; i++ {
var (
publicKey, privateKey, representative [32]byte
publicKeyClean [32]byte
tweak [1]byte
)
_, _ = rand.Read(privateKey[:])
_, _ = rand.Read(tweak[:])
// This won't match the public key from the Elligator2-ed scalar
// basepoint multiply, but we want to ensure that the public keys
// we do happen to generate are interoperable (otherwise something
// is badly broken).
curve25519.ScalarBaseMult(&publicKeyClean, &privateKey)
if !ScalarBaseMult(&publicKey, &representative, &privateKey, tweak[0]) {
t.Logf("bad: %x", privateKey)
bad++
continue
}
t.Logf("good: %x", privateKey)
t.Logf("publicKey: %x, repr: %x", publicKey, representative)
var shared, sharedRep, sharedClean, pkFromRep [32]byte
RepresentativeToPublicKey(&pkFromRep, &representative)
if !bytes.Equal(pkFromRep[:], publicKey[:]) {
t.Fatalf("public key mismatch(repr): expected %x, actual: %x", publicKey, pkFromRep)
}
curve25519.ScalarMult(&sharedClean, &randSk, &publicKeyClean) //nolint: staticcheck
curve25519.ScalarMult(&shared, &randSk, &publicKey) //nolint: staticcheck
curve25519.ScalarMult(&sharedRep, &randSk, &pkFromRep) //nolint: staticcheck
if !bytes.Equal(shared[:], sharedRep[:]) {
t.Fatalf("shared secret mismatch: expected %x, actual: %x", shared, sharedRep)
}
if !bytes.Equal(shared[:], sharedClean[:]) {
t.Fatalf("shared secret mismatch(clean): expected %x, actual: %x", shared, sharedClean)
}
good++
}
t.Logf("good: %d, bad: %d", good, bad)
}
func BenchmarkKeyGeneration(b *testing.B) {
var publicKey, representative, privateKey [32]byte
// Find the private key that results in a point that's in the image of the map.
for {
_, _ = rand.Reader.Read(privateKey[:])
if ScalarBaseMult(&publicKey, &representative, &privateKey, 0) {
break
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
ScalarBaseMult(&publicKey, &representative, &privateKey, 0)
}
}
func BenchmarkMap(b *testing.B) {
var publicKey, representative [32]byte
_, _ = rand.Reader.Read(representative[:])
b.ResetTimer()
for i := 0; i < b.N; i++ {
RepresentativeToPublicKey(&publicKey, &representative)
}
}
Loading…
Cancel
Save