M election/msg.go => election/msg.go +84 -13
@@ 66,22 66,25 @@ type CreateElectionContent struct {
type JoinElectionContent struct {
Version string `json:"version"`
- CreateID id.EventID `json:"create_id"`
- Input string `json:"input"`
- PubKey string `json:"pub_key"`
- SeedPart string `json:"seed_part"`
+ Commitment string `json:"commitment"`
+ CreateID id.EventID `json:"create_id"`
+ Input string `json:"input"`
+ PubKey string `json:"pub_key"`
+ SeedPart string `json:"seed_part"`
}
type StartElectionContent struct {
Version string `json:"version"`
- CreateID id.EventID `json:"create_id"`
- JoinIDs []id.EventID `json:"join_ids"`
+ Commitment string `json:"commitment"`
+ CreateID id.EventID `json:"create_id"`
+ JoinIDs []id.EventID `json:"join_ids"`
}
type KeysMessageContent struct {
Version string `json:"version"`
+ Commitment string `json:"commitment"`
EvalProvingKeyURI id.ContentURI `json:"eval_proving_key_uri"`
JoinID id.EventID `json:"join_id"`
StartID id.EventID `json:"start_id"`
@@ 91,9 94,10 @@ type KeysMessageContent struct {
type EvalsMessageContent struct {
Version string `json:"version"`
- Evals []Eval `json:"evals"`
- JoinID id.EventID `json:"join_id"`
- KeysIDs []id.EventID `json:"keys_ids"`
+ Commitment string `json:"commitment"`
+ Evals []Eval `json:"evals"`
+ JoinID id.EventID `json:"join_id"`
+ KeysIDs []id.EventID `json:"keys_ids"`
}
type Eval struct {
@@ 108,10 112,11 @@ type Eval struct {
type SumMessageContent struct {
Version string `json:"version"`
- EvalsIDs []id.EventID `json:"evals_ids"`
- JoinID id.EventID `json:"join_id"`
- Sum string `json:"sum"`
- Proofs []string `json:"proofs"`
+ Commitment string `json:"commitment"`
+ EvalsIDs []id.EventID `json:"evals_ids"`
+ JoinID id.EventID `json:"join_id"`
+ Sum string `json:"sum"`
+ Proofs []string `json:"proofs"`
}
func init() {
@@ 281,6 286,17 @@ func (elections *ElectionsMap) onJoinElectionMessage(evt *event.Event) (success
return
}
+ // ensure commitment is consistent
+ commitmentHash, err := CalculateCommitment(createEvt.Content)
+ if err != nil {
+ warnf("we couldn't calculate the join event's commitment: %s", err)
+ return
+ }
+ if content.Commitment != commitmentHash {
+ warnf("the commitment (%s) does not match the hash of the create event (%s)", content.Commitment, commitmentHash)
+ return
+ }
+
// SeedPart
if content.SeedPart == "" {
warnf("the seed part is empty")
@@ 355,6 371,9 @@ func (elections *ElectionsMap) onStartElectionMessage(evt *event.Event) (success
return
}
+ contents := make([]event.Content, 0, 1+len(content.JoinIDs))
+ contents = append(contents, createEvt.Content)
+
for _, joinID := range content.JoinIDs {
joinEvt := elections.EventStore.GetJoinEvent(evt.RoomID, joinID)
if joinEvt == nil {
@@ 366,6 385,18 @@ func (elections *ElectionsMap) onStartElectionMessage(evt *event.Event) (success
joinID, content.CreateID)
return
}
+ contents = append(contents, joinEvt.Content)
+ }
+
+ // ensure commitment is consistent
+ commitmentHash, err := CalculateCommitment(contents...)
+ if err != nil {
+ warnf("we couldn't calculate the start event's commitment: %s", err)
+ return
+ }
+ if content.Commitment != commitmentHash {
+ warnf("the commitment (%s) does not match the hash of the prerequisite events (%s)", content.Commitment, commitmentHash)
+ return
}
if createEvt.Sender != evt.Sender {
@@ 436,6 467,17 @@ func (elections *ElectionsMap) onKeysMessage(evt *event.Event, client *mautrix.C
return
}
+ // ensure commitment is consistent
+ commitmentHash, err := CalculateCommitment(joinEvt.Content, startEvt.Content)
+ if err != nil {
+ warnf("we couldn't calculate the keys event's commitment: %s", err)
+ return
+ }
+ if content.Commitment != commitmentHash {
+ warnf("the commitment (%s) does not match the hash of the prerequisite events (%s)", content.Commitment, commitmentHash)
+ return
+ }
+
// ensure keys sender also sent join event
if evt.Sender != joinEvt.Sender {
warnf("they did not send the join event; %s did", joinEvt.Sender)
@@ 548,6 590,8 @@ func (elections *ElectionsMap) onEvalsMessage(evt *event.Event) (success bool) {
return
}
+ contents := []event.Content{joinEvt.Content}
+
el := elections.GetElection(joinEvt.CreateID)
if el == nil {
// should never happen because we got the start event above
@@ 572,6 616,7 @@ func (elections *ElectionsMap) onEvalsMessage(evt *event.Event) (success bool) {
keysEvent.JoinID, keysID, (*el.FinalJoinIDs)[i])
return
}
+ contents = append(contents, keysEvent.Content)
}
if len(content.Evals) != len(*el.FinalJoinIDs) {
@@ 580,6 625,17 @@ func (elections *ElectionsMap) onEvalsMessage(evt *event.Event) (success bool) {
return
}
+ // ensure commitment is consistent
+ commitmentHash, err := CalculateCommitment(contents...)
+ if err != nil {
+ warnf("we couldn't calculate the evals event's commitment: %s", err)
+ return
+ }
+ if content.Commitment != commitmentHash {
+ warnf("the commitment (%s) does not match the hash of the prerequisite events (%s)", content.Commitment, commitmentHash)
+ return
+ }
+
el.Lock()
defer el.Save()
defer el.Unlock()
@@ 763,6 819,8 @@ func (elections *ElectionsMap) onSumMessage(evt *event.Event) (success bool) {
return
}
+ contents := make([]event.Content, 0, len(content.EvalsIDs)+1)
+
for i, evalsID := range content.EvalsIDs {
evalsEvt := elections.EventStore.GetEvalsEvent(evt.RoomID, evalsID)
if evalsEvt == nil {
@@ 774,6 832,19 @@ func (elections *ElectionsMap) onSumMessage(evt *event.Event) (success bool) {
evalsEvt.JoinID, evalsID, (*el.FinalJoinIDs)[i])
return
}
+ contents = append(contents, evalsEvt.Content)
+ }
+
+ // ensure commitment is consistent
+ contents = append(contents, joinEvt.Content)
+ commitmentHash, err := CalculateCommitment(contents...)
+ if err != nil {
+ warnf("we couldn't calculate the sum event's commitment: %s", err)
+ return
+ }
+ if content.Commitment != commitmentHash {
+ warnf("the commitment (%s) does not match the hash of the prerequisite events (%s)", content.Commitment, commitmentHash)
+ return
}
el.Lock()
M election/utils.go => election/utils.go +28 -0
@@ 1,12 1,17 @@
package election
import (
+ "encoding/json"
+ "errors"
"fmt"
"os"
"time"
"github.com/kyoh86/xdg"
log "github.com/sirupsen/logrus"
+ "maunium.net/go/mautrix/crypto/canonicaljson"
+ "maunium.net/go/mautrix/crypto/olm"
+ "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
@@ 20,3 25,26 @@ func LogUpload(contentURI id.ContentURI) {
t, _ := time.Now().UTC().MarshalText()
fmt.Fprintf(file, "%s\t%s\n", string(t), contentURI.String())
}
+
+func CalculateCommitment(prerequisiteEventContent ...event.Content) (string, error) {
+ if len(prerequisiteEventContent) == 0 {
+ return "", errors.New("no content given")
+ }
+
+ olmUtility := olm.NewUtility()
+ digest := ""
+
+ for i, content := range prerequisiteEventContent {
+ byts, err := json.Marshal(content)
+ if err != nil {
+ return "", fmt.Errorf("couldn't marshal content[%d]: %s", i, err)
+ }
+ canonical, err := canonicaljson.CanonicalJSON(byts)
+ if err != nil {
+ return "", fmt.Errorf("couldn't canonicalize marshalled content[%d]: %s", i, err)
+ }
+ digest = olmUtility.Sha256(digest + string(canonical))
+ }
+
+ return digest, nil
+}
M election/voter.go => election/voter.go +84 -14
@@ 98,13 98,19 @@ func (el *Election) JoinElection(client *mautrix.Client, eventStore *EventStore)
return fmt.Errorf("couldn't read random bytes: %s", err)
}
+ commitmentHash, err := CalculateCommitment(el.CreateEvt.Content)
+ if err != nil {
+ return fmt.Errorf("couldn't calculate join event's commitment: %s", err)
+ }
+
resp, err := client.SendMessageEvent(el.RoomID, JoinElectionMessage, JoinElectionContent{
Version: tallyard.Version,
- CreateID: el.CreateEvt.ID,
- Input: base64.StdEncoding.EncodeToString(inputBytes[:]),
- PubKey: base64.StdEncoding.EncodeToString((*pubKey)[:]),
- SeedPart: base64.StdEncoding.EncodeToString(seedBytes[:]),
+ Commitment: commitmentHash,
+ CreateID: el.CreateEvt.ID,
+ Input: base64.StdEncoding.EncodeToString(inputBytes[:]),
+ PubKey: base64.StdEncoding.EncodeToString((*pubKey)[:]),
+ SeedPart: base64.StdEncoding.EncodeToString(seedBytes[:]),
})
if err != nil {
return fmt.Errorf("couldn't send join messages: %s", err)
@@ 148,10 154,25 @@ func (el *Election) StartElection(client *mautrix.Client, eventStore *EventStore
voters = append(voters, voter.JoinEvt.ID)
}
+ var commitmentHash string
+ {
+ contents := make([]event.Content, 0, 1+len(el.Joins))
+ contents = append(contents, el.CreateEvt.Content)
+ for _, voter := range voters {
+ contents = append(contents, el.Joins[voter].JoinEvt.Content)
+ }
+ var err error
+ commitmentHash, err = CalculateCommitment(contents...)
+ if err != nil {
+ return fmt.Errorf("couldn't calculate start event's commitment: %s", err)
+ }
+ }
+
resp, err := client.SendMessageEvent(el.RoomID, StartElectionMessage, StartElectionContent{
- Version: tallyard.Version,
- CreateID: el.CreateEvt.ID,
- JoinIDs: voters,
+ Version: tallyard.Version,
+ Commitment: commitmentHash,
+ CreateID: el.CreateEvt.ID,
+ JoinIDs: voters,
})
if err != nil {
return err
@@ 227,9 248,20 @@ func (el *Election) SendProvingKeys(client *mautrix.Client, eventStore *EventSto
sumProvingKeyURI = uploadResp.ContentURI
}
+ var commitmentHash string
+ {
+ startEvt := eventStore.GetStartEvent(el.RoomID, *el.StartID)
+ var err error
+ commitmentHash, err = CalculateCommitment(el.LocalVoter.JoinEvt.Content, startEvt.Content)
+ if err != nil {
+ return fmt.Errorf("couldn't calculate keys event's commitment: %s", err)
+ }
+ }
+
resp, err := client.SendMessageEvent(el.RoomID, KeysMessage, KeysMessageContent{
Version: tallyard.Version,
+ Commitment: commitmentHash,
EvalProvingKeyURI: evalProvingKeyURI,
JoinID: el.LocalVoter.JoinEvt.ID,
StartID: *el.StartID,
@@ 313,12 345,31 @@ func (el *Election) SendEvals(client *mautrix.Client, eventStore *EventStore) er
keysIDs[i] = *voter.KeysID
}
+ var commitmentHash string
+ {
+ contents := make([]event.Content, 0, 1+len(keysIDs))
+ contents = append(contents, el.LocalVoter.JoinEvt.Content)
+ for _, keysID := range keysIDs {
+ keysEvent := eventStore.GetKeysEvent(el.RoomID, keysID)
+ if keysEvent == nil {
+ return fmt.Errorf("couldn't get keys event %s for evals commitment calculation", keysID)
+ }
+ contents = append(contents, keysEvent.Content)
+ }
+ var err error
+ commitmentHash, err = CalculateCommitment(contents...)
+ if err != nil {
+ return fmt.Errorf("couldn't calculate evals event's commitment: %s", err)
+ }
+ }
+
resp, err := client.SendMessageEvent(el.RoomID, EvalsMessage, EvalsMessageContent{
Version: tallyard.Version,
- Evals: evals,
- JoinID: el.LocalVoter.JoinEvt.ID,
- KeysIDs: keysIDs,
+ Commitment: commitmentHash,
+ Evals: evals,
+ JoinID: el.LocalVoter.JoinEvt.ID,
+ KeysIDs: keysIDs,
})
el.RUnlock()
if err != nil {
@@ 399,14 450,33 @@ func (el *Election) SendSum(client *mautrix.Client, eventStore *EventStore) erro
}
}
+ var commitmentHash string
+ {
+ contents := make([]event.Content, 0, len(evalsIDs)+1)
+ for _, evalsID := range evalsIDs {
+ evalsEvent := eventStore.GetEvalsEvent(el.RoomID, evalsID)
+ if evalsEvent == nil {
+ return fmt.Errorf("couldn't get evals event %s for sum commitment calculation", evalsID)
+ }
+ contents = append(contents, evalsEvent.Content)
+ }
+ contents = append(contents, el.LocalVoter.JoinEvt.Content)
+ var err error
+ commitmentHash, err = CalculateCommitment(contents...)
+ if err != nil {
+ return fmt.Errorf("couldn't calculate sum event's commitment: %s", err)
+ }
+ }
+
sumBytes := el.LocalVoter.Sum.Bytes()
resp, err := client.SendMessageEvent(el.RoomID, SumMessage, SumMessageContent{
Version: tallyard.Version,
- EvalsIDs: evalsIDs,
- JoinID: el.LocalVoter.JoinEvt.ID,
- Sum: base64.StdEncoding.EncodeToString(sumBytes[:]),
- Proofs: proofs,
+ Commitment: commitmentHash,
+ EvalsIDs: evalsIDs,
+ JoinID: el.LocalVoter.JoinEvt.ID,
+ Sum: base64.StdEncoding.EncodeToString(sumBytes[:]),
+ Proofs: proofs,
})
el.RUnlock()
if err != nil {
M go.sum => go.sum +4 -0
@@ 106,9 106,13 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/tidwall/gjson v1.10.2 h1:APbLGOM0rrEkd8WBw9C24nllro4ajFuJu0Sc9hRz8Bo=
github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/sjson v1.2.3 h1:5+deguEhHSEjmuICXZ21uSSsXotWMA0orU783+Z7Cp8=
github.com/tidwall/sjson v1.2.3/go.mod h1:5WdjKx3AQMvCJ4RG6/2UYT7dLrGvJUV1x4jdTAyGvZs=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=