Skip to content
This repository has been archived by the owner on Sep 6, 2022. It is now read-only.

Commit

Permalink
feat: support encoding/decoding peer IDs as CIDs in text (#41)
Browse files Browse the repository at this point in the history
What:

1. Supports decoding CIDs (of libp2p keys) as peer IDs 
   (libp2p/specs#216)
2. Continues to encode peer IDs as base58 multihashes by default.
3. Adds functions for converting between peer IDs and CIDs.
4. Deprecates IDB58{Decode,Encode}, replacing them with {Decode,Encode}.

Why:

1. We _need_ to support multibase somehow, so we can put peer IDs in domains.
2. This makes peer IDs fully self describing. That is, the CID itself indicates that it's a hash of a libp2p public key.
3. It's much easier to upgrade wire protocols than text. This change punts
pids-as-cids on the wire down the road but that's something we can revisit if it ever becomes relevant. 

See libp2p/specs#111 for context.

Deviations from that issue:

* This _retains_ the current peer ID inlining of ed25519 keys. That turned out to be a nightmare, one I'd like to punt a bit more down the road.
* Likewise, this _punts_ the question of embedding the multi-hash algorithm in the public key.
  • Loading branch information
lidel authored Dec 10, 2019
2 parents edd5242 + 4184c18 commit ba9101b
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 7 deletions.
73 changes: 66 additions & 7 deletions peer/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"encoding/hex"
"errors"
"fmt"
"strings"

cid "github.com/ipfs/go-cid"
ic "github.com/libp2p/go-libp2p-core/crypto"
b58 "github.com/mr-tron/base58/base58"
mh "github.com/multiformats/go-multihash"
Expand Down Expand Up @@ -129,23 +131,24 @@ func IDFromBytes(b []byte) (ID, error) {
return ID(b), nil
}

// IDB58Decode accepts a base58-encoded multihash representing a peer ID
// and returns the decoded ID if the input is valid.
// IDB58Decode decodes a peer ID.
//
// Deprecated: Use Decode.
func IDB58Decode(s string) (ID, error) {
m, err := mh.FromB58String(s)
if err != nil {
return "", err
}
return ID(m), err
return Decode(s)
}

// IDB58Encode returns the base58-encoded multihash representation of the ID.
//
// Deprecated: Use Encode.
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.
//
// Deprecated: Don't raw-hex encode peer IDs, use base16 CIDs.
func IDHexDecode(s string) (ID, error) {
m, err := mh.FromHexString(s)
if err != nil {
Expand All @@ -155,10 +158,66 @@ func IDHexDecode(s string) (ID, error) {
}

// IDHexEncode returns the hex-encoded multihash representation of the ID.
//
// Deprecated: Don't raw-hex encode peer IDs, use base16 CIDs.
func IDHexEncode(id ID) string {
return hex.EncodeToString([]byte(id))
}

// Decode accepts an encoded peer ID and returns the decoded ID if the input is
// valid.
//
// The encoded peer ID can either be a CID of a key or a raw multihash (identity
// or sha256-256).
func Decode(s string) (ID, error) {
if strings.HasPrefix(s, "Qm") || strings.HasPrefix(s, "1") {
// base58 encoded sha256 or identity multihash
m, err := mh.FromB58String(s)
if err != nil {
return "", fmt.Errorf("failed to parse peer ID: %s", err)
}
return ID(m), nil
}

c, err := cid.Decode(s)
if err != nil {
return "", fmt.Errorf("failed to parse peer ID: %s", err)
}
return FromCid(c)
}

// Encode encodes a peer ID as a string.
//
// At the moment, it base58 encodes the peer ID but, in the future, it will
// switch to encoding it as a CID by default.
func Encode(id ID) string {
return IDB58Encode(id)
}

// FromCid converts a CID to a peer ID, if possible.
func FromCid(c cid.Cid) (ID, error) {
ty := c.Type()
if ty != cid.Libp2pKey {
s := cid.CodecToStr[ty]
if s == "" {
s = fmt.Sprintf("[unknown multicodec %d]", ty)
}
return "", fmt.Errorf("can't convert CID of type %s to a peer ID", s)
}
return ID(c.Hash()), nil
}

// ToCid encodes a peer ID as a CID of the public key.
//
// If the peer ID is invalid (e.g., empty), this will return the empty CID.
func ToCid(id ID) cid.Cid {
m, err := mh.Cast([]byte(id))
if err != nil {
return cid.Cid{}
}
return cid.NewCidV1(cid.Libp2pKey, m)
}

// IDFromPublicKey returns the Peer ID corresponding to the public key pk.
func IDFromPublicKey(pk ic.PubKey) (ID, error) {
b, err := pk.Bytes()
Expand Down
45 changes: 45 additions & 0 deletions peer/peer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,51 @@ func TestIDMatchesPrivateKey(t *testing.T) {
test(man)
}

func TestIDEncoding(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")
}

c := ToCid(p1)
p2, err := FromCid(c)
if err != nil || p1 != p2 {
t.Fatal("failed to round-trip through CID:", err)
}
p3, err := Decode(c.String())
if err != nil {
t.Fatal(err)
}
if p3 != p1 {
t.Fatal("failed to round trip through CID string")
}

if ks.hpkp != Encode(p1) {
t.Fatal("should always encode peer IDs as base58 by default")
}
}

test(gen1)
test(gen2)
test(man)

exampleCid := "bafkreifoybygix7fh3r3g5rqle3wcnhqldgdg4shzf4k3ulyw3gn7mabt4"
_, err := Decode(exampleCid)
if err == nil {
t.Fatal("should refuse to decode a non-peer ID CID")
}

c := ToCid("")
if c.Defined() {
t.Fatal("cid of empty peer ID should have been undefined")
}
}

func TestPublicKeyExtraction(t *testing.T) {
t.Skip("disabled until libp2p/go-libp2p-crypto#51 is fixed")
// Happy path
Expand Down

0 comments on commit ba9101b

Please sign in to comment.