diff --git a/crypto/zk/utils.go b/crypto/zk/utils.go new file mode 100644 index 000000000..6bf81e869 --- /dev/null +++ b/crypto/zk/utils.go @@ -0,0 +1,62 @@ +// Package zk provides utilities around the zkSNARK (Groth16) tooling. +package zk + +import ( + "fmt" + "io/ioutil" + "math/big" + + "github.com/vocdoni/go-snark/parsers" + "github.com/vocdoni/go-snark/types" + models "go.vocdoni.io/proto/build/go/models" +) + +func LoadVkFromFile(path string) (*types.Vk, error) { + vkJSON, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + vk, err := parsers.ParseVk(vkJSON) + if err != nil { + return nil, err + } + return vk, nil +} + +func ProtobufZKProofToCircomProof(p *models.ProofZkSNARK) (*types.Proof, []*big.Int, error) { + if len(p.A) != 3 && len(p.B) != 6 && len(p.C) != 3 { + return nil, nil, fmt.Errorf("error on zkProof format") + } + proofString := parsers.ProofString{ + A: p.A, + B: [][]string{ + { + p.B[0], + p.B[1], + }, + { + p.B[2], + p.B[3], + }, + { + p.B[4], + p.B[5], + }, + }, + C: p.C, + } + publicInputsString := p.PublicInputs + + // parse zkProof & PublicInputs from tx.Proof + proof, err := parsers.ProofStringToProof(proofString) + if err != nil { + return nil, nil, err + } + publicInputs, err := parsers.PublicSignalsStringToPublicSignals(publicInputsString) + if err != nil { + return nil, nil, err + } + + return proof, publicInputs, nil +} diff --git a/crypto/zk/utils_test.go b/crypto/zk/utils_test.go new file mode 100644 index 000000000..15fb2fc6b --- /dev/null +++ b/crypto/zk/utils_test.go @@ -0,0 +1,46 @@ +package zk + +import ( + "encoding/json" + "math/big" + "testing" + + qt "github.com/frankban/quicktest" + models "go.vocdoni.io/proto/build/go/models" +) + +func TestProtobufZKProofToCircomProof(t *testing.T) { + protoProof := &models.ProofZkSNARK{ + A: []string{ + "17569240301865190069940703408776620561950070507358357198130890464508555015742", + "14719281396036152924308513019111720587276284390020911371919541479835430607528", + "1", + }, + B: []string{ + "19366523330111704407267566338410994924319459949138724547828188860719056192113", + "3554431156699466263343064300468289853516840393068286815512868805374822045471", + "7069001739799325309551446576989712671469685847725867674777886436500905588451", + "9519609195825772265125524447464801412742967232326200600178197674408796399758", + "1", + "0", + }, + C: []string{ + "1803067082675811010286176187174786634523306099072027370858753233512049893073", + "12821812994233574817558778896965446058705557189286765124075749479926329638171", + "1", + }, + PublicInputs: []string{"1", "2", "3"}, + } + proof, pubInputs, err := ProtobufZKProofToCircomProof(protoProof) + qt.Assert(t, err, qt.IsNil) + + expectedStr := `{"pi_a":"26d7d66de7e4ed7fa7abf7078ef9e4e45bf0000787c451874ee9f31c930bb63e208ad16ae0f6916670dcf183c1d23efe1e2ddc45dbb138d922c79ad1b4bcdaa8","pi_b":"07dbbc9b161442d63d59869a61a2b1724f49f8ce7cd83f92bc33edf4e697d71f2ad1105288e9d12915b079725ad629845a3c6cdd4311b21778be06cc82ae8271150be869d01f1fcb87fb7f4930e690c3b873d29529f3d9db0f8021253806a88e0fa0e9c753288b4110ef539418f37c30de082a0bc60083222e8ca6694b2ef6e3","pi_c":"03fc7ff321b29905a8a64f324906272e69c9a0e7b097b523ef57fc6a320f5ed11c58e39436354cb151affb97a664401f1eb6b54a9f3a6a366543bf6813f5fd1b"}` + + proofJSON, err := json.Marshal(proof) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, expectedStr, qt.Equals, string(proofJSON)) + a := []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} + for i := range a { + qt.Assert(t, a[i].String(), qt.Equals, pubInputs[i].String()) + } +} diff --git a/go.mod b/go.mod index 32e75d5b9..a73e44f3a 100644 --- a/go.mod +++ b/go.mod @@ -50,9 +50,10 @@ require ( github.com/timshannon/badgerhold/v3 v3.0.0-20210415132401-e7c90fb5919f github.com/vocdoni/arbo v0.0.0-20210909102959-f09b0b039255 github.com/vocdoni/go-external-ip v0.0.0-20210705122950-fae6195a1d44 + github.com/vocdoni/go-snark v0.0.0-20210709152824-f6e4c27d7319 github.com/vocdoni/storage-proofs-eth-go v0.1.6 go.uber.org/zap v1.18.1 - go.vocdoni.io/proto v1.0.4-0.20210910085433-e7c056b7c23a + go.vocdoni.io/proto v1.0.4-0.20210910152144-4f30a5b8664c golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d google.golang.org/protobuf v1.27.1 diff --git a/go.sum b/go.sum index 490b0afbc..206110072 100644 --- a/go.sum +++ b/go.sum @@ -354,6 +354,7 @@ github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHj github.com/ethereum/go-ethereum v1.8.27/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= github.com/ethereum/go-ethereum v1.9.3/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= github.com/ethereum/go-ethereum v1.9.12/go.mod h1:PvsVkQmhZFx92Y+h2ylythYlheEDt/uBgFbl61Js/jo= +github.com/ethereum/go-ethereum v1.9.13/go.mod h1:qwN9d1GLyDh0N7Ab8bMGd0H9knaji2jOBm2RrMGjXls= github.com/ethereum/go-ethereum v1.9.20/go.mod h1:JSSTypSMTkGZtAdAChH2wP5dZEvPGh3nUTuDpH+hNrg= github.com/ethereum/go-ethereum v1.9.23/go.mod h1:JIfVb6esrqALTExdz9hRYvrP0xBDf6wCncIu1hNwHpM= github.com/ethereum/go-ethereum v1.9.25/go.mod h1:vMkFiYLHI4tgPw4k2j4MHKoovchFE8plZ0M9VMk4/oM= @@ -676,6 +677,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/iden3/go-iden3-core v0.0.8-0.20200325104031-1ed04a261b78/go.mod h1:N9xiQvwx5EZhrVuO0hW8GBF9Uy0ZbONdhguPHSC+wQg= github.com/iden3/go-iden3-crypto v0.0.4/go.mod h1:LLcgB7DLWAUs+8eBSKne+ZHy5z7xtAmlYlEz0M9M8gE= +github.com/iden3/go-iden3-crypto v0.0.5/go.mod h1:XKw1oDwYn2CIxKOtr7m/mL5jMn4mLOxAxtZBRxQBev8= github.com/iden3/go-iden3-crypto v0.0.6-0.20210308142348-8f85683b2cef h1:72PG9b2eDlLqKszJVLrsoJbpt4CtgJLhKOjH1MJqCVY= github.com/iden3/go-iden3-crypto v0.0.6-0.20210308142348-8f85683b2cef/go.mod h1:oBgthFLboAWi9feaBUFy7OxEcyn9vA1khHSL/WwWFyg= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -1977,6 +1979,8 @@ github.com/vocdoni/blind-ca v0.1.4/go.mod h1:4ouWDqlvXrrNS0Csf3hKA3cuDTmKh6nP7kS github.com/vocdoni/eth-storage-proof v0.1.4-0.20201128112323-de7513ce5e25/go.mod h1:NLA1A55raZ1VNMmKulPUm+Lu9CVetCDVuDCYk5bSYrE= github.com/vocdoni/go-external-ip v0.0.0-20210705122950-fae6195a1d44 h1:OK5B1GPq2zPu2Z1sYaUcAmt3CpILABwYsvkK2raI1AQ= github.com/vocdoni/go-external-ip v0.0.0-20210705122950-fae6195a1d44/go.mod h1:o/kqzlz81Aq5/++p7zIRaaOiEUmCOiap7ulEAUeSQWI= +github.com/vocdoni/go-snark v0.0.0-20210709152824-f6e4c27d7319 h1:W8N7yfnWsVD3l2Sh0pTtXCDd4+LNh58lE427D1U9SVY= +github.com/vocdoni/go-snark v0.0.0-20210709152824-f6e4c27d7319/go.mod h1:A4ZJ8jq+ZbNvxrNUmScv2ghL34A6c6vw5Y1Oza2h7lo= github.com/vocdoni/multirpc v0.1.9/go.mod h1:SETFzlLbdZq2YFGy0udT1u2ouQUU2cIesopAIgyAjOU= github.com/vocdoni/multirpc v0.1.12/go.mod h1:OYWh2wBX0rztgatZC7VEcPW0Ew4Hm8f/msRAO24mpk0= github.com/vocdoni/multirpc v0.1.21/go.mod h1:zCx0l/hu/Vdeav44geFJmK4PAawN+qoo1HYT2icU4GM= @@ -2105,10 +2109,8 @@ go.vocdoni.io/proto v0.1.7/go.mod h1:cyITrt7+sHmUJH06WLu69xB7LBY9c9FakFaBOe8gs/M go.vocdoni.io/proto v0.1.8/go.mod h1:cyITrt7+sHmUJH06WLu69xB7LBY9c9FakFaBOe8gs/M= go.vocdoni.io/proto v0.1.9-0.20210304214308-6f7363b52750/go.mod h1:cyITrt7+sHmUJH06WLu69xB7LBY9c9FakFaBOe8gs/M= go.vocdoni.io/proto v1.0.4-0.20210726091234-bceaf416353b/go.mod h1:QV3gKc9Zf0xHW3o8wEaqSn8iZ94UTl8gOzekxoz3kWs= -go.vocdoni.io/proto v1.0.4-0.20210909161946-f3158498ba88 h1:EJnuknmHjwPQL44d5TVOZJhzVA4oFzZT41etyaRervk= -go.vocdoni.io/proto v1.0.4-0.20210909161946-f3158498ba88/go.mod h1:QV3gKc9Zf0xHW3o8wEaqSn8iZ94UTl8gOzekxoz3kWs= -go.vocdoni.io/proto v1.0.4-0.20210910085433-e7c056b7c23a h1:7QimTKlkCY0O5KRFi0ejafFkLQze7zN0qOE/BuZosbE= -go.vocdoni.io/proto v1.0.4-0.20210910085433-e7c056b7c23a/go.mod h1:oi/WtiBFJ6QwNDv2aUQYwOnUKzYuS/fBqXF8xDNwcGo= +go.vocdoni.io/proto v1.0.4-0.20210910152144-4f30a5b8664c h1:kM/ZBLJ7UQZqqu8INlFbaHDxAt4o7L1omJMgL4AJcJw= +go.vocdoni.io/proto v1.0.4-0.20210910152144-4f30a5b8664c/go.mod h1:oi/WtiBFJ6QwNDv2aUQYwOnUKzYuS/fBqXF8xDNwcGo= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU= go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= diff --git a/vochain/app.go b/vochain/app.go index a21bb1d55..58ecc1362 100644 --- a/vochain/app.go +++ b/vochain/app.go @@ -15,6 +15,7 @@ import ( nm "github.com/tendermint/tendermint/node" ctypes "github.com/tendermint/tendermint/rpc/core/types" tmtypes "github.com/tendermint/tendermint/types" + snarkTypes "github.com/vocdoni/go-snark/types" "google.golang.org/protobuf/proto" "go.vocdoni.io/dvote/config" @@ -40,6 +41,8 @@ type BaseApplication struct { height uint32 timestamp int64 chainId string + // ZkVks contains the VerificationKey for each circuit parameters index + ZkVks []*snarkTypes.Vk } var _ abcitypes.Application = (*BaseApplication)(nil) @@ -323,7 +326,7 @@ func (app *BaseApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.Resp return abcitypes.ResponseCheckTx{Code: 0, Data: data} } if tx, txBytes, signature, err = UnmarshalTx(req.Tx); err == nil { - if data, err = AddTx(tx, txBytes, signature, app.State, TxKey(req.Tx), false); err != nil { + if data, err = app.AddTx(tx, txBytes, signature, TxKey(req.Tx), false); err != nil { log.Debugf("checkTx error: %s", err) return abcitypes.ResponseCheckTx{Code: 1, Data: []byte("addTx " + err.Error())} } @@ -344,7 +347,7 @@ func (app *BaseApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes. defer app.State.TxCounterAdd() if tx, txBytes, signature, err = UnmarshalTx(req.Tx); err == nil { log.Debugf("deliver tx: %s", log.FormatProto(tx)) - if data, err = AddTx(tx, txBytes, signature, app.State, TxKey(req.Tx), true); err != nil { + if data, err = app.AddTx(tx, txBytes, signature, TxKey(req.Tx), true); err != nil { log.Debugf("rejected tx: %v", err) return abcitypes.ResponseDeliverTx{Code: 1, Data: []byte(err.Error())} } diff --git a/vochain/transaction.go b/vochain/transaction.go index 6df0ecdfe..2b8ca305d 100644 --- a/vochain/transaction.go +++ b/vochain/transaction.go @@ -6,8 +6,11 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/vocdoni/arbo" + "github.com/vocdoni/go-snark/verifier" "go.vocdoni.io/dvote/crypto/ethereum" "go.vocdoni.io/dvote/crypto/nacl" + "go.vocdoni.io/dvote/crypto/zk" "go.vocdoni.io/dvote/log" "go.vocdoni.io/dvote/types" models "go.vocdoni.io/proto/build/go/models" @@ -18,32 +21,37 @@ import ( // It returns a bytes value which depends on the transaction type: // Tx_Vote: vote nullifier // default: []byte{} -func AddTx(vtx *models.Tx, txBytes, signature []byte, state *State, +func (app *BaseApplication) AddTx(vtx *models.Tx, txBytes, signature []byte, txID [32]byte, commit bool) ([]byte, error) { - if vtx == nil || state == nil || vtx.Payload == nil { + if vtx == nil || app.State == nil || vtx.Payload == nil { return nil, fmt.Errorf("transaction, state, and/or transaction payload is nil") } switch vtx.Payload.(type) { case *models.Tx_Vote: - v, err := VoteTxCheck(vtx, txBytes, signature, state, txID, commit) + if vtx == nil { + return nil, fmt.Errorf("vote envelope transaction is nil") + } + // get VoteEnvelope from tx + txVote := vtx.GetVote() + v, err := app.VoteEnvelopeCheck(txVote, txBytes, signature, txID, commit) if err != nil || v == nil { return []byte{}, fmt.Errorf("voteTxCheck: %w", err) } if commit { - return v.Nullifier, state.AddVote(v) + return v.Nullifier, app.State.AddVote(v) } return v.Nullifier, nil case *models.Tx_Admin: - if err := AdminTxCheck(vtx, txBytes, signature, state); err != nil { - return []byte{}, fmt.Errorf("adminTxCheck: %w", err) + if err := AdminTxCheck(vtx, txBytes, signature, app.State); err != nil { + return []byte{}, fmt.Errorf("adminTxChek: %w", err) } tx := vtx.GetAdmin() if commit { switch tx.Txtype { case models.TxType_ADD_ORACLE: - return []byte{}, state.AddOracle(common.BytesToAddress(tx.Address)) + return []byte{}, app.State.AddOracle(common.BytesToAddress(tx.Address)) case models.TxType_REMOVE_ORACLE: - return []byte{}, state.RemoveOracle(common.BytesToAddress(tx.Address)) + return []byte{}, app.State.RemoveOracle(common.BytesToAddress(tx.Address)) case models.TxType_ADD_VALIDATOR: pk, err := hexPubKeyToTendermintEd25519(fmt.Sprintf("%x", tx.PublicKey)) if err == nil { @@ -55,35 +63,35 @@ func AddTx(vtx *models.Tx, txBytes, signature []byte, state *State, PubKey: pk.Bytes(), Power: *tx.Power, } - return []byte{}, state.AddValidator(validator) + return []byte{}, app.State.AddValidator(validator) } return []byte{}, fmt.Errorf("addValidator: %w", err) case models.TxType_REMOVE_VALIDATOR: - return []byte{}, state.RemoveValidator(tx.Address) + return []byte{}, app.State.RemoveValidator(tx.Address) case models.TxType_ADD_PROCESS_KEYS: - return []byte{}, state.AddProcessKeys(tx) + return []byte{}, app.State.AddProcessKeys(tx) case models.TxType_REVEAL_PROCESS_KEYS: - return []byte{}, state.RevealProcessKeys(tx) + return []byte{}, app.State.RevealProcessKeys(tx) } } case *models.Tx_NewProcess: - if p, err := NewProcessTxCheck(vtx, txBytes, signature, state); err == nil { + if p, err := NewProcessTxCheck(vtx, txBytes, signature, app.State); err == nil { if commit { tx := vtx.GetNewProcess() if tx.Process == nil { return []byte{}, fmt.Errorf("newProcess process is empty") } - return []byte{}, state.AddProcess(p) + return []byte{}, app.State.AddProcess(p) } } else { return []byte{}, fmt.Errorf("newProcess: %w", err) } case *models.Tx_SetProcess: - if err := SetProcessTxCheck(vtx, txBytes, signature, state); err != nil { + if err := SetProcessTxCheck(vtx, txBytes, signature, app.State); err != nil { return []byte{}, fmt.Errorf("setProcess: %w", err) } if commit { @@ -93,29 +101,29 @@ func AddTx(vtx *models.Tx, txBytes, signature []byte, state *State, if tx.GetStatus() == models.ProcessStatus_PROCESS_UNKNOWN { return []byte{}, fmt.Errorf("set process status, status unknown") } - return []byte{}, state.SetProcessStatus(tx.ProcessId, *tx.Status, true) + return []byte{}, app.State.SetProcessStatus(tx.ProcessId, *tx.Status, true) case models.TxType_SET_PROCESS_RESULTS: if tx.GetResults() == nil { return []byte{}, fmt.Errorf("set process results, results is nil") } - return []byte{}, state.SetProcessResults(tx.ProcessId, tx.Results, true) + return []byte{}, app.State.SetProcessResults(tx.ProcessId, tx.Results, true) case models.TxType_SET_PROCESS_CENSUS: if tx.GetCensusRoot() == nil { return []byte{}, fmt.Errorf("set process census, census root is nil") } - return []byte{}, state.SetProcessCensus(tx.ProcessId, tx.CensusRoot, tx.GetCensusURI(), true) + return []byte{}, app.State.SetProcessCensus(tx.ProcessId, tx.CensusRoot, tx.GetCensusURI(), true) default: return []byte{}, fmt.Errorf("unknown set process tx type") } } case *models.Tx_RegisterKey: - if err := RegisterKeyTxCheck(vtx, txBytes, signature, state); err != nil { + if err := RegisterKeyTxCheck(vtx, txBytes, signature, app.State); err != nil { return []byte{}, fmt.Errorf("registerKeyTx %w", err) } if commit { tx := vtx.GetRegisterKey() - return []byte{}, state.AddToRollingCensus(tx.ProcessId, tx.NewKey, new(big.Int).SetBytes(tx.Weight)) + return []byte{}, app.State.AddToRollingCensus(tx.ProcessId, tx.NewKey, new(big.Int).SetBytes(tx.Weight)) } default: @@ -136,32 +144,31 @@ func UnmarshalTx(content []byte) (*models.Tx, []byte, []byte, error) { return vtx, stx.GetTx(), stx.GetSignature(), proto.Unmarshal(stx.GetTx(), vtx) } -// VoteTxCheck is an abstraction of ABCI checkTx for submitting a vote +// VoteEnvelopeCheck is an abstraction of ABCI checkTx for submitting a vote // All hexadecimal strings should be already sanitized (without 0x) -func VoteTxCheck(vtx *models.Tx, txBytes, signature []byte, state *State, +func (app *BaseApplication) VoteEnvelopeCheck(ve *models.VoteEnvelope, txBytes, signature []byte, txID [32]byte, forCommit bool) (*models.Vote, error) { - tx := vtx.GetVote() // Perform basic/general checks - if tx == nil { - return nil, fmt.Errorf("vote envelope transaction is nil") + if ve == nil { + return nil, fmt.Errorf("vote envelope is nil") } - process, err := state.Process(tx.ProcessId, false) + process, err := app.State.Process(ve.ProcessId, false) if err != nil { return nil, fmt.Errorf("cannot fetch processId: %w", err) } if process == nil || process.EnvelopeType == nil || process.Mode == nil { - return nil, fmt.Errorf("process %x malformed", tx.ProcessId) + return nil, fmt.Errorf("process %x malformed", ve.ProcessId) } - height := state.Height() + height := app.State.Height() endBlock := process.StartBlock + process.BlockCount if height < process.StartBlock || height > endBlock { - return nil, fmt.Errorf("process %x not started or finished", tx.ProcessId) + return nil, fmt.Errorf("process %x not started or finished", ve.ProcessId) } if process.Status != models.ProcessStatus_READY { - return nil, fmt.Errorf("process %x not in READY state", tx.ProcessId) + return nil, fmt.Errorf("process %x not in READY state", ve.ProcessId) } // Check in case of keys required, they have been sent by some keykeeper @@ -174,8 +181,55 @@ func VoteTxCheck(vtx *models.Tx, txBytes, signature []byte, state *State, var vote *models.Vote switch { case process.EnvelopeType.Anonymous: - // TODO check snark - return nil, fmt.Errorf("snark vote not implemented") + // Supports Groth16 proof generated from circom snark compatible + // prover + proofZkSNARK := ve.Proof.GetZkSnark() + if proofZkSNARK == nil { + return nil, fmt.Errorf("zkSNARK proof is empty") + } + proof, publicInputsFromUser, err := zk.ProtobufZKProofToCircomProof(proofZkSNARK) + if err != nil { + return nil, err + } + + if int(proofZkSNARK.CircuitParametersIndex) >= len(app.ZkVks) { + return nil, fmt.Errorf("invalid CircuitParametersIndex: %d", proofZkSNARK.CircuitParametersIndex) + } + verificationKey := app.ZkVks[proofZkSNARK.CircuitParametersIndex] + + // prepare the publicInputs that are defined by the process + censusRootBI := arbo.BytesToBigInt(process.CensusRoot) + electionIdBI := arbo.BytesToBigInt(process.ProcessId) + publicInputs := []*big.Int{ + electionIdBI, + censusRootBI, + } + // append the user defined public inputs values + publicInputs = append(publicInputs, publicInputsFromUser...) + + // check zkSnark proof + if !verifier.Verify(verificationKey, proof, publicInputs) { + return nil, fmt.Errorf("zkSNARK proof verification failed") + } + + // TODO the next 12 lines of code are the same than a little + // further down. TODO: maybe move them before the 'switch', as + // is a logic that must be done even if + // process.EnvelopeType.Anonymous==true or not + vote = &models.Vote{ + Height: height, + ProcessId: ve.ProcessId, + VotePackage: ve.VotePackage, + } + // If process encrypted, check the vote is encrypted (includes at least one key index) + if process.EnvelopeType.EncryptedVotes { + if len(ve.EncryptionKeyIndexes) == 0 { + return nil, fmt.Errorf("no key indexes provided on vote package") + } + vote.EncryptionKeyIndexes = ve.EncryptionKeyIndexes + } + + return vote, nil default: // Signature based voting if signature == nil { return nil, fmt.Errorf("signature missing on voteTx") @@ -185,13 +239,13 @@ func VoteTxCheck(vtx *models.Tx, txBytes, signature []byte, state *State, // Every N seconds the old votes which are not yet in the blockchain will be removed from cache. // If the same vote (but different transaction) is send to the mempool, the cache will detect it // and vote will be discarted. - vote = state.CacheGet(txID) + vote = app.State.CacheGet(txID) // if vote is in cache, lazy check and remove it from cache if forCommit && vote != nil { vote.Height = height // update vote height - defer state.CacheDel(txID) - if exist, err := state.EnvelopeExists(vote.ProcessId, + defer app.State.CacheDel(txID) + if exist, err := app.State.EnvelopeExists(vote.ProcessId, vote.Nullifier, false); err != nil || exist { if err != nil { return nil, err @@ -210,21 +264,21 @@ func VoteTxCheck(vtx *models.Tx, txBytes, signature []byte, state *State, // if not in cache, full check // extract pubKey, generate nullifier and check census proof. // add the transaction in the cache - if tx.Proof == nil { + if ve.Proof == nil { return nil, fmt.Errorf("proof not found on transaction") } vote = &models.Vote{ Height: height, - ProcessId: tx.ProcessId, - VotePackage: tx.VotePackage, + ProcessId: ve.ProcessId, + VotePackage: ve.VotePackage, } // If process encrypted, check the vote is encrypted (includes at least one key index) if process.EnvelopeType.EncryptedVotes { - if len(tx.EncryptionKeyIndexes) == 0 { + if len(ve.EncryptionKeyIndexes) == 0 { return nil, fmt.Errorf("no key indexes provided on vote package") } - vote.EncryptionKeyIndexes = tx.EncryptionKeyIndexes + vote.EncryptionKeyIndexes = ve.EncryptionKeyIndexes } pubKey, err := ethereum.PubKeyFromSignature(txBytes, signature) if err != nil { @@ -240,21 +294,21 @@ func VoteTxCheck(vtx *models.Tx, txBytes, signature []byte, state *State, // check that nullifier does not exist in cache already, this avoids // processing multiple transactions with same nullifier. - if state.CacheHasNullifier(vote.Nullifier) { + if app.State.CacheHasNullifier(vote.Nullifier) { return nil, fmt.Errorf("nullifier %x already exists in cache", vote.Nullifier) } // check if vote already exists - if exist, err := state.EnvelopeExists(vote.ProcessId, + if exist, err := app.State.EnvelopeExists(vote.ProcessId, vote.Nullifier, false); err != nil || exist { if err != nil { return nil, err } return nil, fmt.Errorf("vote %x already exists", vote.Nullifier) } - log.Debugf("new vote %x for address %s and process %x", vote.Nullifier, addr.Hex(), tx.ProcessId) + log.Debugf("new vote %x for address %s and process %x", vote.Nullifier, addr.Hex(), ve.ProcessId) - valid, weight, err := VerifyProof(process, tx.Proof, + valid, weight, err := VerifyProof(process, ve.Proof, process.CensusOrigin, process.CensusRoot, process.ProcessId, pubKey, addr) if err != nil { @@ -266,7 +320,7 @@ func VoteTxCheck(vtx *models.Tx, txBytes, signature []byte, state *State, vote.Weight = weight.Bytes() // add the vote to cache - state.CacheAdd(txID, vote) + app.State.CacheAdd(txID, vote) } return vote, nil } diff --git a/vochain/transaction_test.go b/vochain/transaction_test.go new file mode 100644 index 000000000..e0489018a --- /dev/null +++ b/vochain/transaction_test.go @@ -0,0 +1,192 @@ +package vochain + +import ( + "math/big" + "testing" + + qt "github.com/frankban/quicktest" + "github.com/vocdoni/arbo" + snarkParsers "github.com/vocdoni/go-snark/parsers" + models "go.vocdoni.io/proto/build/go/models" +) + +func TestVoteEnvelopeCheckCaseZkSNARK(t *testing.T) { + app, err := NewBaseApplication(t.TempDir()) + qt.Assert(t, err, qt.IsNil) + + vkJSON := ` + { + "protocol": "groth16", + "curve": "bn128", + "nPublic": 5, + "vk_alpha_1": [ + "306173029903602630729311012982340700491234800927877597206759293990284528210", + "20106879623667538638269962569230954942944631331052974849151810316932051942222", + "1" + ], + "vk_beta_2": [ + [ + "6388001092721447861165885641715123572539913010792888888192734071510557971913", + "20848050992563430829274627211469581012739038121766063115794423352413732464002" + ], + [ + "12394370189260130796904435534938825578562104521748712723133084156874623405320", + "19568896438322287296581438238708355738182103315047232043072893821280037010279" + ], + [ + "1", + "0" + ] + ], + "vk_gamma_2": [ + [ + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + ], + [ + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + ], + [ + "1", + "0" + ] + ], + "vk_delta_2": [ + [ + "9578317963632610151336009162988348724787547079411333329677896001428855832312", + "15223652499722719159424906581777102078862523870106238832193552620426177289449" + ], + [ + "7265010420518973797514673122071363300792836381472918485237957338548759328172", + "15818506560941235416309800918620469588826088456435875574829581240997757125029" + ], + [ + "1", + "0" + ] + ], + "vk_alphabeta_12": [ + [ + [ + "14988641425665988543173012437855962873430267927938843845212377695680893509401", + "13376631292518172229524588785085616596640262697110939214207125670596806668662" + ], + [ + "9665928106521994323707670301003991839963339714237244404040672746749971183445", + "11286286613183756834700510175396685972593158891616055183077552693217821866140" + ], + [ + "14670343343674699726672162746105329956494703426891625238828562002584076829768", + "5335774586431426730845318728369258274051480530378671404594262495169846336906" + ] + ], + [ + [ + "13064684412674695810467070467168442433339019821373530072208985706106784339796", + "496264313146617107248544685516502919968496068699196759329190902119056326572" + ], + [ + "2674640337879171525175235435666044353562373445419785183123002628033923203032", + "19393605000452539039361872279996726097592730963031488252366290344458812187303" + ], + [ + "17258633520774171265962566789183247690305589194147697336919680418587614956516", + "15787466130746444516485041757175598132724707998252298761321817658606865792978" + ] + ] + ], + "IC": [ + [ + "12446248444031568981644257616045744421897424289005791262520820980003833304613", + "14126708031549800150251786798334874245826413261481575346471080364230164514748", + "1" + ], + [ + "11599899900117225048041999225445311506159975436943878453332146996355954055091", + "12297636032842741549067951675347041044599253527166300668918276287765908064748", + "1" + ], + [ + "19735911389920508717813623267801440121340989591129667156007378533800446155427", + "6370726871639726261638177994806792690899743443170098478085371913895858297436", + "1" + ], + [ + "3056357461368316928050448698580197612474935705336531040887295932203479094319", + "4578265088046347073940510401159988824211136564709579407923367900462670239568", + "1" + ], + [ + "10395748184835372666023760228174670695333928071630341607951040865085882942077", + "9750990852540117403757726698930048971735350814370660404001667648661895269869", + "1" + ], + [ + "1126132770607892831010140912602315025459598690608356545941023769519478104014", + "1866290431477058083436703633558469917331614919276756798997938992757656788463", + "1" + ] + ] + } + ` + vk0, err := snarkParsers.ParseVk([]byte(vkJSON)) + qt.Assert(t, err, qt.IsNil) + app.ZkVks = append(app.ZkVks, vk0) + + processId := arbo.BigIntToBytes(32, big.NewInt(10)) + entityId := []byte("entityid-test") + censusRootBI, ok := new(big.Int).SetString("13256983273841966279055596043431919350426357891097196583481278449962353221936", 10) + qt.Assert(t, ok, qt.IsTrue) + process := &models.Process{ + ProcessId: processId, + EntityId: entityId, + EnvelopeType: &models.EnvelopeType{ + Anonymous: true, + }, + Mode: &models.ProcessMode{}, + Status: models.ProcessStatus_READY, + CensusRoot: arbo.BigIntToBytes(32, censusRootBI), + } + err = app.State.AddProcess(process) + qt.Assert(t, err, qt.IsNil) + + protoProof := models.ProofZkSNARK{ + CircuitParametersIndex: 0, + A: []string{ + "8565618924009803349128724448755637260817268535764018694748071697999960309465", + "10567467520683290855894011826361531515330977110060690707312139021253814259201", + "1", + }, + B: []string{ + "7331705443978474465740013805022628883217016623626657489677999970845326148519", + "14088543429308940796639598167648469598115265384006174847203062566695157397842", + "21650416714230850553304309420788184918411764695672027474950575748521240000392", + "9608621404453315322574104078089794462171122070923660024556835674016394593207", + "1", + "0", + }, + C: []string{ + "2581164907110383930740960311274244222988702887156649148820057728817188544394", + "4874727216192126962804861520752733436306034259929110689845718277073170774972", + "1", + }, + PublicInputs: []string{"512956825156072911942930000072467850503272879939251541441978430922425553120", "302689215824177652345211539748426020171", "205062086841587857568430695525160476881"}, + } + + vtx := &models.VoteEnvelope{ + ProcessId: processId, + Proof: &models.Proof{ + Payload: &models.Proof_ZkSnark{ + ZkSnark: &protoProof, + }, + }, + } + signature := []byte{} + txBytes := []byte{} + txID := [32]byte{} + commit := false + + _, err = app.VoteEnvelopeCheck(vtx, txBytes, signature, txID, commit) + qt.Assert(t, err, qt.IsNil) +}