arnaucube
arnaucube/go-dvotePublic Self-hosted repository
Census Service refactory

- Add new JSON field `method`, so method is now specified in the JSON
object (not in URL anymore)
- Use Ethereum ECDSA signing library
- Refactor censusmanager code to allow other than HTTP transport layers
- Update documentation

Signed-off-by: p4u <p4u@dabax.net>
p4u p4u@dabax.net
Mon, 18 Feb 2019 10:43:05 +0100
commit

a4d8b87d9360d5d5d08574e71131872af7327645

parent

0536b3fd54e13526a5e381531e288771e10154f0

M cmd/censushttp/README.md → cmd/censushttp/README.md

@@ -25,7 +25,7 @@ ```

## API -A HTTP jSON endpoint is available with the following possible fields: `censusId`, `claimData`, `rootHash` and `proofData`. +A HTTP jSON endpoint is available with the following possible fields: `method` `censusId`, `claimData`, `rootHash` and `proofData`. If `pubKey` has been configured for a specific `censusId`, then two more methods are available (`timeStamp` and `signature`) to provide authentication.

@@ -33,7 +33,7 @@ The next table shows the available methods and its relation with the fields.

| method | censusId | claimData | rootHash | proofData | protected? | description | |------------|-----------|-----------|----------|-----------|------------|------------| -| `addCLaim` | mandatory | mandatory | none | none | yes | adds a new claim to the merkle tree | +| `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

@@ -53,13 +53,13 @@ #### add claims

Add two new claims, one for `Jon Snow` and another for `Tyrion`. ``` -curl -d '{"censusID":"GoT_Favorite","claimData":"Jon Snow"}' http://localhost:1500/addClaim +curl -d '{"method":"addClaim","censusID":"GoT_Favorite","claimData":"Jon Snow"}' http://localhost:1500 {"error":false,"response":""} ``` ``` -curl -d '{"censusID":"GoT_Favorite","claimData":"Tyrion"}' http://localhost:1500/addClaim +curl -d '{"method":"addClaim","censusID":"GoT_Favorite","claimData":"Tyrion"}' http://localhost:1500 {"error":false,"response":""} ```

@@ -68,10 +68,11 @@ In case signature is enabled:

``` curl -d '{ +"method":"addClaim", "censusID":"GoT_Favorite", "claimData":"Jon Snow", "timeStamp":"1547814675", -"signature":"a117c4ce12b29090884112ffe57e664f007e7ef142a1679996e2d34fd2b852fe76966e47932f1e9d3a54610d0f361383afe2d9aab096e15d136c236abb0a0d0e" }' http://localhost:1500/addClaim +"signature":"a117c4ce12b29090884112ffe57e664f007e7ef142a1679996e2d34fd2b852fe76966e47932f1e9d3a54610d0f361383afe2d9aab096e15d136c236abb0a0d0e" }' http://localhost:1500 {"error":false,"response":""} ```

@@ -82,7 +83,7 @@

Generate a merkle proof for the claim `Jon Snow` ``` -curl -d '{"censusID":"GoT_Favorite","claimData":"Jon Snow"}' http://localhost:1500/genProof +curl -d '{"method":"genProof","censusID":"GoT_Favorite","claimData":"Jon Snow"}' http://localhost:1500 {"error":false,"response":"0x000200000000000000000000000000000000000000000000000000000000000212f8134039730791388a9bd0460f9fbd0757327212a64b3a2b0f0841ce561ee3"} ```

@@ -94,7 +95,7 @@

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 +curl -d '{"method":"getRoot","censusID":"GoT_Favorite"}' http://localhost:1500 {"error":false,"response":"0x2f0ddde5cb995eae23dc3b75a5c0333f1cc89b73f3a00b0fe71996fb90fef04b"} ```

@@ -106,9 +107,10 @@ Now let's check if the proof is valid

``` curl -d '{ +"method":"checkProof", "censusID":"GoT_Favorite","claimData":"Jon Snow", "rootHash":"0x2f0ddde5cb995eae23dc3b75a5c0333f1cc89b73f3a00b0fe71996fb90fef04b", -"proofData":"0x000200000000000000000000000000000000000000000000000000000000000212f8134039730791388a9bd0460f9fbd0757327212a64b3a2b0f0841ce561ee3"}' http://localhost:1500/checkProof +"proofData":"0x000200000000000000000000000000000000000000000000000000000000000212f8134039730791388a9bd0460f9fbd0757327212a64b3a2b0f0841ce561ee3"}' http://localhost:1500 {"error":false,"response":"valid"} ```

@@ -120,7 +122,7 @@

Dump contents of a specific censusId (values) ``` -curl -d '{"censusID":"GoT_Favorite"}' http://localhost:1500/dump +curl -d '{"method":"dump","censusID":"GoT_Favorite"}' http://localhost:1500 {"error":false,"response":"[\"Tyrion\",\"Jon Snow\"]"} ```
M service/census/censusmanager.go → service/census/censusmanager.go

@@ -8,17 +8,24 @@ "net/http"

"strconv" "time" + signature "github.com/vocdoni/go-dvote/crypto/signature_ecdsa" tree "github.com/vocdoni/go-dvote/tree" - signature "github.com/vocdoni/go-dvote/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 +// Time window (seconds) in which TimeStamp will be accepted if auth enabled +const authTimeWindow = 10 + +// MkTrees map of merkle trees indexed by censusId +var MkTrees map[string]*tree.Tree + +// Signatures map of management pubKeys indexed by censusId var Signatures map[string]string -var Signature signature.SignKeys // Signature go-dvote library + +var currentSignature signature.SignKeys +// Claim type represents a JSON object with all possible fields type Claim struct { + Method string `json:"method"` // method to call 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

@@ -27,11 +34,13 @@ TimeStamp string `json:"timeStamp"` // Unix TimeStamp in seconds

Signature string `json:"signature"` // Signature as Hexadecimal String } +// Result type represents a JSON object with the result of the method executed type Result struct { Error bool `json:"error"` Response string `json:"response"` } +// AddNamespace adds a new merkletree identified by a censusId (name) func AddNamespace(name, pubKey string) { if len(MkTrees) == 0 { MkTrees = make(map[string]*tree.Tree)

@@ -63,7 +72,7 @@ }

return true } -func checkAuth(timestamp, signature, pubKey, message string) bool { +func checkAuth(timestamp, signed, pubKey, message string) bool { if len(pubKey) < 1 { return true }

@@ -75,7 +84,7 @@ return false

} if timeStampRemote < currentTime+authTimeWindow && timeStampRemote > currentTime-authTimeWindow { - v, err := Signature.Verify(message, signature, pubKey) + v, err := currentSignature.Verify(message, signed, pubKey) if err != nil { log.Printf("Verification error: %s\n", err) }

@@ -84,10 +93,8 @@ }

return false } -func claimHandler(w http.ResponseWriter, req *http.Request, op string) { +func httpHandler(w http.ResponseWriter, req *http.Request) { var c Claim - var resp Result - if ok := checkRequest(w, req); !ok { return }

@@ -97,6 +104,18 @@ if err != nil {

http.Error(w, err.Error(), 400) return } + if len(c.Method) < 1 { + http.Error(w, "method must be specified", 400) + return + } + resp := opHandler(&c) + reply(resp, w) +} + +func opHandler(c *Claim) *Result { + var resp Result + op := c.Method + var err error // Process data log.Printf("censusId:{%s} rootHash:{%s} claimData:{%s} proofData:{%s} timeStamp:{%s} signature:{%s}\n",

@@ -111,73 +130,61 @@ }

if !censusFound { resp.Error = true resp.Response = "censusId not valid or not found" - reply(&resp, w) - return + return &resp + } + + //Methods without rootHash + + if op == "getRoot" { + resp.Response = MkTrees[c.CensusID].GetRoot() + return &resp } - if op == "add" { + if op == "addClaim" { 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" } + return &resp } - if op == "gen" { - 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] + //Methods with rootHash, if rootHash specified snapshot the tree + + 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 = "invalid root hash" + return &resp } + } else { //if rootHash not specified use current tree + t = MkTrees[c.CensusID] + } + + if op == "genProof" { resp.Response, err = t.GenProof([]byte(c.ClaimData)) if err != nil { resp.Error = true resp.Response = err.Error() - reply(&resp, w) - return } + return &resp } - if op == "root" { - resp.Response = MkTrees[c.CensusID].GetRoot() - } - - if op == "idx" { - + if op == "getIdx" { + resp.Response, err = t.GetIndex([]byte(c.ClaimData)) + return &resp } if op == "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 + return &resp } - - 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 {

@@ -192,44 +199,31 @@ } else {

resp.Response = fmt.Sprintf("%s", jValues) } } + return &resp } - if op == "check" { + if op == "checkProof" { 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 = "invalid root hash" - reply(&resp, w) - return - } - } else { //if rootHash not specified use current merkletree - t = MkTrees[c.CensusID] + return &resp } - + // Generate proof and return it validProof, err := t.CheckProof([]byte(c.ClaimData), c.ProofData) if err != nil { resp.Error = true resp.Response = err.Error() - reply(&resp, w) - return + return &resp } if validProof { resp.Response = "valid" } else { resp.Response = "invalid" } + return &resp } - reply(&resp, w) + return &resp } func addCorsHeaders(w *http.ResponseWriter, req *http.Request) {

@@ -238,6 +232,7 @@ (*w).Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")

(*w).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") } +// Listen starts a census service defined of type "proto" func Listen(port int, proto string) { srv := &http.Server{ Addr: fmt.Sprintf(":%d", port),

@@ -247,52 +242,14 @@ WriteTimeout: 4 * time.Second,

IdleTimeout: 3 * time.Second, } - http.HandleFunc("/addClaim", func(w http.ResponseWriter, r *http.Request) { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { addCorsHeaders(&w, r) - if r.Method == http.MethodPost { - claimHandler(w, r, "add") + httpHandler(w, r) } else if r.Method != http.MethodOptions { http.Error(w, "Not found", http.StatusNotFound) } }) - http.HandleFunc("/genProof", func(w http.ResponseWriter, r *http.Request) { - addCorsHeaders(&w, r) - - if r.Method == http.MethodPost { - claimHandler(w, r, "gen") - } else if r.Method != http.MethodOptions { - http.Error(w, "Not found", http.StatusNotFound) - } - }) - http.HandleFunc("/checkProof", func(w http.ResponseWriter, r *http.Request) { - addCorsHeaders(&w, r) - - if r.Method == http.MethodPost { - claimHandler(w, r, "check") - } else if r.Method != http.MethodOptions { - http.Error(w, "Not found", http.StatusNotFound) - } - }) - http.HandleFunc("/getRoot", func(w http.ResponseWriter, r *http.Request) { - addCorsHeaders(&w, r) - - if r.Method == http.MethodPost { - claimHandler(w, r, "root") - } else if r.Method != http.MethodOptions { - http.Error(w, "Not found", http.StatusNotFound) - } - }) - http.HandleFunc("/dump", func(w http.ResponseWriter, r *http.Request) { - addCorsHeaders(&w, r) - - if r.Method == http.MethodPost { - claimHandler(w, r, "dump") - } else if r.Method != http.MethodOptions { - http.Error(w, "Not found", http.StatusNotFound) - } - }) - if proto == "https" { log.Print("Starting server in https mode") if err := srv.ListenAndServeTLS("server.crt", "server.key"); err != nil {