Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: update api docs #333

Merged
merged 4 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 16 additions & 98 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,11 @@
- [Usage](#usage)
- [Create record](#create-record)
- [Validate record](#validate-record)
- [Embed public key to record](#embed-public-key-to-record)
- [Extract public key from record](#extract-public-key-from-record)
- [Marshal data with proto buffer](#marshal-data-with-proto-buffer)
- [Unmarshal data from proto buffer](#unmarshal-data-from-proto-buffer)
- [Validator](#validator)
- [API](#api)
- [Create record](#create-record-1)
- [Validate record](#validate-record-1)
- [Marshal data with proto buffer](#marshal-data-with-proto-buffer-1)
- [Unmarshal data from proto buffer](#unmarshal-data-from-proto-buffer-1)
- [Extract public key from record](#extract-public-key-from-record-1)
- [Namespace](#namespace)
- [API Docs](#api-docs)
- [Namespace](#namespace)
- [License](#license)
- [Contribute](#contribute)

Expand All @@ -53,42 +45,44 @@ This module contains all the necessary code for creating, understanding and vali
```js
import * as ipns from 'ipns'

const ipnsRecord = await ipns.create(privateKey, value, sequenceNumber, lifetime)
const ipnsRecord = await ipns.createIPNSRecord(privateKey, value, sequenceNumber, lifetime)
```

### Validate record
### Validate record against public key

```js
import * as ipns from 'ipns'
import { validate } from 'ipns/validator'

await ipns.validate(publicKey, marshalledData)
await validate(publicKey, marshalledRecord)
// if no error thrown, the record is valid
```

### Embed public key to record
### Validate record against routing key

This is useful when validating IPNS names that use RSA keys, whose public key is embedded in the record (rather than in the routing key as with Ed25519).

```js
import * as ipns from 'ipns'
import { ipnsValidator } from 'ipns/validator'

const ipnsRecordWithEmbeddedPublicKey = await ipns.embedPublicKey(publicKey, ipnsRecord)
await ipnsValidator(routingKey, marshalledRecord)
```

### Extract public key from record

```js
import * as ipns from 'ipns'

const publicKey = await ipns.extractPublicKey(peerId, ipnsRecord)
const publicKey = await ipns.extractPublicKeyFromIPNSRecord(peerId, ipnsRecord)
```

### Marshal data with proto buffer

```js
import * as ipns from 'ipns'

const ipnsRecord = await ipns.create(privateKey, value, sequenceNumber, lifetime)
const ipnsRecord = await ipns.createIPNSRecord(privateKey, value, sequenceNumber, lifetime)
// ...
const marshalledData = ipns.marshal(ipnsRecord)
const marshalledData = ipns.marshalIPNSRecord(ipnsRecord)
// ...
```

Expand All @@ -99,89 +93,16 @@ Returns the record data serialized.
```js
import * as ipns from 'ipns'

const ipnsRecord = ipns.unmarshal(storedData)
const ipnsRecord = ipns.unmarshalIPNSRecord(storedData)
```

Returns the `IPNSRecord` after being deserialized.

### Validator

```js
import * as ipns from 'ipns'

const validator = ipns.validator
```

Contains an object with `validate (marshalledData, key)` and `select (dataA, dataB)` functions.

The `validate` async function aims to verify if an IPNS record is valid. First the record is unmarshalled, then the public key is obtained and finally the record is validated (`signatureV2` of CBOR `data` is verified).

The `select` function is responsible for deciding which IPNS record is the best (newer) between two records. Both records are unmarshalled and their sequence numbers are compared. If the first record provided is the newer, the operation result will be `0`, otherwise the operation result will be `1`.

## API

### Create record

```js

ipns.create(privateKey, value, sequenceNumber, lifetime, options)
```

Create an IPNS record for being stored in a protocol buffer.

- `privateKey` ([PrivateKey](https://libp2p.github.io/js-libp2p/interfaces/_libp2p_interface.keys.PrivateKey.html)): key to be used for cryptographic operations.
- `value` (string): IPFS path of the object to be published.
- `sequenceNumber` (Number): number representing the current version of the record.
- `lifetime` (Number): lifetime of the record (in milliseconds).
- `options` (CreateOptions): additional creation options.

Returns a `Promise` that resolves to an object with a `IPNSRecord`.

### Validate record

```js
ipns.validate(publicKey, ipnsRecord)
```

Validate an IPNS record previously stored in a protocol buffer.

- `publicKey` ([PublicKey](https://libp2p.github.io/js-libp2p/interfaces/_libp2p_interface.keys.PublicKey.html)): key to be used for cryptographic operations.
- `ipnsRecord` (`IPNSRecord`): IPNS record (obtained using the create function).

Returns a `Promise`, which may be rejected if the validation was not successful.

### Marshal data with proto buffer

```js
const marshalledData = ipns.marshal(ipnsRecord)
```

Returns the serialized IPNS record.

- `ipnsRecord` (`IPNSRecord`): ipns record (obtained using the create function).

### Unmarshal data from proto buffer

```js
const data = ipns.unmarshal(storedData)
```

Returns a `IPNSRecord` after being serialized.

- `storedData` (Uint8Array): ipns record serialized.

### Extract public key from record

```js
const publicKey = await ipns.extractPublicKey(peerId, ipnsRecord)
```

Extract a public key from an IPNS record.
## API Docs

- `peerId` ([PeerId](https://libp2p.github.io/js-libp2p/types/_libp2p_interface.peer_id.PeerId.html)): peer identifier object.
- `ipnsRecord` (`IPNSRecord`): ipns record (obtained using the create function).
- <https://ipfs.github.io/js-ipns>

Returns a `Promise` which resolves to public key ([`PublicKey`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-keys/src/index.ts) ): may be used for cryptographic operations.

### Namespace

Expand All @@ -199,9 +120,6 @@ ipns.namespaceLength
// 6
```

## API Docs

- <https://ipfs.github.io/js-ipns>

## License

Expand Down
8 changes: 4 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ const defaultCreateOptions: CreateOptions = {
* The IPNS Record validity should follow the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision.
* Note: This function does not embed the public key. If you want to do that, use `EmbedPublicKey`.
*
* The passed value can be a CID, a PeerID or an arbitrary string path.
* The passed value can be a CID, a PublicKey or an arbitrary string path e.g. `/ipfs/...` or `/ipns/...`.
*
* * CIDs will be converted to v1 and stored in the record as a string similar to: `/ipfs/${cid}`
* * PeerIDs will create recursive records, eg. the record value will be `/ipns/${cidV1Libp2pKey}`
* * PublicKeys will create recursive records, eg. the record value will be `/ipns/${cidV1Libp2pKey}`
* * String paths will be stored in the record as-is, but they must start with `"/"`
*
* @param {PrivateKey} privateKey - the private key for signing the record.
Expand All @@ -174,10 +174,10 @@ export async function createIPNSRecord (privateKey: PrivateKey, value: CID | Pub
* Same as create(), but instead of generating a new Date, it receives the intended expiration time
* WARNING: nano precision is not standard, make sure the value in seconds is 9 orders of magnitude lesser than the one provided.
*
* The passed value can be a CID, a PeerID or an arbitrary string path.
* The passed value can be a CID, a PublicKey or an arbitrary string path e.g. `/ipfs/...` or `/ipns/...`.
*
* * CIDs will be converted to v1 and stored in the record as a string similar to: `/ipfs/${cid}`
* * PeerIDs will create recursive records, eg. the record value will be `/ipns/${cidV1Libp2pKey}`
* * PublicKeys will create recursive records, eg. the record value will be `/ipns/${cidV1Libp2pKey}`
* * String paths will be stored in the record as-is, but they must start with `"/"`
*
* @param {PrivateKey} privateKey - the private key for signing the record.
Expand Down
11 changes: 11 additions & 0 deletions src/selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ import NanoDate from 'timestamp-nano'
import { IpnsEntry } from './pb/ipns.js'
import { unmarshalIPNSRecord } from './utils.js'

/**
* Selects the latest valid IPNS record from an array of marshalled IPNS records.
*
* Records are sorted by:
* 1. Sequence number (higher takes precedence)
* 2. Validity time for EOL records with same sequence number (longer lived record takes precedence)
*
* @param key - The routing key for the IPNS record
* @param data - Array of marshalled IPNS records to select from
* @returns The index of the most valid record from the input array
*/
export function ipnsSelector (key: Uint8Array, data: Uint8Array[]): number {
const entries = data.map((buf, index) => ({
record: unmarshalIPNSRecord(buf),
Expand Down
26 changes: 17 additions & 9 deletions src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ const MAX_RECORD_SIZE = 1024 * 10
* Validates the given IPNS Record against the given public key. We need a "raw"
* record in order to be able to access to all of its fields.
*/
export const validate = async (publicKey: PublicKey, buf: Uint8Array): Promise<void> => {
export const validate = async (publicKey: PublicKey, marshalledRecord: Uint8Array): Promise<void> => {
// unmarshal ensures that (1) SignatureV2 and Data are present, (2) that ValidityType
// and Validity are of valid types and have a value, (3) that CBOR data matches protobuf
// if it's a V1+V2 record.
const record = unmarshalIPNSRecord(buf)
const record = unmarshalIPNSRecord(marshalledRecord)

// Validate Signature V2
let isValid
Expand Down Expand Up @@ -53,13 +53,21 @@ export const validate = async (publicKey: PublicKey, buf: Uint8Array): Promise<v
log('ipns record for %s is valid', record.value)
}

export async function ipnsValidator (key: Uint8Array, marshalledData: Uint8Array): Promise<void> {
if (marshalledData.byteLength > MAX_RECORD_SIZE) {
/**
* Validate the given IPNS record against the given routing key.
*
* @see https://specs.ipfs.tech/ipns/ipns-record/#routing-record for the binary format of the routing key
*
* @param routingKey - The routing key in binary format: binary(ascii(IPNS_PREFIX) + multihash(public key))
* @param marshalledRecord - The marshalled record to validate.
*/
export async function ipnsValidator (routingKey: Uint8Array, marshalledRecord: Uint8Array): Promise<void> {
if (marshalledRecord.byteLength > MAX_RECORD_SIZE) {
throw new RecordTooLargeError('The record is too large')
}

// try to extract public key from routing key
const routingMultihash = multihashFromIPNSRoutingKey(key)
const routingMultihash = multihashFromIPNSRoutingKey(routingKey)
let routingPubKey: PublicKey | undefined

// identity hash
Expand All @@ -68,19 +76,19 @@ export async function ipnsValidator (key: Uint8Array, marshalledData: Uint8Array
}

// extract public key from record
const receivedRecord = unmarshalIPNSRecord(marshalledData)
const receivedRecord = unmarshalIPNSRecord(marshalledRecord)
const recordPubKey = extractPublicKeyFromIPNSRecord(receivedRecord) ?? routingPubKey

if (recordPubKey == null) {
throw new InvalidEmbeddedPublicKeyError('Could not extract public key from IPNS record or routing key')
}

const routingKey = multihashToIPNSRoutingKey(recordPubKey.toMultihash())
const expectedRoutingKey = multihashToIPNSRoutingKey(recordPubKey.toMultihash())

if (!uint8ArrayEquals(key, routingKey)) {
if (!uint8ArrayEquals(expectedRoutingKey, routingKey)) {
throw new InvalidEmbeddedPublicKeyError('Embedded public key did not match routing key')
}

// Record validation
await validate(recordPubKey, marshalledData)
await validate(recordPubKey, marshalledRecord)
}
10 changes: 10 additions & 0 deletions test/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ describe('utils', () => {
output: '/ipns/k73ap3wtp70r7cd9ofyhwgogv1j96huvtvfnsof5spyfaaopkxmonumi4fckgguqr'
},

// path input
'/ipfs/CID path': {
input: '/ipfs/QmWEekX7EZLUd9VXRNMRXW3LXe4F6x7mB8oPxY5XLptrBq/docs/readme.md',
output: '/ipfs/QmWEekX7EZLUd9VXRNMRXW3LXe4F6x7mB8oPxY5XLptrBq/docs/readme.md'
},
'/ipns/CID path': {
input: '/ipns/k51qzi5uqu5djni72pr40dt64kxlh0zb8baat8h7dtdvkov66euc2lho0oidr3',
output: '/ipns/k51qzi5uqu5djni72pr40dt64kxlh0zb8baat8h7dtdvkov66euc2lho0oidr3'
},

// peer id input
'Ed25519 PeerId': {
input: peerIdFromString('12D3KooWKBpVwnRACfEsk6QME7dA5CZnFYVHQ7Zc927BEzuUekQe'),
Expand Down
Loading