Browse Source

Merge branch 'feature/new_merkletree'

Signed-off-by: p4u <p4u@dabax.net>
feature_chain_module
p4u 5 years ago
parent
commit
7fb1d51fab
5 changed files with 264 additions and 225 deletions
  1. +61
    -67
      cmd/censushttp/README.md
  2. +15
    -8
      cmd/censushttp/censushttp.go
  3. +0
    -15
      service/README.md
  4. +115
    -82
      service/censusmanager.go
  5. +73
    -53
      tree/tree.go

+ 61
- 67
cmd/censushttp/README.md

@ -2,7 +2,7 @@
Reference implementation of a voting census service running on the Vocdoni platform Reference implementation of a voting census service running on the Vocdoni platform
#### Compile
## Compile
In a GO ready environment: In a GO ready environment:
@ -11,124 +11,118 @@ go get -u github.com/vocdoni/dvote-census/...
go build -o censusHttpService github.com/vocdoni/dvote-census/cmd/censushttp go build -o censusHttpService github.com/vocdoni/dvote-census/cmd/censushttp
``` ```
#### Usage
## Usage
`./censusHttpService <port> [publicKey]`
`./censusHttpService <port> <censusId>[:pubKey] [<censusId>[:pubKey] ...]`
Example:
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/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
```
curl -d '{"censusID":"GoT_Favorite",
"claimData":"Jon Snow",
"timeStamp":"1547814675",
"signature":"a117c4ce12b29090884112ffe57e664f007e7ef142a1679996e2d34fd2b852fe76966e47932f1e9d3a54610d0f361383afe2d9aab096e15d136c236abb0a0d0e"}' http://localhost:1500/addClaim
{"error":false,"response":""}
```
## Signature
#### generate proof
The signature provides authentication by signing a concatenation of the following strings (even if empty) without spaces: `censusId rootHash claimData timeStamp`.
```
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.

+ 15
- 8
cmd/censushttp/censushttp.go

@ -4,13 +4,15 @@ import (
"log" "log"
"os" "os"
"strconv" "strconv"
"strings"
censusmanager "github.com/vocdoni/dvote-census/service" censusmanager "github.com/vocdoni/dvote-census/service"
) )
func main() { func main() {
if len(os.Args) < 2 { if len(os.Args) < 2 {
log.Fatal("Usage: " + os.Args[0] + " <port> [pubKey]")
log.Fatal("Usage: " + os.Args[0] +
" <port> <namespace>[:pubKey] [<namespace>[:pubKey]]...")
os.Exit(2) os.Exit(2)
} }
port, err := strconv.Atoi(os.Args[1]) port, err := strconv.Atoi(os.Args[1])
@ -18,13 +20,18 @@ func main() {
log.Fatal(err) log.Fatal(err)
os.Exit(2) 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")
} }

+ 0
- 15
service/README.md

@ -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)
```

+ 115
- 82
service/censusmanager.go

@ -6,22 +6,21 @@ import (
"log" "log"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time" "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 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
var Signature signature.SignKeys // Signature dvote-relay library
type Claim struct { 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 ClaimData string `json:"claimData"` // Data to add to the MerkleTree
ProofData string `json:"proofData"` // MerkleProof to check ProofData string `json:"proofData"` // MerkleProof to check
TimeStamp string `json:"timeStamp"` // Unix TimeStamp in seconds TimeStamp string `json:"timeStamp"` // Unix TimeStamp in seconds
@ -33,6 +32,20 @@ type Result struct {
Response string `json:"response"` 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) { func reply(resp *Result, w http.ResponseWriter) {
err := json.NewEncoder(w).Encode(resp) err := json.NewEncoder(w).Encode(resp)
if err != nil { if err != nil {
@ -50,8 +63,8 @@ func checkRequest(w http.ResponseWriter, req *http.Request) bool {
return true 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 return true
} }
currentTime := int64(time.Now().Unix()) currentTime := int64(time.Now().Unix())
@ -62,7 +75,7 @@ func checkAuth(timestamp, signature, message string) bool {
} }
if timeStampRemote < currentTime+authTimeWindow && if timeStampRemote < currentTime+authTimeWindow &&
timeStampRemote > currentTime-authTimeWindow { timeStampRemote > currentTime-authTimeWindow {
v, err := S.Verify(message, signature, authPubKey)
v, err := Signature.Verify(message, signature, pubKey)
if err != nil { if err != nil {
log.Printf("Verification error: %s\n", err) log.Printf("Verification error: %s\n", err)
} }
@ -74,6 +87,7 @@ func checkAuth(timestamp, signature, message string) bool {
func claimHandler(w http.ResponseWriter, req *http.Request, op string) { func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
var c Claim var c Claim
var resp Result var resp Result
if ok := checkRequest(w, req); !ok { if ok := checkRequest(w, req); !ok {
return return
} }
@ -85,36 +99,25 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
} }
// Process data // 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.Error = false
resp.Response = "" resp.Response = ""
censusFound := false
if len(c.CensusID) > 0 { 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.Error = true
resp.Response = "data not valid"
resp.Response = "censusId not valid or not found"
reply(&resp, w) reply(&resp, w)
return return
} }
if op == "add" { 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 strings.HasPrefix(c.CensusID, "0x") {
resp.Error = true
resp.Response = "add claim to snapshot is not allowed"
} else {
err = T.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 { } else {
resp.Error = true resp.Error = true
resp.Response = "invalid authentication" resp.Response = "invalid authentication"
@ -122,47 +125,72 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
} }
if op == "gen" { if op == "gen" {
resp.Response, err = T.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" { if op == "root" {
resp.Response = T.GetRoot()
resp.Response = MkTrees[c.CensusID].GetRoot()
}
if op == "idx" {
} }
if op == "dump" { if op == "dump" {
values, err := T.Dump()
if err != nil {
var t *tree.Tree
if auth := checkAuth(c.TimeStamp, c.Signature, Signatures[c.CensusID], authString); !auth {
resp.Error = true resp.Error = true
resp.Response = fmt.Sprint(err)
} else {
jValues, err := json.Marshal(values)
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 { if err != nil {
log.Printf("Snapshot error: %s", err.Error())
resp.Error = true resp.Error = true
resp.Response = fmt.Sprint(err)
} else {
resp.Response = string(jValues)
resp.Response = "invalid root hash"
reply(&resp, w)
return
} }
} else { //if rootHash not specified use current merkletree
t = MkTrees[c.CensusID]
} }
}
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 strings.HasPrefix(c.CensusID, "0x") {
//dump the claim data and return it
values, err := t.Dump()
if err != nil {
resp.Error = true
resp.Response = err.Error()
} else {
jValues, err := json.Marshal(values)
if err != nil {
resp.Error = true resp.Error = true
resp.Response = "snapshot an snapshot makes no sense"
resp.Response = err.Error()
} else { } else {
snapshotNamespace, err := T.Snapshot()
if err != nil {
resp.Error = true
resp.Response = fmt.Sprint(err)
} else {
resp.Response = snapshotNamespace
}
resp.Response = fmt.Sprintf("%s", jValues)
} }
} else {
resp.Error = true
resp.Response = "invalid authentication"
} }
} }
@ -170,13 +198,34 @@ func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
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"
} else {
validProof, _ := T.CheckProof([]byte(c.ClaimData), c.ProofData)
if validProof {
resp.Response = "valid"
} else {
resp.Response = "invalid"
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]
}
validProof, err := t.CheckProof([]byte(c.ClaimData), c.ProofData)
if err != nil {
resp.Error = true
resp.Response = err.Error()
reply(&resp, w)
return
}
if validProof {
resp.Response = "valid"
} else {
resp.Response = "invalid"
} }
} }
@ -189,7 +238,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")
} }
func Listen(port int, proto string, pubKey string) {
func Listen(port int, proto string) {
srv := &http.Server{ srv := &http.Server{
Addr: fmt.Sprintf(":%d", port), Addr: fmt.Sprintf(":%d", port),
ReadHeaderTimeout: 4 * time.Second, ReadHeaderTimeout: 4 * time.Second,
@ -234,15 +283,6 @@ func Listen(port int, proto string, pubKey string) {
http.Error(w, "Not found", http.StatusNotFound) http.Error(w, "Not found", http.StatusNotFound)
} }
}) })
http.HandleFunc("/snapshot", func(w http.ResponseWriter, r *http.Request) {
addCorsHeaders(&w, r)
if r.Method == http.MethodPost {
claimHandler(w, r, "snapshot")
} else if r.Method != http.MethodOptions {
http.Error(w, "Not found", http.StatusNotFound)
}
})
http.HandleFunc("/dump", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/dump", func(w http.ResponseWriter, r *http.Request) {
addCorsHeaders(&w, r) addCorsHeaders(&w, r)
@ -253,13 +293,6 @@ func Listen(port int, proto string, pubKey string) {
} }
}) })
if len(pubKey) > 1 {
log.Printf("Enabling signature authentication with %s\n", pubKey)
authPubKey = pubKey
} else {
authPubKey = ""
}
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 {

+ 73
- 53
tree/tree.go

@ -1,38 +1,40 @@
package tree package tree
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"os/user" "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 { type Tree struct {
Namespace string
Storage string Storage string
Tree *merkletree.MerkleTree Tree *merkletree.MerkleTree
DbStorage *db.LevelDbStorage DbStorage *db.LevelDbStorage
} }
func (t *Tree) Init() error {
func (t *Tree) Init(namespace string) error {
if len(t.Storage) < 1 { if len(t.Storage) < 1 {
if len(namespace) < 1 {
return errors.New("namespace not valid")
}
usr, err := user.Current() usr, err := user.Current()
if err == nil { if err == nil {
t.Storage = usr.HomeDir + "/.dvote/Tree"
t.Storage = usr.HomeDir + "/.dvote/census/" + namespace
} else { } else {
t.Storage = "./dvoteTree"
t.Storage = "./dvoteTree/" + namespace
} }
} }
mtdb, err := db.NewLevelDbStorage(t.Storage, false) mtdb, err := db.NewLevelDbStorage(t.Storage, false)
if err != nil { if err != nil {
return err return err
} }
mt, err := merkletree.New(mtdb, 140)
mt, err := merkletree.NewMerkleTree(mtdb, 140)
if err != nil { if err != nil {
return err return err
} }
@ -45,74 +47,92 @@ func (t *Tree) Close() {
defer t.Tree.Storage().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, '\x00')
}
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 { 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) { 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
}
mp, err := t.Tree.GenerateProof(e.Entry().HIndex())
if err != nil { if err != nil {
return "", err return "", err
} }
mpHex := common3.BytesToHex(mp)
mpHex := common3.HexEncode(mp.Bytes())
return mpHex, nil return mpHex, nil
} }
func (t *Tree) CheckProof(data []byte, mpHex string) (bool, error) { 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
}
mp, err := merkletree.NewProofFromBytes(mpBytes)
if err != nil {
return false, err
}
e, err := t.GetClaim(data)
if err != nil { if err != nil {
return false, err 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
return merkletree.VerifyProof(t.Tree.RootKey(), mp,
e.Entry().HIndex(), e.Entry().HValue()), nil
} }
func (t *Tree) GetRoot() string { func (t *Tree) GetRoot() string {
return t.Tree.Root().Hex()
return common3.HexEncode(t.Tree.RootKey().Bytes())
} }
/* 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.
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
}
To explore: Values are stored twice in the BD?
*/
func (t *Tree) Dump() ([]string, error) { func (t *Tree) Dump() ([]string, error) {
var response []string 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(nil, func(n *merkletree.Node) {
if n.Type == merkletree.NodeTypeLeaf {
data := bytes.Trim(n.Value()[65:], "\x00")
response = append(response, fmt.Sprintf("%s", 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)
}
}
})
return snapshotNamespace, nil
func (t *Tree) Snapshot(root string) (*Tree, error) {
var rootHash merkletree.Hash
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
} }

Loading…
Cancel
Save