@helia/ipns 9.2.1-35b081c7 → 9.2.1-73a28eda

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 (68) hide show
  1. package/README.md +12 -49
  2. package/dist/index.min.js +5 -16
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/errors.d.ts +33 -5
  5. package/dist/src/errors.d.ts.map +1 -1
  6. package/dist/src/errors.js +33 -20
  7. package/dist/src/errors.js.map +1 -1
  8. package/dist/src/index.d.ts +53 -91
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +12 -52
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/ipns/publisher.d.ts +5 -9
  13. package/dist/src/ipns/publisher.d.ts.map +1 -1
  14. package/dist/src/ipns/publisher.js +30 -22
  15. package/dist/src/ipns/publisher.js.map +1 -1
  16. package/dist/src/ipns/republisher.d.ts +3 -5
  17. package/dist/src/ipns/republisher.d.ts.map +1 -1
  18. package/dist/src/ipns/republisher.js +18 -9
  19. package/dist/src/ipns/republisher.js.map +1 -1
  20. package/dist/src/ipns/resolver.d.ts +6 -5
  21. package/dist/src/ipns/resolver.d.ts.map +1 -1
  22. package/dist/src/ipns/resolver.js +32 -78
  23. package/dist/src/ipns/resolver.js.map +1 -1
  24. package/dist/src/ipns.d.ts +6 -4
  25. package/dist/src/ipns.d.ts.map +1 -1
  26. package/dist/src/ipns.js +33 -4
  27. package/dist/src/ipns.js.map +1 -1
  28. package/dist/src/pb/ipns.d.ts +62 -0
  29. package/dist/src/pb/ipns.d.ts.map +1 -0
  30. package/dist/src/pb/ipns.js +203 -0
  31. package/dist/src/pb/ipns.js.map +1 -0
  32. package/dist/src/pb/metadata.d.ts +1 -1
  33. package/dist/src/pb/metadata.d.ts.map +1 -1
  34. package/dist/src/records.d.ts +155 -0
  35. package/dist/src/records.d.ts.map +1 -0
  36. package/dist/src/records.js +88 -0
  37. package/dist/src/records.js.map +1 -0
  38. package/dist/src/routing/pubsub.d.ts +3 -0
  39. package/dist/src/routing/pubsub.d.ts.map +1 -1
  40. package/dist/src/routing/pubsub.js +15 -10
  41. package/dist/src/routing/pubsub.js.map +1 -1
  42. package/dist/src/selector.d.ts +14 -0
  43. package/dist/src/selector.d.ts.map +1 -0
  44. package/dist/src/selector.js +47 -0
  45. package/dist/src/selector.js.map +1 -0
  46. package/dist/src/utils.d.ts +29 -2
  47. package/dist/src/utils.d.ts.map +1 -1
  48. package/dist/src/utils.js +308 -0
  49. package/dist/src/utils.js.map +1 -1
  50. package/dist/src/validator.d.ts +18 -0
  51. package/dist/src/validator.d.ts.map +1 -0
  52. package/dist/src/validator.js +58 -0
  53. package/dist/src/validator.js.map +1 -0
  54. package/package.json +18 -17
  55. package/src/errors.ts +40 -25
  56. package/src/index.ts +54 -92
  57. package/src/ipns/publisher.ts +34 -29
  58. package/src/ipns/republisher.ts +24 -13
  59. package/src/ipns/resolver.ts +40 -88
  60. package/src/ipns.ts +44 -7
  61. package/src/pb/ipns.proto +39 -0
  62. package/src/pb/ipns.ts +280 -0
  63. package/src/pb/metadata.ts +1 -1
  64. package/src/records.ts +273 -0
  65. package/src/routing/pubsub.ts +17 -10
  66. package/src/selector.ts +55 -0
  67. package/src/utils.ts +371 -2
  68. package/src/validator.ts +67 -0
@@ -0,0 +1,58 @@
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";
5
+ /**
6
+ * Validate the given IPNS record against the given routing key.
7
+ *
8
+ * @see https://specs.ipfs.tech/ipns/ipns-record/#routing-record for the binary
9
+ * format of the routing key
10
+ */
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');
14
+ }
15
+ // Validate Signature V2
16
+ let isValid;
17
+ try {
18
+ const dataForSignature = ipnsRecordDataForV2Sig(record.data);
19
+ isValid = await record.publicKey.verify(dataForSignature, record.signatureV2, options);
20
+ }
21
+ catch {
22
+ isValid = false;
23
+ }
24
+ if (!isValid) {
25
+ throw new SignatureVerificationError('Record signature verification failed');
26
+ }
27
+ // Validate according to the validity type
28
+ if (record.validityType === IpnsEntry.ValidityType.EOL) {
29
+ if (NanoDate.fromString(record.validity).toDate().getTime() < Date.now()) {
30
+ throw new RecordExpiredError('record has expired');
31
+ }
32
+ }
33
+ else if (record.validityType != null) {
34
+ throw new UnsupportedValidityError('The validity type is unsupported');
35
+ }
36
+ }
37
+ /**
38
+ * Returns the number of milliseconds until the record expires.
39
+ * If the record is already expired, returns 0.
40
+ *
41
+ * @param record - The IPNS record to validate.
42
+ * @returns The number of milliseconds until the record expires, or 0 if the record is already expired.
43
+ */
44
+ export function validFor(record) {
45
+ if (record.validityType !== IpnsEntry.ValidityType.EOL) {
46
+ throw new UnsupportedValidityError();
47
+ }
48
+ if (record.validity == null) {
49
+ throw new UnsupportedValidityError();
50
+ }
51
+ const validUntil = NanoDate.fromString(record.validity).toDate().getTime();
52
+ const now = Date.now();
53
+ if (validUntil < now) {
54
+ return 0;
55
+ }
56
+ return validUntil - now;
57
+ }
58
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helia/ipns",
3
- "version": "9.2.1-35b081c7",
3
+ "version": "9.2.1-73a28eda",
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",
@@ -66,7 +66,7 @@
66
66
  "doc-check": "aegir doc-check",
67
67
  "build": "aegir build",
68
68
  "docs": "aegir docs",
69
- "generate": "protons ./src/pb/metadata.proto",
69
+ "generate": "protons ./src/pb/*.proto",
70
70
  "test": "aegir test",
71
71
  "test:chrome": "aegir test -t browser --cov",
72
72
  "test:chrome-webworker": "aegir test -t webworker",
@@ -76,34 +76,35 @@
76
76
  "test:electron-main": "aegir test -t electron-main"
77
77
  },
78
78
  "dependencies": {
79
- "@helia/interface": "6.2.1-35b081c7",
80
- "@libp2p/crypto": "^5.1.15",
79
+ "@helia/interface": "6.2.1-73a28eda",
81
80
  "@libp2p/fetch": "^4.1.0",
82
81
  "@libp2p/interface": "^3.2.0",
83
82
  "@libp2p/kad-dht": "^16.2.0",
84
- "@libp2p/keychain": "^6.0.12",
85
83
  "@libp2p/logger": "^6.2.4",
86
84
  "@libp2p/peer-collections": "^7.0.15",
87
85
  "@libp2p/utils": "^7.0.15",
86
+ "abort-error": "^1.0.2",
88
87
  "any-signal": "^4.2.0",
88
+ "cborg": "^5.1.1",
89
89
  "delay": "^7.0.0",
90
- "interface-datastore": "^9.0.3",
91
- "ipns": "^10.1.3",
92
- "multiformats": "^13.4.2",
90
+ "interface-datastore": "^10.0.1",
91
+ "multiformats": "^14.0.0",
93
92
  "progress-events": "^1.1.0",
94
- "protons-runtime": "^6.0.1",
93
+ "protons-runtime": "^7.0.0",
95
94
  "race-signal": "^2.0.0",
96
- "uint8arraylist": "^2.4.8",
97
- "uint8arrays": "^5.1.0"
95
+ "timestamp-nano": "^1.0.1",
96
+ "uint8arraylist": "^3.0.2",
97
+ "uint8arrays": "^6.1.1"
98
98
  },
99
99
  "devDependencies": {
100
- "@libp2p/crypto": "^5.1.15",
101
- "@libp2p/peer-id": "^6.0.6",
102
- "aegir": "^47.1.5",
103
- "datastore-core": "^11.0.3",
100
+ "@ipshipyard/crypto": "^1.1.0",
101
+ "@ipshipyard/keychain": "^1.0.2",
102
+ "aegir": "^48.0.4",
103
+ "datastore-core": "^12.0.1",
104
104
  "it-drain": "^3.0.12",
105
- "protons": "^8.1.1",
106
- "sinon": "^21.1.0",
105
+ "it-last": "^3.0.11",
106
+ "protons": "^9.0.1",
107
+ "sinon": "^22.0.0",
107
108
  "sinon-ts": "^2.0.0"
108
109
  },
109
110
  "browser": {
package/src/errors.ts CHANGED
@@ -1,49 +1,64 @@
1
1
  export class RecordsFailedValidationError extends Error {
2
2
  static name = 'RecordsFailedValidationError'
3
-
4
- constructor (message = 'Records failed validation') {
5
- super(message)
6
- this.name = 'RecordsFailedValidationError'
7
- }
3
+ name = 'RecordsFailedValidationError'
8
4
  }
9
5
 
10
6
  export class UnsupportedMultibasePrefixError extends Error {
11
7
  static name = 'UnsupportedMultibasePrefixError'
12
-
13
- constructor (message = 'Unsupported multibase prefix') {
14
- super(message)
15
- this.name = 'UnsupportedMultibasePrefixError'
16
- }
8
+ name = 'UnsupportedMultibasePrefixError'
17
9
  }
18
10
 
19
11
  export class UnsupportedMultihashCodecError extends Error {
20
12
  static name = 'UnsupportedMultihashCodecError'
21
-
22
- constructor (message = 'Unsupported multihash codec') {
23
- super(message)
24
- this.name = 'UnsupportedMultihashCodecError'
25
- }
13
+ name = 'UnsupportedMultihashCodecError'
26
14
  }
27
15
 
28
16
  export class InvalidValueError extends Error {
29
17
  static name = 'InvalidValueError'
30
-
31
- constructor (message = 'Invalid value') {
32
- super(message)
33
- this.name = 'InvalidValueError'
34
- }
18
+ name = 'InvalidValueError'
35
19
  }
36
20
 
37
21
  export class InvalidTopicError extends Error {
38
22
  static name = 'InvalidTopicError'
39
-
40
- constructor (message = 'Invalid topic') {
41
- super(message)
42
- this.name = 'InvalidTopicError'
43
- }
23
+ name = 'InvalidTopicError'
44
24
  }
45
25
 
46
26
  export class RecordNotFoundError extends Error {
47
27
  static name = 'RecordNotFoundError'
48
28
  name = 'RecordNotFoundError'
49
29
  }
30
+
31
+ export class SignatureCreationError extends Error {
32
+ static name = 'SignatureCreationError'
33
+ name = 'SignatureCreationError'
34
+ }
35
+
36
+ export class SignatureVerificationError extends Error {
37
+ static name = 'SignatureVerificationError'
38
+ name = 'SignatureVerificationError'
39
+ }
40
+
41
+ export class RecordExpiredError extends Error {
42
+ static name = 'RecordExpiredError'
43
+ name = 'RecordExpiredError'
44
+ }
45
+
46
+ export class UnsupportedValidityError extends Error {
47
+ static name = 'UnsupportedValidityError'
48
+ name = 'UnsupportedValidityError'
49
+ }
50
+
51
+ export class RecordTooLargeError extends Error {
52
+ static name = 'RecordTooLargeError'
53
+ name = 'RecordTooLargeError'
54
+ }
55
+
56
+ export class InvalidRecordDataError extends Error {
57
+ static name = 'InvalidRecordDataError'
58
+ name = 'InvalidRecordDataError'
59
+ }
60
+
61
+ export class InvalidEmbeddedPublicKeyError extends Error {
62
+ static name = 'InvalidEmbeddedPublicKeyError'
63
+ name = 'InvalidEmbeddedPublicKeyError'
64
+ }
package/src/index.ts CHANGED
@@ -23,9 +23,9 @@
23
23
  * const { publicKey } = await name.publish('key-1', cid)
24
24
  *
25
25
  * // resolve the name
26
- * const result = await name.resolve(publicKey)
27
- *
28
- * console.info(result.cid, result.path)
26
+ * for await (const result of name.resolve(publicKey)) {
27
+ * console.info(result.record.value) // /ipfs/QmFoo
28
+ * }
29
29
  * ```
30
30
  *
31
31
  * @example Publishing a recursive record
@@ -53,8 +53,9 @@
53
53
  * const { publicKey: recursivePublicKey } = await name.publish('key-2', publicKey)
54
54
  *
55
55
  * // resolve the name recursively - it resolves until a CID is found
56
- * const result = await name.resolve(recursivePublicKey)
57
- * console.info(result.cid.toString() === cid.toString()) // true
56
+ * for await (const result of name.resolve(recursivePublicKey)) {
57
+ * console.info(result.record.value) // /ipfs/QmFoo../foo.txt
58
+ * }
58
59
  * ```
59
60
  *
60
61
  * @example Publishing a record with a path
@@ -82,9 +83,9 @@
82
83
  * const { publicKey } = await name.publish('key-1', `/ipfs/${finalDirCid}/foo.txt`)
83
84
  *
84
85
  * // resolve the name
85
- * const result = await name.resolve(publicKey)
86
- *
87
- * console.info(result.cid, result.path) // QmFoo.. 'foo.txt'
86
+ * for await (const result of name.resolve(publicKey)) {
87
+ * console.info(result.record.value) // /ipfs/QmFoo../foo.txt
88
+ * }
88
89
  * ```
89
90
  *
90
91
  * @example Using custom PubSub router
@@ -135,51 +136,12 @@
135
136
  * const { publicKey } = await name.publish('key-1', cid)
136
137
  *
137
138
  * // resolve the name
138
- * const result = await name.resolve(publicKey)
139
- * ```
140
- *
141
- * @example Republishing an existing IPNS record
142
- *
143
- * It is sometimes useful to be able to republish an existing IPNS record
144
- * without needing the private key. This allows you to extend the availability
145
- * of a record that was created elsewhere.
146
- *
147
- * ```TypeScript
148
- * import { createHelia } from 'helia'
149
- * import { ipns, ipnsValidator } from '@helia/ipns'
150
- * import { delegatedRoutingV1HttpApiClient } from '@helia/delegated-routing-v1-http-api-client'
151
- * import { CID } from 'multiformats/cid'
152
- * import { multihashToIPNSRoutingKey, marshalIPNSRecord } from 'ipns'
153
- * import { defaultLogger } from '@libp2p/logger'
154
- *
155
- * const helia = await createHelia()
156
- * const name = ipns(helia)
157
- *
158
- * const ipnsName = 'k51qzi5uqu5dktsyfv7xz8h631pri4ct7osmb43nibxiojpttxzoft6hdyyzg4'
159
- * const parsedCid: CID<unknown, 114, 0 | 18, 1> = CID.parse(ipnsName)
160
- * const delegatedClient = delegatedRoutingV1HttpApiClient({
161
- * url: 'https://delegated-ipfs.dev'
162
- * })({
163
- * logger: defaultLogger()
164
- * })
165
- * const record = await delegatedClient.getIPNS(parsedCid)
166
- *
167
- * const routingKey = multihashToIPNSRoutingKey(parsedCid.multihash)
168
- * const marshaledRecord = marshalIPNSRecord(record)
169
- *
170
- * // validate that they key corresponds to the record
171
- * await ipnsValidator(routingKey, marshaledRecord)
172
- *
173
- * // publish record to routing
174
- * await Promise.all(
175
- * name.routers.map(async r => {
176
- * await r.put(routingKey, marshaledRecord)
177
- * })
178
- * )
139
+ * for await (const result of name.resolve(publicKey)) {
140
+ * console.info(result.record.value)
141
+ * }
179
142
  * ```
180
143
  */
181
144
 
182
- import { ipnsValidator } from 'ipns/validator'
183
145
  import { CID } from 'multiformats/cid'
184
146
  import { IPNSResolver as IPNSResolverClass } from './ipns/resolver.ts'
185
147
  import { IPNS as IPNSClass } from './ipns.ts'
@@ -187,12 +149,12 @@ import { localStore } from './local-store.ts'
187
149
  import { helia } from './routing/index.ts'
188
150
  import { localStoreRouting } from './routing/local-store.ts'
189
151
  import type { IPNSResolverComponents } from './ipns/resolver.ts'
152
+ import type { IPNSRecord } from './records.ts'
190
153
  import type { IPNSRouting, IPNSRoutingProgressEvents } from './routing/index.ts'
191
- import type { Routing, HeliaEvents } from '@helia/interface'
192
- import type { AbortOptions, ComponentLogger, Libp2p, PeerId, PublicKey, TypedEventEmitter } from '@libp2p/interface'
193
- import type { Keychain } from '@libp2p/keychain'
154
+ import type { Routing, HeliaEvents, Keychain, PublicKey } from '@helia/interface'
155
+ import type { ComponentLogger, TypedEventEmitter } from '@libp2p/interface'
156
+ import type { AbortOptions } from 'abort-error'
194
157
  import type { Datastore } from 'interface-datastore'
195
- import type { IPNSRecord } from 'ipns'
196
158
  import type { MultihashDigest } from 'multiformats/hashes/interface'
197
159
  import type { ProgressEvent, ProgressOptions } from 'progress-events'
198
160
 
@@ -214,23 +176,31 @@ export type DatastoreProgressEvents =
214
176
 
215
177
  export interface PublishOptions extends AbortOptions, ProgressOptions<PublishProgressEvents | IPNSRoutingProgressEvents> {
216
178
  /**
217
- * Time duration of the signature validity in ms (default: 48hrs)
179
+ * Time duration of the signature validity in ms
180
+ *
181
+ * @default 172_800_000
218
182
  */
219
183
  lifetime?: number
220
184
 
221
185
  /**
222
- * Only publish to a local datastore (default: false)
186
+ * Only publish to a local datastore
187
+ *
188
+ * @default false
223
189
  */
224
190
  offline?: boolean
225
191
 
226
192
  /**
227
193
  * By default a IPNS V1 and a V2 signature is added to every record. Pass
228
- * false here to only add a V2 signature. (default: true)
194
+ * false here to only add a V2 signature.
195
+ *
196
+ * @default true
229
197
  */
230
198
  v1Compatible?: boolean
231
199
 
232
200
  /**
233
- * The TTL of the record in ms (default: 5 minutes)
201
+ * The TTL of the record in ms
202
+ *
203
+ * @default 300_000
234
204
  */
235
205
  ttl?: number
236
206
  }
@@ -257,32 +227,25 @@ export interface ResolveOptions extends AbortOptions, ProgressOptions<ResolvePro
257
227
  }
258
228
 
259
229
  export interface ResolveResult {
260
- /**
261
- * The CID that was resolved
262
- */
263
- cid: CID
264
-
265
- /**
266
- * Any path component that was part of the resolved record
267
- */
268
- path?: string
269
- }
270
-
271
- export interface IPNSResolveResult extends ResolveResult {
272
230
  /**
273
231
  * The resolved record
274
232
  */
275
233
  record: IPNSRecord
276
234
  }
277
235
 
278
- export interface IPNSPublishResult {
236
+ export interface PublishResult {
279
237
  /**
280
238
  * The published record
281
239
  */
282
240
  record: IPNSRecord
283
241
 
284
242
  /**
285
- * The public key that was used to publish the record
243
+ * The IPNS name that can be used to resolve this record
244
+ */
245
+ name: string
246
+
247
+ /**
248
+ * The public key that was used to sign and publish the record
286
249
  */
287
250
  publicKey: PublicKey
288
251
  }
@@ -295,7 +258,7 @@ export interface IPNSResolver {
295
258
  * Ed25519, secp256k1 or RSA PeerId and recursively resolves the IPNS record
296
259
  * corresponding to that key until a value is found.
297
260
  */
298
- resolve(key: CID<unknown, 0x72, 0x00 | 0x12, 1> | PublicKey | MultihashDigest<0x00 | 0x12> | PeerId, options?: ResolveOptions): Promise<IPNSResolveResult>
261
+ resolve(key: CID<unknown, 0x72> | MultihashDigest, options?: ResolveOptions): AsyncGenerator<ResolveResult>
299
262
  }
300
263
 
301
264
  export interface IPNS {
@@ -308,12 +271,14 @@ export interface IPNS {
308
271
  * Creates and publishes an IPNS record that will resolve the passed value
309
272
  * signed by a key stored in the libp2p keychain under the passed key name.
310
273
  *
274
+ * If the key does not exist, a new Ed25519 key will be created. To use a
275
+ * different key types, ensure the key is created and stored in the keychain
276
+ * before invoking this method.
277
+ *
311
278
  * It is possible to create a recursive IPNS record by passing:
312
279
  *
313
- * - A PeerId,
314
- * - A PublicKey
315
- * - A CID with the libp2p-key codec and Identity or SHA256 hash algorithms
316
- * - A Multihash with the Identity or SHA256 hash algorithms
280
+ * - A CID with the libp2p-key codec
281
+ * - A Multihash
317
282
  * - A string IPNS key (e.g. `/ipns/Qmfoo`)
318
283
  *
319
284
  * @example
@@ -332,38 +297,36 @@ export interface IPNS {
332
297
  * console.info(result) // { answer: ... }
333
298
  * ```
334
299
  */
335
- publish(keyName: string, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | PeerId | string, options?: PublishOptions): Promise<IPNSPublishResult>
300
+ publish(keyName: string, value: CID | PublicKey | MultihashDigest | string, options?: PublishOptions): Promise<PublishResult>
336
301
 
337
302
  /**
338
- * Accepts a libp2p public key, a CID with the libp2p-key codec and either the
339
- * identity hash (for Ed25519 and secp256k1 public keys) or a SHA256 hash (for
340
- * RSA public keys), or the multihash of a libp2p-key encoded CID, or a
341
- * Ed25519, secp256k1 or RSA PeerId and recursively resolves the IPNS record
342
- * corresponding to that key until a value is found.
303
+ * Accepts a multihash of a public key, a libp2p-key CID containing the
304
+ * multihash of a public key, or an IPNS name in it's string representation
305
+ * and recursively resolves IPNS records until a non-recursive record is found
306
+ * (e.g. the value can be parsed as a string that does not start with
307
+ * `/ipns/`).
343
308
  */
344
- resolve(key: CID<unknown, 0x72, 0x00 | 0x12, 1> | PublicKey | MultihashDigest<0x00 | 0x12> | PeerId, options?: ResolveOptions): Promise<IPNSResolveResult>
309
+ resolve(name: CID<unknown, 0x72> | PublicKey | MultihashDigest | string, options?: ResolveOptions): AsyncGenerator<ResolveResult>
345
310
 
346
311
  /**
347
312
  * Stop republishing of an IPNS record
348
313
  *
349
- * This will delete the last signed IPNS record from the datastore, but the
350
- * key will remain in the keychain.
314
+ * This will delete the last signed IPNS record from the datastore.
351
315
  *
352
316
  * Note that the record may still be resolved by other peers until it expires
353
- * or is no longer valid.
317
+ * or is otherwise no longer valid.
354
318
  */
355
319
  unpublish(keyName: string, options?: AbortOptions): Promise<void>
356
320
  }
357
321
 
358
322
  export type { IPNSRouting } from './routing/index.ts'
359
-
360
- export type { IPNSRecord } from 'ipns'
323
+ export type { IPNSRecord } from './records.ts'
361
324
 
362
325
  export interface IPNSComponents {
363
326
  datastore: Datastore
364
327
  routing: Routing
365
328
  logger: ComponentLogger
366
- libp2p: Libp2p<{ keychain: Keychain }>
329
+ keychain: Keychain
367
330
  events: TypedEventEmitter<HeliaEvents> // Helia event bus
368
331
  }
369
332
 
@@ -414,5 +377,4 @@ export function ipnsResolver (components: IPNSResolverComponents, options: IPNSR
414
377
  })
415
378
  }
416
379
 
417
- export { ipnsValidator, type IPNSRoutingProgressEvents }
418
- export { ipnsSelector } from 'ipns/selector'
380
+ export type { IPNSRoutingProgressEvents }
@@ -1,21 +1,19 @@
1
- import { generateKeyPair } from '@libp2p/crypto/keys'
2
- import { isPeerId } from '@libp2p/interface'
3
- import { createIPNSRecord, marshalIPNSRecord, multihashToIPNSRoutingKey, unmarshalIPNSRecord } from 'ipns'
4
- import { CID } from 'multiformats/cid'
1
+ import { base36 } from 'multiformats/bases/base36'
5
2
  import { CustomProgressEvent } from 'progress-events'
6
3
  import { DEFAULT_LIFETIME_MS, DEFAULT_TTL_NS } from '../constants.ts'
7
- import type { IPNSPublishResult, PublishOptions } from '../index.ts'
4
+ import { createIPNSRecord } from '../records.ts'
5
+ import { marshalIPNSRecord, multihashToIPNSRoutingKey, unmarshalIPNSRecord } from '../utils.ts'
6
+ import type { PublishResult, PublishOptions } from '../index.ts'
8
7
  import type { LocalStore } from '../local-store.ts'
9
8
  import type { IPNSRouting } from '../routing/index.ts'
10
- import type { AbortOptions, ComponentLogger, Libp2p, PeerId, PrivateKey, PublicKey } from '@libp2p/interface'
11
- import type { Keychain } from '@libp2p/keychain'
9
+ import type { Keychain, PrivateKey } from '@helia/interface'
10
+ import type { AbortOptions, ComponentLogger } from '@libp2p/interface'
12
11
  import type { Datastore } from 'interface-datastore'
13
- import type { MultihashDigest } from 'multiformats/hashes/interface'
14
12
 
15
13
  export interface IPNSPublisherComponents {
16
14
  datastore: Datastore
17
15
  logger: ComponentLogger
18
- libp2p: Libp2p<{ keychain: Keychain }>
16
+ keychain: Keychain
19
17
  }
20
18
 
21
19
  export interface IPNSPublisherInit {
@@ -29,32 +27,29 @@ export class IPNSPublisher {
29
27
  private readonly keychain: Keychain
30
28
 
31
29
  constructor (components: IPNSPublisherComponents, init: IPNSPublisherInit) {
32
- this.keychain = components.libp2p.services.keychain
30
+ this.keychain = components.keychain
33
31
  this.localStore = init.localStore
34
32
  this.routers = init.routers
35
33
  }
36
34
 
37
- async publish (keyName: string, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | PeerId | string, options: PublishOptions = {}): Promise<IPNSPublishResult> {
35
+ async publish (keyName: string, value: string, options: PublishOptions = {}): Promise<PublishResult> {
38
36
  try {
39
- const privKey = await this.#loadOrCreateKey(keyName)
37
+ const key = await this.#loadOrCreateKey(keyName, options)
38
+ const digest = key.publicKey.toMultihash()
39
+ const routingKey = multihashToIPNSRoutingKey(digest)
40
40
  let sequenceNumber = 1n
41
- const routingKey = multihashToIPNSRoutingKey(privKey.publicKey.toMultihash())
42
41
 
43
42
  if (await this.localStore.has(routingKey, options)) {
44
43
  // if we have published under this key before, increment the sequence number
45
44
  const { record } = await this.localStore.get(routingKey, options)
46
- const existingRecord = unmarshalIPNSRecord(record)
45
+ const existingRecord = await unmarshalIPNSRecord(routingKey, record, this.keychain, options)
47
46
  sequenceNumber = existingRecord.sequence + 1n
48
47
  }
49
48
 
50
- if (isPeerId(value)) {
51
- value = value.toCID()
52
- }
53
-
54
49
  // convert ttl from milliseconds to nanoseconds as createIPNSRecord expects
55
50
  const ttlNs = options.ttl != null ? BigInt(options.ttl) * 1_000_000n : DEFAULT_TTL_NS
56
51
  const lifetime = options.lifetime ?? DEFAULT_LIFETIME_MS
57
- const record = await createIPNSRecord(privKey, value, sequenceNumber, lifetime, { ...options, ttlNs })
52
+ const record = await createIPNSRecord(key, value, sequenceNumber, lifetime, { ...options, ttlNs })
58
53
  const marshaledRecord = marshalIPNSRecord(record)
59
54
 
60
55
  if (options.offline === true) {
@@ -68,8 +63,12 @@ export class IPNSPublisher {
68
63
  }
69
64
 
70
65
  return {
71
- record,
72
- publicKey: privKey.publicKey
66
+ record: {
67
+ ...record,
68
+ publicKey: key.publicKey
69
+ },
70
+ name: `/ipns/${key.publicKey.toCID().toString(base36)}`,
71
+ publicKey: key.publicKey
73
72
  }
74
73
  } catch (err: any) {
75
74
  options.onProgress?.(new CustomProgressEvent<Error>('ipns:publish:error', err))
@@ -77,20 +76,26 @@ export class IPNSPublisher {
77
76
  }
78
77
  }
79
78
 
80
- async #loadOrCreateKey (keyName: string): Promise<PrivateKey> {
79
+ /**
80
+ * Create the private key if it is not in the keychain already, defaulting to
81
+ * Ed25519 keys
82
+ */
83
+ async #loadOrCreateKey (keyName: string, options?: AbortOptions): Promise<PrivateKey> {
81
84
  try {
82
- return await this.keychain.exportKey(keyName)
85
+ return await this.keychain.exportKey(keyName, options)
83
86
  } catch (err: any) {
84
- // If no named key found in keychain, generate and import
85
- const privKey = await generateKeyPair('Ed25519')
86
- await this.keychain.importKey(keyName, privKey)
87
- return privKey
87
+ if (err.name === 'NotFoundError') {
88
+ // create a new key
89
+ return this.keychain.generateKey(keyName, options)
90
+ } else {
91
+ throw err
92
+ }
88
93
  }
89
94
  }
90
95
 
91
96
  async unpublish (keyName: string, options?: AbortOptions): Promise<void> {
92
- const { publicKey } = await this.keychain.exportKey(keyName)
93
- const digest = publicKey.toMultihash()
97
+ const key = await this.keychain.exportKey(keyName, options)
98
+ const digest = key.publicKey.toMultihash()
94
99
  const routingKey = multihashToIPNSRoutingKey(digest)
95
100
  await this.localStore.delete(routingKey, options)
96
101
  }