Skip to content

Commit

Permalink
Merge pull request #1 from perun-network/libp2p_wire
Browse files Browse the repository at this point in the history
feat: libp2p wire/net Bus implementation
  • Loading branch information
NhoxxKienn authored May 3, 2024
2 parents 90c9ae7 + 138c737 commit d7c24dd
Show file tree
Hide file tree
Showing 12 changed files with 1,266 additions and 0 deletions.
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/perun-network/perun-libp2p-wire

go 1.16

require (
github.com/libp2p/go-libp2p v0.13.0
github.com/libp2p/go-libp2p-core v0.8.5
github.com/multiformats/go-multiaddr v0.3.3
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.9.0
perun.network/go-perun v0.11.0
polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37
)
680 changes: 680 additions & 0 deletions go.sum

Large diffs are not rendered by default.

62 changes: 62 additions & 0 deletions p2p/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package p2p

import (
"context"
"crypto/sha256"
"math/rand"

"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/host"
"github.com/pkg/errors"
"perun.network/go-perun/wire"
)

// Account represents a libp2p wire account.
type Account struct {
host.Host
privateKey crypto.PrivKey
}

// Address returns the account's address.
func (acc *Account) Address() wire.Address {
return &Address{acc.ID()}
}

// Sign signs the given message with the account's private key.
func (acc *Account) Sign(data []byte) ([]byte, error) {
// Extract the private key from the account.
if acc.privateKey == nil {
return nil, errors.New("private key not set")
}
hashed := sha256.Sum256(data)

signature, err := acc.privateKey.Sign(hashed[:])
if err != nil {
return nil, err
}
return signature, nil

}

// NewRandomAccount generates a new random account.
func NewRandomAccount(rng *rand.Rand) *Account {
// Creates a new RSA key pair for this host.
prvKey, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, rng)
if err != nil {
panic(err)
}

// Construct a new libp2p client for our relay-server.
// Identity(prvKey) - Use a RSA private key to generate the ID of the host.
// EnableRelay() - Enable relay system and configures itself as a node behind a NAT
client, err := libp2p.New(
context.Background(),
libp2p.Identity(prvKey),
)

if err != nil {
panic(err)
}
return &Account{client, prvKey}
}
21 changes: 21 additions & 0 deletions p2p/account_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package p2p

import (
"testing"

"github.com/stretchr/testify/assert"
pkgtest "polycry.pt/poly-go/test"
)

func TestNewAccount(t *testing.T) {
rng := pkgtest.Prng(t)
acc := NewRandomAccount(rng)
assert.NotNil(t, acc)
}

func getHost(t *testing.T) *Account {
rng := pkgtest.Prng(t)
acc := NewRandomAccount(rng)
assert.NotNil(t, acc)
return acc
}
77 changes: 77 additions & 0 deletions p2p/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package p2p

import (
"fmt"
"math/rand"

"github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/peer"
"perun.network/go-perun/wire"
)

// Address is a peer address for wire discovery.
type Address struct {
peer.ID
}

// NewAddress returns a new address.
func NewAddress(id peer.ID) *Address {
return &Address{ID: id}
}

// Equal returns whether the two addresses are equal.
func (a *Address) Equal(b wire.Address) bool {
bTyped, ok := b.(*Address)
if !ok {
panic("wrong type")
}

return a.ID == bTyped.ID
}

// Cmp compares the byte representation of two addresses. For `a.Cmp(b)`
// returns -1 if a < b, 0 if a == b, 1 if a > b.
func (a *Address) Cmp(b wire.Address) int {
bTyped, ok := b.(*Address)
if !ok {
panic("wrong type")
}
if a.ID < bTyped.ID {
return -1
} else if a.ID == bTyped.ID {
return 0
}
return 1

}

// NewRandomAddress returns a new random peer address.
func NewRandomAddress(rng *rand.Rand) *Address {
_, publicKey, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, rng)
if err != nil {
panic(err)
}

id, err := peer.IDFromPublicKey(publicKey)
if err != nil {
panic(err)
}
return &Address{id}
}

// Verify verifies the signature of a message.
func (a *Address) Verify(msg []byte, sig []byte) error {
publicKey, err := a.ExtractPublicKey()
if err != nil {
return fmt.Errorf("extracting public key: %w", err)
}

b, err := publicKey.Verify(msg, sig)
if err != nil {
return fmt.Errorf("verifying signature: %w", err)
}
if b {
return nil
}
return fmt.Errorf("signature verification failed")
}
18 changes: 18 additions & 0 deletions p2p/address_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package p2p_test

import (
"math/rand"
"testing"

"github.com/perun-network/perun-libp2p-wire/p2p"
"perun.network/go-perun/wire"
"perun.network/go-perun/wire/test"
)

func TestAddress(t *testing.T) {
test.TestAddressImplementation(t, func() wire.Address {
return p2p.NewAddress("")
}, func(rng *rand.Rand) wire.Address {
return p2p.NewRandomAddress(rng)
})
}
43 changes: 43 additions & 0 deletions p2p/bus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package p2p

import (
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/multiformats/go-multiaddr"
"github.com/pkg/errors"
"perun.network/go-perun/wire/net"
perunio "perun.network/go-perun/wire/perunio/serializer"
)

// Net contains the client's components for the P2P communication.
type Net struct {
*net.Bus
*Listener
*Dialer
MultiAddr string
}

// NewP2PBus creates a dialer, listener, and a bus for the given account `acc`
// and includes them in the returned P2P Net.
func NewP2PBus(acc *Account) (*Net, error) {
listener := NewP2PListener(acc)
dialer := NewP2PDialer(acc)
bus := net.NewBus(acc, dialer, perunio.Serializer())

multiAddr, err := getHostMA(acc)
if err != nil {
return nil, err
}

return &Net{Bus: bus, Dialer: dialer, Listener: listener, MultiAddr: multiAddr.String()}, nil
}

// getHostMA returns the first multiaddress of the given host.
func getHostMA(host host.Host) (multiaddr.Multiaddr, error) {
peerInfo := peer.AddrInfo{
ID: host.ID(),
Addrs: host.Addrs(),
}
addrs, err := peer.AddrInfoToP2pAddrs(&peerInfo)
return addrs[0], errors.Wrap(err, "converting peer info to multiaddress")
}
82 changes: 82 additions & 0 deletions p2p/dialer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package p2p

import (
"context"
"sync"

"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/peer"
ma "github.com/multiformats/go-multiaddr"
"github.com/pkg/errors"
"perun.network/go-perun/wire"
wirenet "perun.network/go-perun/wire/net"
pkgsync "polycry.pt/poly-go/sync"
)

// Dialer is a dialer for p2p connections.
type Dialer struct {
mutex sync.RWMutex // Protects peers.
peers map[wire.AddrKey]string
host host.Host
closer pkgsync.Closer
}

// NewP2PDialer creates a new dialer for the given account.
func NewP2PDialer(acc *Account) *Dialer {
return &Dialer{
host: acc,
peers: make(map[wire.AddrKey]string),
}
}

// Dial implements Dialer.Dial().
func (d *Dialer) Dial(ctx context.Context, addr wire.Address, serializer wire.EnvelopeSerializer) (wirenet.Conn, error) {
peerMA, ok := d.get(wire.Key(addr))
if !ok {
return nil, errors.New("peer not found")
}

_peerMA, err := ma.NewMultiaddr(peerMA)
if err != nil {
return nil, errors.Wrap(err, "failed to parse multiaddress of peer")
}

peerAddrInfo, err := peer.AddrInfoFromP2pAddr(_peerMA)
if err != nil {
return nil, errors.Wrap(err, "converting peer multiaddress to address info")
}
if err := d.host.Connect(ctx, *peerAddrInfo); err != nil {
return nil, errors.Wrap(err, "failed to dial peer: failed to connecting to peer")
}

s, err := d.host.NewStream(ctx, peerAddrInfo.ID, "/client")
if err != nil {
return nil, errors.Wrap(err, "failed to dial peer: failed to creating a new stream")
}

return wirenet.NewIoConn(s, serializer), nil
}

// Register registers a p2p multiaddress for a peer wire address.
func (d *Dialer) Register(addr wire.Address, p2pAddress string) {
d.mutex.Lock()
defer d.mutex.Unlock()

d.peers[wire.Key(addr)] = p2pAddress
}

// Close closes the dialer by closing the underlying libp2p host.
func (d *Dialer) Close() error {
if err := d.closer.Close(); err != nil {
return err
}
return d.host.Close()
}

// get returns the p2p multiaddress for the given address if registered.
func (d *Dialer) get(addr wire.AddrKey) (p2pAddress string, ok bool) {
d.mutex.RLock()
defer d.mutex.RUnlock()
p2pAddress, ok = d.peers[addr]
return
}
Loading

0 comments on commit d7c24dd

Please sign in to comment.