The InterPlanetary File System (IPFS) is powered by content-addressed data, which by nature is immutable: changing an object would change its hash, and consequently its address, making it a different object altogether. However, there are several use cases where we benefit from having mutable data. This is where the InterPlanetary Naming System (IPNS) gets into the equation. IPNS records provide cryptographically verifiable, mutable pointers to objects.
Each time a file is modified, its content address changes. As a consequence, the address previously used for getting that file needs to be updated by who is using it. As this is not practical, IPNS was created to solve the problem.
InterPlanetary Naming System (IPNS) is based on Self-certifying File System (SFS). It consists of a PKI namespace, where a name is simply the hash of a public key. As a result, whoever controls the private key has full control over the name. Accordingly, records are signed by the private key and then distributed across the network (in IPFS, via the routing system). This is an egalitarian way to assign mutable names on the Internet at large, without any centralization whatsoever, or certificate authorities.
Implementations MUST support Ed25519 with signatures defined in [rfc8032]. Ed25519 is the current default key type.
Implementations SHOULD support RSA if they wish to interoperate with legacy IPNS names (RSA was used before Ed25519).
Implementations MAY support Secp256k1 and ECDSA for private use, but peers from the public IPFS swarm and DHT may not be able to resolve IPNS records signed by these optional key types.
When implementing support for key types, follow signature implementation notes from PeerID specs.
In all cases, the IPNS implementation MAY allow the user to enable/disable specific key types via configuration. Note that disabling support for compulsory key type will hinder IPNS interop.
IPNS encodes keys in protobuf
containing a KeyType
and the encoded key in a Data
field:
syntax = "proto2";
enum KeyType {
RSA = 0;
Ed25519 = 1;
Secp256k1 = 2;
ECDSA = 3;
}
// PublicKey
message PublicKey {
required KeyType Type = 1;
required bytes Data = 2;
}
// PrivateKey
message PrivateKey {
required KeyType Type = 1;
required bytes Data = 2;
}
Note:
Data
encoding depends on KeyType
(see Key Types)
Although private keys are not transmitted over the wire, the PrivateKey
serialization format used to store keys on disk is also included as a
reference for IPNS implementers who would like to import existing IPNS key
pairs.
PublicKey
and PrivateKey
structures were originally defined in
PeerID specification,
and are currently the same in both libp2p and IPNS. If the PeerID
specification ever changes in the future, definition from this file takes the
precedence.
IPNS Name is a Multihash
of a serialized PublicKey
.
If a PublicKey
is small, it can be inlined inside of a multihash using the identity
function.
This is the default behavior for Ed25519 keys.
IPNS Name should be represented as a
CIDv1 with libp2p-key
multicodec (code 0x72
),
and encoded using case-insensitive
Multibase such as Base36.
A good practice is to prefix IPNS Name with /ipns/
namespace,
and refer to IPNS addresses as /ipns/{ipns-name}
(or /ipns/{libp2p-key}
).
A logical IPNS Record is a data structure containing the following fields:
/ipns/{ipns-key}
path to another IPNS record, a DNSLink path (/ipns/example.com
) or an immutable IPFS path (/ipfs/baf...
).IpnsEntry.value
and inside the DAG-CBOR document in IpnsEntry.data[Value]
.0
, which indicates the validity
field contains the expiration date after which the IPNS record becomes invalid.validityType = 0
and include this value in both IpnsEntry.validityType
and inside the DAG-CBOR document at IpnsEntry.data[ValidityType]
.validityType = 0
1970-01-01T00:00:00.000000001Z
).IpnsEntry.validity
and inside the DAG-CBOR document at IpnsEntry.data[Validity]
.IpnsEntry.sequence
and inside the DAG-CBOR document at IpnsEntry.data[Sequence]
.IpnsEntry.ttl
and inside the DAG-CBOR document at IpnsEntry.data[TTL]
.identity
multihash), IpnsEntry.pubKey
field is redundant and MAY be skipped to save space.IpnsEntry.signatureV2
and follow signature creation and verification as described in Record Creation and Record Verification._
to avoid collisions with any new
mandatory fields that may be added in a future version of this
specification.IpnsEntry.data[_namespace][customfield]
.IPNS records are stored locally, as well as spread across the network, in order to be accessible to everyone.
For storing this structured data at rest and on the wire, we use IpnsEntry
encoded as protobuf, which is a language-neutral, platform neutral extensible mechanism for serializing structured data.
The extensible part of IPNS Record is placed in IpnsEntry.data
field, which itself is encoded using a strict and deterministic subset of CBOR named DAG-CBOR.
message IpnsEntry {
enum ValidityType {
// setting an EOL says "this record is valid until..."
EOL = 0;
}
// deserialized copy of data[Value]
optional bytes value = 1;
// legacy field, verify 'signatureV2' instead
optional bytes signatureV1 = 2;
// deserialized copies of data[ValidityType] and data[Validity]
optional ValidityType validityType = 3;
optional bytes validity = 4;
// deserialized copy of data[Sequence]
optional uint64 sequence = 5;
// record TTL in nanoseconds, a deserialized copy of data[TTL]
optional uint64 ttl = 6;
// in order for nodes to properly validate a record upon receipt, they need the public
// key associated with it. For old RSA keys, its easiest if we just send this as part of
// the record itself. For newer Ed25519 keys, the public key can be embedded in the
// IPNS Name itself, making this field unnecessary.
optional bytes pubKey = 7;
// the signature of the IPNS record
optional bytes signatureV2 = 8;
// extensible record data in DAG-CBOR format
optional bytes data = 9;
}
Notes:
IpnsEntry
protobuf and IpnsEntry.data
CBOR.
This should not be ignored, as it impacts interoperability with old software.IPNS implementations MUST support sending and receiving a serialized
IpnsEntry
less than or equal to 10 KiB in size.
Records over the limit MAY be ignored. Handling records larger than the limit is not recommended so as to keep compatibility with implementations and transports that follow this specification.
Implementations that want to interop with the public IPFS swarm MUST maintain backward compatibility for legacy consumers of IPNS records:
This means, for example, that changes made to the IpnsEntry
protobuf, or
validation logic should always be additive.
Future changes to this spec should include design decisions that allow legacy nodes to gracefully ignore new fields and verify compatible records using legacy logic.
Taking into consideration a p2p network, each peer should be able to publish IPNS records to the network, as well as to resolve the IPNS records published by other peers.
When a node intends to publish a record to the network, an IPNS record needs to be created first. The node needs to have a previously generated asymmetric key pair to create the record according to the data structure previously specified. It is important pointing out that the record needs to be uniquely identified in the network. As a result, the record identifier should be a hash of the public key used to sign the record.
As an IPNS record may be updated during its lifetime, a versioning related logic is needed during the publish process. As a consequence, the record must be stored locally, in order to enable the publisher to understand which is the most recent record published. Accordingly, before creating the record, the node must verify if a previous version of the record exists, and update the sequence value for the new record being created.
Once the record is created, it is ready to be spread through the network. This way, a peer can use whatever routing system it supports to make the record accessible to the remaining peers of the network.
The means of distribution are left unspecified. Implementations MAY choose to publish signed record using multiple routing systems, such as libp2p Kademlia DHT or [ipns-pubsub-router] (see Routing record).
On the other side, each peer must be able to get a record published by another node. It only needs to have the unique identifier used to publish the record to the network. Taking into account the routing system being used, we may obtain a set of occurrences of the record from the network. In this case, records can be compared using the sequence number, in order to obtain the most recent one.
As soon as the node has the most recent record, the signature and the validity must be verified, in order to conclude that the record is still valid and not compromised.
Finally, the network nodes may also republish their records, so that the records in the network continue to be valid to the other nodes.
IPNS record MUST be serialized as IpnsEntry
protobuf, and IpnsEntry.data
MUST be signed using the private key.
Creating a new IPNS record MUST follow the below steps:
IpnsEntry
and set value
, validity
, validityType
, sequence
, and ttl
sequence
and validity
Value
, Validity
, ValidityType
, Sequence
, and TTL
IpnsEntry.data
.
IpnsEntry.data
.IpnsEntry.pubKey
IpnsEntry.signatureV2
ipns-signature:
prefix (bytes in hex: 69706e732d7369676e61747572653a
) with raw CBOR bytes from IpnsEntry.data
IpnsEntry.signatureV2
IpnsEntry.signatureV1
(backward compatibility, for legacy software)
IpnsEntry.value
+ IpnsEntry.validity
+ string(IpnsEntry.validityType)
IpnsEntry.signatureV1
IpnsEntry
bytes sum to less than or equal to the size limit.Implementations MUST resolve IPNS Names using only verified records. Record's data and signature verification MUST be implemented as outlined below, and fail on the first error.
IpnsEntry
bytes sum to less than or equal to the size limit.IpnsEntry.signatureV2
and IpnsEntry.data
are present and are not emptyIpnsEntry.pubKey
or a cached entry in the local key store, if present.identity
multihash)identity
IpnsEntry.data
as a DAG-CBOR documentIpnsEntry
protobuf match deserialized ones from IpnsEntry.data
:
IpnsEntry.value
must match IpnsEntry.data[Value]
IpnsEntry.validity
must match IpnsEntry.data[Validity]
IpnsEntry.validityType
must match IpnsEntry.data[ValidityType]
IpnsEntry.sequence
must match IpnsEntry.data[Sequence]
IpnsEntry.ttl
must match IpnsEntry.data[TTL]
ipns-signature:
prefix (bytes in hex: 69706e732d7369676e61747572653a
) with raw CBOR bytes from IpnsEntry.data
IpnsEntry.signatureV2
against concatenation result from the previous step.Value in IpnsEntry.signatureV1
MUST be ignored.
Below are additional notes for implementers, documenting how IPNS is integrated within IPFS ecosystem.
This record is stored in the peer's repo datastore and contains the latest version of the IPNS record published by the provided key. This record is useful for republishing, as well as tracking the sequence number.
A legacy convention that implementers MAY want to follow is to store serialized IpnsEntry
under:
Key format: /ipns/base32(<HASH>)
Note: Base32 according to the [rfc4648].
The routing record is spread across the network according to the available routing systems. The two routing systems currently available in IPFS are the libp2p Kademlia DHT and [ipns-pubsub-router].
Key format: /ipns/BINARY_ID
/ipns/
is the ASCII prefix (bytes in hex: 2f69706e732f
)BINARY_ID
is the binary representation of IPNS NameAs the pubsub
topics must be utf-8
for interoperability among different implementations, IPNS over PubSub topics use additional wrapping /record/base64url-unpadded(key)