~edwargix/tallyard

df12c5348c0d867eb6ba0b9064a0e40ac49e5f01 — David Florness 5 years ago 8fe4022 v0.2.0
Remove election data JSON file

I'm going to switch to protocol buffers.
4 files changed, 2 insertions(+), 281 deletions(-)

D config.go
D config_test.go
M election.go
M main.go
D config.go => config.go +0 -192
@@ 1,192 0,0 @@
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path"

	"github.com/kyoh86/xdg"
	"github.com/libp2p/go-libp2p-core/crypto"
	"github.com/mr-tron/base58/base58"
)

// where cached election info is stored
var (
	tallyardConfigPath = path.Join(xdg.DataHome(), "tallyard")
	electionInfoFilePath = path.Join(tallyardConfigPath, "elections.json")
)

// maps an election's key to its cached info
type ElectionInfos map[string]*ElectionInfo

// an entry of an election's cached info
type ElectionInfo struct{
	Candidates      []Candidate
	PrivKey         crypto.PrivKey
	PubKey          crypto.PubKey
	RendezvousNonce Nonce
}

// used for JSON {en,de}coding
type RawElectionInfos map[string]RawElectionInfo

type RawElectionInfo struct{
	Candidates      []string
	// Our private key in base58. TODO: encrypt
	PrivKey         string
	// Our public key in base58. Kept in addition to the private key to
	// check against when the private key is later encrypted
	PubKey          string
	RendezvousNonce string
}

// Saves an election's info into a file that can later be easily retrieved, even
// on separate invocations of tallyard.  Returns an error if there was one.
func (e *Election) saveInfo() error {
	err := os.MkdirAll(tallyardConfigPath, 0700)
	if err != nil {
		return err
	}
	infos, err := loadPastElectionInfos()
	if err != nil {
		return fmt.Errorf("unable to load election info while saving election info: %s", err)
	}
	file, err := os.OpenFile(electionInfoFilePath,
		os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return fmt.Errorf("unable to open election info file: %s", err)
	}
	defer file.Close()
	localVoter := e.localVoter
	infos[e.electionKey] = &ElectionInfo{
		e.Candidates,
		localVoter.Peerstore().PrivKey(localVoter.ID()),
		localVoter.Peerstore().PubKey(localVoter.ID()),
		e.rendezvousNonce,
	}
	raw, err := marshalElectionInfos(infos)
	if err != nil {
		return fmt.Errorf("unable to marshal election info for cache file: %s", err)
	}
	err = encodeElectionsInfos(file, raw)
	if err != nil {
		return fmt.Errorf("unable to encode election info for cache file: %s", err)
	}
	return nil
}

// Checks whether we've joined this election before and attempts to retrieve its
// info we cached for it.  Useful if a connection was suddently lost and
// tallyard had to restart.  Returns the election's info (nil if no such info
// exists) and an error
func getElectionInfo(electionKey string) (*ElectionInfo, error) {
	infos, err := loadPastElectionInfos()
	if err != nil {
		return nil, fmt.Errorf("unable to get election info: %s", err)
	}
	if info, hasElectionKey := infos[electionKey]; hasElectionKey {
		return info, nil
	}
	return nil, nil
}

func loadPastElectionInfos() (ElectionInfos, error) {
	err := os.MkdirAll(tallyardConfigPath, 0700)
	if err != nil {
		return nil, err
	}
	file, err := os.OpenFile(electionInfoFilePath,
		os.O_RDONLY|os.O_CREATE, 0600)
	if err != nil {
		return nil, fmt.Errorf("unable to open election info file: %s", err)
	}
	defer file.Close()
	raw, err := decodeElectionInfos(file)
	if err != nil {
		return nil, fmt.Errorf("unable to decode election info for cache file: %s", err)
	}
	infos, err := unmarshalRawElectionInfos(raw)
	if err != nil {
		return nil, fmt.Errorf("unable to unmarshal election info for cache file: %s", err)
	}
	return infos, nil
}

func encodeElectionsInfos(writer io.Writer, rawElections RawElectionInfos) error {
	encoder := json.NewEncoder(writer)
	return encoder.Encode(rawElections)
}

func decodeElectionInfos(reader io.Reader) (rawElections RawElectionInfos, err error) {
	decoder := json.NewDecoder(reader)
	err = decoder.Decode(&rawElections)
	if err == io.EOF {
		return make(RawElectionInfos), nil
	}
	if err != nil {
		return nil, err
	}
	return rawElections, nil
}

func marshalElectionInfos(electionConfigs ElectionInfos) (rawElections RawElectionInfos, err error) {
	rawElections = make(RawElectionInfos)
	for electionKey, electionConfig := range electionConfigs {
		candidates := make([]string, 0, len(electionConfig.Candidates))
		for _, c := range electionConfig.Candidates {
			candidates = append(candidates, string(c))
		}
		privKeyBytes, err := crypto.MarshalPrivateKey(electionConfig.PrivKey)
		if err != nil {
			return nil, err
		}
		privKeyStr := base58.Encode(privKeyBytes)
		pubKeyBytes, err := crypto.MarshalPublicKey(electionConfig.PubKey)
		if err != nil {
			return nil, err
		}
		pubKeyStr := base58.Encode(pubKeyBytes)
		rawElections[electionKey] = RawElectionInfo{
			candidates,
			privKeyStr,
			pubKeyStr,
			string(electionConfig.RendezvousNonce),
		}
	}
	return rawElections, nil
}

func unmarshalRawElectionInfos(rawElections RawElectionInfos) (electionConfigs ElectionInfos, err error) {
	electionConfigs = make(ElectionInfos)
	for electionKey, info := range rawElections {
		candidates := make([]Candidate, 0, len(info.Candidates))
		for _, s := range info.Candidates {
			candidates = append(candidates, Candidate(s))
		}
		privKeyBytes, err := base58.Decode(info.PrivKey)
		if err != nil {
			return nil, err
		}
		privKey, err :=  crypto.UnmarshalPrivateKey(privKeyBytes)
		if err != nil {
			return nil, err
		}
		pubKeyBytes, err := base58.Decode(info.PubKey)
		if err != nil {
			return nil, err
		}
		pubKey, err :=  crypto.UnmarshalPublicKey(pubKeyBytes)
		if err != nil {
			return nil, err
		}
		electionConfigs[electionKey] = &ElectionInfo{
			candidates,
			privKey,
			pubKey,
			Nonce(info.RendezvousNonce),
		}
	}
	return electionConfigs, nil
}

D config_test.go => config_test.go +0 -65
@@ 1,65 0,0 @@
package main

import (
	"fmt"
	"strings"
	"testing"
)

const (
	electionKey0 = "DpgZ3EG8cyNAxrbGejTTUuJB6KkvKVGK9WEpQWDh6RLd0QmVitsctABm8xbadtkqU46WMLCjuAadUR2RAkjaPf7xz7X"
	electionKey1 = "Dmfo6renA9YKuTFdGL4iSP5fcHbxHG4Qc9XTHkBzdHPa0QmVjm2QACdwMYguMtMooDoLsiagyVfe7qwxpuzUmX1stTM"
)

var rawElectionInfosRef = RawElectionInfos{
	electionKey0:
	{
		PrivKey:  "7r1r8iBFRYi5hLWbNS5vx53fAvnyFFGrgnDEwu8DSGRVdik5TVuPhCBpqummjaMMXgogcoz6h88rEWvM1af5G4VV4t5cNeFF6hXDyM14gxAMBkm2cEgpcf7d3AAH4auiHhZcB5XWUZeGKUgjz2WGtLPChHhAZzCA8GnDZHhrLSL2qfSt1ZYhyyvTLbEvfNZ7VhwXK64Y4UxjaRAXsMpBCret9aAck9pJxJnE5gm91JxTQybcTKULN9BxvJV99kLUdZ66PJ6ff5TZB4oNzShEQcdyP2G8tUJkCujokXWpZQh7ZDE4dtuoP6uvQhYSkPtWTgYv888BRYDoNDDqWc1nqZGqftr5EHogUTC4H7D3UWngVpwd1Mjbi8vS2DicKDXwJqP2bSyeVGLG3yLkWyjfxfBwNwnHGcBmhiSrL6bHfUrpJqy95UPC3ZSJeLNaYEToQAJmCEah4D7BZchF13rXjLGMsXxTUTHt9mMmEa8jdxLbqUkafVsN1VcHeZBVScvnFCS1e4xe1Ja54xg8H7b5HddB8JcSHaW2fJkvTrr3avT24NivAw4nvRg5UasMR2WMzgYP1rkmyV5vyPoJAnytYX8hZp2zX6gPbZBqUEDAQcvAegibjcDhRZMxMLCrR73UReEqXjjFhKYh7AsDz6uNDrwE6oVRJZ9GLBAetQ9HothETXr2PUaFkh82zCCCYbNL16aFWgiKYBAd1iaKL7idA4GrzTXuYR5HHJxcGSXNc9mUrGEEY2tLkNLPk3VHrqEDpzKArA72PGy1mKXVghoaJeZAe5am49ZPuPG4VQnkxHXB8NkhPkc26cbD6iYxq1cC7XDEMrQ8ggvecjhFxVnnFsDE2Ub7cZyVompvckPMX7R7VuERq7Ax1Vk4qNgbP5hHpn5F5DGpRNRdtpsEpzseSvBKQ6nDknSXFYSya99b4T7nwVkYCj7KuD21Xfmj4mRYQxy4aebKggUvPWAmMNSTWdrPK9QF7Y2cwagpDYQx8KEcVgGU5CrCeKscAbc84PbP6H2HMaMB5ivdMmuEbQfJuyMpX7sXwXCvHUg4p8R4UMf7nkF5c76H4oRH2CaJnnQ25DwYtMtfWmidTFFJECtaLU154z47L2TzNtn3G16UtUNvyLBA5b5HBKhKcjzwVHmRBreWz3urCF1obBT49mERb9mrsyicYjfoKqXS1mVVVCkvs1vAEm73RepXBHD37aQ4DVk9ThFB2RMiDxCwaW7EYUkNLpRaH1FHPJg2o9x1EkPsqcTg5TL31e3EctivtyLCVPLsaUsFYFmzyXCsniXeJPdPGxvt32fbeP1GUXv8N2xLAoVg815cuEnGvkjvztLK8Mv2fxtsPGQaEH6D9FEM2x1xRAFj6cspkbC5RpEbTcuEaaXBpBgVZSxT7TkYWUyerSYKLhocacwmGSYgduxvTB5w1nTfbk3sobEEjCrZwZjiQzdwkrP1iG4x1YDSB4f97n7gnbkEs5LDvuDwgS6KBeWnWTw1V2P5U8VkTCFhj6UAiv1FP9Tdi664MgDVNXQoEGQryy98zShcyg3BZ2Jr8yCdri3gVgMLG7b6Q91AXehz4RRs2X6SrZtCLKN2uBmhGwntvL2FvXDkGanXuRfUEMP8g2VdtWuW8",
		PubKey:   "7ySHQR7YZJApfZRKds9hzzFypZo8s6WQyZqXCYVHRjug84TSqhdKgdVBhS3cZ6NYMfygyfPMvAgjnn2mp3c5dSME49opHiD3ioUaArqGqddyWf3gonWVfnjkTnMcdpGXvDt74uAYf2NrwsphN1MuBBX5NAoHtE5vTRMAiZL94KSg7qFNZM1PckSJfo4xJUD1JgmEMqBvavosjFoenhZv7FjLkXFJhHFGfF7KaK4Q62bUubYtgiR1bkYk1RtctkjUvnBW8YnSx1QFGrmA5qAz8ZUCRmeqgktvpYDR2bj3XFDUGmjqNG3N4QMikg2a6vquCuuT6kv5WefzUYncvijQLHx85z74F8wMZV5huUxQM3RS2aZgc89WvFjeP5Pkmv1bUqvdZ5n1Yi3U65b9KBCR9ycL",
	},
	electionKey1:
	{
		PrivKey:  "B9bLmHeKLQU1hX23meSn2kJP4wwPAGeaXRpYnkZyKfR98RYs51wo7syA9N7oSbcf49hyrp9vaFpo2GA9yYkiWsPh3smeDDbSZsuzNoAZXr4tjPFytaaddvbsbsme5YbdMejRh1qPncRR6vmhBwb9r4xZBugb2PZPNbqBKKkVotF3YzVzJ3s6kjKyTirGW8xowFbma8hwWntfaNVuZF143BeHQMjyAB5QYRtLtx85RS4Zkxm4wv71Y5ZAcf3VMgmjGXTVXivWX4pkDHBSYfXYr6CoqLy4iNNWstnehBVPAJFM9YvYTnoT3DYXyXXgBSnfPLXFgX9yxkKh8YtXVNKewLqa3b9uKHzCh1FbxTEThfTK1Q4YPFLEek8eR2DUHinGuPrEgCmH4H8ZKXmfLkmGTgHWDkzDr4u2e74jE7Fz9XWmwHq39ZzUsE5SFVWVm6domENEgQMPHU83X7RFXPwgfCCiNdNYkozL9hvBgPujDtiiyBPExbUqY5dnCceTuD6ysJmhwL4SHWpdHKZwwWETWeDTzyA7snZjbebcmE1visFydZ2UQaQdHKnNU5gV2NkiQupyyCEvs9rrHW6xf6g9myMrzdRQVRBBuKswCjq91CNA2uAtkyNLzEhD9GSLuGFRgmtzEwGxek2p12ysUjJyfgBDY28CoMJVJfeK8dyCHQnyvtmA32uxQ8m83QDRxTGY52ubsLaKm9n1L4MiJKW73BC8JoZxdQmuW8278JUAWdTK7KDk2yCDfFfUwGLq6Jz1aTqV5KM23Ysjto6ENeYSHiNrwJ4yBCP2cq4bs48dfdCqWodzBN2HaUpiFiPfQDG5r52zow1aULEaLeUrUbCChKTi6WqWyEBKxEpVMiL7dRQqzHyq3JAFyu51CgmbfFyQtamV8ULE4aVMotrCm2QcrvWxLxkjvSb4cCcnJwyQ9QyT8jFpfVnDWSdLSpbRAxJzr4c4pxWMDjhhPfQYWLArMTv7N6woQwMg8L6AJVbm96qQQ8v99VHVRzib4gVP36DYmTT8WEks2LtFXBwypU8x17iREvwex2ZXHQgvYRAkgw1uYg8rkGMQ5ZTrekTbbsBnUxmcV7Y5Cb18PUVeTRvDtFUB8K2VsZZeTnfWsfvKNqURs4nLyn4VPicgtUbDwLvJnpijDfodv6ydYfbdpDEtkGrKXKwNTwWZPFAts61Wnsouu7dLCN2gKWfhh8x4NdcnZP3fKE15GfLViFMeanAZvww2DVyW78cXpeztanCmkbUqLSvoGkRJsZ49pFEtVeGv1at6SFNKYzyPCWYnegfvs2f54rUKCchWDoNZwYw7fja32d87qEUzWouorLXc4ATkykaKzHthoiwEMjweQKGgGgUXbNbP56x1jZYgLPSCVHHgGaLNKgqW6tSXEKThEEg42kzDNiymr7soWwGrZPsNyE5vpfoxd7ebeKy2LJ4H9AwqAbUFhuFAFwzcTRLsiWGJKg9ysi8fpcGBcg7NbrrkHwsR1jvvpV7f855pRTPMXCUQpYiGWVPtJ1UZAsmX5RqVmgfdyJHYok8aL2XML9GeHTHZZNg7iD2ST8DSdKNhaYs7oxyTMvWsWnqPezUq5kaQxRa8zTkFqUJYUAgiZrKDFrgsXC4EgboyZM3CK",
		PubKey:   "7ySHQR7YZJApfZRKds9hzzFypZo8s6WQyZqXCYVHRjug84TSqhdKDnnZpz69WtL8kvTgtrjEF3GHmsfjgMSt2t7vYgU26qHZfCXPUhA9u93vkCXSG6ZLBWw8K2HxMWPR69LoWzCsfXnLnw9mbr64uHmEeP64PSidtyshApUS73ikt7KGMdk3twJLr7SZ8uaqw6DCvBd9po5tVtPZ9gS1NPG3vuavLfm2P9YT2LMzCf68bJmqzgiVpUxRemvpwppVP675C5m6ctDELBTvqwQ9dc8baG4ztZPehEeUhULG5JpgtMKayCYRNhQVKdENyWXfFYzpMJ27iMH3LT2dixjgXRkqh8PKFt1srJA4we6vCLx1yjDjJV7MELo9P9nkXbojjQLXnqa41bYYGMN4ChK1C78U",
	},
}

func TestRawElectionInfosDecoding(t *testing.T) {
	jsonStream := fmt.Sprintf(`
	{"%s": {"PrivKey": "%s",
		"PubKey":  "%s"},
	 "%s": {"PrivKey": "%s",
		"PubKey":  "%s"}}`,
		electionKey0,
		rawElectionInfosRef[electionKey0].PrivKey,
		rawElectionInfosRef[electionKey0].PubKey,
		electionKey1,
		rawElectionInfosRef[electionKey1].PrivKey,
		rawElectionInfosRef[electionKey1].PubKey,
	)
	rawElections, err := decodeElectionInfos(strings.NewReader(jsonStream))
	if err != nil {
		panic(err)
	}
	if len(rawElections) != 2 {
		t.FailNow()
	}
	if _, exists := rawElections[electionKey0]; !exists {
		t.FailNow()
	}
	if _, exists := rawElections[electionKey1]; !exists {
		t.FailNow()
	}
	if rawElections[electionKey0].PrivKey != rawElectionInfosRef[electionKey0].PrivKey {
		t.FailNow()
	}
	if rawElections[electionKey1].PrivKey != rawElectionInfosRef[electionKey1].PrivKey {
		t.FailNow()
	}
	if rawElections[electionKey0].PubKey != rawElectionInfosRef[electionKey0].PubKey {
		t.FailNow()
	}
	if rawElections[electionKey1].PubKey != rawElectionInfosRef[electionKey1].PubKey {
		t.FailNow()
	}
}

M election.go => election.go +2 -19
@@ 61,25 61,7 @@ func NewElectionWithElectionKey(electionKey string) *Election {
		localVoter      *LocalVoter
		rendezvousNonce Nonce
	)
	// try to recover info from elections file to see if we've
	// joined/created this election before
	electionInfo, err := getElectionInfo(electionKey)
	if err != nil {
		panic(err)
	}
	if electionInfo != nil { // we've joined this election before
		// ensure public key from config is same as one derived from
		// private key
		candidates = electionInfo.Candidates
		if !electionInfo.PrivKey.GetPublic().Equals(electionInfo.PubKey) {
			// TODO: handle properly
			panic("loaded public key does not match derived one from private key")
		}
		localVoter = NewLocalVoter(libp2p.Identity(electionInfo.PrivKey))
		rendezvousNonce = electionInfo.RendezvousNonce
	} else { // we've never joined this election before
		localVoter = NewLocalVoter(libp2p.RandomIdentity)
	}
	localVoter = NewLocalVoter(libp2p.RandomIdentity)
	e := &Election{
		Candidates:      candidates,
		electionKey:     electionKey,


@@ 92,6 74,7 @@ func NewElectionWithElectionKey(electionKey string) *Election {
		panic("invalid election key")
	}
	logger.Info("merkle root:", electionKey[:zeroi])
	var err error
	e.merkleRoot, err = base58.Decode(electionKey[:zeroi])
	if err != nil {
		panic(err)

M main.go => main.go +0 -5
@@ 212,11 212,6 @@ func main() {
		}
	}

	// saves identity
	if err := election.saveInfo(); err != nil {
		panic(err)
	}

	election.localVoter.Bootstrap()

	if election.masterID == election.localVoter.ID() {