[tor-commits] [pluggable-transports/obfs4] 05/08: Test that public keys are not always on the prime-order subgroup.
gitolite role
git at cupani.torproject.org
Thu Sep 8 14:27:15 UTC 2022
This is an automated email from the git hooks/post-receive script.
meskio pushed a commit to branch master
in repository pluggable-transports/obfs4.
commit 586fbf43752592ee299233274e3e5d5597bf7f34
Author: David Fifield <david at bamsoftware.com>
AuthorDate: Fri Sep 2 11:53:57 2022 -0400
Test that public keys are not always on the prime-order subgroup.
See discussion under "Step 2" at https://elligator.org/key-exchange.
---
common/ntor/ntor_test.go | 136 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 136 insertions(+)
diff --git a/common/ntor/ntor_test.go b/common/ntor/ntor_test.go
index e4d1543..7b2026d 100644
--- a/common/ntor/ntor_test.go
+++ b/common/ntor/ntor_test.go
@@ -30,6 +30,10 @@ package ntor
import (
"bytes"
"testing"
+
+ "filippo.io/edwards25519"
+ "filippo.io/edwards25519/field"
+ "gitlab.com/yawning/edwards25519-extra.git/elligator2"
)
// TestNewKeypair tests Curve25519/Elligator keypair generation.
@@ -126,6 +130,138 @@ func TestHandshake(t *testing.T) {
}
}
+// TestPublicKeySubgroup tests that Elligator representatives produced by
+// NewKeypair map to public keys that are not always on the prime-order subgroup
+// of Curve25519. (And incidentally that Elligator representatives agree with
+// the public key stored in the Keypair.)
+//
+// See discussion under "Step 2" at https://elligator.org/key-exchange.
+func TestPublicKeySubgroup(t *testing.T) {
+ // We will test the public keys that comes out of NewKeypair by
+ // multiplying each one by L, the order of the prime-order subgroup of
+ // Curve25519, then checking the order of the resulting point. The error
+ // condition we are checking for specifically is output points always
+ // having order 1, which means that public keys are always on the
+ // prime-order subgroup of Curve25519, which would make Elligator
+ // representatives distinguishable from random. More generally, we want
+ // to ensure that all possible output points of low order are covered.
+ //
+ // We have to do some contortions to conform to the interfaces we use.
+ // We do scalar multiplication by L using Edwards coordinates, rather
+ // than the Montgomery coordinates output by Keypair.Public and
+ // Representative.ToPublic, because the Montgomery-based
+ // crypto/curve25519.X25519 clamps the scalar to be a multiple of 8,
+ // which would not allow us to use the scalar we need. The Edwards-based
+ // ScalarMult only accepts scalars that are strictly less than L; we
+ // work around this by multiplying the point by L - 1, then adding the
+ // point once to the product.
+
+ scalarOrderMinus1, err := edwards25519.NewScalar().SetCanonicalBytes(
+ // This is the same as scMinusOne in filippo.io/edwards25519.
+ // https://github.com/FiloSottile/edwards25519/blob/v1.0.0/scalar.go#L34
+ []byte{236, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16},
+ )
+ if err != nil {
+ panic(err)
+ }
+ // Returns a new edwards25519.Point that is v multiplied by the subgroup
+ // order.
+ scalarMultOrder := func(v *edwards25519.Point) *edwards25519.Point {
+ p := new(edwards25519.Point)
+ // v * (L - 1) + v => v * L
+ p.ScalarMult(scalarOrderMinus1, v)
+ p.Add(p, v)
+ return p
+ }
+
+ // Generates a new Keypair using NewKeypair, and returns the Keypair
+ // along, with its public key as a newly allocated edwards25519.Point.
+ generate := func() (*Keypair, *edwards25519.Point) {
+ kp, err := NewKeypair(true)
+ if err != nil {
+ panic(err)
+ }
+
+ // We will be using the Edwards representation of the public key
+ // (mapped from the Elligator representative) for further
+ // processing. But while we're here, check that the Montgomery
+ // representation output by Representative agrees with the
+ // stored public key.
+ if *kp.Representative().ToPublic() != *kp.Public() {
+ t.Fatal(kp.Representative().ToPublic(), kp.Public())
+ }
+
+ // Do the Elligator map in Edwards coordinates.
+ var clamped [32]byte
+ copy(clamped[:], kp.Representative().Bytes()[:])
+ clamped[31] &= 63
+ repr, err := new(field.Element).SetBytes(clamped[:])
+ if err != nil {
+ panic(err)
+ }
+ ed := elligator2.EdwardsFlavor(repr)
+ if !bytes.Equal(ed.BytesMontgomery(), kp.Public().Bytes()[:]) {
+ panic("Failed to derive an equivalent public key in Edwards coordinates")
+ }
+ return kp, ed
+ }
+
+ // These are all the points of low order that may result from
+ // multiplying an Elligator-mapped point by L. We will test that all of
+ // them are covered.
+ lowOrderPoints := [][32]byte{
+ /* order 1 */ {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+ /* order 2 */ {236, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127},
+ /* order 4 */ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+ /* order 4 */ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128},
+ /* order 8 */ {38, 232, 149, 143, 194, 178, 39, 176, 69, 195, 244, 137, 242, 239, 152, 240, 213, 223, 172, 5, 211, 198, 51, 57, 177, 56, 2, 136, 109, 83, 252, 5},
+ /* order 8 */ {38, 232, 149, 143, 194, 178, 39, 176, 69, 195, 244, 137, 242, 239, 152, 240, 213, 223, 172, 5, 211, 198, 51, 57, 177, 56, 2, 136, 109, 83, 252, 133},
+ /* order 8 */ {199, 23, 106, 112, 61, 77, 216, 79, 186, 60, 11, 118, 13, 16, 103, 15, 42, 32, 83, 250, 44, 57, 204, 198, 78, 199, 253, 119, 146, 172, 3, 122},
+ /* order 8 */ {199, 23, 106, 112, 61, 77, 216, 79, 186, 60, 11, 118, 13, 16, 103, 15, 42, 32, 83, 250, 44, 57, 204, 198, 78, 199, 253, 119, 146, 172, 3, 250},
+ }
+ counts := make(map[[32]byte]int)
+ for _, b := range lowOrderPoints {
+ counts[b] = 0
+ }
+ // Assuming a uniform distribution of representatives, the probability
+ // that a specific low-order point will not be covered after n trials is
+ // (7/8)^n. The probability that *any* of the 8 low-order points will
+ // remain uncovered after n trials is at most 8 times that, 8*(7/8)^n.
+ // We must do at least log((1e-12)/8)/log(7/8) = 222.50 trials, in the
+ // worst case, to ensure a false error rate of less than 1 in a
+ // trillion. In practice, we keep track of the number of covered points
+ // and break the loop when it reaches 8, so when representatives are
+ // actually uniform we will usually run much fewer iterations.
+ numCovered := 0
+ for i := 0; i < 225; i++ {
+ kp, pk := generate()
+ v := scalarMultOrder(pk)
+ var b [32]byte
+ copy(b[:], v.Bytes())
+ if _, ok := counts[b]; !ok {
+ t.Fatalf("map(%x)*order yielded unexpected point %v",
+ *kp.Representative().Bytes(), b)
+ }
+ counts[b]++
+ if counts[b] == 1 {
+ // We just covered a new point for the first time.
+ numCovered++
+ if numCovered == len(lowOrderPoints) {
+ break
+ }
+ }
+ }
+ for _, b := range lowOrderPoints {
+ count, ok := counts[b]
+ if !ok {
+ panic(b)
+ }
+ if count == 0 {
+ t.Errorf("low-order point %x not covered", b)
+ }
+ }
+}
+
// Benchmark Client/Server handshake. The actual time taken that will be
// observed on either the Client or Server is half the reported time per
// operation since the benchmark does both sides.
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the tor-commits
mailing list