Browse Source

Initial import.

Yawning Angel 3 years ago
commit
f71885fdfd

+ 48 - 0
CODE_OF_MERIT.md

@@ -0,0 +1,48 @@
+# Code of Merit
+
+1. The project creators, lead developers, core team, constitute
+the managing members of the project and have final say in every decision
+of the project, technical or otherwise, including overruling previous decisions.
+There are no limitations to this decisional power.
+
+2. Contributions are an expected result of your membership on the project.
+Don't expect others to do your work or help you with your work forever. 
+
+3. All members have the same opportunities to seek any challenge they want
+within the project. 
+
+4. Authority or position in the project will be proportional
+to the accrued contribution. Seniority must be earned.
+
+5. Software is evolutive: the better implementations must supersede lesser
+implementations. Technical advantage is the primary evaluation metric.
+
+6. This is a space for technical prowess; topics outside of the project
+will not be tolerated.
+
+7. Non technical conflicts will be discussed in a separate space. Disruption
+of the project will not be allowed.
+
+8. Individual characteristics, including but not limited to,
+body, sex, sexual preference, race, language, religion, nationality,
+or political preferences are irrelevant in the scope of the project and
+will not be taken into account concerning your value or that of your contribution
+to the project.
+
+9. Discuss or debate the idea, not the person.
+
+10. There is no room for ambiguity: Ambiguity will be met with questioning;
+further ambiguity will be met with silence. It is the responsibility
+of the originator to provide requested context.
+
+11. This Code of Merit governs the technical procedures of the project not the 
+activities outside of it. 
+
+12. Participation on the project equates to agreement of this Code of Merit.
+
+13. No objectives beyond the stated objectives of this project are relevant
+to the project. Any intent to deviate the project from its original purpose
+of existence will constitute grounds for remedial action which may include
+expulsion from the project.
+
+This document is adapted from the Code of Merit (http://code-of-merit.org), version 1.0.

+ 76 - 0
README.md

@@ -0,0 +1,76 @@
+### basket2 - A transport for the paranoid.
+#### Yawning Angel (yawning at schwanenlied dot me)
+
+basket2 is the next transport in the obfs series.  It derives inspiration
+primarily from obfs4 and predecessors, and incorporates ideas initially
+prototyped in the experimental basket transport.
+
+Features:
+
+ * Authentication, data integrity, and confidentiality.
+ * Active probing resistance.
+ * Passive fingerprinting resistance, significantly improved over obfs4.
+ * Client driven dynamic negotiation of runtime padding to better suit various
+   adversary models.
+ * Better separation between the handshake obfuscation and the authenticated
+   key exchange mechanisms.
+ * Significantly improved link layer framing.
+ * Optional user authentication.
+ * Optional post-quantum forward secrecy.
+ * License switch from 3BSD to AGPL for more Freedom.
+
+Dependencies:
+
+ * Go 1.5.x - (May work with older versions, don't care if they don't)
+ * golang.org/x/crypto - SHA3, Curve25519, Poly1305
+ * github.com/agl/ed25519 - Ed25519, Edwards curve field arithmatic
+ * git.schwanenlied.me/yawning/chacha20.git - (X)ChaCha20
+ * git.schwanenlied.me/yawning/x448.git - X448
+ * git.schwanenlied.me/yawning/newhope.git - newhope
+
+Notes:
+
+ * I am waiving the remote network interaction requirements specified in
+   Section 13 ("Remote Network Interaction; Use with the GNU General Public
+   License") of the AGPL, per the terms of Section 7 ("Additional Terms"),
+   for users that:
+
+    * Are using the software to operate a publically accessible Bridge to
+      provide access to the public Tor network as a Tor Pluggable Transport
+      server.  This means:
+
+        The Bridge publishes a descriptor to the Bridge Authority, and is
+        available via BridgeDB OR is a default Bridge pre-configured and
+        distributed with Tor Browser.
+
+   All other users MUST comply with the AGPL in it's entirety as a general
+   rule, though other licensing arrangements may be possible on request.
+   I will likely be fairly liberal here, so please contact me if the
+   current licensing is unsuitable for your use case.
+
+ * The post-quantum cryptography does not apply to active attackers in
+   posession of a quantum computer, and only will protect pre-existing data
+   from later decryption.
+
+   Using a PQ signature algorithm such as SPHINCS256 would solve this
+   problem, however the key and signature sizes are still larger than what
+   I feel comfortable with being able to obfsucate.
+
+ * Yeah, this uses SHA3 instead of whatever trendy BLAKE variant kids like
+   these days.
+
+ * If your system has busted PMTUD, this probably won't work at all.  Not my
+   problem.  Complain to your OS vendor.
+
+TODO:
+
+ * Write a formal specification.
+
+ * Someone that's not me should write assembly optimized ChaCha20 for ARM and
+   i386.  I may do both if I feel bored enough, but no promises.
+
+ * Write optimized assembler versions of things for gccgo (or C if that's
+   easier).  Low priority.
+
+ * Define more padding primitives.
+

+ 89 - 0
crypto/secretbox/secretbox.go

@@ -0,0 +1,89 @@
+// secretbox.go - crypto_secretbox_xchacha20poly1305
+// Copyright (C) 2015  Yawning Angel.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// Package secretbox provides secret-key authenticated encryption based
+// around a crypto_secretbox_xchacha20poly1305 construct.
+package secretbox
+
+import (
+	"errors"
+
+	"git.schwanenlied.me/yawning/basket2.git/crypto"
+	"git.schwanenlied.me/yawning/chacha20.git"
+	"golang.org/x/crypto/poly1305"
+)
+
+const (
+	// KeySize is the secretbox key size in bytes.
+	KeySize = chacha20.KeySize
+
+	// NonceSize is the secretbox nonce size in bytes.
+	NonceSize = chacha20.XNonceSize
+
+	// OverheadSize is the secretbox overhead size in bytes.
+	OverheadSize = poly1305.TagSize
+)
+
+// ErrInvalidTag is the error returned when the tag is invalid.
+var ErrInvalidTag = errors.New("tag is invalid")
+
+// Seal encrypts and authenticates the message msg using a secret key and a
+// nonce, and returns the resulting ciphertext.  Note that it is the caller's
+// responsiblity to ensure the uniqueness of nonces.  Nonces are long enough
+// that randomly generated nonces have a negligible risk of collision.
+func Seal(msg []byte, nonce *[NonceSize]byte, key *[KeySize]byte) []byte {
+	s, err := chacha20.NewCipher(key[:], nonce[:])
+	if err != nil {
+		panic(err)
+	}
+
+	var authKey [32]byte
+	var authTag [poly1305.TagSize]byte
+	defer crypto.Memwipe(authKey[:])
+	s.KeyStream(authKey[:])
+
+	box := make([]byte, OverheadSize+len(msg))
+	s.XORKeyStream(box[OverheadSize:], msg)
+	poly1305.Sum(&authTag, box[OverheadSize:], &authKey)
+	copy(box[:OverheadSize], authTag[:])
+	return box
+}
+
+// Open authentictates and decrypts the ciphertext box using a secret key and a
+// nonce, and returns the resulting plaintext.
+func Open(box []byte, nonce *[NonceSize]byte, key *[KeySize]byte) ([]byte, error) {
+	if len(box) < OverheadSize {
+		return nil, ErrInvalidTag
+	}
+
+	s, err := chacha20.NewCipher(key[:], nonce[:])
+	if err != nil {
+		return nil, err
+	}
+
+	var authKey [32]byte
+	var authTag [poly1305.TagSize]byte
+	defer crypto.Memwipe(authKey[:])
+	s.KeyStream(authKey[:])
+
+	copy(authTag[:], box[:OverheadSize])
+	if poly1305.Verify(&authTag, box[OverheadSize:], &authKey) {
+		msg := make([]byte, len(box)-OverheadSize)
+		s.XORKeyStream(msg, box[OverheadSize:])
+		return msg, nil
+	}
+	return nil, ErrInvalidTag
+}

+ 118 - 0
crypto/secretbox/secretbox_test.go

@@ -0,0 +1,118 @@
+// secretbox_test.go - crypto_secretbox_xchacha20poly1305
+// Copyright (C) 2015  Yawning Angel.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package secretbox
+
+import (
+	"bytes"
+	"testing"
+
+	"golang.org/x/crypto/nacl/secretbox"
+)
+
+func TestSecretBox(t *testing.T) {
+	key := [KeySize]byte{
+		0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+		0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
+		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
+		0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00,
+	}
+	nonce := [NonceSize]byte{
+		0x0f, 0x1e, 0x2d, 0x3c, 0x4b, 0x5a, 0x69, 0x78,
+		0x87, 0x96, 0xa5, 0xb4, 0xc3, 0xd2, 0xe1, 0xf0,
+		0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87,
+	}
+
+	msg := []byte("At start, no has lyte. An Ceiling Cat sayz, i can haz lite? An lite wuz. An Ceiling Cat sawed teh lite, to seez stuffs, An splitted teh lite from dark but taht wuz ok cuz kittehs can see in teh dark An not tripz over nethin. An Ceiling Cat sayed light Day An dark no Day. It were FURST!!!1")
+
+	// Seal()/Open()
+	box := Seal(msg, &nonce, &key)
+	opened, err := Open(box, &nonce, &key)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Test that Seal -> Open is indepotent.
+	if !bytes.Equal(opened, msg) {
+		t.Errorf("opened != msg (%x != %x)", opened, msg)
+	}
+
+	// Test that a invalid tag will be rejected.
+	fuktbox := make([]byte, len(box))
+	copy(fuktbox, box)
+	fuktbox[0] = ^fuktbox[0]
+	_, err = Open(fuktbox, &nonce, &key)
+	if err == nil {
+		t.Errorf("invalid tag was accepted")
+	}
+
+	// Test that a invalid ciphertext will be rejected.
+	copy(fuktbox, box)
+	fuktbox[OverheadSize] = ^fuktbox[OverheadSize]
+	_, err = Open(fuktbox, &nonce, &key)
+	if err == nil {
+		t.Errorf("invalid ciphertext was accepted")
+	}
+}
+
+func doBenchN(b *testing.B, n int) {
+	var key [KeySize]byte
+	var nonce [NonceSize]byte
+	msg := make([]byte, n)
+
+	b.SetBytes(int64(n))
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		box := Seal(msg, &nonce, &key)
+		_, err := Open(box, &nonce, &key)
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+func BenchmarkSecretbox_16(b *testing.B) {
+	doBenchN(b, 16)
+}
+
+func BenchmarkSecretbox_1k(b *testing.B) {
+	doBenchN(b, 1024)
+}
+
+func BenchmarkSecretbox_4k(b *testing.B) {
+	doBenchN(b, 4096)
+}
+
+func BenchmarkSecretbox_64k(b *testing.B) {
+	doBenchN(b, 65536)
+}
+
+// As a comparison.
+func BenchmarkNaCl_64k(b *testing.B) {
+	var key [KeySize]byte
+	var nonce [NonceSize]byte
+	msg := make([]byte, 65536)
+
+	b.SetBytes(int64(65536))
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		box := secretbox.Seal(nil, msg, &nonce, &key)
+		_, ok := secretbox.Open(nil, box, &nonce, &key)
+		if !ok {
+			b.Fatal("secretbox.Open failed")
+		}
+	}
+}

+ 33 - 0
crypto/utils.go

@@ -0,0 +1,33 @@
+// utils.go - Misc crypto utilities.
+// Copyright (C) 2015  Yawning Angel.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package crypto
+
+// Memwipe santizes the buffer buf.
+func Memwipe(buf []byte) {
+	for i := range buf {
+		buf[i] = 0
+	}
+}
+
+// MemIsZero returns true if all bytes in the buffer buf are 0.
+func MemIsZero(buf []byte) bool {
+	var b byte
+	for _, v := range buf {
+		b |= v
+	}
+	return b == 0
+}

+ 395 - 0
ext/elligator2/elligator2.go

@@ -0,0 +1,395 @@
+// Copyright (c) 2012-2013 The Go Authors. All rights reserved.
+// Copyright (c) 2014 Yawning Angel. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//   * Redistributions of source code must retain the above copyright
+//     notice, this list of conditions and the following disclaimer.
+//   * Redistributions in binary form must reproduce the above
+//     copyright notice, this list of conditions and the following disclaimer
+//     in the documentation and/or other materials provided with the
+//     distribution.
+//   * Neither the name of Google Inc. nor the names of its
+//     contributors may be used to endorse or promote products derived from
+//     this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Package elligator2 implements the Elligator 2 forward/reverse mapping for
+// Curve25519.  This implementation does NOT interoperate with the original
+// codebase as the original codebase is wrong.
+//
+// See http://elligator.cr.yp.to/elligator-20130828.pdf.
+package elligator2
+
+import (
+	"io"
+
+	"github.com/agl/ed25519/edwards25519"
+	"golang.org/x/crypto/sha3"
+)
+
+// sqrtMinusA is sqrt(-486662)
+var sqrtMinusA = 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)
+}
+
+// GenerateKey generates a Curve25519 key pair along with a uniform
+// representative for the public key, using the random source random
+// (Hint: crypto/rand.Reader).
+func GenerateKey(random io.Reader, publicKey, representative, privateKey *[32]byte) error {
+	// Note: This can be optimized by only doing the scalar basepoint multiply
+	// once, and manipulating the private key/public key if there is no
+	// representative for a ~40% performance gain.
+	//
+	// However:
+	//  * This should not be a critical path operation, since it's ideally
+	//    only done once per connection.
+	//  * edwards25519 doesn't expose geAdd() so adding the optimization is
+	//    somewhat annoying.
+
+	// Initialize a SHAKE128 instance with tweak + 32 bytes of entropy.
+	// This avoids hitting up the system entropy pool repeatedly for private
+	// keys and attempts to mitigate CSPRNG output with low entropy.
+	h := sha3.NewShake128()
+	if _, err := io.ReadFull(random, privateKey[:]); err != nil {
+		return err
+	}
+	h.Write([]byte("elligator2-tweak")) // Domain separation.
+	h.Write(privateKey[:])
+	defer h.Reset()
+
+	for {
+		// Squeeze out a candidate private key from the SHAKE construct.
+		if _, err := io.ReadFull(h, privateKey[:]); err != nil {
+			return err
+		}
+
+		// Attempt to generate a private key and uniform representative.
+		if ok := scalarBaseMult(publicKey, representative, privateKey); ok {
+			break
+		}
+	}
+
+	// Randomize the 2 high bits of the representative.  This uses the system
+	// entropy source since `h` should only be used for private values that
+	// never appear on the wire.
+	var bits [1]byte
+	if _, err := io.ReadFull(random, bits[:]); err != nil {
+		return err
+	}
+	representative[31] |= bits[0] & 0xc0
+
+	return nil
+}
+
+// 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.
+//
+// It is the caller's responsibility to randomize the 2 high bits of the
+// representative before sending it out on the network.
+//
+// See http://elligator.cr.yp.to/elligator-20130828.pdf.
+func scalarBaseMult(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, &sqrtMinusA)
+
+	var b edwards25519.FieldElement
+	edwards25519.FeAdd(&b, &u, &edwards25519.A)
+
+	var c, b3, b8 edwards25519.FieldElement
+	edwards25519.FeSquare(&b3, &b)   // 2
+	edwards25519.FeMul(&b3, &b3, &b) // 3
+	edwards25519.FeSquare(&c, &b3)   // 6
+	edwards25519.FeMul(&c, &c, &b)   // 7
+	edwards25519.FeMul(&b8, &c, &b)  // 8
+	edwards25519.FeMul(&c, &c, &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, &b)   // 2
+	edwards25519.FeMul(&t0, &t0, &b) // 3
+	edwards25519.FeSquare(&t0, &t0)  // 6
+	edwards25519.FeMul(&t0, &t0, &b) // 7
+	edwards25519.FeSquare(&t0, &t0)  // 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)
+
+	// 5.5: Here |b| means b if b in {0, 1, ..., (q - 1)/2}, otherwise -b.
+	var rBytes [32]byte
+	edwards25519.FeToBytes(&rBytes, &r)
+	negateB := 1 & (^feBytesLE(&rBytes, &halfQMinus1Bytes))
+	edwards25519.FeNeg(&r1, &r)
+	edwards25519.FeCMove(&r, &r1, negateB)
+
+	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
+}
+
+// RepresentativeToPublicKey converts a uniform representative value for a
+// curve25519 public key, as produced by GenerateKey, to a curve25519 public
+// key.
+func RepresentativeToPublicKey(publicKey, representative *[32]byte) {
+	// Mask out the 2 high bits, of the representative.
+	var maskedRepresentative [32]byte
+	copy(maskedRepresentative[:], representative[:])
+	maskedRepresentative[31] &= 0x3f
+
+	var rr2, v, e edwards25519.FieldElement
+	edwards25519.FeFromBytes(&rr2, &maskedRepresentative)
+
+	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)
+}

+ 199 - 0
ext/elligator2/elligator2_test.go

@@ -0,0 +1,199 @@
+// elligator2_test.go - Elligator2 unit tests.
+//
+// To the extent possible under law, Yawning Angel has waived all copyright
+// and related or neighboring rights to elligator2_test.go, using the Creative
+// Commons "CC0" public domain dedication. See LICENSE or
+// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
+
+package elligator2
+
+import (
+	"bytes"
+	"crypto/rand"
+	"testing"
+)
+
+func TestElligator2KAT(t *testing.T) {
+	// Use test vectors I generated when I fixed libelligator's implementation
+	// to match the paper.  These test vectors do not randomize the high bits
+	// of the representative.
+
+	type KATResult struct {
+		pk   [32]byte
+		repr [32]byte
+		sk   [32]byte
+		ok   bool
+	}
+	vectors := []KATResult{
+		{
+			[32]byte{0x11, 0x95, 0x3b, 0xb6, 0x26, 0x8b, 0x92, 0xa2, 0xae, 0x97, 0xbe, 0x71, 0x9d, 0xcf, 0x7d, 0x2d, 0xb8, 0x64, 0x32, 0x0f, 0x80, 0xc2, 0x06, 0x7c, 0xa8, 0xc1, 0xd6, 0x49, 0x3d, 0xca, 0x20, 0x11},
+			[32]byte{0xe6, 0x1a, 0x1a, 0x7a, 0xb0, 0xb7, 0xba, 0x28, 0xa7, 0x43, 0xfc, 0x01, 0x10, 0x82, 0x70, 0x5a, 0x8a, 0x32, 0xac, 0xcc, 0xc0, 0x02, 0xf9, 0xed, 0x8d, 0xef, 0x87, 0x43, 0x75, 0x0e, 0xd9, 0x0e},
+			[32]byte{0xa8, 0x0d, 0xcd, 0xfa, 0xf4, 0xa3, 0x3b, 0x5f, 0xda, 0x02, 0xec, 0xdf, 0xc1, 0x7c, 0xc0, 0x16, 0xdc, 0xd7, 0xf9, 0xc7, 0x0d, 0xfd, 0xc8, 0x84, 0xa6, 0x0e, 0x33, 0x1b, 0xd3, 0xbd, 0x3f, 0x7f},
+			true,
+		},
+
+		{
+			[32]byte{0xad, 0x8f, 0x77, 0x2a, 0xc6, 0x27, 0x40, 0x19, 0x6b, 0xfd, 0x0b, 0x00, 0xe6, 0x1d, 0x4f, 0xbb, 0x7b, 0x61, 0x64, 0xfc, 0xfa, 0x9b, 0x9b, 0xaa, 0x99, 0x12, 0xdc, 0x35, 0xf8, 0x20, 0xcb, 0x3c},
+			[32]byte{0x7c, 0xfb, 0x14, 0xfa, 0xb7, 0x2d, 0x23, 0x21, 0x53, 0xfa, 0x55, 0x8c, 0x10, 0x29, 0xc5, 0xcb, 0x12, 0x14, 0x2d, 0x34, 0xb4, 0xf3, 0x54, 0xc0, 0xc1, 0x50, 0x6e, 0x96, 0x25, 0x73, 0x79, 0x2e},
+			[32]byte{0x28, 0x57, 0x2c, 0x0e, 0xdb, 0x0b, 0x76, 0xf2, 0x54, 0x21, 0x45, 0x5d, 0xdb, 0x77, 0x8c, 0x79, 0x05, 0x13, 0xfd, 0x54, 0x29, 0xb6, 0x1f, 0xce, 0x83, 0x25, 0x0d, 0xfe, 0xfa, 0x04, 0x83, 0x45},
+			true,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0xe0, 0xc7, 0x38, 0x0b, 0x24, 0xc2, 0xc4, 0x5e, 0xc7, 0x91, 0x8f, 0x9e, 0xe8, 0xa5, 0x33, 0x1c, 0xa0, 0xb0, 0xd0, 0xa1, 0xd1, 0xe8, 0x37, 0x84, 0x23, 0x8c, 0xdf, 0xb2, 0x4f, 0x12, 0xa7, 0x62},
+			false,
+		},
+		{
+			[32]byte{0x09, 0xef, 0x7b, 0xb3, 0x50, 0xfc, 0x8b, 0xc3, 0x09, 0x58, 0x11, 0xa5, 0x48, 0x78, 0xef, 0x18, 0x3d, 0x44, 0xcb, 0xfc, 0x23, 0x37, 0x34, 0xda, 0xef, 0xcd, 0x48, 0x96, 0x98, 0xef, 0x6a, 0x05},
+			[32]byte{0x45, 0xcc, 0xe7, 0xed, 0xf5, 0x88, 0xdd, 0xda, 0xfb, 0x09, 0xdf, 0x3d, 0x2d, 0xfe, 0x4f, 0x3c, 0xca, 0x36, 0x07, 0x50, 0xdd, 0x2f, 0xe7, 0x76, 0xc9, 0x47, 0x2f, 0x0d, 0xbc, 0xa6, 0x0b, 0x20},
+			[32]byte{0x80, 0x80, 0xff, 0x43, 0x54, 0x0a, 0xf3, 0x79, 0x14, 0xf0, 0xf1, 0x87, 0x4f, 0xeb, 0x00, 0x83, 0x6b, 0x03, 0xf4, 0x78, 0x9f, 0x4c, 0x0a, 0xbf, 0xcd, 0x2e, 0xff, 0x9c, 0x10, 0xe4, 0x3c, 0x64},
+			true,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0xb0, 0x22, 0x90, 0x32, 0x2f, 0x40, 0x00, 0x83, 0x91, 0x5d, 0x3d, 0x6b, 0x26, 0x74, 0x78, 0x5e, 0x91, 0xe5, 0x06, 0xb4, 0x6c, 0x34, 0x4c, 0x08, 0xbb, 0xf9, 0x54, 0x90, 0x49, 0x32, 0xc5, 0x44},
+			false,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0xb0, 0xfd, 0xa1, 0x8f, 0x60, 0x53, 0xf2, 0x4a, 0x7e, 0x05, 0x2c, 0xbf, 0x58, 0x83, 0x6e, 0xa3, 0x23, 0x3e, 0x79, 0x87, 0xc3, 0x8c, 0xcb, 0x80, 0x2d, 0xde, 0x4c, 0x8c, 0x77, 0x90, 0x41, 0x4b},
+			false,
+		},
+		{
+			[32]byte{0x18, 0x44, 0xaf, 0x5a, 0x11, 0xbc, 0xa9, 0xf4, 0xa6, 0xa1, 0xf8, 0x9e, 0x8c, 0x24, 0x11, 0x0c, 0x0b, 0x4a, 0xf4, 0x22, 0x15, 0xc2, 0x67, 0x0c, 0x68, 0x66, 0x03, 0xd2, 0x50, 0x44, 0x3f, 0x26},
+			[32]byte{0xd9, 0xa2, 0xfc, 0xfd, 0x5b, 0xbb, 0x2b, 0x6d, 0x83, 0x21, 0xd2, 0xc2, 0xe0, 0x2f, 0xc0, 0x28, 0x74, 0x1e, 0xa7, 0x01, 0x94, 0xfa, 0xb8, 0xd2, 0x53, 0xd5, 0x31, 0xe3, 0xe7, 0xe5, 0x7f, 0x08},
+			[32]byte{0x00, 0xab, 0xc9, 0x71, 0xb5, 0x37, 0xe6, 0x84, 0xd8, 0x22, 0x90, 0x4e, 0x26, 0x84, 0x53, 0xe1, 0x91, 0xec, 0x17, 0x6e, 0xa1, 0xb7, 0x0d, 0xb3, 0x5b, 0x37, 0x03, 0xb4, 0x52, 0x38, 0x53, 0x41},
+			true,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0x08, 0x38, 0x73, 0x29, 0x43, 0xbb, 0xad, 0xa2, 0x84, 0x40, 0xda, 0x27, 0x42, 0xc4, 0x05, 0x95, 0xd0, 0x7d, 0x47, 0x76, 0x87, 0x8f, 0xc5, 0xa9, 0x42, 0xfa, 0xea, 0x77, 0x42, 0x9d, 0x2d, 0x62},
+			false,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0x70, 0x6d, 0x2c, 0x88, 0x78, 0x1b, 0x60, 0x80, 0x8a, 0xaf, 0x82, 0x98, 0xde, 0xfe, 0x19, 0x54, 0xcc, 0x11, 0x2f, 0x50, 0x50, 0x8c, 0x81, 0xfe, 0xed, 0x64, 0xdb, 0x66, 0x39, 0xcc, 0x1c, 0x7a},
+			false,
+		},
+		{
+			[32]byte{0xd8, 0x8b, 0x32, 0xdc, 0xf1, 0x98, 0x2e, 0x3e, 0x11, 0x99, 0xe7, 0x5c, 0x0b, 0x78, 0x6f, 0x4e, 0xec, 0x11, 0xbb, 0x55, 0xcb, 0x64, 0xce, 0xc5, 0x03, 0xcd, 0x70, 0xea, 0x95, 0x9f, 0x8a, 0x10},
+			[32]byte{0x47, 0x54, 0xd8, 0xf2, 0xfd, 0xa9, 0xe3, 0xb3, 0xa5, 0xc2, 0x86, 0xc5, 0x36, 0x87, 0x7e, 0x45, 0x68, 0x57, 0xde, 0xf4, 0xd5, 0xfb, 0x49, 0xfc, 0xbf, 0x6f, 0x2f, 0x81, 0xf9, 0xe8, 0xd9, 0x23},
+			[32]byte{0x60, 0xe4, 0x7b, 0x67, 0x61, 0xda, 0xa7, 0x81, 0xce, 0x2b, 0xc6, 0xd6, 0xca, 0xd6, 0x41, 0x35, 0xc8, 0x80, 0x46, 0x4d, 0x74, 0x4d, 0xfb, 0x6b, 0x83, 0xe4, 0xac, 0x9b, 0xf1, 0xb6, 0xbd, 0x7f},
+			true,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0x28, 0xae, 0x07, 0xef, 0x5f, 0xe0, 0x1c, 0xd5, 0x4c, 0x69, 0xd1, 0xdc, 0xa1, 0x14, 0xad, 0x6c, 0x86, 0xbf, 0x52, 0x7a, 0x8b, 0xcb, 0xfe, 0xf0, 0x68, 0x9a, 0x83, 0xd8, 0x6f, 0x36, 0xac, 0x74},
+			false,
+		},
+		{
+			[32]byte{0x71, 0x27, 0x7e, 0xf1, 0xc6, 0xb9, 0x5e, 0xc5, 0xe1, 0xcb, 0xea, 0xba, 0x8f, 0x3d, 0x57, 0xf4, 0x87, 0x8c, 0x5e, 0xd1, 0x59, 0xdd, 0x9c, 0xfa, 0xb6, 0x21, 0x73, 0xfd, 0x15, 0x94, 0xd1, 0x70},
+			[32]byte{0x82, 0xd1, 0x70, 0xac, 0x60, 0x92, 0x83, 0x7a, 0xb7, 0xab, 0x55, 0x44, 0x40, 0x51, 0xce, 0xa0, 0x56, 0xc9, 0x35, 0x45, 0x3f, 0x3f, 0xae, 0x74, 0xdf, 0xca, 0x2d, 0x5d, 0x97, 0x37, 0xa1, 0x1b},
+			[32]byte{0x40, 0xda, 0x08, 0x13, 0xd0, 0xd7, 0xe9, 0xbb, 0xe8, 0xc4, 0x4c, 0xad, 0x8d, 0xec, 0x81, 0x5c, 0xa0, 0x40, 0x4a, 0xfd, 0xff, 0xe5, 0x4d, 0x57, 0x5e, 0x3f, 0x05, 0x0a, 0x98, 0xc4, 0x50, 0x79},
+			true,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0x40, 0x60, 0xca, 0x06, 0x86, 0xc6, 0x5d, 0xa9, 0x01, 0xf8, 0xdc, 0xb6, 0x3c, 0x2a, 0x28, 0x95, 0x39, 0x10, 0x54, 0x1a, 0x31, 0x60, 0x04, 0x69, 0x9d, 0x61, 0xd2, 0x24, 0x0d, 0x6e, 0xdb, 0x5f},
+			false,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0x28, 0x5f, 0xcd, 0x92, 0xec, 0xbe, 0x3e, 0x58, 0xa0, 0xa9, 0xa0, 0x74, 0x5e, 0x3d, 0x2e, 0xce, 0x8f, 0xe7, 0x41, 0xc7, 0xc8, 0x52, 0x88, 0xc7, 0x2c, 0x17, 0x32, 0x70, 0x5a, 0x55, 0xe0, 0x5a},
+			false,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0x68, 0x6b, 0x89, 0x7f, 0xbf, 0x61, 0x3d, 0x88, 0x81, 0x82, 0x73, 0x83, 0x68, 0xff, 0x30, 0x3d, 0xd4, 0x06, 0x76, 0x93, 0x06, 0xcf, 0x2f, 0x4f, 0x7c, 0x1c, 0x84, 0x40, 0xef, 0x68, 0x1f, 0x5a},
+			false,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0x60, 0x17, 0x34, 0x90, 0xc7, 0x76, 0x79, 0x13, 0x9a, 0x5d, 0xc7, 0xaf, 0xa7, 0xbd, 0x56, 0x24, 0xfc, 0xa6, 0x51, 0xca, 0xae, 0x4e, 0x06, 0x91, 0xcb, 0x07, 0xb9, 0x06, 0x4e, 0xa3, 0xde, 0x7f},
+			false,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0x98, 0x14, 0xc3, 0x73, 0x86, 0x67, 0x34, 0x6e, 0xe9, 0xd5, 0xf7, 0xc2, 0x60, 0x71, 0xe6, 0x72, 0x07, 0xce, 0x1c, 0x9a, 0xc8, 0x98, 0xd3, 0xc2, 0x01, 0x89, 0xb6, 0xaa, 0x73, 0xed, 0x70, 0x74},
+			false,
+		},
+		{
+			[32]byte{0x9a, 0x43, 0x23, 0xe3, 0x12, 0xe8, 0x8e, 0x6d, 0x6c, 0xc8, 0xc9, 0x4b, 0x3e, 0xc7, 0x6e, 0xed, 0x7d, 0x56, 0x6c, 0x3c, 0x8a, 0xd6, 0xf5, 0x03, 0x55, 0x6f, 0xb6, 0x11, 0x8e, 0xa8, 0x53, 0x12},
+			[32]byte{0x58, 0xb2, 0x75, 0xe2, 0xbe, 0xfc, 0xfe, 0xdd, 0xfc, 0x36, 0xc3, 0x7d, 0x8e, 0xca, 0xe5, 0xaa, 0x27, 0x76, 0x60, 0xcb, 0x52, 0x92, 0xba, 0xc2, 0x6e, 0xa9, 0xf2, 0x95, 0x4e, 0x78, 0x08, 0x24},
+			[32]byte{0xf0, 0xae, 0xb3, 0xcf, 0x6f, 0xf5, 0xc8, 0xdc, 0xf6, 0x74, 0xe7, 0x14, 0xdc, 0xfc, 0x29, 0xeb, 0x5f, 0x76, 0x89, 0x58, 0x14, 0x6c, 0x13, 0x89, 0xa7, 0x9f, 0x8a, 0x60, 0x6e, 0x99, 0x2c, 0x79},
+			true,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0xd0, 0xbc, 0xdc, 0xef, 0x6e, 0xd6, 0x6a, 0xe4, 0xf9, 0x5a, 0x45, 0x20, 0x1f, 0x13, 0x6f, 0xbc, 0x86, 0x51, 0xcd, 0xf9, 0xad, 0xc7, 0xa5, 0x81, 0x7c, 0x55, 0x87, 0x89, 0x06, 0x38, 0x33, 0x5b},
+			false,
+		},
+		{
+			[32]byte{},
+			[32]byte{},
+			[32]byte{0xe0, 0x2e, 0xd2, 0x9b, 0x82, 0x9b, 0xd4, 0x1d, 0xa9, 0x47, 0x92, 0x40, 0xfe, 0x5a, 0x51, 0xe7, 0xa3, 0xdf, 0x2e, 0x57, 0x7e, 0x77, 0x9a, 0x1d, 0x01, 0x8c, 0x63, 0x6d, 0xd4, 0x6b, 0x4b, 0x7e},
+			false,
+		},
+	}
+
+	var pk, repr [32]byte
+	for i, v := range vectors {
+		ok := scalarBaseMult(&pk, &repr, &v.sk)
+		if ok != v.ok {
+			t.Errorf("[%d]: %v != %v", i, ok, v.ok)
+			continue
+		}
+		if !ok {
+			continue
+		}
+		if !bytes.Equal(pk[:], v.pk[:]) {
+			t.Errorf("[%d]: Public key mismatch", i)
+			continue
+		}
+		if !bytes.Equal(repr[:], v.repr[:]) {
+			t.Errorf("[%d]: Representative mismatch", i)
+			continue
+		}
+	}
+}
+
+func TestElligator2Fuzz(t *testing.T) {
+	// Generate a bunch of keys, and ensure the representative actually
+	// corresponds to the private key.
+	var pk, repr, sk [32]byte
+	var pkRev [32]byte
+	var highByte byte
+	for i := 0; i < 1000; i++ {
+		if err := GenerateKey(rand.Reader, &pk, &repr, &sk); err != nil {
+			t.Fatalf("[%d]: Failed to generate key: %v", i, err)
+		}
+		highByte |= repr[31]
+
+		RepresentativeToPublicKey(&pkRev, &repr)
+		if !bytes.Equal(pkRev[:], pk[:]) {
+			t.Fatalf("[%d]: Reversed mapping failed.")
+		}
+	}
+
+	// Ensure that the upper 2 bits appear to be randomized.
+	//
+	// NB: Probabalistic, and can fail, but extremely unlikely.
+	if highByte&0x40 == 0 {
+		t.Errorf("The 2nd highest bit was 0 for all 1k iterations")
+	}
+	if highByte&0x80 == 0 {
+		t.Errorf("The highest bit was 0 for all 1k iterations")
+	}
+}

+ 35 - 0
framing/const.go

@@ -0,0 +1,35 @@
+// const.go - Various constants.
+// Copyright (C) 2015-2016  Yawning Angel.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package framing
+
+// The various commands for each message carried via the framing layer.  Each
+// command is 7 bits, with the most significant bit, signifying the direction.
+const (
+	CmdData = iota
+	CmdHandshake
+
+	CmdServer     = 0x80
+	CmdServerMask = 0x7f
+)
+
+const (
+	// MaxIPv4TcpSize is the typical Ethernet IPv4 TCP MSS.
+	MaxIPv4TcpSize = 1500 - (20 + 20)
+
+	// MaxIPv6TcpSize is the typical Ethernet IPv6 TCP MSS.
+	MaxIPv6TcpSize = 1500 - (40 + 20)
+)

+ 497 - 0
framing/tentp/tentp.go

@@ -0,0 +1,497 @@
+// tentp.go - Trivial Encrypted Network Transport Protocol
+// Copyright (C) 2015-2016  Yawning Angel.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// Package tentp implements the framing layer portion of the Trivial
+// Encrypted Network Transport Protocol, a lightweight XChaCha20 + Poly1305
+// based authentication/encryption protocol for streams with reliable-in-order
+// delivery semantics.
+//
+// All security properties are lost if multiple sessions re-use the
+// Encoder/Decoder keys, so don't do that.
+//
+// This implementation is somewhat different from the draft that Nick M. and I
+// worked on a while ago but the basic ideas and concepts are the same.
+package tentp
+
+import (
+	"encoding/binary"
+	"errors"
+	"io"
+
+	"git.schwanenlied.me/yawning/basket2.git/crypto"
+	"git.schwanenlied.me/yawning/basket2.git/framing"
+	"git.schwanenlied.me/yawning/chacha20.git"
+	"golang.org/x/crypto/poly1305"
+)
+
+const (
+	// KeySize is the size of a Encoder/Decoder key in bytes (56 bytes).
+	KeySize = chacha20.KeySize + chacha20.XNonceSize
+
+	// MaxPlaintextRecordSize is the maximum length of a message payload that
+	// can be sent per record.  (The length of payload + padding is also
+	// limited to this maximum value).
+	MaxPlaintextRecordSize = 16383
+
+	// MaxPaddingSize is the maximum length of padding that can be sent per
+	// record.  (The length of payload + padding is also limited to this
+	// maximum value).
+	MaxPaddingSize = 16383
+
+	// FramingOverhead is the amount of constant overhead incurred regardless
+	// of payload/padding length (24 bytes).
+	FramingOverhead = poly1305.TagSize + recordHeaderSize
+
+	// PayloadOverhead is the amount of *additional* overhead incurred when
+	// sending any payload/padding (16 bytes).
+	PayloadOverhead = poly1305.TagSize
+
+	// MaxIdealIPv4Size is the "ideal" maximum payload + padding for a single
+	// record for an IPv4 connection over Ethernet (1420 bytes).
+	MaxIdealIPv4Size = framing.MaxIPv4TcpSize - (FramingOverhead + PayloadOverhead)
+
+	// MaxIdealIPv6Size is the "ideal" maximum payload + padding for a single
+	// record for an IPv6 connection over Ethernet (1400 bytes).
+	MaxIdealIPv6Size = framing.MaxIPv6TcpSize - (FramingOverhead + PayloadOverhead)
+
+	tentpVersion     = 0x01
+	recordHeaderSize = 8
+	hdrOffset        = poly1305.TagSize
+	hdrNsendOffset   = hdrOffset - 8
+)
+
+var (
+	// ErrInvalidKeySize is the error returned when the key size is invalid.
+	ErrInvalidKeySize = errors.New("tentp: invalid key size")
+
+	// ErrMsgSize is the error returned when the message/pad size is invalid.
+	ErrMsgSize = errors.New("tentp: invalid msg/pad size")
+
+	// ErrSendSeqNr is the error returned when NSEND is exhausted.
+	ErrSendSeqNr = errors.New("tentp: out of send sequence space")
+
+	// ErrHdrSize is the error returned when the header size is invalid.
+	ErrHdrSize = errors.New("tentp: invalid hdr size")
+
+	// ErrDecoderState is the error returned when the decoder calls are made
+	// in the wrong order (caller bug).
+	ErrDecoderState = errors.New("tentp: decoder in invalid state")
+
+	// ErrInvalidTag is the error returned when the MAC verification fails.
+	ErrInvalidTag = errors.New("tentp: invalid tag")
+
+	// ErrProtocol is the error returned when the protocol invariants are
+	// violated by the peer. (Invalid version, invalid reserved fields).
+	ErrProtocol = errors.New("tentp: protocol invariant violation")
+
+	// ErrRecvSeqNr is the error returned when NRECV is exhausted.
+	ErrRecvSeqNr = errors.New("tentp: out of recv sequence space")
+
+	// ErrWasReset is the error returned when the Encoder/Decoder are called
+	// after the internal state has been obliterated.
+	ErrWasReset = errors.New("tentp: attempted encode/decode after Reset")
+)
+
+// recordKeys is the keying material derived from the Encoder/Decoder stream
+// cipher before the paylaod/padding is processed.  It is exactly 2 XChaCha20
+// blocks, and thus the payload/padding processing can take the fast path in
+// the underlying cipher implementation.
+type recordKeys struct {
+	hdrAuthKey    [32]byte
+	hdrKeyStream  [recordHeaderSize]byte
+	dataAuthKey   [32]byte
+	nextRecordKey [KeySize]byte
+}
+
+func (k *recordKeys) derive(c *chacha20.Cipher) {
+	c.KeyStream(k.hdrAuthKey[:])
+	c.KeyStream(k.hdrKeyStream[:])
+	c.KeyStream(k.dataAuthKey[:])
+	c.KeyStream(k.nextRecordKey[:])
+}
+
+func (k *recordKeys) xorHdrStream(dst, src []byte) {
+	if len(dst) < recordHeaderSize || len(src) < recordHeaderSize {
+		panic("invalid src/dst buffers when xoring key stream")
+	}
+	for i, v := range k.hdrKeyStream {
+		dst[i] = src[i] ^ v
+	}
+}
+
+func (k *recordKeys) reset() {
+	crypto.Memwipe(k.hdrAuthKey[:])
+	crypto.Memwipe(k.hdrKeyStream[:])
+	crypto.Memwipe(k.dataAuthKey[:])
+	crypto.Memwipe(k.nextRecordKey[:])
+}
+
+/* This is what the record header looks like, though it's assembled manually.
+type recordHeader struct {
+	version       byte   // 0x01 for this version.
+	cmd           byte
+	payloadLength uint16
+	paddingLength uint16
+	reserved      uint16 // Always 0x00 0x00.
+}
+*/
+
+// Encoder is a TENTP frame encoder instance.
+type Encoder struct {
+	cipher *chacha20.Cipher
+	keys   recordKeys
+	nSend  uint64
+	err    error
+}
+
+// Reset clears sensitive data from the Encoder's internal state and
+// irreversably invalidates the instance.
+func (e *Encoder) Reset() {
+	e.cipher.Reset()
+	e.keys.reset()
+	if e.err == nil {
+		// Preserve the previous error...
+		e.err = ErrWasReset
+	}
+}
+
+// EncodeRecord encodes a message with command cmd, message msg, and padLen
+// bytes of padding, and returns the encrypted/authenticated ciphertext.
+func (e *Encoder) EncodeRecord(cmd byte, msg []byte, padLen int) ([]byte, error) {
+	if e.err != nil {
+		// Certain errors render the encoder permanently unusable.
+		return nil, e.err
+	}
+	if len(msg) > MaxPlaintextRecordSize || padLen > MaxPaddingSize || padLen < 0 {
+		return nil, ErrMsgSize
+	}
+	if len(msg)+padLen > MaxPlaintextRecordSize {
+		// Can't overflow, previous check will fail.
+		return nil, ErrMsgSize
+	}
+
+	// Calculate the encoded length, and preallocate the buffer.
+	encodedLen := FramingOverhead
+	if len(msg)+padLen > 0 {
+		encodedLen += PayloadOverhead + len(msg) + padLen
+	}
+	buf := make([]byte, encodedLen)
+
+	// Generate all the per-record keys.
+	e.keys.derive(e.cipher)
+	defer e.keys.reset()
+
+	// Build the encrypted record, starting from the payload, and working
+	// backwards.  This lets us temporarily emplace NSEND in the encoded
+	// buffer where the tag will live, and saves a malloc since the
+	// golang.org poly1305 doesn't have init/update/final semantics.
+	var tmpTag [poly1305.TagSize]byte // The API is dumb in other ways...
+
+	// Encrypt/auth the data.
+	if len(msg)+padLen > 0 {
+		const (
+			msgOffset      = FramingOverhead + PayloadOverhead
+			msgNsendOffset = msgOffset - 8
+		)
+
+		binary.BigEndian.PutUint64(buf[msgNsendOffset:], e.nSend)
+		e.cipher.XORKeyStream(buf[msgOffset:], msg)
+		if padLen > 0 {
+			e.cipher.KeyStream(buf[msgOffset+len(msg):])
+		}
+		poly1305.Sum(&tmpTag, buf[msgNsendOffset:], &e.keys.dataAuthKey)
+		copy(buf[FramingOverhead:], tmpTag[:])
+	}
+
+	// Build/encrypt/auth the header.
+	const (
+		hdrVerOffset        = hdrOffset
+		hdrCmdOffset        = hdrVerOffset + 1
+		hdrPayloadLenOffset = hdrCmdOffset + 1
+		hdrPaddingLenOffset = hdrPayloadLenOffset + 2
+	)
+	binary.BigEndian.PutUint64(buf[hdrNsendOffset:], e.nSend)
+	buf[hdrVerOffset] = tentpVersion
+	buf[hdrCmdOffset] = cmd
+	binary.BigEndian.PutUint16(buf[hdrPayloadLenOffset:], uint16(len(msg)))
+	binary.BigEndian.PutUint16(buf[hdrPaddingLenOffset:], uint16(padLen))
+
+	e.keys.xorHdrStream(buf[hdrOffset:], buf[hdrOffset:])
+	poly1305.Sum(&tmpTag, buf[hdrNsendOffset:FramingOverhead], &e.keys.hdrAuthKey)
+	copy(buf[:FramingOverhead], tmpTag[:])
+
+	// Advance the state forward.
+	if err := e.cipher.ReKey(e.keys.nextRecordKey[:chacha20.KeySize], e.keys.nextRecordKey[chacha20.KeySize:]); err != nil {
+		// Failure to rekey the stream cipher in preparation of the next
+		// record is catastrophic.
+		e.err = err
+		return nil, e.err
+	}
+	e.nSend++ // Increment the send counter.
+	if e.nSend == 0 {
+		// Out of nSends, this is the 2^64th packet.  The next send should
+		// fail.
+		e.err = ErrSendSeqNr
+	}
+	return buf, nil
+}
+
+// NewEncoder creates a new Encoder instance with the specificed key.
+func NewEncoder(key []byte) (*Encoder, error) {
+	if len(key) != KeySize {
+		return nil, ErrInvalidKeySize
+	}
+
+	var err error
+	e := new(Encoder)
+	e.cipher, err = chacha20.NewCipher(key[:chacha20.KeySize], key[chacha20.KeySize:])
+	if err != nil {
+		return nil, err
+	}
+	return e, nil
+}
+
+// NewEncoderFromKDF creates a new Encoder instance with material read from a
+// KDF.  This is intended to be used with the golang.org/x/crypto SHAKE
+// implementation.
+func NewEncoderFromKDF(kdf io.Reader) (*Encoder, error) {
+	var key [KeySize]byte
+	defer crypto.Memwipe(key[:])
+	if _, err := io.ReadFull(kdf, key[:]); err != nil {
+		return nil, err
+	}
+	return NewEncoder(key[:])
+}
+
+type decoderState int
+
+const (
+	decoderStateHdr decoderState = iota
+	decoderStateMsg
+)
+
+// Decoder is a TENTP frame decoder instance.
+type Decoder struct {
+	cipher *chacha20.Cipher
+	keys   recordKeys
+	nRecv  uint64
+	err    error
+
+	state          decoderState
+	wantPayloadLen int
+	wantPaddingLen int
+}
+
+// Reset clears sensitive data from the Decoder's internal state and
+// irreversably invalidates the instance.
+func (d *Decoder) Reset() {
+	d.cipher.Reset()
+	d.keys.reset()
+	if d.err == nil {
+		// Preserve the previous error...
+		d.err = ErrWasReset
+	}
+}
+
+func (d *Decoder) advanceState() error {
+	if err := d.cipher.ReKey(d.keys.nextRecordKey[:chacha20.KeySize], d.keys.nextRecordKey[chacha20.KeySize:]); err != nil {
+		d.err = err
+		return d.err
+	}
+	d.keys.reset()
+	d.nRecv++ // Increment the recv counter.
+	if d.nRecv == 0 {
+		// Out of nRecvs, this is the 2^64th packet.  The next send should
+		// fail.
+		d.err = ErrRecvSeqNr
+	}
+	d.wantPayloadLen = 0
+	d.wantPaddingLen = 0
+	d.state = decoderStateHdr
+	return nil
+}
+
+// DecodeRecordHdr decodes a given FramingOverhead length byte slice, and
+// returns the command, and expected payload/padding ciphertext length
+// (including overhead) that must be passed to DecodeRecordBody.  If want
+// is 0, the call to DecodeRecordBody may be omitted.
+func (d *Decoder) DecodeRecordHdr(encHdr []byte) (cmd byte, want int, err error) {
+	if d.err != nil {
+		// Certain errors render the decoder permanently unusable.
+		return 0, 0, d.err
+	}
+
+	// Actually, all errors render the decoder permanently unsuable since
+	// it's either an attack or a bug.
+	defer func() {
+		if err != nil {
+			d.err = err
+		}
+	}()
+
+	if len(encHdr) != FramingOverhead {
+		return 0, 0, ErrHdrSize
+	}
+	if d.state != decoderStateHdr {
+		return 0, 0, ErrDecoderState
+	}
+
+	// Generate all the per-record keys.
+	d.keys.derive(d.cipher)
+	defer func() {
+		if err != nil {
+			d.keys.reset()
+		}
+	}()
+
+	// Authenticate/decrypt the header.
+	var recvdTag [poly1305.TagSize]byte
+	copy(recvdTag[:], encHdr[:])
+	binary.BigEndian.PutUint64(encHdr[hdrNsendOffset:], d.nRecv)
+	if !poly1305.Verify(&recvdTag, encHdr[hdrNsendOffset:], &d.keys.hdrAuthKey) {
+		return 0, 0, ErrInvalidTag
+	}
+	var hdr [recordHeaderSize]byte
+	d.keys.xorHdrStream(hdr[:], encHdr[hdrOffset:])
+
+	// Deserialize/validate the header.
+	const (
+		hdrVerOffset        = 0
+		hdrCmdOffset        = hdrVerOffset + 1
+		hdrPayloadLenOffset = hdrCmdOffset + 1
+		hdrPaddingLenOffset = hdrPayloadLenOffset + 2
+		hdrReservedOffset   = hdrPaddingLenOffset + 2
+	)
+	if hdr[hdrVerOffset] != tentpVersion {
+		return 0, 0, ErrProtocol
+	}
+	cmd = hdr[hdrCmdOffset]
+	d.wantPayloadLen = int(binary.BigEndian.Uint16(hdr[hdrPayloadLenOffset:]))
+	d.wantPaddingLen = int(binary.BigEndian.Uint16(hdr[hdrPaddingLenOffset:]))
+	if d.wantPayloadLen+d.wantPaddingLen > MaxPlaintextRecordSize {
+		return 0, 0, ErrMsgSize
+	}
+	if binary.BigEndian.Uint16(hdr[hdrReservedOffset:]) != 0 {
+		return 0, 0, ErrProtocol
+	}
+
+	// Figure out if we should expect the next header, or payload/padding,
+	// and advance the internal state as appropriate.
+	want = d.wantPayloadLen + d.wantPaddingLen
+	if want == 0 {
+		if err = d.advanceState(); err != nil {
+			return 0, 0, err
+		}
+	} else {
+		d.state = decoderStateMsg
+		want += PayloadOverhead
+	}
+
+	return
+}
+
+// DecodeRecordBody decodes a encrypted/authenticated record payload + padding
+// message and returns the payload plaintext.  It is possible, and perfectly
+// valid for buf to be nil.
+func (d *Decoder) DecodeRecordBody(encMsg []byte) (buf []byte, err error) {
+	defer func() {
+		if err != nil {
+			d.keys.reset()
+		}
+	}()
+	if d.err != nil {
+		return nil, d.err
+	}
+	if d.state == decoderStateHdr {
+		// DecodeRecordHdr returned want = 0, but they called DecodeRecordBody
+		// anyway.  Fastpath out and just return, there was no payload.
+		return nil, nil
+	} else if d.state != decoderStateMsg {
+		return nil, ErrDecoderState
+	}
+
+	if len(encMsg) != PayloadOverhead+d.wantPayloadLen+d.wantPaddingLen {
+		d.err = ErrMsgSize
+		return nil, ErrMsgSize
+	}
+
+	// Authenticate the payload + padding.
+	const (
+		msgOffset      = PayloadOverhead
+		msgNsendOffset = msgOffset - 8
+	)
+	var recvdTag [poly1305.TagSize]byte
+	copy(recvdTag[:], encMsg[:])
+	binary.BigEndian.PutUint64(encMsg[msgNsendOffset:], d.nRecv)
+	if !poly1305.Verify(&recvdTag, encMsg[msgNsendOffset:], &d.keys.dataAuthKey) {
+		d.err = ErrInvalidTag
+		return nil, d.err
+	}
+
+	// Decrypt the payload + padding.
+	decodedLen := len(encMsg) - PayloadOverhead
+	buf = make([]byte, decodedLen)
+	d.cipher.XORKeyStream(buf, encMsg[msgOffset:])
+	if d.wantPaddingLen > 0 {
+		// Ensure that all the padding bytes are 0.  Technically unneeded, but
+		// this check makes it harder (but not impossible) to use the padding
+		// as a subliminal channel.
+		paddingOffset := decodedLen - d.wantPaddingLen
+		if !crypto.MemIsZero(buf[paddingOffset:]) {
+			d.err = ErrProtocol
+			return nil, d.err
+		}
+	}
+	buf = buf[:d.wantPayloadLen] // Truncate off the padding.
+	if len(buf) == 0 {
+		buf = nil
+	}
+
+	// Ready to receive the next header.
+	if err := d.advanceState(); err != nil {
+		return nil, err
+	}
+
+	return buf, nil
+}
+
+// NewDecoder creates a new Decoder instance with the specificed key.
+func NewDecoder(key []byte) (*Decoder, error) {
+	if len(key) != KeySize {
+		return nil, ErrInvalidKeySize
+	}
+
+	var err error
+	d := new(Decoder)
+	d.cipher, err = chacha20.NewCipher(key[:chacha20.KeySize], key[chacha20.KeySize:])
+	if err != nil {
+		return nil, err
+	}
+	d.state = decoderStateHdr
+	return d, nil
+}
+
+// NewDecoderFromKDF creates a new Dcoder instance with material read from a
+// KDF.  This is intended to be used with the golang.org/x/crypto SHAKE
+// implementation.
+func NewDecoderFromKDF(kdf io.Reader) (*Decoder, error) {
+	var key [KeySize]byte
+	defer crypto.Memwipe(key[:])
+	if _, err := io.ReadFull(kdf, key[:]); err != nil {
+		return nil, err
+	}
+	return NewDecoder(key[:])
+}

+ 197 - 0
framing/tentp/tentp_test.go

@@ -0,0 +1,197 @@
+// tentp_test.go - Trivial Encrypted Network Transport Protocol tests
+// Copyright (C) 2016  Yawning Angel.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package tentp
+
+import (
+	"bytes"
+	"crypto/rand"
+	"io"
+	mrand "math/rand"
+	"testing"
+	"time"
+)
+
+func TestTentpSmoke(t *testing.T) {
+	// Yes, I'm time seeding a non-CSPRNG, so I can randomize the pad lengths
+	// I test.  Deal with it.
+	mrand.Seed(time.Now().UnixNano())
+
+	var key [KeySize]byte
+	if _, err := io.ReadFull(rand.Reader, key[:]); err != nil {
+		t.Fatalf("failed to generate random key: %v", err)
+	}
+	enc, err := NewEncoder(key[:])
+	if err != nil {
+		t.Fatalf("failed to initialize encoder: %v", err)
+	}
+	dec, err := NewDecoder(key[:])
+	if err != nil {
+		t.Fatalf("failed to initialize decoder: %v", err)
+	}
+
+	// The full version of this takes... quite a while (~2 mins on an i5).
+	padIters := 10
+	if testing.Short() {
+		padIters = 2
+	}
+
+	for i := 0; i <= MaxPlaintextRecordSize; i++ {
+		var sndCmd [1]byte
+		if _, err := io.ReadFull(rand.Reader, sndCmd[:]); err != nil {
+			t.Fatalf("failed to generate command: %v", err)
+		}
+
+		// For each size, randomize the padding.
+	padLoop:
+		for j := 0; j < padIters; j++ {
+			padLen := 0
+			if i != MaxPlaintextRecordSize && j != 0 {
+				// Production code should use a CSPRNG, naturally...
+				padLen = mrand.Intn(MaxPlaintextRecordSize - i)
+			}
+
+			buf := make([]byte, i)
+			if _, err := io.ReadFull(rand.Reader, buf[:]); err != nil {
+				t.Fatalf("failed to generate payload: %v", err)
+			}
+
+			encoded, err := enc.EncodeRecord(sndCmd[0], buf, padLen)
+			if err != nil {
+				t.Fatalf("[%d]: failed to encode: %v", i, err)
+			}
+
+			rxCmd, want, err := dec.DecodeRecordHdr(encoded[:FramingOverhead])
+			if err != nil {
+				t.Fatalf("[%d]: failed to decode hdr: %v", i, err)
+			}
+			if want != len(encoded)-FramingOverhead {
+				t.Fatalf("[%d]: unexpected want length: %v", i, want)
+			}
+			if rxCmd != sndCmd[0] {
+				t.Fatalf("[%d]: snd/recv cmd mismatch", i)
+			}
+			rxMsg, err := dec.DecodeRecordBody(encoded[FramingOverhead:])
+			if err != nil {
+				t.Fatalf("[%d]: failed to decode body: %v", i, err)
+			}
+			if !bytes.Equal(rxMsg, buf) {
+				t.Fatalf("[%d]: payload mismatch", i)
+			}
+			if i == MaxPlaintextRecordSize {
+				// No point in altering the padding here.
+				break padLoop
+			}
+		}
+	}
+}
+
+func doBenchN(b *testing.B, n int, encode bool) {
+	var key [KeySize]byte
+	if _, err := io.ReadFull(rand.Reader, key[:]); err != nil {
+		b.Fatalf("failed to generate random key: %v", err)
+	}
+	enc, err := NewEncoder(key[:])
+	if err != nil {
+		b.Fatalf("failed to initialize encoder: %v", err)
+	}
+	dec, err := NewDecoder(key[:])
+	if err != nil {
+		b.Fatalf("failed to initialize decoder: %v", err)
+	}
+	s := make([]byte, n)
+	if _, err := io.ReadFull(rand.Reader, s); err != nil {
+		b.Fatalf("failed to generate payload: %v", err)
+	}
+
+	b.SetBytes(int64(n))
+	b.ResetTimer()
+	b.StopTimer()
+	for i := 0; i < b.N; i++ {
+		if encode {
+			b.StartTimer()
+		}
+
+		encoded, err := enc.EncodeRecord(0xa5, s, 0)
+		if err != nil {
+			b.Fatalf("failed to encode: %v", err)
+		}
+
+		if encode {
+			b.StopTimer()
+		} else {
+			b.StartTimer()
+		}
+
+		rxCmd, want, err := dec.DecodeRecordHdr(encoded[:FramingOverhead])
+		if err != nil {
+			b.Fatalf("failed to decode: %v", err)
+		}
+
+		// These checks get timed, but they're cheap, so whatever.
+		if want != len(encoded)-FramingOverhead {
+			b.Fatalf("unexpected want length: %v", want)
+		}
+		if rxCmd != 0xa5 {
+			b.Fatalf("snd/recv cmd mismatch")
+		}
+
+		rxMsg, err := dec.DecodeRecordBody(encoded[FramingOverhead:])
+		if err != nil {
+			b.Fatalf("failed to decode body: %v", err)
+		}
+
+		if !encode {
+			b.StopTimer()
+		}
+
+		if !bytes.Equal(rxMsg, s) {
+			b.Fatalf("payload mismatch")
+		}
+	}
+}
+
+func BenchmarkTentp_Encode_64(b *testing.B) {
+	doBenchN(b, 64, true)
+}
+
+func BenchmarkTentp_Decode_64(b *testing.B) {
+	doBenchN(b, 64, false)
+}
+
+func BenchmarkTentp_Encode_512(b *testing.B) {
+	doBenchN(b, 512, true)
+}
+
+func BenchmarkTentp_Decode_512(b *testing.B) {
+	doBenchN(b, 512, false)
+}
+
+func BenchmarkTentp_Encode_1500(b *testing.B) {
+	doBenchN(b, 1500, true)
+}
+
+func BenchmarkTentp_Decode_1500(b *testing.B) {
+	doBenchN(b, 1500, false)
+}
+
+func BenchmarkTentp_Encode_16383(b *testing.B) {
+	doBenchN(b, 16383, true)
+}
+
+func BenchmarkTentp_Decode_16383(b *testing.B) {
+	doBenchN(b, 16383, false)
+}

+ 341 - 0
handshake/obfuscation.go

@@ -0,0 +1,341 @@
+// obfuscation.go - Handshake message obfsucator.
+// Copyright (C) 2015-2016 Yawning Angel.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package handshake
+
+import (
+	"crypto/rand"
+	"crypto/subtle"
+	"encoding/binary"
+	"errors"
+	"io"
+	"time"
+
+	"git.schwanenlied.me/yawning/basket2.git/crypto"
+	"git.schwanenlied.me/yawning/basket2.git/ext/elligator2"
+	"git.schwanenlied.me/yawning/basket2.git/framing"
+	"git.schwanenlied.me/yawning/basket2.git/framing/tentp"
+
+	"golang.org/x/crypto/curve25519"
+	"golang.org/x/crypto/sha3"
+)
+
+const (
+	obfsClientOverhead = 32 + 32 + tentp.FramingOverhead + tentp.PayloadOverhead
+	obfsServerOverhead = tentp.FramingOverhead + tentp.PayloadOverhead
+)
+
+var (
+	obfsMarkTweak = []byte("basket2-obfs-mark-tweak")
+	obfsKdfTweak  = []byte("basket2-obfs-kdf-tweak")
+
+	ErrInvalidPoint = errors.New("obfs: invalid point")
+	ErrInvalidCmd   = errors.New("obfs: invalid command")
+	ErrInvalidMark  = errors.New("obfs: client send invalid mark")
+	ErrNoPayload    = errors.New("obfs: no handshake paylaod")
+)
+
+// clientObfsCtx is the client handshake obfuscator state.
+type clientObfsCtx struct {
+	serverPublicKey [32]byte
+
+	privKey      [32]byte
+	repr         [32]byte
+	masterSecret [32]byte
+}
+
+// handshake obfuscates and transmits the request message msg, and returns the
+// decrypted peer response.  It is the caller's responsibility to pre-configure
+// connection timeouts as appropriate.
+func (o *clientObfsCtx) handshake(rw io.ReadWriter, msg []byte, padLen int) ([]byte, error) {
+	// Stash the number of hours since the epoch (fixed for the duration of
+	// this handshake).
+	var epochHour [8]byte
+	eh := getEpochHour()
+	binary.BigEndian.PutUint64(epochHour[:], eh)
+
+	// Craft the request blob:
+	//  uint8_t representative[32]
+	//  uint8_t mark[32] (SHA3-256(markTweak | serverPk | repr | epochHour)
+	//  uint8_t cipherText[] (TENTP(reqKey, cmd, msg, padLen))
+	//
+	// Note: The cipherText is structured such that the decoder can determine
+	// the length.
+	reqBlob := make([]byte, 0, obfsClientOverhead+len(msg)+padLen)
+	reqBlob = append(reqBlob, o.repr[:]...)
+	markHash := sha3.New256()
+	markHash.Write(obfsMarkTweak[:])
+	markHash.Write(o.serverPublicKey[:])
+	markHash.Write(o.repr[:])
+	markHash.Write(epochHour[:])
+	reqBlob = markHash.Sum(reqBlob)
+
+	// Derive the reqKey/respKey used for the handshake.
+	keyHash := sha3.NewShake256()
+	defer keyHash.Reset()
+	keyHash.Write(obfsKdfTweak)
+	keyHash.Write(o.masterSecret[:])
+	keyHash.Write(reqBlob[32:64]) // Include the mark in the KDF input.
+
+	// Initialize the frame encoder and decoder used for the duration of
+	// the handshake process, and frame the handshake record.
+	enc, err := tentp.NewEncoderFromKDF(keyHash)
+	if err != nil {
+		return nil, err
+	}
+	defer enc.Reset()
+	dec, err := tentp.NewDecoderFromKDF(keyHash)
+	if err != nil {
+		return nil, err
+	}
+	defer dec.Reset()
+	frame, err := enc.EncodeRecord(framing.CmdHandshake, msg, padLen)
+	if err != nil {
+		return nil, err
+	}
+	reqBlob = append(reqBlob, frame...)
+
+	// Send the request blob.
+	if _, err := rw.Write(reqBlob[:]); err != nil {
+		return nil, err
+	}
+
+	// Receive/Decode the peer's response TENTP header.
+	var respHdr [tentp.FramingOverhead]byte
+	if _, err := io.ReadFull(rw, respHdr[:]); err != nil {
+		return nil, err
+	}
+	respCmd, want, err := dec.DecodeRecordHdr(respHdr[:])
+	if err != nil {
+		return nil, err
+	}
+	if respCmd != framing.CmdServer|framing.CmdHandshake {
+		return nil, ErrInvalidCmd
+	}
+	if want == 0 {
+		// This is technically valid, but is stupid, so disallow it.
+		return nil, ErrNoPayload
+	}
+
+	// Receive/Decode the peer's response payload body.
+	//
+	// By virtue of this succeding, the server can be considered authenticated
+	// as they know the private component of serverPublicKey.
+	//
+	// Note: ~128 bits classical security for the authentication.  You lose
+	// if they have a quantum computer and are mounting a man in the middle
+	// attack.  Likewise, the contents of both obfuscated ciphertexts should
+	// be assumed not to be secret assuming a quantum computer is in the
+	// picture, or if the servers static obfuscation/authentication key is
+	// compromised.
+	//
+	// There's nothing stopping someone from adding extra authentication
+	// (eg: the encrypted ciphertexts could transmit a SIGMA-I key exchange)
+	// but that is beyond the scope of what this layer provides.
+	respBody := make([]byte, want)
+	if _, err := io.ReadFull(rw, respBody); err != nil {
+		return nil, err
+	}
+	return dec.DecodeRecordBody(respBody)
+}
+
+// reset sanitizes private values from the client handshake obfuscator state.
+func (o *clientObfsCtx) reset() {
+	crypto.Memwipe(o.privKey[:])
+	crypto.Memwipe(o.masterSecret[:])
+}
+
+// newClientObfs creates a new client side handshake obfuscator instance, for
+// bootstrapping communication with a given peer, identified by a public key.
+//
+// Note: Due to the rejection sampling in Elligator 2 keypair generation, this
+// should be done offline.  The timing variation only leaks information about
+// the obfuscation method, and does not compromise secrecy or integrity.
+func newClientObfs(serverPublicKey *[32]byte) (*clientObfsCtx, error) {
+	o := new(clientObfsCtx)
+	copy(o.serverPublicKey[:], serverPublicKey[:])
+
+	// Generate a Curve25519 keypair, along with an Elligator 2 uniform
+	// random representative of the public key.
+	var publicKey [32]byte // Don't need our public key past validation.
+	if err := elligator2.GenerateKey(rand.Reader, &publicKey, &o.repr, &o.privKey); err != nil {
+		return nil, err
+	}
+	if crypto.MemIsZero(publicKey[:]) {
+		return nil, ErrInvalidPoint
+	}
+
+	// Calculate a shared secret with our ephemeral key, and the server's
+	// long term public key.
+	curve25519.ScalarMult(&o.masterSecret, &o.privKey, &o.serverPublicKey)
+	if crypto.MemIsZero(o.masterSecret[:]) {
+		return nil, ErrInvalidPoint
+	}
+
+	return o, nil
+}
+
+type serverObfsKeypair struct {
+	privateKey [32]byte
+	publicKey  [32]byte
+}
+
+type serverObfsCtx struct {
+	clientPublicKey [32]byte
+
+	keypair      *serverObfsKeypair
+	masterSecret [32]byte
+	keyHash      sha3.ShakeHash
+}
+
+// reset sanitizes private values from the server handshake obfuscator state.
+func (o *serverObfsCtx) reset() {
+	o.keypair = nil // It's a pointer. >.>
+	crypto.Memwipe(o.masterSecret[:])
+	if o.keyHash != nil {
+		o.keyHash.Reset()
+	}
+}
+
+func (o *serverObfsCtx) recvHandshakeReq(rw io.ReadWriter) ([]byte, error) {
+	// Read the client representative.
+	var repr [32]byte
+	if _, err := io.ReadFull(rw, repr[:]); err != nil {
+		return nil, err
+	}
+
+	// Read/Validate the client mark, allowing for +- 1h clock difference
+	// between the client and server.
+	var mark [32]byte
+	if _, err := io.ReadFull(rw, mark[:]); err != nil {
+		return nil, err
+	}
+	eh := getEpochHour()
+	markOk := false
+	for _, v := range []uint64{eh - 1, eh, eh + 1} {
+		// This is kind of expensive. :(
+		var epochHour [8]byte
+		binary.BigEndian.PutUint64(epochHour[:], v)
+
+		markHash := sha3.New256()
+		markHash.Write(obfsMarkTweak[:])
+		markHash.Write(o.keypair.publicKey[:])
+		markHash.Write(repr[:])
+		markHash.Write(epochHour[:])
+
+		derivedMark := markHash.Sum(nil)
+		if subtle.ConstantTimeCompare(derivedMark[:], mark[:]) == 1 {
+			markOk = true
+		}
+	}
+	if !markOk {
+		// Invalid mark, either the clock skew is too large or the peer
+		// isn't supposed to be connecting to us.
+		return nil, ErrInvalidMark
+	}
+
+	// XXX: Replay check the mark.
+
+	// Calculate the shared secret, with the client's representative and our
+	// long term private key.
+	elligator2.RepresentativeToPublicKey(&o.clientPublicKey, &repr)
+	if crypto.MemIsZero(o.clientPublicKey[:]) {
+		return nil, ErrInvalidPoint
+	}
+	curve25519.ScalarMult(&o.masterSecret, &o.keypair.privateKey, &o.clientPublicKey)
+	if crypto.MemIsZero(o.masterSecret[:]) {
+		return nil, ErrInvalidPoint
+	}
+
+	// Derive the handshake symmetric keys, and initialize the frame decoder
+	// used to decode the handshake request.  As this function is split,
+	// initializing/keying the frame encoder happens when the response is
+	// sent.
+	o.keyHash = sha3.NewShake256()
+	o.keyHash.Write(obfsKdfTweak)
+	o.keyHash.Write(o.masterSecret[:])
+	o.keyHash.Write(mark[:]) // Include the mark in the KDF input.
+	dec, err := tentp.NewDecoderFromKDF(o.keyHash)
+	if err != nil {
+		return nil, err
+	}
+	defer dec.Reset()
+
+	// Read/Decode client request header.
+	var reqHdr [tentp.FramingOverhead]byte
+	if _, err := io.ReadFull(rw, reqHdr[:]); err != nil {
+		return nil, err
+	}
+	reqCmd, want, err := dec.DecodeRecordHdr(reqHdr[:])
+	if err != nil {
+		return nil, err
+	}
+	if reqCmd != framing.CmdHandshake {
+		return nil, ErrInvalidCmd
+	}
+	if want == 0 {
+		// This is technically valid, but is stupid, so disallow it.
+		return nil, ErrNoPayload
+	}
+
+	// Read/Decode client request body.
+	reqBody := make([]byte, want)
+	if _, err := io.ReadFull(rw, reqBody); err != nil {
+		return nil, err
+	}
+	return dec.DecodeRecordBody(reqBody)
+}
+
+func (o *serverObfsCtx) sendHandshakeResp(rw io.ReadWriter, msg []byte, padLen int) error {
+	// Initialize the frame encoder used to send the response.
+	defer o.keyHash.Reset()
+	enc, err := tentp.NewEncoderFromKDF(o.keyHash)
+	if err != nil {
+		return err
+	}
+	defer enc.Reset()
+
+	// Encode the response message.
+	frame, err := enc.EncodeRecord(framing.CmdServer|framing.CmdHandshake, msg, padLen)
+	if err != nil {
+		return err
+	}
+
+	// Send the response blob.
+	if _, err := rw.Write(frame); err != nil {
+		return err
+	}
+	return nil
+}
+
+func newServerObfs(staticObfsKeypair *serverObfsKeypair) (*serverObfsCtx, error) {
+	o := new(serverObfsCtx)
+	o.keypair = staticObfsKeypair
+
+	if crypto.MemIsZero(o.keypair.publicKey[:]) {
+		return nil, ErrInvalidPoint
+	}
+	// The paranoid thing to do would be to validate that the public key
+	// actually comes from the private key, but this is an internal API,
+	// so just assume it's correct and save a scalar basepoint multiply.
+
+	return o, nil
+}
+
+// getEpochHour returns the number of hours since the UNIX epoch.
+func getEpochHour() uint64 {
+	return uint64(time.Now().Unix() / 3600)
+}

+ 114 - 0
handshake/obfuscation_test.go

@@ -0,0 +1,114 @@
+// obfuscation_test.go - Handshake message obfsucator tests.
+// Copyright (C) 2015-2016 Yawning Angel.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package handshake
+
+import (
+	"bytes"
+	"crypto/rand"
+	"fmt"
+	"io"
+	"net"
+	"sync"
+	"testing"
+
+	"golang.org/x/crypto/curve25519"
+)
+
+var (
+	smokeReqMsg  = []byte("It is the stillest words that bring on the storm.  Thoughts that come on doves' feet guide the world.")
+	smokeRespMsg = []byte("Everyone wants the same, everyone is the same: whoever feels different goes wilingly into the madhouse.")
+)
+
+func TestObfuscationSmoke(t *testing.T) {
+	// Generate Bob's long term identity keypair.
+	var bobKeys serverObfsKeypair
+	if _, err := io.ReadFull(rand.Reader, bobKeys.privateKey[:]); err != nil {
+		t.Fatalf("failed to generate bobSk: %v", err)
+	}
+	curve25519.ScalarBaseMult(&bobKeys.publicKey, &bobKeys.privateKey)
+
+	// Launch Alice and Bob's portions in their own go routines.
+	var wg sync.WaitGroup
+	aliceCh := make(chan error)
+	bobCh := make(chan error)
+	alicePipe, bobPipe := net.Pipe()
+	defer alicePipe.Close()
+	defer bobPipe.Close()
+	aliceTestFn := func() {
+		defer wg.Done()
+		if err := aliceSmokeTestFn(alicePipe, &bobKeys.publicKey); err != nil {
+			aliceCh <- err
+			alicePipe.Close()
+		}
+		return
+	}
+	bobTestFn := func() {
+		defer wg.Done()
+		if err := bobSmokeTestFn(bobPipe, &bobKeys); err != nil {
+			bobCh <- err
+			bobPipe.Close()
+		}
+		return
+	}
+	wg.Add(2)
+	go aliceTestFn()
+	go bobTestFn()
+
+	// Wait for the handshake to complete, collect/handle errors.
+	wg.Wait()
+	if len(aliceCh) > 0 {
+		err := <-aliceCh
+		t.Errorf("alice go routine failure: %v", err)
+	}
+	if len(bobCh) > 0 {
+		err := <-bobCh
+		t.Errorf("bob go routine faulure: %v", err)
+	}
+}
+
+func aliceSmokeTestFn(conn net.Conn, bobPk *[32]byte) error {
+	obfs, err := newClientObfs(bobPk)
+	if err != nil {
+		return err
+	}
+	resp, err := obfs.handshake(conn, smokeReqMsg, 0)
+	if err != nil {
+		return err
+	}
+	if !bytes.Equal(smokeRespMsg, resp) {
+		return fmt.Errorf("response mismatch")
+	}
+	return nil
+}
+
+func bobSmokeTestFn(conn net.Conn, keys *serverObfsKeypair) error {
+	obfs, err := newServerObfs(keys)
+	if err != nil {
+		return err
+	}
+	req, err := obfs.recvHandshakeReq(conn)
+	if err != nil {
+		return err
+	}
+	if !bytes.Equal(smokeReqMsg, req) {
+		return fmt.Errorf("request mismatch")
+	}
+	if err := obfs.sendHandshakeResp(conn, smokeRespMsg, 0); err != nil {
+		return err
+	}
+	return nil
+}