~edwargix/tallyard

c9687894af04d3c0d4d4ebd86942a7e566bd3242 — David Florness 2 years ago 43b16a9
Speed up marshalling by storing keys in separate files
6 files changed, 119 insertions(+), 81 deletions(-)

M election/map.go
M election/marshal.go
M election/marshal_test.go
M election/msg.go
M election/voter.go
M go.sum
M election/map.go => election/map.go +1 -1
@@ 29,7 29,7 @@ type ElectionsMap struct {
	save       func(*ElectionsMap)      `json:"-"`
}

const electionsMapVersion = 6
const electionsMapVersion = 7

func NewElectionsMap(userID id.UserID, save func(*ElectionsMap)) *ElectionsMap {
	return &ElectionsMap{

M election/marshal.go => election/marshal.go +79 -36
@@ 1,67 1,110 @@
package election

import (
	"bytes"
	"encoding/base64"
	"os"

	"github.com/consensys/gnark-crypto/ecc"
	"github.com/consensys/gnark/backend/groth16"
	"github.com/kyoh86/xdg"
	log "github.com/sirupsen/logrus"
	"maunium.net/go/mautrix/appservice"
)

type MarshallableProvingKey struct {
	Pk groth16.ProvingKey
func init() {
	os.MkdirAll(xdg.DataHome() + "/tallyard/files", 0700)
}

func (t *MarshallableProvingKey) UnmarshalJSON(b []byte) error {
	var buf bytes.Buffer
	_, err := buf.Write(bytes.Trim(b, "\""))
func filePath(fileID string) string {
	return xdg.DataHome() + "/tallyard/files/" + fileID
}

type ProvingKeyFile struct {
	FileID string             `json:"file_id"`
	pk     groth16.ProvingKey `json:"-"`
}

func NewProvingKeyFile(fileID string, pk groth16.ProvingKey) *ProvingKeyFile {
	filePath := filePath(fileID)
	file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
	if err != nil {
		return err
		log.Errorf("couldn't open file %s; this proving key will be discarded upon exit: %s", filePath, err)
		return &ProvingKeyFile{"", pk}
	}
	_, err = pk.WriteTo(file)
	if err != nil {
		log.Errorf("couldn't write proving key to file %s; this proving will be discarded upon exit: %s", filePath, err)
		return &ProvingKeyFile{"", pk}
	}
	return &ProvingKeyFile{
		FileID: fileID,
		pk:     pk,
	}
	t.Pk = groth16.NewProvingKey(ecc.BLS12_381)
	_, err = t.Pk.ReadFrom(base64.NewDecoder(base64.StdEncoding, &buf))
	return err
}

func (t *MarshallableProvingKey) MarshalJSON() ([]byte, error) {
	var buf bytes.Buffer
	encoder := base64.NewEncoder(base64.StdEncoding, &buf)
	_, err := t.Pk.WriteTo(encoder)
func (provingKey *ProvingKeyFile) Pk() groth16.ProvingKey {
	if provingKey.pk != nil {
		return provingKey.pk
	}

	filePath := filePath(provingKey.FileID)
	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
		log.Errorf("couldn't open proving key file %s: %s", filePath, err)
		return nil
	}
	err = encoder.Close()
	pk := groth16.NewProvingKey(ecc.BLS12_381)
	_, err = pk.ReadFrom(file)
	if err != nil {
		return nil, err
		log.Errorf("couldn't read proving key file %s: %s", filePath, err)
		return nil
	}
	return append([]byte{'"'}, append(buf.Bytes(), '"')...), nil

	provingKey.pk = pk
	return pk
}

type MarshallableVerifyingKey struct {
	Vk groth16.VerifyingKey
type VerifyingKeyFile struct {
	FileID string               `json:"file_id"`
	vk     groth16.VerifyingKey `json:"-"`
}

func (t *MarshallableVerifyingKey) UnmarshalJSON(b []byte) error {
	var buf bytes.Buffer
	_, err := buf.Write(bytes.Trim(b, "\""))
func NewVerifyingKeyFile(vk groth16.VerifyingKey) *VerifyingKeyFile {
	fileID := appservice.RandomString(24)
	filePath := filePath(fileID)
	file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
	if err != nil {
		log.Errorf("couldn't open file %s; this verifying key will be discarded upon exit: %s", filePath, err)
		return &VerifyingKeyFile{"", vk}
	}
	_, err = vk.WriteTo(file)
	if err != nil {
		return err
		log.Errorf("couldn't write verifying key to file %s; this proving will be discarded upon exit: %s", filePath, err)
		return &VerifyingKeyFile{"", vk}
	}
	return &VerifyingKeyFile{
		FileID: fileID,
		vk:     vk,
	}
	t.Vk = groth16.NewVerifyingKey(ecc.BLS12_381)
	_, err = t.Vk.ReadFrom(base64.NewDecoder(base64.StdEncoding, &buf))
	return err
}

func (t *MarshallableVerifyingKey) MarshalJSON() ([]byte, error) {
	var buf bytes.Buffer
	encoder := base64.NewEncoder(base64.StdEncoding, &buf)
	_, err := t.Vk.WriteRawTo(encoder)
func (verifyingKey *VerifyingKeyFile) Vk() groth16.VerifyingKey {
	if verifyingKey.vk != nil {
		return verifyingKey.vk
	}

	filePath := filePath(verifyingKey.FileID)
	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
		log.Errorf("couldn't open verifying key file %s: %s", filePath, err)
		return nil
	}
	err = encoder.Close()
	vk := groth16.NewVerifyingKey(ecc.BLS12_381)
	_, err = vk.ReadFrom(file)
	if err != nil {
		return nil, err
		log.Errorf("couldn't read verifying key file %s: %s", filePath, err)
		return nil
	}
	return append([]byte{'"'}, append(buf.Bytes(), '"')...), nil

	verifyingKey.vk = vk
	return vk
}

M election/marshal_test.go => election/marshal_test.go +8 -8
@@ 122,9 122,9 @@ func randomLocalVoter(t *testing.T) *LocalVoter {
		}

		// EvalProvingKey
		localVoter.EvalProvingKey   = &MarshallableProvingKey{evalProvingKey}
		localVoter.EvalProvingKey   = NewProvingKeyFile("a", evalProvingKey)
		// EvalVerifyingKey
		localVoter.EvalVerifyingKey = &MarshallableVerifyingKey{evalVerifyingKey}
		localVoter.EvalVerifyingKey = NewVerifyingKeyFile(evalVerifyingKey)
	}

	{


@@ 137,9 137,9 @@ func randomLocalVoter(t *testing.T) *LocalVoter {
			t.Fatal(err)
		}
		// SumProvingKey
		localVoter.SumProvingKey    = &MarshallableProvingKey{sumProvingKey}
		localVoter.SumProvingKey   = NewProvingKeyFile("b", sumProvingKey)
		// SumVerifyingKey
		localVoter.SumVerifyingKey  = &MarshallableVerifyingKey{sumVerifyingKey}
		localVoter.SumVerifyingKey = NewVerifyingKeyFile(sumVerifyingKey)
	}

	ballot := make([][]byte, numCandidates)


@@ 226,19 226,19 @@ func ensureEqual(t *testing.T, lv1 *LocalVoter, lv2 *LocalVoter) {
	}

	// EvalProvingKey
	if lv2.EvalProvingKey.Pk.IsDifferent(lv1.EvalProvingKey.Pk) {
	if lv2.EvalProvingKey.Pk().IsDifferent(lv1.EvalProvingKey.Pk()) {
		t.Error("eval proving keys not equal")
	}
	// EvalVerifyingKey
	if lv2.EvalVerifyingKey.Vk.IsDifferent(lv1.EvalVerifyingKey.Vk) {
	if lv2.EvalVerifyingKey.Vk().IsDifferent(lv1.EvalVerifyingKey.Vk()) {
		t.Error("eval verifying keys not equal")
	}
	// SumProvingKey
	if lv2.SumProvingKey.Pk.IsDifferent(lv1.SumProvingKey.Pk) {
	if lv2.SumProvingKey.Pk().IsDifferent(lv1.SumProvingKey.Pk()) {
		t.Error("sum proving keys not equal")
	}
	// SumVerifyingKey
	if lv2.SumVerifyingKey.Vk.IsDifferent(lv1.SumVerifyingKey.Vk) {
	if lv2.SumVerifyingKey.Vk().IsDifferent(lv1.SumVerifyingKey.Vk()) {
		t.Error("sum verifying keys not equal")
	}
}

M election/msg.go => election/msg.go +18 -27
@@ 499,47 499,38 @@ func (elections *ElectionsMap) onKeysMessage(evt *event.Event, client *mautrix.C
		return
	}

	var evalProvingKey groth16.ProvingKey
	var evalProvingKey *ProvingKeyFile
	{
		// yes, I know we should be using Download here
		byts, err := client.DownloadBytes(content.EvalProvingKeyURI)
		readCloser, err := client.Download(content.EvalProvingKeyURI)
		if err != nil {
			errorf("couldn't download eval proving key: %s", err)
			return
		}
		var buf bytes.Buffer
		_, err = buf.Write(byts)
		if err != nil {
			errorf("couldn't write to eval bytes buffer: %s")
			return
		}
		evalProvingKey = groth16.NewProvingKey(ecc.BLS12_381)
		_, err = evalProvingKey.ReadFrom(&buf)
		pk := groth16.NewProvingKey(ecc.BLS12_381)
		_, err = pk.ReadFrom(readCloser)
		readCloser.Close()
		if err != nil {
			errorf("couldn't read eval key from downloaded eval proving keys file: %s", err)
			errorf("couldn't read eval proving key: %s", err)
			return
		}
		evalProvingKey = NewProvingKeyFile(content.EvalProvingKeyURI.FileID, pk)
	}

	var sumProvingKey groth16.ProvingKey
	var sumProvingKey *ProvingKeyFile
	{
		byts, err := client.DownloadBytes(content.SumProvingKeyURI)
		readCloser, err := client.Download(content.SumProvingKeyURI)
		if err != nil {
			errorf("couldn't download sum proving key: %s", err)
			return
		}
		var buf bytes.Buffer
		_, err = buf.Write(byts)
		if err != nil {
			errorf("couldn't write to sum bytes buffer: %s")
			return
		}
		sumProvingKey = groth16.NewProvingKey(ecc.BLS12_381)
		_, err = sumProvingKey.ReadFrom(&buf)
		pk := groth16.NewProvingKey(ecc.BLS12_381)
		_, err = pk.ReadFrom(readCloser)
		readCloser.Close()
		if err != nil {
			errorf("couldn't read sum key from downloaded sum keys file: %s", err)
			errorf("couldn't read sum proving key: %s", err)
			return
		}
		sumProvingKey = NewProvingKeyFile(content.SumProvingKeyURI.FileID, pk)
	}

	var voter *Voter


@@ 565,8 556,8 @@ func (elections *ElectionsMap) onKeysMessage(evt *event.Event, client *mautrix.C
	defer el.Save()
	defer el.Unlock()

	voter.EvalProvingKey = &MarshallableProvingKey{evalProvingKey}
	voter.SumProvingKey = &MarshallableProvingKey{sumProvingKey}
	voter.EvalProvingKey = evalProvingKey
	voter.SumProvingKey = sumProvingKey
	voter.KeysID = &evt.ID

	return true


@@ 749,7 740,7 @@ func (elections *ElectionsMap) onEvalsMessage(evt *event.Event) (success bool) {
			errorf("our evals verifying key is nil")
			return
		}
		err := groth16.Verify(proof, el.LocalVoter.EvalVerifyingKey.Vk, &publicCircuit)
		err := groth16.Verify(proof, el.LocalVoter.EvalVerifyingKey.Vk(), &publicCircuit)
		if err != nil {
			warnf("poly eval proof verification failed: %s", err)
			return


@@ 946,7 937,7 @@ func (elections *ElectionsMap) onSumMessage(evt *event.Event) (success bool) {
			errorf("our sum verifying key is nil")
			return
		}
		err := groth16.Verify(proof, el.LocalVoter.SumVerifyingKey.Vk, &publicCircuit)
		err := groth16.Verify(proof, el.LocalVoter.SumVerifyingKey.Vk(), &publicCircuit)
		if err != nil {
			warnf("sum proof verification failed: %s", err)
			return

M election/voter.go => election/voter.go +9 -9
@@ 33,8 33,8 @@ type Voter struct {
	Sum          *fr.Element `json:"sum,omitempty"`
	SumID        *id.EventID `json:"sum_id,omitempty"`

	EvalProvingKey *MarshallableProvingKey `json:"eval_proving_key,omitempty"`
	SumProvingKey  *MarshallableProvingKey `json:"sum_proving_key,omitempty"`
	EvalProvingKey *ProvingKeyFile `json:"eval_proving_key,omitempty"`
	SumProvingKey  *ProvingKeyFile `json:"sum_proving_key,omitempty"`
}

type LocalVoter struct {


@@ 42,9 42,9 @@ type LocalVoter struct {

	PrivKey [32]byte `json:"priv_key"`

	EvalVerifyingKey *MarshallableVerifyingKey `json:"eval_verifying_key,omitempty"`
	Poly             *math.Poly                `json:"poly,omitempty"`
	SumVerifyingKey  *MarshallableVerifyingKey `json:"sum_verifying_key,omitempty"`
	EvalVerifyingKey *VerifyingKeyFile `json:"eval_verifying_key,omitempty"`
	Poly             *math.Poly        `json:"poly,omitempty"`
	SumVerifyingKey  *VerifyingKeyFile `json:"sum_verifying_key,omitempty"`
}

func NewVoter(input *fr.Element, joinEvt *event.Event, pubKey *[32]byte, seedPart []byte) *Voter {


@@ 286,8 286,8 @@ func (el *Election) SendProvingKeys(client *mautrix.Client, eventStore *EventSto
	defer el.Save()
	defer el.Unlock()

	el.LocalVoter.EvalVerifyingKey = &MarshallableVerifyingKey{evalVerifyingKey}
	el.LocalVoter.SumVerifyingKey = &MarshallableVerifyingKey{sumVerifyingKey}
	el.LocalVoter.EvalVerifyingKey = NewVerifyingKeyFile(evalVerifyingKey)
	el.LocalVoter.SumVerifyingKey = NewVerifyingKeyFile(sumVerifyingKey)

	return nil
}


@@ 304,7 304,7 @@ func (el *Election) SendEvals(client *mautrix.Client, eventStore *EventStore) er
	for i, joinID := range *el.FinalJoinIDs {
		voter := el.Joins[joinID]

		output, outputHash, proof := el.LocalVoter.Poly.EvalAndProve(&voter.Input, voter.EvalProvingKey.Pk)
		output, outputHash, proof := el.LocalVoter.Poly.EvalAndProve(&voter.Input, voter.EvalProvingKey.Pk())

		// encrypt output
		var outputBytes [32]byte


@@ 429,7 429,7 @@ func (el *Election) SendSum(client *mautrix.Client, eventStore *EventStore) erro

		for i, joinID := range *el.FinalJoinIDs {
			voter := *el.Joins[joinID]
			proof, err := groth16.Prove(r1cs, voter.SumProvingKey.Pk, &witness)
			proof, err := groth16.Prove(r1cs, voter.SumProvingKey.Pk(), &witness)
			if err != nil {
				el.RUnlock()
				return fmt.Errorf("couldn't prove sum: %s", err)

M go.sum => go.sum +4 -0
@@ 14,7 14,9 @@ github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04=
github.com/gdamore/tcell/v2 v2.4.1-0.20211227212015-3260e4ac4385 h1:O5oaOCRcXvNnsPikhB6xGd4a1bbfgcuFQCQgDB4tM7Y=
github.com/gdamore/tcell/v2 v2.4.1-0.20211227212015-3260e4ac4385/go.mod h1:I8YJFI9gzgl4dHi9UlRDZosCW+jYkDA37AXmXvL51w4=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=


@@ 98,10 100,12 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maunium.net/go/maulogger/v2 v2.3.2 h1:1XmIYmMd3PoQfp9J+PaHhpt80zpfmMqaShzUTC7FwY0=
maunium.net/go/maulogger/v2 v2.3.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
maunium.net/go/mautrix v0.10.11 h1:u3D5+Ko7Pk0ruVFUAgjfk5E6U5Ys9VVObEGrytr0Hk4=
maunium.net/go/mautrix v0.10.11/go.mod h1:Ynac6y32yvdJC8YiYvWjWp6u1WjVTNq+JssC+07ZZWw=