From abb712d2b647b337fe571000410868445ddb78d1 Mon Sep 17 00:00:00 2001 From: p4u Date: Thu, 31 Jan 2019 18:54:02 +0100 Subject: [PATCH 1/7] Adapt merkletree to new go-iden3 implementation First iteration, more work needs to be done. Signed-off-by: p4u --- tree/tree.go | 96 ++++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/tree/tree.go b/tree/tree.go index 88de057..ae1d5fa 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -4,12 +4,11 @@ import ( "errors" "fmt" "os/user" - "strings" - common3 "github.com/vocdoni/go-iden3/common" - mkcore "github.com/vocdoni/go-iden3/core" - "github.com/vocdoni/go-iden3/db" - "github.com/vocdoni/go-iden3/merkletree" + common3 "github.com/iden3/go-iden3/common" + mkcore "github.com/iden3/go-iden3/core" + db "github.com/iden3/go-iden3/db" + merkletree "github.com/iden3/go-iden3/merkletree" ) type Tree struct { @@ -32,7 +31,7 @@ func (t *Tree) Init() error { if err != nil { return err } - mt, err := merkletree.New(mtdb, 140) + mt, err := merkletree.NewMerkleTree(mtdb, 140) if err != nil { return err } @@ -45,74 +44,75 @@ func (t *Tree) Close() { defer t.Tree.Storage().Close() } +func (t *Tree) GetClaim(data []byte) (*mkcore.ClaimBasic, error) { + if len(data) > 496/8 { + return nil, errors.New("claim data too large") + } + for i := len(data); i <= 496/8; i++ { + data = append(data, byte('0')) + } + var indexSlot [400 / 8]byte + var dataSlot [496 / 8]byte + copy(indexSlot[:], data[:400/8]) + copy(dataSlot[:], data[:496/8]) + e := mkcore.NewClaimBasic(indexSlot, dataSlot) + return e, nil +} + func (t *Tree) AddClaim(data []byte) error { - isSnapshot := strings.HasPrefix(t.Namespace, "0x") - if isSnapshot { - return errors.New("No new claims can be added to a Snapshot") + e, err := t.GetClaim(data) + if err != nil { + return err } - claim := mkcore.NewGenericClaim(t.Namespace, "default", data, nil) - return t.Tree.Add(claim) + return t.Tree.Add(e.Entry()) } func (t *Tree) GenProof(data []byte) (string, error) { - claim := mkcore.NewGenericClaim(t.Namespace, "default", data, nil) - mp, err := t.Tree.GenerateProof(claim.Hi()) + e, err := t.GetClaim(data) if err != nil { return "", err } - mpHex := common3.BytesToHex(mp) + mp, err := t.Tree.GenerateProof(e.Entry().HIndex()) + if err != nil { + return "", err + } + mpHex := common3.HexEncode(mp.Bytes()) return mpHex, nil } func (t *Tree) CheckProof(data []byte, mpHex string) (bool, error) { - mp, err := common3.HexToBytes(mpHex) + mpBytes, err := common3.HexDecode(mpHex) if err != nil { return false, err } - claim := mkcore.NewGenericClaim(t.Namespace, "default", data, nil) - return merkletree.CheckProof(t.Tree.Root(), mp, claim.Hi(), claim.Ht(), t.Tree.NumLevels()), nil + mp, err := merkletree.NewProofFromBytes(mpBytes) + if err != nil { + return false, err + } + e, err := t.GetClaim(data) + if err != nil { + return false, err + } + return merkletree.VerifyProof(t.Tree.RootKey(), mp, + e.Entry().HIndex(), e.Entry().HValue()), nil } func (t *Tree) GetRoot() string { - return t.Tree.Root().Hex() + return t.Tree.RootKey().String() } -/* Dump, Export and Snapshot functions are a bit tricky. - Since go-iden3 does not provide the necessary tools. Low level operations must be performed. - Once go-iden3 API is mature enough, these functions must be adapted. - - To explore: Values are stored twice in the BD? -*/ func (t *Tree) Dump() ([]string, error) { var response []string - substorage := t.DbStorage.WithPrefix([]byte(t.Namespace)) - nsHash := merkletree.HashBytes([]byte(t.Namespace)) - substorage.Iterate(func(key, value []byte) { - nsValue := value[5:37] - if fmt.Sprint(nsHash) == fmt.Sprint(nsValue) { - response = append(response, string(value[69:])) + + err := t.Tree.Walk(t.Tree.RootKey(), func(n *merkletree.Node) { + if n.Type == merkletree.NodeTypeLeaf { + response = append(response, fmt.Sprintf("|%s", n.Entry.Data)) } }) - return response, nil + return response, err } func (t *Tree) Snapshot() (string, error) { - substorage := t.DbStorage.WithPrefix([]byte(t.Namespace)) - nsHash := merkletree.HashBytes([]byte(t.Namespace)) - snapshotNamespace := t.GetRoot() - fmt.Printf("Snapshoting %s\n", snapshotNamespace) - t.Namespace = snapshotNamespace - substorage.Iterate(func(key, value []byte) { - nsValue := value[5:37] - if fmt.Sprint(nsHash) == fmt.Sprint(nsValue) { - fmt.Printf("%x\n", value) - data := value[69:] - //claim := mkcore.NewGenericClaim(snapshotNamespace, "default", data, nil) - err := t.AddClaim(data) - if err != nil { - fmt.Println(err) - } - } - }) + snapshotNamespace := t.Tree.RootKey().String() return snapshotNamespace, nil } From dad369ebff6b79c4a2de01b13348c96188321502 Mon Sep 17 00:00:00 2001 From: p4u Date: Fri, 1 Feb 2019 20:20:34 +0100 Subject: [PATCH 2/7] Keep adapting the code to new merkletree implementation Use different database storages and merkletree for each censusID Signed-off-by: p4u --- cmd/censushttp/README.md | 10 +++-- cmd/censushttp/censushttp.go | 23 +++++++---- service/README.md | 15 -------- service/censusmanager.go | 74 ++++++++++++++++++------------------ tree/tree.go | 22 ++++++++--- 5 files changed, 74 insertions(+), 70 deletions(-) delete mode 100644 service/README.md diff --git a/cmd/censushttp/README.md b/cmd/censushttp/README.md index f00433d..b52242e 100644 --- a/cmd/censushttp/README.md +++ b/cmd/censushttp/README.md @@ -13,14 +13,14 @@ go build -o censusHttpService github.com/vocdoni/dvote-census/cmd/censushttp #### Usage -`./censusHttpService [publicKey]` +`./censusHttpService [:pubKey] [[:pubKey] ...]` Example: ``` -./censusHttpService 1500 -2018/12/17 09:54:20 Starting process HTTP service on port 1500 -2018/12/17 09:54:20 Starting server in http mode +./censusHttpService 1500 Got_Favorite +2019/02/01 20:01:15 Starting process HTTP service on port 1500 for namespace GoT_Favorite +2019/02/01 20:01:15 Starting server in http mode ``` #### add claims @@ -35,6 +35,8 @@ If public key authentication enabled, `signature` field is required using Nancl The signed message is expected to be a concatenation of the following fields: `censusID, claimData, timeStamp` +There is a time window of 10 seconds while the signature is considered valid. + ``` curl -d '{"censusID":"GoT_Favorite", "claimData":"Jon Snow", diff --git a/cmd/censushttp/censushttp.go b/cmd/censushttp/censushttp.go index 68d545a..b80c64c 100644 --- a/cmd/censushttp/censushttp.go +++ b/cmd/censushttp/censushttp.go @@ -4,13 +4,15 @@ import ( "log" "os" "strconv" + "strings" censusmanager "github.com/vocdoni/dvote-census/service" ) func main() { if len(os.Args) < 2 { - log.Fatal("Usage: " + os.Args[0] + " [pubKey]") + log.Fatal("Usage: " + os.Args[0] + + " [:pubKey] [[:pubKey]]...") os.Exit(2) } port, err := strconv.Atoi(os.Args[1]) @@ -18,13 +20,18 @@ func main() { log.Fatal(err) os.Exit(2) } - pubK := "" - if len(os.Args) > 2 { - log.Print("Public key authentication enabled") - pubK = os.Args[2] + for i := 2; i < len(os.Args); i++ { + s := strings.Split(os.Args[i], ":") + ns := s[0] + pubK := "" + if len(s) > 1 { + pubK = s[1] + log.Printf("Public Key authentication enabled on namespace %s\n", ns) + } + censusmanager.AddNamespace(ns, pubK) + log.Printf("Starting process HTTP service on port %d for namespace %s\n", + port, ns) } - log.Print("Starting process HTTP service on port " + os.Args[1]) - censusmanager.T.Init() - censusmanager.Listen(port, "http", pubK) + censusmanager.Listen(port, "http") } diff --git a/service/README.md b/service/README.md deleted file mode 100644 index 8263538..0000000 --- a/service/README.md +++ /dev/null @@ -1,15 +0,0 @@ -## dvot-census http service library - -#### How to use it - -``` -processHttp.T.Init() -processHttp.Listen(1500, "http", "") -``` - -To enable authentication (using pubKey signature): - -``` -pubK := "39f54ce5293520b689f6658ea7f3401f4ff931fa3d90dea21ff901cdf82bb8aa" -processHttp.Listen(1500, "http", pubK) -``` diff --git a/service/censusmanager.go b/service/censusmanager.go index 6b56b85..4bb6da1 100644 --- a/service/censusmanager.go +++ b/service/censusmanager.go @@ -9,16 +9,14 @@ import ( "strings" "time" - "github.com/vocdoni/dvote-census/tree" - "github.com/vocdoni/dvote-relay/crypto/signature" + tree "github.com/vocdoni/dvote-census/tree" + signature "github.com/vocdoni/dvote-relay/crypto/signature" ) -const authTimeWindow = 10 // Time window (seconds) in which TimeStamp will be accepted if auth enabled - -var authPubKey string - -var T tree.Tree // MerkleTree dvote-census library -var S signature.SignKeys // Signature dvote-relay library +const authTimeWindow = 10 // Time window (seconds) in which TimeStamp will be accepted if auth enabled +var MkTrees map[string]*tree.Tree // MerkleTree dvote-census library +var Signatures map[string]string +var Signature signature.SignKeys // Signature dvote-relay library type Claim struct { CensusID string `json:"censusID"` // References to MerkleTree namespace @@ -33,6 +31,20 @@ type Result struct { Response string `json:"response"` } +func AddNamespace(name, pubKey string) { + if len(MkTrees) == 0 { + MkTrees = make(map[string]*tree.Tree) + } + if len(Signatures) == 0 { + Signatures = make(map[string]string) + } + + mkTree := tree.Tree{} + mkTree.Init(name) + MkTrees[name] = &mkTree + Signatures[name] = pubKey +} + func reply(resp *Result, w http.ResponseWriter) { err := json.NewEncoder(w).Encode(resp) if err != nil { @@ -50,8 +62,8 @@ func checkRequest(w http.ResponseWriter, req *http.Request) bool { return true } -func checkAuth(timestamp, signature, message string) bool { - if len(authPubKey) < 1 { +func checkAuth(timestamp, signature, pubKey, message string) bool { + if len(pubKey) < 1 { return true } currentTime := int64(time.Now().Unix()) @@ -62,7 +74,7 @@ func checkAuth(timestamp, signature, message string) bool { } if timeStampRemote < currentTime+authTimeWindow && timeStampRemote > currentTime-authTimeWindow { - v, err := S.Verify(message, signature, authPubKey) + v, err := Signature.Verify(message, signature, pubKey) if err != nil { log.Printf("Verification error: %s\n", err) } @@ -74,6 +86,7 @@ func checkAuth(timestamp, signature, message string) bool { func claimHandler(w http.ResponseWriter, req *http.Request, op string) { var c Claim var resp Result + if ok := checkRequest(w, req); !ok { return } @@ -89,31 +102,25 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) { c.CensusID, c.ClaimData, c.ProofData, c.TimeStamp, c.Signature) resp.Error = false resp.Response = "" - + censusFound := false if len(c.CensusID) > 0 { - T.Namespace = c.CensusID - } else { - resp.Error = true - resp.Response = "censusID is not valid" - reply(&resp, w) - return + _, censusFound = MkTrees[c.CensusID] } - - if len(c.ClaimData) < 0 { + if !censusFound { resp.Error = true - resp.Response = "data not valid" + resp.Response = "censusID not valid or not found" reply(&resp, w) return } if op == "add" { msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp) - if auth := checkAuth(c.TimeStamp, c.Signature, msg); auth { + if auth := checkAuth(c.TimeStamp, c.Signature, Signatures[c.CensusID], msg); auth { if strings.HasPrefix(c.CensusID, "0x") { resp.Error = true resp.Response = "add claim to snapshot is not allowed" } else { - err = T.AddClaim([]byte(c.ClaimData)) + err = MkTrees[c.CensusID].AddClaim([]byte(c.ClaimData)) } } else { resp.Error = true @@ -122,15 +129,15 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) { } if op == "gen" { - resp.Response, err = T.GenProof([]byte(c.ClaimData)) + resp.Response, err = MkTrees[c.CensusID].GenProof([]byte(c.ClaimData)) } if op == "root" { - resp.Response = T.GetRoot() + resp.Response = MkTrees[c.CensusID].GetRoot() } if op == "dump" { - values, err := T.Dump() + values, err := MkTrees[c.CensusID].Dump() if err != nil { resp.Error = true resp.Response = fmt.Sprint(err) @@ -147,12 +154,12 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) { if op == "snapshot" { msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp) - if auth := checkAuth(c.TimeStamp, c.Signature, msg); auth { + if auth := checkAuth(c.TimeStamp, c.Signature, Signatures[c.CensusID], msg); auth { if strings.HasPrefix(c.CensusID, "0x") { resp.Error = true resp.Response = "snapshot an snapshot makes no sense" } else { - snapshotNamespace, err := T.Snapshot() + snapshotNamespace, err := MkTrees[c.CensusID].Snapshot() if err != nil { resp.Error = true resp.Response = fmt.Sprint(err) @@ -171,7 +178,7 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) { resp.Error = true resp.Response = "proofData not provided" } else { - validProof, _ := T.CheckProof([]byte(c.ClaimData), c.ProofData) + validProof, _ := MkTrees[c.CensusID].CheckProof([]byte(c.ClaimData), c.ProofData) if validProof { resp.Response = "valid" } else { @@ -183,7 +190,7 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) { reply(&resp, w) } -func Listen(port int, proto string, pubKey string) { +func Listen(port int, proto string) { srv := &http.Server{ Addr: fmt.Sprintf(":%d", port), ReadHeaderTimeout: 4 * time.Second, @@ -211,13 +218,6 @@ func Listen(port int, proto string, pubKey string) { claimHandler(w, r, "dump") }) - if len(pubKey) > 1 { - log.Printf("Enabling signature authentication with %s\n", pubKey) - authPubKey = pubKey - } else { - authPubKey = "" - } - if proto == "https" { log.Print("Starting server in https mode") if err := srv.ListenAndServeTLS("server.crt", "server.key"); err != nil { diff --git a/tree/tree.go b/tree/tree.go index ae1d5fa..4ebeac6 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -12,19 +12,21 @@ import ( ) type Tree struct { - Namespace string Storage string Tree *merkletree.MerkleTree DbStorage *db.LevelDbStorage } -func (t *Tree) Init() error { +func (t *Tree) Init(namespace string) error { if len(t.Storage) < 1 { + if len(namespace) < 1 { + return errors.New("namespace not valid") + } usr, err := user.Current() if err == nil { - t.Storage = usr.HomeDir + "/.dvote/Tree" + t.Storage = usr.HomeDir + "/.dvote/census/" + namespace } else { - t.Storage = "./dvoteTree" + t.Storage = "./dvoteTree/" + namespace } } mtdb, err := db.NewLevelDbStorage(t.Storage, false) @@ -49,7 +51,7 @@ func (t *Tree) GetClaim(data []byte) (*mkcore.ClaimBasic, error) { return nil, errors.New("claim data too large") } for i := len(data); i <= 496/8; i++ { - data = append(data, byte('0')) + data = append(data, byte('.')) } var indexSlot [400 / 8]byte var dataSlot [496 / 8]byte @@ -106,7 +108,15 @@ func (t *Tree) Dump() ([]string, error) { err := t.Tree.Walk(t.Tree.RootKey(), func(n *merkletree.Node) { if n.Type == merkletree.NodeTypeLeaf { - response = append(response, fmt.Sprintf("|%s", n.Entry.Data)) + rawValue := n.Value() + var cleanValue []byte + for i := 0; i < len(rawValue); i++ { + if rawValue[i] == byte('.') { + break + } + cleanValue = append(cleanValue, rawValue[i]) + } + response = append(response, fmt.Sprintf("%s", cleanValue)) } }) return response, err From 0c3587bb9c26b3a2953487c0b2d92cd6ca50bcc7 Mon Sep 17 00:00:00 2001 From: p4u Date: Thu, 7 Feb 2019 17:16:28 +0100 Subject: [PATCH 3/7] Keep adapting code to new merkletree Add RootHash JSON parameter. Remove Spanshopt API method. Add support on genProof/checkProof/dump to specify the RootHash Refactory of some code Signed-off-by: p4u --- service/censusmanager.go | 127 +++++++++++++++++++++++++-------------- tree/tree.go | 30 +++++++-- 2 files changed, 109 insertions(+), 48 deletions(-) diff --git a/service/censusmanager.go b/service/censusmanager.go index 4bb6da1..aad9dc5 100644 --- a/service/censusmanager.go +++ b/service/censusmanager.go @@ -6,13 +6,13 @@ import ( "log" "net/http" "strconv" - "strings" "time" tree "github.com/vocdoni/dvote-census/tree" signature "github.com/vocdoni/dvote-relay/crypto/signature" ) +const hashSize = 32 const authTimeWindow = 10 // Time window (seconds) in which TimeStamp will be accepted if auth enabled var MkTrees map[string]*tree.Tree // MerkleTree dvote-census library var Signatures map[string]string @@ -20,6 +20,7 @@ var Signature signature.SignKeys // Signature dvote-relay library type Claim struct { CensusID string `json:"censusID"` // References to MerkleTree namespace + RootHash string `json:"rootHash"` // References to MerkleTree rootHash ClaimData string `json:"claimData"` // Data to add to the MerkleTree ProofData string `json:"proofData"` // MerkleProof to check TimeStamp string `json:"timeStamp"` // Unix TimeStamp in seconds @@ -98,8 +99,9 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) { } // Process data - log.Printf("Received censusID:{%s} claimData:{%s} proofData:{%s} timeStamp:{%s} signature:{%s}\n", - c.CensusID, c.ClaimData, c.ProofData, c.TimeStamp, c.Signature) + log.Printf("censusID:{%s} rootHash:{%s} claimData:{%s} proofData:{%s} timeStamp:{%s} signature:{%s}\n", + c.CensusID, c.RootHash, c.ClaimData, c.ProofData, c.TimeStamp, c.Signature) + authString := fmt.Sprintf("%s%s%s%s", c.CensusID, c.RootHash, c.ClaimData, c.TimeStamp) resp.Error = false resp.Response = "" censusFound := false @@ -114,14 +116,8 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) { } if op == "add" { - msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp) - if auth := checkAuth(c.TimeStamp, c.Signature, Signatures[c.CensusID], msg); auth { - if strings.HasPrefix(c.CensusID, "0x") { - resp.Error = true - resp.Response = "add claim to snapshot is not allowed" - } else { - err = MkTrees[c.CensusID].AddClaim([]byte(c.ClaimData)) - } + if auth := checkAuth(c.TimeStamp, c.Signature, Signatures[c.CensusID], authString); auth { + err = MkTrees[c.CensusID].AddClaim([]byte(c.ClaimData)) } else { resp.Error = true resp.Response = "invalid authentication" @@ -129,61 +125,107 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) { } if op == "gen" { - resp.Response, err = MkTrees[c.CensusID].GenProof([]byte(c.ClaimData)) + var t *tree.Tree + var err error + if len(c.RootHash) > 1 { //if rootHash specified + t, err = MkTrees[c.CensusID].Snapshot(c.RootHash) + if err != nil { + log.Printf("Snapshot error: %s", err.Error()) + resp.Error = true + resp.Response = "invalid root hash" + reply(&resp, w) + return + } + } else { //if rootHash not specified use current tree + t = MkTrees[c.CensusID] + } + resp.Response, err = t.GenProof([]byte(c.ClaimData)) + if err != nil { + resp.Error = true + resp.Response = err.Error() + reply(&resp, w) + return + } } if op == "root" { resp.Response = MkTrees[c.CensusID].GetRoot() } + if op == "idx" { + + } + if op == "dump" { - values, err := MkTrees[c.CensusID].Dump() + var t *tree.Tree + if auth := checkAuth(c.TimeStamp, c.Signature, Signatures[c.CensusID], authString); !auth { + resp.Error = true + resp.Response = "invalid authentication" + reply(&resp, w) + return + } + + if len(c.RootHash) > 1 { //if rootHash specified + t, err = MkTrees[c.CensusID].Snapshot(c.RootHash) + if err != nil { + log.Printf("Snapshot error: %s", err.Error()) + resp.Error = true + resp.Response = "invalid root hash" + reply(&resp, w) + return + } + } else { //if rootHash not specified use current merkletree + t = MkTrees[c.CensusID] + } + + //dump the claim data and return it + values, err := t.Dump() if err != nil { resp.Error = true - resp.Response = fmt.Sprint(err) + resp.Response = err.Error() } else { jValues, err := json.Marshal(values) if err != nil { resp.Error = true - resp.Response = fmt.Sprint(err) + resp.Response = err.Error() } else { resp.Response = string(jValues) } } } - if op == "snapshot" { - msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp) - if auth := checkAuth(c.TimeStamp, c.Signature, Signatures[c.CensusID], msg); auth { - if strings.HasPrefix(c.CensusID, "0x") { + if op == "check" { + if len(c.ProofData) < 1 { + resp.Error = true + resp.Response = "proofData not provided" + reply(&resp, w) + return + } + var t *tree.Tree + if len(c.RootHash) > 1 { //if rootHash specified + t, err = MkTrees[c.CensusID].Snapshot(c.RootHash) + if err != nil { + log.Printf("Snapshot error: %s", err.Error()) resp.Error = true - resp.Response = "snapshot an snapshot makes no sense" - } else { - snapshotNamespace, err := MkTrees[c.CensusID].Snapshot() - if err != nil { - resp.Error = true - resp.Response = fmt.Sprint(err) - } else { - resp.Response = snapshotNamespace - } + resp.Response = "invalid root hash" + reply(&resp, w) + return } - } else { - resp.Error = true - resp.Response = "invalid authentication" + } else { //if rootHash not specified use current merkletree + t = MkTrees[c.CensusID] } - } - if op == "check" { - if len(c.ProofData) < 1 { + validProof, err := t.CheckProof([]byte(c.ClaimData), c.ProofData) + if err != nil { resp.Error = true - resp.Response = "proofData not provided" + resp.Response = err.Error() + reply(&resp, w) + return + } + if validProof { + resp.Response = "valid" } else { - validProof, _ := MkTrees[c.CensusID].CheckProof([]byte(c.ClaimData), c.ProofData) - if validProof { - resp.Response = "valid" - } else { - resp.Response = "invalid" - } + resp.Response = "invalid" } } @@ -211,9 +253,6 @@ func Listen(port int, proto string) { http.HandleFunc("/getRoot", func(w http.ResponseWriter, r *http.Request) { claimHandler(w, r, "root") }) - http.HandleFunc("/snapshot", func(w http.ResponseWriter, r *http.Request) { - claimHandler(w, r, "snapshot") - }) http.HandleFunc("/dump", func(w http.ResponseWriter, r *http.Request) { claimHandler(w, r, "dump") }) diff --git a/tree/tree.go b/tree/tree.go index 4ebeac6..d3bf3e3 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -1,8 +1,8 @@ package tree import ( + "bytes" "errors" - "fmt" "os/user" common3 "github.com/iden3/go-iden3/common" @@ -103,6 +103,16 @@ func (t *Tree) GetRoot() string { return t.Tree.RootKey().String() } +func (t *Tree) GetIndex(data []byte) (string, error) { + e, err := t.GetClaim(data) + if err != nil { + return "", err + } + index, err := t.Tree.GetDataByIndex(e.Entry().HIndex()) + return index.String(), err +} + +/* func (t *Tree) Dump() ([]string, error) { var response []string @@ -121,8 +131,20 @@ func (t *Tree) Dump() ([]string, error) { }) return response, err } +*/ + +func (t *Tree) Dump() (string, error) { + w := bytes.NewBufferString("") + err := t.Tree.DumpClaims(w, nil) // as rootKey we can pass a nil pointer, and it will use the current RootKey + + return w.String(), err +} -func (t *Tree) Snapshot() (string, error) { - snapshotNamespace := t.Tree.RootKey().String() - return snapshotNamespace, nil +func (t *Tree) Snapshot(root string) (*Tree, error) { + var rootHash merkletree.Hash + copy(rootHash[:32], root) + mt, err := t.Tree.Snapshot(&rootHash) + snapshotTree := new(Tree) + snapshotTree.Tree = mt + return snapshotTree, err } From c258a9ad9cf309ab2cd71b4f4c5ee34179c62c03 Mon Sep 17 00:00:00 2001 From: p4u Date: Mon, 11 Feb 2019 10:36:33 +0100 Subject: [PATCH 4/7] Using camelCase on censusID Signed-off-by: p4u --- service/censusmanager.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/service/censusmanager.go b/service/censusmanager.go index aad9dc5..c84a9f1 100644 --- a/service/censusmanager.go +++ b/service/censusmanager.go @@ -19,7 +19,7 @@ var Signatures map[string]string var Signature signature.SignKeys // Signature dvote-relay library type Claim struct { - CensusID string `json:"censusID"` // References to MerkleTree namespace + CensusID string `json:"censusId"` // References to MerkleTree namespace RootHash string `json:"rootHash"` // References to MerkleTree rootHash ClaimData string `json:"claimData"` // Data to add to the MerkleTree ProofData string `json:"proofData"` // MerkleProof to check @@ -99,7 +99,7 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) { } // Process data - log.Printf("censusID:{%s} rootHash:{%s} claimData:{%s} proofData:{%s} timeStamp:{%s} signature:{%s}\n", + log.Printf("censusId:{%s} rootHash:{%s} claimData:{%s} proofData:{%s} timeStamp:{%s} signature:{%s}\n", c.CensusID, c.RootHash, c.ClaimData, c.ProofData, c.TimeStamp, c.Signature) authString := fmt.Sprintf("%s%s%s%s", c.CensusID, c.RootHash, c.ClaimData, c.TimeStamp) resp.Error = false @@ -110,7 +110,7 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) { } if !censusFound { resp.Error = true - resp.Response = "censusID not valid or not found" + resp.Response = "censusId not valid or not found" reply(&resp, w) return } From 6d3bf7c18d3dcf2929bcb98dce88e16593e6f68c Mon Sep 17 00:00:00 2001 From: p4u Date: Mon, 11 Feb 2019 18:17:00 +0100 Subject: [PATCH 5/7] Fix dump function --- service/censusmanager.go | 2 +- tree/tree.go | 26 +++++--------------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/service/censusmanager.go b/service/censusmanager.go index c84a9f1..aa9bfad 100644 --- a/service/censusmanager.go +++ b/service/censusmanager.go @@ -189,7 +189,7 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) { resp.Error = true resp.Response = err.Error() } else { - resp.Response = string(jValues) + resp.Response = fmt.Sprintf("%s", jValues) } } } diff --git a/tree/tree.go b/tree/tree.go index d3bf3e3..7c466c0 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -3,6 +3,7 @@ package tree import ( "bytes" "errors" + "fmt" "os/user" common3 "github.com/iden3/go-iden3/common" @@ -51,7 +52,7 @@ func (t *Tree) GetClaim(data []byte) (*mkcore.ClaimBasic, error) { return nil, errors.New("claim data too large") } for i := len(data); i <= 496/8; i++ { - data = append(data, byte('.')) + data = append(data, '\x00') } var indexSlot [400 / 8]byte var dataSlot [496 / 8]byte @@ -112,33 +113,16 @@ func (t *Tree) GetIndex(data []byte) (string, error) { return index.String(), err } -/* func (t *Tree) Dump() ([]string, error) { var response []string - - err := t.Tree.Walk(t.Tree.RootKey(), func(n *merkletree.Node) { + err := t.Tree.Walk(nil, func(n *merkletree.Node) { if n.Type == merkletree.NodeTypeLeaf { - rawValue := n.Value() - var cleanValue []byte - for i := 0; i < len(rawValue); i++ { - if rawValue[i] == byte('.') { - break - } - cleanValue = append(cleanValue, rawValue[i]) - } - response = append(response, fmt.Sprintf("%s", cleanValue)) + data := bytes.Trim(n.Value()[65:], "\x00") + response = append(response, fmt.Sprintf("%s", data)) } }) return response, err } -*/ - -func (t *Tree) Dump() (string, error) { - w := bytes.NewBufferString("") - err := t.Tree.DumpClaims(w, nil) // as rootKey we can pass a nil pointer, and it will use the current RootKey - - return w.String(), err -} func (t *Tree) Snapshot(root string) (*Tree, error) { var rootHash merkletree.Hash From b8dccadfaaad6d3c50489a4ecfc5f7d3e2b7a17f Mon Sep 17 00:00:00 2001 From: p4u Date: Mon, 11 Feb 2019 20:03:49 +0100 Subject: [PATCH 6/7] Fix getRoot Signed-off-by: p4u --- tree/tree.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tree/tree.go b/tree/tree.go index 7c466c0..a164d32 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -101,7 +101,7 @@ func (t *Tree) CheckProof(data []byte, mpHex string) (bool, error) { } func (t *Tree) GetRoot() string { - return t.Tree.RootKey().String() + return common3.HexEncode(t.Tree.RootKey().Bytes()) } func (t *Tree) GetIndex(data []byte) (string, error) { @@ -126,9 +126,13 @@ func (t *Tree) Dump() ([]string, error) { func (t *Tree) Snapshot(root string) (*Tree, error) { var rootHash merkletree.Hash - copy(rootHash[:32], root) - mt, err := t.Tree.Snapshot(&rootHash) snapshotTree := new(Tree) + rootBytes, err := common3.HexDecode(root) + if err != nil { + return snapshotTree, err + } + copy(rootHash[:32], rootBytes) + mt, err := t.Tree.Snapshot(&rootHash) snapshotTree.Tree = mt return snapshotTree, err } From 3e2b4d1cfa7904c2723115b3cb845bd8f9e1ef40 Mon Sep 17 00:00:00 2001 From: p4u Date: Tue, 12 Feb 2019 10:53:01 +0100 Subject: [PATCH 7/7] Adapt documentation to new merkle tree Signed-off-by: p4u --- cmd/censushttp/README.md | 128 ++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 68 deletions(-) diff --git a/cmd/censushttp/README.md b/cmd/censushttp/README.md index b52242e..c0c3d1e 100644 --- a/cmd/censushttp/README.md +++ b/cmd/censushttp/README.md @@ -2,7 +2,7 @@ Reference implementation of a voting census service running on the Vocdoni platform -#### Compile +## Compile In a GO ready environment: @@ -11,126 +11,118 @@ go get -u github.com/vocdoni/dvote-census go build -o censusHttpService github.com/vocdoni/dvote-census/cmd/censushttp ``` -#### Usage +## Usage -`./censusHttpService [:pubKey] [[:pubKey] ...]` +`./censusHttpService [:pubKey] [[:pubKey] ...]` -Example: +Example ``` ./censusHttpService 1500 Got_Favorite -2019/02/01 20:01:15 Starting process HTTP service on port 1500 for namespace GoT_Favorite -2019/02/01 20:01:15 Starting server in http mode +2019/02/12 10:20:16 Starting process HTTP service on port 1500 for namespace GoT_Favorite +2019/02/12 10:20:16 Starting server in http mode ``` -#### add claims +## API -``` -curl -d '{"censusID":"GoT_Favorite","claimData":"Jon Snow"}' http://localhost:1500/addClaim +A HTTP jSON endpoint is available with the following possible fields: `censusId`, `claimData`, `rootHash` and `proofData`. -{"error":false,"response":""} -``` +If `pubKey` has been configured for a specific `censusId`, then two more methods are available (`timeStamp` and `signature`) to provide authentication. -If public key authentication enabled, `signature` field is required using Nancl signature. +The next table shows the available methods and its relation with the fields. -The signed message is expected to be a concatenation of the following fields: `censusID, claimData, timeStamp` +| method | censusId | claimData | rootHash | proofData | protected? | description | +|------------|-----------|-----------|----------|-----------|------------|------------| +| `addCLaim` | mandatory | mandatory | none | none | yes | adds a new claim to the merkle tree | +| `getRoot` | mandatory | none | none | none | no | get the current merkletree root hash +| `genProof` | mandatory | mandatory | optional | none | no | generate the merkle proof for a given claim +| `checkProof` | mandatory | mandatory | optional | mandatory | no | check a claim and its merkle proof +| `getIdx` | mandatory | mandatory | optional | none | no | get the merkletree data index of a given claim +| `dump` | mandatory | none | optional | none | yes | list the contents of the census for a given hash -There is a time window of 10 seconds while the signature is considered valid. -``` -curl -d '{"censusID":"GoT_Favorite", -"claimData":"Jon Snow", -"timeStamp":"1547814675", -"signature":"a117c4ce12b29090884112ffe57e664f007e7ef142a1679996e2d34fd2b852fe76966e47932f1e9d3a54610d0f361383afe2d9aab096e15d136c236abb0a0d0e"}' http://localhost:1500/addClaim - -{"error":false,"response":""} -``` +## Signature +The signature provides authentication by signing a concatenation of the following strings (even if empty) without spaces: `censusId rootHash claimData timeStamp`. -#### generate proof - -``` -curl -d '{"censusID":"GoT_Favorite","claimData":"Jon Snow"}' http://localhost:1500/genProof +The `timeStamp` when received on the server side must not differ more than 10 seconds from the current UNIX time. -{"error":false,"response":"0x000000000000000000000000000000000000000000000000000000000000000352f3ca2aaf635ec2ae4452f6a65be7bca72678287a8bb08ad4babfcccd76c2fef1aac7675261bf6d12c746fb7907beea6d1f1635af93ba931eec0c6a747ecc37"} -``` +## Examples -#### check proof +#### add claims +Add two new claims, one for `Jon Snow` and another for `Tyrion`. ``` -curl -d '{"censusID":"GoT_Favorite","claimData":"Jon Snow", "proofData": "0x0000000000000000000000000000000000000000000000000000000000000000000123"}' http://localhost:1500/checkProof +curl -d '{"censusID":"GoT_Favorite","claimData":"Jon Snow"}' http://localhost:1500/addClaim -{"error":false,"response":"invalid"} +{"error":false,"response":""} ``` ``` -curl -d '{"censusID":"GoT_Favorite","claimData":"Jon Snow", "proofData": "0x000000000000000000000000000000000000000000000000000000000000000352f3ca2aaf635ec2ae4452f6a65be7bca72678287a8bb08ad4babfcccd76c2fef1aac7675261bf6d12c746fb7907beea6d1f1635af93ba931eec0c6a747ecc37"}' http://localhost:1500/checkProof +curl -d '{"censusID":"GoT_Favorite","claimData":"Tyrion"}' http://localhost:1500/addClaim -{"error":false,"response":"valid"} +{"error":false,"response":""} ``` -#### make snapshot - -Snapshots are static and unmutable copies of a specific census +In case signature is enabled: ``` -curl -d '{"censusID":"GoT_Favorite"}' http://localhost:1500/snapshot +curl -d '{ +"censusID":"GoT_Favorite", +"claimData":"Jon Snow", +"timeStamp":"1547814675", +"signature":"a117c4ce12b29090884112ffe57e664f007e7ef142a1679996e2d34fd2b852fe76966e47932f1e9d3a54610d0f361383afe2d9aab096e15d136c236abb0a0d0e" }' http://localhost:1500/addClaim -{"error":false,"response":"0x8647692e073a10980d821764c65ca3fddbc606bb936f9812a7a806bfa97df152"} +{"error":false,"response":""} ``` -The name for the snapshot is "0x8647692e073a10980d821764c65ca3fddbc606bb936f9812a7a806bfa97df152" which represents the Root Hash. - -Now you can use it as censusID for checkProof, getRoot, genProof and dump. But addClaim is not longer allowed. -#### dump +#### generate proof -Dump contents of a specific censusID (values) +Generate a merkle proof for the claim `Jon Snow` ``` -curl -d '{"censusID":"GoT_Favorite"}' http://localhost:1500/dump -{"error":false,"response":"[\"Tyrion\",\"Jon Snow\"]"} -``` - - - +curl -d '{"censusID":"GoT_Favorite","claimData":"Jon Snow"}' http://localhost:1500/genProof +{"error":false,"response":"0x000200000000000000000000000000000000000000000000000000000000000212f8134039730791388a9bd0460f9fbd0757327212a64b3a2b0f0841ce561ee3"} +``` +If `rootHash` is specified, the proof will be calculated for the given root hash. +#### get root +The previous merkle proof is valid only for the current root hash. Let's get it +``` +curl -d '{"censusID":"GoT_Favorite"}' http://localhost:1500/getRoot +{"error":false,"response":"0x2f0ddde5cb995eae23dc3b75a5c0333f1cc89b73f3a00b0fe71996fb90fef04b"} +``` +#### check proof -##### add claims +Now let's check if the proof is valid ``` -curl -d '{"processID":"GoT_Favorite","claimData":"Jon Snow"}' http://localhost:1500/addClaim -{"error":false,"response":""} -``` +curl -d '{ +"censusID":"GoT_Favorite","claimData":"Jon Snow", +"rootHash":"0x2f0ddde5cb995eae23dc3b75a5c0333f1cc89b73f3a00b0fe71996fb90fef04b", +"proofData":"0x000200000000000000000000000000000000000000000000000000000000000212f8134039730791388a9bd0460f9fbd0757327212a64b3a2b0f0841ce561ee3"}' http://localhost:1500/checkProof -``` -curl -d '{"processID":"GoT_Favorite","claimData":"Tyrion"}' http://localhost:1500/addClaim -{"error":false,"response":""} +{"error":false,"response":"valid"} ``` -##### generate proof +If `rootHash` is not specified, the current root hash is used. -``` -curl -d '{"censusID":"GoT_Favorite","claimData":"Jon Snow"}' http://localhost:1500/genProof -{"error":false,"response":"0x000000000000000000000000000000000000000000000000000000000000000352f3ca2aaf635ec2ae4452f6a65be7bca72678287a8bb08ad4babfcccd76c2fef1aac7675261bf6d12c746fb7907beea6d1f1635af93ba931eec0c6a747ecc37"} -``` +#### dump -##### check proof +Dump contents of a specific censusId (values) ``` -curl -d '{"censusID":"GoT_Favorite","claimData":"Jon Snow", "proofData": "0x0000000000000000000000000000000000000000000000000000000000000000000123"}' http://localhost:1500/checkProof -{"error":false,"response":"invalid"} -``` +curl -d '{"censusID":"GoT_Favorite"}' http://localhost:1500/dump -``` -curl -d '{"censusID":"GoT_Favorite","claimData":"Jon Snow", "proofData": "0x000000000000000000000000000000000000000000000000000000000000000352f3ca2aaf635ec2ae4452f6a65be7bca72678287a8bb08ad4babfcccd76c2fef1aac7675261bf6d12c746fb7907beea6d1f1635af93ba931eec0c6a747ecc37"}' http://localhost:1500/checkProof -{"error":false,"response":"valid"} +{"error":false,"response":"[\"Tyrion\",\"Jon Snow\"]"} ``` +If `rootHash` is specified, dump will return the values for the merkle tree with the given root hash. \ No newline at end of file