mirror of https://gitlab.com/yawning/obfs4
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
parent
e330d1b702
commit
393aca86cc
@ -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,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…
Reference in New Issue