mirror of
https://github.com/libp2p/go-libp2p-core.git
synced 2025-01-14 02:20:06 +08:00
Absorb go-libp2p abstractions and core types into this module (#1)
This commit is contained in:
parent
2aa2acf05f
commit
6e566d10f4
@ -1,7 +1,5 @@
|
||||
# go-libp2p-core
|
||||
|
||||
⚠️⚠️⚠️ WIP ⚠️⚠️⚠️
|
||||
|
||||
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai)
|
||||
[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](https://libp2p.io/)
|
||||
[![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p)
|
||||
|
50
alias.go
Normal file
50
alias.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Package core provides convenient access to foundational, central go-libp2p primitives via type aliases.
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/libp2p/go-libp2p-core/host"
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
// Multiaddr aliases the Multiaddr type from github.com/multiformats/go-multiaddr.
|
||||
//
|
||||
// Refer to the docs on that type for more info.
|
||||
type Multiaddr = multiaddr.Multiaddr
|
||||
|
||||
// PeerID aliases peer.ID.
|
||||
//
|
||||
// Refer to the docs on that type for more info.
|
||||
type PeerID = peer.ID
|
||||
|
||||
// PeerID aliases protocol.ID.
|
||||
//
|
||||
// Refer to the docs on that type for more info.
|
||||
type ProtocolID = protocol.ID
|
||||
|
||||
// PeerAddrInfo aliases peer.AddrInfo.
|
||||
//
|
||||
// Refer to the docs on that type for more info.
|
||||
type PeerAddrInfo = peer.AddrInfo
|
||||
|
||||
// Host aliases host.Host.
|
||||
//
|
||||
// Refer to the docs on that type for more info.
|
||||
type Host = host.Host
|
||||
|
||||
// Network aliases network.Network.
|
||||
//
|
||||
// Refer to the docs on that type for more info.
|
||||
type Network = network.Network
|
||||
|
||||
// Conn aliases network.Conn.
|
||||
//
|
||||
// Refer to the docs on that type for more info.
|
||||
type Conn = network.Conn
|
||||
|
||||
// Stream aliases network.Stream.
|
||||
//
|
||||
// Refer to the docs on that type for more info.
|
||||
type Stream = network.Stream
|
78
connmgr/connmgr.go
Normal file
78
connmgr/connmgr.go
Normal file
@ -0,0 +1,78 @@
|
||||
// Package connmgr provides connection tracking and management interfaces for libp2p.
|
||||
//
|
||||
// The ConnManager interface exported from this package allows libp2p to enforce an
|
||||
// upper bound on the total number of open connections. To avoid service disruptions,
|
||||
// connections can be tagged with metadata and optionally "protected" to ensure that
|
||||
// essential connections are not arbitrarily cut.
|
||||
package connmgr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
)
|
||||
|
||||
// ConnManager tracks connections to peers, and allows consumers to associate metadata
|
||||
// with each peer.
|
||||
//
|
||||
// It enables connections to be trimmed based on implementation-defined heuristics.
|
||||
// The ConnManager allows libp2p to enforce an upper bound on the total number of
|
||||
// open connections.
|
||||
type ConnManager interface {
|
||||
|
||||
// TagPeer tags a peer with a string, associating a weight with the tag.
|
||||
TagPeer(peer.ID, string, int)
|
||||
|
||||
// Untag removes the tagged value from the peer.
|
||||
UntagPeer(p peer.ID, tag string)
|
||||
|
||||
// UpsertTag updates an existing tag or inserts a new one.
|
||||
//
|
||||
// The connection manager calls the upsert function supplying the current
|
||||
// value of the tag (or zero if inexistent). The return value is used as
|
||||
// the new value of the tag.
|
||||
UpsertTag(p peer.ID, tag string, upsert func(int) int)
|
||||
|
||||
// GetTagInfo returns the metadata associated with the peer,
|
||||
// or nil if no metadata has been recorded for the peer.
|
||||
GetTagInfo(p peer.ID) *TagInfo
|
||||
|
||||
// TrimOpenConns terminates open connections based on an implementation-defined
|
||||
// heuristic.
|
||||
TrimOpenConns(ctx context.Context)
|
||||
|
||||
// Notifee returns an implementation that can be called back to inform of
|
||||
// opened and closed connections.
|
||||
Notifee() network.Notifiee
|
||||
|
||||
// Protect protects a peer from having its connection(s) pruned.
|
||||
//
|
||||
// Tagging allows different parts of the system to manage protections without interfering with one another.
|
||||
//
|
||||
// Calls to Protect() with the same tag are idempotent. They are not refcounted, so after multiple calls
|
||||
// to Protect() with the same tag, a single Unprotect() call bearing the same tag will revoke the protection.
|
||||
Protect(id peer.ID, tag string)
|
||||
|
||||
// Unprotect removes a protection that may have been placed on a peer, under the specified tag.
|
||||
//
|
||||
// The return value indicates whether the peer continues to be protected after this call, by way of a different tag.
|
||||
// See notes on Protect() for more info.
|
||||
Unprotect(id peer.ID, tag string) (protected bool)
|
||||
|
||||
// Close closes the connection manager and stops background processes
|
||||
Close() error
|
||||
}
|
||||
|
||||
// TagInfo stores metadata associated with a peer.
|
||||
type TagInfo struct {
|
||||
FirstSeen time.Time
|
||||
Value int
|
||||
|
||||
// Tags maps tag ids to the numerical values.
|
||||
Tags map[string]int
|
||||
|
||||
// Conns maps connection ids (such as remote multiaddr) to their creation time.
|
||||
Conns map[string]time.Time
|
||||
}
|
23
connmgr/null.go
Normal file
23
connmgr/null.go
Normal file
@ -0,0 +1,23 @@
|
||||
package connmgr
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
)
|
||||
|
||||
// NullConnMgr is a ConnMgr that provides no functionality.
|
||||
type NullConnMgr struct{}
|
||||
|
||||
var _ ConnManager = (*NullConnMgr)(nil)
|
||||
|
||||
func (_ NullConnMgr) TagPeer(peer.ID, string, int) {}
|
||||
func (_ NullConnMgr) UntagPeer(peer.ID, string) {}
|
||||
func (_ NullConnMgr) UpsertTag(peer.ID, string, func(int) int) {}
|
||||
func (_ NullConnMgr) GetTagInfo(peer.ID) *TagInfo { return &TagInfo{} }
|
||||
func (_ NullConnMgr) TrimOpenConns(ctx context.Context) {}
|
||||
func (_ NullConnMgr) Notifee() network.Notifiee { return network.GlobalNoopNotifiee }
|
||||
func (_ NullConnMgr) Protect(peer.ID, string) {}
|
||||
func (_ NullConnMgr) Unprotect(peer.ID, string) bool { return false }
|
||||
func (_ NullConnMgr) Close() error { return nil }
|
84
crypto/bench_test.go
Normal file
84
crypto/bench_test.go
Normal file
@ -0,0 +1,84 @@
|
||||
package crypto
|
||||
|
||||
import "testing"
|
||||
|
||||
func BenchmarkSignRSA1B(b *testing.B) { RunBenchmarkSignRSA(b, 1) }
|
||||
func BenchmarkSignRSA10B(b *testing.B) { RunBenchmarkSignRSA(b, 10) }
|
||||
func BenchmarkSignRSA100B(b *testing.B) { RunBenchmarkSignRSA(b, 100) }
|
||||
func BenchmarkSignRSA1000B(b *testing.B) { RunBenchmarkSignRSA(b, 1000) }
|
||||
func BenchmarkSignRSA10000B(b *testing.B) { RunBenchmarkSignRSA(b, 10000) }
|
||||
func BenchmarkSignRSA100000B(b *testing.B) { RunBenchmarkSignRSA(b, 100000) }
|
||||
|
||||
func BenchmarkVerifyRSA1B(b *testing.B) { RunBenchmarkVerifyRSA(b, 1) }
|
||||
func BenchmarkVerifyRSA10B(b *testing.B) { RunBenchmarkVerifyRSA(b, 10) }
|
||||
func BenchmarkVerifyRSA100B(b *testing.B) { RunBenchmarkVerifyRSA(b, 100) }
|
||||
func BenchmarkVerifyRSA1000B(b *testing.B) { RunBenchmarkVerifyRSA(b, 1000) }
|
||||
func BenchmarkVerifyRSA10000B(b *testing.B) { RunBenchmarkVerifyRSA(b, 10000) }
|
||||
func BenchmarkVerifyRSA100000B(b *testing.B) { RunBenchmarkVerifyRSA(b, 100000) }
|
||||
|
||||
func BenchmarkSignEd255191B(b *testing.B) { RunBenchmarkSignEd25519(b, 1) }
|
||||
func BenchmarkSignEd2551910B(b *testing.B) { RunBenchmarkSignEd25519(b, 10) }
|
||||
func BenchmarkSignEd25519100B(b *testing.B) { RunBenchmarkSignEd25519(b, 100) }
|
||||
func BenchmarkSignEd255191000B(b *testing.B) { RunBenchmarkSignEd25519(b, 1000) }
|
||||
func BenchmarkSignEd2551910000B(b *testing.B) { RunBenchmarkSignEd25519(b, 10000) }
|
||||
func BenchmarkSignEd25519100000B(b *testing.B) { RunBenchmarkSignEd25519(b, 100000) }
|
||||
|
||||
func BenchmarkVerifyEd255191B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 1) }
|
||||
func BenchmarkVerifyEd2551910B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 10) }
|
||||
func BenchmarkVerifyEd25519100B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 100) }
|
||||
func BenchmarkVerifyEd255191000B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 1000) }
|
||||
func BenchmarkVerifyEd2551910000B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 10000) }
|
||||
func BenchmarkVerifyEd25519100000B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 100000) }
|
||||
|
||||
func RunBenchmarkSignRSA(b *testing.B, numBytes int) {
|
||||
runBenchmarkSign(b, numBytes, RSA)
|
||||
}
|
||||
|
||||
func RunBenchmarkSignEd25519(b *testing.B, numBytes int) {
|
||||
runBenchmarkSign(b, numBytes, Ed25519)
|
||||
}
|
||||
|
||||
func runBenchmarkSign(b *testing.B, numBytes int, t int) {
|
||||
secret, _, err := GenerateKeyPair(t, 1024)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
someData := make([]byte, numBytes)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := secret.Sign(someData)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RunBenchmarkVerifyRSA(b *testing.B, numBytes int) {
|
||||
runBenchmarkSign(b, numBytes, RSA)
|
||||
}
|
||||
|
||||
func RunBenchmarkVerifyEd25519(b *testing.B, numBytes int) {
|
||||
runBenchmarkSign(b, numBytes, Ed25519)
|
||||
}
|
||||
|
||||
func runBenchmarkVerify(b *testing.B, numBytes int, t int) {
|
||||
secret, public, err := GenerateKeyPair(t, 1024)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
someData := make([]byte, numBytes)
|
||||
signature, err := secret.Sign(someData)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
valid, err := public.Verify(someData, signature)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if !valid {
|
||||
b.Fatal("signature should be valid")
|
||||
}
|
||||
}
|
||||
}
|
186
crypto/ecdsa.go
Normal file
186
crypto/ecdsa.go
Normal file
@ -0,0 +1,186 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
pb "github.com/libp2p/go-libp2p-core/crypto/pb"
|
||||
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// ECDSAPrivateKey is an implementation of an ECDSA private key
|
||||
type ECDSAPrivateKey struct {
|
||||
priv *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
// ECDSAPublicKey is an implementation of an ECDSA public key
|
||||
type ECDSAPublicKey struct {
|
||||
pub *ecdsa.PublicKey
|
||||
}
|
||||
|
||||
// ECDSASig holds the r and s values of an ECDSA signature
|
||||
type ECDSASig struct {
|
||||
R, S *big.Int
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrNotECDSAPubKey is returned when the public key passed is not an ecdsa public key
|
||||
ErrNotECDSAPubKey = errors.New("not an ecdsa public key")
|
||||
// ErrNilSig is returned when the signature is nil
|
||||
ErrNilSig = errors.New("sig is nil")
|
||||
// ErrNilPrivateKey is returned when a nil private key is provided
|
||||
ErrNilPrivateKey = errors.New("private key is nil")
|
||||
// ECDSACurve is the default ecdsa curve used
|
||||
ECDSACurve = elliptic.P256()
|
||||
)
|
||||
|
||||
// GenerateECDSAKeyPair generates a new ecdsa private and public key
|
||||
func GenerateECDSAKeyPair(src io.Reader) (PrivKey, PubKey, error) {
|
||||
return GenerateECDSAKeyPairWithCurve(ECDSACurve, src)
|
||||
}
|
||||
|
||||
// GenerateECDSAKeyPairWithCurve generates a new ecdsa private and public key with a speicified curve
|
||||
func GenerateECDSAKeyPairWithCurve(curve elliptic.Curve, src io.Reader) (PrivKey, PubKey, error) {
|
||||
priv, err := ecdsa.GenerateKey(curve, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &ECDSAPrivateKey{priv}, &ECDSAPublicKey{&priv.PublicKey}, nil
|
||||
}
|
||||
|
||||
// ECDSAKeyPairFromKey generates a new ecdsa private and public key from an input private key
|
||||
func ECDSAKeyPairFromKey(priv *ecdsa.PrivateKey) (PrivKey, PubKey, error) {
|
||||
if priv == nil {
|
||||
return nil, nil, ErrNilPrivateKey
|
||||
}
|
||||
|
||||
return &ECDSAPrivateKey{priv}, &ECDSAPublicKey{&priv.PublicKey}, nil
|
||||
}
|
||||
|
||||
// MarshalECDSAPrivateKey returns x509 bytes from a private key
|
||||
func MarshalECDSAPrivateKey(ePriv ECDSAPrivateKey) ([]byte, error) {
|
||||
return x509.MarshalECPrivateKey(ePriv.priv)
|
||||
}
|
||||
|
||||
// MarshalECDSAPublicKey returns x509 bytes from a public key
|
||||
func MarshalECDSAPublicKey(ePub ECDSAPublicKey) ([]byte, error) {
|
||||
return x509.MarshalPKIXPublicKey(ePub.pub)
|
||||
}
|
||||
|
||||
// UnmarshalECDSAPrivateKey returns a private key from x509 bytes
|
||||
func UnmarshalECDSAPrivateKey(data []byte) (PrivKey, error) {
|
||||
priv, err := x509.ParseECPrivateKey(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ECDSAPrivateKey{priv}, nil
|
||||
}
|
||||
|
||||
// UnmarshalECDSAPublicKey returns the public key from x509 bytes
|
||||
func UnmarshalECDSAPublicKey(data []byte) (PubKey, error) {
|
||||
pubIfc, err := x509.ParsePKIXPublicKey(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pub, ok := pubIfc.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, ErrNotECDSAPubKey
|
||||
}
|
||||
|
||||
return &ECDSAPublicKey{pub}, nil
|
||||
}
|
||||
|
||||
// Bytes returns the private key as protobuf bytes
|
||||
func (ePriv *ECDSAPrivateKey) Bytes() ([]byte, error) {
|
||||
return MarshalPrivateKey(ePriv)
|
||||
}
|
||||
|
||||
// Type returns the key type
|
||||
func (ePriv *ECDSAPrivateKey) Type() pb.KeyType {
|
||||
return pb.KeyType_ECDSA
|
||||
}
|
||||
|
||||
// Raw returns x509 bytes from a private key
|
||||
func (ePriv *ECDSAPrivateKey) Raw() ([]byte, error) {
|
||||
return x509.MarshalECPrivateKey(ePriv.priv)
|
||||
}
|
||||
|
||||
// Equals compares to private keys
|
||||
func (ePriv *ECDSAPrivateKey) Equals(o Key) bool {
|
||||
oPriv, ok := o.(*ECDSAPrivateKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return ePriv.priv.D.Cmp(oPriv.priv.D) == 0
|
||||
}
|
||||
|
||||
// Sign returns the signature of the input data
|
||||
func (ePriv *ECDSAPrivateKey) Sign(data []byte) ([]byte, error) {
|
||||
hash := sha256.Sum256(data)
|
||||
r, s, err := ecdsa.Sign(rand.Reader, ePriv.priv, hash[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return asn1.Marshal(ECDSASig{
|
||||
R: r,
|
||||
S: s,
|
||||
})
|
||||
}
|
||||
|
||||
// GetPublic returns a public key
|
||||
func (ePriv *ECDSAPrivateKey) GetPublic() PubKey {
|
||||
return &ECDSAPublicKey{&ePriv.priv.PublicKey}
|
||||
}
|
||||
|
||||
// Bytes returns the public key as protobuf bytes
|
||||
func (ePub *ECDSAPublicKey) Bytes() ([]byte, error) {
|
||||
return MarshalPublicKey(ePub)
|
||||
}
|
||||
|
||||
// Type returns the key type
|
||||
func (ePub *ECDSAPublicKey) Type() pb.KeyType {
|
||||
return pb.KeyType_ECDSA
|
||||
}
|
||||
|
||||
// Raw returns x509 bytes from a public key
|
||||
func (ePub ECDSAPublicKey) Raw() ([]byte, error) {
|
||||
return x509.MarshalPKIXPublicKey(ePub.pub)
|
||||
}
|
||||
|
||||
// Equals compares to public keys
|
||||
func (ePub *ECDSAPublicKey) Equals(o Key) bool {
|
||||
oPub, ok := o.(*ECDSAPublicKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return ePub.pub.X != nil && ePub.pub.Y != nil && oPub.pub.X != nil && oPub.pub.Y != nil &&
|
||||
0 == ePub.pub.X.Cmp(oPub.pub.X) && 0 == ePub.pub.Y.Cmp(oPub.pub.Y)
|
||||
}
|
||||
|
||||
// Verify compares data to a signature
|
||||
func (ePub *ECDSAPublicKey) Verify(data, sigBytes []byte) (bool, error) {
|
||||
sig := new(ECDSASig)
|
||||
if _, err := asn1.Unmarshal(sigBytes, sig); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if sig == nil {
|
||||
return false, ErrNilSig
|
||||
}
|
||||
|
||||
hash := sha256.Sum256(data)
|
||||
|
||||
return ecdsa.Verify(ePub.pub, hash[:], sig.R, sig.S), nil
|
||||
}
|
96
crypto/ecdsa_test.go
Normal file
96
crypto/ecdsa_test.go
Normal file
@ -0,0 +1,96 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestECDSABasicSignAndVerify(t *testing.T) {
|
||||
priv, pub, err := GenerateECDSAKeyPair(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data := []byte("hello! and welcome to some awesome crypto primitives")
|
||||
|
||||
sig, err := priv.Sign(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err := pub.Verify(data, sig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Fatal("signature didnt match")
|
||||
}
|
||||
|
||||
// change data
|
||||
data[0] = ^data[0]
|
||||
ok, err = pub.Verify(data, sig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ok {
|
||||
t.Fatal("signature matched and shouldn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestECDSASignZero(t *testing.T) {
|
||||
priv, pub, err := GenerateECDSAKeyPair(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data := make([]byte, 0)
|
||||
sig, err := priv.Sign(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err := pub.Verify(data, sig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("signature didn't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestECDSAMarshalLoop(t *testing.T) {
|
||||
priv, pub, err := GenerateECDSAKeyPair(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
privB, err := priv.Bytes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
privNew, err := UnmarshalPrivateKey(privB)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !priv.Equals(privNew) || !privNew.Equals(priv) {
|
||||
t.Fatal("keys are not equal")
|
||||
}
|
||||
|
||||
pubB, err := pub.Bytes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pubNew, err := UnmarshalPublicKey(pubB)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !pub.Equals(pubNew) || !pubNew.Equals(pub) {
|
||||
t.Fatal("keys are not equal")
|
||||
}
|
||||
|
||||
}
|
155
crypto/ed25519.go
Normal file
155
crypto/ed25519.go
Normal file
@ -0,0 +1,155 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
pb "github.com/libp2p/go-libp2p-core/crypto/pb"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
// Ed25519PrivateKey is an ed25519 private key.
|
||||
type Ed25519PrivateKey struct {
|
||||
k ed25519.PrivateKey
|
||||
}
|
||||
|
||||
// Ed25519PublicKey is an ed25519 public key.
|
||||
type Ed25519PublicKey struct {
|
||||
k ed25519.PublicKey
|
||||
}
|
||||
|
||||
// GenerateEd25519Key generates a new ed25519 private and public key pair.
|
||||
func GenerateEd25519Key(src io.Reader) (PrivKey, PubKey, error) {
|
||||
pub, priv, err := ed25519.GenerateKey(src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &Ed25519PrivateKey{
|
||||
k: priv,
|
||||
},
|
||||
&Ed25519PublicKey{
|
||||
k: pub,
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
// Type of the private key (Ed25519).
|
||||
func (k *Ed25519PrivateKey) Type() pb.KeyType {
|
||||
return pb.KeyType_Ed25519
|
||||
}
|
||||
|
||||
// Bytes marshals an ed25519 private key to protobuf bytes.
|
||||
func (k *Ed25519PrivateKey) Bytes() ([]byte, error) {
|
||||
return MarshalPrivateKey(k)
|
||||
}
|
||||
|
||||
// Raw private key bytes.
|
||||
func (k *Ed25519PrivateKey) Raw() ([]byte, error) {
|
||||
// The Ed25519 private key contains two 32-bytes curve points, the private
|
||||
// key and the public key.
|
||||
// It makes it more efficient to get the public key without re-computing an
|
||||
// elliptic curve multiplication.
|
||||
buf := make([]byte, len(k.k))
|
||||
copy(buf, k.k)
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (k *Ed25519PrivateKey) pubKeyBytes() []byte {
|
||||
return k.k[ed25519.PrivateKeySize-ed25519.PublicKeySize:]
|
||||
}
|
||||
|
||||
// Equals compares two ed25519 private keys.
|
||||
func (k *Ed25519PrivateKey) Equals(o Key) bool {
|
||||
edk, ok := o.(*Ed25519PrivateKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.Equal(k.k, edk.k)
|
||||
}
|
||||
|
||||
// GetPublic returns an ed25519 public key from a private key.
|
||||
func (k *Ed25519PrivateKey) GetPublic() PubKey {
|
||||
return &Ed25519PublicKey{k: k.pubKeyBytes()}
|
||||
}
|
||||
|
||||
// Sign returns a signature from an input message.
|
||||
func (k *Ed25519PrivateKey) Sign(msg []byte) ([]byte, error) {
|
||||
return ed25519.Sign(k.k, msg), nil
|
||||
}
|
||||
|
||||
// Type of the public key (Ed25519).
|
||||
func (k *Ed25519PublicKey) Type() pb.KeyType {
|
||||
return pb.KeyType_Ed25519
|
||||
}
|
||||
|
||||
// Bytes returns a ed25519 public key as protobuf bytes.
|
||||
func (k *Ed25519PublicKey) Bytes() ([]byte, error) {
|
||||
return MarshalPublicKey(k)
|
||||
}
|
||||
|
||||
// Raw public key bytes.
|
||||
func (k *Ed25519PublicKey) Raw() ([]byte, error) {
|
||||
return k.k, nil
|
||||
}
|
||||
|
||||
// Equals compares two ed25519 public keys.
|
||||
func (k *Ed25519PublicKey) Equals(o Key) bool {
|
||||
edk, ok := o.(*Ed25519PublicKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.Equal(k.k, edk.k)
|
||||
}
|
||||
|
||||
// Verify checks a signature agains the input data.
|
||||
func (k *Ed25519PublicKey) Verify(data []byte, sig []byte) (bool, error) {
|
||||
return ed25519.Verify(k.k, data, sig), nil
|
||||
}
|
||||
|
||||
// UnmarshalEd25519PublicKey returns a public key from input bytes.
|
||||
func UnmarshalEd25519PublicKey(data []byte) (PubKey, error) {
|
||||
if len(data) != 32 {
|
||||
return nil, errors.New("expect ed25519 public key data size to be 32")
|
||||
}
|
||||
|
||||
return &Ed25519PublicKey{
|
||||
k: ed25519.PublicKey(data),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UnmarshalEd25519PrivateKey returns a private key from input bytes.
|
||||
func UnmarshalEd25519PrivateKey(data []byte) (PrivKey, error) {
|
||||
switch len(data) {
|
||||
case ed25519.PrivateKeySize + ed25519.PublicKeySize:
|
||||
// Remove the redundant public key. See issue #36.
|
||||
redundantPk := data[ed25519.PrivateKeySize:]
|
||||
pk := data[ed25519.PrivateKeySize-ed25519.PublicKeySize : ed25519.PrivateKeySize]
|
||||
if !bytes.Equal(pk, redundantPk) {
|
||||
return nil, errors.New("expected redundant ed25519 public key to be redundant")
|
||||
}
|
||||
|
||||
// No point in storing the extra data.
|
||||
newKey := make([]byte, ed25519.PrivateKeySize)
|
||||
copy(newKey, data[:ed25519.PrivateKeySize])
|
||||
data = newKey
|
||||
case ed25519.PrivateKeySize:
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"expected ed25519 data size to be %d or %d, got %d",
|
||||
ed25519.PrivateKeySize,
|
||||
ed25519.PrivateKeySize+ed25519.PublicKeySize,
|
||||
len(data),
|
||||
)
|
||||
}
|
||||
|
||||
return &Ed25519PrivateKey{
|
||||
k: ed25519.PrivateKey(data),
|
||||
}, nil
|
||||
}
|
220
crypto/ed25519_test.go
Normal file
220
crypto/ed25519_test.go
Normal file
@ -0,0 +1,220 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
pb "github.com/libp2p/go-libp2p-core/crypto/pb"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
func TestBasicSignAndVerify(t *testing.T) {
|
||||
priv, pub, err := GenerateEd25519Key(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data := []byte("hello! and welcome to some awesome crypto primitives")
|
||||
|
||||
sig, err := priv.Sign(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err := pub.Verify(data, sig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Fatal("signature didn't match")
|
||||
}
|
||||
|
||||
// change data
|
||||
data[0] = ^data[0]
|
||||
ok, err = pub.Verify(data, sig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ok {
|
||||
t.Fatal("signature matched and shouldn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignZero(t *testing.T) {
|
||||
priv, pub, err := GenerateEd25519Key(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data := make([]byte, 0)
|
||||
sig, err := priv.Sign(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err := pub.Verify(data, sig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("signature didn't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalLoop(t *testing.T) {
|
||||
priv, pub, err := GenerateEd25519Key(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("PrivateKey", func(t *testing.T) {
|
||||
for name, f := range map[string]func() ([]byte, error){
|
||||
"Bytes": priv.Bytes,
|
||||
"Marshal": func() ([]byte, error) {
|
||||
return MarshalPrivateKey(priv)
|
||||
},
|
||||
"Redundant": func() ([]byte, error) {
|
||||
// See issue #36.
|
||||
// Ed25519 private keys used to contain the public key twice.
|
||||
// For backwards-compatibility, we need to continue supporting
|
||||
// that scenario.
|
||||
pbmes := new(pb.PrivateKey)
|
||||
pbmes.Type = priv.Type()
|
||||
data, err := priv.Raw()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pbmes.Data = append(data, data[len(data)-ed25519.PublicKeySize:]...)
|
||||
return pbmes.Marshal()
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
bts, err := f()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
privNew, err := UnmarshalPrivateKey(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !priv.Equals(privNew) || !privNew.Equals(priv) {
|
||||
t.Fatal("keys are not equal")
|
||||
}
|
||||
|
||||
msg := []byte("My child, my sister,\nThink of the rapture\nOf living together there!")
|
||||
signed, err := privNew.Sign(msg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err := privNew.GetPublic().Verify(msg, signed)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Fatal("signature didn't match")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("PublicKey", func(t *testing.T) {
|
||||
for name, f := range map[string]func() ([]byte, error){
|
||||
"Bytes": pub.Bytes,
|
||||
"Marshal": func() ([]byte, error) {
|
||||
return MarshalPublicKey(pub)
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
bts, err := f()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pubNew, err := UnmarshalPublicKey(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !pub.Equals(pubNew) || !pubNew.Equals(pub) {
|
||||
t.Fatal("keys are not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalErrors(t *testing.T) {
|
||||
t.Run("PublicKey", func(t *testing.T) {
|
||||
t.Run("Invalid data length", func(t *testing.T) {
|
||||
pbmes := &pb.PublicKey{
|
||||
Type: pb.KeyType_Ed25519,
|
||||
Data: []byte{42},
|
||||
}
|
||||
|
||||
data, err := pbmes.Marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = UnmarshalPublicKey(data)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("PrivateKey", func(t *testing.T) {
|
||||
t.Run("Redundant public key mismatch", func(t *testing.T) {
|
||||
priv, _, err := GenerateEd25519Key(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pbmes := new(pb.PrivateKey)
|
||||
pbmes.Type = priv.Type()
|
||||
data, err := priv.Raw()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Append the private key instead of the public key.
|
||||
pbmes.Data = append(data, data[:ed25519.PublicKeySize]...)
|
||||
b, err := pbmes.Marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = UnmarshalPrivateKey(b)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
if err.Error() != "expected redundant ed25519 public key to be redundant" {
|
||||
t.Fatalf("invalid error received: %s", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Invalid data length", func(t *testing.T) {
|
||||
pbmes := &pb.PrivateKey{
|
||||
Type: pb.KeyType_Ed25519,
|
||||
Data: []byte{42},
|
||||
}
|
||||
|
||||
data, err := pbmes.Marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = UnmarshalPrivateKey(data)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
132
crypto/fixture_test.go
Normal file
132
crypto/fixture_test.go
Normal file
@ -0,0 +1,132 @@
|
||||
package crypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
crypto "github.com/libp2p/go-libp2p-core/crypto"
|
||||
crypto_pb "github.com/libp2p/go-libp2p-core/crypto/pb"
|
||||
)
|
||||
|
||||
var message = []byte("Libp2p is the _best_!")
|
||||
|
||||
type testCase struct {
|
||||
keyType crypto_pb.KeyType
|
||||
gen func(i io.Reader) (crypto.PrivKey, crypto.PubKey, error)
|
||||
sigDeterministic bool
|
||||
}
|
||||
|
||||
var keyTypes = []testCase{
|
||||
{
|
||||
keyType: crypto_pb.KeyType_ECDSA,
|
||||
gen: crypto.GenerateECDSAKeyPair,
|
||||
},
|
||||
{
|
||||
keyType: crypto_pb.KeyType_Secp256k1,
|
||||
sigDeterministic: true,
|
||||
gen: crypto.GenerateSecp256k1Key,
|
||||
},
|
||||
{
|
||||
keyType: crypto_pb.KeyType_RSA,
|
||||
sigDeterministic: true,
|
||||
gen: func(i io.Reader) (crypto.PrivKey, crypto.PubKey, error) {
|
||||
return crypto.GenerateRSAKeyPair(2048, i)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func fname(kt crypto_pb.KeyType, ext string) string {
|
||||
return fmt.Sprintf("test_data/%d.%s", kt, ext)
|
||||
}
|
||||
|
||||
func TestFixtures(t *testing.T) {
|
||||
for _, tc := range keyTypes {
|
||||
t.Run(tc.keyType.String(), func(t *testing.T) {
|
||||
pubBytes, err := ioutil.ReadFile(fname(tc.keyType, "pub"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
privBytes, err := ioutil.ReadFile(fname(tc.keyType, "priv"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sigBytes, err := ioutil.ReadFile(fname(tc.keyType, "sig"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pub, err := crypto.UnmarshalPublicKey(pubBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pubBytes2, err := pub.Bytes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(pubBytes2, pubBytes) {
|
||||
t.Fatal("encoding round-trip failed")
|
||||
}
|
||||
priv, err := crypto.UnmarshalPrivateKey(privBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
privBytes2, err := priv.Bytes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(privBytes2, privBytes) {
|
||||
t.Fatal("encoding round-trip failed")
|
||||
}
|
||||
ok, err := pub.Verify(message, sigBytes)
|
||||
if !ok || err != nil {
|
||||
t.Fatal("failed to validate signature with public key")
|
||||
}
|
||||
|
||||
if tc.sigDeterministic {
|
||||
sigBytes2, err := priv.Sign(message)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(sigBytes2, sigBytes) {
|
||||
t.Fatal("signature not deterministic")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// set to true to re-generate test data
|
||||
if false {
|
||||
generate()
|
||||
panic("generated")
|
||||
}
|
||||
}
|
||||
|
||||
// generate re-generates test data
|
||||
func generate() {
|
||||
for _, tc := range keyTypes {
|
||||
priv, pub, err := tc.gen(rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pubb, err := pub.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
privb, err := priv.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sig, err := priv.Sign(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ioutil.WriteFile(fname(tc.keyType, "pub"), pubb, 0666)
|
||||
ioutil.WriteFile(fname(tc.keyType, "priv"), privb, 0666)
|
||||
ioutil.WriteFile(fname(tc.keyType, "sig"), sig, 0666)
|
||||
}
|
||||
}
|
351
crypto/key.go
Normal file
351
crypto/key.go
Normal file
@ -0,0 +1,351 @@
|
||||
// Package crypto implements various cryptographic utilities used by libp2p.
|
||||
// This includes a Public and Private key interface and key implementations
|
||||
// for supported key algorithms.
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/elliptic"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
pb "github.com/libp2p/go-libp2p-core/crypto/pb"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
const (
|
||||
// RSA is an enum for the supported RSA key type
|
||||
RSA = iota
|
||||
// Ed25519 is an enum for the supported Ed25519 key type
|
||||
Ed25519
|
||||
// Secp256k1 is an enum for the supported Secp256k1 key type
|
||||
Secp256k1
|
||||
// ECDSA is an enum for the supported ECDSA key type
|
||||
ECDSA
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrBadKeyType is returned when a key is not supported
|
||||
ErrBadKeyType = errors.New("invalid or unsupported key type")
|
||||
// KeyTypes is a list of supported keys
|
||||
KeyTypes = []int{
|
||||
RSA,
|
||||
Ed25519,
|
||||
Secp256k1,
|
||||
ECDSA,
|
||||
}
|
||||
)
|
||||
|
||||
// PubKeyUnmarshaller is a func that creates a PubKey from a given slice of bytes
|
||||
type PubKeyUnmarshaller func(data []byte) (PubKey, error)
|
||||
|
||||
// PrivKeyUnmarshaller is a func that creates a PrivKey from a given slice of bytes
|
||||
type PrivKeyUnmarshaller func(data []byte) (PrivKey, error)
|
||||
|
||||
// PubKeyUnmarshallers is a map of unmarshallers by key type
|
||||
var PubKeyUnmarshallers = map[pb.KeyType]PubKeyUnmarshaller{
|
||||
pb.KeyType_RSA: UnmarshalRsaPublicKey,
|
||||
pb.KeyType_Ed25519: UnmarshalEd25519PublicKey,
|
||||
pb.KeyType_Secp256k1: UnmarshalSecp256k1PublicKey,
|
||||
pb.KeyType_ECDSA: UnmarshalECDSAPublicKey,
|
||||
}
|
||||
|
||||
// PrivKeyUnmarshallers is a map of unmarshallers by key type
|
||||
var PrivKeyUnmarshallers = map[pb.KeyType]PrivKeyUnmarshaller{
|
||||
pb.KeyType_RSA: UnmarshalRsaPrivateKey,
|
||||
pb.KeyType_Ed25519: UnmarshalEd25519PrivateKey,
|
||||
pb.KeyType_Secp256k1: UnmarshalSecp256k1PrivateKey,
|
||||
pb.KeyType_ECDSA: UnmarshalECDSAPrivateKey,
|
||||
}
|
||||
|
||||
// Key represents a crypto key that can be compared to another key
|
||||
type Key interface {
|
||||
// Bytes returns a serialized, storeable representation of this key
|
||||
// DEPRECATED in favor of Marshal / Unmarshal
|
||||
Bytes() ([]byte, error)
|
||||
|
||||
// Equals checks whether two PubKeys are the same
|
||||
Equals(Key) bool
|
||||
|
||||
// Raw returns the raw bytes of the key (not wrapped in the
|
||||
// libp2p-crypto protobuf).
|
||||
//
|
||||
// This function is the inverse of {Priv,Pub}KeyUnmarshaler.
|
||||
Raw() ([]byte, error)
|
||||
|
||||
// Type returns the protobof key type.
|
||||
Type() pb.KeyType
|
||||
}
|
||||
|
||||
// PrivKey represents a private key that can be used to generate a public key and sign data
|
||||
type PrivKey interface {
|
||||
Key
|
||||
|
||||
// Cryptographically sign the given bytes
|
||||
Sign([]byte) ([]byte, error)
|
||||
|
||||
// Return a public key paired with this private key
|
||||
GetPublic() PubKey
|
||||
}
|
||||
|
||||
// PubKey is a public key that can be used to verifiy data signed with the corresponding private key
|
||||
type PubKey interface {
|
||||
Key
|
||||
|
||||
// Verify that 'sig' is the signed hash of 'data'
|
||||
Verify(data []byte, sig []byte) (bool, error)
|
||||
}
|
||||
|
||||
// GenSharedKey generates the shared key from a given private key
|
||||
type GenSharedKey func([]byte) ([]byte, error)
|
||||
|
||||
// GenerateKeyPair generates a private and public key
|
||||
func GenerateKeyPair(typ, bits int) (PrivKey, PubKey, error) {
|
||||
return GenerateKeyPairWithReader(typ, bits, rand.Reader)
|
||||
}
|
||||
|
||||
// GenerateKeyPairWithReader returns a keypair of the given type and bitsize
|
||||
func GenerateKeyPairWithReader(typ, bits int, src io.Reader) (PrivKey, PubKey, error) {
|
||||
switch typ {
|
||||
case RSA:
|
||||
return GenerateRSAKeyPair(bits, src)
|
||||
case Ed25519:
|
||||
return GenerateEd25519Key(src)
|
||||
case Secp256k1:
|
||||
return GenerateSecp256k1Key(src)
|
||||
case ECDSA:
|
||||
return GenerateECDSAKeyPair(src)
|
||||
default:
|
||||
return nil, nil, ErrBadKeyType
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateEKeyPair returns an ephemeral public key and returns a function that will compute
|
||||
// the shared secret key. Used in the identify module.
|
||||
//
|
||||
// Focuses only on ECDH now, but can be made more general in the future.
|
||||
func GenerateEKeyPair(curveName string) ([]byte, GenSharedKey, error) {
|
||||
var curve elliptic.Curve
|
||||
|
||||
switch curveName {
|
||||
case "P-256":
|
||||
curve = elliptic.P256()
|
||||
case "P-384":
|
||||
curve = elliptic.P384()
|
||||
case "P-521":
|
||||
curve = elliptic.P521()
|
||||
}
|
||||
|
||||
priv, x, y, err := elliptic.GenerateKey(curve, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pubKey := elliptic.Marshal(curve, x, y)
|
||||
|
||||
done := func(theirPub []byte) ([]byte, error) {
|
||||
// Verify and unpack node's public key.
|
||||
x, y := elliptic.Unmarshal(curve, theirPub)
|
||||
if x == nil {
|
||||
return nil, fmt.Errorf("malformed public key: %d %v", len(theirPub), theirPub)
|
||||
}
|
||||
|
||||
if !curve.IsOnCurve(x, y) {
|
||||
return nil, errors.New("invalid public key")
|
||||
}
|
||||
|
||||
// Generate shared secret.
|
||||
secret, _ := curve.ScalarMult(x, y, priv)
|
||||
|
||||
return secret.Bytes(), nil
|
||||
}
|
||||
|
||||
return pubKey, done, nil
|
||||
}
|
||||
|
||||
// StretchedKeys ...
|
||||
type StretchedKeys struct {
|
||||
IV []byte
|
||||
MacKey []byte
|
||||
CipherKey []byte
|
||||
}
|
||||
|
||||
// KeyStretcher returns a set of keys for each party by stretching the shared key.
|
||||
// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey)
|
||||
func KeyStretcher(cipherType string, hashType string, secret []byte) (StretchedKeys, StretchedKeys) {
|
||||
var cipherKeySize int
|
||||
var ivSize int
|
||||
switch cipherType {
|
||||
case "AES-128":
|
||||
ivSize = 16
|
||||
cipherKeySize = 16
|
||||
case "AES-256":
|
||||
ivSize = 16
|
||||
cipherKeySize = 32
|
||||
case "Blowfish":
|
||||
ivSize = 8
|
||||
// Note: cypherKeySize arbitrarily selected, needs more thought
|
||||
cipherKeySize = 32
|
||||
}
|
||||
|
||||
hmacKeySize := 20
|
||||
|
||||
seed := []byte("key expansion")
|
||||
|
||||
result := make([]byte, 2*(ivSize+cipherKeySize+hmacKeySize))
|
||||
|
||||
var h func() hash.Hash
|
||||
|
||||
switch hashType {
|
||||
case "SHA1":
|
||||
h = sha1.New
|
||||
case "SHA256":
|
||||
h = sha256.New
|
||||
case "SHA512":
|
||||
h = sha512.New
|
||||
default:
|
||||
panic("Unrecognized hash function, programmer error?")
|
||||
}
|
||||
|
||||
m := hmac.New(h, secret)
|
||||
// note: guaranteed to never return an error
|
||||
m.Write(seed)
|
||||
|
||||
a := m.Sum(nil)
|
||||
|
||||
j := 0
|
||||
for j < len(result) {
|
||||
m.Reset()
|
||||
|
||||
// note: guaranteed to never return an error.
|
||||
m.Write(a)
|
||||
m.Write(seed)
|
||||
|
||||
b := m.Sum(nil)
|
||||
|
||||
todo := len(b)
|
||||
|
||||
if j+todo > len(result) {
|
||||
todo = len(result) - j
|
||||
}
|
||||
|
||||
copy(result[j:j+todo], b)
|
||||
|
||||
j += todo
|
||||
|
||||
m.Reset()
|
||||
|
||||
// note: guaranteed to never return an error.
|
||||
m.Write(a)
|
||||
|
||||
a = m.Sum(nil)
|
||||
}
|
||||
|
||||
half := len(result) / 2
|
||||
r1 := result[:half]
|
||||
r2 := result[half:]
|
||||
|
||||
var k1 StretchedKeys
|
||||
var k2 StretchedKeys
|
||||
|
||||
k1.IV = r1[0:ivSize]
|
||||
k1.CipherKey = r1[ivSize : ivSize+cipherKeySize]
|
||||
k1.MacKey = r1[ivSize+cipherKeySize:]
|
||||
|
||||
k2.IV = r2[0:ivSize]
|
||||
k2.CipherKey = r2[ivSize : ivSize+cipherKeySize]
|
||||
k2.MacKey = r2[ivSize+cipherKeySize:]
|
||||
|
||||
return k1, k2
|
||||
}
|
||||
|
||||
// UnmarshalPublicKey converts a protobuf serialized public key into its
|
||||
// representative object
|
||||
func UnmarshalPublicKey(data []byte) (PubKey, error) {
|
||||
pmes := new(pb.PublicKey)
|
||||
err := proto.Unmarshal(data, pmes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
um, ok := PubKeyUnmarshallers[pmes.GetType()]
|
||||
if !ok {
|
||||
return nil, ErrBadKeyType
|
||||
}
|
||||
|
||||
return um(pmes.GetData())
|
||||
}
|
||||
|
||||
// MarshalPublicKey converts a public key object into a protobuf serialized
|
||||
// public key
|
||||
func MarshalPublicKey(k PubKey) ([]byte, error) {
|
||||
pbmes := new(pb.PublicKey)
|
||||
pbmes.Type = k.Type()
|
||||
data, err := k.Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pbmes.Data = data
|
||||
|
||||
return proto.Marshal(pbmes)
|
||||
}
|
||||
|
||||
// UnmarshalPrivateKey converts a protobuf serialized private key into its
|
||||
// representative object
|
||||
func UnmarshalPrivateKey(data []byte) (PrivKey, error) {
|
||||
pmes := new(pb.PrivateKey)
|
||||
err := proto.Unmarshal(data, pmes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
um, ok := PrivKeyUnmarshallers[pmes.GetType()]
|
||||
if !ok {
|
||||
return nil, ErrBadKeyType
|
||||
}
|
||||
|
||||
return um(pmes.GetData())
|
||||
}
|
||||
|
||||
// MarshalPrivateKey converts a key object into its protobuf serialized form.
|
||||
func MarshalPrivateKey(k PrivKey) ([]byte, error) {
|
||||
pbmes := new(pb.PrivateKey)
|
||||
pbmes.Type = k.Type()
|
||||
data, err := k.Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pbmes.Data = data
|
||||
return proto.Marshal(pbmes)
|
||||
}
|
||||
|
||||
// ConfigDecodeKey decodes from b64 (for config file), and unmarshals.
|
||||
func ConfigDecodeKey(b string) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(b)
|
||||
}
|
||||
|
||||
// ConfigEncodeKey encodes to b64 (for config file), and marshals.
|
||||
func ConfigEncodeKey(b []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
// KeyEqual checks whether two Keys are equivalent (have identical byte representations).
|
||||
func KeyEqual(k1, k2 Key) bool {
|
||||
if k1 == k2 {
|
||||
return true
|
||||
}
|
||||
|
||||
b1, err1 := k1.Bytes()
|
||||
b2, err2 := k2.Bytes()
|
||||
return bytes.Equal(b1, b2) && err1 == err2
|
||||
}
|
147
crypto/key_test.go
Normal file
147
crypto/key_test.go
Normal file
@ -0,0 +1,147 @@
|
||||
package crypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
. "github.com/libp2p/go-libp2p-core/crypto"
|
||||
pb "github.com/libp2p/go-libp2p-core/crypto/pb"
|
||||
"github.com/libp2p/go-libp2p-testing/crypto"
|
||||
)
|
||||
|
||||
func TestKeys(t *testing.T) {
|
||||
for _, typ := range KeyTypes {
|
||||
testKeyType(typ, t)
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyType(typ int, t *testing.T) {
|
||||
sk, pk, err := tcrypto.RandTestKeyPair(typ, 512)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testKeySignature(t, sk)
|
||||
testKeyEncoding(t, sk)
|
||||
testKeyEquals(t, sk)
|
||||
testKeyEquals(t, pk)
|
||||
}
|
||||
|
||||
func testKeySignature(t *testing.T, sk PrivKey) {
|
||||
pk := sk.GetPublic()
|
||||
|
||||
text := make([]byte, 16)
|
||||
if _, err := rand.Read(text); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sig, err := sk.Sign(text)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
valid, err := pk.Verify(text, sig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !valid {
|
||||
t.Fatal("Invalid signature.")
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyEncoding(t *testing.T, sk PrivKey) {
|
||||
skbm, err := MarshalPrivateKey(sk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sk2, err := UnmarshalPrivateKey(skbm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !sk.Equals(sk2) {
|
||||
t.Error("Unmarshaled private key didn't match original.\n")
|
||||
}
|
||||
|
||||
skbm2, err := MarshalPrivateKey(sk2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(skbm, skbm2) {
|
||||
t.Error("skb -> marshal -> unmarshal -> skb failed.\n", skbm, "\n", skbm2)
|
||||
}
|
||||
|
||||
pk := sk.GetPublic()
|
||||
pkbm, err := MarshalPublicKey(pk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pk2, err := UnmarshalPublicKey(pkbm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !pk.Equals(pk2) {
|
||||
t.Error("Unmarshaled public key didn't match original.\n")
|
||||
}
|
||||
|
||||
pkbm2, err := MarshalPublicKey(pk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(pkbm, pkbm2) {
|
||||
t.Error("skb -> marshal -> unmarshal -> skb failed.\n", pkbm, "\n", pkbm2)
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyEquals(t *testing.T, k Key) {
|
||||
kb, err := k.Bytes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !KeyEqual(k, k) {
|
||||
t.Fatal("Key not equal to itself.")
|
||||
}
|
||||
|
||||
if !KeyEqual(k, testkey(kb)) {
|
||||
t.Fatal("Key not equal to key with same bytes.")
|
||||
}
|
||||
|
||||
sk, pk, err := tcrypto.RandTestKeyPair(RSA, 512)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if KeyEqual(k, sk) {
|
||||
t.Fatal("Keys should not equal.")
|
||||
}
|
||||
|
||||
if KeyEqual(k, pk) {
|
||||
t.Fatal("Keys should not equal.")
|
||||
}
|
||||
}
|
||||
|
||||
type testkey []byte
|
||||
|
||||
func (pk testkey) Bytes() ([]byte, error) {
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
func (pk testkey) Type() pb.KeyType {
|
||||
return pb.KeyType_RSA
|
||||
}
|
||||
|
||||
func (pk testkey) Raw() ([]byte, error) {
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
func (pk testkey) Equals(k Key) bool {
|
||||
return KeyEqual(pk, k)
|
||||
}
|
98
crypto/openssl_common.go
Normal file
98
crypto/openssl_common.go
Normal file
@ -0,0 +1,98 @@
|
||||
// +build openssl
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
pb "github.com/libp2p/go-libp2p-core/crypto/pb"
|
||||
|
||||
openssl "github.com/spacemonkeygo/openssl"
|
||||
)
|
||||
|
||||
// define these as separate types so we can add more key types later and reuse
|
||||
// code.
|
||||
|
||||
type opensslPublicKey struct {
|
||||
key openssl.PublicKey
|
||||
}
|
||||
|
||||
type opensslPrivateKey struct {
|
||||
key openssl.PrivateKey
|
||||
}
|
||||
|
||||
func unmarshalOpensslPrivateKey(b []byte) (opensslPrivateKey, error) {
|
||||
sk, err := openssl.LoadPrivateKeyFromDER(b)
|
||||
if err != nil {
|
||||
return opensslPrivateKey{}, err
|
||||
}
|
||||
return opensslPrivateKey{sk}, nil
|
||||
}
|
||||
|
||||
func unmarshalOpensslPublicKey(b []byte) (opensslPublicKey, error) {
|
||||
sk, err := openssl.LoadPublicKeyFromDER(b)
|
||||
if err != nil {
|
||||
return opensslPublicKey{}, err
|
||||
}
|
||||
return opensslPublicKey{sk}, nil
|
||||
}
|
||||
|
||||
// Verify compares a signature against input data
|
||||
func (pk *opensslPublicKey) Verify(data, sig []byte) (bool, error) {
|
||||
err := pk.key.VerifyPKCS1v15(openssl.SHA256_Method, data, sig)
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func (pk *opensslPublicKey) Type() pb.KeyType {
|
||||
switch pk.key.KeyType() {
|
||||
case openssl.KeyTypeRSA:
|
||||
return pb.KeyType_RSA
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns protobuf bytes of a public key
|
||||
func (pk *opensslPublicKey) Bytes() ([]byte, error) {
|
||||
return MarshalPublicKey(pk)
|
||||
}
|
||||
|
||||
func (pk *opensslPublicKey) Raw() ([]byte, error) {
|
||||
return pk.key.MarshalPKIXPublicKeyDER()
|
||||
}
|
||||
|
||||
// Equals checks whether this key is equal to another
|
||||
func (pk *opensslPublicKey) Equals(k Key) bool {
|
||||
return KeyEqual(pk, k)
|
||||
}
|
||||
|
||||
// Sign returns a signature of the input data
|
||||
func (sk *opensslPrivateKey) Sign(message []byte) ([]byte, error) {
|
||||
return sk.key.SignPKCS1v15(openssl.SHA256_Method, message)
|
||||
}
|
||||
|
||||
// GetPublic returns a public key
|
||||
func (sk *opensslPrivateKey) GetPublic() PubKey {
|
||||
return &opensslPublicKey{sk.key}
|
||||
}
|
||||
|
||||
func (sk *opensslPrivateKey) Type() pb.KeyType {
|
||||
switch sk.key.KeyType() {
|
||||
case openssl.KeyTypeRSA:
|
||||
return pb.KeyType_RSA
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns protobuf bytes from a private key
|
||||
func (sk *opensslPrivateKey) Bytes() ([]byte, error) {
|
||||
return MarshalPrivateKey(sk)
|
||||
}
|
||||
|
||||
func (sk *opensslPrivateKey) Raw() ([]byte, error) {
|
||||
return sk.key.MarshalPKCS1PrivateKeyDER()
|
||||
}
|
||||
|
||||
// Equals checks whether this key is equal to another
|
||||
func (sk *opensslPrivateKey) Equals(k Key) bool {
|
||||
return KeyEqual(sk, k)
|
||||
}
|
11
crypto/pb/Makefile
Normal file
11
crypto/pb/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
PB = $(wildcard *.proto)
|
||||
GO = $(PB:.proto=.pb.go)
|
||||
|
||||
all: $(GO)
|
||||
|
||||
%.pb.go: %.proto
|
||||
protoc --proto_path=$(GOPATH)/src:. --gogofaster_out=. $<
|
||||
|
||||
clean:
|
||||
rm -f *.pb.go
|
||||
rm -f *.go
|
643
crypto/pb/crypto.pb.go
Normal file
643
crypto/pb/crypto.pb.go
Normal file
@ -0,0 +1,643 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: crypto.proto
|
||||
|
||||
package crypto_pb
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
github_com_gogo_protobuf_proto "github.com/gogo/protobuf/proto"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
io "io"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type KeyType int32
|
||||
|
||||
const (
|
||||
KeyType_RSA KeyType = 0
|
||||
KeyType_Ed25519 KeyType = 1
|
||||
KeyType_Secp256k1 KeyType = 2
|
||||
KeyType_ECDSA KeyType = 3
|
||||
)
|
||||
|
||||
var KeyType_name = map[int32]string{
|
||||
0: "RSA",
|
||||
1: "Ed25519",
|
||||
2: "Secp256k1",
|
||||
3: "ECDSA",
|
||||
}
|
||||
|
||||
var KeyType_value = map[string]int32{
|
||||
"RSA": 0,
|
||||
"Ed25519": 1,
|
||||
"Secp256k1": 2,
|
||||
"ECDSA": 3,
|
||||
}
|
||||
|
||||
func (x KeyType) Enum() *KeyType {
|
||||
p := new(KeyType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x KeyType) String() string {
|
||||
return proto.EnumName(KeyType_name, int32(x))
|
||||
}
|
||||
|
||||
func (x *KeyType) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(KeyType_value, data, "KeyType")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = KeyType(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (KeyType) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_527278fb02d03321, []int{0}
|
||||
}
|
||||
|
||||
type PublicKey struct {
|
||||
Type KeyType `protobuf:"varint,1,req,name=Type,enum=crypto.pb.KeyType" json:"Type"`
|
||||
Data []byte `protobuf:"bytes,2,req,name=Data" json:"Data"`
|
||||
}
|
||||
|
||||
func (m *PublicKey) Reset() { *m = PublicKey{} }
|
||||
func (m *PublicKey) String() string { return proto.CompactTextString(m) }
|
||||
func (*PublicKey) ProtoMessage() {}
|
||||
func (*PublicKey) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_527278fb02d03321, []int{0}
|
||||
}
|
||||
func (m *PublicKey) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *PublicKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_PublicKey.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *PublicKey) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PublicKey.Merge(m, src)
|
||||
}
|
||||
func (m *PublicKey) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *PublicKey) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PublicKey.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PublicKey proto.InternalMessageInfo
|
||||
|
||||
func (m *PublicKey) GetType() KeyType {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return KeyType_RSA
|
||||
}
|
||||
|
||||
func (m *PublicKey) GetData() []byte {
|
||||
if m != nil {
|
||||
return m.Data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PrivateKey struct {
|
||||
Type KeyType `protobuf:"varint,1,req,name=Type,enum=crypto.pb.KeyType" json:"Type"`
|
||||
Data []byte `protobuf:"bytes,2,req,name=Data" json:"Data"`
|
||||
}
|
||||
|
||||
func (m *PrivateKey) Reset() { *m = PrivateKey{} }
|
||||
func (m *PrivateKey) String() string { return proto.CompactTextString(m) }
|
||||
func (*PrivateKey) ProtoMessage() {}
|
||||
func (*PrivateKey) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_527278fb02d03321, []int{1}
|
||||
}
|
||||
func (m *PrivateKey) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *PrivateKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_PrivateKey.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *PrivateKey) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PrivateKey.Merge(m, src)
|
||||
}
|
||||
func (m *PrivateKey) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *PrivateKey) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PrivateKey.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PrivateKey proto.InternalMessageInfo
|
||||
|
||||
func (m *PrivateKey) GetType() KeyType {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return KeyType_RSA
|
||||
}
|
||||
|
||||
func (m *PrivateKey) GetData() []byte {
|
||||
if m != nil {
|
||||
return m.Data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("crypto.pb.KeyType", KeyType_name, KeyType_value)
|
||||
proto.RegisterType((*PublicKey)(nil), "crypto.pb.PublicKey")
|
||||
proto.RegisterType((*PrivateKey)(nil), "crypto.pb.PrivateKey")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("crypto.proto", fileDescriptor_527278fb02d03321) }
|
||||
|
||||
var fileDescriptor_527278fb02d03321 = []byte{
|
||||
// 203 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2e, 0xaa, 0x2c,
|
||||
0x28, 0xc9, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x84, 0xf1, 0x92, 0x94, 0x82, 0xb9,
|
||||
0x38, 0x03, 0x4a, 0x93, 0x72, 0x32, 0x93, 0xbd, 0x53, 0x2b, 0x85, 0x74, 0xb8, 0x58, 0x42, 0x2a,
|
||||
0x0b, 0x52, 0x25, 0x18, 0x15, 0x98, 0x34, 0xf8, 0x8c, 0x84, 0xf4, 0xe0, 0xca, 0xf4, 0xbc, 0x53,
|
||||
0x2b, 0x41, 0x32, 0x4e, 0x2c, 0x27, 0xee, 0xc9, 0x33, 0x04, 0x81, 0x55, 0x09, 0x49, 0x70, 0xb1,
|
||||
0xb8, 0x24, 0x96, 0x24, 0x4a, 0x30, 0x29, 0x30, 0x69, 0xf0, 0xc0, 0x64, 0x40, 0x22, 0x4a, 0x21,
|
||||
0x5c, 0x5c, 0x01, 0x45, 0x99, 0x65, 0x89, 0x25, 0xa9, 0x54, 0x34, 0x55, 0xcb, 0x92, 0x8b, 0x1d,
|
||||
0xaa, 0x41, 0x88, 0x9d, 0x8b, 0x39, 0x28, 0xd8, 0x51, 0x80, 0x41, 0x88, 0x9b, 0x8b, 0xdd, 0x35,
|
||||
0xc5, 0xc8, 0xd4, 0xd4, 0xd0, 0x52, 0x80, 0x51, 0x88, 0x97, 0x8b, 0x33, 0x38, 0x35, 0xb9, 0xc0,
|
||||
0xc8, 0xd4, 0x2c, 0xdb, 0x50, 0x80, 0x49, 0x88, 0x93, 0x8b, 0xd5, 0xd5, 0xd9, 0x25, 0xd8, 0x51,
|
||||
0x80, 0xd9, 0x49, 0xe2, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63,
|
||||
0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0x00, 0x01, 0x00,
|
||||
0x00, 0xff, 0xff, 0x13, 0xbe, 0xd4, 0xff, 0x19, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *PublicKey) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *PublicKey) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
dAtA[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintCrypto(dAtA, i, uint64(m.Type))
|
||||
if m.Data != nil {
|
||||
dAtA[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintCrypto(dAtA, i, uint64(len(m.Data)))
|
||||
i += copy(dAtA[i:], m.Data)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (m *PrivateKey) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *PrivateKey) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
dAtA[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintCrypto(dAtA, i, uint64(m.Type))
|
||||
if m.Data != nil {
|
||||
dAtA[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintCrypto(dAtA, i, uint64(len(m.Data)))
|
||||
i += copy(dAtA[i:], m.Data)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeVarintCrypto(dAtA []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return offset + 1
|
||||
}
|
||||
func (m *PublicKey) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
n += 1 + sovCrypto(uint64(m.Type))
|
||||
if m.Data != nil {
|
||||
l = len(m.Data)
|
||||
n += 1 + l + sovCrypto(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *PrivateKey) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
n += 1 + sovCrypto(uint64(m.Type))
|
||||
if m.Data != nil {
|
||||
l = len(m.Data)
|
||||
n += 1 + l + sovCrypto(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovCrypto(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
func sozCrypto(x uint64) (n int) {
|
||||
return sovCrypto(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *PublicKey) Unmarshal(dAtA []byte) error {
|
||||
var hasFields [1]uint64
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowCrypto
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: PublicKey: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: PublicKey: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
|
||||
}
|
||||
m.Type = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowCrypto
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Type |= KeyType(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
hasFields[0] |= uint64(0x00000001)
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowCrypto
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthCrypto
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthCrypto
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Data == nil {
|
||||
m.Data = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
hasFields[0] |= uint64(0x00000002)
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipCrypto(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthCrypto
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthCrypto
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
if hasFields[0]&uint64(0x00000001) == 0 {
|
||||
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("Type")
|
||||
}
|
||||
if hasFields[0]&uint64(0x00000002) == 0 {
|
||||
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("Data")
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *PrivateKey) Unmarshal(dAtA []byte) error {
|
||||
var hasFields [1]uint64
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowCrypto
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: PrivateKey: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: PrivateKey: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
|
||||
}
|
||||
m.Type = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowCrypto
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Type |= KeyType(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
hasFields[0] |= uint64(0x00000001)
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowCrypto
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthCrypto
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthCrypto
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Data == nil {
|
||||
m.Data = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
hasFields[0] |= uint64(0x00000002)
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipCrypto(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthCrypto
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthCrypto
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
if hasFields[0]&uint64(0x00000001) == 0 {
|
||||
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("Type")
|
||||
}
|
||||
if hasFields[0]&uint64(0x00000002) == 0 {
|
||||
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("Data")
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipCrypto(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowCrypto
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowCrypto
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
return iNdEx, nil
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowCrypto
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthCrypto
|
||||
}
|
||||
iNdEx += length
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthCrypto
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 3:
|
||||
for {
|
||||
var innerWire uint64
|
||||
var start int = iNdEx
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowCrypto
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
innerWire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
innerWireType := int(innerWire & 0x7)
|
||||
if innerWireType == 4 {
|
||||
break
|
||||
}
|
||||
next, err := skipCrypto(dAtA[start:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthCrypto
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 4:
|
||||
return iNdEx, nil
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
return iNdEx, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthCrypto = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowCrypto = fmt.Errorf("proto: integer overflow")
|
||||
)
|
20
crypto/pb/crypto.proto
Normal file
20
crypto/pb/crypto.proto
Normal file
@ -0,0 +1,20 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package crypto.pb;
|
||||
|
||||
enum KeyType {
|
||||
RSA = 0;
|
||||
Ed25519 = 1;
|
||||
Secp256k1 = 2;
|
||||
ECDSA = 3;
|
||||
}
|
||||
|
||||
message PublicKey {
|
||||
required KeyType Type = 1;
|
||||
required bytes Data = 2;
|
||||
}
|
||||
|
||||
message PrivateKey {
|
||||
required KeyType Type = 1;
|
||||
required bytes Data = 2;
|
||||
}
|
10
crypto/rsa_common.go
Normal file
10
crypto/rsa_common.go
Normal file
@ -0,0 +1,10 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrRsaKeyTooSmall is returned when trying to generate or parse an RSA key
|
||||
// that's smaller than 512 bits. Keys need to be larger enough to sign a 256bit
|
||||
// hash so this is a reasonable absolute minimum.
|
||||
var ErrRsaKeyTooSmall = errors.New("rsa keys must be >= 512 bits to be useful")
|
125
crypto/rsa_go.go
Normal file
125
crypto/rsa_go.go
Normal file
@ -0,0 +1,125 @@
|
||||
// +build !openssl
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
pb "github.com/libp2p/go-libp2p-core/crypto/pb"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// RsaPrivateKey is an rsa private key
|
||||
type RsaPrivateKey struct {
|
||||
sk rsa.PrivateKey
|
||||
}
|
||||
|
||||
// RsaPublicKey is an rsa public key
|
||||
type RsaPublicKey struct {
|
||||
k rsa.PublicKey
|
||||
}
|
||||
|
||||
// GenerateRSAKeyPair generates a new rsa private and public key
|
||||
func GenerateRSAKeyPair(bits int, src io.Reader) (PrivKey, PubKey, error) {
|
||||
if bits < 512 {
|
||||
return nil, nil, ErrRsaKeyTooSmall
|
||||
}
|
||||
priv, err := rsa.GenerateKey(src, bits)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pk := priv.PublicKey
|
||||
return &RsaPrivateKey{sk: *priv}, &RsaPublicKey{pk}, nil
|
||||
}
|
||||
|
||||
// Verify compares a signature against input data
|
||||
func (pk *RsaPublicKey) Verify(data, sig []byte) (bool, error) {
|
||||
hashed := sha256.Sum256(data)
|
||||
err := rsa.VerifyPKCS1v15(&pk.k, crypto.SHA256, hashed[:], sig)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (pk *RsaPublicKey) Type() pb.KeyType {
|
||||
return pb.KeyType_RSA
|
||||
}
|
||||
|
||||
// Bytes returns protobuf bytes of a public key
|
||||
func (pk *RsaPublicKey) Bytes() ([]byte, error) {
|
||||
return MarshalPublicKey(pk)
|
||||
}
|
||||
|
||||
func (pk *RsaPublicKey) Raw() ([]byte, error) {
|
||||
return x509.MarshalPKIXPublicKey(&pk.k)
|
||||
}
|
||||
|
||||
// Equals checks whether this key is equal to another
|
||||
func (pk *RsaPublicKey) Equals(k Key) bool {
|
||||
return KeyEqual(pk, k)
|
||||
}
|
||||
|
||||
// Sign returns a signature of the input data
|
||||
func (sk *RsaPrivateKey) Sign(message []byte) ([]byte, error) {
|
||||
hashed := sha256.Sum256(message)
|
||||
return rsa.SignPKCS1v15(rand.Reader, &sk.sk, crypto.SHA256, hashed[:])
|
||||
}
|
||||
|
||||
// GetPublic returns a public key
|
||||
func (sk *RsaPrivateKey) GetPublic() PubKey {
|
||||
return &RsaPublicKey{sk.sk.PublicKey}
|
||||
}
|
||||
|
||||
func (sk *RsaPrivateKey) Type() pb.KeyType {
|
||||
return pb.KeyType_RSA
|
||||
}
|
||||
|
||||
// Bytes returns protobuf bytes from a private key
|
||||
func (sk *RsaPrivateKey) Bytes() ([]byte, error) {
|
||||
return MarshalPrivateKey(sk)
|
||||
}
|
||||
|
||||
func (sk *RsaPrivateKey) Raw() ([]byte, error) {
|
||||
b := x509.MarshalPKCS1PrivateKey(&sk.sk)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Equals checks whether this key is equal to another
|
||||
func (sk *RsaPrivateKey) Equals(k Key) bool {
|
||||
return KeyEqual(sk, k)
|
||||
}
|
||||
|
||||
// UnmarshalRsaPrivateKey returns a private key from the input x509 bytes
|
||||
func UnmarshalRsaPrivateKey(b []byte) (PrivKey, error) {
|
||||
sk, err := x509.ParsePKCS1PrivateKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sk.N.BitLen() < 512 {
|
||||
return nil, ErrRsaKeyTooSmall
|
||||
}
|
||||
return &RsaPrivateKey{sk: *sk}, nil
|
||||
}
|
||||
|
||||
// UnmarshalRsaPublicKey returns a public key from the input x509 bytes
|
||||
func UnmarshalRsaPublicKey(b []byte) (PubKey, error) {
|
||||
pub, err := x509.ParsePKIXPublicKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pk, ok := pub.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("not actually an rsa public key")
|
||||
}
|
||||
if pk.N.BitLen() < 512 {
|
||||
return nil, ErrRsaKeyTooSmall
|
||||
}
|
||||
return &RsaPublicKey{*pk}, nil
|
||||
}
|
62
crypto/rsa_openssl.go
Normal file
62
crypto/rsa_openssl.go
Normal file
@ -0,0 +1,62 @@
|
||||
// +build openssl
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
openssl "github.com/spacemonkeygo/openssl"
|
||||
)
|
||||
|
||||
// RsaPrivateKey is an rsa private key
|
||||
type RsaPrivateKey struct {
|
||||
opensslPrivateKey
|
||||
}
|
||||
|
||||
// RsaPublicKey is an rsa public key
|
||||
type RsaPublicKey struct {
|
||||
opensslPublicKey
|
||||
}
|
||||
|
||||
// GenerateRSAKeyPair generates a new rsa private and public key
|
||||
func GenerateRSAKeyPair(bits int, _ io.Reader) (PrivKey, PubKey, error) {
|
||||
if bits < 512 {
|
||||
return nil, nil, ErrRsaKeyTooSmall
|
||||
}
|
||||
|
||||
key, err := openssl.GenerateRSAKey(bits)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &RsaPrivateKey{opensslPrivateKey{key}}, &RsaPublicKey{opensslPublicKey{key}}, nil
|
||||
}
|
||||
|
||||
// GetPublic returns a public key
|
||||
func (sk *RsaPrivateKey) GetPublic() PubKey {
|
||||
return &RsaPublicKey{opensslPublicKey{sk.opensslPrivateKey.key}}
|
||||
}
|
||||
|
||||
// UnmarshalRsaPrivateKey returns a private key from the input x509 bytes
|
||||
func UnmarshalRsaPrivateKey(b []byte) (PrivKey, error) {
|
||||
key, err := unmarshalOpensslPrivateKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key.Type() != RSA {
|
||||
return nil, errors.New("not actually an rsa public key")
|
||||
}
|
||||
return &RsaPrivateKey{key}, nil
|
||||
}
|
||||
|
||||
// UnmarshalRsaPublicKey returns a public key from the input x509 bytes
|
||||
func UnmarshalRsaPublicKey(b []byte) (PubKey, error) {
|
||||
key, err := unmarshalOpensslPublicKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key.Type() != RSA {
|
||||
return nil, errors.New("not actually an rsa public key")
|
||||
}
|
||||
return &RsaPublicKey{key}, nil
|
||||
}
|
102
crypto/rsa_test.go
Normal file
102
crypto/rsa_test.go
Normal file
@ -0,0 +1,102 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRSABasicSignAndVerify(t *testing.T) {
|
||||
priv, pub, err := GenerateRSAKeyPair(512, rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data := []byte("hello! and welcome to some awesome crypto primitives")
|
||||
|
||||
sig, err := priv.Sign(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err := pub.Verify(data, sig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Fatal("signature didnt match")
|
||||
}
|
||||
|
||||
// change data
|
||||
data[0] = ^data[0]
|
||||
ok, err = pub.Verify(data, sig)
|
||||
if err == nil {
|
||||
t.Fatal("should have produced a verification error")
|
||||
}
|
||||
|
||||
if ok {
|
||||
t.Fatal("signature matched and shouldn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSASmallKey(t *testing.T) {
|
||||
_, _, err := GenerateRSAKeyPair(384, rand.Reader)
|
||||
if err != ErrRsaKeyTooSmall {
|
||||
t.Fatal("should have refused to create small RSA key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSASignZero(t *testing.T) {
|
||||
priv, pub, err := GenerateRSAKeyPair(512, rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data := make([]byte, 0)
|
||||
sig, err := priv.Sign(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err := pub.Verify(data, sig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("signature didn't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSAMarshalLoop(t *testing.T) {
|
||||
priv, pub, err := GenerateRSAKeyPair(512, rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
privB, err := priv.Bytes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
privNew, err := UnmarshalPrivateKey(privB)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !priv.Equals(privNew) || !privNew.Equals(priv) {
|
||||
t.Fatal("keys are not equal")
|
||||
}
|
||||
|
||||
pubB, err := pub.Bytes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pubNew, err := UnmarshalPublicKey(pubB)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !pub.Equals(pubNew) || !pubNew.Equals(pub) {
|
||||
t.Fatal("keys are not equal")
|
||||
}
|
||||
}
|
125
crypto/secp256k1.go
Normal file
125
crypto/secp256k1.go
Normal file
@ -0,0 +1,125 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
pb "github.com/libp2p/go-libp2p-core/crypto/pb"
|
||||
|
||||
btcec "github.com/btcsuite/btcd/btcec"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// Secp256k1PrivateKey is an Secp256k1 private key
|
||||
type Secp256k1PrivateKey btcec.PrivateKey
|
||||
|
||||
// Secp256k1PublicKey is an Secp256k1 public key
|
||||
type Secp256k1PublicKey btcec.PublicKey
|
||||
|
||||
// GenerateSecp256k1Key generates a new Secp256k1 private and public key pair
|
||||
func GenerateSecp256k1Key(src io.Reader) (PrivKey, PubKey, error) {
|
||||
privk, err := btcec.NewPrivateKey(btcec.S256())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
k := (*Secp256k1PrivateKey)(privk)
|
||||
return k, k.GetPublic(), nil
|
||||
}
|
||||
|
||||
// UnmarshalSecp256k1PrivateKey returns a private key from bytes
|
||||
func UnmarshalSecp256k1PrivateKey(data []byte) (PrivKey, error) {
|
||||
if len(data) != btcec.PrivKeyBytesLen {
|
||||
return nil, fmt.Errorf("expected secp256k1 data size to be %d", btcec.PrivKeyBytesLen)
|
||||
}
|
||||
|
||||
privk, _ := btcec.PrivKeyFromBytes(btcec.S256(), data)
|
||||
return (*Secp256k1PrivateKey)(privk), nil
|
||||
}
|
||||
|
||||
// UnmarshalSecp256k1PublicKey returns a public key from bytes
|
||||
func UnmarshalSecp256k1PublicKey(data []byte) (PubKey, error) {
|
||||
k, err := btcec.ParsePubKey(data, btcec.S256())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return (*Secp256k1PublicKey)(k), nil
|
||||
}
|
||||
|
||||
// Bytes returns protobuf bytes from a private key
|
||||
func (k *Secp256k1PrivateKey) Bytes() ([]byte, error) {
|
||||
return MarshalPrivateKey(k)
|
||||
}
|
||||
|
||||
// Type returns the private key type
|
||||
func (k *Secp256k1PrivateKey) Type() pb.KeyType {
|
||||
return pb.KeyType_Secp256k1
|
||||
}
|
||||
|
||||
// Raw returns the bytes of the key
|
||||
func (k *Secp256k1PrivateKey) Raw() ([]byte, error) {
|
||||
return (*btcec.PrivateKey)(k).Serialize(), nil
|
||||
}
|
||||
|
||||
// Equals compares two private keys
|
||||
func (k *Secp256k1PrivateKey) Equals(o Key) bool {
|
||||
sk, ok := o.(*Secp256k1PrivateKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return k.D.Cmp(sk.D) == 0
|
||||
}
|
||||
|
||||
// Sign returns a signature from input data
|
||||
func (k *Secp256k1PrivateKey) Sign(data []byte) ([]byte, error) {
|
||||
hash := sha256.Sum256(data)
|
||||
sig, err := (*btcec.PrivateKey)(k).Sign(hash[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sig.Serialize(), nil
|
||||
}
|
||||
|
||||
// GetPublic returns a public key
|
||||
func (k *Secp256k1PrivateKey) GetPublic() PubKey {
|
||||
return (*Secp256k1PublicKey)((*btcec.PrivateKey)(k).PubKey())
|
||||
}
|
||||
|
||||
// Bytes returns protobuf bytes from a public key
|
||||
func (k *Secp256k1PublicKey) Bytes() ([]byte, error) {
|
||||
return MarshalPublicKey(k)
|
||||
}
|
||||
|
||||
// Type returns the public key type
|
||||
func (k *Secp256k1PublicKey) Type() pb.KeyType {
|
||||
return pb.KeyType_Secp256k1
|
||||
}
|
||||
|
||||
// Raw returns the bytes of the key
|
||||
func (k *Secp256k1PublicKey) Raw() ([]byte, error) {
|
||||
return (*btcec.PublicKey)(k).SerializeCompressed(), nil
|
||||
}
|
||||
|
||||
// Equals compares two public keys
|
||||
func (k *Secp256k1PublicKey) Equals(o Key) bool {
|
||||
sk, ok := o.(*Secp256k1PublicKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return (*btcec.PublicKey)(k).IsEqual((*btcec.PublicKey)(sk))
|
||||
}
|
||||
|
||||
// Verify compares a signature against the input data
|
||||
func (k *Secp256k1PublicKey) Verify(data []byte, sigStr []byte) (bool, error) {
|
||||
sig, err := btcec.ParseDERSignature(sigStr, btcec.S256())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
hash := sha256.Sum256(data)
|
||||
return sig.Verify(hash[:], (*btcec.PublicKey)(k)), nil
|
||||
}
|
96
crypto/secp256k1_test.go
Normal file
96
crypto/secp256k1_test.go
Normal file
@ -0,0 +1,96 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSecp256k1BasicSignAndVerify(t *testing.T) {
|
||||
priv, pub, err := GenerateSecp256k1Key(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data := []byte("hello! and welcome to some awesome crypto primitives")
|
||||
|
||||
sig, err := priv.Sign(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err := pub.Verify(data, sig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Fatal("signature didnt match")
|
||||
}
|
||||
|
||||
// change data
|
||||
data[0] = ^data[0]
|
||||
ok, err = pub.Verify(data, sig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ok {
|
||||
t.Fatal("signature matched and shouldn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecp256k1SignZero(t *testing.T) {
|
||||
priv, pub, err := GenerateSecp256k1Key(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data := make([]byte, 0)
|
||||
sig, err := priv.Sign(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err := pub.Verify(data, sig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("signature didn't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecp256k1MarshalLoop(t *testing.T) {
|
||||
priv, pub, err := GenerateSecp256k1Key(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
privB, err := priv.Bytes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
privNew, err := UnmarshalPrivateKey(privB)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !priv.Equals(privNew) || !privNew.Equals(priv) {
|
||||
t.Fatal("keys are not equal")
|
||||
}
|
||||
|
||||
pubB, err := pub.Bytes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pubNew, err := UnmarshalPublicKey(pubB)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !pub.Equals(pubNew) || !pubNew.Equals(pub) {
|
||||
t.Fatal("keys are not equal")
|
||||
}
|
||||
|
||||
}
|
BIN
crypto/test_data/0.priv
Normal file
BIN
crypto/test_data/0.priv
Normal file
Binary file not shown.
BIN
crypto/test_data/0.pub
Normal file
BIN
crypto/test_data/0.pub
Normal file
Binary file not shown.
BIN
crypto/test_data/0.sig
Normal file
BIN
crypto/test_data/0.sig
Normal file
Binary file not shown.
1
crypto/test_data/2.priv
Normal file
1
crypto/test_data/2.priv
Normal file
@ -0,0 +1 @@
|
||||
1A½`jPLDò4”ØóNò[¹µ-ªÐX¾ƒ¶àF±X
|
1
crypto/test_data/2.pub
Normal file
1
crypto/test_data/2.pub
Normal file
@ -0,0 +1 @@
|
||||
!5@„*ψ¤5Q©Mƒƒ¥U©’&Pk<50>ωS<CF89>磡³ΦΆ
|
1
crypto/test_data/2.sig
Normal file
1
crypto/test_data/2.sig
Normal file
@ -0,0 +1 @@
|
||||
0D 1§Ö3ÂóäZŤCuú¨Ü›˘@’ĹőłĘŇČţLó<4C>ň IęŤ!źE†<EFBFBD>ŇGuŐCꏲ<EFBFBD>pCGű5I<@;ÂÂY˛ťž
|
BIN
crypto/test_data/3.priv
Normal file
BIN
crypto/test_data/3.priv
Normal file
Binary file not shown.
BIN
crypto/test_data/3.pub
Normal file
BIN
crypto/test_data/3.pub
Normal file
Binary file not shown.
BIN
crypto/test_data/3.sig
Normal file
BIN
crypto/test_data/3.sig
Normal file
Binary file not shown.
27
discovery/discovery.go
Normal file
27
discovery/discovery.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Package discovery provides service advertisement and peer discovery interfaces for libp2p.
|
||||
package discovery
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
)
|
||||
|
||||
// Advertiser is an interface for advertising services
|
||||
type Advertiser interface {
|
||||
// Advertise advertises a service
|
||||
Advertise(ctx context.Context, ns string, opts ...Option) (time.Duration, error)
|
||||
}
|
||||
|
||||
// Discoverer is an interface for peer discovery
|
||||
type Discoverer interface {
|
||||
// FindPeers discovers peers providing a service
|
||||
FindPeers(ctx context.Context, ns string, opts ...Option) (<-chan peer.AddrInfo, error)
|
||||
}
|
||||
|
||||
// Discovery is an interface that combines service advertisement and peer discovery
|
||||
type Discovery interface {
|
||||
Advertiser
|
||||
Discoverer
|
||||
}
|
41
discovery/options.go
Normal file
41
discovery/options.go
Normal file
@ -0,0 +1,41 @@
|
||||
package discovery
|
||||
|
||||
import "time"
|
||||
|
||||
// DiscoveryOpt is a single discovery option.
|
||||
type Option func(opts *Options) error
|
||||
|
||||
// DiscoveryOpts is a set of discovery options.
|
||||
type Options struct {
|
||||
Ttl time.Duration
|
||||
Limit int
|
||||
|
||||
// Other (implementation-specific) options
|
||||
Other map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// Apply applies the given options to this DiscoveryOpts
|
||||
func (opts *Options) Apply(options ...Option) error {
|
||||
for _, o := range options {
|
||||
if err := o(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TTL is an option that provides a hint for the duration of an advertisement
|
||||
func TTL(ttl time.Duration) Option {
|
||||
return func(opts *Options) error {
|
||||
opts.Ttl = ttl
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Limit is an option that provides an upper bound on the peer count for discovery
|
||||
func Limit(limit int) Option {
|
||||
return func(opts *Options) error {
|
||||
opts.Limit = limit
|
||||
return nil
|
||||
}
|
||||
}
|
17
go.mod
Normal file
17
go.mod
Normal file
@ -0,0 +1,17 @@
|
||||
module github.com/libp2p/go-libp2p-core
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32
|
||||
github.com/coreos/go-semver v0.3.0
|
||||
github.com/gogo/protobuf v1.2.1
|
||||
github.com/ipfs/go-cid v0.0.1
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8
|
||||
github.com/libp2p/go-flow-metrics v0.0.1
|
||||
github.com/libp2p/go-libp2p-testing v0.0.0-20190508172549-1a0da3de1915
|
||||
github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16
|
||||
github.com/mr-tron/base58 v1.1.1
|
||||
github.com/multiformats/go-multiaddr v0.0.2
|
||||
github.com/multiformats/go-multihash v0.0.1
|
||||
github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a
|
||||
golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b
|
||||
)
|
76
go.sum
Normal file
76
go.sum
Normal file
@ -0,0 +1,76 @@
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32 h1:qkOC5Gd33k54tobS36cXdAzJbeHaduLtnLQQwNoIi78=
|
||||
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495 h1:6IyqGr3fnd0tM3YxipK27TUskaOVUjU2nG45yzwcQKY=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gxed/hashland/keccakpg v0.0.1 h1:wrk3uMNaMxbXiHibbPO4S0ymqJMm41WiudyFSs7UnsU=
|
||||
github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU=
|
||||
github.com/gxed/hashland/murmur3 v0.0.1 h1:SheiaIt0sda5K+8FLz952/1iWS9zrnKsEJaOJu4ZbSc=
|
||||
github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ipfs/go-cid v0.0.1 h1:GBjWPktLnNyX0JiQCNFpUuUSoMw5KMyqrsejHYlILBE=
|
||||
github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8 h1:bspPhN+oKYFk5fcGNuQzp6IGzYQSenLEgH3s6jkXrWw=
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/libp2p/go-flow-metrics v0.0.1 h1:0gxuFd2GuK7IIP5pKljLwps6TvcuYgvG7Atqi3INF5s=
|
||||
github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8=
|
||||
github.com/libp2p/go-libp2p-core v0.0.0-20190508144953-ed42958fbb3a/go.mod h1:U1aaQsPt97g1BVcfdLEnzHdSNbaSg3Gh5eKgJ/KunKg=
|
||||
github.com/libp2p/go-libp2p-testing v0.0.0-20190508172549-1a0da3de1915 h1:ut3tAnP5cOMPZbUHEEhKnvJaJGorIUOdqz+UBC45T/g=
|
||||
github.com/libp2p/go-libp2p-testing v0.0.0-20190508172549-1a0da3de1915/go.mod h1:1FFj6xmoClrgHPBxgUgSSYWYxJ6CdkoP+s+sFf8nVgo=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
|
||||
github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16 h1:5W7KhL8HVF3XCFOweFD3BNESdnO8ewyYTFT2R+/b8FQ=
|
||||
github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
|
||||
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
|
||||
github.com/mr-tron/base58 v1.1.1 h1:OJIdWOWYe2l5PQNgimGtuwHY8nDskvJ5vvs//YnzRLs=
|
||||
github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
|
||||
github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI=
|
||||
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
|
||||
github.com/multiformats/go-multiaddr v0.0.2 h1:RBysRCv5rv3FWlhKWKoXv8tnsCUpEpIZpCmqAGZos2s=
|
||||
github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
|
||||
github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA=
|
||||
github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs=
|
||||
github.com/multiformats/go-multihash v0.0.1 h1:HHwN1K12I+XllBCrqKnhX949Orn4oawPkegHMu2vDqQ=
|
||||
github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a h1:/eS3yfGjQKG+9kayBkj0ip1BGhq6zJ3eaVksphxAaek=
|
||||
github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0=
|
||||
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU=
|
||||
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b h1:+/WWzjwW6gidDJnMKWLKLX1gxn7irUTF1fLpQovfQ5M=
|
||||
golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190219092855-153ac476189d h1:Z0Ahzd7HltpJtjAHHxX8QFP3j1yYgiuvjbjRzDj/KH0=
|
||||
golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
42
helpers/match.go
Normal file
42
helpers/match.go
Normal file
@ -0,0 +1,42 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
)
|
||||
|
||||
// MultistreamSemverMatcher returns a matcher function for a given base protocol.
|
||||
// The matcher function will return a boolean indicating whether a protocol ID
|
||||
// matches the base protocol. A given protocol ID matches the base protocol if
|
||||
// the IDs are the same and if the semantic version of the base protocol is the
|
||||
// same or higher than that of the protocol ID provided.
|
||||
// TODO
|
||||
func MultistreamSemverMatcher(base protocol.ID) (func(string) bool, error) {
|
||||
parts := strings.Split(string(base), "/")
|
||||
vers, err := semver.NewVersion(parts[len(parts)-1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(check string) bool {
|
||||
chparts := strings.Split(check, "/")
|
||||
if len(chparts) != len(parts) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, v := range chparts[:len(chparts)-1] {
|
||||
if parts[i] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
chvers, err := semver.NewVersion(chparts[len(chparts)-1])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return vers.Major == chvers.Major && vers.Minor >= chvers.Minor
|
||||
}, nil
|
||||
}
|
33
helpers/match_test.go
Normal file
33
helpers/match_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSemverMatching(t *testing.T) {
|
||||
m, err := MultistreamSemverMatcher("/testing/4.3.5")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cases := map[string]bool{
|
||||
"/testing/4.3.0": true,
|
||||
"/testing/4.3.7": true,
|
||||
"/testing/4.3.5": true,
|
||||
"/testing/4.2.7": true,
|
||||
"/testing/4.0.0": true,
|
||||
"/testing/5.0.0": false,
|
||||
"/cars/dogs/4.3.5": false,
|
||||
"/foo/1.0.0": false,
|
||||
"": false,
|
||||
"dogs": false,
|
||||
"/foo": false,
|
||||
"/foo/1.1.1.1": false,
|
||||
}
|
||||
|
||||
for p, ok := range cases {
|
||||
if m(p) != ok {
|
||||
t.Fatalf("expected %s to be %t", p, ok)
|
||||
}
|
||||
}
|
||||
}
|
56
helpers/stream.go
Normal file
56
helpers/stream.go
Normal file
@ -0,0 +1,56 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
)
|
||||
|
||||
// EOFTimeout is the maximum amount of time to wait to successfully observe an
|
||||
// EOF on the stream. Defaults to 60 seconds.
|
||||
var EOFTimeout = time.Second * 60
|
||||
|
||||
// ErrExpectedEOF is returned when we read data while expecting an EOF.
|
||||
var ErrExpectedEOF = errors.New("read data when expecting EOF")
|
||||
|
||||
// FullClose closes the stream and waits to read an EOF from the other side.
|
||||
//
|
||||
// * If it reads any data *before* the EOF, it resets the stream.
|
||||
// * If it doesn't read an EOF within EOFTimeout, it resets the stream.
|
||||
//
|
||||
// You'll likely want to invoke this as `go FullClose(stream)` to close the
|
||||
// stream in the background.
|
||||
func FullClose(s network.Stream) error {
|
||||
if err := s.Close(); err != nil {
|
||||
s.Reset()
|
||||
return err
|
||||
}
|
||||
return AwaitEOF(s)
|
||||
}
|
||||
|
||||
// AwaitEOF waits for an EOF on the given stream, returning an error if that
|
||||
// fails. It waits at most EOFTimeout (defaults to 1 minute) after which it
|
||||
// resets the stream.
|
||||
func AwaitEOF(s network.Stream) error {
|
||||
// So we don't wait forever
|
||||
s.SetDeadline(time.Now().Add(EOFTimeout))
|
||||
|
||||
// We *have* to observe the EOF. Otherwise, we leak the stream.
|
||||
// Now, technically, we should do this *before*
|
||||
// returning from SendMessage as the message
|
||||
// hasn't really been sent yet until we see the
|
||||
// EOF but we don't actually *know* what
|
||||
// protocol the other side is speaking.
|
||||
n, err := s.Read([]byte{0})
|
||||
if n > 0 || err == nil {
|
||||
s.Reset()
|
||||
return ErrExpectedEOF
|
||||
}
|
||||
if err != io.EOF {
|
||||
s.Reset()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
151
helpers/stream_test.go
Normal file
151
helpers/stream_test.go
Normal file
@ -0,0 +1,151 @@
|
||||
package helpers_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/helpers"
|
||||
network "github.com/libp2p/go-libp2p-core/network"
|
||||
)
|
||||
|
||||
var errCloseFailed = errors.New("close failed")
|
||||
var errWriteFailed = errors.New("write failed")
|
||||
var errReadFailed = errors.New("read failed")
|
||||
|
||||
type stream struct {
|
||||
network.Stream
|
||||
|
||||
data []byte
|
||||
|
||||
failRead, failWrite, failClose bool
|
||||
|
||||
reset bool
|
||||
}
|
||||
|
||||
func (s *stream) Reset() error {
|
||||
s.reset = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stream) Close() error {
|
||||
if s.failClose {
|
||||
return errCloseFailed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stream) SetDeadline(t time.Time) error {
|
||||
s.SetReadDeadline(t)
|
||||
s.SetWriteDeadline(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stream) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stream) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stream) Write(b []byte) (int, error) {
|
||||
if s.failWrite {
|
||||
return 0, errWriteFailed
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (s *stream) Read(b []byte) (int, error) {
|
||||
var err error
|
||||
if s.failRead {
|
||||
err = errReadFailed
|
||||
}
|
||||
if len(s.data) == 0 {
|
||||
if err == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
n := copy(b, s.data)
|
||||
s.data = s.data[n:]
|
||||
return n, err
|
||||
}
|
||||
|
||||
func TestNormal(t *testing.T) {
|
||||
var s stream
|
||||
if err := helpers.FullClose(&s); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s.reset {
|
||||
t.Fatal("stream should not have been reset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailRead(t *testing.T) {
|
||||
var s stream
|
||||
s.failRead = true
|
||||
if helpers.FullClose(&s) != errReadFailed {
|
||||
t.Fatal("expected read to fail with:", errReadFailed)
|
||||
}
|
||||
if !s.reset {
|
||||
t.Fatal("expected stream to be reset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailClose(t *testing.T) {
|
||||
var s stream
|
||||
s.failClose = true
|
||||
if helpers.FullClose(&s) != errCloseFailed {
|
||||
t.Fatal("expected close to fail with:", errCloseFailed)
|
||||
}
|
||||
if !s.reset {
|
||||
t.Fatal("expected stream to be reset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailWrite(t *testing.T) {
|
||||
var s stream
|
||||
s.failWrite = true
|
||||
if err := helpers.FullClose(&s); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s.reset {
|
||||
t.Fatal("stream should not have been reset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDataOne(t *testing.T) {
|
||||
var s stream
|
||||
s.data = []byte{0}
|
||||
if err := helpers.FullClose(&s); err != helpers.ErrExpectedEOF {
|
||||
t.Fatal("expected:", helpers.ErrExpectedEOF)
|
||||
}
|
||||
if !s.reset {
|
||||
t.Fatal("stream have been reset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDataMany(t *testing.T) {
|
||||
var s stream
|
||||
s.data = []byte{0, 1, 2, 3}
|
||||
if err := helpers.FullClose(&s); err != helpers.ErrExpectedEOF {
|
||||
t.Fatal("expected:", helpers.ErrExpectedEOF)
|
||||
}
|
||||
if !s.reset {
|
||||
t.Fatal("stream have been reset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDataError(t *testing.T) {
|
||||
var s stream
|
||||
s.data = []byte{0, 1, 2, 3}
|
||||
s.failRead = true
|
||||
if err := helpers.FullClose(&s); err != helpers.ErrExpectedEOF {
|
||||
t.Fatal("expected:", helpers.ErrExpectedEOF)
|
||||
}
|
||||
if !s.reset {
|
||||
t.Fatal("stream have been reset")
|
||||
}
|
||||
}
|
11
host/helpers.go
Normal file
11
host/helpers.go
Normal file
@ -0,0 +1,11 @@
|
||||
package host
|
||||
|
||||
import "github.com/libp2p/go-libp2p-core/peer"
|
||||
|
||||
// InfoFromHost returns a peer.AddrInfo struct with the Host's ID and all of its Addrs.
|
||||
func InfoFromHost(h Host) *peer.AddrInfo {
|
||||
return &peer.AddrInfo{
|
||||
ID: h.ID(),
|
||||
Addrs: h.Addrs(),
|
||||
}
|
||||
}
|
71
host/host.go
Normal file
71
host/host.go
Normal file
@ -0,0 +1,71 @@
|
||||
// Package host provides the core Host interface for libp2p.
|
||||
//
|
||||
// Host represents a single libp2p node in a peer-to-peer network.
|
||||
package host
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/connmgr"
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/libp2p/go-libp2p-core/peerstore"
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
// Host is an object participating in a p2p network, which
|
||||
// implements protocols or provides services. It handles
|
||||
// requests like a Server, and issues requests like a Client.
|
||||
// It is called Host because it is both Server and Client (and Peer
|
||||
// may be confusing).
|
||||
type Host interface {
|
||||
// ID returns the (local) peer.ID associated with this Host
|
||||
ID() peer.ID
|
||||
|
||||
// Peerstore returns the Host's repository of Peer Addresses and Keys.
|
||||
Peerstore() peerstore.Peerstore
|
||||
|
||||
// Returns the listen addresses of the Host
|
||||
Addrs() []ma.Multiaddr
|
||||
|
||||
// Networks returns the Network interface of the Host
|
||||
Network() network.Network
|
||||
|
||||
// Mux returns the Mux multiplexing incoming streams to protocol handlers
|
||||
Mux() protocol.Switch
|
||||
|
||||
// Connect ensures there is a connection between this host and the peer with
|
||||
// given peer.ID. Connect will absorb the addresses in pi into its internal
|
||||
// peerstore. If there is not an active connection, Connect will issue a
|
||||
// h.Network.Dial, and block until a connection is open, or an error is
|
||||
// returned. // TODO: Relay + NAT.
|
||||
Connect(ctx context.Context, pi peer.AddrInfo) error
|
||||
|
||||
// SetStreamHandler sets the protocol handler on the Host's Mux.
|
||||
// This is equivalent to:
|
||||
// host.Mux().SetHandler(proto, handler)
|
||||
// (Threadsafe)
|
||||
SetStreamHandler(pid protocol.ID, handler network.StreamHandler)
|
||||
|
||||
// SetStreamHandlerMatch sets the protocol handler on the Host's Mux
|
||||
// using a matching function for protocol selection.
|
||||
SetStreamHandlerMatch(protocol.ID, func(string) bool, network.StreamHandler)
|
||||
|
||||
// RemoveStreamHandler removes a handler on the mux that was set by
|
||||
// SetStreamHandler
|
||||
RemoveStreamHandler(pid protocol.ID)
|
||||
|
||||
// NewStream opens a new stream to given peer p, and writes a p2p/protocol
|
||||
// header with given ProtocolID. If there is no connection to p, attempts
|
||||
// to create one. If ProtocolID is "", writes no header.
|
||||
// (Threadsafe)
|
||||
NewStream(ctx context.Context, p peer.ID, pids ...protocol.ID) (network.Stream, error)
|
||||
|
||||
// Close shuts down the host, its Network, and services.
|
||||
Close() error
|
||||
|
||||
// ConnManager returns this hosts connection manager
|
||||
ConnManager() connmgr.ConnManager
|
||||
}
|
153
metrics/bandwidth.go
Normal file
153
metrics/bandwidth.go
Normal file
@ -0,0 +1,153 @@
|
||||
// Package metrics provides metrics collection and reporting interfaces for libp2p.
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/libp2p/go-flow-metrics"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
)
|
||||
|
||||
// BandwidthCounter tracks incoming and outgoing data transferred by the local peer.
|
||||
// Metrics are available for total bandwidth across all peers / protocols, as well
|
||||
// as segmented by remote peer ID and protocol ID.
|
||||
type BandwidthCounter struct {
|
||||
totalIn flow.Meter
|
||||
totalOut flow.Meter
|
||||
|
||||
protocolIn flow.MeterRegistry
|
||||
protocolOut flow.MeterRegistry
|
||||
|
||||
peerIn flow.MeterRegistry
|
||||
peerOut flow.MeterRegistry
|
||||
}
|
||||
|
||||
// NewBandwidthCounter creates a new BandwidthCounter.
|
||||
func NewBandwidthCounter() *BandwidthCounter {
|
||||
return new(BandwidthCounter)
|
||||
}
|
||||
|
||||
// LogSentMessage records the size of an outgoing message
|
||||
// without associating the bandwidth to a specific peer or protocol.
|
||||
func (bwc *BandwidthCounter) LogSentMessage(size int64) {
|
||||
bwc.totalOut.Mark(uint64(size))
|
||||
}
|
||||
|
||||
// LogRecvMessage records the size of an incoming message
|
||||
// without associating the bandwith to a specific peer or protocol.
|
||||
func (bwc *BandwidthCounter) LogRecvMessage(size int64) {
|
||||
bwc.totalIn.Mark(uint64(size))
|
||||
}
|
||||
|
||||
// LogSentMessageStream records the size of an outgoing message over a single logical stream.
|
||||
// Bandwidth is associated with the given protocol.ID and peer.ID.
|
||||
func (bwc *BandwidthCounter) LogSentMessageStream(size int64, proto protocol.ID, p peer.ID) {
|
||||
bwc.protocolOut.Get(string(proto)).Mark(uint64(size))
|
||||
bwc.peerOut.Get(string(p)).Mark(uint64(size))
|
||||
}
|
||||
|
||||
// LogRecvMessageStream records the size of an incoming message over a single logical stream.
|
||||
// Bandwidth is associated with the given protocol.ID and peer.ID.
|
||||
func (bwc *BandwidthCounter) LogRecvMessageStream(size int64, proto protocol.ID, p peer.ID) {
|
||||
bwc.protocolIn.Get(string(proto)).Mark(uint64(size))
|
||||
bwc.peerIn.Get(string(p)).Mark(uint64(size))
|
||||
}
|
||||
|
||||
// GetBandwidthForPeer returns a Stats struct with bandwidth metrics associated with the given peer.ID.
|
||||
// The metrics returned include all traffic sent / received for the peer, regardless of protocol.
|
||||
func (bwc *BandwidthCounter) GetBandwidthForPeer(p peer.ID) (out Stats) {
|
||||
inSnap := bwc.peerIn.Get(string(p)).Snapshot()
|
||||
outSnap := bwc.peerOut.Get(string(p)).Snapshot()
|
||||
|
||||
return Stats{
|
||||
TotalIn: int64(inSnap.Total),
|
||||
TotalOut: int64(outSnap.Total),
|
||||
RateIn: inSnap.Rate,
|
||||
RateOut: outSnap.Rate,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBandwidthForProtocol returns a Stats struct with bandwidth metrics associated with the given protocol.ID.
|
||||
// The metrics returned include all traffic sent / recieved for the protocol, regardless of which peers were
|
||||
// involved.
|
||||
func (bwc *BandwidthCounter) GetBandwidthForProtocol(proto protocol.ID) (out Stats) {
|
||||
inSnap := bwc.protocolIn.Get(string(proto)).Snapshot()
|
||||
outSnap := bwc.protocolOut.Get(string(proto)).Snapshot()
|
||||
|
||||
return Stats{
|
||||
TotalIn: int64(inSnap.Total),
|
||||
TotalOut: int64(outSnap.Total),
|
||||
RateIn: inSnap.Rate,
|
||||
RateOut: outSnap.Rate,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBandwidthTotals returns a Stats struct with bandwidth metrics for all data sent / recieved by the
|
||||
// local peer, regardless of protocol or remote peer IDs.
|
||||
func (bwc *BandwidthCounter) GetBandwidthTotals() (out Stats) {
|
||||
inSnap := bwc.totalIn.Snapshot()
|
||||
outSnap := bwc.totalOut.Snapshot()
|
||||
|
||||
return Stats{
|
||||
TotalIn: int64(inSnap.Total),
|
||||
TotalOut: int64(outSnap.Total),
|
||||
RateIn: inSnap.Rate,
|
||||
RateOut: outSnap.Rate,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBandwidthByPeer returns a map of all remembered peers and the bandwidth
|
||||
// metrics with respect to each. This method may be very expensive.
|
||||
func (bwc *BandwidthCounter) GetBandwidthByPeer() map[peer.ID]Stats {
|
||||
peers := make(map[peer.ID]Stats)
|
||||
|
||||
bwc.peerIn.ForEach(func(p string, meter *flow.Meter) {
|
||||
id := peer.ID(p)
|
||||
snap := meter.Snapshot()
|
||||
|
||||
stat := peers[id]
|
||||
stat.TotalIn = int64(snap.Total)
|
||||
stat.RateIn = snap.Rate
|
||||
peers[id] = stat
|
||||
})
|
||||
|
||||
bwc.peerOut.ForEach(func(p string, meter *flow.Meter) {
|
||||
id := peer.ID(p)
|
||||
snap := meter.Snapshot()
|
||||
|
||||
stat := peers[id]
|
||||
stat.TotalOut = int64(snap.Total)
|
||||
stat.RateOut = snap.Rate
|
||||
peers[id] = stat
|
||||
})
|
||||
|
||||
return peers
|
||||
}
|
||||
|
||||
// GetBandwidthByProtocol returns a map of all remembered protocols and
|
||||
// the bandwidth metrics with respect to each. This method may be moderately
|
||||
// expensive.
|
||||
func (bwc *BandwidthCounter) GetBandwidthByProtocol() map[protocol.ID]Stats {
|
||||
protocols := make(map[protocol.ID]Stats)
|
||||
|
||||
bwc.protocolIn.ForEach(func(p string, meter *flow.Meter) {
|
||||
id := protocol.ID(p)
|
||||
snap := meter.Snapshot()
|
||||
|
||||
stat := protocols[id]
|
||||
stat.TotalIn = int64(snap.Total)
|
||||
stat.RateIn = snap.Rate
|
||||
protocols[id] = stat
|
||||
})
|
||||
|
||||
bwc.protocolOut.ForEach(func(p string, meter *flow.Meter) {
|
||||
id := protocol.ID(p)
|
||||
snap := meter.Snapshot()
|
||||
|
||||
stat := protocols[id]
|
||||
stat.TotalOut = int64(snap.Total)
|
||||
stat.RateOut = snap.Rate
|
||||
protocols[id] = stat
|
||||
})
|
||||
|
||||
return protocols
|
||||
}
|
158
metrics/bandwidth_test.go
Normal file
158
metrics/bandwidth_test.go
Normal file
@ -0,0 +1,158 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
)
|
||||
|
||||
func BenchmarkBandwidthCounter(b *testing.B) {
|
||||
b.StopTimer()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
bwc := NewBandwidthCounter()
|
||||
round(bwc, b)
|
||||
}
|
||||
}
|
||||
|
||||
func round(bwc *BandwidthCounter, b *testing.B) {
|
||||
start := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(10000)
|
||||
for i := 0; i < 1000; i++ {
|
||||
p := peer.ID(fmt.Sprintf("peer-%d", i))
|
||||
for j := 0; j < 10; j++ {
|
||||
proto := protocol.ID(fmt.Sprintf("bitswap-%d", j))
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
<-start
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
bwc.LogSentMessage(100)
|
||||
bwc.LogSentMessageStream(100, proto, p)
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
close(start)
|
||||
wg.Wait()
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
// Allow 7% errors for bw calculations.
|
||||
const acceptableError = 0.07
|
||||
|
||||
func TestBandwidthCounter(t *testing.T) {
|
||||
bwc := NewBandwidthCounter()
|
||||
start := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(200)
|
||||
for i := 0; i < 100; i++ {
|
||||
p := peer.ID(fmt.Sprintf("peer-%d", i))
|
||||
for j := 0; j < 2; j++ {
|
||||
proto := protocol.ID(fmt.Sprintf("proto-%d", j))
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
<-start
|
||||
|
||||
t := time.NewTicker(100 * time.Millisecond)
|
||||
defer t.Stop()
|
||||
|
||||
for i := 0; i < 40; i++ {
|
||||
bwc.LogSentMessage(100)
|
||||
bwc.LogRecvMessage(50)
|
||||
bwc.LogSentMessageStream(100, proto, p)
|
||||
bwc.LogRecvMessageStream(50, proto, p)
|
||||
<-t.C
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
assertProtocols := func(check func(Stats)) {
|
||||
byProtocol := bwc.GetBandwidthByProtocol()
|
||||
if len(byProtocol) != 2 {
|
||||
t.Errorf("expected 2 protocols, got %d", len(byProtocol))
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
p := protocol.ID(fmt.Sprintf("proto-%d", i))
|
||||
for _, stats := range [...]Stats{bwc.GetBandwidthForProtocol(p), byProtocol[p]} {
|
||||
check(stats)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertPeers := func(check func(Stats)) {
|
||||
byPeer := bwc.GetBandwidthByPeer()
|
||||
if len(byPeer) != 100 {
|
||||
t.Errorf("expected 100 peers, got %d", len(byPeer))
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
p := peer.ID(fmt.Sprintf("peer-%d", i))
|
||||
for _, stats := range [...]Stats{bwc.GetBandwidthForPeer(p), byPeer[p]} {
|
||||
check(stats)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(start)
|
||||
time.Sleep(2*time.Second + 100*time.Millisecond)
|
||||
|
||||
assertPeers(func(stats Stats) {
|
||||
assertApproxEq(t, 2000, stats.RateOut)
|
||||
assertApproxEq(t, 1000, stats.RateIn)
|
||||
})
|
||||
|
||||
assertProtocols(func(stats Stats) {
|
||||
assertApproxEq(t, 100000, stats.RateOut)
|
||||
assertApproxEq(t, 50000, stats.RateIn)
|
||||
})
|
||||
|
||||
{
|
||||
stats := bwc.GetBandwidthTotals()
|
||||
assertApproxEq(t, 200000, stats.RateOut)
|
||||
assertApproxEq(t, 100000, stats.RateIn)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
assertPeers(func(stats Stats) {
|
||||
assertEq(t, 8000, stats.TotalOut)
|
||||
assertEq(t, 4000, stats.TotalIn)
|
||||
})
|
||||
|
||||
assertProtocols(func(stats Stats) {
|
||||
assertEq(t, 400000, stats.TotalOut)
|
||||
assertEq(t, 200000, stats.TotalIn)
|
||||
})
|
||||
|
||||
{
|
||||
stats := bwc.GetBandwidthTotals()
|
||||
assertEq(t, 800000, stats.TotalOut)
|
||||
assertEq(t, 400000, stats.TotalIn)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEq(t *testing.T, expected, actual int64) {
|
||||
if expected != actual {
|
||||
t.Errorf("expected %d, got %d", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func assertApproxEq(t *testing.T, expected, actual float64) {
|
||||
t.Helper()
|
||||
margin := expected * acceptableError
|
||||
if !(math.Abs(expected-actual) <= margin) {
|
||||
t.Errorf("expected %f (±%f), got %f", expected, margin, actual)
|
||||
}
|
||||
}
|
31
metrics/reporter.go
Normal file
31
metrics/reporter.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Package metrics provides metrics collection and reporting interfaces for libp2p.
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
)
|
||||
|
||||
// Stats represents a point-in-time snapshot of bandwidth metrics.
|
||||
//
|
||||
// The TotalIn and TotalOut fields record cumulative bytes sent / received.
|
||||
// The RateIn and RateOut fields record bytes sent / received per second.
|
||||
type Stats struct {
|
||||
TotalIn int64
|
||||
TotalOut int64
|
||||
RateIn float64
|
||||
RateOut float64
|
||||
}
|
||||
|
||||
// Reporter provides methods for logging and retrieving metrics.
|
||||
type Reporter interface {
|
||||
LogSentMessage(int64)
|
||||
LogRecvMessage(int64)
|
||||
LogSentMessageStream(int64, protocol.ID, peer.ID)
|
||||
LogRecvMessageStream(int64, protocol.ID, peer.ID)
|
||||
GetBandwidthForPeer(peer.ID) Stats
|
||||
GetBandwidthForProtocol(protocol.ID) Stats
|
||||
GetBandwidthTotals() Stats
|
||||
GetBandwidthByPeer() map[peer.ID]Stats
|
||||
GetBandwidthByProtocol() map[protocol.ID]Stats
|
||||
}
|
70
mux/mux.go
Normal file
70
mux/mux.go
Normal file
@ -0,0 +1,70 @@
|
||||
// Package mux provides stream multiplexing interfaces for libp2p.
|
||||
//
|
||||
// For a conceptual overview of stream multiplexing in libp2p, see
|
||||
// https://docs.libp2p.io/concepts/stream-multiplexing/
|
||||
package mux
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrReset is returned when reading or writing on a reset stream.
|
||||
var ErrReset = errors.New("stream reset")
|
||||
|
||||
// Stream is a bidirectional io pipe within a connection.
|
||||
type MuxedStream interface {
|
||||
io.Reader
|
||||
io.Writer
|
||||
|
||||
// Close closes the stream for writing. Reading will still work (that
|
||||
// is, the remote side can still write).
|
||||
io.Closer
|
||||
|
||||
// Reset closes both ends of the stream. Use this to tell the remote
|
||||
// side to hang up and go away.
|
||||
Reset() error
|
||||
|
||||
SetDeadline(time.Time) error
|
||||
SetReadDeadline(time.Time) error
|
||||
SetWriteDeadline(time.Time) error
|
||||
}
|
||||
|
||||
// NoopHandler do nothing. Resets streams as soon as they are opened.
|
||||
var NoopHandler = func(s MuxedStream) { s.Reset() }
|
||||
|
||||
// MuxedConn represents a connection to a remote peer that has been
|
||||
// extended to support stream multiplexing.
|
||||
//
|
||||
// A MuxedConn allows a single net.Conn connection to carry many logically
|
||||
// independent bidirectional streams of binary data.
|
||||
//
|
||||
// Together with network.ConnSecurity, MuxedConn is a component of the
|
||||
// transport.CapableConn interface, which represents a "raw" network
|
||||
// connection that has been "upgraded" to support the libp2p capabilities
|
||||
// of secure communication and stream multiplexing.
|
||||
type MuxedConn interface {
|
||||
// Close closes the stream muxer and the the underlying net.Conn.
|
||||
io.Closer
|
||||
|
||||
// IsClosed returns whether a connection is fully closed, so it can
|
||||
// be garbage collected.
|
||||
IsClosed() bool
|
||||
|
||||
// OpenStream creates a new stream.
|
||||
OpenStream() (MuxedStream, error)
|
||||
|
||||
// AcceptStream accepts a stream opened by the other side.
|
||||
AcceptStream() (MuxedStream, error)
|
||||
}
|
||||
|
||||
// Multiplexer wraps a net.Conn with a stream multiplexing
|
||||
// implementation and returns a MuxedConn that supports opening
|
||||
// multiple streams over the underlying net.Conn
|
||||
type Multiplexer interface {
|
||||
|
||||
// NewConn constructs a new connection
|
||||
NewConn(c net.Conn, isServer bool) (MuxedConn, error)
|
||||
}
|
58
network/conn.go
Normal file
58
network/conn.go
Normal file
@ -0,0 +1,58 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
ic "github.com/libp2p/go-libp2p-core/crypto"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
// Conn is a connection to a remote peer. It multiplexes streams.
|
||||
// Usually there is no need to use a Conn directly, but it may
|
||||
// be useful to get information about the peer on the other side:
|
||||
// stream.Conn().RemotePeer()
|
||||
type Conn interface {
|
||||
io.Closer
|
||||
|
||||
ConnSecurity
|
||||
ConnMultiaddrs
|
||||
|
||||
// NewStream constructs a new Stream over this conn.
|
||||
NewStream() (Stream, error)
|
||||
|
||||
// GetStreams returns all open streams over this conn.
|
||||
GetStreams() []Stream
|
||||
|
||||
// Stat stores metadata pertaining to this conn.
|
||||
Stat() Stat
|
||||
}
|
||||
|
||||
// ConnSecurity is the interface that one can mix into a connection interface to
|
||||
// give it the security methods.
|
||||
type ConnSecurity interface {
|
||||
// LocalPeer returns our peer ID
|
||||
LocalPeer() peer.ID
|
||||
|
||||
// LocalPrivateKey returns our private key
|
||||
LocalPrivateKey() ic.PrivKey
|
||||
|
||||
// RemotePeer returns the peer ID of the remote peer.
|
||||
RemotePeer() peer.ID
|
||||
|
||||
// RemotePublicKey returns the public key of the remote peer.
|
||||
RemotePublicKey() ic.PubKey
|
||||
}
|
||||
|
||||
// ConnMultiaddrs is an interface mixin for connection types that provide multiaddr
|
||||
// addresses for the endpoints.
|
||||
type ConnMultiaddrs interface {
|
||||
// LocalMultiaddr returns the local Multiaddr associated
|
||||
// with this connection
|
||||
LocalMultiaddr() ma.Multiaddr
|
||||
|
||||
// RemoteMultiaddr returns the remote Multiaddr associated
|
||||
// with this connection
|
||||
RemoteMultiaddr() ma.Multiaddr
|
||||
}
|
48
network/context.go
Normal file
48
network/context.go
Normal file
@ -0,0 +1,48 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DialPeerTimeout is the default timeout for a single call to `DialPeer`. When
|
||||
// there are multiple concurrent calls to `DialPeer`, this timeout will apply to
|
||||
// each independently.
|
||||
var DialPeerTimeout = 60 * time.Second
|
||||
|
||||
type noDialCtxKey struct{}
|
||||
type dialPeerTimeoutCtxKey struct{}
|
||||
|
||||
var noDial = noDialCtxKey{}
|
||||
|
||||
// WithNoDial constructs a new context with an option that instructs the network
|
||||
// to not attempt a new dial when opening a stream.
|
||||
func WithNoDial(ctx context.Context, reason string) context.Context {
|
||||
return context.WithValue(ctx, noDial, reason)
|
||||
}
|
||||
|
||||
// GetNoDial returns true if the no dial option is set in the context.
|
||||
func GetNoDial(ctx context.Context) (nodial bool, reason string) {
|
||||
v := ctx.Value(noDial)
|
||||
if v != nil {
|
||||
return true, v.(string)
|
||||
}
|
||||
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// GetDialPeerTimeout returns the current DialPeer timeout (or the default).
|
||||
func GetDialPeerTimeout(ctx context.Context) time.Duration {
|
||||
if to, ok := ctx.Value(dialPeerTimeoutCtxKey{}).(time.Duration); ok {
|
||||
return to
|
||||
}
|
||||
return DialPeerTimeout
|
||||
}
|
||||
|
||||
// WithDialPeerTimeout returns a new context with the DialPeer timeout applied.
|
||||
//
|
||||
// This timeout overrides the default DialPeerTimeout and applies per-dial
|
||||
// independently.
|
||||
func WithDialPeerTimeout(ctx context.Context, timeout time.Duration) context.Context {
|
||||
return context.WithValue(ctx, dialPeerTimeoutCtxKey{}, timeout)
|
||||
}
|
40
network/context_test.go
Normal file
40
network/context_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDefaultTimeout(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
dur := GetDialPeerTimeout(ctx)
|
||||
if dur != DialPeerTimeout {
|
||||
t.Fatal("expected default peer timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonDefaultTimeout(t *testing.T) {
|
||||
customTimeout := time.Duration(1)
|
||||
ctx := context.WithValue(
|
||||
context.Background(),
|
||||
dialPeerTimeoutCtxKey{},
|
||||
customTimeout,
|
||||
)
|
||||
dur := GetDialPeerTimeout(ctx)
|
||||
if dur != customTimeout {
|
||||
t.Fatal("peer timeout doesn't match set timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingTimeout(t *testing.T) {
|
||||
customTimeout := time.Duration(1)
|
||||
ctx := WithDialPeerTimeout(
|
||||
context.Background(),
|
||||
customTimeout,
|
||||
)
|
||||
dur := GetDialPeerTimeout(ctx)
|
||||
if dur != customTimeout {
|
||||
t.Fatal("peer timeout doesn't match set timeout")
|
||||
}
|
||||
}
|
10
network/errors.go
Normal file
10
network/errors.go
Normal file
@ -0,0 +1,10 @@
|
||||
package network
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrNoRemoteAddrs is returned when there are no addresses associated with a peer during a dial.
|
||||
var ErrNoRemoteAddrs = errors.New("no remote addresses")
|
||||
|
||||
// ErrNoConn is returned when attempting to open a stream to a peer with the NoDial
|
||||
// option and no usable connection is available.
|
||||
var ErrNoConn = errors.New("no usable connection to peer")
|
138
network/network.go
Normal file
138
network/network.go
Normal file
@ -0,0 +1,138 @@
|
||||
// Package network provides core networking abstractions for libp2p.
|
||||
//
|
||||
// The network package provides the high-level Network interface for interacting
|
||||
// with other libp2p peers, which is the primary public API for initiating and
|
||||
// accepting connections to remote peers.
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/jbenet/goprocess"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/libp2p/go-libp2p-core/peerstore"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
// MessageSizeMax is a soft (recommended) maximum for network messages.
|
||||
// One can write more, as the interface is a stream. But it is useful
|
||||
// to bunch it up into multiple read/writes when the whole message is
|
||||
// a single, large serialized object.
|
||||
const MessageSizeMax = 1 << 22 // 4 MB
|
||||
|
||||
// Direction represents which peer in a stream initiated a connection.
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
// DirUnknown is the default direction.
|
||||
DirUnknown Direction = iota
|
||||
// DirInbound is for when the remote peer initiated a connection.
|
||||
DirInbound
|
||||
// DirOutbound is for when the local peer initiated a connection.
|
||||
DirOutbound
|
||||
)
|
||||
|
||||
// Connectedness signals the capacity for a connection with a given node.
|
||||
// It is used to signal to services and other peers whether a node is reachable.
|
||||
type Connectedness int
|
||||
|
||||
const (
|
||||
// NotConnected means no connection to peer, and no extra information (default)
|
||||
NotConnected Connectedness = iota
|
||||
|
||||
// Connected means has an open, live connection to peer
|
||||
Connected
|
||||
|
||||
// CanConnect means recently connected to peer, terminated gracefully
|
||||
CanConnect
|
||||
|
||||
// CannotConnect means recently attempted connecting but failed to connect.
|
||||
// (should signal "made effort, failed")
|
||||
CannotConnect
|
||||
)
|
||||
|
||||
// Stat stores metadata pertaining to a given Stream/Conn.
|
||||
type Stat struct {
|
||||
Direction Direction
|
||||
Extra map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// StreamHandler is the type of function used to listen for
|
||||
// streams opened by the remote side.
|
||||
type StreamHandler func(Stream)
|
||||
|
||||
// ConnHandler is the type of function used to listen for
|
||||
// connections opened by the remote side.
|
||||
type ConnHandler func(Conn)
|
||||
|
||||
// Network is the interface used to connect to the outside world.
|
||||
// It dials and listens for connections. it uses a Swarm to pool
|
||||
// connections (see swarm pkg, and peerstream.Swarm). Connections
|
||||
// are encrypted with a TLS-like protocol.
|
||||
type Network interface {
|
||||
Dialer
|
||||
io.Closer
|
||||
|
||||
// SetStreamHandler sets the handler for new streams opened by the
|
||||
// remote side. This operation is threadsafe.
|
||||
SetStreamHandler(StreamHandler)
|
||||
|
||||
// SetConnHandler sets the handler for new connections opened by the
|
||||
// remote side. This operation is threadsafe.
|
||||
SetConnHandler(ConnHandler)
|
||||
|
||||
// NewStream returns a new stream to given peer p.
|
||||
// If there is no connection to p, attempts to create one.
|
||||
NewStream(context.Context, peer.ID) (Stream, error)
|
||||
|
||||
// Listen tells the network to start listening on given multiaddrs.
|
||||
Listen(...ma.Multiaddr) error
|
||||
|
||||
// ListenAddresses returns a list of addresses at which this network listens.
|
||||
ListenAddresses() []ma.Multiaddr
|
||||
|
||||
// InterfaceListenAddresses returns a list of addresses at which this network
|
||||
// listens. It expands "any interface" addresses (/ip4/0.0.0.0, /ip6/::) to
|
||||
// use the known local interfaces.
|
||||
InterfaceListenAddresses() ([]ma.Multiaddr, error)
|
||||
|
||||
// Process returns the network's Process
|
||||
Process() goprocess.Process
|
||||
}
|
||||
|
||||
// Dialer represents a service that can dial out to peers
|
||||
// (this is usually just a Network, but other services may not need the whole
|
||||
// stack, and thus it becomes easier to mock)
|
||||
type Dialer interface {
|
||||
// Peerstore returns the internal peerstore
|
||||
// This is useful to tell the dialer about a new address for a peer.
|
||||
// Or use one of the public keys found out over the network.
|
||||
Peerstore() peerstore.Peerstore
|
||||
|
||||
// LocalPeer returns the local peer associated with this network
|
||||
LocalPeer() peer.ID
|
||||
|
||||
// DialPeer establishes a connection to a given peer
|
||||
DialPeer(context.Context, peer.ID) (Conn, error)
|
||||
|
||||
// ClosePeer closes the connection to a given peer
|
||||
ClosePeer(peer.ID) error
|
||||
|
||||
// Connectedness returns a state signaling connection capabilities
|
||||
Connectedness(peer.ID) Connectedness
|
||||
|
||||
// Peers returns the peers connected
|
||||
Peers() []peer.ID
|
||||
|
||||
// Conns returns the connections in this Netowrk
|
||||
Conns() []Conn
|
||||
|
||||
// ConnsToPeer returns the connections in this Netowrk for given peer.
|
||||
ConnsToPeer(p peer.ID) []Conn
|
||||
|
||||
// Notify/StopNotify register and unregister a notifiee for signals
|
||||
Notify(Notifiee)
|
||||
StopNotify(Notifiee)
|
||||
}
|
92
network/notifee.go
Normal file
92
network/notifee.go
Normal file
@ -0,0 +1,92 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
// Notifiee is an interface for an object wishing to receive
|
||||
// notifications from a Network.
|
||||
type Notifiee interface {
|
||||
Listen(Network, ma.Multiaddr) // called when network starts listening on an addr
|
||||
ListenClose(Network, ma.Multiaddr) // called when network stops listening on an addr
|
||||
Connected(Network, Conn) // called when a connection opened
|
||||
Disconnected(Network, Conn) // called when a connection closed
|
||||
OpenedStream(Network, Stream) // called when a stream opened
|
||||
ClosedStream(Network, Stream) // called when a stream closed
|
||||
|
||||
// TODO
|
||||
// PeerConnected(Network, peer.ID) // called when a peer connected
|
||||
// PeerDisconnected(Network, peer.ID) // called when a peer disconnected
|
||||
}
|
||||
|
||||
// NotifyBundle implements Notifiee by calling any of the functions set on it,
|
||||
// and nop'ing if they are unset. This is the easy way to register for
|
||||
// notifications.
|
||||
type NotifyBundle struct {
|
||||
ListenF func(Network, ma.Multiaddr)
|
||||
ListenCloseF func(Network, ma.Multiaddr)
|
||||
|
||||
ConnectedF func(Network, Conn)
|
||||
DisconnectedF func(Network, Conn)
|
||||
|
||||
OpenedStreamF func(Network, Stream)
|
||||
ClosedStreamF func(Network, Stream)
|
||||
}
|
||||
|
||||
var _ Notifiee = (*NotifyBundle)(nil)
|
||||
|
||||
// Listen calls ListenF if it is not null.
|
||||
func (nb *NotifyBundle) Listen(n Network, a ma.Multiaddr) {
|
||||
if nb.ListenF != nil {
|
||||
nb.ListenF(n, a)
|
||||
}
|
||||
}
|
||||
|
||||
// ListenClose calls ListenCloseF if it is not null.
|
||||
func (nb *NotifyBundle) ListenClose(n Network, a ma.Multiaddr) {
|
||||
if nb.ListenCloseF != nil {
|
||||
nb.ListenCloseF(n, a)
|
||||
}
|
||||
}
|
||||
|
||||
// Connected calls ConnectedF if it is not null.
|
||||
func (nb *NotifyBundle) Connected(n Network, c Conn) {
|
||||
if nb.ConnectedF != nil {
|
||||
nb.ConnectedF(n, c)
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnected calls DisconnectedF if it is not null.
|
||||
func (nb *NotifyBundle) Disconnected(n Network, c Conn) {
|
||||
if nb.DisconnectedF != nil {
|
||||
nb.DisconnectedF(n, c)
|
||||
}
|
||||
}
|
||||
|
||||
// OpenedStream calls OpenedStreamF if it is not null.
|
||||
func (nb *NotifyBundle) OpenedStream(n Network, s Stream) {
|
||||
if nb.OpenedStreamF != nil {
|
||||
nb.OpenedStreamF(n, s)
|
||||
}
|
||||
}
|
||||
|
||||
// ClosedStream calls ClosedStreamF if it is not null.
|
||||
func (nb *NotifyBundle) ClosedStream(n Network, s Stream) {
|
||||
if nb.ClosedStreamF != nil {
|
||||
nb.ClosedStreamF(n, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Global noop notifiee. Do not change.
|
||||
var GlobalNoopNotifiee = &NoopNotifiee{}
|
||||
|
||||
type NoopNotifiee struct{}
|
||||
|
||||
var _ Notifiee = (*NoopNotifiee)(nil)
|
||||
|
||||
func (nn *NoopNotifiee) Connected(n Network, c Conn) {}
|
||||
func (nn *NoopNotifiee) Disconnected(n Network, c Conn) {}
|
||||
func (nn *NoopNotifiee) Listen(n Network, addr ma.Multiaddr) {}
|
||||
func (nn *NoopNotifiee) ListenClose(n Network, addr ma.Multiaddr) {}
|
||||
func (nn *NoopNotifiee) OpenedStream(Network, Stream) {}
|
||||
func (nn *NoopNotifiee) ClosedStream(Network, Stream) {}
|
123
network/notifee_test.go
Normal file
123
network/notifee_test.go
Normal file
@ -0,0 +1,123 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
func TestListen(T *testing.T) {
|
||||
var notifee NotifyBundle
|
||||
addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234")
|
||||
if err != nil {
|
||||
T.Fatal("unexpected multiaddr error")
|
||||
}
|
||||
notifee.Listen(nil, addr)
|
||||
|
||||
called := false
|
||||
notifee.ListenF = func(Network, ma.Multiaddr) {
|
||||
called = true
|
||||
}
|
||||
if called {
|
||||
T.Fatal("called should be false")
|
||||
}
|
||||
|
||||
notifee.Listen(nil, addr)
|
||||
if !called {
|
||||
T.Fatal("Listen should have been called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenClose(T *testing.T) {
|
||||
var notifee NotifyBundle
|
||||
addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234")
|
||||
if err != nil {
|
||||
T.Fatal("unexpected multiaddr error")
|
||||
}
|
||||
notifee.ListenClose(nil, addr)
|
||||
|
||||
called := false
|
||||
notifee.ListenCloseF = func(Network, ma.Multiaddr) {
|
||||
called = true
|
||||
}
|
||||
if called {
|
||||
T.Fatal("called should be false")
|
||||
}
|
||||
|
||||
notifee.ListenClose(nil, addr)
|
||||
if !called {
|
||||
T.Fatal("ListenClose should have been called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnected(T *testing.T) {
|
||||
var notifee NotifyBundle
|
||||
notifee.Connected(nil, nil)
|
||||
|
||||
called := false
|
||||
notifee.ConnectedF = func(Network, Conn) {
|
||||
called = true
|
||||
}
|
||||
if called {
|
||||
T.Fatal("called should be false")
|
||||
}
|
||||
|
||||
notifee.Connected(nil, nil)
|
||||
if !called {
|
||||
T.Fatal("Connected should have been called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisconnected(T *testing.T) {
|
||||
var notifee NotifyBundle
|
||||
notifee.Disconnected(nil, nil)
|
||||
|
||||
called := false
|
||||
notifee.DisconnectedF = func(Network, Conn) {
|
||||
called = true
|
||||
}
|
||||
if called {
|
||||
T.Fatal("called should be false")
|
||||
}
|
||||
|
||||
notifee.Disconnected(nil, nil)
|
||||
if !called {
|
||||
T.Fatal("Disconnected should have been called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenedStream(T *testing.T) {
|
||||
var notifee NotifyBundle
|
||||
notifee.OpenedStream(nil, nil)
|
||||
|
||||
called := false
|
||||
notifee.OpenedStreamF = func(Network, Stream) {
|
||||
called = true
|
||||
}
|
||||
if called {
|
||||
T.Fatal("called should be false")
|
||||
}
|
||||
|
||||
notifee.OpenedStream(nil, nil)
|
||||
if !called {
|
||||
T.Fatal("OpenedStream should have been called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClosedStream(T *testing.T) {
|
||||
var notifee NotifyBundle
|
||||
notifee.ClosedStream(nil, nil)
|
||||
|
||||
called := false
|
||||
notifee.ClosedStreamF = func(Network, Stream) {
|
||||
called = true
|
||||
}
|
||||
if called {
|
||||
T.Fatal("called should be false")
|
||||
}
|
||||
|
||||
notifee.ClosedStream(nil, nil)
|
||||
if !called {
|
||||
T.Fatal("ClosedStream should have been called")
|
||||
}
|
||||
}
|
24
network/stream.go
Normal file
24
network/stream.go
Normal file
@ -0,0 +1,24 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"github.com/libp2p/go-libp2p-core/mux"
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
)
|
||||
|
||||
// Stream represents a bidirectional channel between two agents in
|
||||
// a libp2p network. "agent" is as granular as desired, potentially
|
||||
// being a "request -> reply" pair, or whole protocols.
|
||||
//
|
||||
// Streams are backed by a multiplexer underneath the hood.
|
||||
type Stream interface {
|
||||
mux.MuxedStream
|
||||
|
||||
Protocol() protocol.ID
|
||||
SetProtocol(id protocol.ID)
|
||||
|
||||
// Stat returns metadata pertaining to this stream.
|
||||
Stat() Stat
|
||||
|
||||
// Conn returns the connection this stream is part of.
|
||||
Conn() Conn
|
||||
}
|
80
peer/addrinfo.go
Normal file
80
peer/addrinfo.go
Normal file
@ -0,0 +1,80 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
// AddrInfo is a small struct used to pass around a peer with
|
||||
// a set of addresses (and later, keys?).
|
||||
type AddrInfo struct {
|
||||
ID ID
|
||||
Addrs []ma.Multiaddr
|
||||
}
|
||||
|
||||
var _ fmt.Stringer = AddrInfo{}
|
||||
|
||||
func (pi AddrInfo) String() string {
|
||||
return fmt.Sprintf("{%v: %v}", pi.ID, pi.Addrs)
|
||||
}
|
||||
|
||||
var ErrInvalidAddr = fmt.Errorf("invalid p2p multiaddr")
|
||||
|
||||
func AddrInfoFromP2pAddr(m ma.Multiaddr) (*AddrInfo, error) {
|
||||
if m == nil {
|
||||
return nil, ErrInvalidAddr
|
||||
}
|
||||
|
||||
// make sure it's a P2P addr
|
||||
parts := ma.Split(m)
|
||||
if len(parts) < 1 {
|
||||
return nil, ErrInvalidAddr
|
||||
}
|
||||
|
||||
// TODO(lgierth): we shouldn't assume /p2p is the last part
|
||||
p2ppart := parts[len(parts)-1]
|
||||
if p2ppart.Protocols()[0].Code != ma.P_P2P {
|
||||
return nil, ErrInvalidAddr
|
||||
}
|
||||
|
||||
// make sure the /p2p value parses as a peer.ID
|
||||
peerIdParts := strings.Split(p2ppart.String(), "/")
|
||||
peerIdStr := peerIdParts[len(peerIdParts)-1]
|
||||
id, err := IDB58Decode(peerIdStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// we might have received just an /p2p part, which means there's no addr.
|
||||
var addrs []ma.Multiaddr
|
||||
if len(parts) > 1 {
|
||||
addrs = append(addrs, ma.Join(parts[:len(parts)-1]...))
|
||||
}
|
||||
|
||||
return &AddrInfo{
|
||||
ID: id,
|
||||
Addrs: addrs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func AddrInfoToP2pAddrs(pi *AddrInfo) ([]ma.Multiaddr, error) {
|
||||
var addrs []ma.Multiaddr
|
||||
tpl := "/" + ma.ProtocolWithCode(ma.P_P2P).Name + "/"
|
||||
for _, addr := range pi.Addrs {
|
||||
p2paddr, err := ma.NewMultiaddr(tpl + IDB58Encode(pi.ID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrs = append(addrs, addr.Encapsulate(p2paddr))
|
||||
}
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
func (pi *AddrInfo) Loggable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"peerID": pi.ID.Pretty(),
|
||||
"addrs": pi.Addrs,
|
||||
}
|
||||
}
|
38
peer/addrinfo_serde.go
Normal file
38
peer/addrinfo_serde.go
Normal file
@ -0,0 +1,38 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
func (pi AddrInfo) MarshalJSON() ([]byte, error) {
|
||||
out := make(map[string]interface{})
|
||||
out["ID"] = pi.ID.Pretty()
|
||||
var addrs []string
|
||||
for _, a := range pi.Addrs {
|
||||
addrs = append(addrs, a.String())
|
||||
}
|
||||
out["Addrs"] = addrs
|
||||
return json.Marshal(out)
|
||||
}
|
||||
|
||||
func (pi *AddrInfo) UnmarshalJSON(b []byte) error {
|
||||
var data map[string]interface{}
|
||||
err := json.Unmarshal(b, &data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pid, err := IDB58Decode(data["ID"].(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pi.ID = pid
|
||||
addrs, ok := data["Addrs"].([]interface{})
|
||||
if ok {
|
||||
for _, a := range addrs {
|
||||
pi.Addrs = append(pi.Addrs, ma.StringCast(a.(string)))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
186
peer/peer.go
Normal file
186
peer/peer.go
Normal file
@ -0,0 +1,186 @@
|
||||
// Package peer implements an object used to represent peers in the libp2p network.
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
ic "github.com/libp2p/go-libp2p-core/crypto"
|
||||
b58 "github.com/mr-tron/base58/base58"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrEmptyPeerID is an error for empty peer ID.
|
||||
ErrEmptyPeerID = errors.New("empty peer ID")
|
||||
// ErrNoPublicKey is an error for peer IDs that don't embed public keys
|
||||
ErrNoPublicKey = errors.New("public key is not embedded in peer ID")
|
||||
)
|
||||
|
||||
// AdvancedEnableInlining enables automatically inlining keys shorter than
|
||||
// 42 bytes into the peer ID (using the "identity" multihash function).
|
||||
//
|
||||
// WARNING: This flag will likely be set to false in the future and eventually
|
||||
// be removed in favor of using a hash function specified by the key itself.
|
||||
// See: https://github.com/libp2p/specs/issues/138
|
||||
//
|
||||
// DO NOT change this flag unless you know what you're doing.
|
||||
//
|
||||
// This currently defaults to true for backwards compatibility but will likely
|
||||
// be set to false by default when an upgrade path is determined.
|
||||
var AdvancedEnableInlining = true
|
||||
|
||||
const maxInlineKeyLength = 42
|
||||
|
||||
// ID is a libp2p peer identity.
|
||||
//
|
||||
// Peer IDs are derived by hashing a peer's public key and encoding the
|
||||
// hash output as a multihash. See IDFromPublicKey for details.
|
||||
type ID string
|
||||
|
||||
// Pretty returns a base58-encoded string representation of the ID.
|
||||
func (id ID) Pretty() string {
|
||||
return IDB58Encode(id)
|
||||
}
|
||||
|
||||
// Loggable returns a pretty peer ID string in loggable JSON format.
|
||||
func (id ID) Loggable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"peerID": id.Pretty(),
|
||||
}
|
||||
}
|
||||
|
||||
func (id ID) String() string {
|
||||
return id.Pretty()
|
||||
}
|
||||
|
||||
// String prints out the peer ID.
|
||||
//
|
||||
// TODO(brian): ensure correctness at ID generation and
|
||||
// enforce this by only exposing functions that generate
|
||||
// IDs safely. Then any peer.ID type found in the
|
||||
// codebase is known to be correct.
|
||||
func (id ID) ShortString() string {
|
||||
pid := id.Pretty()
|
||||
if len(pid) <= 10 {
|
||||
return fmt.Sprintf("<peer.ID %s>", pid)
|
||||
}
|
||||
return fmt.Sprintf("<peer.ID %s*%s>", pid[:2], pid[len(pid)-6:])
|
||||
}
|
||||
|
||||
// MatchesPrivateKey tests whether this ID was derived from the secret key sk.
|
||||
func (id ID) MatchesPrivateKey(sk ic.PrivKey) bool {
|
||||
return id.MatchesPublicKey(sk.GetPublic())
|
||||
}
|
||||
|
||||
// MatchesPublicKey tests whether this ID was derived from the public key pk.
|
||||
func (id ID) MatchesPublicKey(pk ic.PubKey) bool {
|
||||
oid, err := IDFromPublicKey(pk)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return oid == id
|
||||
}
|
||||
|
||||
// ExtractPublicKey attempts to extract the public key from an ID
|
||||
//
|
||||
// This method returns ErrNoPublicKey if the peer ID looks valid but it can't extract
|
||||
// the public key.
|
||||
func (id ID) ExtractPublicKey() (ic.PubKey, error) {
|
||||
decoded, err := mh.Decode([]byte(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if decoded.Code != mh.ID {
|
||||
return nil, ErrNoPublicKey
|
||||
}
|
||||
pk, err := ic.UnmarshalPublicKey(decoded.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
// Validate checks if ID is empty or not.
|
||||
func (id ID) Validate() error {
|
||||
if id == ID("") {
|
||||
return ErrEmptyPeerID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IDFromString casts a string to the ID type, and validates
|
||||
// the value to make sure it is a multihash.
|
||||
func IDFromString(s string) (ID, error) {
|
||||
if _, err := mh.Cast([]byte(s)); err != nil {
|
||||
return ID(""), err
|
||||
}
|
||||
return ID(s), nil
|
||||
}
|
||||
|
||||
// IDFromBytes casts a byte slice to the ID type, and validates
|
||||
// the value to make sure it is a multihash.
|
||||
func IDFromBytes(b []byte) (ID, error) {
|
||||
if _, err := mh.Cast(b); err != nil {
|
||||
return ID(""), err
|
||||
}
|
||||
return ID(b), nil
|
||||
}
|
||||
|
||||
// IDB58Decode accepts a base58-encoded multihash representing a peer ID
|
||||
// and returns the decoded ID if the input is valid.
|
||||
func IDB58Decode(s string) (ID, error) {
|
||||
m, err := mh.FromB58String(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ID(m), err
|
||||
}
|
||||
|
||||
// IDB58Encode returns the base58-encoded multihash representation of the ID.
|
||||
func IDB58Encode(id ID) string {
|
||||
return b58.Encode([]byte(id))
|
||||
}
|
||||
|
||||
// IDHexDecode accepts a hex-encoded multihash representing a peer ID
|
||||
// and returns the decoded ID if the input is valid.
|
||||
func IDHexDecode(s string) (ID, error) {
|
||||
m, err := mh.FromHexString(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ID(m), err
|
||||
}
|
||||
|
||||
// IDHexEncode returns the hex-encoded multihash representation of the ID.
|
||||
func IDHexEncode(id ID) string {
|
||||
return hex.EncodeToString([]byte(id))
|
||||
}
|
||||
|
||||
// IDFromPublicKey returns the Peer ID corresponding to the public key pk.
|
||||
func IDFromPublicKey(pk ic.PubKey) (ID, error) {
|
||||
b, err := pk.Bytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var alg uint64 = mh.SHA2_256
|
||||
if AdvancedEnableInlining && len(b) <= maxInlineKeyLength {
|
||||
alg = mh.ID
|
||||
}
|
||||
hash, _ := mh.Sum(b, alg, -1)
|
||||
return ID(hash), nil
|
||||
}
|
||||
|
||||
// IDFromPrivateKey returns the Peer ID corresponding to the secret key sk.
|
||||
func IDFromPrivateKey(sk ic.PrivKey) (ID, error) {
|
||||
return IDFromPublicKey(sk.GetPublic())
|
||||
}
|
||||
|
||||
// IDSlice for sorting peers
|
||||
type IDSlice []ID
|
||||
|
||||
func (es IDSlice) Len() int { return len(es) }
|
||||
func (es IDSlice) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
|
||||
func (es IDSlice) Less(i, j int) bool { return string(es[i]) < string(es[j]) }
|
75
peer/peer_serde.go
Normal file
75
peer/peer_serde.go
Normal file
@ -0,0 +1,75 @@
|
||||
// This file contains Protobuf and JSON serialization/deserialization methods for peer IDs.
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Interface assertions commented out to avoid introducing hard dependencies to protobuf.
|
||||
// var _ proto.Marshaler = (*ID)(nil)
|
||||
// var _ proto.Unmarshaler = (*ID)(nil)
|
||||
var _ json.Marshaler = (*ID)(nil)
|
||||
var _ json.Unmarshaler = (*ID)(nil)
|
||||
|
||||
var _ encoding.BinaryMarshaler = (*ID)(nil)
|
||||
var _ encoding.BinaryUnmarshaler = (*ID)(nil)
|
||||
var _ encoding.TextMarshaler = (*ID)(nil)
|
||||
var _ encoding.TextUnmarshaler = (*ID)(nil)
|
||||
|
||||
func (id ID) Marshal() ([]byte, error) {
|
||||
return []byte(id), nil
|
||||
}
|
||||
|
||||
// BinaryMarshal returns the byte representation of the peer ID.
|
||||
func (id ID) MarshalBinary() ([]byte, error) {
|
||||
return id.Marshal()
|
||||
}
|
||||
|
||||
func (id ID) MarshalTo(data []byte) (n int, err error) {
|
||||
return copy(data, []byte(id)), nil
|
||||
}
|
||||
|
||||
func (id *ID) Unmarshal(data []byte) (err error) {
|
||||
*id, err = IDFromBytes(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// BinaryUnmarshal sets the ID from its binary representation.
|
||||
func (id *ID) UnmarshalBinary(data []byte) error {
|
||||
return id.Unmarshal(data)
|
||||
}
|
||||
|
||||
// Implements Gogo's proto.Sizer, but we omit the compile-time assertion to avoid introducing a hard
|
||||
// dependency on gogo.
|
||||
func (id ID) Size() int {
|
||||
return len([]byte(id))
|
||||
}
|
||||
|
||||
func (id ID) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(IDB58Encode(id))
|
||||
}
|
||||
|
||||
func (id *ID) UnmarshalJSON(data []byte) (err error) {
|
||||
var v string
|
||||
if err = json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
*id, err = IDB58Decode(v)
|
||||
return err
|
||||
}
|
||||
|
||||
// TextMarshal returns the text encoding of the ID.
|
||||
func (id ID) MarshalText() ([]byte, error) {
|
||||
return []byte(IDB58Encode(id)), nil
|
||||
}
|
||||
|
||||
// TextUnmarshal restores the ID from its text encoding.
|
||||
func (id *ID) UnmarshalText(data []byte) error {
|
||||
pid, err := IDB58Decode(string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*id = pid
|
||||
return nil
|
||||
}
|
83
peer/peer_serde_test.go
Normal file
83
peer/peer_serde_test.go
Normal file
@ -0,0 +1,83 @@
|
||||
package peer_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
. "github.com/libp2p/go-libp2p-testing/peer"
|
||||
)
|
||||
|
||||
func TestPeerSerdePB(t *testing.T) {
|
||||
id, err := RandPeerID()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, err := id.Marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var id2 peer.ID
|
||||
if err = id2.Unmarshal(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if id != id2 {
|
||||
t.Error("expected equal ids in circular serde test")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeerSerdeJSON(t *testing.T) {
|
||||
id, err := RandPeerID()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, err := id.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var id2 peer.ID
|
||||
if err = id2.UnmarshalJSON(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if id != id2 {
|
||||
t.Error("expected equal ids in circular serde test")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinaryMarshaler(t *testing.T) {
|
||||
id, err := RandPeerID()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, err := id.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var id2 peer.ID
|
||||
if err = id2.UnmarshalBinary(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if id != id2 {
|
||||
t.Error("expected equal ids in circular serde test")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextMarshaler(t *testing.T) {
|
||||
id, err := RandPeerID()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, err := id.MarshalText()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var id2 peer.ID
|
||||
if err = id2.UnmarshalText(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if id != id2 {
|
||||
t.Error("expected equal ids in circular serde test")
|
||||
}
|
||||
}
|
244
peer/peer_test.go
Normal file
244
peer/peer_test.go
Normal file
@ -0,0 +1,244 @@
|
||||
package peer_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
ic "github.com/libp2p/go-libp2p-core/crypto"
|
||||
"github.com/libp2p/go-libp2p-testing/crypto"
|
||||
|
||||
. "github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/libp2p/go-libp2p-testing/peer"
|
||||
|
||||
b58 "github.com/mr-tron/base58/base58"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
var gen1 keyset // generated
|
||||
var gen2 keyset // generated
|
||||
var man keyset // manual
|
||||
|
||||
func hash(b []byte) []byte {
|
||||
h, _ := mh.Sum(b, mh.SHA2_256, -1)
|
||||
return []byte(h)
|
||||
}
|
||||
|
||||
func init() {
|
||||
if err := gen1.generate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := gen2.generate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
skManBytes = strings.Replace(skManBytes, "\n", "", -1)
|
||||
if err := man.load(hpkpMan, skManBytes); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type keyset struct {
|
||||
sk ic.PrivKey
|
||||
pk ic.PubKey
|
||||
hpk string
|
||||
hpkp string
|
||||
}
|
||||
|
||||
func (ks *keyset) generate() error {
|
||||
var err error
|
||||
ks.sk, ks.pk, err = tcrypto.RandTestKeyPair(ic.RSA, 512)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bpk, err := ks.pk.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ks.hpk = string(hash(bpk))
|
||||
ks.hpkp = b58.Encode([]byte(ks.hpk))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ks *keyset) load(hpkp, skBytesStr string) error {
|
||||
skBytes, err := base64.StdEncoding.DecodeString(skBytesStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ks.sk, err = ic.UnmarshalPrivateKey(skBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ks.pk = ks.sk.GetPublic()
|
||||
bpk, err := ks.pk.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ks.hpk = string(hash(bpk))
|
||||
ks.hpkp = b58.Encode([]byte(ks.hpk))
|
||||
if ks.hpkp != hpkp {
|
||||
return fmt.Errorf("hpkp doesn't match key. %s", hpkp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestIDMatchesPublicKey(t *testing.T) {
|
||||
|
||||
test := func(ks keyset) {
|
||||
p1, err := IDB58Decode(ks.hpkp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ks.hpk != string(p1) {
|
||||
t.Error("p1 and hpk differ")
|
||||
}
|
||||
|
||||
if !p1.MatchesPublicKey(ks.pk) {
|
||||
t.Fatal("p1 does not match pk")
|
||||
}
|
||||
|
||||
p2, err := IDFromPublicKey(ks.pk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if p1 != p2 {
|
||||
t.Error("p1 and p2 differ", p1.Pretty(), p2.Pretty())
|
||||
}
|
||||
|
||||
if p2.Pretty() != ks.hpkp {
|
||||
t.Error("hpkp and p2.Pretty differ", ks.hpkp, p2.Pretty())
|
||||
}
|
||||
}
|
||||
|
||||
test(gen1)
|
||||
test(gen2)
|
||||
test(man)
|
||||
}
|
||||
|
||||
func TestIDMatchesPrivateKey(t *testing.T) {
|
||||
|
||||
test := func(ks keyset) {
|
||||
p1, err := IDB58Decode(ks.hpkp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ks.hpk != string(p1) {
|
||||
t.Error("p1 and hpk differ")
|
||||
}
|
||||
|
||||
if !p1.MatchesPrivateKey(ks.sk) {
|
||||
t.Fatal("p1 does not match sk")
|
||||
}
|
||||
|
||||
p2, err := IDFromPrivateKey(ks.sk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if p1 != p2 {
|
||||
t.Error("p1 and p2 differ", p1.Pretty(), p2.Pretty())
|
||||
}
|
||||
}
|
||||
|
||||
test(gen1)
|
||||
test(gen2)
|
||||
test(man)
|
||||
}
|
||||
|
||||
func TestPublicKeyExtraction(t *testing.T) {
|
||||
t.Skip("disabled until libp2p/go-libp2p-crypto#51 is fixed")
|
||||
// Happy path
|
||||
_, originalPub, err := ic.GenerateEd25519Key(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
id, err := IDFromPublicKey(originalPub)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
extractedPub, err := id.ExtractPublicKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if extractedPub == nil {
|
||||
t.Fatal("failed to extract public key")
|
||||
}
|
||||
if !originalPub.Equals(extractedPub) {
|
||||
t.Fatal("extracted public key doesn't match")
|
||||
}
|
||||
|
||||
// Test invalid multihash (invariant of the type of public key)
|
||||
pk, err := ID("").ExtractPublicKey()
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
if pk != nil {
|
||||
t.Fatal("expected a nil public key")
|
||||
}
|
||||
|
||||
// Shouldn't work for, e.g. RSA keys (too large)
|
||||
|
||||
_, rsaPub, err := ic.GenerateKeyPair(ic.RSA, 2048)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rsaId, err := IDFromPublicKey(rsaPub)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
extractedRsaPub, err := rsaId.ExtractPublicKey()
|
||||
if err != ErrNoPublicKey {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if extractedRsaPub != nil {
|
||||
t.Fatal("expected to fail to extract public key from rsa ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
// Empty peer ID invalidates
|
||||
err := ID("").Validate()
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
} else if err != ErrEmptyPeerID {
|
||||
t.Error("expected error message: " + ErrEmptyPeerID.Error())
|
||||
}
|
||||
|
||||
// Non-empty peer ID validates
|
||||
p, err := tpeer.RandPeerID()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = p.Validate()
|
||||
if err != nil {
|
||||
t.Error("expected nil, but found " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var hpkpMan = `QmRK3JgmVEGiewxWbhpXLJyjWuGuLeSTMTndA1coMHEy5o`
|
||||
var skManBytes = `
|
||||
CAAS4AQwggJcAgEAAoGBAL7w+Wc4VhZhCdM/+Hccg5Nrf4q9NXWwJylbSrXz/unFS24wyk6pEk0zi3W
|
||||
7li+vSNVO+NtJQw9qGNAMtQKjVTP+3Vt/jfQRnQM3s6awojtjueEWuLYVt62z7mofOhCtj+VwIdZNBo
|
||||
/EkLZ0ETfcvN5LVtLYa8JkXybnOPsLvK+PAgMBAAECgYBdk09HDM7zzL657uHfzfOVrdslrTCj6p5mo
|
||||
DzvCxLkkjIzYGnlPuqfNyGjozkpSWgSUc+X+EGLLl3WqEOVdWJtbM61fewEHlRTM5JzScvwrJ39t7o6
|
||||
CCAjKA0cBWBd6UWgbN/t53RoWvh9HrA2AW5YrT0ZiAgKe9y7EMUaENVJ8QJBAPhpdmb4ZL4Fkm4OKia
|
||||
NEcjzn6mGTlZtef7K/0oRC9+2JkQnCuf6HBpaRhJoCJYg7DW8ZY+AV6xClKrgjBOfERMCQQDExhnzu2
|
||||
dsQ9k8QChBlpHO0TRbZBiQfC70oU31kM1AeLseZRmrxv9Yxzdl8D693NNWS2JbKOXl0kMHHcuGQLMVA
|
||||
kBZ7WvkmPV3aPL6jnwp2pXepntdVnaTiSxJ1dkXShZ/VSSDNZMYKY306EtHrIu3NZHtXhdyHKcggDXr
|
||||
qkBrdgErAkAlpGPojUwemOggr4FD8sLX1ot2hDJyyV7OK2FXfajWEYJyMRL1Gm9Uk1+Un53RAkJneqp
|
||||
JGAzKpyttXBTIDO51AkEA98KTiROMnnU8Y6Mgcvr68/SMIsvCYMt9/mtwSBGgl80VaTQ5Hpaktl6Xbh
|
||||
VUt5Wv0tRxlXZiViCGCD1EtrrwTw==
|
||||
`
|
71
peer/set.go
Normal file
71
peer/set.go
Normal file
@ -0,0 +1,71 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// PeerSet is a threadsafe set of peers
|
||||
type Set struct {
|
||||
lk sync.RWMutex
|
||||
ps map[ID]struct{}
|
||||
|
||||
size int
|
||||
}
|
||||
|
||||
func NewSet() *Set {
|
||||
ps := new(Set)
|
||||
ps.ps = make(map[ID]struct{})
|
||||
ps.size = -1
|
||||
return ps
|
||||
}
|
||||
|
||||
func NewLimitedSet(size int) *Set {
|
||||
ps := new(Set)
|
||||
ps.ps = make(map[ID]struct{})
|
||||
ps.size = size
|
||||
return ps
|
||||
}
|
||||
|
||||
func (ps *Set) Add(p ID) {
|
||||
ps.lk.Lock()
|
||||
ps.ps[p] = struct{}{}
|
||||
ps.lk.Unlock()
|
||||
}
|
||||
|
||||
func (ps *Set) Contains(p ID) bool {
|
||||
ps.lk.RLock()
|
||||
_, ok := ps.ps[p]
|
||||
ps.lk.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
func (ps *Set) Size() int {
|
||||
ps.lk.RLock()
|
||||
defer ps.lk.RUnlock()
|
||||
return len(ps.ps)
|
||||
}
|
||||
|
||||
// TryAdd Attempts to add the given peer into the set.
|
||||
// This operation can fail for one of two reasons:
|
||||
// 1) The given peer is already in the set
|
||||
// 2) The number of peers in the set is equal to size
|
||||
func (ps *Set) TryAdd(p ID) bool {
|
||||
var success bool
|
||||
ps.lk.Lock()
|
||||
if _, ok := ps.ps[p]; !ok && (len(ps.ps) < ps.size || ps.size == -1) {
|
||||
success = true
|
||||
ps.ps[p] = struct{}{}
|
||||
}
|
||||
ps.lk.Unlock()
|
||||
return success
|
||||
}
|
||||
|
||||
func (ps *Set) Peers() []ID {
|
||||
ps.lk.Lock()
|
||||
out := make([]ID, 0, len(ps.ps))
|
||||
for p, _ := range ps.ps {
|
||||
out = append(out, p)
|
||||
}
|
||||
ps.lk.Unlock()
|
||||
return out
|
||||
}
|
162
peerstore/peerstore.go
Normal file
162
peerstore/peerstore.go
Normal file
@ -0,0 +1,162 @@
|
||||
// Package peerstore provides types and interfaces for local storage of address information,
|
||||
// metadata, and public key material about libp2p peers.
|
||||
package peerstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
|
||||
ic "github.com/libp2p/go-libp2p-core/crypto"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
var ErrNotFound = errors.New("item not found")
|
||||
|
||||
var (
|
||||
// AddressTTL is the expiration time of addresses.
|
||||
AddressTTL = time.Hour
|
||||
|
||||
// TempAddrTTL is the ttl used for a short lived address
|
||||
TempAddrTTL = time.Minute * 2
|
||||
|
||||
// ProviderAddrTTL is the TTL of an address we've received from a provider.
|
||||
// This is also a temporary address, but lasts longer. After this expires,
|
||||
// the records we return will require an extra lookup.
|
||||
ProviderAddrTTL = time.Minute * 10
|
||||
|
||||
// RecentlyConnectedAddrTTL is used when we recently connected to a peer.
|
||||
// It means that we are reasonably certain of the peer's address.
|
||||
RecentlyConnectedAddrTTL = time.Minute * 10
|
||||
|
||||
// OwnObservedAddrTTL is used for our own external addresses observed by peers.
|
||||
OwnObservedAddrTTL = time.Minute * 10
|
||||
)
|
||||
|
||||
// Permanent TTLs (distinct so we can distinguish between them, constant as they
|
||||
// are, in fact, permanent)
|
||||
const (
|
||||
// PermanentAddrTTL is the ttl for a "permanent address" (e.g. bootstrap nodes).
|
||||
PermanentAddrTTL = math.MaxInt64 - iota
|
||||
|
||||
// ConnectedAddrTTL is the ttl used for the addresses of a peer to whom
|
||||
// we're connected directly. This is basically permanent, as we will
|
||||
// clear them + re-add under a TempAddrTTL after disconnecting.
|
||||
ConnectedAddrTTL
|
||||
)
|
||||
|
||||
// Peerstore provides a threadsafe store of Peer related
|
||||
// information.
|
||||
type Peerstore interface {
|
||||
io.Closer
|
||||
|
||||
AddrBook
|
||||
KeyBook
|
||||
PeerMetadata
|
||||
Metrics
|
||||
ProtoBook
|
||||
|
||||
// PeerInfo returns a peer.PeerInfo struct for given peer.ID.
|
||||
// This is a small slice of the information Peerstore has on
|
||||
// that peer, useful to other services.
|
||||
PeerInfo(peer.ID) peer.AddrInfo
|
||||
|
||||
// Peers returns all of the peer IDs stored across all inner stores.
|
||||
Peers() peer.IDSlice
|
||||
}
|
||||
|
||||
// PeerMetadata can handle values of any type. Serializing values is
|
||||
// up to the implementation. Dynamic type introspection may not be
|
||||
// supported, in which case explicitly enlisting types in the
|
||||
// serializer may be required.
|
||||
//
|
||||
// Refer to the docs of the underlying implementation for more
|
||||
// information.
|
||||
type PeerMetadata interface {
|
||||
// Get/Put is a simple registry for other peer-related key/value pairs.
|
||||
// if we find something we use often, it should become its own set of
|
||||
// methods. this is a last resort.
|
||||
Get(p peer.ID, key string) (interface{}, error)
|
||||
Put(p peer.ID, key string, val interface{}) error
|
||||
}
|
||||
|
||||
// AddrBook holds the multiaddrs of peers.
|
||||
type AddrBook interface {
|
||||
|
||||
// AddAddr calls AddAddrs(p, []ma.Multiaddr{addr}, ttl)
|
||||
AddAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration)
|
||||
|
||||
// AddAddrs gives this AddrBook addresses to use, with a given ttl
|
||||
// (time-to-live), after which the address is no longer valid.
|
||||
// If the manager has a longer TTL, the operation is a no-op for that address
|
||||
AddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration)
|
||||
|
||||
// SetAddr calls mgr.SetAddrs(p, addr, ttl)
|
||||
SetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration)
|
||||
|
||||
// SetAddrs sets the ttl on addresses. This clears any TTL there previously.
|
||||
// This is used when we receive the best estimate of the validity of an address.
|
||||
SetAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration)
|
||||
|
||||
// UpdateAddrs updates the addresses associated with the given peer that have
|
||||
// the given oldTTL to have the given newTTL.
|
||||
UpdateAddrs(p peer.ID, oldTTL time.Duration, newTTL time.Duration)
|
||||
|
||||
// Addresses returns all known (and valid) addresses for a given peer
|
||||
Addrs(p peer.ID) []ma.Multiaddr
|
||||
|
||||
// AddrStream returns a channel that gets all addresses for a given
|
||||
// peer sent on it. If new addresses are added after the call is made
|
||||
// they will be sent along through the channel as well.
|
||||
AddrStream(context.Context, peer.ID) <-chan ma.Multiaddr
|
||||
|
||||
// ClearAddresses removes all previously stored addresses
|
||||
ClearAddrs(p peer.ID)
|
||||
|
||||
// PeersWithAddrs returns all of the peer IDs stored in the AddrBook
|
||||
PeersWithAddrs() peer.IDSlice
|
||||
}
|
||||
|
||||
// KeyBook tracks the keys of Peers.
|
||||
type KeyBook interface {
|
||||
// PubKey stores the public key of a peer.
|
||||
PubKey(peer.ID) ic.PubKey
|
||||
|
||||
// AddPubKey stores the public key of a peer.
|
||||
AddPubKey(peer.ID, ic.PubKey) error
|
||||
|
||||
// PrivKey returns the private key of a peer, if known. Generally this might only be our own
|
||||
// private key, see
|
||||
// https://discuss.libp2p.io/t/what-is-the-purpose-of-having-map-peer-id-privatekey-in-peerstore/74.
|
||||
PrivKey(peer.ID) ic.PrivKey
|
||||
|
||||
// AddPrivKey stores the private key of a peer.
|
||||
AddPrivKey(peer.ID, ic.PrivKey) error
|
||||
|
||||
// PeersWithKeys returns all the peer IDs stored in the KeyBook
|
||||
PeersWithKeys() peer.IDSlice
|
||||
}
|
||||
|
||||
// Metrics is just an object that tracks metrics
|
||||
// across a set of peers.
|
||||
type Metrics interface {
|
||||
// RecordLatency records a new latency measurement
|
||||
RecordLatency(peer.ID, time.Duration)
|
||||
|
||||
// LatencyEWMA returns an exponentially-weighted moving avg.
|
||||
// of all measurements of a peer's latency.
|
||||
LatencyEWMA(peer.ID) time.Duration
|
||||
}
|
||||
|
||||
// ProtoBook tracks the protocols supported by peers
|
||||
type ProtoBook interface {
|
||||
GetProtocols(peer.ID) ([]string, error)
|
||||
AddProtocols(peer.ID, ...string) error
|
||||
SetProtocols(peer.ID, ...string) error
|
||||
SupportsProtocols(peer.ID, ...string) ([]string, error)
|
||||
}
|
19
pnet/env.go
Normal file
19
pnet/env.go
Normal file
@ -0,0 +1,19 @@
|
||||
package pnet
|
||||
|
||||
import "os"
|
||||
|
||||
// EnvKey defines environment variable name for forcing usage of PNet in libp2p
|
||||
// When environment variable of this name is set to "1" the ForcePrivateNetwork
|
||||
// variable will be set to true.
|
||||
const EnvKey = "LIBP2P_FORCE_PNET"
|
||||
|
||||
// ForcePrivateNetwork is boolean variable that forces usage of PNet in libp2p
|
||||
// Setting this variable to true or setting LIBP2P_FORCE_PNET environment variable
|
||||
// to true will make libp2p to require private network protector.
|
||||
// If no network protector is provided and this variable is set to true libp2p will
|
||||
// refuse to connect.
|
||||
var ForcePrivateNetwork = false
|
||||
|
||||
func init() {
|
||||
ForcePrivateNetwork = os.Getenv(EnvKey) == "1"
|
||||
}
|
34
pnet/error.go
Normal file
34
pnet/error.go
Normal file
@ -0,0 +1,34 @@
|
||||
package pnet
|
||||
|
||||
// ErrNotInPrivateNetwork is an error that should be returned by libp2p when it
|
||||
// tries to dial with ForcePrivateNetwork set and no PNet Protector
|
||||
var ErrNotInPrivateNetwork = NewError("private network was not configured but" +
|
||||
" is enforced by the environment")
|
||||
|
||||
// Error is error type for ease of detecting PNet errors
|
||||
type Error interface {
|
||||
IsPNetError() bool
|
||||
}
|
||||
|
||||
// NewError creates new Error
|
||||
func NewError(err string) error {
|
||||
return pnetErr("privnet: " + err)
|
||||
}
|
||||
|
||||
// IsPNetError checks if given error is PNet Error
|
||||
func IsPNetError(err error) bool {
|
||||
v, ok := err.(Error)
|
||||
return ok && v.IsPNetError()
|
||||
}
|
||||
|
||||
type pnetErr string
|
||||
|
||||
var _ Error = (*pnetErr)(nil)
|
||||
|
||||
func (p pnetErr) Error() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (pnetErr) IsPNetError() bool {
|
||||
return true
|
||||
}
|
20
pnet/error_test.go
Normal file
20
pnet/error_test.go
Normal file
@ -0,0 +1,20 @@
|
||||
package pnet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsPnetErr(t *testing.T) {
|
||||
err := NewError("test")
|
||||
|
||||
if err.Error() != "privnet: test" {
|
||||
t.Fatalf("expected 'privnet: test' got '%s'", err.Error())
|
||||
}
|
||||
if !IsPNetError(err) {
|
||||
t.Fatal("expected the pnetErr to be detected by IsPnetError")
|
||||
}
|
||||
if IsPNetError(errors.New("not pnet error")) {
|
||||
t.Fatal("expected generic error not to be pnetError")
|
||||
}
|
||||
}
|
15
pnet/protector.go
Normal file
15
pnet/protector.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Package pnet provides interfaces for private networking in libp2p.
|
||||
package pnet
|
||||
|
||||
import "net"
|
||||
|
||||
// Protector interface is a way for private network implementation to be transparent in
|
||||
// libp2p. It is created by implementation and use by libp2p-conn to secure connections
|
||||
// so they can be only established with selected number of peers.
|
||||
type Protector interface {
|
||||
// Wraps passed connection to protect it
|
||||
Protect(net.Conn) (net.Conn, error)
|
||||
|
||||
// Returns key fingerprint that is safe to expose
|
||||
Fingerprint() []byte
|
||||
}
|
9
protocol/id.go
Normal file
9
protocol/id.go
Normal file
@ -0,0 +1,9 @@
|
||||
package protocol
|
||||
|
||||
// ID is an identifier used to write protocol headers in streams.
|
||||
type ID string
|
||||
|
||||
// These are reserved protocol.IDs.
|
||||
const (
|
||||
TestingID ID = "/p2p/_testing"
|
||||
)
|
81
protocol/switch.go
Normal file
81
protocol/switch.go
Normal file
@ -0,0 +1,81 @@
|
||||
// Package protocol provides core interfaces for protocol routing and negotiation in libp2p.
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// HandlerFunc is a user-provided function used by the Router to
|
||||
// handle a protocol/stream.
|
||||
//
|
||||
// Will be invoked with the protocol ID string as the first argument,
|
||||
// which may differ from the ID used for registration if the handler
|
||||
// was registered using a match function.
|
||||
type HandlerFunc = func(protocol string, rwc io.ReadWriteCloser) error
|
||||
|
||||
// Router is an interface that allows users to add and remove protocol handlers,
|
||||
// which will be invoked when incoming stream requests for registered protocols
|
||||
// are accepted.
|
||||
//
|
||||
// Upon receiving an incoming stream request, the Router will check all registered
|
||||
// protocol handlers to determine which (if any) is capable of handling the stream.
|
||||
// The handlers are checked in order of registration; if multiple handlers are
|
||||
// eligible, only the first to be registered will be invoked.
|
||||
type Router interface {
|
||||
|
||||
// AddHandler registers the given handler to be invoked for
|
||||
// an exact literal match of the given protocol ID string.
|
||||
AddHandler(protocol string, handler HandlerFunc)
|
||||
|
||||
// AddHandlerWithFunc registers the given handler to be invoked
|
||||
// when the provided match function returns true.
|
||||
//
|
||||
// The match function will be invoked with an incoming protocol
|
||||
// ID string, and should return true if the handler supports
|
||||
// the protocol. Note that the protocol ID argument is not
|
||||
// used for matching; if you want to match the protocol ID
|
||||
// string exactly, you must check for it in your match function.
|
||||
AddHandlerWithFunc(protocol string, match func(string) bool, handler HandlerFunc)
|
||||
|
||||
// RemoveHandler removes the registered handler (if any) for the
|
||||
// given protocol ID string.
|
||||
RemoveHandler(protocol string)
|
||||
|
||||
// Protocols returns a list of all registered protocol ID strings.
|
||||
// Note that the Router may be able to handle protocol IDs not
|
||||
// included in this list if handlers were added with match functions
|
||||
// using AddHandlerWithFunc.
|
||||
Protocols() []string
|
||||
}
|
||||
|
||||
// Negotiator is a component capable of reaching agreement over what protocols
|
||||
// to use for inbound streams of communication.
|
||||
type Negotiator interface {
|
||||
|
||||
// NegotiateLazy will return the registered protocol handler to use
|
||||
// for a given inbound stream, returning as soon as the protocol has been
|
||||
// determined. Returns an error if negotiation fails.
|
||||
//
|
||||
// NegotiateLazy may return before all protocol negotiation responses have been
|
||||
// written to the stream. This is in contrast to Negotiate, which will block until
|
||||
// the Negotiator is finished with the stream.
|
||||
NegotiateLazy(rwc io.ReadWriteCloser) (io.ReadWriteCloser, string, HandlerFunc, error)
|
||||
|
||||
// Negotiate will return the registered protocol handler to use for a given
|
||||
// inbound stream, returning after the protocol has been determined and the
|
||||
// Negotiator has finished using the stream for negotiation. Returns an
|
||||
// error if negotiation fails.
|
||||
Negotiate(rwc io.ReadWriteCloser) (string, HandlerFunc, error)
|
||||
|
||||
// Handle calls Negotiate to determine which protocol handler to use for an
|
||||
// inbound stream, then invokes the protocol handler function, passing it
|
||||
// the protocol ID and the stream. Returns an error if negotiation fails.
|
||||
Handle(rwc io.ReadWriteCloser) error
|
||||
}
|
||||
|
||||
// Switch is the component responsible for "dispatching" incoming stream requests to
|
||||
// their corresponding stream handlers. It is both a Negotiator and a Router.
|
||||
type Switch interface {
|
||||
Router
|
||||
Negotiator
|
||||
}
|
50
routing/options.go
Normal file
50
routing/options.go
Normal file
@ -0,0 +1,50 @@
|
||||
package routing
|
||||
|
||||
// Option is a single routing option.
|
||||
type Option func(opts *Options) error
|
||||
|
||||
// Options is a set of routing options
|
||||
type Options struct {
|
||||
// Allow expired values.
|
||||
Expired bool
|
||||
Offline bool
|
||||
// Other (ValueStore implementation specific) options.
|
||||
Other map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// Apply applies the given options to this Options
|
||||
func (opts *Options) Apply(options ...Option) error {
|
||||
for _, o := range options {
|
||||
if err := o(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToOption converts this Options to a single Option.
|
||||
func (opts *Options) ToOption() Option {
|
||||
return func(nopts *Options) error {
|
||||
*nopts = *opts
|
||||
if opts.Other != nil {
|
||||
nopts.Other = make(map[interface{}]interface{}, len(opts.Other))
|
||||
for k, v := range opts.Other {
|
||||
nopts.Other[k] = v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Expired is an option that tells the routing system to return expired records
|
||||
// when no newer records are known.
|
||||
var Expired Option = func(opts *Options) error {
|
||||
opts.Expired = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Offline is an option that tells the routing system to operate offline (i.e., rely on cached/local data only).
|
||||
var Offline Option = func(opts *Options) error {
|
||||
opts.Offline = true
|
||||
return nil
|
||||
}
|
86
routing/query.go
Normal file
86
routing/query.go
Normal file
@ -0,0 +1,86 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
)
|
||||
|
||||
type QueryEventType int
|
||||
|
||||
// Number of events to buffer.
|
||||
var QueryEventBufferSize = 16
|
||||
|
||||
const (
|
||||
SendingQuery QueryEventType = iota
|
||||
PeerResponse
|
||||
FinalPeer
|
||||
QueryError
|
||||
Provider
|
||||
Value
|
||||
AddingPeer
|
||||
DialingPeer
|
||||
)
|
||||
|
||||
type QueryEvent struct {
|
||||
ID peer.ID
|
||||
Type QueryEventType
|
||||
Responses []*peer.AddrInfo
|
||||
Extra string
|
||||
}
|
||||
|
||||
type routingQueryKey struct{}
|
||||
type eventChannel struct {
|
||||
mu sync.Mutex
|
||||
ctx context.Context
|
||||
ch chan<- *QueryEvent
|
||||
}
|
||||
|
||||
// waitThenClose is spawned in a goroutine when the channel is registered. This
|
||||
// safely cleans up the channel when the context has been canceled.
|
||||
func (e *eventChannel) waitThenClose() {
|
||||
<-e.ctx.Done()
|
||||
e.mu.Lock()
|
||||
close(e.ch)
|
||||
// 1. Signals that we're done.
|
||||
// 2. Frees memory (in case we end up hanging on to this for a while).
|
||||
e.ch = nil
|
||||
e.mu.Unlock()
|
||||
}
|
||||
|
||||
// send sends an event on the event channel, aborting if either the passed or
|
||||
// the internal context expire.
|
||||
func (e *eventChannel) send(ctx context.Context, ev *QueryEvent) {
|
||||
e.mu.Lock()
|
||||
// Closed.
|
||||
if e.ch == nil {
|
||||
e.mu.Unlock()
|
||||
return
|
||||
}
|
||||
// in case the passed context is unrelated, wait on both.
|
||||
select {
|
||||
case e.ch <- ev:
|
||||
case <-e.ctx.Done():
|
||||
case <-ctx.Done():
|
||||
}
|
||||
e.mu.Unlock()
|
||||
}
|
||||
|
||||
func RegisterForQueryEvents(ctx context.Context) (context.Context, <-chan *QueryEvent) {
|
||||
ch := make(chan *QueryEvent, QueryEventBufferSize)
|
||||
ech := &eventChannel{ch: ch, ctx: ctx}
|
||||
go ech.waitThenClose()
|
||||
return context.WithValue(ctx, routingQueryKey{}, ech), ch
|
||||
}
|
||||
|
||||
func PublishQueryEvent(ctx context.Context, ev *QueryEvent) {
|
||||
ich := ctx.Value(routingQueryKey{})
|
||||
if ich == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// We *want* to panic here.
|
||||
ech := ich.(*eventChannel)
|
||||
ech.send(ctx, ev)
|
||||
}
|
40
routing/query_serde.go
Normal file
40
routing/query_serde.go
Normal file
@ -0,0 +1,40 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
)
|
||||
|
||||
func (qe *QueryEvent) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"ID": peer.IDB58Encode(qe.ID),
|
||||
"Type": int(qe.Type),
|
||||
"Responses": qe.Responses,
|
||||
"Extra": qe.Extra,
|
||||
})
|
||||
}
|
||||
|
||||
func (qe *QueryEvent) UnmarshalJSON(b []byte) error {
|
||||
temp := struct {
|
||||
ID string
|
||||
Type int
|
||||
Responses []*peer.AddrInfo
|
||||
Extra string
|
||||
}{}
|
||||
err := json.Unmarshal(b, &temp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(temp.ID) > 0 {
|
||||
pid, err := peer.IDB58Decode(temp.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qe.ID = pid
|
||||
}
|
||||
qe.Type = QueryEventType(temp.Type)
|
||||
qe.Responses = temp.Responses
|
||||
qe.Extra = temp.Extra
|
||||
return nil
|
||||
}
|
44
routing/query_test.go
Normal file
44
routing/query_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEventsCancel(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, events := RegisterForQueryEvents(ctx)
|
||||
goch := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 100; i++ {
|
||||
PublishQueryEvent(ctx, &QueryEvent{Extra: fmt.Sprint(i)})
|
||||
}
|
||||
close(goch)
|
||||
for i := 100; i < 1000; i++ {
|
||||
PublishQueryEvent(ctx, &QueryEvent{Extra: fmt.Sprint(i)})
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
i := 0
|
||||
for e := range events {
|
||||
if i < 100 {
|
||||
if e.Extra != fmt.Sprint(i) {
|
||||
t.Errorf("expected %d, got %s", i, e.Extra)
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i < 100 {
|
||||
t.Errorf("expected at least 100 events, got %d", i)
|
||||
}
|
||||
}()
|
||||
<-goch
|
||||
cancel()
|
||||
wg.Wait()
|
||||
}
|
125
routing/routing.go
Normal file
125
routing/routing.go
Normal file
@ -0,0 +1,125 @@
|
||||
// Package routing provides interfaces for peer routing and content routing in libp2p.
|
||||
package routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
ci "github.com/libp2p/go-libp2p-core/crypto"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
|
||||
cid "github.com/ipfs/go-cid"
|
||||
)
|
||||
|
||||
// ErrNotFound is returned when the router fails to find the requested record.
|
||||
var ErrNotFound = errors.New("routing: not found")
|
||||
|
||||
// ErrNotSupported is returned when the router doesn't support the given record
|
||||
// type/operation.
|
||||
var ErrNotSupported = errors.New("routing: operation or key not supported")
|
||||
|
||||
// ContentRouting is a value provider layer of indirection. It is used to find
|
||||
// information about who has what content.
|
||||
//
|
||||
// Content is identified by CID (content identifier), which encodes a hash
|
||||
// of the identified content in a future-proof manner.
|
||||
type ContentRouting interface {
|
||||
// Provide adds the given cid to the content routing system. If 'true' is
|
||||
// passed, it also announces it, otherwise it is just kept in the local
|
||||
// accounting of which objects are being provided.
|
||||
Provide(context.Context, cid.Cid, bool) error
|
||||
|
||||
// Search for peers who are able to provide a given key
|
||||
FindProvidersAsync(context.Context, cid.Cid, int) <-chan peer.AddrInfo
|
||||
}
|
||||
|
||||
// PeerRouting is a way to find address information about certain peers.
|
||||
// This can be implemented by a simple lookup table, a tracking server,
|
||||
// or even a DHT.
|
||||
type PeerRouting interface {
|
||||
// Find specific Peer
|
||||
// FindPeer searches for a peer with given ID, returns a peer.AddrInfo
|
||||
// with relevant addresses.
|
||||
FindPeer(context.Context, peer.ID) (peer.AddrInfo, error)
|
||||
}
|
||||
|
||||
// ValueStore is a basic Put/Get interface.
|
||||
type ValueStore interface {
|
||||
|
||||
// PutValue adds value corresponding to given Key.
|
||||
PutValue(context.Context, string, []byte, ...Option) error
|
||||
|
||||
// GetValue searches for the value corresponding to given Key.
|
||||
GetValue(context.Context, string, ...Option) ([]byte, error)
|
||||
|
||||
// SearchValue searches for better and better values from this value
|
||||
// store corresponding to the given Key. By default implementations must
|
||||
// stop the search after a good value is found. A 'good' value is a value
|
||||
// that would be returned from GetValue.
|
||||
//
|
||||
// Useful when you want a result *now* but still want to hear about
|
||||
// better/newer results.
|
||||
//
|
||||
// Implementations of this methods won't return ErrNotFound. When a value
|
||||
// couldn't be found, the channel will get closed without passing any results
|
||||
SearchValue(context.Context, string, ...Option) (<-chan []byte, error)
|
||||
}
|
||||
|
||||
// Routing is the combination of different routing types supported by libp2p.
|
||||
// It can be satisfied by a single item (such as a DHT) or multiple different
|
||||
// pieces that are more optimized to each task.
|
||||
type Routing interface {
|
||||
ContentRouting
|
||||
PeerRouting
|
||||
ValueStore
|
||||
|
||||
// Bootstrap allows callers to hint to the routing system to get into a
|
||||
// Boostrapped state and remain there. It is not a synchronous call.
|
||||
Bootstrap(context.Context) error
|
||||
|
||||
// TODO expose io.Closer or plain-old Close error
|
||||
}
|
||||
|
||||
// PubKeyFetcher is an interfaces that should be implemented by value stores
|
||||
// that can optimize retrieval of public keys.
|
||||
//
|
||||
// TODO(steb): Consider removing, see https://github.com/libp2p/go-libp2p-routing/issues/22.
|
||||
type PubKeyFetcher interface {
|
||||
// GetPublicKey returns the public key for the given peer.
|
||||
GetPublicKey(context.Context, peer.ID) (ci.PubKey, error)
|
||||
}
|
||||
|
||||
// KeyForPublicKey returns the key used to retrieve public keys
|
||||
// from a value store.
|
||||
func KeyForPublicKey(id peer.ID) string {
|
||||
return "/pk/" + string(id)
|
||||
}
|
||||
|
||||
// GetPublicKey retrieves the public key associated with the given peer ID from
|
||||
// the value store.
|
||||
//
|
||||
// If the ValueStore is also a PubKeyFetcher, this method will call GetPublicKey
|
||||
// (which may be better optimized) instead of GetValue.
|
||||
func GetPublicKey(r ValueStore, ctx context.Context, p peer.ID) (ci.PubKey, error) {
|
||||
switch k, err := p.ExtractPublicKey(); err {
|
||||
case peer.ErrNoPublicKey:
|
||||
// check the datastore
|
||||
case nil:
|
||||
return k, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dht, ok := r.(PubKeyFetcher); ok {
|
||||
// If we have a DHT as our routing system, use optimized fetcher
|
||||
return dht.GetPublicKey(ctx, p)
|
||||
}
|
||||
key := KeyForPublicKey(p)
|
||||
pkval, err := r.GetValue(ctx, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get PublicKey from node.Data
|
||||
return ci.UnmarshalPublicKey(pkval)
|
||||
}
|
90
sec/insecure/insecure.go
Normal file
90
sec/insecure/insecure.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Package insecure provides an insecure, unencrypted implementation of the the SecureConn and SecureTransport interfaces.
|
||||
//
|
||||
// Recommended only for testing and other non-production usage.
|
||||
package insecure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/libp2p/go-libp2p-core/sec"
|
||||
|
||||
ci "github.com/libp2p/go-libp2p-core/crypto"
|
||||
)
|
||||
|
||||
// ID is the multistream-select protocol ID that should be used when identifying
|
||||
// this security transport.
|
||||
const ID = "/plaintext/1.0.0"
|
||||
|
||||
// Transport is a no-op stream security transport. It provides no
|
||||
// security and simply mocks the security and identity methods to
|
||||
// return peer IDs known ahead of time.
|
||||
type Transport struct {
|
||||
id peer.ID
|
||||
}
|
||||
|
||||
// New constructs a new insecure transport.
|
||||
func New(id peer.ID) *Transport {
|
||||
return &Transport{
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
|
||||
// LocalPeer returns the transports local peer ID.
|
||||
func (t *Transport) LocalPeer() peer.ID {
|
||||
return t.id
|
||||
}
|
||||
|
||||
// LocalPrivateKey returns nil. This transport is not secure.
|
||||
func (t *Transport) LocalPrivateKey() ci.PrivKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SecureInbound *pretends to secure* an outbound connection to the given peer.
|
||||
func (t *Transport) SecureInbound(ctx context.Context, insecure net.Conn) (sec.SecureConn, error) {
|
||||
return &Conn{
|
||||
Conn: insecure,
|
||||
local: t.id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SecureOutbound *pretends to secure* an outbound connection to the given peer.
|
||||
func (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
|
||||
return &Conn{
|
||||
Conn: insecure,
|
||||
local: t.id,
|
||||
remote: p,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Conn is the connection type returned by the insecure transport.
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
local peer.ID
|
||||
remote peer.ID
|
||||
}
|
||||
|
||||
// LocalPeer returns the local peer ID.
|
||||
func (ic *Conn) LocalPeer() peer.ID {
|
||||
return ic.local
|
||||
}
|
||||
|
||||
// RemotePeer returns the remote peer ID if we initiated the dial. Otherwise, it
|
||||
// returns "" (because this connection isn't actually secure).
|
||||
func (ic *Conn) RemotePeer() peer.ID {
|
||||
return ic.remote
|
||||
}
|
||||
|
||||
// RemotePublicKey returns nil. This connection is not secure
|
||||
func (ic *Conn) RemotePublicKey() ci.PubKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalPrivateKey returns nil. This connection is not secure.
|
||||
func (ic *Conn) LocalPrivateKey() ci.PrivKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ sec.SecureTransport = (*Transport)(nil)
|
||||
var _ sec.SecureConn = (*Conn)(nil)
|
26
sec/security.go
Normal file
26
sec/security.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Package sec provides secure connection and transport interfaces for libp2p.
|
||||
package sec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
)
|
||||
|
||||
// SecureConn is an authenticated, encrypted connection.
|
||||
type SecureConn interface {
|
||||
net.Conn
|
||||
network.ConnSecurity
|
||||
}
|
||||
|
||||
// A SecureTransport turns inbound and outbound unauthenticated,
|
||||
// plain-text, native connections into authenticated, encrypted connections.
|
||||
type SecureTransport interface {
|
||||
// SecureInbound secures an inbound connection.
|
||||
SecureInbound(ctx context.Context, insecure net.Conn) (SecureConn, error)
|
||||
|
||||
// SecureOutbound secures an outbound connection.
|
||||
SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (SecureConn, error)
|
||||
}
|
113
transport/transport.go
Normal file
113
transport/transport.go
Normal file
@ -0,0 +1,113 @@
|
||||
// Package transport provides the Transport interface, which represents
|
||||
// the devices and network protocols used to send and recieve data.
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/mux"
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
// DialTimeout is the maximum duration a Dial is allowed to take.
|
||||
// This includes the time between dialing the raw network connection,
|
||||
// protocol selection as well the handshake, if applicable.
|
||||
var DialTimeout = 60 * time.Second
|
||||
|
||||
// AcceptTimeout is the maximum duration an Accept is allowed to take.
|
||||
// This includes the time between accepting the raw network connection,
|
||||
// protocol selection as well as the handshake, if applicable.
|
||||
var AcceptTimeout = 60 * time.Second
|
||||
|
||||
// A CapableConn represents a connection that has offers the basic
|
||||
// capabilities required by libp2p: stream multiplexing, encryption and
|
||||
// peer authentication.
|
||||
//
|
||||
// These capabilities may be natively provided by the transport, or they
|
||||
// may be shimmed via the "connection upgrade" process, which converts a
|
||||
// "raw" network connection into one that supports such capabilities by
|
||||
// layering an encryption channel and a stream multiplexer.
|
||||
//
|
||||
// CapableConn provides accessors for the local and remote multiaddrs used to
|
||||
// establish the connection and an accessor for the underlying Transport.
|
||||
type CapableConn interface {
|
||||
mux.MuxedConn
|
||||
network.ConnSecurity
|
||||
network.ConnMultiaddrs
|
||||
|
||||
// Transport returns the transport to which this connection belongs.
|
||||
Transport() Transport
|
||||
}
|
||||
|
||||
// Transport represents any device by which you can connect to and accept
|
||||
// connections from other peers.
|
||||
//
|
||||
// The Transport interface allows you to open connections to other peers
|
||||
// by dialing them, and also lets you listen for incoming connections.
|
||||
//
|
||||
// Connections returned by Dial and passed into Listeners are of type
|
||||
// CapableConn, which means that they have been upgraded to support
|
||||
// stream multiplexing and connection security (encryption and authentication).
|
||||
//
|
||||
// For a conceptual overview, see https://docs.libp2p.io/concepts/transport/
|
||||
type Transport interface {
|
||||
// Dial dials a remote peer. It should try to reuse local listener
|
||||
// addresses if possible but it may choose not to.
|
||||
Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (CapableConn, error)
|
||||
|
||||
// CanDial returns true if this transport knows how to dial the given
|
||||
// multiaddr.
|
||||
//
|
||||
// Returning true does not guarantee that dialing this multiaddr will
|
||||
// succeed. This function should *only* be used to preemptively filter
|
||||
// out addresses that we can't dial.
|
||||
CanDial(addr ma.Multiaddr) bool
|
||||
|
||||
// Listen listens on the passed multiaddr.
|
||||
Listen(laddr ma.Multiaddr) (Listener, error)
|
||||
|
||||
// Protocol returns the set of protocols handled by this transport.
|
||||
//
|
||||
// See the Network interface for an explanation of how this is used.
|
||||
Protocols() []int
|
||||
|
||||
// Proxy returns true if this is a proxy transport.
|
||||
//
|
||||
// See the Network interface for an explanation of how this is used.
|
||||
// TODO: Make this a part of the go-multiaddr protocol instead?
|
||||
Proxy() bool
|
||||
}
|
||||
|
||||
// Listener is an interface closely resembling the net.Listener interface. The
|
||||
// only real difference is that Accept() returns Conn's of the type in this
|
||||
// package, and also exposes a Multiaddr method as opposed to a regular Addr
|
||||
// method
|
||||
type Listener interface {
|
||||
Accept() (CapableConn, error)
|
||||
Close() error
|
||||
Addr() net.Addr
|
||||
Multiaddr() ma.Multiaddr
|
||||
}
|
||||
|
||||
// Network is an inet.Network with methods for managing transports.
|
||||
type TransportNetwork interface {
|
||||
network.Network
|
||||
|
||||
// AddTransport adds a transport to this Network.
|
||||
//
|
||||
// When dialing, this Network will iterate over the protocols in the
|
||||
// remote multiaddr and pick the first protocol registered with a proxy
|
||||
// transport, if any. Otherwise, it'll pick the transport registered to
|
||||
// handle the last protocol in the multiaddr.
|
||||
//
|
||||
// When listening, this Network will iterate over the protocols in the
|
||||
// local multiaddr and pick the *last* protocol registered with a proxy
|
||||
// transport, if any. Otherwise, it'll pick the transport registered to
|
||||
// handle the last protocol in the multiaddr.
|
||||
AddTransport(t Transport) error
|
||||
}
|
Loading…
Reference in New Issue
Block a user