From 4d9eb9c4b4a417805668c8356475b93736766580 Mon Sep 17 00:00:00 2001 From: Gui Iribarren Date: Tue, 28 Nov 2023 10:06:07 +0100 Subject: [PATCH] vochain fork: refactor ZkCircuits handling * circuit/config: rename `dev` -> `v0.0.1` and add voceremony as `v1.0.0` * circuit/config: rename `tag` concept into `version` * circuit/config: now ZkCircuitConfig has Version field, drop app.circuitConfigTag * TransactionHandler: unexport zkCircuit field, expose over ZkCircuit methods that implement sync.Mutex * api: /chain/info now returns circuitVersion (instead of misspelt cicuitConfigurationTag) * apiclient: simplify NewHTTPclient, fetching /chain/info/circuit endpoint * vochain/app.go: SetZkCircuit during beginBlock * add config/forks.go * circuit: add DownloadZkCircuits funcs * DownloadZkCircuitsForChainID * DownloadDefaultZkCircuit NewBaseApplication now calls circuit.DownloadDefaultZkCircuit instead of transactionHandler.LoadZkCircuit and newTendermint now calls circuit.DownloadZkCircuitsForChainID --- api/api_types.go | 28 +++++++++---------- api/censuses.go | 6 ++--- api/chain.go | 35 +++++++++++------------- apiclient/client.go | 12 +++------ config/forks.go | 27 +++++++++++++++++++ crypto/zk/circuit/circuit.go | 33 ++++++++++++++++++----- crypto/zk/circuit/config.go | 43 ++++++++++++++++-------------- vochain/app.go | 34 +++++++++++------------ vochain/hysteresis_test.go | 5 ++-- vochain/start.go | 4 +++ vochain/transaction/election_tx.go | 4 +-- vochain/transaction/transaction.go | 33 +++++++++++++++++++---- vochain/transaction/vote_tx.go | 4 +-- vochain/transaction_zk_test.go | 5 ++-- 14 files changed, 169 insertions(+), 104 deletions(-) create mode 100644 config/forks.go diff --git a/api/api_types.go b/api/api_types.go index 4c3aceba6..202a47d4e 100644 --- a/api/api_types.go +++ b/api/api_types.go @@ -200,20 +200,20 @@ type GenericTransactionWithInfo struct { } type ChainInfo struct { - ID string `json:"chainId" example:"azeno"` - BlockTime [5]uint64 `json:"blockTime" example:"12000,11580,11000,11100,11100"` - ElectionCount uint64 `json:"electionCount" example:"120"` - OrganizationCount uint64 `json:"organizationCount" example:"20"` - GenesisTime time.Time `json:"genesisTime" format:"date-time" example:"2022-11-17T18:00:57.379551614Z"` - Height uint32 `json:"height" example:"5467"` - Syncing bool `json:"syncing" example:"true"` - Timestamp int64 `json:"blockTimestamp" swaggertype:"string" format:"date-time" example:"2022-11-17T18:00:57.379551614Z"` - TransactionCount uint64 `json:"transactionCount" example:"554"` - ValidatorCount uint32 `json:"validatorCount" example:"5"` - VoteCount uint64 `json:"voteCount" example:"432"` - CircuitConfigurationTag string `json:"cicuitConfigurationTag" example:"dev"` - MaxCensusSize uint64 `json:"maxCensusSize" example:"50000"` - NetworkCapacity uint64 `json:"networkCapacity" example:"2000"` + ID string `json:"chainId" example:"azeno"` + BlockTime [5]uint64 `json:"blockTime" example:"12000,11580,11000,11100,11100"` + ElectionCount uint64 `json:"electionCount" example:"120"` + OrganizationCount uint64 `json:"organizationCount" example:"20"` + GenesisTime time.Time `json:"genesisTime" format:"date-time" example:"2022-11-17T18:00:57.379551614Z"` + Height uint32 `json:"height" example:"5467"` + Syncing bool `json:"syncing" example:"true"` + Timestamp int64 `json:"blockTimestamp" swaggertype:"string" format:"date-time" example:"2022-11-17T18:00:57.379551614Z"` + TransactionCount uint64 `json:"transactionCount" example:"554"` + ValidatorCount uint32 `json:"validatorCount" example:"5"` + VoteCount uint64 `json:"voteCount" example:"432"` + CircuitVersion string `json:"circuitVersion" example:"v1.0.0"` + MaxCensusSize uint64 `json:"maxCensusSize" example:"50000"` + NetworkCapacity uint64 `json:"networkCapacity" example:"2000"` } type Account struct { diff --git a/api/censuses.go b/api/censuses.go index a4f06d885..1b00dd673 100644 --- a/api/censuses.go +++ b/api/censuses.go @@ -210,9 +210,9 @@ func (a *API) censusCreateHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCont } // get census max levels from vochain app if available - maxLevels := circuit.CircuitsConfigurations[circuit.DefaultCircuitConfigurationTag].Levels - if a.vocapp != nil { - maxLevels = a.vocapp.TransactionHandler.ZkCircuit.Config.Levels + maxLevels := circuit.CircuitsConfigurations[circuit.DefaultZkCircuitVersion].Levels + if a.vocapp != nil && a.vocapp.TransactionHandler != nil { + maxLevels = a.vocapp.TransactionHandler.ZkCircuit().Config.Levels } censusID := util.RandomBytes(32) diff --git a/api/chain.go b/api/chain.go index 635737a28..b42c27c98 100644 --- a/api/chain.go +++ b/api/chain.go @@ -8,7 +8,6 @@ import ( "time" tmtypes "github.com/cometbft/cometbft/types" - "go.vocdoni.io/dvote/crypto/zk/circuit" "go.vocdoni.io/dvote/httprouter" "go.vocdoni.io/dvote/httprouter/apirest" "go.vocdoni.io/dvote/types" @@ -301,20 +300,20 @@ func (a *API) chainInfoHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) } data, err := json.Marshal(&ChainInfo{ - ID: a.vocapp.ChainID(), - BlockTime: *a.vocinfo.BlockTimes(), - ElectionCount: a.indexer.CountTotalProcesses(), - OrganizationCount: a.indexer.CountTotalEntities(), - Height: a.vocapp.Height(), - Syncing: a.vocapp.IsSynchronizing(), - TransactionCount: transactionCount, - ValidatorCount: uint32(len(validators)), - Timestamp: a.vocapp.Timestamp(), - VoteCount: voteCount, - GenesisTime: a.vocapp.Genesis().GenesisTime, - CircuitConfigurationTag: a.vocapp.CircuitConfigurationTag(), - MaxCensusSize: maxCensusSize, - NetworkCapacity: networkCapacity, + ID: a.vocapp.ChainID(), + BlockTime: *a.vocinfo.BlockTimes(), + ElectionCount: a.indexer.CountTotalProcesses(), + OrganizationCount: a.indexer.CountTotalEntities(), + Height: a.vocapp.Height(), + Syncing: a.vocapp.IsSynchronizing(), + TransactionCount: transactionCount, + ValidatorCount: uint32(len(validators)), + Timestamp: a.vocapp.Timestamp(), + VoteCount: voteCount, + GenesisTime: a.vocapp.Genesis().GenesisTime, + CircuitVersion: a.vocapp.TransactionHandler.ZkCircuitVersion(), + MaxCensusSize: maxCensusSize, + NetworkCapacity: networkCapacity, }) if err != nil { return err @@ -332,10 +331,8 @@ func (a *API) chainInfoHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) // @Success 200 {object} circuit.ZkCircuitConfig // @Router /chain/info/circuit [get] func (a *API) chainCircuitInfoHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { - // Get current circuit tag - circuitConfig := circuit.GetCircuitConfiguration(a.vocapp.CircuitConfigurationTag()) - // Encode the circuit configuration to JSON - data, err := json.Marshal(circuitConfig) + // Encode the current circuit configuration to JSON + data, err := json.Marshal(a.vocapp.TransactionHandler.ZkCircuit().Config) if err != nil { return err } diff --git a/apiclient/client.go b/apiclient/client.go index b4077e150..1d4d7f282 100644 --- a/apiclient/client.go +++ b/apiclient/client.go @@ -59,21 +59,17 @@ func NewHTTPclient(addr *url.URL, bearerToken *uuid.UUID) (*HTTPclient, error) { addr: addr, retries: DefaultRetries, } - data, status, err := c.Request(HTTPGET, nil, "chain", "info") + data, status, err := c.Request(HTTPGET, nil, "chain", "info", "circuit") if err != nil { return nil, err } if status != apirest.HTTPstatusOK { - log.Warnw("cannot get chain info from API server", "status", status, "data", data) + log.Warnw("cannot get circuit info from API server", "status", status, "data", data) return c, nil } - info := &api.ChainInfo{} - if err := json.Unmarshal(data, info); err != nil { - return nil, fmt.Errorf("cannot get chain ID from API server") + if err := json.Unmarshal(data, c.circuit); err != nil { + return nil, fmt.Errorf("cannot get unmarshsal circuit from API server") } - c.chainID = info.ID - // Get the default circuit config - c.circuit = circuit.GetCircuitConfiguration(info.CircuitConfigurationTag) return c, nil } diff --git a/config/forks.go b/config/forks.go new file mode 100644 index 000000000..3d82b31d0 --- /dev/null +++ b/config/forks.go @@ -0,0 +1,27 @@ +package config + +// ForksCfg allows applying softforks at specified heights +type ForksCfg struct { + VoceremonyForkBlock uint32 +} + +// Forks is a map of chainIDs +var Forks = map[string]*ForksCfg{ + "vocdoni/DEV/29": { + VoceremonyForkBlock: 180000, // estimated 2023-11-30T23:45:29.095375169Z + }, + "vocdoni/STAGE/9": { + VoceremonyForkBlock: 190000, // estimated 2023-12-04T11:25:38.643517797Z + }, + "vocdoni/LTS/1.2": { + VoceremonyForkBlock: 350000, // estimated 2023-12-06T05:59:27.529386884Z + }, +} + +// ForksForChainID returns the ForksCfg of chainID, if found, or an empty ForksCfg otherwise +func ForksForChainID(chainID string) *ForksCfg { + if cfg, found := Forks[chainID]; found { + return cfg + } + return &ForksCfg{} +} diff --git a/crypto/zk/circuit/circuit.go b/crypto/zk/circuit/circuit.go index 33a2d5ea8..0fd0c58b1 100644 --- a/crypto/zk/circuit/circuit.go +++ b/crypto/zk/circuit/circuit.go @@ -12,10 +12,11 @@ import ( "path/filepath" "time" + "go.vocdoni.io/dvote/config" "go.vocdoni.io/dvote/log" ) -var downloadCircuitsTimeout = time.Minute * 5 +const downloadCircuitsTimeout = time.Minute * 5 // BaseDir is where the artifact cache is expected to be found. // If the artifacts are not found there, they will be downloaded and stored. @@ -40,11 +41,31 @@ type ZkCircuit struct { Config *ZkCircuitConfig } -// LoadZkCircuitByTag gets the circuit configuration associated to the provided -// tag or gets the default one and load its artifacts to prepare the circuit to -// be used. -func LoadZkCircuitByTag(configTag string) (*ZkCircuit, error) { - circuitConf := GetCircuitConfiguration(configTag) +// DownloadDefaultZkCircuit ensures the default circuit is cached locally +func DownloadDefaultZkCircuit() error { + _, err := LoadZkCircuitVersion(DefaultZkCircuitVersion) + if err != nil { + return fmt.Errorf("could not load zk verification keys: %w", err) + } + return nil +} + +// DownloadZkCircuitsForChainID ensures all circuits needed for chainID are cached locally +func DownloadZkCircuitsForChainID(chainID string) error { + if config.ForksForChainID(chainID).VoceremonyForkBlock > 0 { + _, err := LoadZkCircuitVersion(PreVoceremonyForkZkCircuitVersion) + if err != nil { + return fmt.Errorf("could not load zk verification keys: %w", err) + } + } + return DownloadDefaultZkCircuit() +} + +// LoadZkCircuitVersion load the circuit artifacts based on the version provided. +// First, tries to load the artifacts from local storage, if they are not +// available, tries to download from their remote location. +func LoadZkCircuitVersion(version string) (*ZkCircuit, error) { + circuitConf := GetCircuitConfiguration(version) ctx, cancel := context.WithTimeout(context.Background(), downloadCircuitsTimeout) defer cancel() return LoadZkCircuit(ctx, circuitConf) diff --git a/crypto/zk/circuit/config.go b/crypto/zk/circuit/config.go index e22afd8a6..26af960fd 100644 --- a/crypto/zk/circuit/config.go +++ b/crypto/zk/circuit/config.go @@ -10,14 +10,10 @@ import ( "go.vocdoni.io/dvote/util" ) -// DefaultCircuitConfigurationTag constant contains the tag value that points -// to the default ZkSnark circuit configuration. It ensures that at least one -// circuit configuration is available so the configuration referred by this tag -// must be defined. -const DefaultCircuitConfigurationTag = "dev" - // ZkCircuitConfig defines the configuration of the files to be downloaded type ZkCircuitConfig struct { + // Version of the published circuit + Version string // URI defines the URI from where to download the files URI string `json:"uri"` // CircuitPath defines the path from where the files are downloaded. @@ -75,12 +71,19 @@ func (conf *ZkCircuitConfig) SupportsCensusSize(maxCensusSize uint64) bool { return conf.MaxCensusSize().Cmp(new(big.Int).SetUint64(maxCensusSize)) > 0 } +// DefaultZkCircuitVersion is the circuit version used by default +const DefaultZkCircuitVersion = "v1.0.0" + +// PreVoceremonyForkZkCircuitVersion is the circuit version used before VoceremonyForkBlock +const PreVoceremonyForkZkCircuitVersion = "v0.0.1" + // CircuitsConfigurations stores the relation between the different vochain nets // and the associated circuit configuration. Any circuit configuration must have // the remote and local location of the circuits artifacts and their metadata // such as artifacts hash or the number of parameters. var CircuitsConfigurations = map[string]*ZkCircuitConfig{ - "dev": { + "v0.0.1": { + Version: "v0.0.1", URI: "https://raw.githubusercontent.com/vocdoni/" + "zk-franchise-proof-circuit/master", CircuitPath: "artifacts/zkCensus/dev/160", @@ -92,29 +95,29 @@ var CircuitsConfigurations = map[string]*ZkCircuitConfig{ WasmHash: hexToBytes("0x80a73567f6a4655d4332301efcff4bc5711bb48176d1c71fdb1e48df222ac139"), WasmFilename: "circuit.wasm", }, - "prod": { - URI: "https://raw.githubusercontent.com/vocdoni/" + - "zk-franchise-proof-circuit/master", - CircuitPath: "artifacts/zkCensus/dev/160", + "v1.0.0": { + Version: "v1.0.0", + URI: "https://raw.githubusercontent.com/altergui/zk-voceremony", + CircuitPath: "ceremony/vocdoni-zkcensus-ceremony/results", Levels: 160, // ZkCircuit number of levels - ProvingKeyHash: hexToBytes("0xe359b256e5e3c78acaccf8dab5dc4bea99a2f07b2a05e935b5ca658c714dea4a"), - ProvingKeyFilename: "proving_key.zkey", - VerificationKeyHash: hexToBytes("0x235e55571812f8e324e73e37e53829db0c4ac8f68469b9b953876127c97b425f"), - VerificationKeyFilename: "verification_key.json", - WasmHash: hexToBytes("0x80a73567f6a4655d4332301efcff4bc5711bb48176d1c71fdb1e48df222ac139"), - WasmFilename: "circuit.wasm", + ProvingKeyHash: hexToBytes("0x94f4062db3e43175ac1136f285551d547a177e37b0616a41900a38ed5ec3d478"), + ProvingKeyFilename: "census_proving_key.zkey", + VerificationKeyHash: hexToBytes("0x2a47ff7e511926290fedfa406886944eeb0a3df9021ca26333c0c124c89aa7b0"), + VerificationKeyFilename: "census_verification_key.json", + WasmHash: hexToBytes("0xc98133cf4d84ced677549e0d848739f4e80ddf78af678cbc8b95377247a92773"), + WasmFilename: "census.wasm", }, } // GetCircuitConfiguration returns the circuit configuration associated with the // provided tag or gets the default one. -func GetCircuitConfiguration(configTag string) *ZkCircuitConfig { +func GetCircuitConfiguration(version string) *ZkCircuitConfig { // check if the provided config tag exists and return it if it does - if conf, ok := CircuitsConfigurations[configTag]; ok { + if conf, ok := CircuitsConfigurations[version]; ok { return conf } // if not, return default configuration - return CircuitsConfigurations[DefaultCircuitConfigurationTag] + return CircuitsConfigurations[DefaultZkCircuitVersion] } // hexToBytes parses a hex string and returns the byte array from it. Warning, diff --git a/vochain/app.go b/vochain/app.go index 3d3340fcb..28fea5f5b 100644 --- a/vochain/app.go +++ b/vochain/app.go @@ -78,7 +78,6 @@ type BaseApplication struct { // abcitypes.RequestBeginBlock.Header.Time startBlockTimestamp atomic.Int64 chainID string - circuitConfigTag string dataDir string genesisInfo *tmtypes.GenesisDoc @@ -136,10 +135,11 @@ func NewBaseApplication(vochainCfg *config.VochainCfg) (*BaseApplication, error) istc, filepath.Join(vochainCfg.DataDir, "txHandler"), ) - // Load or download the zk verification keys - if err := transactionHandler.LoadZkCircuit(circuit.DefaultCircuitConfigurationTag); err != nil { + + if err := circuit.DownloadDefaultZkCircuit(); err != nil { return nil, fmt.Errorf("cannot load zk circuit: %w", err) } + blockCache, err := lru.New[int64, *tmtypes.Block](32) if err != nil { return nil, err @@ -150,7 +150,6 @@ func NewBaseApplication(vochainCfg *config.VochainCfg) (*BaseApplication, error) TransactionHandler: transactionHandler, blockCache: blockCache, dataDir: vochainCfg.DataDir, - circuitConfigTag: circuit.DefaultCircuitConfigurationTag, genesisInfo: &tmtypes.GenesisDoc{}, }, nil } @@ -260,6 +259,7 @@ func (app *BaseApplication) beginBlock(t time.Time, height uint32) { app.State.Rollback() app.startBlockTimestamp.Store(t.Unix()) app.State.SetHeight(height) + app.SetZkCircuit() go app.State.CachePurge(height) app.State.OnBeginBlock(vstate.BeginBlock{ Height: int64(height), @@ -352,22 +352,18 @@ func (app *BaseApplication) Genesis() *tmtypes.GenesisDoc { return app.genesisInfo } -// SetCircuitConfigTag sets the current BaseApplication circuit config tag -// attribute to the provided one and loads the circuit configuration based on -// it. The available circuit config tags are defined in -// /crypto/zk/circuit/config.go -func (app *BaseApplication) SetCircuitConfigTag(tag string) error { - // Update the loaded circuit of the current app transactionHandler - if err := app.TransactionHandler.LoadZkCircuit(tag); err != nil { - return fmt.Errorf("cannot load zk circuit: %w", err) +// SetZkCircuit ensures the TransactionHandler ZkCircuit is the correct for a chain that implements forks +func (app *BaseApplication) SetZkCircuit() { + if app.Height() < config.ForksForChainID(app.chainID).VoceremonyForkBlock { + // if already using correct version, do nothing + if app.TransactionHandler.ZkCircuitVersion() == circuit.PreVoceremonyForkZkCircuitVersion { + return + } + err := app.TransactionHandler.SetZkCircuitVersion(circuit.PreVoceremonyForkZkCircuitVersion) + if err != nil { + log.Fatalf("failed to set ZkCircuit pre VoceremonyForkBlock: %w", err) + } } - app.circuitConfigTag = tag - return nil -} - -// CircuitConfigurationTag returns the Node CircuitConfigurationTag -func (app *BaseApplication) CircuitConfigurationTag() string { - return app.circuitConfigTag } // IsSynchronizing informs if the blockchain is synchronizing or not. diff --git a/vochain/hysteresis_test.go b/vochain/hysteresis_test.go index 64e5b9cb3..7c210c3e1 100644 --- a/vochain/hysteresis_test.go +++ b/vochain/hysteresis_test.go @@ -23,9 +23,8 @@ func TestHysteresis(t *testing.T) { // create test app and load zk circuit app := TestBaseApplication(t) - devCircuit, err := circuit.LoadZkCircuitByTag(circuit.DefaultCircuitConfigurationTag) + err := app.TransactionHandler.SetZkCircuitVersion(circuit.DefaultZkCircuitVersion) c.Assert(err, qt.IsNil) - app.TransactionHandler.ZkCircuit = devCircuit // initial accounts testWeight := big.NewInt(10) @@ -94,7 +93,7 @@ func TestHysteresis(t *testing.T) { encInputs, err := json.Marshal(inputs) c.Assert(err, qt.IsNil) - zkProof, err := prover.Prove(devCircuit.ProvingKey, devCircuit.Wasm, encInputs) + zkProof, err := prover.Prove(app.TransactionHandler.ZkCircuit().ProvingKey, app.TransactionHandler.ZkCircuit().Wasm, encInputs) c.Assert(err, qt.IsNil) protoZkProof, err := zk.ProverProofToProtobufZKProof(zkProof, nil, nil, nil, nil, nil) diff --git a/vochain/start.go b/vochain/start.go index 1aee974fc..79b765675 100644 --- a/vochain/start.go +++ b/vochain/start.go @@ -12,6 +12,7 @@ import ( "go.vocdoni.io/dvote/config" "go.vocdoni.io/dvote/crypto/ethereum" + "go.vocdoni.io/dvote/crypto/zk/circuit" vocdoniGenesis "go.vocdoni.io/dvote/vochain/genesis" tmcfg "github.com/cometbft/cometbft/config" @@ -275,6 +276,9 @@ func newTendermint(app *BaseApplication, log.Infow("genesis file", "genesis", tconfig.GenesisFile(), "chainID", genesisCID.ChainID) app.SetChainID(genesisCID.ChainID) + // the chain might need additional ZkCircuits, now that we know the chainID ensure they are downloaded + circuit.DownloadZkCircuitsForChainID(genesisCID.ChainID) + // assign the default tendermint methods app.SetDefaultMethods() node, err := tmnode.NewNode(tconfig, diff --git a/vochain/transaction/election_tx.go b/vochain/transaction/election_tx.go index 853b3e56b..bbf379c76 100644 --- a/vochain/transaction/election_tx.go +++ b/vochain/transaction/election_tx.go @@ -72,10 +72,10 @@ func (t *TransactionHandler) NewProcessTxCheck(vtx *vochaintx.Tx) (*models.Proce fmt.Errorf("maxCensusSize is greater than the maximum allowed (%d)", maxProcessSize) } // check that the census size is not bigger than the circuit levels - if tx.Process.EnvelopeType.Anonymous && !t.ZkCircuit.Config.SupportsCensusSize(txMaxCensusSize) { + if tx.Process.EnvelopeType.Anonymous && !t.ZkCircuit().Config.SupportsCensusSize(txMaxCensusSize) { return nil, ethereum.Address{}, fmt.Errorf("maxCensusSize for anonymous envelope "+ "cannot be bigger than the number of levels of the circuit (max:%d provided:%d)", - t.ZkCircuit.Config.MaxCensusSize().Int64(), txMaxCensusSize) + t.ZkCircuit().Config.MaxCensusSize().Int64(), txMaxCensusSize) } // check signature diff --git a/vochain/transaction/transaction.go b/vochain/transaction/transaction.go index 8f8e94001..e50167452 100644 --- a/vochain/transaction/transaction.go +++ b/vochain/transaction/transaction.go @@ -3,6 +3,7 @@ package transaction import ( "encoding/hex" "fmt" + "sync" cometCrypto256k1 "github.com/cometbft/cometbft/crypto/secp256k1" "github.com/ethereum/go-ethereum/common" @@ -46,8 +47,9 @@ type TransactionHandler struct { istc *ist.Controller // dataDir is the path for storing some files dataDir string - // ZkCircuit contains the current chain circuit - ZkCircuit *circuit.ZkCircuit + // zkCircuit contains the current chain circuit + zkCircuit *circuit.ZkCircuit + zkCircuitMtx sync.Mutex } // NewTransactionHandler creates a new TransactionHandler. @@ -59,12 +61,33 @@ func NewTransactionHandler(state *vstate.State, istc *ist.Controller, dataDir st } } -func (t *TransactionHandler) LoadZkCircuit(configTag string) error { - circuit, err := circuit.LoadZkCircuitByTag(configTag) +// ZkCircuitConfig returns the current ZkCircuit used by TransactionHandler +func (t *TransactionHandler) ZkCircuit() *circuit.ZkCircuit { + t.zkCircuitMtx.Lock() + defer t.zkCircuitMtx.Unlock() + return t.zkCircuit +} + +// ZkCircuitVersion returns the version of the current ZkCircuit used by TransactionHandler, +// or an empty string if ZkCircuit is not yet initialized +func (t *TransactionHandler) ZkCircuitVersion() string { + t.zkCircuitMtx.Lock() + defer t.zkCircuitMtx.Unlock() + if t.zkCircuit == nil { + return "" + } + return t.zkCircuit.Config.Version +} + +// SetZkCircuitVersion will LoadZkCircuit into t.ZkCircuit +func (t *TransactionHandler) SetZkCircuitVersion(version string) error { + circuit, err := circuit.LoadZkCircuitVersion(version) if err != nil { return fmt.Errorf("could not load zk verification keys: %w", err) } - t.ZkCircuit = circuit + t.zkCircuitMtx.Lock() + defer t.zkCircuitMtx.Unlock() + t.zkCircuit = circuit return nil } diff --git a/vochain/transaction/vote_tx.go b/vochain/transaction/vote_tx.go index b355843bf..c9ba5f11a 100644 --- a/vochain/transaction/vote_tx.go +++ b/vochain/transaction/vote_tx.go @@ -131,7 +131,7 @@ func (t *TransactionHandler) VoteTxCheck(vtx *vochaintx.Tx, forCommit bool) (*vs // verify the proof associated with the vote if process.EnvelopeType.Anonymous { - if t.ZkCircuit == nil { + if t.ZkCircuit() == nil { return nil, fmt.Errorf("anonymous voting not supported, missing zk circuits data") } // get snark proof from vote envelope @@ -165,7 +165,7 @@ func (t *TransactionHandler) VoteTxCheck(vtx *vochaintx.Tx, forCommit bool) (*vs "electionID", fmt.Sprintf("%x", voteEnvelope.ProcessId), ) // verify the proof with the circuit verification key - if err := proof.Verify(t.ZkCircuit.VerificationKey); err != nil { + if err := proof.Verify(t.ZkCircuit().VerificationKey); err != nil { return nil, fmt.Errorf("zkSNARK proof verification failed: %w", err) } diff --git a/vochain/transaction_zk_test.go b/vochain/transaction_zk_test.go index 74b70bfb4..b4c483f73 100644 --- a/vochain/transaction_zk_test.go +++ b/vochain/transaction_zk_test.go @@ -22,9 +22,8 @@ func TestVoteCheckZkSNARK(t *testing.T) { c := qt.New(t) // create test app and load zk circuit app := TestBaseApplication(t) - devCircuit, err := circuit.LoadZkCircuitByTag(circuit.DefaultCircuitConfigurationTag) + err := app.TransactionHandler.SetZkCircuitVersion(circuit.DefaultZkCircuitVersion) c.Assert(err, qt.IsNil) - app.TransactionHandler.ZkCircuit = devCircuit // set initial inputs testWeight := big.NewInt(10) accounts, censusRoot, proofs := testCreateKeysAndBuildWeightedZkCensus(t, 10, testWeight) @@ -84,7 +83,7 @@ func TestVoteCheckZkSNARK(t *testing.T) { c.Assert(err, qt.IsNil) encInputs, err := json.Marshal(inputs) c.Assert(err, qt.IsNil) - proof, err := prover.Prove(devCircuit.ProvingKey, devCircuit.Wasm, encInputs) + proof, err := prover.Prove(app.TransactionHandler.ZkCircuit().ProvingKey, app.TransactionHandler.ZkCircuit().Wasm, encInputs) c.Assert(err, qt.IsNil) // generate nullifier nullifier, err := testAccount.AccountSIKnullifier(electionId, nil)