Skip to content

Commit

Permalink
Merge pull request #457 from dedis/feature/neff-shuffle-sequences
Browse files Browse the repository at this point in the history
Adds neff shuffling of sequences
  • Loading branch information
nkcr authored Nov 30, 2022
2 parents 860d748 + f17d5e1 commit 199132f
Show file tree
Hide file tree
Showing 5 changed files with 451 additions and 5 deletions.
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"
"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 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)
}
69 changes: 69 additions & 0 deletions shuffle/sequence_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
187 changes: 187 additions & 0 deletions shuffle/sequences.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package shuffle

import (
"crypto/cipher"
"errors"
"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. The
// function will panic if the expectations are not met.
//
// 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.
func SequencesShuffle(group kyber.Group, g, h kyber.Point, X, Y [][]kyber.Point,
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])

// 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(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
Xbar = make([][]kyber.Point, NQ)
Ybar = make([][]kyber.Point, NQ)

for j := 0; j < NQ; j++ {
Xbar[j] = make([]kyber.Point, k)
Ybar[j] = make([]kyber.Point, k)

for i := 0; i < k; i++ {
Xbar[j][i] = group.Point().Mul(beta[j][pi[i]], g)
Xbar[j][i].Add(Xbar[j][i], X[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
// (XUp, YUp), (XDown, YDown) 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]))
}
}

XUp, YUp, _, _ := GetSequenceVerifiable(group, X, Y, Xbar, Ybar, e)

return ps.Prove(pi, g, h, beta2, XUp, YUp, rand, ctx)
}, nil
}

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, 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])

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
XUp[i] = group.Point().Mul(e[0], X[0][i])
YUp[i] = group.Point().Mul(e[0], Y[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++ {
XUp[i] = group.Point().Add(XUp[i],
group.Point().Mul(e[j], X[j][i]))
YUp[i] = group.Point().Add(YUp[i],
group.Point().Mul(e[j], Y[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 XUp, YUp, XDown, YDown
}
Loading

0 comments on commit 199132f

Please sign in to comment.