How do I determine the PeerIDs key type?

How do I determine whether a libp2p peer ID is Ed25519? Or broadly, how is the key type determined when encoded using the legacy base58btc format?

  • The js-libp2p codebase determines based on the first three characters: 12D.
  • The base58btc characters 12D map to 2 bytes, which would be the first two bytes of the multihash, i.e. <varint hash function code><varint digest size in bytes>. So no information there on the key type?
  • Why is the key length for the public key 12D3KooWRBy97UB99e3J6hiPesre1MZeuNQvfan4gBziswrRJsNK 36 bytes and not 32 bytes like in the spec? The peerID spec says it follows the Ed25519 standard which says the length is 32 bytes.

I created the following codepen to demonstrate this:

How I got to these questions while making notes on PeerIDs and keys in libp2p

Some relevant links

Peer ID Spec
Multihash spec
Multicodec table

Decoding a Peer ID

According to the Peer ID spec:

If it starts with 1 or Qm, it’s a bare base58btc encoded multihash. Decode it according to the base58btc algorithm.

For example, QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa is a base58btc encoded multihash.

The multihash has the following format:

<varint hash function code><varint digest size in bytes><hash function output>

QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa decodes to code 18 (0x12 in hex) which is sha2-256, size 32 (bytes) of the hash

Another example, 12D3KooWRBy97UB99e3J6hiPesre1MZeuNQvfan4gBziswrRJsNK is a base58btc encoded multihash.

A bit confusing is that this is an identity multihash, i.e. it’s not hashed.

12D3KooWRBy97UB99e3J6hiPesre1MZeuNQvfan4gBziswrRJsNK decodes to code 0 (0x00 in hex) which is identity and has the size 36 bytes

Ok, it seems that the js-libp2p codebase determines the key type based on the type and digest size of the multihash.

However, that still leaves open the question of why ED25519 public keys are 36 bytes and not 32 bytes?

The ed public key itself is 32 bytes but as you’ve found 12D3Koo... is a base58btc encoded (no multibase prefix mind, wah wah) identity multihash, the digest of which are bytes that can be interpreted as a protobuf encoded structure with fields for the key type and data: js-libp2p/packages/crypto/src/keys/keys.proto at main · libp2p/js-libp2p · GitHub

So the extra bytes are the protobuf values signifying the presence/length of the Type and Data fields.

In js-land we could decode the digest to derive the type, but it’s a bit cheaper to just use the length - we only support RSA, Ed and Secp PeerIds currently and they are all different lengths.

A bit confusing is that this is an identity multihash, i.e. it’s not hashed.

It is hashed, but the hashing algorithm is i => i, e.g. an identity function.

We can call this a hash because i uniquely identifies i.

It has the nice hashing property that it’s collision-free, though it’s also reversible and not a fixed length so not terribly desirable as a cryptographic hash :stuck_out_tongue_winking_eye:

1 Like

One more question on the ProtoBuf definition.

How come the spec uses required while the js-libp2p implementation uses optional?

Is that because of the upgrade to ProtoBuf 3 which removed support for required? Do you know if this has any implications in terms of canonical serialisation, i.e. serialising the same key on different versions/implementations will results in a different PeerID?