Skip to content

Commit

Permalink
Merge Upstream Changes (#3)
Browse files Browse the repository at this point in the history
* deps: bump @libp2p/crypto from 3.0.4 to 4.0.0 (ipfs#304)

Bumps [@libp2p/crypto](https://github.com/libp2p/js-libp2p) from 3.0.4 to 4.0.0.
- [Release notes](https://github.com/libp2p/js-libp2p/releases)
- [Changelog](https://github.com/libp2p/js-libp2p/blob/main/.release-please.json)
- [Commits](libp2p/js-libp2p@perf-v3.0.4...utils-v4.0.0)

---
updated-dependencies:
- dependency-name: "@libp2p/crypto"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(release): 8.0.2 [skip ci]

## [8.0.2](ipfs/js-ipns@v8.0.1...v8.0.2) (2024-01-15)

### Dependencies

* bump @libp2p/crypto from 3.0.4 to 4.0.0 ([ipfs#304](ipfs#304)) ([ed83244](ipfs@ed83244))

* fix: mark package as side-effect free (ipfs#305)

[Tree shaking](https://webpack.js.org/guides/tree-shaking/) results in smaller web bundles by deleting unused code.

This module is side-effect free so mark it as such to signal to bundlers that unused exports can be excluded from bundles.

* chore(release): 8.0.3 [skip ci]

## [8.0.3](ipfs/js-ipns@v8.0.2...v8.0.3) (2024-01-16)

### Bug Fixes

* mark package as side-effect free ([ipfs#305](ipfs#305)) ([a389fe8](ipfs@a389fe8))

* fix: log type as string (ipfs#306)

The value field is a string so it needs to be logged as `%s` - logging as `%b` will throw.

* chore(release): 8.0.4 [skip ci]

## [8.0.4](ipfs/js-ipns@v8.0.3...v8.0.4) (2024-01-18)

### Bug Fixes

* log type as string ([ipfs#306](ipfs#306)) ([de68e4c](ipfs@de68e4c))

* fix!: treat validity as opaque (ipfs#307)

Instead of parsing the validity field of an IPNS record as a timestamp
during unmarshalling, treat it as an opaque string value.

This ensures we can round-trip records supplied by other systems which
may have a different interpretation of rfc3339 dates, for example.

It also means we can handle different types of validity types, not
just EOL.

BREAKING CHANGE: the validity field is now a string

* chore(release): 9.0.0 [skip ci]

## [9.0.0](ipfs/js-ipns@v8.0.4...v9.0.0) (2024-01-18)

### ⚠ BREAKING CHANGES

* the validity field is now a string

### Bug Fixes

* treat validity as opaque ([ipfs#307](ipfs#307)) ([461190e](ipfs@461190e))

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net>
Co-authored-by: Alex Potsides <alex@achingbrain.net>
  • Loading branch information
4 people authored Jan 25, 2024
1 parent 01f3f2d commit c2b48c9
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 84 deletions.
2 changes: 1 addition & 1 deletion .aegir.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
/** @type {import('aegir').PartialOptions} */
export default {
build: {
bundlesizeMax: '143KB'
bundlesizeMax: '60KB'
}
}
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
## [9.0.0](https://github.com/ipfs/js-ipns/compare/v8.0.4...v9.0.0) (2024-01-18)


### ⚠ BREAKING CHANGES

* the validity field is now a string

### Bug Fixes

* treat validity as opaque ([#307](https://github.com/ipfs/js-ipns/issues/307)) ([461190e](https://github.com/ipfs/js-ipns/commit/461190e215173e0ac2aad1dca107de5cb65a52ef))

## [8.0.4](https://github.com/ipfs/js-ipns/compare/v8.0.3...v8.0.4) (2024-01-18)


### Bug Fixes

* log type as string ([#306](https://github.com/ipfs/js-ipns/issues/306)) ([de68e4c](https://github.com/ipfs/js-ipns/commit/de68e4c0601702fb5d567a97e305b26f65c34fc2))

## [8.0.3](https://github.com/ipfs/js-ipns/compare/v8.0.2...v8.0.3) (2024-01-16)


### Bug Fixes

* mark package as side-effect free ([#305](https://github.com/ipfs/js-ipns/issues/305)) ([a389fe8](https://github.com/ipfs/js-ipns/commit/a389fe8f0e6dff4867ef22b6ddada43880476754))

## [8.0.2](https://github.com/ipfs/js-ipns/compare/v8.0.1...v8.0.2) (2024-01-15)


### Dependencies

* bump @libp2p/crypto from 3.0.4 to 4.0.0 ([#304](https://github.com/ipfs/js-ipns/issues/304)) ([ed83244](https://github.com/ipfs/js-ipns/commit/ed832448a9c903dc2ea0dd6158cc73211eacded7))

## [8.0.1](https://github.com/ipfs/js-ipns/compare/v8.0.0...v8.0.1) (2024-01-12)


Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ipns",
"version": "8.0.1",
"version": "9.0.0",
"description": "IPNS record definitions",
"author": "Vasco Santos <vasco.santos@moxy.studio>",
"license": "Apache-2.0 OR MIT",
Expand Down Expand Up @@ -166,7 +166,7 @@
"docs:no-publish": "NODE_OPTIONS=--max_old_space_size=8192 aegir docs --publish false"
},
"dependencies": {
"@libp2p/crypto": "^3.0.3",
"@libp2p/crypto": "^4.0.0",
"@libp2p/interface": "^1.1.0",
"@libp2p/logger": "^4.0.3",
"@libp2p/peer-id": "^4.0.3",
Expand All @@ -183,5 +183,6 @@
"@libp2p/peer-id-factory": "^4.0.2",
"aegir": "^42.1.1",
"protons": "^7.3.3"
}
},
"sideEffects": false
}
21 changes: 11 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface IPNSRecordV1V2 {
/**
* expiration datetime for the record in RFC3339 format
*/
validity: NanoDate
validity: string

/**
* number representing the version of the record
Expand Down Expand Up @@ -85,9 +85,10 @@ export interface IPNSRecordV2 {
validityType: IpnsEntry.ValidityType

/**
* expiration datetime for the record in RFC3339 format
* If the validity type is EOL, this is the expiration datetime for the record
* in RFC3339 format
*/
validity: NanoDate
validity: string

/**
* number representing the version of the record
Expand Down Expand Up @@ -171,7 +172,7 @@ export async function create (peerId: PeerId, value: CID | PeerId | string, seq:
const validityType = IpnsEntry.ValidityType.EOL
const lifetimeNs = typeof options.lifetimeNs === "bigint" ? options.lifetimeNs : DEFAULT_TTL

return _create(peerId, value, seq, validityType, expirationDate, lifetimeNs, options)
return _create(peerId, value, seq, validityType, expirationDate.toString(), lifetimeNs, options)
}

/**
Expand All @@ -198,12 +199,12 @@ export async function createWithExpiration (peerId: PeerId, value: CID | PeerId
const validityType = IpnsEntry.ValidityType.EOL
const lifetimeNs = typeof options.lifetimeNs === "bigint" ? options.lifetimeNs : DEFAULT_TTL

return _create(peerId, value, seq, validityType, expirationDate, lifetimeNs, options)
return _create(peerId, value, seq, validityType, expirationDate.toString(), lifetimeNs, options)
}

const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, validityType: IpnsEntry.ValidityType, expirationDate: NanoDate, ttl: bigint, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> => {
const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, validityType: IpnsEntry.ValidityType, validity: string, ttl: bigint, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> => {
seq = BigInt(seq)
const isoValidity = uint8ArrayFromString(expirationDate.toString())
const isoValidity = uint8ArrayFromString(validity)
const normalizedValue = normalizeValue(value)
const encodedValue = uint8ArrayFromString(normalizedValue)

Expand All @@ -212,7 +213,7 @@ const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number
}

const privateKey = await unmarshalPrivateKey(peerId.privateKey)
const data = createCborData(encodedValue, isoValidity, validityType, seq, ttl)
const data = createCborData(encodedValue, validityType, isoValidity, seq, ttl)
const sigData = ipnsRecordDataForV2Sig(data)
const signatureV2 = await privateKey.sign(sigData)
let pubKey: Uint8Array | undefined
Expand All @@ -233,7 +234,7 @@ const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number
const record: IPNSRecord = {
value: normalizedValue,
signatureV1,
validity: expirationDate,
validity,
validityType,
sequence: seq,
ttl,
Expand All @@ -249,7 +250,7 @@ const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number
} else {
const record: IPNSRecordV2 = {
value: normalizedValue,
validity: expirationDate,
validity,
validityType,
sequence: seq,
ttl,
Expand Down
20 changes: 12 additions & 8 deletions src/selector.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import NanoDate from 'timestamp-nano'
import { IpnsEntry } from './pb/ipns.js'
import { unmarshal } from './utils.js'

export function ipnsSelector (key: Uint8Array, data: Uint8Array[]): number {
Expand All @@ -21,16 +23,18 @@ export function ipnsSelector (key: Uint8Array, data: Uint8Array[]): number {
return 1
}

// choose longer lived record if sequence numbers the same
const recordAValidityDate = a.record.validity.toDate()
const recordBValidityDate = b.record.validity.toDate()
if (a.record.validityType === IpnsEntry.ValidityType.EOL && b.record.validityType === IpnsEntry.ValidityType.EOL) {
// choose longer lived record if sequence numbers the same
const recordAValidityDate = NanoDate.fromString(a.record.validity).toDate()
const recordBValidityDate = NanoDate.fromString(b.record.validity).toDate()

if (recordAValidityDate.getTime() > recordBValidityDate.getTime()) {
return -1
}
if (recordAValidityDate.getTime() > recordBValidityDate.getTime()) {
return -1
}

if (recordAValidityDate.getTime() < recordBValidityDate.getTime()) {
return 1
if (recordAValidityDate.getTime() < recordBValidityDate.getTime()) {
return 1
}
}

return 0
Expand Down
61 changes: 3 additions & 58 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as cborg from 'cborg'
import errCode from 'err-code'
import { base36 } from 'multiformats/bases/base36'
import { CID } from 'multiformats/cid'
import NanoDate from 'timestamp-nano'
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
Expand All @@ -20,53 +19,6 @@ const log = logger('ipns:utils')
const IPNS_PREFIX = uint8ArrayFromString('/ipns/')
const LIBP2P_CID_CODEC = 114

/**
* Convert a JavaScript date into an `RFC3339Nano` formatted
* string
*/
export function toRFC3339 (time: Date): string {
const year = time.getUTCFullYear()
const month = String(time.getUTCMonth() + 1).padStart(2, '0')
const day = String(time.getUTCDate()).padStart(2, '0')
const hour = String(time.getUTCHours()).padStart(2, '0')
const minute = String(time.getUTCMinutes()).padStart(2, '0')
const seconds = String(time.getUTCSeconds()).padStart(2, '0')
const milliseconds = time.getUTCMilliseconds()
const nanoseconds = milliseconds * 1000 * 1000

return `${year}-${month}-${day}T${hour}:${minute}:${seconds}.${nanoseconds}Z`
}

/**
* Parses a date string formatted as `RFC3339Nano` into a
* JavaScript Date object
*/
export function parseRFC3339 (time: string): Date {
const rfc3339Matcher = new RegExp(
// 2006-01-02T
'(\\d{4})-(\\d{2})-(\\d{2})T' +
// 15:04:05
'(\\d{2}):(\\d{2}):(\\d{2})' +
// .999999999Z
'\\.(\\d+)Z'
)
const m = String(time).trim().match(rfc3339Matcher)

if (m == null) {
throw new Error('Invalid format')
}

const year = parseInt(m[1], 10)
const month = parseInt(m[2], 10) - 1
const date = parseInt(m[3], 10)
const hour = parseInt(m[4], 10)
const minute = parseInt(m[5], 10)
const second = parseInt(m[6], 10)
const millisecond = parseInt(m[7].padEnd(6, '0').slice(0, 3), 10)

return new Date(Date.UTC(year, month, date, hour, minute, second, millisecond))
}

/**
* Extracts a public key from the passed PeerId, falling
* back to the pubKey embedded in the ipns record
Expand Down Expand Up @@ -129,7 +81,7 @@ export const marshal = (obj: IPNSRecord | IPNSRecordV2): Uint8Array => {
value: uint8ArrayFromString(obj.value),
signatureV1: obj.signatureV1,
validityType: obj.validityType,
validity: uint8ArrayFromString(obj.validity.toString()),
validity: uint8ArrayFromString(obj.validity),
sequence: obj.sequence,
ttl: obj.ttl,
pubKey: obj.pubKey,
Expand Down Expand Up @@ -167,14 +119,7 @@ export function unmarshal (buf: Uint8Array): IPNSRecord {

const data = parseCborData(message.data)
const value = normalizeValue(data.Value)

let validity
try {
validity = NanoDate.fromDate(parseRFC3339(uint8ArrayToString(data.Validity)))
} catch (e) {
log.error('unrecognized validity format (not an rfc3339 format)')
throw errCode(new Error('unrecognized validity format (not an rfc3339 format)'), ERRORS.ERR_UNRECOGNIZED_FORMAT)
}
const validity = uint8ArrayToString(data.Validity)

if (message.value != null && message.signatureV1 != null) {
// V1+V2
Expand Down Expand Up @@ -219,7 +164,7 @@ export const peerIdFromRoutingKey = (key: Uint8Array): PeerId => {
return peerIdFromBytes(key.slice(IPNS_PREFIX.length))
}

export const createCborData = (value: Uint8Array, validity: Uint8Array, validityType: string, sequence: bigint, ttl: bigint): Uint8Array => {
export const createCborData = (value: Uint8Array, validityType: IpnsEntry.ValidityType, validity: Uint8Array, sequence: bigint, ttl: bigint): Uint8Array => {
let ValidityType

if (validityType === IpnsEntry.ValidityType.EOL) {
Expand Down
5 changes: 3 additions & 2 deletions src/validator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { logger } from '@libp2p/logger'
import errCode from 'err-code'
import NanoDate from 'timestamp-nano'
import * as ERRORS from './errors.js'
import { IpnsEntry } from './pb/ipns.js'
import { extractPublicKey, ipnsRecordDataForV2Sig, unmarshal, peerIdFromRoutingKey } from './utils.js'
Expand Down Expand Up @@ -37,7 +38,7 @@ export const validate = async (publicKey: PublicKey, buf: Uint8Array): Promise<v

// Validate according to the validity type
if (record.validityType === IpnsEntry.ValidityType.EOL) {
if (record.validity.toDate().getTime() < Date.now()) {
if (NanoDate.fromString(record.validity).toDate().getTime() < Date.now()) {
log.error('record has expired')
throw errCode(new Error('record has expired'), ERRORS.ERR_IPNS_EXPIRED_RECORD)
}
Expand All @@ -46,7 +47,7 @@ export const validate = async (publicKey: PublicKey, buf: Uint8Array): Promise<v
throw errCode(new Error('unrecognized validity type'), ERRORS.ERR_UNRECOGNIZED_VALIDITY)
}

log('ipns record for %b is valid', record.value)
log('ipns record for %s is valid', record.value)
}

export async function ipnsValidator (key: Uint8Array, marshalledData: Uint8Array): Promise<void> {
Expand Down
17 changes: 17 additions & 0 deletions test/conformance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,21 @@ describe('conformance', function () {

expect(record.value).to.equal('/ipfs/bafkqadtwgiww63tmpeqhezldn5zgi')
})

it('should round trip fixtures', () => {
const fixtures = [
'test/fixtures/k51qzi5uqu5dlkw8pxuw9qmqayfdeh4kfebhmreauqdc6a7c3y7d5i9fi8mk9w_v1-v2.ipns-record',
'test/fixtures/k51qzi5uqu5diamp7qnnvs1p1gzmku3eijkeijs3418j23j077zrkok63xdm8c_v1-v2-broken-signature-v2.ipns-record',
'test/fixtures/k51qzi5uqu5dilgf7gorsh9vcqqq4myo6jd4zmqkuy9pxyxi5fua3uf7axph4y_v1-v2-broken-signature-v1.ipns-record',
'test/fixtures/k51qzi5uqu5dit2ku9mutlfgwyz8u730on38kd10m97m36bjt66my99hb6103f_v2.ipns-record'
]

for (const fixture of fixtures) {
const buf = loadFixture(fixture)
const record = ipns.unmarshal(buf)
const marshalled = ipns.marshal(record)

expect(buf).to.equalBytes(marshalled)
}
})
})
7 changes: 7 additions & 0 deletions test/fixtures/records.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { peerIdFromString } from '@libp2p/peer-id'

export const kuboRecord = {
bytes: Uint8Array.from([10, 52, 47, 105, 112, 102, 115, 47, 81, 109, 97, 52, 115, 87, 121, 111, 84, 105, 74, 75, 89, 120, 49, 119, 84, 106, 107, 120, 87, 89, 55, 49, 100, 89, 103, 49, 111, 87, 103, 69, 55, 83, 69, 57, 111, 105, 84, 71, 113, 71, 110, 121, 111, 82, 18, 64, 178, 225, 212, 157, 188, 23, 25, 166, 9, 89, 255, 63, 227, 160, 140, 70, 192, 237, 178, 167, 94, 6, 112, 184, 106, 130, 89, 252, 141, 158, 84, 53, 65, 125, 253, 93, 255, 17, 28, 93, 9, 176, 232, 89, 51, 118, 104, 236, 126, 137, 136, 72, 0, 127, 101, 88, 178, 83, 115, 6, 30, 28, 140, 5, 24, 0, 34, 27, 50, 48, 50, 52, 45, 48, 49, 45, 49, 57, 84, 49, 54, 58, 51, 51, 58, 50, 48, 46, 56, 53, 56, 50, 48, 57, 90, 40, 0, 48, 128, 192, 226, 133, 227, 104, 66, 64, 133, 91, 52, 64, 253, 186, 129, 154, 218, 85, 188, 18, 104, 96, 180, 216, 254, 176, 210, 145, 130, 209, 176, 150, 134, 33, 59, 197, 162, 193, 15, 252, 71, 190, 240, 25, 3, 169, 60, 24, 236, 68, 218, 171, 61, 235, 157, 73, 215, 0, 51, 52, 24, 195, 90, 158, 245, 199, 172, 204, 12, 249, 89, 7, 74, 136, 1, 165, 99, 84, 84, 76, 27, 0, 0, 3, 70, 48, 184, 160, 0, 101, 86, 97, 108, 117, 101, 88, 52, 47, 105, 112, 102, 115, 47, 81, 109, 97, 52, 115, 87, 121, 111, 84, 105, 74, 75, 89, 120, 49, 119, 84, 106, 107, 120, 87, 89, 55, 49, 100, 89, 103, 49, 111, 87, 103, 69, 55, 83, 69, 57, 111, 105, 84, 71, 113, 71, 110, 121, 111, 82, 104, 83, 101, 113, 117, 101, 110, 99, 101, 0, 104, 86, 97, 108, 105, 100, 105, 116, 121, 88, 27, 50, 48, 50, 52, 45, 48, 49, 45, 49, 57, 84, 49, 54, 58, 51, 51, 58, 50, 48, 46, 56, 53, 56, 50, 48, 57, 90, 108, 86, 97, 108, 105, 100, 105, 116, 121, 84, 121, 112, 101, 0]),
peerId: peerIdFromString('12D3KooWBT21CjaZgY3MvoFFwRJLBEqgk7zwa294Boh9wdX2RUX2'),
value: '/ipfs/Qma4sWyoTiJKYx1wTjkxWY71dYg1oWgE7SE9oiTGqGnyoR'
}
20 changes: 18 additions & 2 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import * as ipns from '../src/index.js'
import { IpnsEntry } from '../src/pb/ipns.js'
import { extractPublicKey, peerIdToRoutingKey, parseCborData, createCborData, ipnsRecordDataForV2Sig } from '../src/utils.js'
import { ipnsValidator } from '../src/validator.js'
import { kuboRecord } from './fixtures/records.js'
import type { PeerId } from '@libp2p/interface'

describe('ipns', function () {
Expand Down Expand Up @@ -186,7 +187,7 @@ describe('ipns', function () {
const record = await ipns.create(peerId, inputValue, 0, 1000000)

const pb = IpnsEntry.decode(ipns.marshal(record))
pb.data = createCborData(uint8ArrayFromString(inputValue), pb.validity ?? new Uint8Array(0), pb.validityType ?? '', pb.sequence ?? 0n, pb.ttl ?? 0n)
pb.data = createCborData(uint8ArrayFromString(inputValue), pb.validityType ?? IpnsEntry.ValidityType.EOL, pb.validity ?? new Uint8Array(0), pb.sequence ?? 0n, pb.ttl ?? 0n)
pb.value = uint8ArrayFromString(inputValue)

const modifiedRecord = ipns.unmarshal(IpnsEntry.encode(pb))
Expand All @@ -199,7 +200,7 @@ describe('ipns', function () {
const record = await ipns.create(peerId, inputValue, 0, 1000000)

const pb = IpnsEntry.decode(ipns.marshal(record))
pb.data = createCborData(uint8ArrayFromString(inputValue), pb.validity ?? new Uint8Array(0), pb.validityType ?? '', pb.sequence ?? 0n, pb.ttl ?? 0n)
pb.data = createCborData(uint8ArrayFromString(inputValue), pb.validityType ?? IpnsEntry.ValidityType.EOL, pb.validity ?? new Uint8Array(0), pb.sequence ?? 0n, pb.ttl ?? 0n)
pb.value = uint8ArrayFromString(inputValue)

const modifiedRecord = ipns.unmarshal(IpnsEntry.encode(pb))
Expand Down Expand Up @@ -397,4 +398,19 @@ describe('ipns', function () {

expect(record).to.have.property('value', '/ipfs/bafkqae3imvwgy3zamzzg63janjzs22lqnzzqu')
})

it('should round trip kubo records to bytes and back', async () => {
// the IPNS spec gives an example for the Validity field as
// 1970-01-01T00:00:00.000000001Z - e.g. nanosecond precision but Kubo only
// uses microsecond precision. The value is a timestamp as defined by
// rfc3339 which doesn't have a strong opinion on fractions of seconds so
// both are valid but we must be able to round trip them intact.
const unmarshalled = ipns.unmarshal(kuboRecord.bytes)
const remarhshalled = ipns.marshal(unmarshalled)

const reUnmarshalled = ipns.unmarshal(remarhshalled)

expect(unmarshalled).to.deep.equal(reUnmarshalled)
expect(remarhshalled).to.equalBytes(kuboRecord.bytes)
})
})

0 comments on commit c2b48c9

Please sign in to comment.