aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--veto/veto.go329
-rw-r--r--veto/veto_test.go95
-rw-r--r--vote/vote.go233
-rw-r--r--vote/vote_test.go25
4 files changed, 424 insertions, 258 deletions
diff --git a/veto/veto.go b/veto/veto.go
new file mode 100644
index 0000000..f4faa38
--- /dev/null
+++ b/veto/veto.go
@@ -0,0 +1,329 @@
+package veto
+
+import (
+ "bytes"
+ "crypto/rand"
+ "crypto/sha512"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "io"
+ "slices"
+
+ curve "filippo.io/edwards25519"
+)
+
+var b64 = base64.StdEncoding.WithPadding(base64.NoPadding)
+
+type point curve.Point
+type scalar curve.Scalar
+
+// Representation of a vote with veto (true)
+type Vote struct {
+ veto bool
+ private struct {
+ id *scalar
+ x *scalar
+ r *scalar
+ }
+ com *Commitment
+}
+
+// Commitment represents the public data sent by a participant
+// in round 1 of the protocol. It is generated out of a Vote.
+type Commitment struct {
+ Id *point `json:"index"`
+ Points struct {
+ X *point
+ R *point
+ } `json:"points"`
+ Proofs struct {
+ X *Proof
+ R *Proof
+ } `json:"proofs"`
+}
+
+// A Schnorr signature to prove knowledge of v for given g^v and i.
+// Choosing a scalar v randomly, the signature consists of (V, r) with
+//
+// V := g^v, with randomly chosen v
+// r := (v - x*h), with h := H(g, g^v, g^x, i), where i is given by the context.
+//
+// Verification of the signature is by comparing V =?= g^r * g^(x*h)
+type Proof struct {
+ PV *point `json:"V"`
+ Sr *scalar `json:"r"`
+ Id *point `json:"id"`
+}
+
+func randomScalar(random io.Reader) (*scalar, error) {
+ var buf [64]byte
+ if random == nil {
+ random = rand.Reader
+ }
+ random.Read(buf[:])
+ s, e := new(curve.Scalar).SetUniformBytes(buf[:])
+ return (*scalar)(s), e
+}
+
+func (s *scalar) point() *point {
+ p := new(curve.Point).ScalarBaseMult((*curve.Scalar)(s))
+ return (*point)(p)
+}
+
+func (p *point) Bytes() []byte {
+ return ((*curve.Point)(p)).Bytes()
+}
+
+func one() *point {
+ return (*point)(curve.NewIdentityPoint())
+}
+
+type points []*point
+
+func (pts points) prod() (product *point) {
+ product = (*point)(curve.NewIdentityPoint())
+ for _, p := range pts {
+ product = product.mult(p)
+ }
+ return product
+}
+
+// Return p (*) q in group
+func (p *point) mult(q *point) *point {
+ // The implementation in edwards25519 uses addition for the group operation.
+ r := new(curve.Point).Add((*curve.Point)(p), (*curve.Point)(q))
+ return (*point)(r)
+}
+
+// Return n*P , for n scalar, and P point
+func (p *point) scalarMult(s *scalar) *point {
+ r := new(curve.Point).ScalarMult((*curve.Scalar)(s), (*curve.Point)(p))
+ return (*point)(r)
+}
+
+// Return p^(-1)
+func (p *point) inv() *point {
+ n := new(curve.Point).Negate((*curve.Point)(p))
+ return (*point)(n)
+}
+
+func (s *scalar) Bytes() []byte {
+ return ((*curve.Scalar)(s)).Bytes()
+}
+
+func (p *point) equal(q *point) bool {
+ return ((*curve.Point)(p)).Equal((*curve.Point)(q)) == 1
+
+}
+
+// Generates the proof, aka Schnorr signature, for given priv and i.
+// Choosing a scalar v randomly, the signature consists of (V, r) with
+//
+// V := g^v, with randomly chosen v
+// r := (v - x*h), with h := H(g, g^v, g^x, i), where i is given by the context.
+//
+// Verification of the signature is by comparing V =?= g^r * g^(x*h)
+func (x *scalar) proof(id *point) (pr *Proof, e error) {
+ pr = &Proof{Id: id}
+
+ // choose random v
+ v, e := randomScalar(nil)
+ if e != nil {
+ return nil, e
+ }
+
+ // calculate g^v
+ pr.PV = v.point()
+
+ // calculate g^x
+ gx := x.point()
+
+ // calculate h := H(g, g^v, g^x, i)
+ h, e := hash(pr.PV, gx, id)
+ if e != nil {
+ return nil, e
+ }
+
+ // Calculate r := v - x*h
+ xh := new(curve.Scalar).Multiply((*curve.Scalar)(x), h)
+ r := new(curve.Scalar).Subtract((*curve.Scalar)(v), xh)
+ pr.Sr = (*scalar)(r)
+
+ return pr, nil
+}
+
+// Calculate h := H(g, g^v, g^x, i)
+func hash(gv, gx *point, id *point) (*curve.Scalar, error) {
+ h512 := sha512.New()
+ h512.Write(curve.NewGeneratorPoint().Bytes())
+ h512.Write(gv.Bytes())
+ h512.Write(gx.Bytes())
+ h512.Write(id.Bytes())
+ hb := h512.Sum(nil)
+ return new(curve.Scalar).SetUniformBytes(hb)
+}
+
+func combineErr(es ...error) error {
+ var re error
+ for _, e := range es {
+ if e != nil {
+ if re == nil {
+ re = e
+ } else {
+ re = fmt.Errorf("%v and %v", re, e)
+ }
+ }
+ }
+ return re
+}
+
+// Verifies that g^v == g^r*g^(x*h)
+func verifyProof(V *point, Gx *point, r *scalar, id *point) (ok bool) {
+ // Calculate h = H(g, g^v, g^x, id)
+ h, e := hash(V, Gx, id)
+ if e != nil {
+ return false
+ }
+
+ // Calculate g^(x*h) = (g^x)^h
+ gxh := new(curve.Point).ScalarMult(h, (*curve.Point)(Gx))
+
+ // Calculate g^r
+ gr := r.point()
+
+ // Calculate g^r*g^(x*h)
+ // Note that the edwards25519 package uses Addtion as the group
+ grgxh := gr.mult((*point)(gxh))
+
+ // Return true if g^v == g^r*g^(x*h)
+ return V.equal(grgxh)
+}
+
+// Verify verifies the proofs for both, g^x and g^r
+func (c *Commitment) VerifyProofs() (ok bool) {
+ okX := verifyProof(c.Proofs.X.PV, c.Points.X, c.Proofs.X.Sr, c.Id)
+ okR := verifyProof(c.Proofs.R.PV, c.Points.R, c.Proofs.R.Sr, c.Id)
+ return okX && okR
+}
+
+// Generates a vote with commitments and proofs and takes the input for
+// the randomness from the given io.Reader
+func newVoteWithRand(veto bool, rand io.Reader) (v *Vote, e error) {
+ v = &Vote{
+ veto: veto,
+ }
+ var e1, e2, e3 error
+
+ v.private.id, e1 = randomScalar(rand)
+ v.private.x, e2 = randomScalar(rand)
+ v.private.r, e3 = randomScalar(rand)
+
+ e = combineErr(e1, e2, e3)
+ if e != nil {
+ return nil, e
+ }
+
+ c := new(Commitment)
+ v.com = c
+ c.Id = v.private.id.point()
+ c.Points.X = v.private.x.point()
+ c.Points.R = v.private.r.point()
+ c.Proofs.X, e1 = v.private.x.proof(c.Id)
+ c.Proofs.R, e2 = v.private.r.proof(c.Id)
+
+ return v, combineErr(e1, e2)
+}
+
+// NewVote generates a vote for given bit and index, taking crypt/Reader as
+// source for randomness
+func NewVote(bit bool) (vote *Vote, e error) {
+ return newVoteWithRand(bit, nil)
+}
+
+// Generate the commitment to the Vote
+func (v *Vote) Commitment() *Commitment {
+ return v.com
+}
+
+func (p *point) String() string {
+ return b64.EncodeToString(p.Bytes())
+}
+
+func (s *scalar) String() string {
+ return b64.EncodeToString(s.Bytes())
+}
+
+func (s *scalar) MarshalJSON() ([]byte, error) {
+ return []byte(fmt.Sprintf(`"%s"`, s)), nil
+
+}
+func (p *point) MarshalJSON() ([]byte, error) {
+ return []byte(fmt.Sprintf(`"%s"`, p)), nil
+}
+
+func (c *Commitment) String() string {
+ buf := &bytes.Buffer{}
+ dec := json.NewEncoder(buf)
+ dec.SetIndent("", " ")
+ e := dec.Encode(c)
+ if e != nil {
+ return fmt.Sprintf("<error encoding: %v>", e)
+ }
+ return buf.String()
+}
+
+type coms []*Commitment
+
+func (coms coms) prod() (product *point) {
+ product = (*point)(curve.NewIdentityPoint())
+ for _, com := range coms {
+ product = product.mult(com.Points.X)
+ }
+ return product
+}
+
+// For a given slice of commitments of length n, compute
+//
+// g^y_i = Prod(g^x_j, 1, i-1)/Prod(g^x_j, i+1, n)
+func (coms coms) computeGy(index int) *point {
+ gy1 := coms[:index].prod()
+ gy2 := coms[index+1:].prod().inv()
+ return gy1.mult(gy2)
+}
+
+func (v *Vote) Round2(participants []*Commitment) (gcy *point, e error) {
+ var c int
+ for _, p := range participants {
+ if p.Id.equal(v.com.Id) {
+ c += 1
+ }
+ }
+ if c == 0 {
+ return nil, fmt.Errorf("/me(%s) not in participants", v.com.Id)
+ } else if c > 1 {
+ return nil, fmt.Errorf("/me(%s) %d times in participants", v.com.Id, c)
+ }
+
+ slices.SortStableFunc(participants, func(a, b *Commitment) int {
+ return bytes.Compare(a.Id.Bytes(), b.Id.Bytes())
+ })
+
+ index := slices.IndexFunc(participants, func(c *Commitment) bool { return c.Id.equal(v.com.Id) })
+ if index < 0 {
+ // Should not happen!
+ return nil, fmt.Errorf("Wait, what!? Couldn't find /me(%s) after sorting!", v.com.Id)
+ }
+ gy := (coms(participants)).computeGy(index)
+
+ if v.veto {
+ return gy.scalarMult(v.private.r), nil
+ }
+ return gy.scalarMult(v.private.x), nil
+}
+
+func (pts points) IsVetoed() bool {
+ product := pts.prod()
+ one := one()
+ return !one.equal(product)
+} \ No newline at end of file
diff --git a/veto/veto_test.go b/veto/veto_test.go
new file mode 100644
index 0000000..847c7a0
--- /dev/null
+++ b/veto/veto_test.go
@@ -0,0 +1,95 @@
+package veto
+
+import (
+ "math/rand"
+ "testing"
+)
+
+func TestVoteGeneration(t *testing.T) {
+
+ for i := range 100 {
+ veto := i%3 == 1
+ vote, e := newVoteWithRand(veto, nil)
+
+ if e != nil {
+ t.Fatalf("unexpected error: %v", e)
+ }
+ if vote.veto != veto {
+ t.Fatalf("expected veto %t, but got %t", veto, vote.veto)
+ }
+ com := vote.Commitment()
+ if !com.VerifyProofs() {
+ t.Fatalf("Proofs not correct! %+v", com)
+ }
+ }
+}
+
+func TestRound2NoVeto(t *testing.T) {
+ var (
+ vs = []*Vote{}
+ cs = []*Commitment{}
+ gcys = []*point{}
+ num = 100
+ )
+ for i := range num {
+ vote, e := newVoteWithRand(false, nil)
+
+ if e != nil {
+ t.Fatalf("unexpected error: %v", e)
+ }
+ com := vote.Commitment()
+ if !com.VerifyProofs() {
+ t.Fatalf("Proofs not correct for %d! %+v", i, com)
+ }
+ vs = append(vs, vote)
+ cs = append(cs, com)
+ }
+
+ for i := range num {
+ gcy, e := vs[i].Round2(cs)
+ t.Logf("gcy: %v, e: %v", gcy, e)
+ if e != nil {
+ t.Fatalf("Error calculating Round2: %v", e)
+ }
+ gcys = append(gcys, gcy)
+ }
+
+ if (points)(gcys).IsVetoed() {
+ t.Fatalf("veto expected, but got didn't")
+ }
+}
+
+func TestRound2WithVeto(t *testing.T) {
+ var (
+ vs = []*Vote{}
+ cs = []*Commitment{}
+ gcys = []*point{}
+ num = 100
+ index = int(rand.Int31n(int32(num)))
+ )
+ for i := range num {
+ vote, e := newVoteWithRand(i == index, nil)
+ if e != nil {
+ t.Fatalf("unexpected error: %v", e)
+ }
+ com := vote.Commitment()
+ if !com.VerifyProofs() {
+ t.Fatalf("Proofs not correct for %d! %+v", i, com)
+ }
+ vs = append(vs, vote)
+ cs = append(cs, com)
+ }
+
+ for i := range num {
+ gcy, e := vs[i].Round2(cs)
+ t.Logf("gcy: %v, e: %v", gcy, e)
+ if e != nil {
+ t.Fatalf("Error calculating Round2: %v", e)
+ }
+ gcys = append(gcys, gcy)
+ }
+
+ if !(points)(gcys).IsVetoed() {
+ t.Fatalf("not vetoed, but should!")
+ }
+} \ No newline at end of file
diff --git a/vote/vote.go b/vote/vote.go
deleted file mode 100644
index 97367c0..0000000
--- a/vote/vote.go
+++ /dev/null
@@ -1,233 +0,0 @@
-package vote
-
-import (
- "bytes"
- "crypto/rand"
- "crypto/sha512"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io"
-
- curve "filippo.io/edwards25519"
-)
-
-var b64 = base64.StdEncoding.WithPadding(base64.NoPadding)
-
-type point curve.Point
-type scalar curve.Scalar
-
-// Representation of a vote (true or false) of an individual.
-// The .Commitment is sent as data for round1 of the protocol.
-type Vote struct {
- bit bool
- private struct {
- id *scalar
- x *scalar
- r *scalar
- }
-
- Commitment
-}
-
-// Commitment represents the public data sent by a participant
-// in round 1 of the protocol.
-type Commitment struct {
- Id *point `json:"index"`
- Points struct {
- X *point
- R *point
- } `json:"points"`
- Proofs struct {
- X *Proof
- R *Proof
- } `json:"proofs"`
-}
-
-// A Schnorr signature to prove knowledge of v for given g^v and i.
-// Choosing a scalar v randomly, the signature consists of (V, r) with
-//
-// V := g^v, with randomly chosen v
-// r := (v - x*h), with h := H(g, g^v, g^x, i), where i is given by the context.
-//
-// Verification of the signature is by comparing V =?= g^r * g^(x*h)
-type Proof struct {
- PV *point `json:"V"`
- Sr *scalar `json:"r"`
- Id *point `json:"id"`
-}
-
-func randomScalar(random io.Reader) (*scalar, error) {
- var buf [64]byte
- if random == nil {
- random = rand.Reader
- }
- random.Read(buf[:])
- s, e := new(curve.Scalar).SetUniformBytes(buf[:])
- return (*scalar)(s), e
-}
-
-func (s *scalar) point() *point {
- p := new(curve.Point).ScalarBaseMult((*curve.Scalar)(s))
- return (*point)(p)
-}
-
-// Generates the proof, aka Schnorr signature, for given priv and i.
-// Choosing a scalar v randomly, the signature consists of (V, r) with
-//
-// V := g^v, with randomly chosen v
-// r := (v - x*h), with h := H(g, g^v, g^x, i), where i is given by the context.
-//
-// Verification of the signature is by comparing V =?= g^r * g^(x*h)
-func (x *scalar) proof(id *point) (pr *Proof, e error) {
- pr = &Proof{Id: id}
-
- // choose random v
- v, e := randomScalar(nil)
- if e != nil {
- return nil, e
- }
-
- // calculate g^v
- pr.PV = v.point()
-
- // calculate g^x
- gx := x.point()
-
- // calculate h := H(g, g^v, g^x, i)
- h, e := hash(pr.PV, gx, id)
- if e != nil {
- return nil, e
- }
-
- // Calculate r := v - x*h
- xh := new(curve.Scalar).Multiply((*curve.Scalar)(x), h)
- r := new(curve.Scalar).Subtract((*curve.Scalar)(v), xh)
- pr.Sr = (*scalar)(r)
-
- return pr, nil
-}
-
-// Calculate h := H(g, g^v, g^x, i)
-func hash(gv, gx *point, id *point) (*curve.Scalar, error) {
- h512 := sha512.New()
- h512.Write(curve.NewGeneratorPoint().Bytes())
- h512.Write(((*curve.Point)(gv)).Bytes())
- h512.Write(((*curve.Point)(gx)).Bytes())
- h512.Write(((*curve.Point)(id)).Bytes())
- hb := h512.Sum(nil)
- return new(curve.Scalar).SetUniformBytes(hb)
-}
-
-// Generate the proofs for both, the g^x and g^r points.
-func (v *Vote) genProofs() (e error) {
- v.Proofs.X, e = v.private.x.proof(v.Id)
- if e != nil {
- return e
- }
- v.Proofs.R, e = v.private.r.proof(v.Id)
- return e
-}
-
-// Verifies that g^v == g^r*g^(x*h)
-func verifyProof(V *point, Gx *point, r *scalar, id *point) (ok bool) {
- // Calculate h = H(g, g^v, g^x, id)
- h, e := hash(V, Gx, id)
- if e != nil {
- return false
- }
-
- // Calculate g^(x*h) = (g^x)^h
- gxh := new(curve.Point).ScalarMult(h, (*curve.Point)(Gx))
-
- // Calculate g^r
- gr := r.point()
-
- // Calculate g^r*g^(x*h)
- // Note that the edwards25519 package uses Addtion as the group
- grgxh := new(curve.Point).Add((*curve.Point)(gr), gxh)
-
- // Return true if g^v == g^r*g^(x*h)
- return ((*curve.Point)(V)).Equal(grgxh) == 1
-}
-
-func combineErr(e1, e2 error) error {
- if e1 == nil && e2 == nil {
- return nil
- } else if e1 != nil {
- if e2 == nil {
- return e1
- }
- return fmt.Errorf("%v and %v", e1, e2)
- }
- return e2
-}
-
-// Verify verifies the proofs for both, g^x and g^r
-func (v *Vote) VerifyProofs() (ok bool) {
- okX := verifyProof(v.Proofs.X.PV, v.Points.X, v.Proofs.X.Sr, v.Id)
- okR := verifyProof(v.Proofs.R.PV, v.Points.R, v.Proofs.R.Sr, v.Id)
- return okX && okR
-}
-
-// Generates a vote with commitments and proofs and takes the input for
-// the randomness from the given io.Reader
-func newVoteWithRand(bit bool, rand io.Reader) (vote *Vote, e error) {
- vote = &Vote{
- bit: bit,
- }
-
- vote.private.id, e = randomScalar(rand)
- if e != nil {
- return nil, e
- }
- vote.private.x, e = randomScalar(rand)
- if e != nil {
- return nil, e
- }
- vote.private.r, e = randomScalar(rand)
- if e != nil {
- return nil, e
- }
-
- vote.Commitment.Id = vote.private.id.point()
- vote.Commitment.Points.X = vote.private.x.point()
- vote.Commitment.Points.R = vote.private.r.point()
-
- e = vote.genProofs()
-
- return vote, nil
-}
-
-// NewVote generates a vote for given bit and index, taking crypt/Reader as
-// source for randomness
-func NewVote(bit bool) (vote *Vote, e error) {
- return newVoteWithRand(bit, nil)
-}
-
-func (p *point) String() string {
- return b64.EncodeToString(((*curve.Point)(p)).Bytes())
-}
-
-func (s *scalar) String() string {
- return b64.EncodeToString(((*curve.Scalar)(s)).Bytes())
-}
-
-func (s *scalar) MarshalJSON() ([]byte, error) {
- return []byte(fmt.Sprintf(`"%s"`, s)), nil
-
-}
-func (p *point) MarshalJSON() ([]byte, error) {
- return []byte(fmt.Sprintf(`"%s"`, p)), nil
-}
-
-func (c *Commitment) String() string {
- buf := &bytes.Buffer{}
- dec := json.NewEncoder(buf)
- dec.SetIndent("", " ")
- e := dec.Encode(c)
- if e != nil {
- return fmt.Sprintf("<error encoding: %v>", e)
- }
- return buf.String()
-}
diff --git a/vote/vote_test.go b/vote/vote_test.go
deleted file mode 100644
index 437dbe5..0000000
--- a/vote/vote_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package vote
-
-import (
- "testing"
-)
-
-func TestVoteGeneration(t *testing.T) {
-
- for i := range 100 {
- bit := i%3 == 1
- vote, e := newVoteWithRand(bit, nil)
-
- if e != nil {
- t.Fatalf("unexpected error: %v", e)
- }
- if vote.bit != bit {
- t.Fatalf("expected vote %t, but got %t", bit, vote.bit)
- }
- if !vote.VerifyProofs() {
- t.Fatalf("Proofs not correct! %+v", vote)
- }
-
- t.Logf("Generated %s\n", vote)
- }
-}