Skip to content

Commit

Permalink
Adds neff shuffling of sequences
Browse files Browse the repository at this point in the history
  • Loading branch information
nkcr committed Nov 23, 2021
1 parent b627bb3 commit 5cea1f4
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 8 deletions.
106 changes: 106 additions & 0 deletions examples/neff_shuffle_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
3 changes: 0 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
155 changes: 155 additions & 0 deletions shuffle/sequences.go
Original file line number Diff line number Diff line change
@@ -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: [<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 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
}
91 changes: 88 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,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())
}
}
}
Loading

0 comments on commit 5cea1f4

Please sign in to comment.