@helia/ipns 9.2.0 → 9.2.1-17530ed8
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.
- package/README.md +21 -57
- package/dist/index.min.js +9 -23
- package/dist/index.min.js.map +4 -4
- package/dist/src/errors.d.ts +33 -5
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +33 -20
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +62 -99
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +21 -60
- package/dist/src/index.js.map +1 -1
- package/dist/src/ipns/publisher.d.ts +5 -9
- package/dist/src/ipns/publisher.d.ts.map +1 -1
- package/dist/src/ipns/publisher.js +30 -22
- package/dist/src/ipns/publisher.js.map +1 -1
- package/dist/src/ipns/republisher.d.ts +3 -5
- package/dist/src/ipns/republisher.d.ts.map +1 -1
- package/dist/src/ipns/republisher.js +18 -9
- package/dist/src/ipns/republisher.js.map +1 -1
- package/dist/src/ipns/resolver.d.ts +6 -5
- package/dist/src/ipns/resolver.d.ts.map +1 -1
- package/dist/src/ipns/resolver.js +32 -78
- package/dist/src/ipns/resolver.js.map +1 -1
- package/dist/src/ipns.d.ts +6 -4
- package/dist/src/ipns.d.ts.map +1 -1
- package/dist/src/ipns.js +33 -4
- package/dist/src/ipns.js.map +1 -1
- package/dist/src/pb/ipns.d.ts +62 -0
- package/dist/src/pb/ipns.d.ts.map +1 -0
- package/dist/src/pb/ipns.js +203 -0
- package/dist/src/pb/ipns.js.map +1 -0
- package/dist/src/pb/metadata.d.ts +1 -1
- package/dist/src/pb/metadata.d.ts.map +1 -1
- package/dist/src/records.d.ts +155 -0
- package/dist/src/records.d.ts.map +1 -0
- package/dist/src/records.js +88 -0
- package/dist/src/records.js.map +1 -0
- package/dist/src/routing/pubsub.d.ts +3 -0
- package/dist/src/routing/pubsub.d.ts.map +1 -1
- package/dist/src/routing/pubsub.js +15 -10
- package/dist/src/routing/pubsub.js.map +1 -1
- package/dist/src/selector.d.ts +14 -0
- package/dist/src/selector.d.ts.map +1 -0
- package/dist/src/selector.js +47 -0
- package/dist/src/selector.js.map +1 -0
- package/dist/src/utils.d.ts +29 -2
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +308 -0
- package/dist/src/utils.js.map +1 -1
- package/dist/src/validator.d.ts +18 -0
- package/dist/src/validator.d.ts.map +1 -0
- package/dist/src/validator.js +58 -0
- package/dist/src/validator.js.map +1 -0
- package/package.json +32 -28
- package/src/errors.ts +40 -25
- package/src/index.ts +63 -100
- package/src/ipns/publisher.ts +34 -29
- package/src/ipns/republisher.ts +24 -13
- package/src/ipns/resolver.ts +40 -88
- package/src/ipns.ts +44 -7
- package/src/pb/ipns.proto +39 -0
- package/src/pb/ipns.ts +280 -0
- package/src/pb/metadata.ts +1 -1
- package/src/records.ts +273 -0
- package/src/routing/pubsub.ts +17 -10
- package/src/selector.ts +55 -0
- package/src/utils.ts +371 -2
- package/src/validator.ts +67 -0
- package/dist/typedoc-urls.json +0 -51
package/src/pb/ipns.ts
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { decodeMessage, encodeMessage, enumeration, message, streamMessage } from 'protons-runtime'
|
|
2
|
+
import type { Codec, DecodeOptions } from 'protons-runtime'
|
|
3
|
+
import type { Uint8ArrayList } from 'uint8arraylist'
|
|
4
|
+
|
|
5
|
+
export interface IpnsEntry {
|
|
6
|
+
value?: Uint8Array
|
|
7
|
+
signatureV1?: Uint8Array
|
|
8
|
+
validityType?: IpnsEntry.ValidityType
|
|
9
|
+
validity?: Uint8Array
|
|
10
|
+
sequence?: bigint
|
|
11
|
+
ttl?: bigint
|
|
12
|
+
publicKey?: Uint8Array
|
|
13
|
+
signatureV2?: Uint8Array
|
|
14
|
+
data?: Uint8Array
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export namespace IpnsEntry {
|
|
18
|
+
export enum ValidityType {
|
|
19
|
+
EOL = 'EOL'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
enum __ValidityTypeValues {
|
|
23
|
+
EOL = 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export namespace ValidityType {
|
|
27
|
+
export const codec = (): Codec<ValidityType> => {
|
|
28
|
+
return enumeration<ValidityType>(__ValidityTypeValues)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let _codec: Codec<IpnsEntry>
|
|
33
|
+
|
|
34
|
+
export const codec = (): Codec<IpnsEntry> => {
|
|
35
|
+
if (_codec == null) {
|
|
36
|
+
_codec = message<IpnsEntry>((obj, w, opts = {}) => {
|
|
37
|
+
if (opts.lengthDelimited !== false) {
|
|
38
|
+
w.fork()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (obj.value != null) {
|
|
42
|
+
w.uint32(10)
|
|
43
|
+
w.bytes(obj.value)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (obj.signatureV1 != null) {
|
|
47
|
+
w.uint32(18)
|
|
48
|
+
w.bytes(obj.signatureV1)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (obj.validityType != null) {
|
|
52
|
+
w.uint32(24)
|
|
53
|
+
IpnsEntry.ValidityType.codec().encode(obj.validityType, w)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (obj.validity != null) {
|
|
57
|
+
w.uint32(34)
|
|
58
|
+
w.bytes(obj.validity)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (obj.sequence != null) {
|
|
62
|
+
w.uint32(40)
|
|
63
|
+
w.uint64(obj.sequence)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (obj.ttl != null) {
|
|
67
|
+
w.uint32(48)
|
|
68
|
+
w.uint64(obj.ttl)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (obj.publicKey != null) {
|
|
72
|
+
w.uint32(58)
|
|
73
|
+
w.bytes(obj.publicKey)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (obj.signatureV2 != null) {
|
|
77
|
+
w.uint32(66)
|
|
78
|
+
w.bytes(obj.signatureV2)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (obj.data != null) {
|
|
82
|
+
w.uint32(74)
|
|
83
|
+
w.bytes(obj.data)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (opts.lengthDelimited !== false) {
|
|
87
|
+
w.ldelim()
|
|
88
|
+
}
|
|
89
|
+
}, (reader, length, opts = {}) => {
|
|
90
|
+
const obj: any = {}
|
|
91
|
+
|
|
92
|
+
const end = length == null ? reader.len : reader.pos + length
|
|
93
|
+
|
|
94
|
+
while (reader.pos < end) {
|
|
95
|
+
const tag = reader.uint32()
|
|
96
|
+
|
|
97
|
+
switch (tag >>> 3) {
|
|
98
|
+
case 1: {
|
|
99
|
+
obj.value = reader.bytes()
|
|
100
|
+
break
|
|
101
|
+
}
|
|
102
|
+
case 2: {
|
|
103
|
+
obj.signatureV1 = reader.bytes()
|
|
104
|
+
break
|
|
105
|
+
}
|
|
106
|
+
case 3: {
|
|
107
|
+
obj.validityType = IpnsEntry.ValidityType.codec().decode(reader)
|
|
108
|
+
break
|
|
109
|
+
}
|
|
110
|
+
case 4: {
|
|
111
|
+
obj.validity = reader.bytes()
|
|
112
|
+
break
|
|
113
|
+
}
|
|
114
|
+
case 5: {
|
|
115
|
+
obj.sequence = reader.uint64()
|
|
116
|
+
break
|
|
117
|
+
}
|
|
118
|
+
case 6: {
|
|
119
|
+
obj.ttl = reader.uint64()
|
|
120
|
+
break
|
|
121
|
+
}
|
|
122
|
+
case 7: {
|
|
123
|
+
obj.publicKey = reader.bytes()
|
|
124
|
+
break
|
|
125
|
+
}
|
|
126
|
+
case 8: {
|
|
127
|
+
obj.signatureV2 = reader.bytes()
|
|
128
|
+
break
|
|
129
|
+
}
|
|
130
|
+
case 9: {
|
|
131
|
+
obj.data = reader.bytes()
|
|
132
|
+
break
|
|
133
|
+
}
|
|
134
|
+
default: {
|
|
135
|
+
reader.skipType(tag & 7)
|
|
136
|
+
break
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return obj
|
|
142
|
+
}, function * (reader, length, prefix, opts = {}) {
|
|
143
|
+
const end = length == null ? reader.len : reader.pos + length
|
|
144
|
+
|
|
145
|
+
while (reader.pos < end) {
|
|
146
|
+
const tag = reader.uint32()
|
|
147
|
+
|
|
148
|
+
switch (tag >>> 3) {
|
|
149
|
+
case 1: {
|
|
150
|
+
yield {
|
|
151
|
+
field: `${prefix}.value`,
|
|
152
|
+
value: reader.bytes()
|
|
153
|
+
}
|
|
154
|
+
break
|
|
155
|
+
}
|
|
156
|
+
case 2: {
|
|
157
|
+
yield {
|
|
158
|
+
field: `${prefix}.signatureV1`,
|
|
159
|
+
value: reader.bytes()
|
|
160
|
+
}
|
|
161
|
+
break
|
|
162
|
+
}
|
|
163
|
+
case 3: {
|
|
164
|
+
yield {
|
|
165
|
+
field: `${prefix}.validityType`,
|
|
166
|
+
value: IpnsEntry.ValidityType.codec().decode(reader)
|
|
167
|
+
}
|
|
168
|
+
break
|
|
169
|
+
}
|
|
170
|
+
case 4: {
|
|
171
|
+
yield {
|
|
172
|
+
field: `${prefix}.validity`,
|
|
173
|
+
value: reader.bytes()
|
|
174
|
+
}
|
|
175
|
+
break
|
|
176
|
+
}
|
|
177
|
+
case 5: {
|
|
178
|
+
yield {
|
|
179
|
+
field: `${prefix}.sequence`,
|
|
180
|
+
value: reader.uint64()
|
|
181
|
+
}
|
|
182
|
+
break
|
|
183
|
+
}
|
|
184
|
+
case 6: {
|
|
185
|
+
yield {
|
|
186
|
+
field: `${prefix}.ttl`,
|
|
187
|
+
value: reader.uint64()
|
|
188
|
+
}
|
|
189
|
+
break
|
|
190
|
+
}
|
|
191
|
+
case 7: {
|
|
192
|
+
yield {
|
|
193
|
+
field: `${prefix}.publicKey`,
|
|
194
|
+
value: reader.bytes()
|
|
195
|
+
}
|
|
196
|
+
break
|
|
197
|
+
}
|
|
198
|
+
case 8: {
|
|
199
|
+
yield {
|
|
200
|
+
field: `${prefix}.signatureV2`,
|
|
201
|
+
value: reader.bytes()
|
|
202
|
+
}
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
case 9: {
|
|
206
|
+
yield {
|
|
207
|
+
field: `${prefix}.data`,
|
|
208
|
+
value: reader.bytes()
|
|
209
|
+
}
|
|
210
|
+
break
|
|
211
|
+
}
|
|
212
|
+
default: {
|
|
213
|
+
reader.skipType(tag & 7)
|
|
214
|
+
break
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return _codec
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface IpnsEntryValueFieldEvent {
|
|
225
|
+
field: '$.value'
|
|
226
|
+
value: Uint8Array
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface IpnsEntrySignatureV1FieldEvent {
|
|
230
|
+
field: '$.signatureV1'
|
|
231
|
+
value: Uint8Array
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface IpnsEntryValidityTypeFieldEvent {
|
|
235
|
+
field: '$.validityType'
|
|
236
|
+
value: IpnsEntry.ValidityType
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface IpnsEntryValidityFieldEvent {
|
|
240
|
+
field: '$.validity'
|
|
241
|
+
value: Uint8Array
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface IpnsEntrySequenceFieldEvent {
|
|
245
|
+
field: '$.sequence'
|
|
246
|
+
value: bigint
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface IpnsEntryTtlFieldEvent {
|
|
250
|
+
field: '$.ttl'
|
|
251
|
+
value: bigint
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface IpnsEntryPublicKeyFieldEvent {
|
|
255
|
+
field: '$.publicKey'
|
|
256
|
+
value: Uint8Array
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export interface IpnsEntrySignatureV2FieldEvent {
|
|
260
|
+
field: '$.signatureV2'
|
|
261
|
+
value: Uint8Array
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface IpnsEntryDataFieldEvent {
|
|
265
|
+
field: '$.data'
|
|
266
|
+
value: Uint8Array
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function encode (obj: Partial<IpnsEntry>): Uint8Array<ArrayBuffer> {
|
|
270
|
+
return encodeMessage(obj, IpnsEntry.codec())
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function decode (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<IpnsEntry>): IpnsEntry {
|
|
274
|
+
return decodeMessage(buf, IpnsEntry.codec(), opts)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function stream (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<IpnsEntry>): Generator<IpnsEntryValueFieldEvent | IpnsEntrySignatureV1FieldEvent | IpnsEntryValidityTypeFieldEvent | IpnsEntryValidityFieldEvent | IpnsEntrySequenceFieldEvent | IpnsEntryTtlFieldEvent | IpnsEntryPublicKeyFieldEvent | IpnsEntrySignatureV2FieldEvent | IpnsEntryDataFieldEvent> {
|
|
278
|
+
return streamMessage(buf, IpnsEntry.codec(), opts)
|
|
279
|
+
}
|
|
280
|
+
}
|
package/src/pb/metadata.ts
CHANGED
|
@@ -101,7 +101,7 @@ export namespace IPNSPublishMetadata {
|
|
|
101
101
|
value: number
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
export function encode (obj: Partial<IPNSPublishMetadata>): Uint8Array {
|
|
104
|
+
export function encode (obj: Partial<IPNSPublishMetadata>): Uint8Array<ArrayBuffer> {
|
|
105
105
|
return encodeMessage(obj, IPNSPublishMetadata.codec())
|
|
106
106
|
}
|
|
107
107
|
|
package/src/records.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { logger } from '@libp2p/logger'
|
|
2
|
+
import NanoDate from 'timestamp-nano'
|
|
3
|
+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
4
|
+
import { SignatureCreationError } from './errors.ts'
|
|
5
|
+
import { IpnsEntry } from './pb/ipns.ts'
|
|
6
|
+
import { createCborData, ipnsRecordDataForV1Sig, ipnsRecordDataForV2Sig, marshalIPNSRecord } from './utils.ts'
|
|
7
|
+
import type { PrivateKey, PublicKey } from '@helia/interface'
|
|
8
|
+
import type { AbortOptions } from 'abort-error'
|
|
9
|
+
import type { Key } from 'interface-datastore/key'
|
|
10
|
+
|
|
11
|
+
const log = logger('ipns')
|
|
12
|
+
const DEFAULT_TTL_NS = 5 * 60 * 1e+9 // 5 Minutes or 300 Seconds, as suggested by https://specs.ipfs.tech/ipns/ipns-record/#ttl-uint64
|
|
13
|
+
|
|
14
|
+
export const namespace = '/ipns/'
|
|
15
|
+
export const namespaceLength = namespace.length
|
|
16
|
+
|
|
17
|
+
export interface IPNSRecordV1V2 {
|
|
18
|
+
/**
|
|
19
|
+
* value of the record
|
|
20
|
+
*/
|
|
21
|
+
value: string
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* signature of the record
|
|
25
|
+
*/
|
|
26
|
+
signatureV1: Uint8Array
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Type of validation being used
|
|
30
|
+
*/
|
|
31
|
+
validityType: IpnsEntry.ValidityType
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* expiration datetime for the record in RFC3339 format
|
|
35
|
+
*/
|
|
36
|
+
validity: string
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* number representing the version of the record
|
|
40
|
+
*/
|
|
41
|
+
sequence: bigint
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* ttl in nanoseconds
|
|
45
|
+
*/
|
|
46
|
+
ttl?: bigint
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* the public portion of the key that signed this record
|
|
50
|
+
*/
|
|
51
|
+
publicKey: PublicKey
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* the v2 signature of the record
|
|
55
|
+
*/
|
|
56
|
+
signatureV2: Uint8Array
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* extensible data
|
|
60
|
+
*/
|
|
61
|
+
data: Uint8Array
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The marshalled record
|
|
65
|
+
*/
|
|
66
|
+
bytes: Uint8Array
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface IPNSRecordV2 {
|
|
70
|
+
/**
|
|
71
|
+
* value of the record
|
|
72
|
+
*/
|
|
73
|
+
value: string
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* the v2 signature of the record
|
|
77
|
+
*/
|
|
78
|
+
signatureV2: Uint8Array
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Type of validation being used
|
|
82
|
+
*/
|
|
83
|
+
validityType: IpnsEntry.ValidityType
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* If the validity type is EOL, this is the expiration datetime for the record
|
|
87
|
+
* in RFC3339 format
|
|
88
|
+
*/
|
|
89
|
+
validity: string
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* number representing the version of the record
|
|
93
|
+
*/
|
|
94
|
+
sequence: bigint
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* ttl in nanoseconds
|
|
98
|
+
*/
|
|
99
|
+
ttl?: bigint
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* the public portion of the key that signed this record
|
|
103
|
+
*/
|
|
104
|
+
publicKey: PublicKey
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* extensible data
|
|
108
|
+
*/
|
|
109
|
+
data: Uint8Array
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The marshalled record
|
|
113
|
+
*/
|
|
114
|
+
bytes: Uint8Array
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type IPNSRecord = IPNSRecordV1V2 | IPNSRecordV2
|
|
118
|
+
|
|
119
|
+
export interface IPNSRecordData {
|
|
120
|
+
Value: Uint8Array
|
|
121
|
+
Validity: Uint8Array
|
|
122
|
+
ValidityType: IpnsEntry.ValidityType
|
|
123
|
+
Sequence: bigint
|
|
124
|
+
TTL: bigint
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface IDKeys {
|
|
128
|
+
routingPubKey: Key
|
|
129
|
+
pkKey: Key
|
|
130
|
+
routingKey: Key
|
|
131
|
+
ipnsKey: Key
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface CreateOptions extends AbortOptions {
|
|
135
|
+
ttlNs?: number | bigint
|
|
136
|
+
v1Compatible?: boolean
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface CreateV2OrV1Options extends AbortOptions {
|
|
140
|
+
v1Compatible: true
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface CreateV2Options extends AbortOptions {
|
|
144
|
+
v1Compatible: false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const defaultCreateOptions: CreateOptions = {
|
|
148
|
+
v1Compatible: true,
|
|
149
|
+
ttlNs: DEFAULT_TTL_NS
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Creates a new IPNS record and signs it with the given private key.
|
|
154
|
+
* The IPNS Record validity should follow the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision.
|
|
155
|
+
* Note: This function does not embed the public key. If you want to do that, use `EmbedPublicKey`.
|
|
156
|
+
*
|
|
157
|
+
* The passed value can be a CID, a PublicKey or an arbitrary string path e.g. `/ipfs/...` or `/ipns/...`.
|
|
158
|
+
*
|
|
159
|
+
* CIDs will be converted to v1 and stored in the record as a string similar to: `/ipfs/${cid}`
|
|
160
|
+
* PublicKeys will create recursive records, eg. the record value will be `/ipns/${cidV1Libp2pKey}`
|
|
161
|
+
* String paths will be stored in the record as-is, but they must start with `"/"`
|
|
162
|
+
*
|
|
163
|
+
* @param {PrivateKey} privateKey - the private key for signing the record.
|
|
164
|
+
* @param {CID | PublicKey | string} value - content to be stored in the record.
|
|
165
|
+
* @param {number | bigint} seq - number representing the current version of the record.
|
|
166
|
+
* @param {number} lifetime - lifetime of the record (in milliseconds).
|
|
167
|
+
* @param {CreateOptions} options - additional create options.
|
|
168
|
+
*/
|
|
169
|
+
export async function createIPNSRecord (privateKey: PrivateKey, value: string, seq: number | bigint, lifetime: number, options?: CreateV2OrV1Options): Promise<IPNSRecordV1V2>
|
|
170
|
+
export async function createIPNSRecord (privateKey: PrivateKey, value: string, seq: number | bigint, lifetime: number, options: CreateV2Options): Promise<IPNSRecordV2>
|
|
171
|
+
export async function createIPNSRecord (privateKey: PrivateKey, value: string, seq: number | bigint, lifetime: number, options: CreateOptions): Promise<IPNSRecordV1V2>
|
|
172
|
+
export async function createIPNSRecord (privateKey: PrivateKey, value: string, seq: number | bigint, lifetime: number, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> {
|
|
173
|
+
// Validity in ISOString with nanoseconds precision and validity type EOL
|
|
174
|
+
const expirationDate = new NanoDate(Date.now() + Number(lifetime))
|
|
175
|
+
const validityType = IpnsEntry.ValidityType.EOL
|
|
176
|
+
const ttlNs = BigInt(options.ttlNs ?? DEFAULT_TTL_NS)
|
|
177
|
+
|
|
178
|
+
return _create(privateKey, value, seq, validityType, expirationDate.toString(), ttlNs, options)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Same as create(), but instead of generating a new Date, it receives the intended expiration time
|
|
183
|
+
* WARNING: nano precision is not standard, make sure the value in seconds is 9 orders of magnitude lesser than the one provided.
|
|
184
|
+
*
|
|
185
|
+
* The passed value can be a CID, a PublicKey or an arbitrary string path e.g. `/ipfs/...` or `/ipns/...`.
|
|
186
|
+
*
|
|
187
|
+
* CIDs will be converted to v1 and stored in the record as a string similar to: `/ipfs/${cid}`
|
|
188
|
+
* PublicKeys will create recursive records, eg. the record value will be `/ipns/${cidV1Libp2pKey}`
|
|
189
|
+
* String paths will be stored in the record as-is, but they must start with `"/"`
|
|
190
|
+
*
|
|
191
|
+
* @param {PrivateKey} privateKey - the private key for signing the record.
|
|
192
|
+
* @param {CID | PublicKey | string} value - content to be stored in the record.
|
|
193
|
+
* @param {number | bigint} seq - number representing the current version of the record.
|
|
194
|
+
* @param {string} expiration - expiration datetime for record in the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision.
|
|
195
|
+
* @param {CreateOptions} options - additional creation options.
|
|
196
|
+
*/
|
|
197
|
+
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: string, seq: number | bigint, expiration: string, options?: CreateV2OrV1Options): Promise<IPNSRecordV1V2>
|
|
198
|
+
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: string, seq: number | bigint, expiration: string, options: CreateV2Options): Promise<IPNSRecordV2>
|
|
199
|
+
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: string, seq: number | bigint, expiration: string, options: CreateOptions): Promise<IPNSRecordV1V2>
|
|
200
|
+
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: string, seq: number | bigint, expiration: string, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> {
|
|
201
|
+
const expirationDate = NanoDate.fromString(expiration)
|
|
202
|
+
const validityType = IpnsEntry.ValidityType.EOL
|
|
203
|
+
const ttlNs = BigInt(options.ttlNs ?? DEFAULT_TTL_NS)
|
|
204
|
+
|
|
205
|
+
return _create(privateKey, value, seq, validityType, expirationDate.toString(), ttlNs, options)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const _create = async (privateKey: PrivateKey, value: string, seq: number | bigint, validityType: IpnsEntry.ValidityType, validity: string, ttl: bigint, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> => {
|
|
209
|
+
seq = BigInt(seq)
|
|
210
|
+
const isoValidity = uint8ArrayFromString(validity)
|
|
211
|
+
const data = createCborData(value, validityType, isoValidity, seq, ttl)
|
|
212
|
+
const sigData = ipnsRecordDataForV2Sig(data)
|
|
213
|
+
const signatureV2 = await privateKey.sign(sigData, options)
|
|
214
|
+
const publicKey = shouldEmbedPublicKey(privateKey.publicKey) ? privateKey.publicKey : undefined
|
|
215
|
+
let record: any
|
|
216
|
+
|
|
217
|
+
if (options.v1Compatible === true) {
|
|
218
|
+
const signatureV1 = await signLegacyV1(privateKey, value, validityType, isoValidity)
|
|
219
|
+
|
|
220
|
+
record = {
|
|
221
|
+
value,
|
|
222
|
+
signatureV1,
|
|
223
|
+
validity,
|
|
224
|
+
validityType,
|
|
225
|
+
sequence: seq,
|
|
226
|
+
ttl,
|
|
227
|
+
signatureV2,
|
|
228
|
+
data,
|
|
229
|
+
publicKey
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
record = {
|
|
233
|
+
value,
|
|
234
|
+
validity,
|
|
235
|
+
validityType,
|
|
236
|
+
sequence: seq,
|
|
237
|
+
ttl,
|
|
238
|
+
signatureV2,
|
|
239
|
+
data,
|
|
240
|
+
publicKey
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
record.bytes = marshalIPNSRecord(record)
|
|
245
|
+
|
|
246
|
+
return record
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export { unmarshalIPNSRecord } from './utils.ts'
|
|
250
|
+
export { marshalIPNSRecord } from './utils.ts'
|
|
251
|
+
export { multihashToIPNSRoutingKey } from './utils.ts'
|
|
252
|
+
export { multihashFromIPNSRoutingKey } from './utils.ts'
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Sign ipns record data using the legacy V1 signature scheme
|
|
256
|
+
*/
|
|
257
|
+
const signLegacyV1 = async (privateKey: PrivateKey, value: string, validityType: IpnsEntry.ValidityType, validity: Uint8Array, options?: AbortOptions): Promise<Uint8Array> => {
|
|
258
|
+
try {
|
|
259
|
+
const dataForSignature = ipnsRecordDataForV1Sig(value, validityType, validity)
|
|
260
|
+
|
|
261
|
+
return await privateKey.sign(dataForSignature, options)
|
|
262
|
+
} catch (error: any) {
|
|
263
|
+
log.error('record signature creation failed', error)
|
|
264
|
+
throw new SignatureCreationError('Record signature creation failed')
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Returns true if the public key multihash is not an identity hash
|
|
270
|
+
*/
|
|
271
|
+
function shouldEmbedPublicKey (key: PublicKey): boolean {
|
|
272
|
+
return key.toMultihash().code !== 0
|
|
273
|
+
}
|
package/src/routing/pubsub.ts
CHANGED
|
@@ -4,9 +4,6 @@ import { PeerSet } from '@libp2p/peer-collections'
|
|
|
4
4
|
import { Queue } from '@libp2p/utils'
|
|
5
5
|
import { anySignal } from 'any-signal'
|
|
6
6
|
import delay from 'delay'
|
|
7
|
-
import { multihashToIPNSRoutingKey } from 'ipns'
|
|
8
|
-
import { ipnsSelector } from 'ipns/selector'
|
|
9
|
-
import { ipnsValidator } from 'ipns/validator'
|
|
10
7
|
import { CustomProgressEvent } from 'progress-events'
|
|
11
8
|
import { raceSignal } from 'race-signal'
|
|
12
9
|
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
|
|
@@ -14,9 +11,13 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
|
14
11
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
15
12
|
import { InvalidTopicError } from '../errors.ts'
|
|
16
13
|
import { localStore } from '../local-store.ts'
|
|
17
|
-
import {
|
|
14
|
+
import { multihashToIPNSRoutingKey } from '../records.ts'
|
|
15
|
+
import { ipnsSelector } from '../selector.ts'
|
|
16
|
+
import { IPNS_STRING_PREFIX, unmarshalIPNSRecord } from '../utils.ts'
|
|
17
|
+
import { ipnsValidator } from '../validator.ts'
|
|
18
18
|
import type { GetOptions, IPNSRouting, PutOptions } from './index.ts'
|
|
19
19
|
import type { LocalStore } from '../local-store.ts'
|
|
20
|
+
import type { Keychain } from '@helia/interface'
|
|
20
21
|
import type { Fetch } from '@libp2p/fetch'
|
|
21
22
|
import type { PeerId, PublicKey, TypedEventTarget, ComponentLogger, Startable, AbortOptions, Metrics, Libp2p } from '@libp2p/interface'
|
|
22
23
|
import type { Datastore } from 'interface-datastore'
|
|
@@ -62,6 +63,7 @@ export interface PubSub extends TypedEventTarget<PubSubEvents> {
|
|
|
62
63
|
export interface PubsubRoutingComponents {
|
|
63
64
|
datastore: Datastore
|
|
64
65
|
logger: ComponentLogger
|
|
66
|
+
keychain: Keychain
|
|
65
67
|
metrics?: Metrics
|
|
66
68
|
libp2p: Pick<Libp2p<{ pubsub: PubSub, fetch?: Fetch }>, 'peerId' | 'register' | 'unregister' | 'services'>
|
|
67
69
|
}
|
|
@@ -105,6 +107,7 @@ export class PubSubRouting implements IPNSRouting, Startable {
|
|
|
105
107
|
private readonly fetchPeers: PeerSet
|
|
106
108
|
private shutdownController: AbortController
|
|
107
109
|
private fetchTopologyId?: string
|
|
110
|
+
private keychain: Keychain
|
|
108
111
|
|
|
109
112
|
constructor (components: PubsubRoutingComponents, init: PubsubRoutingInit = {}) {
|
|
110
113
|
this.subscriptions = new Set()
|
|
@@ -115,6 +118,7 @@ export class PubSubRouting implements IPNSRouting, Startable {
|
|
|
115
118
|
this.libp2p = components.libp2p
|
|
116
119
|
this.fetchConcurrency = init.fetchConcurrency ?? 8
|
|
117
120
|
this.fetchDelay = init.fetchDelay ?? 0
|
|
121
|
+
this.keychain = components.keychain
|
|
118
122
|
|
|
119
123
|
// default libp2p-fetch timeout is 10 seconds - we should have an existing
|
|
120
124
|
// connection to the peer so this can be shortened
|
|
@@ -235,23 +239,25 @@ export class PubSubRouting implements IPNSRouting, Startable {
|
|
|
235
239
|
}
|
|
236
240
|
|
|
237
241
|
async #handleRecord (topic: string, routingKey: Uint8Array, marshalledRecord: Uint8Array, publish: boolean, options?: AbortOptions): Promise<Uint8Array> {
|
|
238
|
-
await
|
|
242
|
+
const record = await unmarshalIPNSRecord(routingKey, marshalledRecord, this.keychain, options)
|
|
243
|
+
await ipnsValidator(record, options)
|
|
239
244
|
this.shutdownController.signal.throwIfAborted()
|
|
240
245
|
|
|
241
246
|
if (await this.localStore.has(routingKey)) {
|
|
242
|
-
const { record:
|
|
247
|
+
const { record: marshaledCurrentRecord } = await this.localStore.get(routingKey, options)
|
|
248
|
+
const currentRecord = await unmarshalIPNSRecord(routingKey, marshaledCurrentRecord, this.keychain, options)
|
|
243
249
|
|
|
244
|
-
if (uint8ArrayEquals(
|
|
250
|
+
if (uint8ArrayEquals(marshaledCurrentRecord, record.bytes)) {
|
|
245
251
|
log.trace('found identical record for %m', routingKey)
|
|
246
|
-
return currentRecord
|
|
252
|
+
return currentRecord.bytes
|
|
247
253
|
}
|
|
248
254
|
|
|
249
|
-
const records = [currentRecord,
|
|
255
|
+
const records = [currentRecord, record]
|
|
250
256
|
const index = ipnsSelector(routingKey, records)
|
|
251
257
|
|
|
252
258
|
if (index === 0) {
|
|
253
259
|
log.trace('found old record for %m', routingKey)
|
|
254
|
-
return currentRecord
|
|
260
|
+
return currentRecord.bytes
|
|
255
261
|
}
|
|
256
262
|
}
|
|
257
263
|
|
|
@@ -364,6 +370,7 @@ export class PubSubRouting implements IPNSRouting, Startable {
|
|
|
364
370
|
*/
|
|
365
371
|
cancel (key: PublicKey | MultihashDigest<0x00 | 0x12>): void {
|
|
366
372
|
const digest = isPublicKey(key) ? key.toMultihash() : key
|
|
373
|
+
// @ ts-expect-error @libp2p/crypto needs new multiformats
|
|
367
374
|
const routingKey = multihashToIPNSRoutingKey(digest)
|
|
368
375
|
const topic = keyToTopic(routingKey)
|
|
369
376
|
|
package/src/selector.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import NanoDate from 'timestamp-nano'
|
|
2
|
+
import { IpnsEntry } from './pb/ipns.ts'
|
|
3
|
+
import type { IPNSRecord } from './records.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Selects the latest valid IPNS record from an array of marshalled IPNS records.
|
|
7
|
+
*
|
|
8
|
+
* Records are sorted by:
|
|
9
|
+
* 1. Sequence number (higher takes precedence)
|
|
10
|
+
* 2. Validity time for EOL records with same sequence number (longer lived record takes precedence)
|
|
11
|
+
*
|
|
12
|
+
* @param key - The routing key for the IPNS record
|
|
13
|
+
* @param data - Array of marshalled IPNS records to select from
|
|
14
|
+
* @returns The index of the most valid record from the input array
|
|
15
|
+
*/
|
|
16
|
+
export function ipnsSelector (key: Uint8Array, data: IPNSRecord[]): number {
|
|
17
|
+
const entries = data.map((record, index) => ({
|
|
18
|
+
record,
|
|
19
|
+
index
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
entries.sort((a, b) => {
|
|
23
|
+
// Before we'd sort based on the signature version. Unmarshal now fails if
|
|
24
|
+
// a record does not have SignatureV2, so that is no longer needed. V1-only
|
|
25
|
+
// records haven't been issues in a long time.
|
|
26
|
+
|
|
27
|
+
const aSeq = a.record.sequence
|
|
28
|
+
const bSeq = b.record.sequence
|
|
29
|
+
|
|
30
|
+
// choose later sequence number
|
|
31
|
+
if (aSeq > bSeq) {
|
|
32
|
+
return -1
|
|
33
|
+
} else if (aSeq < bSeq) {
|
|
34
|
+
return 1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (a.record.validityType === IpnsEntry.ValidityType.EOL && b.record.validityType === IpnsEntry.ValidityType.EOL) {
|
|
38
|
+
// choose longer lived record if sequence numbers the same
|
|
39
|
+
const recordAValidityDate = NanoDate.fromString(a.record.validity).toDate()
|
|
40
|
+
const recordBValidityDate = NanoDate.fromString(b.record.validity).toDate()
|
|
41
|
+
|
|
42
|
+
if (recordAValidityDate.getTime() > recordBValidityDate.getTime()) {
|
|
43
|
+
return -1
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (recordAValidityDate.getTime() < recordBValidityDate.getTime()) {
|
|
47
|
+
return 1
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return 0
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return entries[0].index
|
|
55
|
+
}
|