Description
Forks off #111 to focus on the backwards compatibility issue instead of the CID/PID design space.
Summary
Peer ID calculation has changed a couple of times over the past year.
- Initially, peer IDs were always sha2-256 multihashes of the public key.
- From go-ipfs 0.4.16-0.4.18, we used the identity multihash to "inline" small keys (specifically, ed25519 keys) into the peer ID under the assumption that nobody was using them.
- We reverted (2) in 0.4.19 dev as OpenBazaar was using ed25519 keys before 0.4.16.
- We are now planning on re-introducing (2) as:
a. Textile is also using ed25519 keys and is relying on 0.4.18 behavior.
b. OpenBazaar is using a forked go-ipfs network. We are adding a package-level flag so they can restore pre-0.4.16 behavior in their forked build. - Finally, we need to agree on a migration path such that all IPFS nodes use the same method to calculate peer IDs.
Goals
This all grew out of several requirements and wants:
Requirements
- We need to provide a way to for users to select a hash function other than sha256 when computing their peer ID.
- Given a public key, peer ID calculation must be deterministic.
- There must be a case-insensitive way to express peer IDs appearing in IPNS paths (for browsers). Currently, peer IDs are always base58 encoded which is not case-insensitive.
Wants
- We'd like a way to "inline" public keys into peer IDs (if the public key is small enough to fit comfortably). This will allow encrypting a message to a peer without needing to look up their key first.
- We'd like to be able to fetch keys with bitswap.
Definitions
- Inlining peer ID (working title...): A peer ID from which the associated
public key can be extracted. - sha256 peer ID: A peer ID created using the sha256 hash function.
Note: "inlining" peer IDs look like 1...
, sha256 peer IDs look like Qm...
.
Events
Below is an exhaustive history of this issue:
- In the beginning, peer IDs were sha256 multihashes of peer IDs.
- At some point, we added support for ed25519 keys in go-libp2p. The plan was
to embed them in peer IDs
(implement ed25519 support go-libp2p-crypto#5). However, we punted on
that. We never added support to go-ipfs
(Allow option to use ed25519 ipfs/kubo#3625). - OpenBazaar started using ed25519 keys anyways.
- We (go) added a separate function for calculating inlining peer IDs (in
libp2p/go-libp2p-peer#15) that embedded ed25519 keys. Unfortunately, that
wasn't usable because everyone needs to compute the same peer ID. - In libp2p/go-libp2p-peer#30, we removed this separate function and
switched to automatically embedding keys shorter than 42 bytes into peer IDs.
This way, everyone would deterministically calculate the same peer ID. We did
this by using the "identity" hash function instead of sha256. - Textile started using go-libp2p and go-ipfs (using ed25519 keys).
- OpenBazaar tried to rebase onto go-ipfs 0.4.18 and discovered that peer ID
calculation had changed. - After discussing the issue in libp2p/specs#111 and on a
call, we decided to revert 5. We were under the
impression that nobody else was using ed25519 keys given that go-ipfs doesn't
provide a way to generate ed25519 keys. - Textile reached out to @whyrusleeping about a weird bug they were seeing when
trying to connect two nodes. @whyrusleeping tried reproducing it got the
errordial attempt failed: <peer.ID Qm*yNGz7a> --> <peer.ID 12*FrJvar>
dial
attempt failed: connected to wrong peer". This is the inverse of the issue
OpenBazaar was having. - At the moment, it doesn't actually look like this is the issue Textile was
having, they're seeing timeouts:dial attempt failed: <peer.ID 12*xWYT4W> --> <peer.ID 12*YsNMRE> dial attempt failed: context deadline exceeded
and
dial attempt failed: <peer.ID 12*xWYT4W> --> <peer.ID 12*YsNMRE> dial attempt failed: dial tcp4 13.57.23.210:4001: i/o timeout
. The latter looks
like it comes from add a TCP connect timeout go-tcp-transport#24.
This issue covers the peer ID issue, not the timeout issues.
Current State
- Most of the (go) network is using the inlining from libp2p/go-libp2p-peer#30.
The latest go-ipfs and go-libp2p masters are not inlining keys into peer IDs.They are now (again) inlining keys.- OpenBazaar is using a forked (separate) network so, for now at least, they
don't actually need to interoperate with the rest of the network. - Textile is not using a forked network so they do need to interoperate. Textile is using peer IDs for identity but these could (potentially) be migrated. They are not using IPNS.
- IPLD-DID (decentralized identity) is using the new inlined keys for IPNS. However, it's unclear if they have many/any users who would be affected.
Affected Subsystems
This covers the affected subsystems and some hacky fixes that I don't recommend.
They're only there to illustrate the issue.
Outbound Connection
When establishing an outbound connection, go-libp2p will:
- Perform a secio handshake.
- Derive the remote peer's ID from their public key.
- Check if this derived peer ID is the target peer ID.
Textile is seeing this fail after updating go-libp2p because they're passing a
peer ID created using the identity hash function into go-libp2p while
go-libp2p-peer is calculating it using the sha256 multihash.
Example Fix: This could be fixed by a hack to convert a peer ID created using
the identity hash function to a sha256 one.
Inbound Connection
When receiving an inbound connection, we compute the peer's ID from their public key. That means:
- If we're running a 0.4.18 go-ipfs node, we'll compute inlining peer IDs for ed25519 keys.
- If we're running a 0.4.19-dev go-ipfs node, we'll compute sha256 peer IDs for ed25519 keys.
Example Fix: In practice, this "just works". That is, we use our version of the
peer's ID internally and don't really care what the other side thinks it's ID
is.
DHT
The DHT is the first place where we really care about calculating the same peer
IDs. If we don't, FindPeer
breaks.
Let's say there's some peer A that wants to connect to a peer B. Let's assume
that peer A and B have this peer ID inlining enabled but the rest of the network
doesn't.
When peer A tries to connect to peer B, it'll walk the DHT looking asking nodes if they either:
- Know of nodes closer to peer B.
- Know how to connect to peer B. Currently (in go-ipfs), only nodes that are
actually connected to peer B will respond.
In this case,
- Peer A will be able to walk the DHT all the way to a node directly connected
to peer B. That is, (1) will work. - That node will know peer B by a different name (ID) so it won't be able to do part (2).
Example Fix: So, if we have an inlining peer ID, we can extract the key and
compute the sha256 peer ID and try to look that up in the DHT. Unfortunately,
we can't go the other way. This fix would also be really hacky.
IPNS
I don't believe IPNS is affected but I haven't thought through it thoroughly. At
worst, we'd have to apply a fix similar to the outbound connection fix.
PubSub
Same as IPNS.
Multibase
Unfortunately, this whole issue also relates to the ask from IPFS In Web
Browsers to make IPNS (and peer IDs) use multibase. If we just have Qm...
IDs, we can avoid allocating Q
as a multibase prefix and we'll be fine.
However, inlining peer IDs start with 12
and 1
is already a valid (albeit
useless) multibase prefix for unary.
The real worry is that if we allow arbitrary mulithash functions, we need to
tackle the multibase issue before we start running into collisions. That is,
hash codes such that Base58Encode([hash code])
maps to some useful multibase
prefix.