From 5cea1f49b9a2a1bce9b30167ee5a8f31dd60176f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mien=20Kocher?= Date: Tue, 23 Nov 2021 15:36:11 +0100 Subject: [PATCH 1/6] Adds neff shuffling of sequences Based on the work from @lhmerino (https://github.com/dedis/votegral/blob/master/apps/Cothority/votegral/lib/crypto/shuffle.go) --- examples/neff_shuffle_test.go | 106 +++++++++++++++++++++++ go.sum | 3 - shuffle/sequences.go | 155 ++++++++++++++++++++++++++++++++++ shuffle/shuffle_test.go | 91 +++++++++++++++++++- shuffle/vartime_test.go | 16 +++- 5 files changed, 363 insertions(+), 8 deletions(-) create mode 100644 examples/neff_shuffle_test.go create mode 100644 shuffle/sequences.go diff --git a/examples/neff_shuffle_test.go b/examples/neff_shuffle_test.go new file mode 100644 index 000000000..acd4ac56a --- /dev/null +++ b/examples/neff_shuffle_test.go @@ -0,0 +1,106 @@ +package examples + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.dedis.ch/kyber/v3" + kproof "go.dedis.ch/kyber/v3/proof" + "go.dedis.ch/kyber/v3/shuffle" +) + +/* +This example illustrates how to use the Neff shuffle protocol with simple, +single pairs. +*/ +func Test_Example_Neff_Shuffle_Simple(t *testing.T) { + numPairs := 3 + + // generate random pairs + + ks := make([]kyber.Point, numPairs) + cs := make([]kyber.Point, numPairs) + + for i := 0; i < numPairs; i++ { + c := suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) + k := suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) + + ks[i] = k + cs[i] = c + } + + // shuffle the pairs + + xx, yy, prover := shuffle.Shuffle(suite, nil, nil, ks, cs, suite.RandomStream()) + + // compute the proof + + proof, err := kproof.HashProve(suite, "PairShuffle", prover) + require.NoError(t, err) + + // check the proof + + verifier := shuffle.Verifier(suite, nil, nil, ks, cs, xx, yy) + + err = kproof.HashVerify(suite, "PairShuffle", verifier, proof) + require.NoError(t, err) +} + +/* +This example illustrates how to use the Neff shuffle protocol on sequences of +pairs. The single pair protocol (see above) uses as inputs one-dimensional +slices. This variation uses 2-dimensional slices, where the number of columns +defines the number of sequences, and the number of rows defines the length of +sequences. There is also a difference when getting the prover. In this variation +the Shuffle function doesn't directly return a prover, but a function to get it. +This is because the verifier must provide a slice of random numbers to the +prover. +*/ +func Test_Example_Neff_Shuffle_Sequence(t *testing.T) { + sequenceLen := 3 + numSequences := 3 + + X := make([][]kyber.Point, numSequences) + Y := make([][]kyber.Point, numSequences) + + // generate random sequences + + for i := 0; i < numSequences; i++ { + xs := make([]kyber.Point, sequenceLen) + ys := make([]kyber.Point, sequenceLen) + + for i := 0; i < sequenceLen; i++ { + xs[i] = suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) + ys[i] = suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) + } + + X[i] = xs + Y[i] = ys + } + + // shuffle sequences + + XX, YY, getProver := shuffle.SequencesShuffle(suite, nil, nil, X, Y, suite.RandomStream()) + + // compute the proof + + NQ := len(X) + e := make([]kyber.Scalar, NQ) + for j := 0; j < NQ; j++ { + e[j] = suite.Scalar().Pick(suite.RandomStream()) + } + + prover, err := getProver(e) + require.NoError(t, err) + + proof, err := kproof.HashProve(suite, "SequencesShuffle", prover) + + // check the proof + + XXUp, YYUp, XXDown, YYDown := shuffle.GetSequenceVerifiable(suite, X, Y, XX, YY, e) + + verifier := shuffle.Verifier(suite, nil, nil, XXUp, YYUp, XXDown, YYDown) + + err = kproof.HashVerify(suite, "SequencesShuffle", verifier, proof) + require.NoError(t, err) +} diff --git a/go.sum b/go.sum index 2a22ca0e9..c9ea88086 100644 --- a/go.sum +++ b/go.sum @@ -7,12 +7,9 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= -go.dedis.ch/kyber/v3 v3.0.4 h1:FDuC/S3STkvwxZ0ooo3gcp56QkUKsN7Jy7cpzBxL+vQ= go.dedis.ch/kyber/v3 v3.0.4/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ= go.dedis.ch/kyber/v3 v3.0.9/go.mod h1:rhNjUUg6ahf8HEg5HUvVBYoWY4boAafX8tYxX+PS+qg= -go.dedis.ch/protobuf v1.0.5 h1:EbF1czEKICxf5KY8Tm7wMF28hcOQbB6yk4IybIFWTYE= go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRLo= -go.dedis.ch/protobuf v1.0.7 h1:wRUEiq3u0/vBhLjcw9CmAVrol+BnDyq2M0XLukdphyI= go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= go.dedis.ch/protobuf v1.0.11 h1:FTYVIEzY/bfl37lu3pR4lIj+F9Vp1jE8oh91VmxKgLo= go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= diff --git a/shuffle/sequences.go b/shuffle/sequences.go new file mode 100644 index 000000000..b3d85912b --- /dev/null +++ b/shuffle/sequences.go @@ -0,0 +1,155 @@ +package shuffle + +import ( + "crypto/cipher" + "fmt" + + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/proof" +) + +// SequencesShuffle shuffles a sequence of ElGamal pairs based on Section 5 of +// "Verifiable Mixing (Shuffling) of ElGamal Pairs" by Andrew Neff (April 2004) +// +// The function expects X and Y to be the same dimension, with each row having +// the same length. It also expect X and Y to have at least one element. +// +// Dim X and Y: [, ] +// +// The number of rows defines the sequences length. The number of columns +// defines the number of sequences. +// +// Seq 1 Seq 2 Seq 3 +// (0,0) (0,1) (0,2) +// (1,0) (1,1) (1,2) +// (2,0) (2,1) (2,2) +// +// In the code coordinates are (j,i), where 1 ≤ i ≤ k, 1 ≤ j ≤ NQ +// +// Last coordinate is (NQ-1, k-1) +// +// Variable names are as representative to the paper as possible. Instead of +// representing (variable name with a bar on top), such as (X with a bar on top) +// with Xbar, we represent it with a repeating letter, such as XX +func SequencesShuffle(group kyber.Group, g, h kyber.Point, X, Y [][]kyber.Point, + rand cipher.Stream) (XX, YY [][]kyber.Point, getProver func(e []kyber.Scalar) ( + proof.Prover, error)) { + + NQ := len(X) + k := len(X[0]) + + // Pick a random permutation used in ALL k ElGamal sequences. The permutation + // (π) of an ElGamal pair at index i always outputs to the same index + pi := make([]int, k) + for i := 0; i < k; i++ { + pi[i] = i + } + + // Fisher–Yates shuffle + for i := k - 1; i > 0; i-- { + j := int(randUint64(rand) % uint64(i+1)) + if j != i { + pi[i], pi[j] = pi[j], pi[i] + } + } + + // Pick a fresh ElGamal blinding factor β(j, i) for each ElGamal sequence + // and each ElGamal pair + beta := make([][]kyber.Scalar, NQ) + for j := 0; j < NQ; j++ { + beta[j] = make([]kyber.Scalar, k) + for i := 0; i < k; i++ { + beta[j][i] = group.Scalar().Pick(rand) + } + } + + // Perform the Shuffle + + XX = make([][]kyber.Point, NQ) + YY = make([][]kyber.Point, NQ) + + for j := 0; j < NQ; j++ { + XX[j] = make([]kyber.Point, k) + YY[j] = make([]kyber.Point, k) + + for i := 0; i < k; i++ { + XX[j][i] = group.Point().Mul(beta[j][pi[i]], g) + XX[j][i].Add(XX[j][i], X[j][pi[i]]) + + YY[j][i] = group.Point().Mul(beta[j][pi[i]], h) + YY[j][i].Add(YY[j][i], Y[j][pi[i]]) + } + } + + getProver = func(e []kyber.Scalar) (proof.Prover, error) { + // EGAR 2 (Prover) - Standard ElGamal k-shuffle proof: Knowledge of + // (XXUp, YYUp), (XXDown, YYDown) and e[j] + + ps := PairShuffle{} + ps.Init(group, k) + + if len(e) != NQ { + return nil, fmt.Errorf("len(e) must be equal to NQ: %d != %d", len(e), NQ) + } + + return func(ctx proof.ProverContext) error { + // Need to consolidate beta to a one dimensional array + beta2 := make([]kyber.Scalar, k) + + for i := 0; i < k; i++ { + beta2[i] = group.Scalar().Mul(e[0], beta[0][i]) + + for j := 1; j < NQ; j++ { + beta2[i] = group.Scalar().Add(beta2[i], + group.Scalar().Mul(e[j], beta[j][i])) + } + } + + XXUp, YYUp, _, _ := GetSequenceVerifiable(group, X, Y, XX, YY, e) + + return ps.Prove(pi, g, h, beta2, XXUp, YYUp, rand, ctx) + }, nil + } + + return XX, YY, getProver +} + +// GetSequenceVerifiable returns the consolidated input and output of sequence +// shuffling elements. Needed by the prover and verifier. +func GetSequenceVerifiable(group kyber.Group, X, Y, XX, YY [][]kyber.Point, e []kyber.Scalar) ( + XXUp, YYUp, XXDown, YYDown []kyber.Point) { + + // EGAR1 (Verifier) - Consolidate input and output + + NQ := len(X) + k := len(X[0]) + + XXUp = make([]kyber.Point, k) + YYUp = make([]kyber.Point, k) + XXDown = make([]kyber.Point, k) + YYDown = make([]kyber.Point, k) + + for i := 0; i < k; i++ { + // No modification could be made for e[0] -> e[0] = 1 if one wanted - + // Remark 7 in the paper + XXUp[i] = group.Point().Mul(e[0], X[0][i]) + YYUp[i] = group.Point().Mul(e[0], Y[0][i]) + + XXDown[i] = group.Point().Mul(e[0], XX[0][i]) + YYDown[i] = group.Point().Mul(e[0], YY[0][i]) + + for j := 1; j < NQ; j++ { + XXUp[i] = group.Point().Add(XXUp[i], + group.Point().Mul(e[j], X[j][i])) + YYUp[i] = group.Point().Add(YYUp[i], + group.Point().Mul(e[j], Y[j][i])) + + XXDown[i] = group.Point().Add(XXDown[i], + group.Point().Mul(e[j], XX[j][i])) + YYDown[i] = group.Point().Add(YYDown[i], + group.Point().Mul(e[j], YY[j][i])) + } + } + + return XXUp, YYUp, XXDown, YYDown +} diff --git a/shuffle/shuffle_test.go b/shuffle/shuffle_test.go index c46fb1323..6d3815c00 100644 --- a/shuffle/shuffle_test.go +++ b/shuffle/shuffle_test.go @@ -10,14 +10,20 @@ import ( ) var k = 5 +var NQ = 6 var N = 10 -func TestShuffle(t *testing.T) { +func TestShufflePair(t *testing.T) { s := edwards25519.NewBlakeSHA256Ed25519WithRand(blake2xb.New(nil)) - shuffleTest(s, k, N) + pairShuffleTest(s, k, N) } -func shuffleTest(suite Suite, k, N int) { +func TestShuffleSequence(t *testing.T) { + s := edwards25519.NewBlakeSHA256Ed25519WithRand(blake2xb.New(nil)) + sequenceShuffleTest(s, k, NQ, N) +} + +func pairShuffleTest(suite Suite, k, N int) { rand := suite.RandomStream() // Create a "server" private/public keypair @@ -64,3 +70,82 @@ func shuffleTest(suite Suite, k, N int) { } } } + +func sequenceShuffleTest(suite Suite, k, NQ, N int) { + rand := suite.RandomStream() + + // Create a "server" private/public keypair + h := suite.Scalar().Pick(rand) + H := suite.Point().Mul(h, nil) + + // Create a set of ephemeral "client" keypairs to shuffle + c := make([]kyber.Scalar, k) + C := make([]kyber.Point, k) + + for i := 0; i < k; i++ { + c[i] = suite.Scalar().Pick(rand) + C[i] = suite.Point().Mul(c[i], nil) + // fmt.Println(" "+C[i].String()) + } + + X := make([][]kyber.Point, NQ) + Y := make([][]kyber.Point, NQ) + + // generate random sequences + + for i := 0; i < NQ; i++ { + xs := make([]kyber.Point, k) + ys := make([]kyber.Point, k) + + for i := 0; i < k; i++ { + xs[i] = suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) + ys[i] = suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) + } + + X[i] = xs + Y[i] = ys + } + + // ElGamal-encrypt all these keypairs with the "server" key + r := suite.Scalar() // temporary + for j := 0; j < NQ; j++ { + for i := 0; i < k; i++ { + r.Pick(rand) + X[j][i] = suite.Point().Mul(r, nil) + Y[j][i] = suite.Point().Mul(r, H) // ElGamal blinding factor + Y[j][i].Add(Y[j][i], C[i]) // Encrypted client public key + } + } + + // Repeat only the actual shuffle portion for test purposes. + for i := 0; i < N; i++ { + + // Do a key-shuffle + XX, YY, getProver := SequencesShuffle(suite, nil, H, X, Y, rand) + + e := make([]kyber.Scalar, NQ) + for j := 0; j < NQ; j++ { + e[j] = suite.Scalar().Pick(suite.RandomStream()) + } + + prover, err := getProver(e) + if err != nil { + panic("failed to get prover: " + err.Error()) + } + + prf, err := proof.HashProve(suite, "PairShuffle", prover) + if err != nil { + panic("failed to hashProve: " + err.Error()) + } + + XXUp, YYUp, XXDown, YYDown := GetSequenceVerifiable(suite, X, Y, XX, YY, e) + + // Check it + verifier := Verifier(suite, nil, H, XXUp, YYUp, XXDown, YYDown) + + err = proof.HashVerify(suite, "PairShuffle", verifier, prf) + if err != nil { + panic("failed to hashVerify: " + err.Error()) + } + } +} diff --git a/shuffle/vartime_test.go b/shuffle/vartime_test.go index f996a7fd1..23d340154 100644 --- a/shuffle/vartime_test.go +++ b/shuffle/vartime_test.go @@ -11,9 +11,21 @@ func BenchmarkBiffleP256(b *testing.B) { } func Benchmark2PairShuffleP256(b *testing.B) { - shuffleTest(nist.NewBlakeSHA256P256(), 2, b.N) + pairShuffleTest(nist.NewBlakeSHA256P256(), 2, b.N) } func Benchmark10PairShuffleP256(b *testing.B) { - shuffleTest(nist.NewBlakeSHA256P256(), 10, b.N) + pairShuffleTest(nist.NewBlakeSHA256P256(), 10, b.N) +} + +func Benchmark2Pair2SeqShuffleP256(b *testing.B) { + sequenceShuffleTest(nist.NewBlakeSHA256P256(), 2, 2, b.N) +} + +func Benchmark2Pair10SeqShuffleP256(b *testing.B) { + sequenceShuffleTest(nist.NewBlakeSHA256P256(), 2, 10, b.N) +} + +func Benchmark10Pair10SeqShuffleP256(b *testing.B) { + sequenceShuffleTest(nist.NewBlakeSHA256P256(), 10, 10, b.N) } From 190450e3452cc68e8bb236235f8726ac11499485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mien=20Kocher?= Date: Wed, 24 Nov 2021 09:19:27 +0100 Subject: [PATCH 2/6] Addresses Linus's comments --- examples/neff_shuffle_test.go | 38 ++++++++++++----------------------- shuffle/sequences.go | 3 +-- shuffle/shuffle_test.go | 8 +++----- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/examples/neff_shuffle_test.go b/examples/neff_shuffle_test.go index acd4ac56a..f60a657b5 100644 --- a/examples/neff_shuffle_test.go +++ b/examples/neff_shuffle_test.go @@ -9,15 +9,12 @@ import ( "go.dedis.ch/kyber/v3/shuffle" ) -/* -This example illustrates how to use the Neff shuffle protocol with simple, -single pairs. -*/ +// This example illustrates how to use the Neff shuffle protocol with simple, +// single pairs. func Test_Example_Neff_Shuffle_Simple(t *testing.T) { numPairs := 3 // generate random pairs - ks := make([]kyber.Point, numPairs) cs := make([]kyber.Point, numPairs) @@ -30,32 +27,27 @@ func Test_Example_Neff_Shuffle_Simple(t *testing.T) { } // shuffle the pairs - xx, yy, prover := shuffle.Shuffle(suite, nil, nil, ks, cs, suite.RandomStream()) // compute the proof - proof, err := kproof.HashProve(suite, "PairShuffle", prover) require.NoError(t, err) // check the proof - verifier := shuffle.Verifier(suite, nil, nil, ks, cs, xx, yy) err = kproof.HashVerify(suite, "PairShuffle", verifier, proof) require.NoError(t, err) } -/* -This example illustrates how to use the Neff shuffle protocol on sequences of -pairs. The single pair protocol (see above) uses as inputs one-dimensional -slices. This variation uses 2-dimensional slices, where the number of columns -defines the number of sequences, and the number of rows defines the length of -sequences. There is also a difference when getting the prover. In this variation -the Shuffle function doesn't directly return a prover, but a function to get it. -This is because the verifier must provide a slice of random numbers to the -prover. -*/ +// This example illustrates how to use the Neff shuffle protocol on sequences of +// pairs. The single pair protocol (see above) uses as inputs one-dimensional +// slices. This variation uses 2-dimensional slices, where the number of columns +// defines the number of sequences, and the number of rows defines the length of +// sequences. There is also a difference when getting the prover. In this +// variation the Shuffle function doesn't directly return a prover, but a +// function to get it. This is because the verifier must provide a slice of +// random numbers to the prover. func Test_Example_Neff_Shuffle_Sequence(t *testing.T) { sequenceLen := 3 numSequences := 3 @@ -64,14 +56,13 @@ func Test_Example_Neff_Shuffle_Sequence(t *testing.T) { Y := make([][]kyber.Point, numSequences) // generate random sequences - for i := 0; i < numSequences; i++ { xs := make([]kyber.Point, sequenceLen) ys := make([]kyber.Point, sequenceLen) - for i := 0; i < sequenceLen; i++ { - xs[i] = suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) - ys[i] = suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) + for j := 0; j < sequenceLen; j++ { + xs[j] = suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) + ys[j] = suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) } X[i] = xs @@ -79,11 +70,9 @@ func Test_Example_Neff_Shuffle_Sequence(t *testing.T) { } // shuffle sequences - XX, YY, getProver := shuffle.SequencesShuffle(suite, nil, nil, X, Y, suite.RandomStream()) // compute the proof - NQ := len(X) e := make([]kyber.Scalar, NQ) for j := 0; j < NQ; j++ { @@ -96,7 +85,6 @@ func Test_Example_Neff_Shuffle_Sequence(t *testing.T) { proof, err := kproof.HashProve(suite, "SequencesShuffle", prover) // check the proof - XXUp, YYUp, XXDown, YYDown := shuffle.GetSequenceVerifiable(suite, X, Y, XX, YY, e) verifier := shuffle.Verifier(suite, nil, nil, XXUp, YYUp, XXDown, YYDown) diff --git a/shuffle/sequences.go b/shuffle/sequences.go index b3d85912b..a08c90288 100644 --- a/shuffle/sequences.go +++ b/shuffle/sequences.go @@ -47,6 +47,7 @@ func SequencesShuffle(group kyber.Group, g, h kyber.Point, X, Y [][]kyber.Point, // Fisher–Yates shuffle for i := k - 1; i > 0; i-- { + j := int(randUint64(rand) % uint64(i+1)) if j != i { pi[i], pi[j] = pi[j], pi[i] @@ -64,7 +65,6 @@ func SequencesShuffle(group kyber.Group, g, h kyber.Point, X, Y [][]kyber.Point, } // Perform the Shuffle - XX = make([][]kyber.Point, NQ) YY = make([][]kyber.Point, NQ) @@ -120,7 +120,6 @@ func GetSequenceVerifiable(group kyber.Group, X, Y, XX, YY [][]kyber.Point, e [] XXUp, YYUp, XXDown, YYDown []kyber.Point) { // EGAR1 (Verifier) - Consolidate input and output - NQ := len(X) k := len(X[0]) diff --git a/shuffle/shuffle_test.go b/shuffle/shuffle_test.go index 6d3815c00..8eafc2466 100644 --- a/shuffle/shuffle_test.go +++ b/shuffle/shuffle_test.go @@ -85,21 +85,19 @@ func sequenceShuffleTest(suite Suite, k, NQ, N int) { for i := 0; i < k; i++ { c[i] = suite.Scalar().Pick(rand) C[i] = suite.Point().Mul(c[i], nil) - // fmt.Println(" "+C[i].String()) } X := make([][]kyber.Point, NQ) Y := make([][]kyber.Point, NQ) // generate random sequences - for i := 0; i < NQ; i++ { xs := make([]kyber.Point, k) ys := make([]kyber.Point, k) - for i := 0; i < k; i++ { - xs[i] = suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) - ys[i] = suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) + for j := 0; j < k; j++ { + xs[j] = suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) + ys[j] = suite.Point().Mul(suite.Scalar().Pick(suite.RandomStream()), nil) } X[i] = xs From 6ef343800acbe2addad08e9c21995a64245227ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mien=20Kocher?= Date: Wed, 24 Nov 2021 09:32:37 +0100 Subject: [PATCH 3/6] Fixes a wrong notation in the comment --- shuffle/sequences.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle/sequences.go b/shuffle/sequences.go index a08c90288..eb5f339e5 100644 --- a/shuffle/sequences.go +++ b/shuffle/sequences.go @@ -24,7 +24,7 @@ import ( // (1,0) (1,1) (1,2) // (2,0) (2,1) (2,2) // -// In the code coordinates are (j,i), where 1 ≤ i ≤ k, 1 ≤ j ≤ NQ +// In the code coordinates are (j,i), where 0 ≤ j ≤ NQ-1, 0 ≤ i ≤ k-1 // // Last coordinate is (NQ-1, k-1) // From 000a0de64d3f6ae2053b97c3b134e10e0439600f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mien=20Kocher?= Date: Wed, 24 Nov 2021 09:50:09 +0100 Subject: [PATCH 4/6] Uses a different random number generator --- shuffle/sequences.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shuffle/sequences.go b/shuffle/sequences.go index eb5f339e5..965c89d7e 100644 --- a/shuffle/sequences.go +++ b/shuffle/sequences.go @@ -3,9 +3,11 @@ package shuffle import ( "crypto/cipher" "fmt" + "math/big" "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/proof" + "go.dedis.ch/kyber/v3/util/random" ) // SequencesShuffle shuffles a sequence of ElGamal pairs based on Section 5 of @@ -48,7 +50,7 @@ func SequencesShuffle(group kyber.Group, g, h kyber.Point, X, Y [][]kyber.Point, // Fisher–Yates shuffle for i := k - 1; i > 0; i-- { - j := int(randUint64(rand) % uint64(i+1)) + j := int(random.Int(big.NewInt(int64(i+1)), rand).Int64()) if j != i { pi[i], pi[j] = pi[j], pi[i] } From 4b7ca53e0f4e9eec1e778564e3e1de7e9cf25ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mien=20Kocher?= Date: Thu, 25 Nov 2021 11:04:12 +0100 Subject: [PATCH 5/6] Adds missing error check --- examples/neff_shuffle_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/neff_shuffle_test.go b/examples/neff_shuffle_test.go index f60a657b5..aca1c69c7 100644 --- a/examples/neff_shuffle_test.go +++ b/examples/neff_shuffle_test.go @@ -83,6 +83,7 @@ func Test_Example_Neff_Shuffle_Sequence(t *testing.T) { require.NoError(t, err) proof, err := kproof.HashProve(suite, "SequencesShuffle", prover) + require.NoError(t, err) // check the proof XXUp, YYUp, XXDown, YYDown := shuffle.GetSequenceVerifiable(suite, X, Y, XX, YY, e) From 7c3589fb2e7b53ca7bb9262400bbfdea9c402a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mien=20Kocher?= Date: Tue, 29 Nov 2022 17:49:25 +0100 Subject: [PATCH 6/6] Addresses Simone's comments --- shuffle/sequence_test.go | 69 ++++++++++++++++++++++++ shuffle/sequences.go | 111 +++++++++++++++++++++++++-------------- 2 files changed, 140 insertions(+), 40 deletions(-) create mode 100644 shuffle/sequence_test.go diff --git a/shuffle/sequence_test.go b/shuffle/sequence_test.go new file mode 100644 index 000000000..2daaea940 --- /dev/null +++ b/shuffle/sequence_test.go @@ -0,0 +1,69 @@ +package shuffle + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.dedis.ch/kyber/v3" +) + +func TestAssertXY(t *testing.T) { + type tdata struct { + x [][]kyber.Point + y [][]kyber.Point + errStr string + } + + // express possible wrong cases and the expected errors + + table := []tdata{ + { + x: nil, + y: nil, + errStr: "X is empty", + }, + { + x: [][]kyber.Point{{}}, + y: [][]kyber.Point{{}}, + errStr: "X is empty", + }, + { + x: [][]kyber.Point{make([]kyber.Point, 1)}, + y: [][]kyber.Point{{}}, + errStr: "Y is empty", + }, + { + x: [][]kyber.Point{make([]kyber.Point, 1)}, + y: nil, + errStr: "Y is empty", + }, + { + x: [][]kyber.Point{make([]kyber.Point, 1), make([]kyber.Point, 2)}, + y: [][]kyber.Point{make([]kyber.Point, 1)}, + errStr: "X and Y have a different size: 2 != 1", + }, + { + x: [][]kyber.Point{make([]kyber.Point, 1)}, + y: [][]kyber.Point{make([]kyber.Point, 2)}, + errStr: "Y[0] has unexpected size: 1 != 2", + }, + { + x: [][]kyber.Point{make([]kyber.Point, 1), make([]kyber.Point, 2)}, + y: [][]kyber.Point{make([]kyber.Point, 1), make([]kyber.Point, 1)}, + errStr: "X[1] has unexpected size: 1 != 2", + }, + } + + for _, entry := range table { + err := assertXY(entry.x, entry.y) + require.EqualError(t, err, entry.errStr) + } + + // check valid data + + x := [][]kyber.Point{make([]kyber.Point, 2), make([]kyber.Point, 2)} + y := [][]kyber.Point{make([]kyber.Point, 2), make([]kyber.Point, 2)} + + err := assertXY(x, y) + require.NoError(t, err) +} diff --git a/shuffle/sequences.go b/shuffle/sequences.go index 965c89d7e..9519f22ad 100644 --- a/shuffle/sequences.go +++ b/shuffle/sequences.go @@ -2,6 +2,7 @@ package shuffle import ( "crypto/cipher" + "errors" "fmt" "math/big" @@ -14,29 +15,33 @@ import ( // "Verifiable Mixing (Shuffling) of ElGamal Pairs" by Andrew Neff (April 2004) // // The function expects X and Y to be the same dimension, with each row having -// the same length. It also expect X and Y to have at least one element. +// the same length. It also expect X and Y to have at least one element. The +// function will panic if the expectations are not met. // // Dim X and Y: [, ] // // The number of rows defines the sequences length. The number of columns // defines the number of sequences. // -// Seq 1 Seq 2 Seq 3 -// (0,0) (0,1) (0,2) -// (1,0) (1,1) (1,2) -// (2,0) (2,1) (2,2) +// Seq 1 Seq 2 Seq 3 +// (0,0) (0,1) (0,2) +// (1,0) (1,1) (1,2) +// (2,0) (2,1) (2,2) // -// In the code coordinates are (j,i), where 0 ≤ j ≤ NQ-1, 0 ≤ i ≤ k-1 +// # In the code coordinates are (j,i), where 0 ≤ j ≤ NQ-1, 0 ≤ i ≤ k-1 // // Last coordinate is (NQ-1, k-1) // -// Variable names are as representative to the paper as possible. Instead of -// representing (variable name with a bar on top), such as (X with a bar on top) -// with Xbar, we represent it with a repeating letter, such as XX +// Variable names are as representative to the paper as possible. func SequencesShuffle(group kyber.Group, g, h kyber.Point, X, Y [][]kyber.Point, - rand cipher.Stream) (XX, YY [][]kyber.Point, getProver func(e []kyber.Scalar) ( + rand cipher.Stream) (Xbar, Ybar [][]kyber.Point, getProver func(e []kyber.Scalar) ( proof.Prover, error)) { + err := assertXY(X, Y) + if err != nil { + panic(fmt.Sprintf("invalid data: %v", err)) + } + NQ := len(X) k := len(X[0]) @@ -49,7 +54,6 @@ func SequencesShuffle(group kyber.Group, g, h kyber.Point, X, Y [][]kyber.Point, // Fisher–Yates shuffle for i := k - 1; i > 0; i-- { - j := int(random.Int(big.NewInt(int64(i+1)), rand).Int64()) if j != i { pi[i], pi[j] = pi[j], pi[i] @@ -67,25 +71,25 @@ func SequencesShuffle(group kyber.Group, g, h kyber.Point, X, Y [][]kyber.Point, } // Perform the Shuffle - XX = make([][]kyber.Point, NQ) - YY = make([][]kyber.Point, NQ) + Xbar = make([][]kyber.Point, NQ) + Ybar = make([][]kyber.Point, NQ) for j := 0; j < NQ; j++ { - XX[j] = make([]kyber.Point, k) - YY[j] = make([]kyber.Point, k) + Xbar[j] = make([]kyber.Point, k) + Ybar[j] = make([]kyber.Point, k) for i := 0; i < k; i++ { - XX[j][i] = group.Point().Mul(beta[j][pi[i]], g) - XX[j][i].Add(XX[j][i], X[j][pi[i]]) + Xbar[j][i] = group.Point().Mul(beta[j][pi[i]], g) + Xbar[j][i].Add(Xbar[j][i], X[j][pi[i]]) - YY[j][i] = group.Point().Mul(beta[j][pi[i]], h) - YY[j][i].Add(YY[j][i], Y[j][pi[i]]) + Ybar[j][i] = group.Point().Mul(beta[j][pi[i]], h) + Ybar[j][i].Add(Ybar[j][i], Y[j][pi[i]]) } } getProver = func(e []kyber.Scalar) (proof.Prover, error) { // EGAR 2 (Prover) - Standard ElGamal k-shuffle proof: Knowledge of - // (XXUp, YYUp), (XXDown, YYDown) and e[j] + // (XUp, YUp), (XDown, YDown) and e[j] ps := PairShuffle{} ps.Init(group, k) @@ -107,50 +111,77 @@ func SequencesShuffle(group kyber.Group, g, h kyber.Point, X, Y [][]kyber.Point, } } - XXUp, YYUp, _, _ := GetSequenceVerifiable(group, X, Y, XX, YY, e) + XUp, YUp, _, _ := GetSequenceVerifiable(group, X, Y, Xbar, Ybar, e) - return ps.Prove(pi, g, h, beta2, XXUp, YYUp, rand, ctx) + return ps.Prove(pi, g, h, beta2, XUp, YUp, rand, ctx) }, nil } - return XX, YY, getProver + return Xbar, Ybar, getProver +} + +// assertXY checks that X, Y have the same dimensions and at least one element +func assertXY(X, Y [][]kyber.Point) error { + if len(X) == 0 || len(X[0]) == 0 { + return errors.New("X is empty") + } + if len(Y) == 0 || len(Y[0]) == 0 { + return errors.New("Y is empty") + } + + if len(X) != len(Y) { + return fmt.Errorf("X and Y have a different size: %d != %d", len(X), len(Y)) + } + + expected := len(X[0]) + + for i := range X { + if len(X[i]) != expected { + return fmt.Errorf("X[%d] has unexpected size: %d != %d", i, expected, len(X[i])) + } + if len(Y[i]) != expected { + return fmt.Errorf("Y[%d] has unexpected size: %d != %d", i, expected, len(Y[i])) + } + } + + return nil } // GetSequenceVerifiable returns the consolidated input and output of sequence // shuffling elements. Needed by the prover and verifier. -func GetSequenceVerifiable(group kyber.Group, X, Y, XX, YY [][]kyber.Point, e []kyber.Scalar) ( - XXUp, YYUp, XXDown, YYDown []kyber.Point) { +func GetSequenceVerifiable(group kyber.Group, X, Y, Xbar, Ybar [][]kyber.Point, e []kyber.Scalar) ( + XUp, YUp, XDown, YDown []kyber.Point) { // EGAR1 (Verifier) - Consolidate input and output NQ := len(X) k := len(X[0]) - XXUp = make([]kyber.Point, k) - YYUp = make([]kyber.Point, k) - XXDown = make([]kyber.Point, k) - YYDown = make([]kyber.Point, k) + XUp = make([]kyber.Point, k) + YUp = make([]kyber.Point, k) + XDown = make([]kyber.Point, k) + YDown = make([]kyber.Point, k) for i := 0; i < k; i++ { // No modification could be made for e[0] -> e[0] = 1 if one wanted - // Remark 7 in the paper - XXUp[i] = group.Point().Mul(e[0], X[0][i]) - YYUp[i] = group.Point().Mul(e[0], Y[0][i]) + XUp[i] = group.Point().Mul(e[0], X[0][i]) + YUp[i] = group.Point().Mul(e[0], Y[0][i]) - XXDown[i] = group.Point().Mul(e[0], XX[0][i]) - YYDown[i] = group.Point().Mul(e[0], YY[0][i]) + XDown[i] = group.Point().Mul(e[0], Xbar[0][i]) + YDown[i] = group.Point().Mul(e[0], Ybar[0][i]) for j := 1; j < NQ; j++ { - XXUp[i] = group.Point().Add(XXUp[i], + XUp[i] = group.Point().Add(XUp[i], group.Point().Mul(e[j], X[j][i])) - YYUp[i] = group.Point().Add(YYUp[i], + YUp[i] = group.Point().Add(YUp[i], group.Point().Mul(e[j], Y[j][i])) - XXDown[i] = group.Point().Add(XXDown[i], - group.Point().Mul(e[j], XX[j][i])) - YYDown[i] = group.Point().Add(YYDown[i], - group.Point().Mul(e[j], YY[j][i])) + XDown[i] = group.Point().Add(XDown[i], + group.Point().Mul(e[j], Xbar[j][i])) + YDown[i] = group.Point().Add(YDown[i], + group.Point().Mul(e[j], Ybar[j][i])) } } - return XXUp, YYUp, XXDown, YYDown + return XUp, YUp, XDown, YDown }