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
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 @@ 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`.
```
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 @@ curl -d '{
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 @@ 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
```
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 @@ If `rootHash` is not specified, the current root hash is used.
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\"]"}
```

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"
"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 @@ type Claim struct {
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 @@ func checkRequest(w http.ResponseWriter, req *http.Request) bool {
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 @@ func checkAuth(timestamp, signature, pubKey, message string) bool {
}
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 @@ func checkAuth(timestamp, signature, pubKey, message string) bool {
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 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
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 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
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
}
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
values, err := t.Dump()
if err != nil {
@ -192,44 +199,31 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
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 @@ 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")
}
// 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 @@ func Listen(port int, proto string) {
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)
if r.Method == http.MethodPost {
claimHandler(w, r, "root")
httpHandler(w, r)
} 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 {

Loading…
Cancel
Save