Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds neff shuffling of sequences #457

Merged
merged 10 commits into from
Nov 30, 2022
95 changes: 95 additions & 0 deletions examples/neff_shuffle_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package examples

import (
"testing"

"github.com/stretchr/testify/require"
"go.dedis.ch/kyber/v3"
kproof "go.dedis.ch/kyber/v3/proof"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you use kproof? I think proof is fine and later we can use p instead of proof.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as a non crypto guy, I highly appreciate when variables are not only single letters :D

"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) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use MixedCaps instead of underscores, but others examples use underscores too, so I guess that's fine. Other examples uses /* ... */ for the example explanation, maybe we want to remain consistent.

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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd assign the point directly to cs[i], same for ks[i].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad practice, in my opinion. It makes the code less readable.

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 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
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)
require.NoError(t, err)

// 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)
}
156 changes: 156 additions & 0 deletions shuffle/sequences.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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
// "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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we don't enforce this at the beginning of the function? Since the cost of the assertion is negligible, I'd add it.

//
// Dim X and Y: [<sequence length, j>, <number of sequences, i>]
//
// 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 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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it would be easier to use Xbar instead of XX since the paper uses $\overline{X}$. Why going with the repeating letter?

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-- {

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove empty line.

j := int(random.Int(big.NewInt(int64(i+1)), rand).Int64())
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
}
89 changes: 86 additions & 3 deletions shuffle/shuffle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -64,3 +70,80 @@ 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)
}

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 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
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())
}
}
}
16 changes: 14 additions & 2 deletions shuffle/vartime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}