From 714a6c753a0fc9ff4a625c19b8331f54a8d2f9e5 Mon Sep 17 00:00:00 2001 From: Mike Goelzer Date: Wed, 10 Oct 2018 17:07:40 +0100 Subject: [PATCH 01/23] docs: add peer id spec --- peer-ids/peer-ids.md | 95 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 peer-ids/peer-ids.md diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md new file mode 100644 index 000000000..c7e191032 --- /dev/null +++ b/peer-ids/peer-ids.md @@ -0,0 +1,95 @@ +# Spec: Peer Ids and Keys + +## Keys + + +Everything is based on a very simple protobuf in [libp2p/go-libp2p-crypto/pb/crypto.proto#L5](https://github.com/libp2p/go-libp2p-crypto/blob/master/pb/crypto.proto#L5): + +``` +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; +} +``` + +As you can see, this proto simply transmits a public/private key pair along with an enum specifying the type of keypair. + +#### Where it's used? + +In libp2p for signing messages. Categories of messages we sign: + - IPNS records + - PubSub messages (coming soon) + - SECIO handshake + +We also use these protos for generating peer ids, as discussed in the section below. + +## Peer Ids + +Here is the process by which we generate peer id's based on the public/private keypairs described above: + + 1. Encode the public key into the protobuf. + 2. Serialize the protobuf containing the public key into bytes using the [canonical protobuf encoding](https://developers.google.com/protocol-buffers/docs/encoding). + 3. If the length of the serialized bytes <= 42, then we compute the "identity" multihash of the serialized bytes. In other words, no hashing is performed, but the [multihash format is still followed](https://github.com/multiformats/multihash) (byte plus varint plus serialized bytes). The idea here is that if the serialized byte array is short enough, we can fit it in a multihash proto without having to condense it using a hash function. + 4. If the length is >42, then we hash it using it using the SHA256 multihash. + +## How Keys are Encoded + +Keys are passed around in code as byte arrays. Keys are encoded within these arrays differently depending on the type of key. + +The following sections describe each key type's encoding rules. + +### RSA + +We encode the public key using the DER-encoded PKIX format. + +We encode the private key as a PKCS1 key using ASN.1 DER. + +To sign a message, we first hash it with SHA-256 and then sign it using the RSASSA-PKCS1-V1.5-SIGN from RSA PKCS#1 v1.5. + +See [libp2p/go-libp2p-crypto/rsa.go](https://github.com/libp2p/go-libp2p-crypto/blob/master/rsa.go) for details + +### Ed25519 + +Ed25519 specifies the exact format for keys and signatures, so we do not do much additional encoding, except as noted below. + +We do not do any special additional encoding for Ed25519 public keys. + +The encoding for Ed25519 private keys is a little unusual. There are two formats that we encourage implementors to support: + + - Preferred method is a simple concatenation: `[private key bytes][public key bytes]` (64 bytes) + - Older versions of the libp2p code used the following format: `[private key][public key][public key]` (96 bytes). If you encounter this type of encoding, the proper way to process it is to compare the two public key strings (32 bytes each) and verify they are identical. If they are, then proceed as you would with the preferred method. If they do not match, reject or error out because the byte array is invalid. + +Ed25519 signatures follow the normal Ed25519 standard. + +See [libp2p/go-libp2p-crypto/ed25519.go](https://github.com/libp2p/go-libp2p-crypto/blob/master/ed25519.go) for details + +### Secp256k1 + +We use the standard Bitcoin EC encoding for Secp256k1 public and private keys. + +To sign a message, we hash the message with SHA 256, then sign it using the standard Bitcoin EC signature algorithm (BIP0062), and then use standard Bitcoin encoding. + +See [libp2p/go-libp2p-crypto/secp256k1.go](https://github.com/libp2p/go-libp2p-crypto/blob/master/secp256k1.go) for details. + +### ECDSA + +We encode the public key using ASN.1 DER. + +We encode the private key using DER-encoded PKIX. + +To sign a message, we hash the message with SHA 256, and then sign it with the ECDSA standard algorithm, then we encode it using DER-encoded ASN.1. + +See [libp2p/go-libp2p-crypto/ecdsa.go](https://github.com/libp2p/go-libp2p-crypto/blob/master/ecdsa.go) for details. + From 565767a75fec9c0d77c70b1daac7131632e87c7b Mon Sep 17 00:00:00 2001 From: Mike Goelzer Date: Wed, 10 Oct 2018 17:18:18 +0100 Subject: [PATCH 02/23] docs: clean up writing --- peer-ids/peer-ids.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index c7e191032..d69ec5d02 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -3,7 +3,7 @@ ## Keys -Everything is based on a very simple protobuf in [libp2p/go-libp2p-crypto/pb/crypto.proto#L5](https://github.com/libp2p/go-libp2p-crypto/blob/master/pb/crypto.proto#L5): +Our key pairs are transmitted on the wire using a simple protobuf defined in [libp2p/go-libp2p-crypto/pb/crypto.proto#L5](https://github.com/libp2p/go-libp2p-crypto/blob/master/pb/crypto.proto#L5): ``` enum KeyType { @@ -24,16 +24,16 @@ message PrivateKey { } ``` -As you can see, this proto simply transmits a public/private key pair along with an enum specifying the type of keypair. +As should be apparent from the above code block, this proto simply encodes for transmission a public/private key pair along with an enum specifying the type of keypair. #### Where it's used? -In libp2p for signing messages. Categories of messages we sign: +Keys are used in two places in libp2p. The first is for signing messages. Here are some examples of messages we sign: - IPNS records - PubSub messages (coming soon) - SECIO handshake -We also use these protos for generating peer ids, as discussed in the section below. +The second is for generating peer ids; this is discussed in the section below. ## Peer Ids @@ -44,7 +44,7 @@ Here is the process by which we generate peer id's based on the public/private k 3. If the length of the serialized bytes <= 42, then we compute the "identity" multihash of the serialized bytes. In other words, no hashing is performed, but the [multihash format is still followed](https://github.com/multiformats/multihash) (byte plus varint plus serialized bytes). The idea here is that if the serialized byte array is short enough, we can fit it in a multihash proto without having to condense it using a hash function. 4. If the length is >42, then we hash it using it using the SHA256 multihash. -## How Keys are Encoded +## How Keys are Encoded and Messages Signed Keys are passed around in code as byte arrays. Keys are encoded within these arrays differently depending on the type of key. From 902fbfe73ce14e3326cd01a7362873ce989885e3 Mon Sep 17 00:00:00 2001 From: Mike Goelzer Date: Wed, 10 Oct 2018 17:40:41 +0100 Subject: [PATCH 03/23] docs: fix @vyzo comment --- peer-ids/peer-ids.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index d69ec5d02..be69ab404 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -3,7 +3,7 @@ ## Keys -Our key pairs are transmitted on the wire using a simple protobuf defined in [libp2p/go-libp2p-crypto/pb/crypto.proto#L5](https://github.com/libp2p/go-libp2p-crypto/blob/master/pb/crypto.proto#L5): +Our key pairs are stored on disk using a simple protobuf defined in [libp2p/go-libp2p-crypto/pb/crypto.proto#L5](https://github.com/libp2p/go-libp2p-crypto/blob/master/pb/crypto.proto#L5): ``` enum KeyType { From 95c23543c462b397d0fc7e94e377ac8c96494ecf Mon Sep 17 00:00:00 2001 From: Mike Goelzer Date: Wed, 10 Oct 2018 17:44:08 +0100 Subject: [PATCH 04/23] docs: syntax highlighting --- peer-ids/peer-ids.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index be69ab404..33091b1ae 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -5,7 +5,7 @@ Our key pairs are stored on disk using a simple protobuf defined in [libp2p/go-libp2p-crypto/pb/crypto.proto#L5](https://github.com/libp2p/go-libp2p-crypto/blob/master/pb/crypto.proto#L5): -``` +```protobuf enum KeyType { RSA = 0; Ed25519 = 1; From d8459bcade78c9004b3ed2e96a78ff6310038907 Mon Sep 17 00:00:00 2001 From: Mike Goelzer Date: Thu, 11 Oct 2018 23:18:55 +0100 Subject: [PATCH 05/23] Key types should/may --- peer-ids/peer-ids.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index 33091b1ae..a1e2bfb1b 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -46,6 +46,14 @@ Here is the process by which we generate peer id's based on the public/private k ## How Keys are Encoded and Messages Signed +Four key types are supported: + - RSA + - Ed25519 + - Secp256k1 + - ECDSA + +Implementations SHOULD support RSA and Ed25519. Implementations MAY support Secp256k1 and ECDSA, but nodes using those keys may not be able to connect to all other nodes. + Keys are passed around in code as byte arrays. Keys are encoded within these arrays differently depending on the type of key. The following sections describe each key type's encoding rules. From e2dfbe22079863cac01b4ba4ab1fa37218da01e0 Mon Sep 17 00:00:00 2001 From: Mike Goelzer Date: Fri, 12 Oct 2018 12:28:51 +0100 Subject: [PATCH 06/23] clarify 42 byte rule --- peer-ids/peer-ids.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index a1e2bfb1b..58419bb58 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -44,6 +44,20 @@ Here is the process by which we generate peer id's based on the public/private k 3. If the length of the serialized bytes <= 42, then we compute the "identity" multihash of the serialized bytes. In other words, no hashing is performed, but the [multihash format is still followed](https://github.com/multiformats/multihash) (byte plus varint plus serialized bytes). The idea here is that if the serialized byte array is short enough, we can fit it in a multihash proto without having to condense it using a hash function. 4. If the length is >42, then we hash it using it using the SHA256 multihash. +For more information, refer to this block in [libp2p/go-libp2p-peer/peer.go](https://github.com/libp2p/go-libp2p-peer/blob/master/peer.go): + +``` +// MaxInlineKeyLength is the maximum length a key can be for it to be inlined in +// the peer ID. +// +// * When `len(pubKey.Bytes()) <= MaxInlineKeyLength`, the peer ID is the +// identity multihash hash of the public key. +// * When `len(pubKey.Bytes()) > MaxInlineKeyLength`, the peer ID is the +// sha2-256 multihash of the public key. +const MaxInlineKeyLength = 42 +``` + + ## How Keys are Encoded and Messages Signed Four key types are supported: From 6c318c930f3b2415b831c0f67c367b284c87a9b3 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Thu, 14 Mar 2019 14:24:54 -0400 Subject: [PATCH 07/23] remove references to private keys & storage formats --- peer-ids/peer-ids.md | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index 58419bb58..a2152d659 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -2,8 +2,8 @@ ## Keys - -Our key pairs are stored on disk using a simple protobuf defined in [libp2p/go-libp2p-crypto/pb/crypto.proto#L5](https://github.com/libp2p/go-libp2p-crypto/blob/master/pb/crypto.proto#L5): +Keys are serialized for transmission using a +[simple protobuf](https://github.com/libp2p/go-libp2p-crypto/blob/master/pb/crypto.proto#L5): ```protobuf enum KeyType { @@ -17,14 +17,9 @@ message PublicKey { required KeyType Type = 1; required bytes Data = 2; } - -message PrivateKey { - required KeyType Type = 1; - required bytes Data = 2; -} ``` -As should be apparent from the above code block, this proto simply encodes for transmission a public/private key pair along with an enum specifying the type of keypair. +This proto simply encodes for transmission a public key along with an enum specifying the type of key. The specific format of the `Data` field depends on the key type, and is [described below](#how-keys-are-encoded-and-messages-signed). #### Where it's used? @@ -37,7 +32,7 @@ The second is for generating peer ids; this is discussed in the section below. ## Peer Ids -Here is the process by which we generate peer id's based on the public/private keypairs described above: +Here is the process by which we generate peer ids based on the public keys described above: 1. Encode the public key into the protobuf. 2. Serialize the protobuf containing the public key into bytes using the [canonical protobuf encoding](https://developers.google.com/protocol-buffers/docs/encoding). @@ -76,30 +71,18 @@ The following sections describe each key type's encoding rules. We encode the public key using the DER-encoded PKIX format. -We encode the private key as a PKCS1 key using ASN.1 DER. - To sign a message, we first hash it with SHA-256 and then sign it using the RSASSA-PKCS1-V1.5-SIGN from RSA PKCS#1 v1.5. See [libp2p/go-libp2p-crypto/rsa.go](https://github.com/libp2p/go-libp2p-crypto/blob/master/rsa.go) for details ### Ed25519 -Ed25519 specifies the exact format for keys and signatures, so we do not do much additional encoding, except as noted below. - -We do not do any special additional encoding for Ed25519 public keys. - -The encoding for Ed25519 private keys is a little unusual. There are two formats that we encourage implementors to support: - - - Preferred method is a simple concatenation: `[private key bytes][public key bytes]` (64 bytes) - - Older versions of the libp2p code used the following format: `[private key][public key][public key]` (96 bytes). If you encounter this type of encoding, the proper way to process it is to compare the two public key strings (32 bytes each) and verify they are identical. If they are, then proceed as you would with the preferred method. If they do not match, reject or error out because the byte array is invalid. - -Ed25519 signatures follow the normal Ed25519 standard. - -See [libp2p/go-libp2p-crypto/ed25519.go](https://github.com/libp2p/go-libp2p-crypto/blob/master/ed25519.go) for details +Ed25519 specifies the exact format for keys and signatures, so we do not do any additional encoding for the public key. +Ed25519 signatures follow the normal [Ed25519 standard](https://tools.ietf.org/html/rfc8032#section-5.1). ### Secp256k1 -We use the standard Bitcoin EC encoding for Secp256k1 public and private keys. +We use the standard Bitcoin EC encoding for Secp256k1 public keys. To sign a message, we hash the message with SHA 256, then sign it using the standard Bitcoin EC signature algorithm (BIP0062), and then use standard Bitcoin encoding. @@ -109,8 +92,6 @@ See [libp2p/go-libp2p-crypto/secp256k1.go](https://github.com/libp2p/go-libp2p-c We encode the public key using ASN.1 DER. -We encode the private key using DER-encoded PKIX. - To sign a message, we hash the message with SHA 256, and then sign it with the ECDSA standard algorithm, then we encode it using DER-encoded ASN.1. See [libp2p/go-libp2p-crypto/ecdsa.go](https://github.com/libp2p/go-libp2p-crypto/blob/master/ecdsa.go) for details. From 878f2fa7e8b5e8a640ee00114270399b636bc55b Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Thu, 14 Mar 2019 14:25:22 -0400 Subject: [PATCH 08/23] remove links to go impl, add links to specs --- peer-ids/peer-ids.md | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index a2152d659..b16c463fc 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -39,19 +39,8 @@ Here is the process by which we generate peer ids based on the public keys descr 3. If the length of the serialized bytes <= 42, then we compute the "identity" multihash of the serialized bytes. In other words, no hashing is performed, but the [multihash format is still followed](https://github.com/multiformats/multihash) (byte plus varint plus serialized bytes). The idea here is that if the serialized byte array is short enough, we can fit it in a multihash proto without having to condense it using a hash function. 4. If the length is >42, then we hash it using it using the SHA256 multihash. -For more information, refer to this block in [libp2p/go-libp2p-peer/peer.go](https://github.com/libp2p/go-libp2p-peer/blob/master/peer.go): - -``` -// MaxInlineKeyLength is the maximum length a key can be for it to be inlined in -// the peer ID. -// -// * When `len(pubKey.Bytes()) <= MaxInlineKeyLength`, the peer ID is the -// identity multihash hash of the public key. -// * When `len(pubKey.Bytes()) > MaxInlineKeyLength`, the peer ID is the -// sha2-256 multihash of the public key. -const MaxInlineKeyLength = 42 -``` - +Peer Ids are multihashes, and they are often encoded into strings, most commonly using a base58 encoding with the alphabet used by bitcoin (`base58btc`). +An example of a `base58btc` encoded SHA256 peer id: `QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N`. ## How Keys are Encoded and Messages Signed @@ -71,9 +60,7 @@ The following sections describe each key type's encoding rules. We encode the public key using the DER-encoded PKIX format. -To sign a message, we first hash it with SHA-256 and then sign it using the RSASSA-PKCS1-V1.5-SIGN from RSA PKCS#1 v1.5. - -See [libp2p/go-libp2p-crypto/rsa.go](https://github.com/libp2p/go-libp2p-crypto/blob/master/rsa.go) for details +To sign a message, we first hash it with SHA-256 and then sign it using the [RSASSA-PKCS1-V1.5-SIGN](https://tools.ietf.org/html/rfc3447#section-8.2) method, as originally defined in [RSA PKCS#1 v1.5](https://tools.ietf.org/html/rfc2313). ### Ed25519 @@ -84,15 +71,10 @@ Ed25519 signatures follow the normal [Ed25519 standard](https://tools.ietf.org/h We use the standard Bitcoin EC encoding for Secp256k1 public keys. -To sign a message, we hash the message with SHA 256, then sign it using the standard Bitcoin EC signature algorithm (BIP0062), and then use standard Bitcoin encoding. - -See [libp2p/go-libp2p-crypto/secp256k1.go](https://github.com/libp2p/go-libp2p-crypto/blob/master/secp256k1.go) for details. +To sign a message, we hash the message with SHA 256, then sign it using the standard [Bitcoin EC signature algorithm (BIP0062)](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki), and then use [standard Bitcoin encoding](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#der-encoding). ### ECDSA We encode the public key using ASN.1 DER. -To sign a message, we hash the message with SHA 256, and then sign it with the ECDSA standard algorithm, then we encode it using DER-encoded ASN.1. - -See [libp2p/go-libp2p-crypto/ecdsa.go](https://github.com/libp2p/go-libp2p-crypto/blob/master/ecdsa.go) for details. - +To sign a message, we hash the message with SHA 256, and then sign it with the [ECDSA standard algorithm](https://tools.ietf.org/html/rfc6979), then we encode it using [DER-encoded ASN.1.](https://wiki.openssl.org/index.php/DER) From eda2295e13e4b36dc3f675a1329ece603753d703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 15 Mar 2019 10:42:34 -0700 Subject: [PATCH 09/23] peer ids: language nit Co-Authored-By: Stebalien --- peer-ids/peer-ids.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index b16c463fc..2129f9cca 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -36,7 +36,7 @@ Here is the process by which we generate peer ids based on the public keys descr 1. Encode the public key into the protobuf. 2. Serialize the protobuf containing the public key into bytes using the [canonical protobuf encoding](https://developers.google.com/protocol-buffers/docs/encoding). - 3. If the length of the serialized bytes <= 42, then we compute the "identity" multihash of the serialized bytes. In other words, no hashing is performed, but the [multihash format is still followed](https://github.com/multiformats/multihash) (byte plus varint plus serialized bytes). The idea here is that if the serialized byte array is short enough, we can fit it in a multihash proto without having to condense it using a hash function. + 3. If the length of the serialized bytes <= 42, then we compute the "identity" multihash of the serialized bytes. In other words, no hashing is performed, but the [multihash format is still followed](https://github.com/multiformats/multihash) (byte plus varint plus serialized bytes). The idea here is that if the serialized byte array is short enough, we can fit it in a multihash verbatim without having to condense it using a hash function. 4. If the length is >42, then we hash it using it using the SHA256 multihash. Peer Ids are multihashes, and they are often encoded into strings, most commonly using a base58 encoding with the alphabet used by bitcoin (`base58btc`). From 242afbe4083a274f26bc3cae8f975283341f57d7 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Tue, 19 Mar 2019 12:47:48 -0400 Subject: [PATCH 10/23] bring back private keys, add context about serialization --- peer-ids/peer-ids.md | 77 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index 2129f9cca..e5bc1843b 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -1,9 +1,26 @@ -# Spec: Peer Ids and Keys +# Spec: Peer Ids and Keys + +Status: **DRAFT** + +## Overview + +libp2p uses cryptographic key pairs to sign messages and derive unique +peer identities (or "peer ids"). + +This document describes the types of keys supported, how keys are serialized +for transmission, and how peer ids are generated from the hash of serialized +public keys. + +Although private keys are not transmitted over the wire, the serialization +format used to store keys on disk is also included as a reference for libp2p +implementors who would like to import existing libp2p key pairs. + +Key encodings and message signing semantics are +[covered below](#how-keys-are-encoded-and-messages-signed). ## Keys -Keys are serialized for transmission using a -[simple protobuf](https://github.com/libp2p/go-libp2p-crypto/blob/master/pb/crypto.proto#L5): +Our key pairs are wrapped in a [simple protobuf](https://github.com/libp2p/go-libp2p-crypto/blob/master/pb/crypto.proto#L5): ```protobuf enum KeyType { @@ -17,9 +34,24 @@ message PublicKey { required KeyType Type = 1; required bytes Data = 2; } + +message PrivateKey { + required KeyType Type = 1; + required bytes Data = 2; +} ``` -This proto simply encodes for transmission a public key along with an enum specifying the type of key. The specific format of the `Data` field depends on the key type, and is [described below](#how-keys-are-encoded-and-messages-signed). +The `PublicKey` and `PrivateKey` messages contain a `Data` field with serialized +keys, and a `Type` enum that specifies the type of key. + +Each key type has its own serialization format within the `Data` field, +[described below](#how-keys-are-encoded-and-messages-signed). + +Note that `PrivateKey` messages are never transmitted over the wire. +Current libp2p implementations store private keys on disk as a serialized +`PrivateKey` protobuf message. libp2p implementors who want to load existing +keys can use the `PrivateKey` message definition to deserialize private key +files. #### Where it's used? @@ -32,14 +64,18 @@ The second is for generating peer ids; this is discussed in the section below. ## Peer Ids -Here is the process by which we generate peer ids based on the public keys described above: +Here is the process by which we generate peer id's based on the public/private keypairs described above: 1. Encode the public key into the protobuf. 2. Serialize the protobuf containing the public key into bytes using the [canonical protobuf encoding](https://developers.google.com/protocol-buffers/docs/encoding). 3. If the length of the serialized bytes <= 42, then we compute the "identity" multihash of the serialized bytes. In other words, no hashing is performed, but the [multihash format is still followed](https://github.com/multiformats/multihash) (byte plus varint plus serialized bytes). The idea here is that if the serialized byte array is short enough, we can fit it in a multihash verbatim without having to condense it using a hash function. 4. If the length is >42, then we hash it using it using the SHA256 multihash. -Peer Ids are multihashes, and they are often encoded into strings, most commonly using a base58 encoding with the alphabet used by bitcoin (`base58btc`). +Peer Ids are multihashes, and they are often encoded into strings. +The canonical string representation of a Peer Id is a base58 encoding with +[the alphabet used by bitcoin](https://en.bitcoinwiki.org/wiki/Base58#Alphabet_Base58). +This encoding is sometimes abbreviated as `base58btc`. + An example of a `base58btc` encoded SHA256 peer id: `QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N`. ## How Keys are Encoded and Messages Signed @@ -52,24 +88,45 @@ Four key types are supported: Implementations SHOULD support RSA and Ed25519. Implementations MAY support Secp256k1 and ECDSA, but nodes using those keys may not be able to connect to all other nodes. -Keys are passed around in code as byte arrays. Keys are encoded within these arrays differently depending on the type of key. +Keys are encoded into byte arrays and serialized into the `Data` field of the +protobuf messages described above. The following sections describe each key type's encoding rules. +libp2p implementations MUST use the encoding described below when embedding +keys into the `PublicKey` and `PrivateKey` messages described above, whether +for transmission, Peer Id generation, or storage. + +Implementations can use whatever in-memory representation is convenient, +provided the encodings described below are used at the "I/O boundary". + +In addition to key encodings, the sections below cover the signing method used +when signing and verifying messages with each key type. + ### RSA We encode the public key using the DER-encoded PKIX format. +We encode the private key as a PKCS1 key using ASN.1 DER. + To sign a message, we first hash it with SHA-256 and then sign it using the [RSASSA-PKCS1-V1.5-SIGN](https://tools.ietf.org/html/rfc3447#section-8.2) method, as originally defined in [RSA PKCS#1 v1.5](https://tools.ietf.org/html/rfc2313). ### Ed25519 -Ed25519 specifies the exact format for keys and signatures, so we do not do any additional encoding for the public key. +Ed25519 specifies the exact format for keys and signatures, so we do not do much additional encoding, except as noted below. + +We do not do any special additional encoding for Ed25519 public keys. + +The encoding for Ed25519 private keys is a little unusual. There are two formats that we encourage implementors to support: + + - Preferred method is a simple concatenation: `[private key bytes][public key bytes]` (64 bytes) + - Older versions of the libp2p code used the following format: `[private key][public key][public key]` (96 bytes). If you encounter this type of encoding, the proper way to process it is to compare the two public key strings (32 bytes each) and verify they are identical. If they are, then proceed as you would with the preferred method. If they do not match, reject or error out because the byte array is invalid. + Ed25519 signatures follow the normal [Ed25519 standard](https://tools.ietf.org/html/rfc8032#section-5.1). ### Secp256k1 -We use the standard Bitcoin EC encoding for Secp256k1 public keys. +We use the standard Bitcoin EC encoding for Secp256k1 public and private keys. To sign a message, we hash the message with SHA 256, then sign it using the standard [Bitcoin EC signature algorithm (BIP0062)](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki), and then use [standard Bitcoin encoding](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#der-encoding). @@ -77,4 +134,6 @@ To sign a message, we hash the message with SHA 256, then sign it using the stan We encode the public key using ASN.1 DER. +We encode the private key using DER-encoded PKIX. + To sign a message, we hash the message with SHA 256, and then sign it with the [ECDSA standard algorithm](https://tools.ietf.org/html/rfc6979), then we encode it using [DER-encoded ASN.1.](https://wiki.openssl.org/index.php/DER) From 52057ac433ac94217dfc0b57b3213bfadd24aee9 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Tue, 19 Mar 2019 13:26:01 -0400 Subject: [PATCH 11/23] base key types MUST be supported --- peer-ids/peer-ids.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index e5bc1843b..fda65292b 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -86,7 +86,7 @@ Four key types are supported: - Secp256k1 - ECDSA -Implementations SHOULD support RSA and Ed25519. Implementations MAY support Secp256k1 and ECDSA, but nodes using those keys may not be able to connect to all other nodes. +Implementations MUST support RSA and Ed25519. Implementations MAY support Secp256k1 and ECDSA, but nodes using those keys may not be able to connect to all other nodes. Keys are encoded into byte arrays and serialized into the `Data` field of the protobuf messages described above. From 3302991590c5899444afff9e0f59feaa65e400d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 27 Mar 2019 12:11:44 +0000 Subject: [PATCH 12/23] peer id: implementations may configure key types --- peer-ids/peer-ids.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index fda65292b..a8a867c14 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -88,6 +88,9 @@ Four key types are supported: Implementations MUST support RSA and Ed25519. Implementations MAY support Secp256k1 and ECDSA, but nodes using those keys may not be able to connect to all other nodes. +In all cases, implementation MAY allow the user to enable/disable specific key types via configuration. +Note that disabling support for compulsory key types may hinder connectivity. + Keys are encoded into byte arrays and serialized into the `Data` field of the protobuf messages described above. From f277f4124d8644417dd701c8b7f3783f63318961 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Wed, 8 May 2019 15:02:01 -0400 Subject: [PATCH 13/23] note that we're using proto2 --- peer-ids/peer-ids.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index a8a867c14..68621e717 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -20,9 +20,12 @@ Key encodings and message signing semantics are ## Keys -Our key pairs are wrapped in a [simple protobuf](https://github.com/libp2p/go-libp2p-crypto/blob/master/pb/crypto.proto#L5): +Our key pairs are wrapped in a [simple protobuf](https://github.com/libp2p/go-libp2p-crypto/blob/master/pb/crypto.proto), +defined using the [Protobuf version 2 syntax](https://developers.google.com/protocol-buffers/docs/proto): ```protobuf +syntax = "proto2"; + enum KeyType { RSA = 0; Ed25519 = 1; From 046c7e88270fd350abcfb48d4848670c21f7eb4a Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Wed, 8 May 2019 15:02:40 -0400 Subject: [PATCH 14/23] soon has come :) --- peer-ids/peer-ids.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index 68621e717..680e453fb 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -60,7 +60,7 @@ files. Keys are used in two places in libp2p. The first is for signing messages. Here are some examples of messages we sign: - IPNS records - - PubSub messages (coming soon) + - PubSub messages - SECIO handshake The second is for generating peer ids; this is discussed in the section below. From 9bfb3704ce7a5d230989295020027c696caca5d9 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Wed, 8 May 2019 15:03:10 -0400 Subject: [PATCH 15/23] mention we're not using multibase for peer-ids --- peer-ids/peer-ids.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index 680e453fb..4030c1c40 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -81,6 +81,10 @@ This encoding is sometimes abbreviated as `base58btc`. An example of a `base58btc` encoded SHA256 peer id: `QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N`. +Note that some projects using libp2p will prefix "base encoded" strings with a +[multibase](https://github.com/multiformats/multibase) code that identifies the encoding base and alphabet. +Peer ids do not use multibase, and can be assumed to be encoded as `base58btc`. + ## How Keys are Encoded and Messages Signed Four key types are supported: From a7de2f63c7effab0b93be3400d74cf42d647855f Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Wed, 8 May 2019 15:10:45 -0400 Subject: [PATCH 16/23] tweak the description of peer id generation Adds "encode to byte array according to rules below" as first step, and makes explicit that we only use the public part of the keypair. --- peer-ids/peer-ids.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index 4030c1c40..d7f659813 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -67,12 +67,13 @@ The second is for generating peer ids; this is discussed in the section below. ## Peer Ids -Here is the process by which we generate peer id's based on the public/private keypairs described above: +Here is the process by which we generate peer ids based on the public component of the keypairs described above: - 1. Encode the public key into the protobuf. - 2. Serialize the protobuf containing the public key into bytes using the [canonical protobuf encoding](https://developers.google.com/protocol-buffers/docs/encoding). - 3. If the length of the serialized bytes <= 42, then we compute the "identity" multihash of the serialized bytes. In other words, no hashing is performed, but the [multihash format is still followed](https://github.com/multiformats/multihash) (byte plus varint plus serialized bytes). The idea here is that if the serialized byte array is short enough, we can fit it in a multihash verbatim without having to condense it using a hash function. - 4. If the length is >42, then we hash it using it using the SHA256 multihash. + 1. Encode the public key into a byte array according to the rules [described below](#how-keys-are-encoded-and-messages-signed) + 2. Construct a `PublicKey` protobuf message, setting the `Data` field to the encoded public key bytes, and also setting the correct `Type` field. + 3. Serialize the protobuf containing the public key into bytes using the [canonical protobuf encoding](https://developers.google.com/protocol-buffers/docs/encoding). + 4. If the length of the serialized bytes <= 42, then we compute the "identity" multihash of the serialized bytes. In other words, no hashing is performed, but the [multihash format is still followed](https://github.com/multiformats/multihash) (byte plus varint plus serialized bytes). The idea here is that if the serialized byte array is short enough, we can fit it in a multihash verbatim without having to condense it using a hash function. + 5. If the length is >42, then we hash it using it using the SHA256 multihash. Peer Ids are multihashes, and they are often encoded into strings. The canonical string representation of a Peer Id is a base58 encoding with From 1237100ee0862cedef209979ba4897e99b273964 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Wed, 8 May 2019 15:12:14 -0400 Subject: [PATCH 17/23] add note about deterministic encoding of PublicKey protobuf --- peer-ids/peer-ids.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index d7f659813..88f65c0dd 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -74,6 +74,25 @@ Here is the process by which we generate peer ids based on the public component 3. Serialize the protobuf containing the public key into bytes using the [canonical protobuf encoding](https://developers.google.com/protocol-buffers/docs/encoding). 4. If the length of the serialized bytes <= 42, then we compute the "identity" multihash of the serialized bytes. In other words, no hashing is performed, but the [multihash format is still followed](https://github.com/multiformats/multihash) (byte plus varint plus serialized bytes). The idea here is that if the serialized byte array is short enough, we can fit it in a multihash verbatim without having to condense it using a hash function. 5. If the length is >42, then we hash it using it using the SHA256 multihash. + +### Note about deterministic encoding: + +Deterministic encoding of the `PublicKey` message is desirable, as it ensures the same public key will always +result in the same peer id. + +The Protobuf specification does not provide sufficient guidance to ensure deterministic serialization of +messages. There are two factors that could lead to semantically identical messages having different serialized +values: field ordering, and the ability to specify the same field multiple times. + +In earlier versions of the Protobuf spec, serializers were encouraged to write known fields in sequential +order by field number, with unknown fields in arbitrary order after the ordered known fields. This guidance +has since been removed, however, libp2p implementors should use a protobuf encoder that provides this behavior. + +The ability to set a field multiple times (with the last value "winning" in the deserialized message), can lead +to different serializations of semantically identical messages. libp2p implementors are therefore encouraged to +set the fields in the `PublicKey` message only once before encoding, and may refuse to deserialize encoded `PublicKey` messages in which a field is set multiple times. + +### String representation Peer Ids are multihashes, and they are often encoded into strings. The canonical string representation of a Peer Id is a base58 encoding with From 870b71ac046b2774be8a90644d398d45c2c35ed8 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Wed, 22 May 2019 14:03:38 -0400 Subject: [PATCH 18/23] revise note about deterministic encoding --- peer-ids/peer-ids.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index 88f65c0dd..88a4de5f5 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -77,20 +77,24 @@ Here is the process by which we generate peer ids based on the public component ### Note about deterministic encoding: -Deterministic encoding of the `PublicKey` message is desirable, as it ensures the same public key will always -result in the same peer id. +Deterministic encoding of the `PublicKey` message is desirable, as it ensures +the same public key will always result in the same peer id. -The Protobuf specification does not provide sufficient guidance to ensure deterministic serialization of -messages. There are two factors that could lead to semantically identical messages having different serialized -values: field ordering, and the ability to specify the same field multiple times. +The Protobuf specification does not provide sufficient guidance to ensure +deterministic serialization of messages. Specifically, there are no guarantees +about field ordering, and it's possible to add any number of unknown fields to a +message. -In earlier versions of the Protobuf spec, serializers were encouraged to write known fields in sequential -order by field number, with unknown fields in arbitrary order after the ordered known fields. This guidance -has since been removed, however, libp2p implementors should use a protobuf encoder that provides this behavior. +A future revision to this spec may define a canonical encoding that is more +strict than the Protobuf encoding spec. -The ability to set a field multiple times (with the last value "winning" in the deserialized message), can lead -to different serializations of semantically identical messages. libp2p implementors are therefore encouraged to -set the fields in the `PublicKey` message only once before encoding, and may refuse to deserialize encoded `PublicKey` messages in which a field is set multiple times. +In the meantime, implementors SHOULD use a protobuf encoder that orders fields +by their field number. This is the default behavior in many protobuf encoders, +but should be verified before committing to an implementation. + +Additionally, implementors MUST avoid extending the `PublicKey` message with +additional fields, as the ordering of unknown fields is currently undefined +behavior. ### String representation From d14a44d5321529ab06c4649087cfbe22a7476809 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Wed, 22 May 2019 14:08:21 -0400 Subject: [PATCH 19/23] update status & generate TOC --- peer-ids/peer-ids.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index 88a4de5f5..86d811072 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -1,6 +1,35 @@ # Spec: Peer Ids and Keys -Status: **DRAFT** +**Table of Contents** + +- [Spec: Peer Ids and Keys](#spec-peer-ids-and-keys) + - [Status](#status) + - [Overview](#overview) + - [Keys](#keys) + - [-](#-) + - [Peer Ids](#peer-ids) + - [Note about deterministic encoding:](#note-about-deterministic-encoding) + - [String representation](#string-representation) + - [How Keys are Encoded and Messages Signed](#how-keys-are-encoded-and-messages-signed) + - [RSA](#rsa) + - [Ed25519](#ed25519) + - [Secp256k1](#secp256k1) + - [ECDSA](#ecdsa) + + +## Status + +Status: 3A - Recommendation, Active + +This document is an Active Recommendation and describes the current state of key +usage and peer id generation in libp2p. + +See [the lifecycle +document](https://github.com/libp2p/specs/00-framework-01-spec-lifecycle.md) for +more information on spec status. + + If you find inaccuracies or room for improvment, please [file an +issue.](https://github.com/libp2p/specs/issues/new) ## Overview From 10043ecc099fd4e9d74c2eb96291237ba280af1c Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Wed, 22 May 2019 15:26:39 -0400 Subject: [PATCH 20/23] fix TOC --- peer-ids/peer-ids.md | 1 - 1 file changed, 1 deletion(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index 86d811072..24559abab 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -6,7 +6,6 @@ - [Status](#status) - [Overview](#overview) - [Keys](#keys) - - [-](#-) - [Peer Ids](#peer-ids) - [Note about deterministic encoding:](#note-about-deterministic-encoding) - [String representation](#string-representation) From 6c4a58793732a06145edc960611df06e4c6865ec Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Thu, 23 May 2019 12:08:08 -0400 Subject: [PATCH 21/23] update status header --- peer-ids/peer-ids.md | 48 +++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index 24559abab..73d4d9476 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -1,13 +1,32 @@ -# Spec: Peer Ids and Keys +# Peer Ids and Keys -**Table of Contents** +| Lifecycle Stage | Maturity Level | Status | Latest Revision | +|-----------------|----------------|--------|-----------------| +| 3A | Recommendation | Active | r0, 2019-05-23 | -- [Spec: Peer Ids and Keys](#spec-peer-ids-and-keys) - - [Status](#status) + +**Authors**: [@mgoelzer][@mgoelzer], [@yusefnapora][@yusefnapora] + +**Interest Group**: [@raulk][@raulk], [@vyzo][@vyzo], [@Stebalien][@Stebalien] + +[@mgoelzer]: https://github.com/mgoelzer +[@yusefnapora]: https://github.com/yusefnapora +[@raulk]: https://github.com/raulk +[@vyzo]: https://github.com/vyzo +[@Stebalien]: https://github.com/Stebalien + +See the [lifecycle document](../00-framework-01-spec-lifecycle.md) for context +about maturity level and spec status. + +## Table of Contents + +- [Peer Ids and Keys](#peer-ids-and-keys) + - [Table of Contents](#table-of-contents) - [Overview](#overview) - [Keys](#keys) + - [Where are keys used?](#where-are-keys-used) - [Peer Ids](#peer-ids) - - [Note about deterministic encoding:](#note-about-deterministic-encoding) + - [Note about deterministic encoding](#note-about-deterministic-encoding) - [String representation](#string-representation) - [How Keys are Encoded and Messages Signed](#how-keys-are-encoded-and-messages-signed) - [RSA](#rsa) @@ -16,20 +35,6 @@ - [ECDSA](#ecdsa) -## Status - -Status: 3A - Recommendation, Active - -This document is an Active Recommendation and describes the current state of key -usage and peer id generation in libp2p. - -See [the lifecycle -document](https://github.com/libp2p/specs/00-framework-01-spec-lifecycle.md) for -more information on spec status. - - If you find inaccuracies or room for improvment, please [file an -issue.](https://github.com/libp2p/specs/issues/new) - ## Overview libp2p uses cryptographic key pairs to sign messages and derive unique @@ -84,7 +89,7 @@ Current libp2p implementations store private keys on disk as a serialized keys can use the `PrivateKey` message definition to deserialize private key files. -#### Where it's used? +### Where are keys used? Keys are used in two places in libp2p. The first is for signing messages. Here are some examples of messages we sign: - IPNS records @@ -103,7 +108,7 @@ Here is the process by which we generate peer ids based on the public component 4. If the length of the serialized bytes <= 42, then we compute the "identity" multihash of the serialized bytes. In other words, no hashing is performed, but the [multihash format is still followed](https://github.com/multiformats/multihash) (byte plus varint plus serialized bytes). The idea here is that if the serialized byte array is short enough, we can fit it in a multihash verbatim without having to condense it using a hash function. 5. If the length is >42, then we hash it using it using the SHA256 multihash. -### Note about deterministic encoding: +### Note about deterministic encoding Deterministic encoding of the `PublicKey` message is desirable, as it ensures the same public key will always result in the same peer id. @@ -199,3 +204,4 @@ We encode the public key using ASN.1 DER. We encode the private key using DER-encoded PKIX. To sign a message, we hash the message with SHA 256, and then sign it with the [ECDSA standard algorithm](https://tools.ietf.org/html/rfc6979), then we encode it using [DER-encoded ASN.1.](https://wiki.openssl.org/index.php/DER) + From 2ec086771dc4b3967df3de9f628c22cba96c646b Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Mon, 27 May 2019 11:36:55 -0400 Subject: [PATCH 22/23] use shortcut reference links for authors in header --- peer-ids/peer-ids.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/peer-ids/peer-ids.md b/peer-ids/peer-ids.md index 73d4d9476..11bac9766 100644 --- a/peer-ids/peer-ids.md +++ b/peer-ids/peer-ids.md @@ -5,9 +5,9 @@ | 3A | Recommendation | Active | r0, 2019-05-23 | -**Authors**: [@mgoelzer][@mgoelzer], [@yusefnapora][@yusefnapora] +**Authors**: [@mgoelzer], [@yusefnapora] -**Interest Group**: [@raulk][@raulk], [@vyzo][@vyzo], [@Stebalien][@Stebalien] +**Interest Group**: [@raulk], [@vyzo], [@Stebalien] [@mgoelzer]: https://github.com/mgoelzer [@yusefnapora]: https://github.com/yusefnapora From ed01eb13cde024b86629cfb00a10bb315e95cd64 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Wed, 19 Jun 2019 09:54:28 -0400 Subject: [PATCH 23/23] add peer id spec to index --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index a48d1d908..e725741bc 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,11 @@ expected lifecycle and document formatting. adopting specs. - [Document Header][spec_header] - A standard document header for libp2p specs. +### Core Abstractions and Types + +- [Peer Ids and Keys][spec_peerids] - Public key types & encodings, peer id calculation, and + message signing semantics + ### Protocols These specs define wire protocols that are used by libp2p for connectivity, @@ -93,3 +98,4 @@ you feel an issue isn't the appropriate place for your topic, please join our [spec_rendezvous]: ./rendezvous/README.md [spec_secio]: ./secio/README.md [spec_tls]: ./tls/tls.md +[spec_peerids]: ./peer-ids/peer-ids.md