Skip to content

Commit

Permalink
add Ciphertexts type
Browse files Browse the repository at this point in the history
  • Loading branch information
altergui authored and p4u committed Jan 7, 2025
1 parent d99addc commit f16ff1a
Show file tree
Hide file tree
Showing 9 changed files with 242 additions and 164 deletions.
19 changes: 10 additions & 9 deletions circuits/statetransition/circuit.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/vocdoni/gnark-crypto-primitives/elgamal"
"github.com/vocdoni/gnark-crypto-primitives/utils"
"github.com/vocdoni/vocdoni-z-sandbox/state"
"github.com/vocdoni/vocdoni-z-sandbox/util"
)

var HashFn = utils.MiMCHasher
Expand Down Expand Up @@ -96,7 +97,7 @@ func (circuit Circuit) VerifyMerkleProofs(api frontend.API, hFn utils.Hasher) {

func (circuit Circuit) VerifyMerkleTransitions(api frontend.API, hFn utils.Hasher) {
// verify chain of tree transitions, order here is fundamental.
api.Println("tree transition starts with RootHashBefore:", prettyHex(circuit.RootHashBefore))
api.Println("tree transition starts with RootHashBefore:", util.PrettyHex(circuit.RootHashBefore))
root := circuit.RootHashBefore
for i := range circuit.Ballot {
root = circuit.Ballot[i].Verify(api, hFn, root)
Expand All @@ -106,32 +107,32 @@ func (circuit Circuit) VerifyMerkleTransitions(api frontend.API, hFn utils.Hashe
}
root = circuit.ResultsAdd.Verify(api, hFn, root)
root = circuit.ResultsSub.Verify(api, hFn, root)
api.Println("and final root is", prettyHex(root), "should be equal to RootHashAfter", prettyHex(circuit.RootHashAfter))
api.Println("and final root is", util.PrettyHex(root), "should be equal to RootHashAfter", util.PrettyHex(circuit.RootHashAfter))
api.AssertIsEqual(root, circuit.RootHashAfter)
}

// VerifyBallots counts the ballots using homomorphic encrpytion
func (circuit Circuit) VerifyBallots(api frontend.API) {
ballotSum, overwrittenSum, zero := elgamal.NewCiphertext(), elgamal.NewCiphertext(), elgamal.NewCiphertext()
ballotSum, overwrittenSum, zero := elgamal.NewCiphertexts(), elgamal.NewCiphertexts(), elgamal.NewCiphertexts()
var ballotCount, overwrittenCount frontend.Variable = 0, 0

for _, b := range circuit.Ballot {
// TODO: check that Hash(NewCiphertext) matches b.NewValue
// and Hash(OldCiphertext) matches b.OldValue
ballotSum.Add(api, ballotSum,
elgamal.NewCiphertext().Select(api, b.IsInsertOrUpdate(api), &b.NewCiphertext, zero))
elgamal.NewCiphertexts().Select(api, b.IsInsertOrUpdate(api), &b.NewCiphertexts, zero))

overwrittenSum.Add(api, overwrittenSum,
elgamal.NewCiphertext().Select(api, b.IsUpdate(api), &b.OldCiphertext, zero))
elgamal.NewCiphertexts().Select(api, b.IsUpdate(api), &b.OldCiphertexts, zero))

ballotCount = api.Add(ballotCount, api.Select(b.IsInsertOrUpdate(api), 1, 0))
overwrittenCount = api.Add(overwrittenCount, api.Select(b.IsUpdate(api), 1, 0))
}

circuit.ResultsAdd.NewCiphertext.AssertIsEqual(api,
circuit.ResultsAdd.OldCiphertext.Add(api, &circuit.ResultsAdd.OldCiphertext, ballotSum))
circuit.ResultsSub.NewCiphertext.AssertIsEqual(api,
circuit.ResultsSub.OldCiphertext.Add(api, &circuit.ResultsSub.OldCiphertext, overwrittenSum))
circuit.ResultsAdd.NewCiphertexts.AssertIsEqual(api,
circuit.ResultsAdd.OldCiphertexts.Add(api, &circuit.ResultsAdd.OldCiphertexts, ballotSum))
circuit.ResultsSub.NewCiphertexts.AssertIsEqual(api,
circuit.ResultsSub.OldCiphertexts.Add(api, &circuit.ResultsSub.OldCiphertexts, overwrittenSum))
api.AssertIsEqual(circuit.NumNewVotes, ballotCount)
api.AssertIsEqual(circuit.NumOverwrites, overwrittenCount)
}
65 changes: 35 additions & 30 deletions circuits/statetransition/circuit_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package statetransition_test

import (
"encoding/hex"
"fmt"
"math/big"
"os"
"reflect"
"testing"

"github.com/consensys/gnark-crypto/ecc"
Expand All @@ -18,6 +16,7 @@ import (
"github.com/vocdoni/vocdoni-z-sandbox/circuits/statetransition"
"github.com/vocdoni/vocdoni-z-sandbox/crypto/elgamal"
"github.com/vocdoni/vocdoni-z-sandbox/state"
"github.com/vocdoni/vocdoni-z-sandbox/util"

"github.com/vocdoni/arbo"
"go.vocdoni.io/dvote/db/metadb"
Expand Down Expand Up @@ -105,39 +104,40 @@ func TestCircuitProve(t *testing.T) {
func debugLog(t *testing.T, witness *statetransition.Circuit) {
// js, _ := json.MarshalIndent(witness, "", " ")
// fmt.Printf("\n\n%s\n\n", js)
t.Log("public: RootHashBefore", prettyHex(witness.RootHashBefore))
t.Log("public: RootHashAfter", prettyHex(witness.RootHashAfter))
t.Log("public: NumVotes", prettyHex(witness.NumNewVotes))
t.Log("public: NumOverwrites", prettyHex(witness.NumOverwrites))
t.Log("public: RootHashBefore", util.PrettyHex(witness.RootHashBefore))
t.Log("public: RootHashAfter", util.PrettyHex(witness.RootHashAfter))
t.Log("public: NumVotes", util.PrettyHex(witness.NumNewVotes))
t.Log("public: NumOverwrites", util.PrettyHex(witness.NumOverwrites))
for name, mts := range map[string][statetransition.VoteBatchSize]state.MerkleTransition{
"Ballot": witness.Ballot,
"Commitment": witness.Commitment,
} {
for _, mt := range mts {
t.Log(name, "transitioned", "(root", util.PrettyHex(mt.OldRoot), "->", util.PrettyHex(mt.NewRoot), ")",
"value", mt.OldValue, "->", mt.NewValue,
)
for i := range mt.OldCiphertexts {
t.Log(name, i, "elgamal.C1.X", mt.OldCiphertexts[i].C1.X, "->", mt.NewCiphertexts[i].C1.X)
t.Log(name, i, "elgamal.C1.Y", mt.OldCiphertexts[i].C1.Y, "->", mt.NewCiphertexts[i].C1.Y)
t.Log(name, i, "elgamal.C2.X", mt.OldCiphertexts[i].C2.X, "->", mt.NewCiphertexts[i].C2.X)
t.Log(name, i, "elgamal.C2.Y", mt.OldCiphertexts[i].C2.Y, "->", mt.NewCiphertexts[i].C2.Y)
}
}
}

for name, mt := range map[string]state.MerkleTransition{
"ResultsAdd": witness.ResultsAdd,
"ResultsSub": witness.ResultsSub,
} {
t.Log(name, "transitioned", "(root", prettyHex(mt.OldRoot), "->", prettyHex(mt.NewRoot), ")",
t.Log(name, "transitioned", "(root", util.PrettyHex(mt.OldRoot), "->", util.PrettyHex(mt.NewRoot), ")",
"value", mt.OldValue, "->", mt.NewValue,
)
t.Log(name, "elgamal.C1.X", mt.OldCiphertext.C1.X, "->", mt.NewCiphertext.C1.X)
t.Log(name, "elgamal.C1.Y", mt.OldCiphertext.C1.Y, "->", mt.NewCiphertext.C1.Y)
t.Log(name, "elgamal.C2.X", mt.OldCiphertext.C2.X, "->", mt.NewCiphertext.C2.X)
t.Log(name, "elgamal.C2.Y", mt.OldCiphertext.C2.Y, "->", mt.NewCiphertext.C2.Y)
}
}

func prettyHex(v frontend.Variable) string {
type hasher interface {
HashCode() [16]byte
}
switch v := v.(type) {
case (*big.Int):
return hex.EncodeToString(arbo.BigIntToBytes(32, v)[:4])
case int:
return fmt.Sprintf("%d", v)
case []byte:
return fmt.Sprintf("%x", v[:4])
case hasher:
return fmt.Sprintf("%x", v.HashCode())
default:
return fmt.Sprintf("(%v)=%+v", reflect.TypeOf(v), v)
for i := range mt.OldCiphertexts {
t.Log(name, i, "elgamal.C1.X", mt.OldCiphertexts[i].C1.X, "->", mt.NewCiphertexts[i].C1.X)
t.Log(name, i, "elgamal.C1.Y", mt.OldCiphertexts[i].C1.Y, "->", mt.NewCiphertexts[i].C1.Y)
t.Log(name, i, "elgamal.C2.X", mt.OldCiphertexts[i].C2.X, "->", mt.NewCiphertexts[i].C2.X)
t.Log(name, i, "elgamal.C2.Y", mt.OldCiphertexts[i].C2.Y, "->", mt.NewCiphertexts[i].C2.Y)
}
}
}

Expand Down Expand Up @@ -286,7 +286,12 @@ func newMockVote(index, amount int64) *state.Vote {
panic(fmt.Errorf("error generating public key: %v", err))
}

ballot, err := elgamal.NewCiphertext(publicKey).Encrypt(big.NewInt(int64(amount)), publicKey, nil)
ballot, err := elgamal.NewCiphertexts(publicKey).Encrypt(
[elgamal.NumCiphertexts]*big.Int{
big.NewInt(int64(amount)),
big.NewInt(int64(amount + 1)),
},
publicKey, nil)
if err != nil {
panic(fmt.Errorf("error encrypting: %v", err))
}
Expand Down
29 changes: 0 additions & 29 deletions circuits/statetransition/util.go

This file was deleted.

8 changes: 2 additions & 6 deletions circuits/statetransition/witness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,15 @@ func GenerateWitnesses(o *state.State) (*statetransition.Circuit, error) {
}

// update ResultsAdd
witness.ResultsAdd.OldCiphertext = o.ResultsAdd.ToGnark()
witness.ResultsAdd.NewCiphertext = o.ResultsAdd.Add(o.ResultsAdd, o.BallotSum).ToGnark()
witness.ResultsAdd, err = o.MerkleTransitionFromAddOrUpdate(
state.KeyResultsAdd, o.ResultsAdd.Serialize())
state.KeyResultsAdd, o.ResultsAdd.Add(o.ResultsAdd, o.BallotSum).Serialize())
if err != nil {
return nil, fmt.Errorf("ResultsAdd: %w", err)
}

// update ResultsSub
witness.ResultsSub.OldCiphertext = o.ResultsSub.ToGnark()
witness.ResultsSub.NewCiphertext = o.ResultsSub.Add(o.ResultsSub, o.OverwriteSum).ToGnark()
witness.ResultsSub, err = o.MerkleTransitionFromAddOrUpdate(
state.KeyResultsSub, o.ResultsSub.Serialize())
state.KeyResultsSub, o.ResultsSub.Add(o.ResultsSub, o.OverwriteSum).Serialize())
if err != nil {
return nil, fmt.Errorf("ResultsSub: %w", err)
}
Expand Down
121 changes: 108 additions & 13 deletions crypto/elgamal/ciphertext.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,103 @@ import (
"github.com/vocdoni/vocdoni-z-sandbox/crypto/ecc/format"
)

// size in bytes needed to serialize an ecc.Point coord
const sizePointCoord = 32
// NumCiphertexts represents how many Ciphertexts are grouped
const NumCiphertexts = 2

// sizes in bytes needed to serialize Ciphertexts
const (
sizeCoord = 32
sizePoint = 2 * sizeCoord
SizeCiphertext = 2 * sizePoint
SizeCiphertexts = NumCiphertexts * SizeCiphertext
)

type Ciphertexts [NumCiphertexts]*Ciphertext

func NewCiphertexts(curve ecc.Point) *Ciphertexts {
cs := &Ciphertexts{}
for i := range cs {
cs[i] = NewCiphertext(curve)
}
return cs
}

// Encrypt encrypts a message using the public key provided as elliptic curve point.
// The randomness k can be provided or nil to generate a new one.
func (cs *Ciphertexts) Encrypt(message [NumCiphertexts]*big.Int, publicKey ecc.Point, k *big.Int) (*Ciphertexts, error) {
for i := range cs {
if _, err := cs[i].Encrypt(message[i], publicKey, k); err != nil {
return nil, err
}
}
return cs, nil
}

// Add adds two Ciphertexts and stores the result in the receiver, which is also returned.
func (cs *Ciphertexts) Add(x, y *Ciphertexts) *Ciphertexts {
for i := range cs {
cs[i].Add(x[i], y[i])
}
return cs
}

// Serialize returns a slice of len N*4*32 bytes,
// representing each Ciphertext C1.X, C1.Y, C2.X, C2.Y as little-endian,
// in reduced twisted edwards form.
func (cs *Ciphertexts) Serialize() []byte {
var buf bytes.Buffer
for _, z := range cs {
buf.Write(z.Serialize())
}
return buf.Bytes()
}

// Deserialize reconstructs a Ciphertexts from a slice of bytes.
// The input must be of len N*4*32 bytes (otherwise it returns an error),
// representing each Ciphertext C1.X, C1.Y, C2.X, C2.Y as little-endian,
// in reduced twisted edwards form.
func (cs *Ciphertexts) Deserialize(data []byte) error {
// Validate the input length
if len(data) != SizeCiphertexts {
return fmt.Errorf("invalid input length: got %d bytes, expected %d bytes", len(data), SizeCiphertexts)
}
for i := range cs {
err := cs[i].Deserialize(data[i*SizeCiphertext : (i+1)*SizeCiphertext])
if err != nil {
return err
}
}
return nil
}

// TODO: implement Marshal, Unmarshal, String for Ciphertexts
// // Marshal converts Ciphertexts to a byte slice.
// func (z *Ciphertexts) Marshal() ([]byte, error) {
// return json.Marshal(z)
// }

// // Unmarshal populates Ciphertexts from a byte slice.
// func (z *Ciphertexts) Unmarshal(data []byte) error {
// return json.Unmarshal(data, z)
// }

// // String returns a string representation of the Ciphertexts.
// func (z *Ciphertexts) String() string {
// if z == nil || z.C1 == nil || z.C2 == nil {
// return "{C1: nil, C2: nil}"
// }
// return fmt.Sprintf("{C1: %s, C2: %s}", z.C1.String(), z.C2.String())
// }

// ToGnark returns cs as the struct used by gnark,
// with the points in reduced twisted edwards format
func (cs *Ciphertexts) ToGnark() *gelgamal.Ciphertexts {
gcs := &gelgamal.Ciphertexts{}
for i := range cs {
gcs[i] = *cs[i].ToGnark()
}
return gcs
}

// Ciphertext represents an ElGamal encrypted message with homomorphic properties.
// It is a wrapper for convenience of the elGamal ciphersystem that encapsulates the two points of a ciphertext.
Expand Down Expand Up @@ -65,7 +160,7 @@ func (z *Ciphertext) Serialize() []byte {
c1x, c1y := format.FromTEtoRTE(z.C1.Point())
c2x, c2y := format.FromTEtoRTE(z.C2.Point())
for _, bi := range []*big.Int{c1x, c1y, c2x, c2y} {
buf.Write(arbo.BigIntToBytes(sizePointCoord, bi))
buf.Write(arbo.BigIntToBytes(sizeCoord, bi))
}
return buf.Bytes()
}
Expand All @@ -76,23 +171,23 @@ func (z *Ciphertext) Serialize() []byte {
// in reduced twisted edwards form.
func (z *Ciphertext) Deserialize(data []byte) error {
// Validate the input length
if len(data) != 4*sizePointCoord {
return fmt.Errorf("invalid input length: got %d bytes, expected %d bytes", len(data), 4*sizePointCoord)
if len(data) != SizeCiphertext {
return fmt.Errorf("invalid input length: got %d bytes, expected %d bytes", len(data), SizeCiphertext)
}

// Helper function to extract *big.Int from a 32-byte slice
// Helper function to extract *big.Int from a serialized slice
readBigInt := func(offset int) *big.Int {
return arbo.BytesToBigInt(data[offset : offset+sizePointCoord])
return arbo.BytesToBigInt(data[offset : offset+sizeCoord])
}
// Deserialize each field
// TODO: we wouldn't need the format conversion if SetPoint() accepts the correct format
z.C1 = z.C1.SetPoint(format.FromRTEtoTE(
readBigInt(0*sizePointCoord),
readBigInt(1*sizePointCoord),
readBigInt(0*sizeCoord),
readBigInt(1*sizeCoord),
))
z.C2 = z.C2.SetPoint(format.FromRTEtoTE(
readBigInt(2*sizePointCoord),
readBigInt(3*sizePointCoord),
readBigInt(2*sizeCoord),
readBigInt(3*sizeCoord),
))
return nil
}
Expand All @@ -117,11 +212,11 @@ func (z *Ciphertext) String() string {

// ToGnark returns z as the struct used by gnark,
// with the points in reduced twisted edwards format
func (z *Ciphertext) ToGnark() gelgamal.Ciphertext {
func (z *Ciphertext) ToGnark() *gelgamal.Ciphertext {
// TODO: we wouldn't need the format conversion if Point() returns the correct format
c1x, c1y := format.FromTEtoRTE(z.C1.Point())
c2x, c2y := format.FromTEtoRTE(z.C2.Point())
return gelgamal.Ciphertext{
return &gelgamal.Ciphertext{
C1: twistededwards.Point{X: c1x, Y: c1y},
C2: twistededwards.Point{X: c2x, Y: c2y},
}
Expand Down
Loading

0 comments on commit f16ff1a

Please sign in to comment.