From bb9afbb770e1a63e6ad155093f310768be9ef118 Mon Sep 17 00:00:00 2001 From: Gui Iribarren Date: Fri, 20 Dec 2024 11:01:42 +0100 Subject: [PATCH] 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) + } +}