Signed-off-by: p4u <p4u@dabax.net>feature_chain_module
@ -0,0 +1,128 @@ |
|||||
|
## Census HTTP service |
||||
|
|
||||
|
Reference implementation of a voting census service running on the Vocdoni platform |
||||
|
|
||||
|
## Compile |
||||
|
|
||||
|
In a GO ready environment: |
||||
|
|
||||
|
``` |
||||
|
go get -u github.com/vocdoni/dvote-census/... |
||||
|
go build -o censusHttpService github.com/vocdoni/dvote-census/cmd/censushttp |
||||
|
``` |
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
`./censusHttpService <port> <censusId>[:pubKey] [<censusId>[:pubKey] ...]` |
||||
|
|
||||
|
Example |
||||
|
|
||||
|
``` |
||||
|
./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 |
||||
|
``` |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
A HTTP jSON endpoint is available with the following possible fields: `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. |
||||
|
|
||||
|
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 | |
||||
|
| `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 |
||||
|
|
||||
|
|
||||
|
## Signature |
||||
|
|
||||
|
The signature provides authentication by signing a concatenation of the following strings (even if empty) without spaces: `censusId rootHash claimData timeStamp`. |
||||
|
|
||||
|
The `timeStamp` when received on the server side must not differ more than 10 seconds from the current UNIX time. |
||||
|
|
||||
|
## Examples |
||||
|
|
||||
|
#### 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 |
||||
|
|
||||
|
{"error":false,"response":""} |
||||
|
``` |
||||
|
|
||||
|
``` |
||||
|
curl -d '{"censusID":"GoT_Favorite","claimData":"Tyrion"}' http://localhost:1500/addClaim |
||||
|
|
||||
|
{"error":false,"response":""} |
||||
|
``` |
||||
|
|
||||
|
In case signature is enabled: |
||||
|
|
||||
|
``` |
||||
|
curl -d '{ |
||||
|
"censusID":"GoT_Favorite", |
||||
|
"claimData":"Jon Snow", |
||||
|
"timeStamp":"1547814675", |
||||
|
"signature":"a117c4ce12b29090884112ffe57e664f007e7ef142a1679996e2d34fd2b852fe76966e47932f1e9d3a54610d0f361383afe2d9aab096e15d136c236abb0a0d0e" }' http://localhost:1500/addClaim |
||||
|
|
||||
|
{"error":false,"response":""} |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
#### generate proof |
||||
|
|
||||
|
Generate a merkle proof for the claim `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 |
||||
|
|
||||
|
Now let's check if the proof is valid |
||||
|
|
||||
|
``` |
||||
|
curl -d '{ |
||||
|
"censusID":"GoT_Favorite","claimData":"Jon Snow", |
||||
|
"rootHash":"0x2f0ddde5cb995eae23dc3b75a5c0333f1cc89b73f3a00b0fe71996fb90fef04b", |
||||
|
"proofData":"0x000200000000000000000000000000000000000000000000000000000000000212f8134039730791388a9bd0460f9fbd0757327212a64b3a2b0f0841ce561ee3"}' http://localhost:1500/checkProof |
||||
|
|
||||
|
{"error":false,"response":"valid"} |
||||
|
``` |
||||
|
|
||||
|
If `rootHash` is not specified, the current root hash is used. |
||||
|
|
||||
|
#### dump |
||||
|
|
||||
|
Dump contents of a specific censusId (values) |
||||
|
|
||||
|
``` |
||||
|
curl -d '{"censusID":"GoT_Favorite"}' http://localhost:1500/dump |
||||
|
|
||||
|
{"error":false,"response":"[\"Tyrion\",\"Jon Snow\"]"} |
||||
|
``` |
||||
|
|
||||
|
If `rootHash` is specified, dump will return the values for the merkle tree with the given root hash. |
@ -0,0 +1,37 @@ |
|||||
|
package main |
||||
|
|
||||
|
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] + |
||||
|
" <port> <namespace>[:pubKey] [<namespace>[:pubKey]]...") |
||||
|
os.Exit(2) |
||||
|
} |
||||
|
port, err := strconv.Atoi(os.Args[1]) |
||||
|
if err != nil { |
||||
|
log.Fatal(err) |
||||
|
os.Exit(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) |
||||
|
} |
||||
|
censusmanager.Listen(port, "http") |
||||
|
|
||||
|
} |
@ -0,0 +1,309 @@ |
|||||
|
package censusmanager |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"log" |
||||
|
"net/http" |
||||
|
"strconv" |
||||
|
"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 |
||||
|
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
|
||||
|
Signature string `json:"signature"` // Signature as Hexadecimal String
|
||||
|
} |
||||
|
|
||||
|
type Result struct { |
||||
|
Error bool `json:"error"` |
||||
|
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 { |
||||
|
http.Error(w, err.Error(), 500) |
||||
|
} else { |
||||
|
w.Header().Set("content-type", "application/json") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func checkRequest(w http.ResponseWriter, req *http.Request) bool { |
||||
|
if req.Body == nil { |
||||
|
http.Error(w, "Please send a request body", 400) |
||||
|
return false |
||||
|
} |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
func checkAuth(timestamp, signature, pubKey, message string) bool { |
||||
|
if len(pubKey) < 1 { |
||||
|
return true |
||||
|
} |
||||
|
currentTime := int64(time.Now().Unix()) |
||||
|
timeStampRemote, err := strconv.ParseInt(timestamp, 10, 32) |
||||
|
if err != nil { |
||||
|
log.Printf("Cannot parse timestamp data %s\n", err) |
||||
|
return false |
||||
|
} |
||||
|
if timeStampRemote < currentTime+authTimeWindow && |
||||
|
timeStampRemote > currentTime-authTimeWindow { |
||||
|
v, err := Signature.Verify(message, signature, pubKey) |
||||
|
if err != nil { |
||||
|
log.Printf("Verification error: %s\n", err) |
||||
|
} |
||||
|
return v |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func claimHandler(w http.ResponseWriter, req *http.Request, op string) { |
||||
|
var c Claim |
||||
|
var resp Result |
||||
|
|
||||
|
if ok := checkRequest(w, req); !ok { |
||||
|
return |
||||
|
} |
||||
|
// Decode JSON
|
||||
|
err := json.NewDecoder(req.Body).Decode(&c) |
||||
|
if err != nil { |
||||
|
http.Error(w, err.Error(), 400) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Process data
|
||||
|
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 |
||||
|
if len(c.CensusID) > 0 { |
||||
|
_, censusFound = MkTrees[c.CensusID] |
||||
|
} |
||||
|
if !censusFound { |
||||
|
resp.Error = true |
||||
|
resp.Response = "censusId not valid or not found" |
||||
|
reply(&resp, w) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if op == "add" { |
||||
|
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" |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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] |
||||
|
} |
||||
|
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" { |
||||
|
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 = err.Error() |
||||
|
} else { |
||||
|
jValues, err := json.Marshal(values) |
||||
|
if err != nil { |
||||
|
resp.Error = true |
||||
|
resp.Response = err.Error() |
||||
|
} else { |
||||
|
resp.Response = fmt.Sprintf("%s", jValues) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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 = "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" |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
reply(&resp, w) |
||||
|
} |
||||
|
|
||||
|
func addCorsHeaders(w *http.ResponseWriter, req *http.Request) { |
||||
|
(*w).Header().Set("Access-Control-Allow-Origin", "*") |
||||
|
(*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") |
||||
|
} |
||||
|
|
||||
|
func Listen(port int, proto string) { |
||||
|
srv := &http.Server{ |
||||
|
Addr: fmt.Sprintf(":%d", port), |
||||
|
ReadHeaderTimeout: 4 * time.Second, |
||||
|
ReadTimeout: 4 * time.Second, |
||||
|
WriteTimeout: 4 * 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) { |
||||
|
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 { |
||||
|
panic(err) |
||||
|
} |
||||
|
} |
||||
|
if proto == "http" { |
||||
|
log.Print("Starting server in http mode") |
||||
|
srv.SetKeepAlivesEnabled(false) |
||||
|
if err := srv.ListenAndServe(); err != nil { |
||||
|
panic(err) |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
## dvote Tree |
||||
|
|
||||
|
Implementation of dvote tree structure. Currently based on iden3 merkle tree. |
||||
|
|
||||
|
Example of usage: |
||||
|
|
||||
|
``` |
||||
|
T := tree.Tree |
||||
|
if T.Init() != nil { fmt.Println("Cannot create tree database") } |
||||
|
err := T.AddClaim([]byte("Hello you!")) |
||||
|
if err != nil { |
||||
|
fmt.Println("Claim already exist") |
||||
|
} |
||||
|
mpHex, err := T.GenProof([]byte("Hello you!")) |
||||
|
fmt.Println(mpHex) |
||||
|
fmt.Println(T.CheckProof([]byte("Hello you!"), mpHex)) |
||||
|
T.Close() |
||||
|
``` |
||||
|
|
||||
|
#### To-Do |
||||
|
|
||||
|
Avoid duplicates on dump/snapshot |
@ -0,0 +1,138 @@ |
|||||
|
package tree |
||||
|
|
||||
|
import ( |
||||
|
"bytes" |
||||
|
"errors" |
||||
|
"fmt" |
||||
|
"os/user" |
||||
|
|
||||
|
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 { |
||||
|
Storage string |
||||
|
Tree *merkletree.MerkleTree |
||||
|
DbStorage *db.LevelDbStorage |
||||
|
} |
||||
|
|
||||
|
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/census/" + namespace |
||||
|
} else { |
||||
|
t.Storage = "./dvoteTree/" + namespace |
||||
|
} |
||||
|
} |
||||
|
mtdb, err := db.NewLevelDbStorage(t.Storage, false) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
mt, err := merkletree.NewMerkleTree(mtdb, 140) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
t.DbStorage = mtdb |
||||
|
t.Tree = mt |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
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, '\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 { |
||||
|
e, err := t.GetClaim(data) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
return t.Tree.Add(e.Entry()) |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) GenProof(data []byte) (string, error) { |
||||
|
e, err := t.GetClaim(data) |
||||
|
if err != nil { |
||||
|
return "", err |
||||
|
} |
||||
|
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) { |
||||
|
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 { |
||||
|
return false, err |
||||
|
} |
||||
|
return merkletree.VerifyProof(t.Tree.RootKey(), mp, |
||||
|
e.Entry().HIndex(), e.Entry().HValue()), nil |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) GetRoot() string { |
||||
|
return common3.HexEncode(t.Tree.RootKey().Bytes()) |
||||
|
} |
||||
|
|
||||
|
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 |
||||
|
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, err |
||||
|
} |
||||
|
|
||||
|
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 |
||||
|
} |