@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.
- package/README.md +3 -7
- package/dist/index.min.js +16 -5
- package/dist/index.min.js.map +4 -4
- package/dist/src/errors.d.ts +0 -8
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +0 -8
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +68 -31
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +11 -11
- package/dist/src/index.js.map +1 -1
- package/dist/src/ipns/publisher.d.ts +2 -2
- package/dist/src/ipns/publisher.d.ts.map +1 -1
- package/dist/src/ipns/publisher.js +27 -14
- package/dist/src/ipns/publisher.js.map +1 -1
- package/dist/src/ipns/republisher.d.ts.map +1 -1
- package/dist/src/ipns/republisher.js +22 -9
- package/dist/src/ipns/republisher.js.map +1 -1
- package/dist/src/ipns/resolver.d.ts +2 -2
- package/dist/src/ipns/resolver.d.ts.map +1 -1
- package/dist/src/ipns/resolver.js +39 -17
- package/dist/src/ipns/resolver.js.map +1 -1
- package/dist/src/ipns.d.ts +3 -3
- package/dist/src/ipns.d.ts.map +1 -1
- package/dist/src/ipns.js +8 -8
- package/dist/src/ipns.js.map +1 -1
- package/dist/src/local-store.d.ts +4 -4
- package/dist/src/local-store.d.ts.map +1 -1
- package/dist/src/local-store.js +2 -1
- package/dist/src/local-store.js.map +1 -1
- package/dist/src/pb/ipns.d.ts +29 -29
- package/dist/src/pb/ipns.d.ts.map +1 -1
- package/dist/src/pb/ipns.js +15 -15
- package/dist/src/records.d.ts +27 -142
- package/dist/src/records.d.ts.map +1 -1
- package/dist/src/records.js +39 -46
- package/dist/src/records.js.map +1 -1
- package/dist/src/routing/helia.d.ts +5 -5
- package/dist/src/routing/helia.d.ts.map +1 -1
- package/dist/src/routing/helia.js +3 -3
- package/dist/src/routing/helia.js.map +1 -1
- package/dist/src/routing/index.d.ts +7 -7
- package/dist/src/routing/index.d.ts.map +1 -1
- package/dist/src/routing/index.js +2 -2
- package/dist/src/routing/index.js.map +1 -1
- package/dist/src/routing/local-store.d.ts +1 -1
- package/dist/src/routing/local-store.d.ts.map +1 -1
- package/dist/src/routing/local-store.js +3 -3
- package/dist/src/routing/local-store.js.map +1 -1
- package/dist/src/routing/pubsub.d.ts +11 -11
- package/dist/src/routing/pubsub.d.ts.map +1 -1
- package/dist/src/routing/pubsub.js +11 -12
- package/dist/src/routing/pubsub.js.map +1 -1
- package/dist/src/selector.d.ts +2 -2
- package/dist/src/selector.d.ts.map +1 -1
- package/dist/src/selector.js +13 -9
- package/dist/src/selector.js.map +1 -1
- package/dist/src/utils.d.ts +10 -11
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +20 -128
- package/dist/src/utils.js.map +1 -1
- package/dist/src/validator.d.ts +9 -3
- package/dist/src/validator.d.ts.map +1 -1
- package/dist/src/validator.js +57 -14
- package/dist/src/validator.js.map +1 -1
- package/package.json +6 -31
- package/src/errors.ts +0 -10
- package/src/index.ts +85 -36
- package/src/ipns/publisher.ts +29 -16
- package/src/ipns/republisher.ts +31 -13
- package/src/ipns/resolver.ts +40 -22
- package/src/ipns.ts +11 -11
- package/src/local-store.ts +8 -7
- package/src/pb/ipns.proto +1 -1
- package/src/pb/ipns.ts +37 -37
- package/src/records.ts +57 -210
- package/src/routing/helia.ts +6 -6
- package/src/routing/index.ts +7 -7
- package/src/routing/local-store.ts +6 -6
- package/src/routing/pubsub.ts +24 -25
- package/src/selector.ts +14 -11
- package/src/utils.ts +26 -148
- package/src/validator.ts +73 -16
- package/dist/typedoc-urls.json +0 -49
package/dist/src/validator.js
CHANGED
|
@@ -1,22 +1,59 @@
|
|
|
1
1
|
import NanoDate from 'timestamp-nano';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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(
|
|
12
|
-
if (
|
|
13
|
-
throw new
|
|
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
|
|
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 (
|
|
29
|
-
if (NanoDate.fromString(
|
|
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 (
|
|
34
|
-
throw new UnsupportedValidityError(
|
|
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
|
-
|
|
87
|
+
const data = decodeExtensibleData(record.data);
|
|
88
|
+
if (data.ValidityType !== IPNSEntry.ValidityType.EOL) {
|
|
46
89
|
throw new UnsupportedValidityError();
|
|
47
90
|
}
|
|
48
|
-
if (
|
|
91
|
+
if (data.Validity == null) {
|
|
49
92
|
throw new UnsupportedValidityError();
|
|
50
93
|
}
|
|
51
|
-
const validUntil = NanoDate.fromString(
|
|
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;
|
|
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.
|
|
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": "
|
|
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
|
|
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
|
-
*
|
|
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 {
|
|
151
|
-
import {
|
|
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 {
|
|
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',
|
|
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',
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
|
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
|
|
278
|
+
export interface IPNSResolveResult {
|
|
231
279
|
/**
|
|
232
280
|
* The resolved record
|
|
233
281
|
*/
|
|
234
|
-
record:
|
|
282
|
+
record: IPNSEntry
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* The value contained within the IPNS record
|
|
286
|
+
*/
|
|
287
|
+
value: string
|
|
235
288
|
}
|
|
236
289
|
|
|
237
|
-
export interface
|
|
290
|
+
export interface IPNSPublishResult {
|
|
238
291
|
/**
|
|
239
292
|
* The published record
|
|
240
293
|
*/
|
|
241
|
-
record:
|
|
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
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
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
|
|
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<
|
|
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
|
|
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
|
-
|
|
371
|
-
|
|
419
|
+
localStoreIPNSRouting(store),
|
|
420
|
+
heliaIPNSRouting(components.routing),
|
|
372
421
|
...(options.routers ?? [])
|
|
373
422
|
]
|
|
374
423
|
|
package/src/ipns/publisher.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { base36 } from 'multiformats/bases/base36'
|
|
2
2
|
import { CustomProgressEvent } from 'progress-events'
|
|
3
|
-
import { DEFAULT_LIFETIME_MS
|
|
3
|
+
import { DEFAULT_LIFETIME_MS } from '../constants.ts'
|
|
4
|
+
import { IPNSEntry } from '../pb/ipns.ts'
|
|
4
5
|
import { createIPNSRecord } from '../records.ts'
|
|
5
|
-
import {
|
|
6
|
-
import type {
|
|
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<
|
|
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 =
|
|
46
|
-
|
|
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, {
|
|
53
|
-
|
|
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, {
|
|
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, {
|
|
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
|
}
|
package/src/ipns/republisher.ts
CHANGED
|
@@ -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 {
|
|
5
|
-
import {
|
|
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:
|
|
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
|
-
|
|
95
|
+
|
|
96
|
+
let ipnsRecord: IPNSEntry
|
|
97
|
+
|
|
95
98
|
try {
|
|
96
|
-
ipnsRecord =
|
|
97
|
-
} catch (err
|
|
98
|
-
this.log.
|
|
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(
|
|
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
|
-
|
|
108
|
-
const
|
|
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,
|
|
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 =
|
|
158
|
+
const marshaledRecord = IPNSEntry.encode(record)
|
|
141
159
|
await Promise.all(
|
|
142
160
|
this.routers.map(r => r.put(routingKey, marshaledRecord, options))
|
|
143
161
|
)
|