-
Notifications
You must be signed in to change notification settings - Fork 168
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #457 from dedis/feature/neff-shuffle-sequences
Adds neff shuffling of sequences
- Loading branch information
Showing
5 changed files
with
451 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.