Browse Source

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>
testnet
p4u 5 years ago
parent
commit
a4d8b87d93
4 changed files with 80 additions and 121 deletions
  1. +11
    -9
      cmd/censushttp/README.md
  2. +0
    -0
      crypto/signature_nancl/signature.go
  3. +0
    -0
      crypto/signature_nancl/signature_test.go
  4. +69
    -112
      service/census/censusmanager.go

+ 11
- 9
cmd/censushttp/README.md

@ -25,7 +25,7 @@ Example
## API ## 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. 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 | | 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 | `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 | `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 | `checkProof` | mandatory | mandatory | optional | mandatory | no | check a claim and its merkle proof
@ -53,13 +53,13 @@ The `timeStamp` when received on the server side must not differ more than 10 se
Add two new claims, one for `Jon Snow` and another for `Tyrion`. 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":""} {"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":""} {"error":false,"response":""}
``` ```
@ -68,10 +68,11 @@ In case signature is enabled:
``` ```
curl -d '{ curl -d '{
"method":"addClaim",
"censusID":"GoT_Favorite", "censusID":"GoT_Favorite",
"claimData":"Jon Snow", "claimData":"Jon Snow",
"timeStamp":"1547814675", "timeStamp":"1547814675",
"signature":"a117c4ce12b29090884112ffe57e664f007e7ef142a1679996e2d34fd2b852fe76966e47932f1e9d3a54610d0f361383afe2d9aab096e15d136c236abb0a0d0e" }' http://localhost:1500/addClaim
"signature":"a117c4ce12b29090884112ffe57e664f007e7ef142a1679996e2d34fd2b852fe76966e47932f1e9d3a54610d0f361383afe2d9aab096e15d136c236abb0a0d0e" }' http://localhost:1500
{"error":false,"response":""} {"error":false,"response":""}
``` ```
@ -82,7 +83,7 @@ curl -d '{
Generate a merkle proof for the claim `Jon Snow` 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"} {"error":false,"response":"0x000200000000000000000000000000000000000000000000000000000000000212f8134039730791388a9bd0460f9fbd0757327212a64b3a2b0f0841ce561ee3"}
``` ```
@ -94,7 +95,7 @@ If `rootHash` is specified, the proof will be calculated for the given root hash
The previous merkle proof is valid only for the current root hash. Let's get it 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"} {"error":false,"response":"0x2f0ddde5cb995eae23dc3b75a5c0333f1cc89b73f3a00b0fe71996fb90fef04b"}
``` ```
@ -106,9 +107,10 @@ Now let's check if the proof is valid
``` ```
curl -d '{ curl -d '{
"method":"checkProof",
"censusID":"GoT_Favorite","claimData":"Jon Snow", "censusID":"GoT_Favorite","claimData":"Jon Snow",
"rootHash":"0x2f0ddde5cb995eae23dc3b75a5c0333f1cc89b73f3a00b0fe71996fb90fef04b", "rootHash":"0x2f0ddde5cb995eae23dc3b75a5c0333f1cc89b73f3a00b0fe71996fb90fef04b",
"proofData":"0x000200000000000000000000000000000000000000000000000000000000000212f8134039730791388a9bd0460f9fbd0757327212a64b3a2b0f0841ce561ee3"}' http://localhost:1500/checkProof
"proofData":"0x000200000000000000000000000000000000000000000000000000000000000212f8134039730791388a9bd0460f9fbd0757327212a64b3a2b0f0841ce561ee3"}' http://localhost:1500
{"error":false,"response":"valid"} {"error":false,"response":"valid"}
``` ```
@ -120,7 +122,7 @@ If `rootHash` is not specified, the current root hash is used.
Dump contents of a specific censusId (values) 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\"]"} {"error":false,"response":"[\"Tyrion\",\"Jon Snow\"]"}
``` ```

crypto/signature/signature.go → crypto/signature_nancl/signature.go


crypto/signature/signature_test.go → crypto/signature_nancl/signature_test.go


+ 69
- 112
service/census/censusmanager.go

@ -8,17 +8,24 @@ import (
"strconv" "strconv"
"time" "time"
signature "github.com/vocdoni/go-dvote/crypto/signature_ecdsa"
tree "github.com/vocdoni/go-dvote/tree" 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 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 { type Claim struct {
Method string `json:"method"` // method to call
CensusID string `json:"censusId"` // References to MerkleTree namespace CensusID string `json:"censusId"` // References to MerkleTree namespace
RootHash string `json:"rootHash"` // References to MerkleTree rootHash RootHash string `json:"rootHash"` // References to MerkleTree rootHash
ClaimData string `json:"claimData"` // Data to add to the MerkleTree ClaimData string `json:"claimData"` // Data to add to the MerkleTree
@ -27,11 +34,13 @@ type Claim struct {
Signature string `json:"signature"` // Signature as Hexadecimal String Signature string `json:"signature"` // Signature as Hexadecimal String
} }
// Result type represents a JSON object with the result of the method executed
type Result struct { type Result struct {
Error bool `json:"error"` Error bool `json:"error"`
Response string `json:"response"` Response string `json:"response"`
} }
// AddNamespace adds a new merkletree identified by a censusId (name)
func AddNamespace(name, pubKey string) { func AddNamespace(name, pubKey string) {
if len(MkTrees) == 0 { if len(MkTrees) == 0 {
MkTrees = make(map[string]*tree.Tree) MkTrees = make(map[string]*tree.Tree)
@ -63,7 +72,7 @@ func checkRequest(w http.ResponseWriter, req *http.Request) bool {
return true return true
} }
func checkAuth(timestamp, signature, pubKey, message string) bool {
func checkAuth(timestamp, signed, pubKey, message string) bool {
if len(pubKey) < 1 { if len(pubKey) < 1 {
return true return true
} }
@ -75,7 +84,7 @@ func checkAuth(timestamp, signature, pubKey, message string) bool {
} }
if timeStampRemote < currentTime+authTimeWindow && if timeStampRemote < currentTime+authTimeWindow &&
timeStampRemote > currentTime-authTimeWindow { timeStampRemote > currentTime-authTimeWindow {
v, err := Signature.Verify(message, signature, pubKey)
v, err := currentSignature.Verify(message, signed, pubKey)
if err != nil { if err != nil {
log.Printf("Verification error: %s\n", err) log.Printf("Verification error: %s\n", err)
} }
@ -84,10 +93,8 @@ func checkAuth(timestamp, signature, pubKey, message string) bool {
return false return false
} }
func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
func httpHandler(w http.ResponseWriter, req *http.Request) {
var c Claim var c Claim
var resp Result
if ok := checkRequest(w, req); !ok { if ok := checkRequest(w, req); !ok {
return return
} }
@ -97,6 +104,18 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
http.Error(w, err.Error(), 400) http.Error(w, err.Error(), 400)
return 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 // 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",
@ -111,73 +130,61 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
if !censusFound { if !censusFound {
resp.Error = true resp.Error = true
resp.Response = "censusId not valid or not found" 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 { if auth := checkAuth(c.TimeStamp, c.Signature, Signatures[c.CensusID], authString); auth {
err = MkTrees[c.CensusID].AddClaim([]byte(c.ClaimData)) err = MkTrees[c.CensusID].AddClaim([]byte(c.ClaimData))
} else { } else {
resp.Error = true resp.Error = true
resp.Response = "invalid authentication" 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)) resp.Response, err = t.GenProof([]byte(c.ClaimData))
if err != nil { if err != nil {
resp.Error = true resp.Error = true
resp.Response = err.Error() 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" { if op == "dump" {
var t *tree.Tree
if auth := checkAuth(c.TimeStamp, c.Signature, Signatures[c.CensusID], authString); !auth { if auth := checkAuth(c.TimeStamp, c.Signature, Signatures[c.CensusID], authString); !auth {
resp.Error = true resp.Error = true
resp.Response = "invalid authentication" 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]
return &resp
} }
//dump the claim data and return it //dump the claim data and return it
values, err := t.Dump() values, err := t.Dump()
if err != nil { if err != nil {
@ -192,44 +199,31 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
resp.Response = fmt.Sprintf("%s", jValues) resp.Response = fmt.Sprintf("%s", jValues)
} }
} }
return &resp
} }
if op == "check" {
if op == "checkProof" {
if len(c.ProofData) < 1 { if len(c.ProofData) < 1 {
resp.Error = true resp.Error = true
resp.Response = "proofData not provided" 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) validProof, err := t.CheckProof([]byte(c.ClaimData), c.ProofData)
if err != nil { if err != nil {
resp.Error = true resp.Error = true
resp.Response = err.Error() resp.Response = err.Error()
reply(&resp, w)
return
return &resp
} }
if validProof { if validProof {
resp.Response = "valid" resp.Response = "valid"
} else { } else {
resp.Response = "invalid" resp.Response = "invalid"
} }
return &resp
} }
reply(&resp, w)
return &resp
} }
func addCorsHeaders(w *http.ResponseWriter, req *http.Request) { func addCorsHeaders(w *http.ResponseWriter, req *http.Request) {
@ -238,6 +232,7 @@ func addCorsHeaders(w *http.ResponseWriter, req *http.Request) {
(*w).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") (*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) { func Listen(port int, proto string) {
srv := &http.Server{ srv := &http.Server{
Addr: fmt.Sprintf(":%d", port), Addr: fmt.Sprintf(":%d", port),
@ -247,52 +242,14 @@ func Listen(port int, proto string) {
IdleTimeout: 3 * time.Second, IdleTimeout: 3 * time.Second,
} }
http.HandleFunc("/addClaim", func(w http.ResponseWriter, r *http.Request) {
addCorsHeaders(&w, r)
if r.Method == http.MethodPost {
claimHandler(w, r, "add")
} 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) {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
addCorsHeaders(&w, r) addCorsHeaders(&w, r)
if r.Method == http.MethodPost { if r.Method == http.MethodPost {
claimHandler(w, r, "root")
httpHandler(w, r)
} else if r.Method != http.MethodOptions { } else if r.Method != http.MethodOptions {
http.Error(w, "Not found", http.StatusNotFound) 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" { if proto == "https" {
log.Print("Starting server in https mode") log.Print("Starting server in https mode")
if err := srv.ListenAndServeTLS("server.crt", "server.key"); err != nil { if err := srv.ListenAndServeTLS("server.crt", "server.key"); err != nil {

Loading…
Cancel
Save