diff options
author | Özgür Kesim <oec@codeblau.de> | 2024-03-21 19:02:20 +0100 |
---|---|---|
committer | Özgür Kesim <oec@codeblau.de> | 2024-03-21 19:02:20 +0100 |
commit | 989d09c8511eb10f2af06977d210deff39b6ff9e (patch) | |
tree | d360f2e3aa06d0e6dc2eaaac62274ca602d8821c | |
parent | 458f9b7172148a272b23d61747f0758bf21dbf1f (diff) |
veto, curve: Added an abstraction layer for elliptic curves
This will allow to easily swap various curves and implementations, for benchmarking, f.e.
-rw-r--r-- | curve/curve.go | 61 | ||||
-rw-r--r-- | curve/ed25519.go | 132 | ||||
-rw-r--r-- | veto/veto.go | 183 | ||||
-rw-r--r-- | veto/veto_test.go | 13 |
4 files changed, 254 insertions, 135 deletions
diff --git a/curve/curve.go b/curve/curve.go new file mode 100644 index 0000000..a194cde --- /dev/null +++ b/curve/curve.go @@ -0,0 +1,61 @@ +// This package implements an abstraction for various +// elliptic curve primitives, as multiplicative group. +package curve + +import "io" + +type Data interface { + Bytes() []byte + String() string +} + +type SomeScalar[S Data] interface { + Add(S) S + Sub(S) S + Mult(S) S + Equal(S) bool + + // Maybe later: + // Invert(S) S + // Negate(S) S +} + +type ScalarReader[S any] interface { + RandomScalar() S + ScalarFromReader(io.Reader) (S, error) + ScalarFromBytes([]byte) (S, error) +} + +type MultiplicativeCurve[S SomeScalar[s], s Data, P MPoint[S, s, p], p Data] interface { + ScalarReader[S] + + Identity() P + Generator() P + Product([]P) P + Exp(S) P +} + +type MPoint[S SomeScalar[s], s Data, P Data] interface { + Mult(P) P + Div(P) P + Inv() P + Exp(S) P + Equal(P) bool +} + +// For additive formulation of the curve, use these interfaces: +type AdditiveCurve[S SomeScalar[s], s Data, P APoint[S, s, p], p Data] interface { + ScalarReader[S] + Zero() P + Generator() P + Sum([]P) P + Mul(S) P +} + +type APoint[S SomeScalar[s], s Data, P Data] interface { + Add(P) P + Sub(P) P + Neg(P) P + ScalarMult(S) P + Equal(P) bool +}
\ No newline at end of file diff --git a/curve/ed25519.go b/curve/ed25519.go new file mode 100644 index 0000000..e8e380d --- /dev/null +++ b/curve/ed25519.go @@ -0,0 +1,132 @@ +// Implementation of a MultiplicateCurve, using filippo.io/edwards25519. +// Note that the implementation in edwards25519 uses addition for the group operation, +// while we are exposing a multiplicative version of it. +package curve + +import ( + "crypto/rand" + "encoding/base64" + "io" + + ed "filippo.io/edwards25519" +) + +var Curve25519 = &c25519{} + +type Curve25519Scalar = scalar +type Curve25519Point = point + +type c25519 struct{} +type scalar ed.Scalar +type point ed.Point + +var _ MPoint[*scalar, *scalar, *point] = (*point)(nil) +var _ MultiplicativeCurve[*scalar, *scalar, *point, *point] = Curve25519 +var _ SomeScalar[*scalar] = (*scalar)(nil) + +var b64 = base64.StdEncoding.WithPadding(base64.NoPadding) + +func (s *scalar) Bytes() []byte { + return ((*ed.Scalar)(s)).Bytes() +} + +func (s *scalar) Equal(t *scalar) bool { + return ((*ed.Scalar)(s)).Equal((*ed.Scalar)(t)) == 1 +} + +func (s *scalar) String() string { + return b64.EncodeToString(s.Bytes()) +} + +func (s *scalar) Add(t *scalar) *scalar { + return (*scalar)(new(ed.Scalar).Add((*ed.Scalar)(s), (*ed.Scalar)(t))) +} + +func (s *scalar) Sub(t *scalar) *scalar { + return (*scalar)(new(ed.Scalar).Subtract((*ed.Scalar)(s), (*ed.Scalar)(t))) +} + +func (s *scalar) Mult(t *scalar) *scalar { + return (*scalar)(new(ed.Scalar).Multiply((*ed.Scalar)(s), (*ed.Scalar)(t))) +} + +func (c *c25519) ScalarFromReader(random io.Reader) (*scalar, error) { + var buf [64]byte + if random == nil { + random = rand.Reader + } + random.Read(buf[:]) + return c.ScalarFromBytes(buf[:]) +} + +func (c *c25519) RandomScalar() *scalar { + for i := 0; i < 3; i++ { + if s, e := c.ScalarFromReader(nil); e == nil { + return s + } + } + panic("couldn't generate scalar") +} + +func (c *c25519) ScalarFromBytes(b []byte) (*scalar, error) { + s, e := new(ed.Scalar).SetUniformBytes(b) + return (*scalar)(s), e +} + +func (c *c25519) Exp(s *scalar) *point { + p := new(ed.Point).ScalarBaseMult((*ed.Scalar)(s)) + return (*point)(p) +} + +func (c *c25519) Identity() *point { + return (*point)(ed.NewIdentityPoint()) +} + +func (c *c25519) Generator() *point { + return (*point)(ed.NewGeneratorPoint()) +} + +func (c *c25519) Product(pts []*point) (product *point) { + product = c.Identity() + for _, p := range pts { + product = product.Mult(p) + } + return product +} + +// Return P^n , for n Sc, and P Pt +func (p *point) Exp(s *scalar) *point { + r := new(ed.Point).ScalarMult((*ed.Scalar)(s), (*ed.Point)(p)) + return (*point)(r) +} + +func (p *point) Bytes() []byte { + return ((*ed.Point)(p)).Bytes() +} + +// Return p (*) q in group +func (p *point) Mult(q *point) *point { + r := new(ed.Point).Add((*ed.Point)(p), (*ed.Point)(q)) + return (*point)(r) +} + +// Return p (/) q in group +func (p *point) Div(q *point) *point { + r := new(ed.Point).Subtract((*ed.Point)(p), (*ed.Point)(q)) + return (*point)(r) +} + +// Return p^(-1) +func (p *point) Inv() *point { + n := new(ed.Point).Negate((*ed.Point)(p)) + return (*point)(n) +} + +func (p *point) Equal(q *point) bool { + return ((*ed.Point)(p)).Equal((*ed.Point)(q)) == 1 + +} + +func (p *point) String() string { + return b64.EncodeToString(p.Bytes()) +} diff --git a/veto/veto.go b/veto/veto.go index e59ec43..cb526d6 100644 --- a/veto/veto.go +++ b/veto/veto.go @@ -2,29 +2,25 @@ package veto import ( "bytes" - "crypto/rand" "crypto/sha512" - "encoding/base64" "encoding/json" "fmt" "io" "slices" - curve "filippo.io/edwards25519" + . "kesim.org/seal/curve" ) -var b64 = base64.StdEncoding.WithPadding(base64.NoPadding) +type Scalar = Curve25519Scalar +type Point = Curve25519Point -type point curve.Point -type scalar curve.Scalar - -// Representation of a vote with veto (true) +// Representation of a vote with veto (if set to true) type Vote struct { veto bool private struct { - id *scalar - x *scalar - r *scalar + id *Scalar + x *Scalar + r *Scalar } com *Commitment } @@ -32,10 +28,10 @@ type Vote struct { // 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"` + Id *Point `json:"identity"` Points struct { - X *point - R *point + X *Point + R *Point } `json:"points"` Proofs struct { X *Proof @@ -51,70 +47,9 @@ type Commitment struct { // // 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 - + PV *Point `json:"V"` + Sr *Scalar `json:"r"` + Id *Point `json:"id"` } // Generates the proof, aka Schnorr signature, for given priv and i. @@ -124,20 +59,20 @@ func (p *point) equal(q *point) bool { // 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) { +func proof(x *Scalar, id *Point) (pr *Proof, e error) { pr = &Proof{Id: id} // choose random v - v, e := randomScalar(nil) + v, e := Curve25519.ScalarFromReader(nil) if e != nil { return nil, e } // calculate g^v - pr.PV = v.point() + pr.PV = Curve25519.Exp(v) // calculate g^x - gx := x.point() + gx := Curve25519.Exp(x) // calculate h := H(g, g^v, g^x, i) h, e := hash(pr.PV, gx, id) @@ -146,22 +81,22 @@ func (x *scalar) proof(id *point) (pr *Proof, e error) { } // 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) + xh := x.Mult(h) + r := v.Sub(xh) + pr.Sr = r return pr, nil } // Calculate h := H(g, g^v, g^x, i) -func hash(gv, gx *point, id *point) (*curve.Scalar, error) { +func hash(gv, gx *Point, id *Point) (*Scalar, error) { h512 := sha512.New() - h512.Write(curve.NewGeneratorPoint().Bytes()) + h512.Write(Curve25519.Identity().Bytes()) h512.Write(gv.Bytes()) h512.Write(gx.Bytes()) h512.Write(id.Bytes()) hb := h512.Sum(nil) - return new(curve.Scalar).SetUniformBytes(hb) + return Curve25519.ScalarFromBytes(hb) } func combineErr(es ...error) error { @@ -179,7 +114,7 @@ func combineErr(es ...error) error { } // Verifies that g^v == g^r*g^(x*h) -func verifyProof(V *point, Gx *point, r *scalar, id *point) (ok bool) { +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 { @@ -187,17 +122,17 @@ func verifyProof(V *point, Gx *point, r *scalar, id *point) (ok bool) { } // Calculate g^(x*h) = (g^x)^h - gxh := new(curve.Point).ScalarMult(h, (*curve.Point)(Gx)) + gxh := Gx.Exp(h) // Calculate g^r - gr := r.point() + gr := Curve25519.Exp(r) // Calculate g^r*g^(x*h) // Note that the edwards25519 package uses Addtion as the group - grgxh := gr.mult((*point)(gxh)) + grgxh := gr.Mult(gxh) // Return true if g^v == g^r*g^(x*h) - return V.equal(grgxh) + return V.Equal(grgxh) } // Verify verifies the proofs for both, g^x and g^r @@ -215,9 +150,9 @@ func newVoteWithRand(veto bool, rand io.Reader) (v *Vote, e error) { } var e1, e2, e3 error - v.private.id, e1 = randomScalar(rand) - v.private.x, e2 = randomScalar(rand) - v.private.r, e3 = randomScalar(rand) + v.private.id, e1 = Curve25519.ScalarFromReader(rand) + v.private.x, e2 = Curve25519.ScalarFromReader(rand) + v.private.r, e3 = Curve25519.ScalarFromReader(rand) e = combineErr(e1, e2, e3) if e != nil { @@ -226,11 +161,11 @@ func newVoteWithRand(veto bool, rand io.Reader) (v *Vote, e error) { 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) + c.Id = Curve25519.Exp(v.private.id) + c.Points.X = Curve25519.Exp(v.private.x) + c.Points.R = Curve25519.Exp(v.private.r) + c.Proofs.X, e1 = proof(v.private.x, c.Id) + c.Proofs.R, e2 = proof(v.private.r, c.Id) return v, combineErr(e1, e2) } @@ -246,22 +181,6 @@ 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) @@ -275,29 +194,29 @@ func (c *Commitment) String() string { type coms []*Commitment -func (coms coms) prod() (product *point) { - product = (*point)(curve.NewIdentityPoint()) +func (coms coms) prod() (product *Point) { + product = Curve25519.Identity() for _, com := range coms { - product = product.mult(com.Points.X) + 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 { +func (coms coms) computeGy(index int) *Point { gy1 := coms[:index].prod() - gy2 := coms[index+1:].prod().inv() - return gy1.mult(gy2) + gy2 := coms[index+1:].prod().Inv() + return gy1.Mult(gy2) } // Round2 implements the round 2 of the AV-Net protocol, where a participant // has received all commitments from the other participants and calculates // g^(c*y), where c is either private.r (when veto is true) or private.x. -func (v *Vote) Round2(participants []*Commitment) (gcy *point, e error) { +func (v *Vote) Round2(participants []*Commitment) (gcy *Point, e error) { var c int for _, p := range participants { - if p.Id.equal(v.com.Id) { + if p.Id.Equal(v.com.Id) { c += 1 } } @@ -311,7 +230,7 @@ func (v *Vote) Round2(participants []*Commitment) (gcy *point, e error) { return bytes.Compare(a.Id.Bytes(), b.Id.Bytes()) }) - index := slices.IndexFunc(participants, func(c *Commitment) bool { return c.Id.equal(v.com.Id) }) + 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) @@ -319,16 +238,18 @@ func (v *Vote) Round2(participants []*Commitment) (gcy *point, e error) { gy := (coms(participants)).computeGy(index) if v.veto { - return gy.scalarMult(v.private.r), nil + return gy.Exp(v.private.r), nil } - return gy.scalarMult(v.private.x), nil + return gy.Exp(v.private.x), nil } +type points []*Point + // IsVetoed is the final step in the AV-Net protocol, where each participant, has // received the g^(c_i*y_i) from all other participants and calculates the product // of them. If the result is the unit element of the group, no veto was present. func (pts points) IsVetoed() bool { - product := pts.prod() - one := one() - return !one.equal(product) + product := Curve25519.Product(pts) + one := Curve25519.Identity() + return !one.Equal(product) }
\ No newline at end of file diff --git a/veto/veto_test.go b/veto/veto_test.go index 847c7a0..a5f4fb2 100644 --- a/veto/veto_test.go +++ b/veto/veto_test.go @@ -3,6 +3,8 @@ package veto import ( "math/rand" "testing" + + "kesim.org/seal/curve" ) func TestVoteGeneration(t *testing.T) { @@ -28,7 +30,7 @@ func TestRound2NoVeto(t *testing.T) { var ( vs = []*Vote{} cs = []*Commitment{} - gcys = []*point{} + gcys = []*curve.Curve25519Point{} num = 100 ) for i := range num { @@ -63,7 +65,7 @@ func TestRound2WithVeto(t *testing.T) { var ( vs = []*Vote{} cs = []*Commitment{} - gcys = []*point{} + gcys = []*curve.Curve25519Point{} num = 100 index = int(rand.Int31n(int32(num))) ) @@ -72,6 +74,9 @@ func TestRound2WithVeto(t *testing.T) { if e != nil { t.Fatalf("unexpected error: %v", e) } + if vote.veto { + t.Logf("Veto for index no. %d\n", i) + } com := vote.Commitment() if !com.VerifyProofs() { t.Fatalf("Proofs not correct for %d! %+v", i, com) @@ -82,10 +87,10 @@ func TestRound2WithVeto(t *testing.T) { 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) + t.Fatalf("Error calculating Round2 for [%d]: %v", i, e) } + t.Logf("gcy[%d]: %v", i, gcy) gcys = append(gcys, gcy) } |