@helia/ipns 10.0.0 → 10.0.1-883ff053

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +3 -7
  2. package/dist/index.min.js +16 -5
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/errors.d.ts +0 -8
  5. package/dist/src/errors.d.ts.map +1 -1
  6. package/dist/src/errors.js +0 -8
  7. package/dist/src/errors.js.map +1 -1
  8. package/dist/src/index.d.ts +68 -31
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +11 -11
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/ipns/publisher.d.ts +2 -2
  13. package/dist/src/ipns/publisher.d.ts.map +1 -1
  14. package/dist/src/ipns/publisher.js +27 -14
  15. package/dist/src/ipns/publisher.js.map +1 -1
  16. package/dist/src/ipns/republisher.d.ts.map +1 -1
  17. package/dist/src/ipns/republisher.js +22 -9
  18. package/dist/src/ipns/republisher.js.map +1 -1
  19. package/dist/src/ipns/resolver.d.ts +2 -2
  20. package/dist/src/ipns/resolver.d.ts.map +1 -1
  21. package/dist/src/ipns/resolver.js +39 -17
  22. package/dist/src/ipns/resolver.js.map +1 -1
  23. package/dist/src/ipns.d.ts +3 -3
  24. package/dist/src/ipns.d.ts.map +1 -1
  25. package/dist/src/ipns.js +8 -8
  26. package/dist/src/ipns.js.map +1 -1
  27. package/dist/src/local-store.d.ts +4 -4
  28. package/dist/src/local-store.d.ts.map +1 -1
  29. package/dist/src/local-store.js +2 -1
  30. package/dist/src/local-store.js.map +1 -1
  31. package/dist/src/pb/ipns.d.ts +29 -29
  32. package/dist/src/pb/ipns.d.ts.map +1 -1
  33. package/dist/src/pb/ipns.js +15 -15
  34. package/dist/src/records.d.ts +27 -142
  35. package/dist/src/records.d.ts.map +1 -1
  36. package/dist/src/records.js +39 -46
  37. package/dist/src/records.js.map +1 -1
  38. package/dist/src/routing/helia.d.ts +5 -5
  39. package/dist/src/routing/helia.d.ts.map +1 -1
  40. package/dist/src/routing/helia.js +3 -3
  41. package/dist/src/routing/helia.js.map +1 -1
  42. package/dist/src/routing/index.d.ts +7 -7
  43. package/dist/src/routing/index.d.ts.map +1 -1
  44. package/dist/src/routing/index.js +2 -2
  45. package/dist/src/routing/index.js.map +1 -1
  46. package/dist/src/routing/local-store.d.ts +1 -1
  47. package/dist/src/routing/local-store.d.ts.map +1 -1
  48. package/dist/src/routing/local-store.js +3 -3
  49. package/dist/src/routing/local-store.js.map +1 -1
  50. package/dist/src/routing/pubsub.d.ts +11 -11
  51. package/dist/src/routing/pubsub.d.ts.map +1 -1
  52. package/dist/src/routing/pubsub.js +11 -12
  53. package/dist/src/routing/pubsub.js.map +1 -1
  54. package/dist/src/selector.d.ts +2 -2
  55. package/dist/src/selector.d.ts.map +1 -1
  56. package/dist/src/selector.js +13 -9
  57. package/dist/src/selector.js.map +1 -1
  58. package/dist/src/utils.d.ts +10 -11
  59. package/dist/src/utils.d.ts.map +1 -1
  60. package/dist/src/utils.js +20 -128
  61. package/dist/src/utils.js.map +1 -1
  62. package/dist/src/validator.d.ts +9 -3
  63. package/dist/src/validator.d.ts.map +1 -1
  64. package/dist/src/validator.js +57 -14
  65. package/dist/src/validator.js.map +1 -1
  66. package/package.json +6 -31
  67. package/src/errors.ts +0 -10
  68. package/src/index.ts +85 -36
  69. package/src/ipns/publisher.ts +29 -16
  70. package/src/ipns/republisher.ts +31 -13
  71. package/src/ipns/resolver.ts +40 -22
  72. package/src/ipns.ts +11 -11
  73. package/src/local-store.ts +8 -7
  74. package/src/pb/ipns.proto +1 -1
  75. package/src/pb/ipns.ts +37 -37
  76. package/src/records.ts +57 -210
  77. package/src/routing/helia.ts +6 -6
  78. package/src/routing/index.ts +7 -7
  79. package/src/routing/local-store.ts +6 -6
  80. package/src/routing/pubsub.ts +24 -25
  81. package/src/selector.ts +14 -11
  82. package/src/utils.ts +26 -148
  83. package/src/validator.ts +73 -16
  84. package/dist/typedoc-urls.json +0 -49
@@ -1,22 +1,59 @@
1
1
  import NanoDate from 'timestamp-nano';
2
- import { InvalidEmbeddedPublicKeyError, RecordExpiredError, SignatureVerificationError, UnsupportedValidityError } from "./errors.js";
3
- import { IpnsEntry } from "./pb/ipns.js";
4
- import { ipnsRecordDataForV2Sig } from "./utils.js";
2
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string';
3
+ import { InvalidEmbeddedPublicKeyError, RecordExpiredError, RecordTooLargeError, SignatureVerificationError, UnsupportedValidityError } from "./errors.js";
4
+ import { IPNSEntry } from "./pb/ipns.js";
5
+ import { decodeExtensibleData, ipnsRecordDataForV2Sig, isCodec, multihashFromIPNSRoutingKey, validateCborDataMatchesPbData } from "./utils.js";
6
+ /**
7
+ * Limit valid IPNS record sizes to 10kb
8
+ */
9
+ const MAX_RECORD_SIZE = 1024 * 10;
5
10
  /**
6
11
  * Validate the given IPNS record against the given routing key.
7
12
  *
13
+ * Ensure that
14
+ * - SignatureV2 and Data are present
15
+ * - ValidityType and Validity are of valid types and have a value
16
+ * - CBOR data matches protobuf if it's a V1+V2 record
17
+ *
8
18
  * @see https://specs.ipfs.tech/ipns/ipns-record/#routing-record for the binary
9
19
  * format of the routing key
10
20
  */
11
- export async function ipnsValidator(record, options) {
12
- if (record.publicKey == null) {
13
- throw new InvalidEmbeddedPublicKeyError('The record had no public key associated with it');
21
+ export async function ipnsValidator(routingKey, marshalledRecord, keychain, options) {
22
+ if (marshalledRecord.byteLength > MAX_RECORD_SIZE) {
23
+ throw new RecordTooLargeError('The record is too large');
24
+ }
25
+ const record = IPNSEntry.decode(marshalledRecord);
26
+ // Check if we have the data field. If we don't, we fail. We've been producing
27
+ // V1+V2 records for quite a while and we don't support V1-only records during
28
+ // validation any more
29
+ if (record.signatureV2 == null) {
30
+ throw new SignatureVerificationError('Missing signatureV2');
31
+ }
32
+ const data = decodeExtensibleData(record.data);
33
+ const validity = uint8ArrayToString(data.Validity);
34
+ let publicKey;
35
+ // try to extract public key from routing key
36
+ const routingMultihash = multihashFromIPNSRoutingKey(routingKey);
37
+ // identity hash
38
+ if (isCodec(routingMultihash, 0x0)) {
39
+ publicKey = await keychain.loadPublicKeyFromProtobuf(routingMultihash.digest, options);
40
+ }
41
+ // otherwise try to load key from message
42
+ if (publicKey == null && record.publicKey != null) {
43
+ publicKey = await keychain.loadPublicKeyFromProtobuf(record.publicKey, options);
44
+ }
45
+ if (publicKey == null) {
46
+ throw new InvalidEmbeddedPublicKeyError('Could not extract public key from IPNS record or routing key');
14
47
  }
15
48
  // Validate Signature V2
16
49
  let isValid;
17
50
  try {
51
+ if (record.data == null) {
52
+ // n.b. decodeExtensibleData would have thrown if record data was missing
53
+ throw new Error('Missing data');
54
+ }
18
55
  const dataForSignature = ipnsRecordDataForV2Sig(record.data);
19
- isValid = await record.publicKey.verify(dataForSignature, record.signatureV2, options);
56
+ isValid = await publicKey.verify(dataForSignature, record.signatureV2, options);
20
57
  }
21
58
  catch {
22
59
  isValid = false;
@@ -25,14 +62,19 @@ export async function ipnsValidator(record, options) {
25
62
  throw new SignatureVerificationError('Record signature verification failed');
26
63
  }
27
64
  // Validate according to the validity type
28
- if (record.validityType === IpnsEntry.ValidityType.EOL) {
29
- if (NanoDate.fromString(record.validity).toDate().getTime() < Date.now()) {
65
+ if (data.ValidityType === IPNSEntry.ValidityType.EOL) {
66
+ if (NanoDate.fromString(validity).toDate().getTime() < Date.now()) {
30
67
  throw new RecordExpiredError('record has expired');
31
68
  }
32
69
  }
33
- else if (record.validityType != null) {
34
- throw new UnsupportedValidityError('The validity type is unsupported');
70
+ else if (data.ValidityType != null) {
71
+ throw new UnsupportedValidityError(`The validity type ${data.ValidityType} is unsupported`);
72
+ }
73
+ if (record.value != null && record.signatureV1 != null) {
74
+ // V1+V2
75
+ validateCborDataMatchesPbData(record, data);
35
76
  }
77
+ return record;
36
78
  }
37
79
  /**
38
80
  * Returns the number of milliseconds until the record expires.
@@ -42,13 +84,14 @@ export async function ipnsValidator(record, options) {
42
84
  * @returns The number of milliseconds until the record expires, or 0 if the record is already expired.
43
85
  */
44
86
  export function validFor(record) {
45
- if (record.validityType !== IpnsEntry.ValidityType.EOL) {
87
+ const data = decodeExtensibleData(record.data);
88
+ if (data.ValidityType !== IPNSEntry.ValidityType.EOL) {
46
89
  throw new UnsupportedValidityError();
47
90
  }
48
- if (record.validity == null) {
91
+ if (data.Validity == null) {
49
92
  throw new UnsupportedValidityError();
50
93
  }
51
- const validUntil = NanoDate.fromString(record.validity).toDate().getTime();
94
+ const validUntil = NanoDate.fromString(uint8ArrayToString(data.Validity)).toDate().getTime();
52
95
  const now = Date.now();
53
96
  if (validUntil < now) {
54
97
  return 0;
@@ -1 +1 @@
1
- {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AACrC,OAAO,EAAE,6BAA6B,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AACrI,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAInD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAE,MAAkB,EAAE,OAAsB;IAC7E,IAAI,MAAM,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;QAC7B,MAAM,IAAI,6BAA6B,CAAC,iDAAiD,CAAC,CAAA;IAC5F,CAAC;IAED,wBAAwB;IACxB,IAAI,OAAO,CAAA;IAEX,IAAI,CAAC;QACH,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAC5D,OAAO,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,KAAK,CAAA;IACjB,CAAC;IAED,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,0BAA0B,CAAC,sCAAsC,CAAC,CAAA;IAC9E,CAAC;IAED,0CAA0C;IAC1C,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC;QACvD,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YACzE,MAAM,IAAI,kBAAkB,CAAC,oBAAoB,CAAC,CAAA;QACpD,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;QACvC,MAAM,IAAI,wBAAwB,CAAC,kCAAkC,CAAC,CAAA;IACxE,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAE,MAAkB;IAC1C,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC;QACvD,MAAM,IAAI,wBAAwB,EAAE,CAAA;IACtC,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;QAC5B,MAAM,IAAI,wBAAwB,EAAE,CAAA;IACtC,CAAC;IAED,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,CAAA;IAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAEtB,IAAI,UAAU,GAAG,GAAG,EAAE,CAAC;QACrB,OAAO,CAAC,CAAA;IACV,CAAC;IAED,OAAO,UAAU,GAAG,GAAG,CAAA;AACzB,CAAC"}
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/validator.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helia/ipns",
3
- "version": "10.0.0",
3
+ "version": "10.0.1-883ff053",
4
4
  "description": "An implementation of IPNS for Helia",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "homepage": "https://github.com/ipfs/helia/tree/main/packages/ipns#readme",
@@ -20,22 +20,6 @@
20
20
  ],
21
21
  "type": "module",
22
22
  "types": "./dist/src/index.d.ts",
23
- "typesVersions": {
24
- "*": {
25
- "*": [
26
- "*",
27
- "dist/*",
28
- "dist/src/*",
29
- "dist/src/*/index"
30
- ],
31
- "src/*": [
32
- "*",
33
- "dist/*",
34
- "dist/src/*",
35
- "dist/src/*/index"
36
- ]
37
- }
38
- },
39
23
  "files": [
40
24
  "src",
41
25
  "dist",
@@ -47,16 +31,6 @@
47
31
  "types": "./dist/src/index.d.ts",
48
32
  "import": "./dist/src/index.js",
49
33
  "module-sync": "./dist/src/index.js"
50
- },
51
- "./dns-resolvers": {
52
- "types": "./dist/src/dns-resolvers/index.d.ts",
53
- "import": "./dist/src/dns-resolvers/index.js",
54
- "module-sync": "./dist/src/dns-resolvers/index.js"
55
- },
56
- "./routing": {
57
- "types": "./dist/src/routing/index.d.ts",
58
- "import": "./dist/src/routing/index.js",
59
- "module-sync": "./dist/src/routing/index.js"
60
34
  }
61
35
  },
62
36
  "scripts": {
@@ -76,7 +50,10 @@
76
50
  "test:electron-main": "aegir test -t electron-main"
77
51
  },
78
52
  "dependencies": {
79
- "@helia/interface": "^7.0.0",
53
+ "@helia/interface": "7.0.0-883ff053",
54
+ "@ipld/dag-cbor": "^10.0.1",
55
+ "@ipshipyard/crypto": "^1.1.0",
56
+ "@ipshipyard/keychain": "^1.0.2",
80
57
  "@libp2p/fetch": "^4.1.6",
81
58
  "@libp2p/interface": "^3.2.3",
82
59
  "@libp2p/kad-dht": "^16.3.2",
@@ -85,7 +62,6 @@
85
62
  "@libp2p/utils": "^7.2.2",
86
63
  "abort-error": "^1.0.2",
87
64
  "any-signal": "^4.2.0",
88
- "cborg": "^5.1.1",
89
65
  "delay": "^7.0.0",
90
66
  "interface-datastore": "^10.0.1",
91
67
  "multiformats": "^14.0.0",
@@ -97,9 +73,8 @@
97
73
  "uint8arrays": "^6.1.1"
98
74
  },
99
75
  "devDependencies": {
100
- "@ipshipyard/crypto": "^1.1.0",
101
- "@ipshipyard/keychain": "^1.0.2",
102
76
  "aegir": "^48.0.11",
77
+ "cborg": "^5.1.5",
103
78
  "datastore-core": "^12.0.1",
104
79
  "it-drain": "^3.0.12",
105
80
  "it-last": "^3.0.11",
package/src/errors.ts CHANGED
@@ -3,16 +3,6 @@ export class RecordsFailedValidationError extends Error {
3
3
  name = 'RecordsFailedValidationError'
4
4
  }
5
5
 
6
- export class UnsupportedMultibasePrefixError extends Error {
7
- static name = 'UnsupportedMultibasePrefixError'
8
- name = 'UnsupportedMultibasePrefixError'
9
- }
10
-
11
- export class UnsupportedMultihashCodecError extends Error {
12
- static name = 'UnsupportedMultihashCodecError'
13
- name = 'UnsupportedMultihashCodecError'
14
- }
15
-
16
6
  export class InvalidValueError extends Error {
17
7
  static name = 'InvalidValueError'
18
8
  name = 'InvalidValueError'
package/src/index.ts CHANGED
@@ -37,7 +37,6 @@
37
37
  * import { createHelia } from 'helia'
38
38
  * import { ipns } from '@helia/ipns'
39
39
  * import { unixfs } from '@helia/unixfs'
40
- * import { generateKeyPair } from '@libp2p/crypto/keys'
41
40
  *
42
41
  * const helia = await createHelia()
43
42
  * const name = ipns(helia)
@@ -66,7 +65,6 @@
66
65
  * import { createHelia } from 'helia'
67
66
  * import { ipns } from '@helia/ipns'
68
67
  * import { unixfs } from '@helia/unixfs'
69
- * import { generateKeyPair } from '@libp2p/crypto/keys'
70
68
  *
71
69
  * const helia = await createHelia()
72
70
  * const name = ipns(helia)
@@ -105,15 +103,13 @@
105
103
  * may fail to be published with "Insufficient peers" errors.
106
104
  *
107
105
  * ```TypeScript
108
- * import { ipns } from '@helia/ipns'
109
- * import { pubsub } from '@helia/ipns/routing'
106
+ * import { ipns, pubSubIPNSRouting } from '@helia/ipns'
110
107
  * import { withLibp2p, libp2pDefaults } from '@helia/libp2p'
111
108
  * import { unixfs } from '@helia/unixfs'
112
- * import { generateKeyPair } from '@libp2p/crypto/keys'
113
109
  * import { floodsub } from '@libp2p/floodsub'
114
110
  * import { createHelia } from 'helia'
115
111
  * import type { Helia } from '@helia/interface'
116
- * import type { PubSub } from '@helia/ipns/routing'
112
+ * import type { PubSub } from '@helia/ipns'
117
113
  * import type { DefaultLibp2pServices } from '@helia/libp2p'
118
114
  * import type { FloodSub } from '@libp2p/floodsub'
119
115
  * import type { Libp2p } from '@libp2p/interface'
@@ -125,7 +121,7 @@
125
121
  *
126
122
  * const name = ipns(helia, {
127
123
  * routers: [
128
- * pubsub(helia)
124
+ * pubSubIPNSRouting(helia)
129
125
  * ]
130
126
  * })
131
127
  *
@@ -147,10 +143,10 @@ import { CID } from 'multiformats/cid'
147
143
  import { IPNSResolver as IPNSResolverClass } from './ipns/resolver.ts'
148
144
  import { IPNS as IPNSClass } from './ipns.ts'
149
145
  import { localStore } from './local-store.ts'
150
- import { helia } from './routing/index.ts'
151
- import { localStoreRouting } from './routing/local-store.ts'
146
+ import { heliaIPNSRouting } from './routing/index.ts'
147
+ import { localStoreIPNSRouting } from './routing/local-store.ts'
152
148
  import type { IPNSResolverComponents } from './ipns/resolver.ts'
153
- import type { IPNSRecord } from './records.ts'
149
+ import type { IPNSEntry } from './pb/ipns.ts'
154
150
  import type { IPNSRouting, IPNSRoutingProgressEvents } from './routing/index.ts'
155
151
  import type { Routing, HeliaEvents, Keychain, PublicKey } from '@helia/interface'
156
152
  import type { ComponentLogger, TypedEventEmitter } from '@libp2p/interface'
@@ -159,14 +155,29 @@ import type { Datastore } from 'interface-datastore'
159
155
  import type { MultihashDigest } from 'multiformats/hashes/interface'
160
156
  import type { ProgressEvent, ProgressOptions } from 'progress-events'
161
157
 
158
+ export * from './routing/index.ts'
159
+ export * from './pb/ipns.ts'
160
+
161
+ export {
162
+ multihashFromIPNSRoutingKey,
163
+ multihashToIPNSRoutingKey
164
+ } from './utils.ts'
165
+
166
+ export {
167
+ createIPNSRecord
168
+ } from './records.ts'
169
+ export type {
170
+ CreateIPNSRecordOptions
171
+ } from './records.ts'
172
+
162
173
  export type PublishProgressEvents =
163
174
  ProgressEvent<'ipns:publish:start'> |
164
- ProgressEvent<'ipns:publish:success', IPNSRecord> |
175
+ ProgressEvent<'ipns:publish:success', IPNSEntry> |
165
176
  ProgressEvent<'ipns:publish:error', Error>
166
177
 
167
178
  export type ResolveProgressEvents =
168
179
  ProgressEvent<'ipns:resolve:start', unknown> |
169
- ProgressEvent<'ipns:resolve:success', IPNSRecord> |
180
+ ProgressEvent<'ipns:resolve:success', IPNSEntry> |
170
181
  ProgressEvent<'ipns:resolve:error', Error>
171
182
 
172
183
  export type DatastoreProgressEvents =
@@ -177,7 +188,8 @@ export type DatastoreProgressEvents =
177
188
 
178
189
  export interface PublishOptions extends AbortOptions, ProgressOptions<PublishProgressEvents | IPNSRoutingProgressEvents> {
179
190
  /**
180
- * Time duration of the signature validity in ms
191
+ * Time duration of the signature validity in ms - after this many ms have
192
+ * expired the record will be invalidated.
181
193
  *
182
194
  * @default 172_800_000
183
195
  */
@@ -199,19 +211,47 @@ export interface PublishOptions extends AbortOptions, ProgressOptions<PublishPro
199
211
  v1Compatible?: boolean
200
212
 
201
213
  /**
202
- * The TTL of the record in ms
214
+ * The TTL of the record in ms - after this many ms have expired, resolving
215
+ * the record will query the routing for an updated version.
216
+ *
217
+ * Before this many ms have expired any locally stored copy will be treated as
218
+ * the latest version and the routing will not be queried.
203
219
  *
204
220
  * @default 300_000
205
221
  */
206
222
  ttl?: number
223
+
224
+ /**
225
+ * Extensible data that will be added to the IPNS record data and signed to
226
+ * verify it's integrity.
227
+ *
228
+ * Note that this data will be encoded as DAG-CBOR so it must be valid.
229
+ *
230
+ * It is recommended that any custom fields set here are prefixed with `_` to
231
+ * avoid collision with any mandatory fields added to future versions of the
232
+ * IPNS specification.
233
+ *
234
+ * @see https://specs.ipfs.tech/ipns/ipns-record/#extensible-data-dag-cbor
235
+ */
236
+ data?: Record<string, any>
207
237
  }
208
238
 
209
- export interface IPNSRecordMetadata {
210
- keyName: string
211
- lifetime: number
239
+ /**
240
+ * Extensible data from the record `data` field.
241
+ *
242
+ * The wire format of this data is DAG-CBOR.
243
+ *
244
+ * @see https://specs.ipfs.tech/ipns/ipns-record/#extensible-data-dag-cbor
245
+ */
246
+ export interface IPNSRecordData extends Record<string, any> {
247
+ Value: Uint8Array<ArrayBuffer>
248
+ Validity: Uint8Array<ArrayBuffer>
249
+ ValidityType: IPNSEntry.ValidityType
250
+ Sequence: bigint
251
+ TTL: bigint
212
252
  }
213
253
 
214
- export interface ResolveOptions extends AbortOptions, ProgressOptions<ResolveProgressEvents | IPNSRoutingProgressEvents> {
254
+ export interface IPNSResolveOptions extends AbortOptions, ProgressOptions<ResolveProgressEvents | IPNSRoutingProgressEvents> {
215
255
  /**
216
256
  * Do not query the network for the IPNS record
217
257
  *
@@ -225,20 +265,33 @@ export interface ResolveOptions extends AbortOptions, ProgressOptions<ResolvePro
225
265
  * @default false
226
266
  */
227
267
  nocache?: boolean
268
+
269
+ /**
270
+ * If true, ensure the record fields and signature are valid. If false, just
271
+ * return the record.
272
+ *
273
+ * @default true
274
+ */
275
+ validate?: boolean
228
276
  }
229
277
 
230
- export interface ResolveResult {
278
+ export interface IPNSResolveResult {
231
279
  /**
232
280
  * The resolved record
233
281
  */
234
- record: IPNSRecord
282
+ record: IPNSEntry
283
+
284
+ /**
285
+ * The value contained within the IPNS record
286
+ */
287
+ value: string
235
288
  }
236
289
 
237
- export interface PublishResult {
290
+ export interface IPNSPublishResult {
238
291
  /**
239
292
  * The published record
240
293
  */
241
- record: IPNSRecord
294
+ record: IPNSEntry
242
295
 
243
296
  /**
244
297
  * The IPNS name that can be used to resolve this record
@@ -253,13 +306,12 @@ export interface PublishResult {
253
306
 
254
307
  export interface IPNSResolver {
255
308
  /**
256
- * Accepts a libp2p public key, a CID with the libp2p-key codec and either the
257
- * identity hash (for Ed25519 and secp256k1 public keys) or a SHA256 hash (for
258
- * RSA public keys), or the multihash of a libp2p-key encoded CID, or a
259
- * Ed25519, secp256k1 or RSA PeerId and recursively resolves the IPNS record
260
- * corresponding to that key until a value is found.
309
+ * Accepts a CID with the libp2p-key codec and either the identity hash (for
310
+ * Ed25519 and secp256k1 public keys) or a SHA256 hash (for RSA public keys),
311
+ * or the multihash of a libp2p-key encoded CID and recursively resolves the
312
+ * IPNS record corresponding to that key until a value is found.
261
313
  */
262
- resolve(key: CID<unknown, 0x72> | MultihashDigest, options?: ResolveOptions): AsyncGenerator<ResolveResult>
314
+ resolve(key: CID | MultihashDigest, options?: IPNSResolveOptions): AsyncGenerator<IPNSResolveResult>
263
315
  }
264
316
 
265
317
  export interface IPNS {
@@ -298,7 +350,7 @@ export interface IPNS {
298
350
  * console.info(result) // { answer: ... }
299
351
  * ```
300
352
  */
301
- publish(keyName: string, value: CID | PublicKey | MultihashDigest | string, options?: PublishOptions): Promise<PublishResult>
353
+ publish(keyName: string, value: CID | PublicKey | MultihashDigest | string, options?: PublishOptions): Promise<IPNSPublishResult>
302
354
 
303
355
  /**
304
356
  * Accepts a multihash of a public key, a libp2p-key CID containing the
@@ -307,10 +359,10 @@ export interface IPNS {
307
359
  * (e.g. the value can be parsed as a string that does not start with
308
360
  * `/ipns/`).
309
361
  */
310
- resolve(name: CID<unknown, 0x72> | PublicKey | MultihashDigest | string, options?: ResolveOptions): AsyncGenerator<ResolveResult>
362
+ resolve(name: CID | PublicKey | MultihashDigest | string, options?: IPNSResolveOptions): AsyncGenerator<IPNSResolveResult>
311
363
 
312
364
  /**
313
- * Stop republishing of an IPNS record
365
+ * Stop republishing of an IPNS record.
314
366
  *
315
367
  * This will delete the last signed IPNS record from the datastore.
316
368
  *
@@ -320,9 +372,6 @@ export interface IPNS {
320
372
  unpublish(keyName: string, options?: AbortOptions): Promise<void>
321
373
  }
322
374
 
323
- export type { IPNSRouting } from './routing/index.ts'
324
- export type { IPNSRecord } from './records.ts'
325
-
326
375
  export interface IPNSComponents {
327
376
  datastore: Datastore
328
377
  routing: Routing
@@ -367,8 +416,8 @@ export function ipns (components: IPNSComponents, options: IPNSOptions = {}): IP
367
416
  export function ipnsResolver (components: IPNSResolverComponents, options: IPNSResolverOptions = {}): IPNSResolver {
368
417
  const store = localStore(components.datastore, components.logger.forComponent('helia:ipns:local-store'))
369
418
  const routers = [
370
- localStoreRouting(store),
371
- helia(components.routing),
419
+ localStoreIPNSRouting(store),
420
+ heliaIPNSRouting(components.routing),
372
421
  ...(options.routers ?? [])
373
422
  ]
374
423
 
@@ -1,9 +1,10 @@
1
1
  import { base36 } from 'multiformats/bases/base36'
2
2
  import { CustomProgressEvent } from 'progress-events'
3
- import { DEFAULT_LIFETIME_MS, DEFAULT_TTL_NS } from '../constants.ts'
3
+ import { DEFAULT_LIFETIME_MS } from '../constants.ts'
4
+ import { IPNSEntry } from '../pb/ipns.ts'
4
5
  import { createIPNSRecord } from '../records.ts'
5
- import { marshalIPNSRecord, multihashToIPNSRoutingKey, unmarshalIPNSRecord } from '../utils.ts'
6
- import type { PublishResult, PublishOptions } from '../index.ts'
6
+ import { decodeExtensibleData, multihashToIPNSRoutingKey } from '../utils.ts'
7
+ import type { IPNSPublishResult, PublishOptions } from '../index.ts'
7
8
  import type { LocalStore } from '../local-store.ts'
8
9
  import type { IPNSRouting } from '../routing/index.ts'
9
10
  import type { Keychain, PrivateKey } from '@helia/interface'
@@ -32,7 +33,7 @@ export class IPNSPublisher {
32
33
  this.routers = init.routers
33
34
  }
34
35
 
35
- async publish (keyName: string, value: string, options: PublishOptions = {}): Promise<PublishResult> {
36
+ async publish (keyName: string, value: string, options: PublishOptions = {}): Promise<IPNSPublishResult> {
36
37
  try {
37
38
  const key = await this.#loadOrCreateKey(keyName, options)
38
39
  const digest = key.publicKey.toMultihash()
@@ -42,31 +43,43 @@ export class IPNSPublisher {
42
43
  if (await this.localStore.has(routingKey, options)) {
43
44
  // if we have published under this key before, increment the sequence number
44
45
  const { record } = await this.localStore.get(routingKey, options)
45
- const existingRecord = await unmarshalIPNSRecord(routingKey, record, this.keychain, options)
46
- sequenceNumber = existingRecord.sequence + 1n
46
+ const existingRecord = IPNSEntry.decode(record)
47
+ const data = decodeExtensibleData(existingRecord.data)
48
+ sequenceNumber = (data.Sequence ?? 0n) + 1n
47
49
  }
48
50
 
49
- // convert ttl from milliseconds to nanoseconds as createIPNSRecord expects
50
- const ttlNs = options.ttl != null ? BigInt(options.ttl) * 1_000_000n : DEFAULT_TTL_NS
51
51
  const lifetime = options.lifetime ?? DEFAULT_LIFETIME_MS
52
- const record = await createIPNSRecord(key, value, sequenceNumber, lifetime, { ...options, ttlNs })
53
- const marshaledRecord = marshalIPNSRecord(record)
52
+ const record = await createIPNSRecord(key, value, sequenceNumber, lifetime, {
53
+ ...options,
54
+ // convert ttl from milliseconds to nanoseconds as createIPNSRecord expects
55
+ ttlNs: options.ttl != null ? BigInt(options.ttl) * 1_000_000n : undefined
56
+ })
57
+ const marshaledRecord = IPNSEntry.encode(record)
54
58
 
55
59
  if (options.offline === true) {
56
60
  // only store record locally
57
- await this.localStore.put(routingKey, marshaledRecord, { ...options, metadata: { keyName, lifetime } })
61
+ await this.localStore.put(routingKey, marshaledRecord, {
62
+ ...options,
63
+ metadata: {
64
+ keyName,
65
+ lifetime
66
+ }
67
+ })
58
68
  } else {
59
69
  // publish record to routing (including the local store)
60
70
  await Promise.all(this.routers.map(async r => {
61
- await r.put(routingKey, marshaledRecord, { ...options, metadata: { keyName, lifetime } })
71
+ await r.put(routingKey, marshaledRecord, {
72
+ ...options,
73
+ metadata: {
74
+ keyName,
75
+ lifetime
76
+ }
77
+ })
62
78
  }))
63
79
  }
64
80
 
65
81
  return {
66
- record: {
67
- ...record,
68
- publicKey: key.publicKey
69
- },
82
+ record,
70
83
  name: `/ipns/${key.publicKey.toCID().toString(base36)}`,
71
84
  publicKey: key.publicKey
72
85
  }
@@ -1,9 +1,10 @@
1
1
  import { Queue, repeatingTask } from '@libp2p/utils'
2
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
2
3
  import { DEFAULT_REPUBLISH_CONCURRENCY, DEFAULT_REPUBLISH_INTERVAL_MS, DEFAULT_TTL_NS } from '../constants.ts'
4
+ import { IPNSEntry } from '../pb/ipns.ts'
3
5
  import { createIPNSRecord } from '../records.ts'
4
- import { marshalIPNSRecord, unmarshalIPNSRecord } from '../utils.ts'
5
- import { shouldRepublish } from '../utils.ts'
6
- import type { IPNSRecord } from '../index.ts'
6
+ import { decodeExtensibleData, shouldRepublish } from '../utils.ts'
7
+ import type { IPNSRecordData } from '../index.ts'
7
8
  import type { LocalStore } from '../local-store.ts'
8
9
  import type { IPNSRouting } from '../routing/index.ts'
9
10
  import type { Keychain, PrivateKey } from '@helia/interface'
@@ -78,7 +79,7 @@ export class IPNSRepublisher {
78
79
  })
79
80
 
80
81
  try {
81
- const recordsToRepublish: Array<{ routingKey: Uint8Array, record: IPNSRecord }> = []
82
+ const recordsToRepublish: Array<{ routingKey: Uint8Array, record: IPNSEntry }> = []
82
83
  let listed = 0
83
84
 
84
85
  // Find all records using the localStore.list method
@@ -91,21 +92,38 @@ export class IPNSRepublisher {
91
92
  this.log('no metadata found for record %b, skipping', routingKey)
92
93
  continue
93
94
  }
94
- let ipnsRecord: IPNSRecord
95
+
96
+ let ipnsRecord: IPNSEntry
97
+
95
98
  try {
96
- ipnsRecord = await unmarshalIPNSRecord(routingKey, record, this.keychain, options)
97
- } catch (err: any) {
98
- this.log.error('error unmarshaling record - %e', err)
99
+ ipnsRecord = IPNSEntry.decode(record)
100
+ } catch (err) {
101
+ this.log.trace('skipping invalid record %b because could not decode', routingKey)
102
+ continue
103
+ }
104
+
105
+ if (ipnsRecord.data == null) {
106
+ this.log.trace('skipping record %b because data was missing', routingKey)
107
+ continue
108
+ }
109
+
110
+ let data: IPNSRecordData
111
+
112
+ try {
113
+ data = decodeExtensibleData(ipnsRecord.data)
114
+ } catch {
115
+ this.log.trace('skipping record %b because could not decode data', routingKey)
99
116
  continue
100
117
  }
101
118
 
102
119
  // Only republish records that are within the DHT or record expiry threshold
103
- if (!shouldRepublish(ipnsRecord, created)) {
120
+ if (!shouldRepublish(created, new Date(uint8ArrayToString(data.Validity)))) {
104
121
  this.log.trace('skipping record %b within republish threshold', routingKey)
105
122
  continue
106
123
  }
107
- const sequenceNumber = ipnsRecord.sequence + 1n
108
- const ttlNs = ipnsRecord.ttl ?? DEFAULT_TTL_NS
124
+
125
+ const sequenceNumber = data.Sequence + 1n
126
+ const ttlNs = data.TTL ?? DEFAULT_TTL_NS
109
127
  let privKey: PrivateKey
110
128
 
111
129
  try {
@@ -116,7 +134,7 @@ export class IPNSRepublisher {
116
134
  }
117
135
 
118
136
  try {
119
- const updatedRecord = await createIPNSRecord(privKey, ipnsRecord.value, sequenceNumber, metadata.lifetime, {
137
+ const updatedRecord = await createIPNSRecord(privKey, uint8ArrayToString(data.Value), sequenceNumber, metadata.lifetime, {
120
138
  ...options,
121
139
  ttlNs
122
140
  })
@@ -137,7 +155,7 @@ export class IPNSRepublisher {
137
155
  // Add job to queue to republish the record to all routers
138
156
  queue.add(async () => {
139
157
  try {
140
- const marshaledRecord = marshalIPNSRecord(record)
158
+ const marshaledRecord = IPNSEntry.encode(record)
141
159
  await Promise.all(
142
160
  this.routers.map(r => r.put(routingKey, marshaledRecord, options))
143
161
  )