From af47299ca17b1ee9524a6de2e0c9d9d1ba928345 Mon Sep 17 00:00:00 2001 From: Gui Iribarren Date: Thu, 19 Dec 2024 02:41:56 +0100 Subject: [PATCH 1/4] add Ciphertexts type --- circuits/statetransition/circuit.go | 14 ++-- circuits/statetransition/circuit_test.go | 17 +++-- circuits/statetransition/witness_test.go | 8 +- crypto/elgamal/ciphertext.go | 93 +++++++++++++++++++++++- state/merkleproof.go | 50 ++++++------- state/state.go | 25 ++++--- state/vote.go | 4 +- 7 files changed, 154 insertions(+), 57 deletions(-) diff --git a/circuits/statetransition/circuit.go b/circuits/statetransition/circuit.go index 80d0a51..97beb92 100644 --- a/circuits/statetransition/circuit.go +++ b/circuits/statetransition/circuit.go @@ -112,26 +112,26 @@ func (circuit Circuit) VerifyMerkleTransitions(api frontend.API, hFn utils.Hashe // 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) } diff --git a/circuits/statetransition/circuit_test.go b/circuits/statetransition/circuit_test.go index ec0a067..65f8089 100644 --- a/circuits/statetransition/circuit_test.go +++ b/circuits/statetransition/circuit_test.go @@ -110,10 +110,12 @@ func debugLog(t *testing.T, witness *statetransition.Circuit) { t.Log(name, "transitioned", "(root", prettyHex(mt.OldRoot), "->", 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) + 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) + } } } @@ -266,7 +268,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)), + }, + publicKey, nil) if err != nil { panic(fmt.Errorf("error encrypting: %v", err)) } diff --git a/circuits/statetransition/witness_test.go b/circuits/statetransition/witness_test.go index 63d33e9..59350dc 100644 --- a/circuits/statetransition/witness_test.go +++ b/circuits/statetransition/witness_test.go @@ -64,8 +64,8 @@ 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.OldCiphertexts = o.ResultsAdd.ToGnark() + witness.ResultsAdd.NewCiphertexts = o.ResultsAdd.Add(o.ResultsAdd, o.BallotSum).ToGnark() witness.ResultsAdd, err = o.MerkleTransitionFromAddOrUpdate( state.KeyResultsAdd, o.ResultsAdd.Serialize()) if err != nil { @@ -73,8 +73,8 @@ func GenerateWitnesses(o *state.State) (*statetransition.Circuit, error) { } // update ResultsSub - witness.ResultsSub.OldCiphertext = o.ResultsSub.ToGnark() - witness.ResultsSub.NewCiphertext = o.ResultsSub.Add(o.ResultsSub, o.OverwriteSum).ToGnark() + witness.ResultsSub.OldCiphertexts = o.ResultsSub.ToGnark() + witness.ResultsSub.NewCiphertexts = o.ResultsSub.Add(o.ResultsSub, o.OverwriteSum).ToGnark() witness.ResultsSub, err = o.MerkleTransitionFromAddOrUpdate( state.KeyResultsSub, o.ResultsSub.Serialize()) if err != nil { diff --git a/crypto/elgamal/ciphertext.go b/crypto/elgamal/ciphertext.go index 0d9e4c7..b883c65 100644 --- a/crypto/elgamal/ciphertext.go +++ b/crypto/elgamal/ciphertext.go @@ -13,6 +13,95 @@ import ( "github.com/vocdoni/vocdoni-z-sandbox/crypto/ecc/format" ) +const NumCiphertexts = 2 + +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) != NumCiphertexts*4*sizePointCoord { + return fmt.Errorf("invalid input length: got %d bytes, expected %d bytes", len(data), NumCiphertexts*4*sizePointCoord) + } + for i := range cs { + err := cs[i].Deserialize(data[i*sizePointCoord : (i+1)*sizePointCoord]) + 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 +} + // size in bytes needed to serialize an ecc.Point coord const sizePointCoord = 32 @@ -117,11 +206,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}, } diff --git a/state/merkleproof.go b/state/merkleproof.go index 6c5c7b6..bec3588 100644 --- a/state/merkleproof.go +++ b/state/merkleproof.go @@ -148,10 +148,10 @@ type MerkleTransition struct { Fnc1 frontend.Variable // TODO: replace Is*ElGamal by a check on len(Ciphertext) or something? - IsOldElGamal frontend.Variable - IsNewElGamal frontend.Variable - OldCiphertext gelgamal.Ciphertext - NewCiphertext gelgamal.Ciphertext + IsOldElGamal frontend.Variable + IsNewElGamal frontend.Variable + OldCiphertexts *gelgamal.Ciphertexts + NewCiphertexts *gelgamal.Ciphertexts } // MerkleTransitionFromArboProofPair generates a MerkleTransition based on the pair of proofs passed @@ -182,20 +182,20 @@ func MerkleTransitionFromArboProofPair(before, after *ArboProof) MerkleTransitio mpBefore := MerkleProofFromArboProof(before) mpAfter := MerkleProofFromArboProof(after) return MerkleTransition{ - Siblings: mpBefore.Siblings, - OldRoot: mpBefore.Root, - OldKey: mpBefore.Key, - OldValue: mpBefore.Value, - NewRoot: mpAfter.Root, - NewKey: mpAfter.Key, - NewValue: mpAfter.Value, - IsOld0: isOld0, - Fnc0: fnc0, - Fnc1: fnc1, - IsOldElGamal: 0, - IsNewElGamal: 0, - OldCiphertext: *gelgamal.NewCiphertext(), - NewCiphertext: *gelgamal.NewCiphertext(), + Siblings: mpBefore.Siblings, + OldRoot: mpBefore.Root, + OldKey: mpBefore.Key, + OldValue: mpBefore.Value, + NewRoot: mpAfter.Root, + NewKey: mpAfter.Key, + NewValue: mpAfter.Value, + IsOld0: isOld0, + Fnc0: fnc0, + Fnc1: fnc1, + IsOldElGamal: 0, + IsNewElGamal: 0, + OldCiphertexts: gelgamal.NewCiphertexts(), + NewCiphertexts: gelgamal.NewCiphertexts(), } } @@ -208,22 +208,22 @@ func (o *State) MerkleTransitionFromAddOrUpdate(k []byte, v []byte) (MerkleTrans } mp := MerkleTransitionFromArboProofPair(mpBefore, mpAfter) - oldCiphertext, newCiphertext := elgamal.NewCiphertext(Curve), elgamal.NewCiphertext(Curve) + oldCiphertexts, newCiphertexts := elgamal.NewCiphertexts(Curve), elgamal.NewCiphertexts(Curve) if len(mpBefore.Value) > 32 { - if err := oldCiphertext.Deserialize(mpBefore.Value); err != nil { + if err := oldCiphertexts.Deserialize(mpBefore.Value); err != nil { return MerkleTransition{}, err } mp.IsOldElGamal = 1 } if len(mpAfter.Value) > 32 { - if err := newCiphertext.Deserialize(mpAfter.Value); err != nil { + if err := newCiphertexts.Deserialize(mpAfter.Value); err != nil { return MerkleTransition{}, err } mp.IsNewElGamal = 1 } - mp.OldCiphertext = oldCiphertext.ToGnark() - mp.NewCiphertext = newCiphertext.ToGnark() + mp.OldCiphertexts = oldCiphertexts.ToGnark() + mp.NewCiphertexts = newCiphertexts.ToGnark() return mp, nil } @@ -257,11 +257,11 @@ func (mp *MerkleTransition) Verify(api frontend.API, hFn utils.Hasher, oldRoot f api.AssertIsEqual(oldRoot, mp.OldRoot) hash1Old := api.Select(mp.IsOldElGamal, - smt.Hash1(api, hFn, mp.OldKey, mp.OldCiphertext.Serialize()...), + smt.Hash1(api, hFn, mp.OldKey, mp.OldCiphertexts.Serialize()...), smt.Hash1(api, hFn, mp.OldKey, mp.OldValue), ) hash1New := api.Select(mp.IsNewElGamal, - smt.Hash1(api, hFn, mp.NewKey, mp.NewCiphertext.Serialize()...), + smt.Hash1(api, hFn, mp.NewKey, mp.NewCiphertexts.Serialize()...), smt.Hash1(api, hFn, mp.NewKey, mp.NewValue), ) diff --git a/state/state.go b/state/state.go index 5ab4fe4..eacbd01 100644 --- a/state/state.go +++ b/state/state.go @@ -1,6 +1,7 @@ package state import ( + "fmt" "math/big" "github.com/vocdoni/arbo" @@ -43,10 +44,10 @@ type State struct { dbTx db.WriteTx // TODO: unexport these, add ArboProofs and only export those via a method - ResultsAdd *elgamal.Ciphertext - ResultsSub *elgamal.Ciphertext - BallotSum *elgamal.Ciphertext - OverwriteSum *elgamal.Ciphertext + ResultsAdd *elgamal.Ciphertexts + ResultsSub *elgamal.Ciphertexts + BallotSum *elgamal.Ciphertexts + OverwriteSum *elgamal.Ciphertexts ballotCount int overwriteCount int votes []*Vote @@ -87,10 +88,10 @@ func (o *State) Initialize(censusRoot, ballotMode, encryptionKey []byte) error { if err := o.tree.Add(KeyEncryptionKey, encryptionKey); err != nil { return err } - if err := o.tree.Add(KeyResultsAdd, elgamal.NewCiphertext(Curve).Serialize()); err != nil { + if err := o.tree.Add(KeyResultsAdd, elgamal.NewCiphertexts(Curve).Serialize()); err != nil { return err } - if err := o.tree.Add(KeyResultsSub, elgamal.NewCiphertext(Curve).Serialize()); err != nil { + if err := o.tree.Add(KeyResultsSub, elgamal.NewCiphertexts(Curve).Serialize()); err != nil { return err } return nil @@ -106,10 +107,10 @@ func (o *State) Close() error { func (o *State) StartBatch() error { o.dbTx = o.db.WriteTx() if o.ResultsAdd == nil { - o.ResultsAdd = elgamal.NewCiphertext(Curve) + o.ResultsAdd = elgamal.NewCiphertexts(Curve) } if o.ResultsSub == nil { - o.ResultsSub = elgamal.NewCiphertext(Curve) + o.ResultsSub = elgamal.NewCiphertexts(Curve) } { @@ -118,7 +119,7 @@ func (o *State) StartBatch() error { return err } if err := o.ResultsAdd.Deserialize(v); err != nil { - return err + return fmt.Errorf("ResultsAdd: %w", err) } } { @@ -127,12 +128,12 @@ func (o *State) StartBatch() error { return err } if err := o.ResultsSub.Deserialize(v); err != nil { - return err + return fmt.Errorf("ResultsSub: %w", err) } } - o.BallotSum = elgamal.NewCiphertext(Curve) - o.OverwriteSum = elgamal.NewCiphertext(Curve) + o.BallotSum = elgamal.NewCiphertexts(Curve) + o.OverwriteSum = elgamal.NewCiphertexts(Curve) o.ballotCount = 0 o.overwriteCount = 0 o.votes = []*Vote{} diff --git a/state/vote.go b/state/vote.go index 81a1564..35e1225 100644 --- a/state/vote.go +++ b/state/vote.go @@ -10,7 +10,7 @@ import ( // Vote describes a vote with homomorphic ballot type Vote struct { Nullifier []byte - Ballot *elgamal.Ciphertext + Ballot *elgamal.Ciphertexts Address []byte Commitment *big.Int } @@ -28,7 +28,7 @@ func (o *State) AddVote(v *Vote) error { // if nullifier exists, it's a vote overwrite, need to count the overwritten vote // so it's later added to circuit.ResultsSub if _, value, err := o.tree.Get(v.Nullifier); err == nil { - oldVote := elgamal.NewCiphertext(Curve) + oldVote := elgamal.NewCiphertexts(Curve) if err := oldVote.Deserialize(value); err != nil { return err } From e8c110d7c6db7888caac1206ace45fbb7a53f5db Mon Sep 17 00:00:00 2001 From: Gui Iribarren Date: Fri, 20 Dec 2024 10:05:17 +0100 Subject: [PATCH 2/4] fixup! add Ciphertexts type --- crypto/elgamal/ciphertext.go | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/crypto/elgamal/ciphertext.go b/crypto/elgamal/ciphertext.go index b883c65..aa17975 100644 --- a/crypto/elgamal/ciphertext.go +++ b/crypto/elgamal/ciphertext.go @@ -13,8 +13,17 @@ import ( "github.com/vocdoni/vocdoni-z-sandbox/crypto/ecc/format" ) +// 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 { @@ -61,11 +70,11 @@ func (cs *Ciphertexts) Serialize() []byte { // in reduced twisted edwards form. func (cs *Ciphertexts) Deserialize(data []byte) error { // Validate the input length - if len(data) != NumCiphertexts*4*sizePointCoord { - return fmt.Errorf("invalid input length: got %d bytes, expected %d bytes", len(data), NumCiphertexts*4*sizePointCoord) + 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*sizePointCoord : (i+1)*sizePointCoord]) + err := cs[i].Deserialize(data[i*SizeCiphertext : (i+1)*SizeCiphertext]) if err != nil { return err } @@ -102,9 +111,6 @@ func (cs *Ciphertexts) ToGnark() *gelgamal.Ciphertexts { return gcs } -// size in bytes needed to serialize an ecc.Point coord -const sizePointCoord = 32 - // 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. type Ciphertext struct { @@ -154,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() } @@ -165,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 } From bb9afbb770e1a63e6ad155093f310768be9ef118 Mon Sep 17 00:00:00 2001 From: Gui Iribarren Date: Fri, 20 Dec 2024 11:01:42 +0100 Subject: [PATCH 3/4] fixup! fixup! add Ciphertexts type --- circuits/statetransition/circuit.go | 13 +++-- circuits/statetransition/circuit_test.go | 50 ++++++++-------- circuits/statetransition/util.go | 29 ---------- circuits/statetransition/witness_test.go | 8 +-- crypto/elgamal/ciphertext.go | 2 +- state/merkleproof.go | 74 ++++++++++-------------- util/utils.go | 23 ++++++++ 7 files changed, 87 insertions(+), 112 deletions(-) delete mode 100644 circuits/statetransition/util.go diff --git a/circuits/statetransition/circuit.go b/circuits/statetransition/circuit.go index 97beb92..9cd4414 100644 --- a/circuits/statetransition/circuit.go +++ b/circuits/statetransition/circuit.go @@ -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 @@ -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) @@ -106,7 +107,7 @@ 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) } @@ -119,19 +120,19 @@ func (circuit Circuit) VerifyBallots(api frontend.API) { // TODO: check that Hash(NewCiphertext) matches b.NewValue // and Hash(OldCiphertext) matches b.OldValue ballotSum.Add(api, ballotSum, - elgamal.NewCiphertexts().Select(api, b.IsInsertOrUpdate(api), b.NewCiphertexts, zero)) + elgamal.NewCiphertexts().Select(api, b.IsInsertOrUpdate(api), &b.NewCiphertexts, zero)) overwrittenSum.Add(api, overwrittenSum, - elgamal.NewCiphertexts().Select(api, b.IsUpdate(api), b.OldCiphertexts, 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.NewCiphertexts.AssertIsEqual(api, - circuit.ResultsAdd.OldCiphertexts.Add(api, circuit.ResultsAdd.OldCiphertexts, ballotSum)) + circuit.ResultsAdd.OldCiphertexts.Add(api, &circuit.ResultsAdd.OldCiphertexts, ballotSum)) circuit.ResultsSub.NewCiphertexts.AssertIsEqual(api, - circuit.ResultsSub.OldCiphertexts.Add(api, circuit.ResultsSub.OldCiphertexts, overwrittenSum)) + circuit.ResultsSub.OldCiphertexts.Add(api, &circuit.ResultsSub.OldCiphertexts, overwrittenSum)) api.AssertIsEqual(circuit.NumNewVotes, ballotCount) api.AssertIsEqual(circuit.NumOverwrites, overwrittenCount) } diff --git a/circuits/statetransition/circuit_test.go b/circuits/statetransition/circuit_test.go index 65f8089..456b146 100644 --- a/circuits/statetransition/circuit_test.go +++ b/circuits/statetransition/circuit_test.go @@ -1,11 +1,9 @@ package statetransition_test import ( - "encoding/hex" "fmt" "math/big" "os" - "reflect" "testing" "github.com/consensys/gnark-crypto/ecc" @@ -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" @@ -99,15 +98,32 @@ 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, ) for i := range mt.OldCiphertexts { @@ -119,24 +135,6 @@ func debugLog(t *testing.T, witness *statetransition.Circuit) { } } -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) - } -} - type CircuitBallots struct { statetransition.Circuit } @@ -271,7 +269,7 @@ func newMockVote(index, amount int64) *state.Vote { ballot, err := elgamal.NewCiphertexts(publicKey).Encrypt( [elgamal.NumCiphertexts]*big.Int{ big.NewInt(int64(amount)), - big.NewInt(int64(amount)), + big.NewInt(int64(amount + 1)), }, publicKey, nil) if err != nil { diff --git a/circuits/statetransition/util.go b/circuits/statetransition/util.go deleted file mode 100644 index bb4dbbe..0000000 --- a/circuits/statetransition/util.go +++ /dev/null @@ -1,29 +0,0 @@ -package statetransition - -import ( - "encoding/hex" - "fmt" - "math/big" - "reflect" - - "github.com/consensys/gnark/frontend" - "github.com/vocdoni/arbo" -) - -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) - } -} diff --git a/circuits/statetransition/witness_test.go b/circuits/statetransition/witness_test.go index 59350dc..f37bd0c 100644 --- a/circuits/statetransition/witness_test.go +++ b/circuits/statetransition/witness_test.go @@ -64,19 +64,15 @@ func GenerateWitnesses(o *state.State) (*statetransition.Circuit, error) { } // update ResultsAdd - witness.ResultsAdd.OldCiphertexts = o.ResultsAdd.ToGnark() - witness.ResultsAdd.NewCiphertexts = 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.OldCiphertexts = o.ResultsSub.ToGnark() - witness.ResultsSub.NewCiphertexts = 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) } diff --git a/crypto/elgamal/ciphertext.go b/crypto/elgamal/ciphertext.go index aa17975..d615969 100644 --- a/crypto/elgamal/ciphertext.go +++ b/crypto/elgamal/ciphertext.go @@ -106,7 +106,7 @@ func (cs *Ciphertexts) Deserialize(data []byte) error { func (cs *Ciphertexts) ToGnark() *gelgamal.Ciphertexts { gcs := &gelgamal.Ciphertexts{} for i := range cs { - gcs[i] = cs[i].ToGnark() + gcs[i] = *cs[i].ToGnark() } return gcs } diff --git a/state/merkleproof.go b/state/merkleproof.go index bec3588..05707c4 100644 --- a/state/merkleproof.go +++ b/state/merkleproof.go @@ -2,20 +2,19 @@ package state import ( "bytes" - "encoding/hex" "errors" "fmt" "math/big" - "reflect" "github.com/consensys/gnark/frontend" "github.com/vocdoni/arbo" gelgamal "github.com/vocdoni/gnark-crypto-primitives/elgamal" + "github.com/vocdoni/gnark-crypto-primitives/utils" garbo "github.com/vocdoni/gnark-crypto-primitives/tree/arbo" "github.com/vocdoni/gnark-crypto-primitives/tree/smt" - "github.com/vocdoni/gnark-crypto-primitives/utils" "github.com/vocdoni/vocdoni-z-sandbox/crypto/elgamal" + "github.com/vocdoni/vocdoni-z-sandbox/util" ) // ArboProof stores the proof in arbo native types @@ -95,6 +94,18 @@ func (o *State) GenMerkleProof(k []byte) (MerkleProof, error) { // MerkleProofFromArboProof converts an ArboProof into a MerkleProof func MerkleProofFromArboProof(p *ArboProof) MerkleProof { + padSiblings := func(unpackedSiblings [][]byte) [MaxLevels]frontend.Variable { + paddedSiblings := [MaxLevels]frontend.Variable{} + for i := range MaxLevels { + if i < len(unpackedSiblings) { + paddedSiblings[i] = arbo.BytesToBigInt(unpackedSiblings[i]) + } else { + paddedSiblings[i] = big.NewInt(0) + } + } + return paddedSiblings + } + fnc := 0 // inclusion if !p.Existence { fnc = 1 // non-inclusion @@ -108,22 +119,12 @@ func MerkleProofFromArboProof(p *ArboProof) MerkleProof { } } -func padSiblings(unpackedSiblings [][]byte) [MaxLevels]frontend.Variable { - paddedSiblings := [MaxLevels]frontend.Variable{} - for i := range MaxLevels { - if i < len(unpackedSiblings) { - paddedSiblings[i] = arbo.BytesToBigInt(unpackedSiblings[i]) - } else { - paddedSiblings[i] = big.NewInt(0) - } - } - return paddedSiblings -} - // Verify uses garbo.CheckInclusionProof to verify that: // - mp.Root matches passed root // - Key + Value belong to Root func (mp *MerkleProof) VerifyProof(api frontend.API, hFn utils.Hasher, root frontend.Variable) { + api.Println("verify proof", mp.String()) // TODO: remove this debug log + api.AssertIsEqual(root, mp.Root) if err := garbo.CheckInclusionProof(api, hFn, mp.Key, mp.Value, mp.Root, mp.Siblings[:]); err != nil { @@ -131,6 +132,10 @@ func (mp *MerkleProof) VerifyProof(api frontend.API, hFn utils.Hasher, root fron } } +func (mp *MerkleProof) String() string { + return fmt.Sprint(mp.Key, "=", mp.Value, " -> ", util.PrettyHex(mp.Root)) +} + // MerkleTransition stores a pair of leaves and root hashes, and a single path common to both proofs type MerkleTransition struct { // NewKey + NewValue hashed through Siblings path, should produce NewRoot hash @@ -150,8 +155,8 @@ type MerkleTransition struct { // TODO: replace Is*ElGamal by a check on len(Ciphertext) or something? IsOldElGamal frontend.Variable IsNewElGamal frontend.Variable - OldCiphertexts *gelgamal.Ciphertexts - NewCiphertexts *gelgamal.Ciphertexts + OldCiphertexts gelgamal.Ciphertexts + NewCiphertexts gelgamal.Ciphertexts } // MerkleTransitionFromArboProofPair generates a MerkleTransition based on the pair of proofs passed @@ -194,8 +199,8 @@ func MerkleTransitionFromArboProofPair(before, after *ArboProof) MerkleTransitio Fnc1: fnc1, IsOldElGamal: 0, IsNewElGamal: 0, - OldCiphertexts: gelgamal.NewCiphertexts(), - NewCiphertexts: gelgamal.NewCiphertexts(), + OldCiphertexts: *gelgamal.NewCiphertexts(), + NewCiphertexts: *gelgamal.NewCiphertexts(), } } @@ -222,8 +227,8 @@ func (o *State) MerkleTransitionFromAddOrUpdate(k []byte, v []byte) (MerkleTrans mp.IsNewElGamal = 1 } - mp.OldCiphertexts = oldCiphertexts.ToGnark() - mp.NewCiphertexts = newCiphertexts.ToGnark() + mp.OldCiphertexts = *oldCiphertexts.ToGnark() + mp.NewCiphertexts = *newCiphertexts.ToGnark() return mp, nil } @@ -252,7 +257,7 @@ func (o *State) MerkleTransitionFromNoop() (MerkleTransition, error) { // // and returns mp.NewRoot func (mp *MerkleTransition) Verify(api frontend.API, hFn utils.Hasher, oldRoot frontend.Variable) frontend.Variable { - mp.printDebugLog(api) + api.Println("verify transition", mp.String()) // TODO: remove this debug log api.AssertIsEqual(oldRoot, mp.OldRoot) @@ -281,28 +286,9 @@ func (mp *MerkleTransition) Verify(api frontend.API, hFn utils.Hasher, oldRoot f return mp.NewRoot } -// TODO: remove this debug log -func (mp *MerkleTransition) printDebugLog(api frontend.API) { - prettyHex := func(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) - } - } - - api.Println("verify transition", prettyHex(mp.OldRoot), "->", prettyHex(mp.NewRoot), "|", - mp.OldKey, "=", mp.OldValue, "->", mp.NewKey, "=", mp.NewValue) +func (mp *MerkleTransition) String() string { + return fmt.Sprint(util.PrettyHex(mp.OldRoot), " -> ", util.PrettyHex(mp.NewRoot), " | ", + mp.OldKey, "=", mp.OldValue, " -> ", mp.NewKey, "=", mp.NewValue) } // IsUpdate returns true when mp.Fnc0 == 0 && mp.Fnc1 == 1 diff --git a/util/utils.go b/util/utils.go index 5f740d4..e1f4390 100644 --- a/util/utils.go +++ b/util/utils.go @@ -2,8 +2,13 @@ package util import ( "crypto/rand" + "encoding/hex" "fmt" "math/big" + "reflect" + + "github.com/consensys/gnark/frontend" + "github.com/vocdoni/arbo" ) // RandomBytes generates a random byte slice of length n. @@ -62,3 +67,21 @@ func BigToFF(iv *big.Int) *big.Int { } return z.Mod(iv, bn254BaseField) } + +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) + } +} From 77941e8fb7016a6b0e55c6209202f5ca65a5faab Mon Sep 17 00:00:00 2001 From: Gui Iribarren Date: Fri, 20 Dec 2024 16:32:16 +0100 Subject: [PATCH 4/4] bump github.com/vocdoni/gnark-crypto-primitives@ba1eee831 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ea1f0bd..0d7436e 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/rs/zerolog v1.33.0 github.com/vocdoni/arbo v0.0.0-20241216103934-e64315269b49 github.com/vocdoni/circom2gnark v1.0.1-0.20241118090531-f24bf0de0e2f - github.com/vocdoni/gnark-crypto-primitives v0.0.2-0.20241218124633-bd3f44d2cb73 + github.com/vocdoni/gnark-crypto-primitives v0.0.2-0.20241220102053-ba1eee831862 go.vocdoni.io/dvote v1.10.2-0.20241024102542-c1ce6d744bc5 ) diff --git a/go.sum b/go.sum index 4e08804..8e281f2 100644 --- a/go.sum +++ b/go.sum @@ -209,8 +209,8 @@ github.com/vocdoni/arbo v0.0.0-20241216103934-e64315269b49 h1:GMyepEuxLflqhdDHts github.com/vocdoni/arbo v0.0.0-20241216103934-e64315269b49/go.mod h1:wXxPP+5vkT5t54lrKz6bCXKIyv8aRplKq8uCFb2wgy4= github.com/vocdoni/circom2gnark v1.0.1-0.20241118090531-f24bf0de0e2f h1:iy2/GnPg5IdlkqslXUwGmqlqONDZSDnDu+1+h9LSDwM= github.com/vocdoni/circom2gnark v1.0.1-0.20241118090531-f24bf0de0e2f/go.mod h1:A1WU0hL7rO9oZlvp82you2uCc4T3/ySi1UNW6N6hBJs= -github.com/vocdoni/gnark-crypto-primitives v0.0.2-0.20241218124633-bd3f44d2cb73 h1:pfmyx98qiu1KpKV/OnjL0UMZ2oH0CueozIOA2gxYnAk= -github.com/vocdoni/gnark-crypto-primitives v0.0.2-0.20241218124633-bd3f44d2cb73/go.mod h1:SWuNIPw2nWhnyLNWnLj2c/6U/51LCGrbx/nLM5E4Lig= +github.com/vocdoni/gnark-crypto-primitives v0.0.2-0.20241220102053-ba1eee831862 h1:Qu6Mv+jwFM0eSV/gYTZUYYImZsN4p3gTOSEPxqxPdM8= +github.com/vocdoni/gnark-crypto-primitives v0.0.2-0.20241220102053-ba1eee831862/go.mod h1:SWuNIPw2nWhnyLNWnLj2c/6U/51LCGrbx/nLM5E4Lig= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=