The traditional key exchange like TLS or SSL needs a CA to ensure key exchange run safely. But in DH-RPC I use a DHT to do that. The main idea is removing CA Cert from the whole system by using a DHT for Naming and Key Exchange.
DH-RPC is a secp256k1-ECDH-AES encrypted P2P RPC framework for decentralized applications written in golang.
CovenantSQL is built on DH-RPC, including:
- Byzantine Fault Tolerance consensus protocol Kayak
- Consistent Secure DHT
- DB API
- Metric Collect
- Blocks sync
Alice Client:
// Init Key Management Systemroute.InitKMS(PubKeyStoreFile)
// Register Node public key, addr to TrackerreqA:=&proto.PingReq{
Node: AliceNode,
}
respA:=new(proto.PingResp)
rpc.NewCaller().CallNode(Tracker.NodeID, "DHT.Ping", reqA, respA)
pc:=rpc.NewPersistentCaller(BobNodeID)
respSimple:=new(string)
pc.Call("Test.Talk", "Hi there", respSimple)
fmt.Printf("Response msg: %s", *respSimple)Bob Server:
// RPC logic// TestService to be register to RPC servertypeTestServicestruct {}
func (s*TestService) Talk(msgstring, ret*string) error {
fmt.Println(msg)
resp:=fmt.Sprintf("got msg %s", msg)
*ret=respreturnnil
}
// Init Key Management Systemroute.InitKMS(PubKeyStoreFile)
// Register DHT serviceserver, err:=rpc.NewServerWithService(rpc.ServiceMap{
"Test": &TestService{},
})
// Init RPC server with an empty master key, which is not recommendserver.InitRPCServer("0.0.0.0:2120", PrivateKeyFile, "")
// Start Node RPC serverserver.Serve()Tracker stuff can refer to the Example section below
- 100% compatible with Go net/rpc standard.
- ID based routing and Key exchange built on Secure Enhanced DHT.
- use MessagePack for serialization which support most types without writing
MarshalandUnmarshal. - Crypto Schema
- Use Elliptic Curve Secp256k1 for Asymmetric Encryption
- ECDH for Key Exchange
- PKCS#7 for padding
- AES-256-CFB for Symmetric Encryption
- Private key protected by master key
- Annoymous connection is also supported
- DHT persistence layer has 2 implementations:
- SQLite3 based simple traditional DHT
- Kayak based 2PC strong consistent DHT
- Connection pool based on Yamux, make thousands of connections multiplexed over One TCP connection.
- DH-RPC = TLS - Cert + DHT
- RPC Layer: compatible with golang
net/rpc - Naming Layer: Consistent Secure DHT
- Pooling Layer: session pool built on Yamux
- Multiplex Layer: Yamux by Hashicorp
- Transport Security Layer: Enhanced TLS
- Network Layer: TCP or KCP for optional later
- RPC Layer: compatible with golang
As we all know, Elliptic Curve Public Key is computed form Private Key
ECPubKey:=ECPrivKey.Pub()DH-RPC node is generated by hash of NodePublicKey and an Uint256 Nonce:
NodeID:=sha256(blake2b-512(NodePublicKey+Uint256Nonce))DHT is used to hold the NodeID:PublicKeyNodeID:Addr map. A RPC connection will do ECDH to get shared secret after TCP connection established.
GenECDHSharedSecret(APub, BPriv) ==GenECDHSharedSecret(BPub, APriv)The main procedure is described as sequence chart below:
So anyone tries to fake NodeB by overwriting the address or public key on DHT without the private key of NodeB will be failed to get the correct shared secret.
The example below is 1 tracker and 2 nodes.
Complete code can be found here
package main
import (
"os""github.com/CovenantSQL/CovenantSQL/conf""github.com/CovenantSQL/CovenantSQL/consistent""github.com/CovenantSQL/CovenantSQL/route""github.com/CovenantSQL/CovenantSQL/rpc""github.com/CovenantSQL/CovenantSQL/utils/log"
)
funcmain() {
//log.SetLevel(log.DebugLevel)conf.GConf, _=conf.LoadConfig(os.Args[1])
log.Debugf("GConf: %#v", conf.GConf)
// Init Key Management Systemroute.InitKMS(conf.GConf.PubKeyStoreFile)
// Creating DHT RPC with simple persistence layerdht, err:=route.NewDHTService(conf.GConf.DHTFileName, new(consistent.KMSStorage), true)
iferr!=nil {
log.Fatalf("init dht failed: %v", err)
}
// Register DHT serviceserver, err:=rpc.NewServerWithService(rpc.ServiceMap{route.DHTRPCName: dht})
iferr!=nil {
log.Fatal(err)
}
// Init RPC server with an empty master key, which is not recommendaddr:=conf.GConf.ListenAddrmasterKey:= []byte("")
server.InitRPCServer(addr, conf.GConf.PrivateKeyFile, masterKey)
server.Serve()
}package main
import (
"bufio""fmt""os""strings""github.com/CovenantSQL/CovenantSQL/conf""github.com/CovenantSQL/CovenantSQL/proto""github.com/CovenantSQL/CovenantSQL/route""github.com/CovenantSQL/CovenantSQL/rpc""github.com/CovenantSQL/CovenantSQL/utils/log"
)
// TestService to be register to RPC servertypeTestServicestruct {
}
funcNewTestService() *TestService {
return&TestService{}
}
func (s*TestService) Talk(msgstring, ret*string) error {
fmt.Println(msg)
resp:=fmt.Sprintf("got %s", msg)
*ret=respreturnnil
}
funcmain() {
//log.SetLevel(log.DebugLevel)conf.GConf, _=conf.LoadConfig(os.Args[1])
log.Debugf("GConf: %#v", conf.GConf)
// Init Key Management Systemroute.InitKMS(conf.GConf.PubKeyStoreFile)
// Register DHT serviceserver, err:=rpc.NewServerWithService(rpc.ServiceMap{
"Test": NewTestService(),
})
iferr!=nil {
log.Fatal(err)
}
// Init RPC server with an empty master key, which is not recommendaddr:=conf.GConf.ListenAddrmasterKey:= []byte("")
server.InitRPCServer(addr, conf.GConf.PrivateKeyFile, masterKey)
// Start Node RPC servergoserver.Serve()
// Register Node public key, addr to Tracker(BP)for_, n:=rangeconf.GConf.KnownNodes {
client:=rpc.NewCaller()
reqA:=&proto.PingReq{
Node: n,
}
respA:=new(proto.PingResp)
err=client.CallNode(conf.GConf.BP.NodeID, "DHT.Ping", reqA, respA)
iferr!=nil {
log.Fatal(err)
}
log.Debugf("respA: %v", respA)
}
// Read target node and connect to itscanner:=bufio.NewScanner(os.Stdin)
fmt.Print("Input target node ID: ")
scanner.Scan()
ifscanner.Err() ==nil {
target:=proto.NodeID(strings.TrimSpace(scanner.Text()))
pc:=rpc.NewPersistentCaller(target)
log.Debugf("connecting to %s", scanner.Text())
fmt.Print("Input msg: ")
forscanner.Scan() {
input:=scanner.Text()
log.Debugf("get input %s", input)
repSimple:=new(string)
err=pc.Call("Test.Talk", input, repSimple)
iferr!=nil {
log.Fatal(err)
}
log.Infof("resp msg: %s", *repSimple)
}
}
}Start tracker and node1, node2
$ ./runTracker.sh &
$ ./runNode2.sh &
$ ./runNode1.sh
$ Input target node ID: 000005aa62048f85da4ae9698ed59c14ec0d48a88a07c15a32265634e7e64ade #node2
$ Input msg: abcdefg

