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)
}
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