@helia/ipns 9.2.1-eb1908b3 → 9.2.1-ed6c3b79

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 +21 -57
  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 +62 -99
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +21 -60
  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 +24 -23
  55. package/src/errors.ts +40 -25
  56. package/src/index.ts +63 -100
  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
@@ -1,17 +1,18 @@
1
1
  import { Queue, repeatingTask } from '@libp2p/utils'
2
- import { createIPNSRecord, marshalIPNSRecord, unmarshalIPNSRecord } from 'ipns'
3
2
  import { DEFAULT_REPUBLISH_CONCURRENCY, DEFAULT_REPUBLISH_INTERVAL_MS, DEFAULT_TTL_NS } from '../constants.ts'
3
+ import { createIPNSRecord } from '../records.ts'
4
+ import { marshalIPNSRecord, unmarshalIPNSRecord } from '../utils.ts'
4
5
  import { shouldRepublish } from '../utils.ts'
6
+ import type { IPNSRecord } from '../index.ts'
5
7
  import type { LocalStore } from '../local-store.ts'
6
8
  import type { IPNSRouting } from '../routing/index.ts'
7
- import type { AbortOptions, ComponentLogger, Libp2p, Logger, PrivateKey } from '@libp2p/interface'
8
- import type { Keychain } from '@libp2p/keychain'
9
+ import type { Keychain, PrivateKey } from '@helia/interface'
10
+ import type { AbortOptions, ComponentLogger, Logger } from '@libp2p/interface'
9
11
  import type { RepeatingTask } from '@libp2p/utils'
10
- import type { IPNSRecord } from 'ipns'
11
12
 
12
13
  export interface IPNSRepublisherComponents {
13
14
  logger: ComponentLogger
14
- libp2p: Libp2p<{ keychain: Keychain }>
15
+ keychain: Keychain
15
16
  }
16
17
 
17
18
  export interface IPNSRepublisherInit {
@@ -33,9 +34,9 @@ export class IPNSRepublisher {
33
34
  constructor (components: IPNSRepublisherComponents, init: IPNSRepublisherInit) {
34
35
  this.log = components.logger.forComponent('helia:ipns')
35
36
  this.localStore = init.localStore
36
- this.keychain = components.libp2p.services.keychain
37
+ this.keychain = components.keychain
37
38
  this.republishConcurrency = init.republishConcurrency || DEFAULT_REPUBLISH_CONCURRENCY
38
- this.started = components.libp2p.status === 'started'
39
+ this.started = false
39
40
  this.routers = init.routers ?? []
40
41
 
41
42
  this.republishTask = repeatingTask(this.#republish.bind(this), init.republishInterval ?? DEFAULT_REPUBLISH_INTERVAL_MS, {
@@ -78,18 +79,21 @@ export class IPNSRepublisher {
78
79
 
79
80
  try {
80
81
  const recordsToRepublish: Array<{ routingKey: Uint8Array, record: IPNSRecord }> = []
82
+ let listed = 0
81
83
 
82
84
  // Find all records using the localStore.list method
83
85
  for await (const { routingKey, record, metadata, created } of this.localStore.list(options)) {
86
+ listed++
87
+
84
88
  if (metadata == null) {
85
89
  // Skip if no metadata is found from before we started
86
90
  // storing metadata or for records republished without a key
87
- this.log(`no metadata found for record ${routingKey.toString()}, skipping`)
91
+ this.log('no metadata found for record %b, skipping', routingKey)
88
92
  continue
89
93
  }
90
94
  let ipnsRecord: IPNSRecord
91
95
  try {
92
- ipnsRecord = unmarshalIPNSRecord(record)
96
+ ipnsRecord = await unmarshalIPNSRecord(routingKey, record, this.keychain, options)
93
97
  } catch (err: any) {
94
98
  this.log.error('error unmarshaling record - %e', err)
95
99
  continue
@@ -97,7 +101,7 @@ export class IPNSRepublisher {
97
101
 
98
102
  // Only republish records that are within the DHT or record expiry threshold
99
103
  if (!shouldRepublish(ipnsRecord, created)) {
100
- this.log.trace(`skipping record ${routingKey.toString()}within republish threshold`)
104
+ this.log.trace('skipping record %b within republish threshold', routingKey)
101
105
  continue
102
106
  }
103
107
  const sequenceNumber = ipnsRecord.sequence + 1n
@@ -110,16 +114,23 @@ export class IPNSRepublisher {
110
114
  this.log.error('missing key %s, skipping republishing record - %e', metadata.keyName, err)
111
115
  continue
112
116
  }
117
+
113
118
  try {
114
- const updatedRecord = await createIPNSRecord(privKey, ipnsRecord.value, sequenceNumber, metadata.lifetime, { ...options, ttlNs })
115
- recordsToRepublish.push({ routingKey, record: updatedRecord })
119
+ const updatedRecord = await createIPNSRecord(privKey, ipnsRecord.value, sequenceNumber, metadata.lifetime, {
120
+ ...options,
121
+ ttlNs
122
+ })
123
+ recordsToRepublish.push({
124
+ routingKey,
125
+ record: updatedRecord
126
+ })
116
127
  } catch (err: any) {
117
128
  this.log.error('error creating updated IPNS record for %s - %e', routingKey, err)
118
129
  continue
119
130
  }
120
131
  }
121
132
 
122
- this.log(`found ${recordsToRepublish.length} records to republish`)
133
+ this.log(`found ${recordsToRepublish.length}/${listed} records to republish`)
123
134
 
124
135
  // Republish each record
125
136
  for (const { routingKey, record } of recordsToRepublish) {
@@ -1,33 +1,21 @@
1
- import { isPeerId, isPublicKey } from '@libp2p/interface'
2
- import { multihashToIPNSRoutingKey, unmarshalIPNSRecord } from 'ipns'
3
- import { ipnsSelector } from 'ipns/selector'
4
- import { ipnsValidator } from 'ipns/validator'
5
- import { base36 } from 'multiformats/bases/base36'
6
- import { base58btc } from 'multiformats/bases/base58'
7
- import { CID } from 'multiformats/cid'
8
- import * as Digest from 'multiformats/hashes/digest'
9
1
  import { DEFAULT_TTL_NS } from '../constants.ts'
10
- import { InvalidValueError, RecordNotFoundError, RecordsFailedValidationError, UnsupportedMultibasePrefixError, UnsupportedMultihashCodecError } from '../errors.ts'
11
- import { isCodec, IDENTITY_CODEC, SHA2_256_CODEC, isLibp2pCID } from '../utils.ts'
12
- import type { IPNSResolveResult, ResolveOptions, ResolveResult } from '../index.ts'
2
+ import { RecordNotFoundError, RecordsFailedValidationError } from '../errors.ts'
3
+ import { ipnsSelector } from '../selector.ts'
4
+ import { multihashToIPNSRoutingKey, unmarshalIPNSRecord, normalizeKey, IPNS_STRING_PREFIX } from '../utils.ts'
5
+ import { ipnsValidator } from '../validator.ts'
6
+ import type { IPNSRecord, ResolveOptions, ResolveResult } from '../index.ts'
13
7
  import type { LocalStore } from '../local-store.ts'
14
8
  import type { IPNSRouting } from '../routing/index.ts'
15
- import type { Routing } from '@helia/interface'
16
- import type { ComponentLogger, Logger, PeerId, PublicKey } from '@libp2p/interface'
9
+ import type { Routing, Keychain } from '@helia/interface'
10
+ import type { ComponentLogger, Logger } from '@libp2p/interface'
17
11
  import type { Datastore } from 'interface-datastore'
18
- import type { IPNSRecord } from 'ipns'
19
- import type { MultibaseDecoder } from 'multiformats/bases/interface'
20
12
  import type { MultihashDigest } from 'multiformats/hashes/interface'
21
13
 
22
- const bases: Record<string, MultibaseDecoder<string>> = {
23
- [base36.prefix]: base36,
24
- [base58btc.prefix]: base58btc
25
- }
26
-
27
14
  export interface IPNSResolverComponents {
28
15
  datastore: Datastore
29
16
  routing: Routing
30
17
  logger: ComponentLogger
18
+ keychain: Keychain
31
19
  }
32
20
 
33
21
  export interface IPNResolverInit {
@@ -39,80 +27,37 @@ export class IPNSResolver {
39
27
  public readonly routers: IPNSRouting[]
40
28
  private readonly localStore: LocalStore
41
29
  private readonly log: Logger
30
+ private keychain: Keychain
42
31
 
43
32
  constructor (components: IPNSResolverComponents, init: IPNResolverInit) {
44
33
  this.log = components.logger.forComponent('helia:ipns')
45
34
  this.localStore = init.localStore
46
35
  this.routers = init.routers
36
+ this.keychain = components.keychain
47
37
  }
48
38
 
49
- async resolve (key: CID<unknown, 0x72, 0x00 | 0x12, 1> | PublicKey | MultihashDigest<0x00 | 0x12> | PeerId, options: ResolveOptions = {}): Promise<IPNSResolveResult> {
50
- const digest = isPublicKey(key) || isPeerId(key) ? key.toMultihash() : isLibp2pCID(key) ? key.multihash : key
51
- const routingKey = multihashToIPNSRoutingKey(digest)
52
- const record = await this.#findIpnsRecord(routingKey, options)
53
-
54
- return {
55
- ...(await this.#resolve(record.value, options)),
56
- record
57
- }
58
- }
39
+ async * resolve (key: MultihashDigest, options: ResolveOptions = {}): AsyncGenerator<ResolveResult> {
40
+ let { digest } = normalizeKey(key)
59
41
 
60
- async #resolve (ipfsPath: string, options: ResolveOptions = {}): Promise<ResolveResult> {
61
- const parts = ipfsPath.split('/')
62
- try {
63
- const scheme = parts[1]
64
-
65
- if (scheme === 'ipns') {
66
- const str = parts[2]
67
- const prefix = str.substring(0, 1)
68
- let buf: Uint8Array | undefined
69
-
70
- if (prefix === '1' || prefix === 'Q') {
71
- buf = base58btc.decode(`z${str}`)
72
- } else if (bases[prefix] != null) {
73
- buf = bases[prefix].decode(str)
74
- } else {
75
- throw new UnsupportedMultibasePrefixError(`Unsupported multibase prefix "${prefix}"`)
76
- }
42
+ while (true) {
43
+ const routingKey = multihashToIPNSRoutingKey(digest)
44
+ const record = await this.#findIpnsRecord(routingKey, options)
77
45
 
78
- let digest: MultihashDigest<number>
79
-
80
- try {
81
- digest = Digest.decode(buf)
82
- } catch {
83
- digest = CID.decode(buf).multihash
84
- }
85
-
86
- if (!isCodec(digest, IDENTITY_CODEC) && !isCodec(digest, SHA2_256_CODEC)) {
87
- throw new UnsupportedMultihashCodecError(`Unsupported multihash codec "${digest.code}"`)
88
- }
89
-
90
- const { cid } = await this.resolve(digest, options)
91
- const path = parts.slice(3).join('/')
92
-
93
- return {
94
- cid,
95
- path: path === '' ? undefined : path
96
- }
97
- } else if (scheme === 'ipfs') {
98
- const cid = CID.parse(parts[2])
99
- const path = parts.slice(3).join('/')
46
+ yield {
47
+ record
48
+ }
100
49
 
101
- return {
102
- cid,
103
- path: path === '' ? undefined : path
104
- }
50
+ if (!record.value.startsWith(IPNS_STRING_PREFIX)) {
51
+ // not a recursive record
52
+ break
105
53
  }
106
- } catch (err) {
107
- this.log.error('error parsing ipfs path - %e', err)
108
- }
109
54
 
110
- this.log.error('invalid ipfs path %s', ipfsPath)
111
- throw new InvalidValueError('Invalid value')
55
+ ({ digest } = normalizeKey(record.value))
56
+ }
112
57
  }
113
58
 
114
59
  async #findIpnsRecord (routingKey: Uint8Array, options: ResolveOptions = {}): Promise<IPNSRecord> {
115
- const records: Uint8Array[] = []
60
+ const records: IPNSRecord[] = []
116
61
  const cached = await this.localStore.has(routingKey, options)
117
62
 
118
63
  if (cached) {
@@ -121,17 +66,19 @@ export class IPNSResolver {
121
66
  if (options.nocache !== true) {
122
67
  try {
123
68
  // check the local cache first
124
- const { record, created } = await this.localStore.get(routingKey, options)
69
+ const { record: marshaledIPNSRecord, created } = await this.localStore.get(routingKey, options)
125
70
 
126
71
  this.log('record retrieved from cache')
127
72
 
73
+ // unmarshal the record
74
+ const ipnsRecord = await unmarshalIPNSRecord(routingKey, marshaledIPNSRecord, this.keychain, options)
75
+
128
76
  // validate the record
129
- await ipnsValidator(routingKey, record)
77
+ await ipnsValidator(ipnsRecord, options)
130
78
 
131
79
  this.log('record was valid')
132
80
 
133
81
  // check the TTL
134
- const ipnsRecord = unmarshalIPNSRecord(record)
135
82
 
136
83
  // IPNS TTL is in nanoseconds, convert to milliseconds, default to one
137
84
  // hour
@@ -155,7 +102,7 @@ export class IPNSResolver {
155
102
  // add the local record to our list of resolved record, and also
156
103
  // search the routing for updates - the most up to date record will be
157
104
  // returned
158
- records.push(record)
105
+ records.push(ipnsRecord)
159
106
  } catch (err) {
160
107
  this.log('cached record was invalid - %e', err)
161
108
  await this.localStore.delete(routingKey, options)
@@ -176,10 +123,10 @@ export class IPNSResolver {
176
123
 
177
124
  await Promise.all(
178
125
  this.routers.map(async (router) => {
179
- let record: Uint8Array
126
+ let marshaledIPNSRecord: Uint8Array
180
127
 
181
128
  try {
182
- record = await router.get(routingKey, {
129
+ marshaledIPNSRecord = await router.get(routingKey, {
183
130
  ...options,
184
131
  validate: false
185
132
  })
@@ -191,7 +138,12 @@ export class IPNSResolver {
191
138
  }
192
139
 
193
140
  try {
194
- await ipnsValidator(routingKey, record)
141
+ // unmarshal ensures that (1) SignatureV2 and Data are present, (2) that ValidityType
142
+ // and Validity are of valid types and have a value, (3) that CBOR data matches protobuf
143
+ // if it's a V1+V2 record
144
+ const record = await unmarshalIPNSRecord(routingKey, marshaledIPNSRecord, this.keychain, options)
145
+
146
+ await ipnsValidator(record, options)
195
147
 
196
148
  records.push(record)
197
149
  } catch (err) {
@@ -212,8 +164,8 @@ export class IPNSResolver {
212
164
 
213
165
  const record = records[ipnsSelector(routingKey, records)]
214
166
 
215
- await this.localStore.put(routingKey, record, options)
167
+ await this.localStore.put(routingKey, record.bytes, options)
216
168
 
217
- return unmarshalIPNSRecord(record)
169
+ return record
218
170
  }
219
171
  }
package/src/ipns.ts CHANGED
@@ -5,10 +5,15 @@ import { IPNSResolver } from './ipns/resolver.ts'
5
5
  import { localStore } from './local-store.ts'
6
6
  import { helia } from './routing/helia.ts'
7
7
  import { localStoreRouting } from './routing/local-store.ts'
8
- import type { IPNSComponents, IPNS as IPNSInterface, IPNSOptions, IPNSPublishResult, IPNSResolveResult, PublishOptions, ResolveOptions } from './index.ts'
8
+ import { ipnsSelector } from './selector.ts'
9
+ import { normalizeKey, normalizeValue, unmarshalIPNSRecord } from './utils.ts'
10
+ import { ipnsValidator } from './validator.ts'
11
+ import type { IPNSComponents, IPNS as IPNSInterface, IPNSOptions, PublishResult, PublishOptions, ResolveOptions, ResolveResult } from './index.ts'
9
12
  import type { LocalStore } from './local-store.ts'
10
13
  import type { IPNSRouting } from './routing/index.ts'
11
- import type { AbortOptions, PeerId, PublicKey, Startable } from '@libp2p/interface'
14
+ import type { PublicKey } from '@helia/interface'
15
+ import type { AbortOptions, Libp2p, Startable } from '@libp2p/interface'
16
+ import type { ValidateFn, SelectFn } from '@libp2p/kad-dht'
12
17
  import type { MultihashDigest } from 'multiformats/hashes/interface'
13
18
 
14
19
  export class IPNS implements IPNSInterface, Startable {
@@ -17,11 +22,13 @@ export class IPNS implements IPNSInterface, Startable {
17
22
  private readonly republisher: IPNSRepublisher
18
23
  private readonly resolver: IPNSResolver
19
24
  private readonly localStore: LocalStore
25
+ private readonly components: IPNSComponents
20
26
  private started: boolean
21
27
 
22
28
  constructor (components: IPNSComponents, init: IPNSOptions = {}) {
23
29
  this.localStore = localStore(components.datastore, components.logger.forComponent('helia:ipns:local-store'))
24
- this.started = components.libp2p.status === 'started'
30
+ this.components = components
31
+ this.started = false
25
32
 
26
33
  this.routers = [
27
34
  localStoreRouting(this.localStore),
@@ -53,6 +60,26 @@ export class IPNS implements IPNSInterface, Startable {
53
60
  if (this.started) {
54
61
  this.republisher.start()
55
62
  }
63
+
64
+ for (const component of Object.values(this.components)) {
65
+ if (isLibp2p(component)) {
66
+ for (const service of Object.values(component.services)) {
67
+ if (isKadDHT(service)) {
68
+ // @ ts-expect-error https://github.com/libp2p/js-libp2p/pull/3506
69
+ service.selectors.ipns = async (key: Uint8Array, values: Uint8Array[]): Promise<number> => {
70
+ const records = await Promise.all(values.map(buf => unmarshalIPNSRecord(key, buf, this.components.keychain)))
71
+
72
+ return ipnsSelector(key, records)
73
+ }
74
+
75
+ service.validators.ipns = async (key: Uint8Array, value: Uint8Array): Promise<void> => {
76
+ const record = await unmarshalIPNSRecord(key, value, this.components.keychain)
77
+ await ipnsValidator(record)
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
56
83
  }
57
84
 
58
85
  start (): void {
@@ -73,15 +100,25 @@ export class IPNS implements IPNSInterface, Startable {
73
100
  this.republisher.stop()
74
101
  }
75
102
 
76
- async publish (keyName: string, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | PeerId | string, options: PublishOptions = {}): Promise<IPNSPublishResult> {
77
- return this.publisher.publish(keyName, value, options)
103
+ async publish (keyName: string, value: PublicKey | CID | MultihashDigest | string, options: PublishOptions = {}): Promise<PublishResult> {
104
+ return this.publisher.publish(keyName, normalizeValue(value), options)
78
105
  }
79
106
 
80
- async resolve (key: CID<unknown, 0x72, 0x00 | 0x12, 1> | PublicKey | MultihashDigest<0x00 | 0x12> | PeerId, options: ResolveOptions = {}): Promise<IPNSResolveResult> {
81
- return this.resolver.resolve(key, options)
107
+ async * resolve (key: PublicKey | CID<unknown, 0x72> | MultihashDigest | string, options: ResolveOptions = {}): AsyncGenerator<ResolveResult> {
108
+ const { digest } = normalizeKey(key)
109
+
110
+ yield * this.resolver.resolve(digest, options)
82
111
  }
83
112
 
84
113
  async unpublish (keyName: string, options?: AbortOptions): Promise<void> {
85
114
  return this.publisher.unpublish(keyName, options)
86
115
  }
87
116
  }
117
+
118
+ function isLibp2p (obj?: any): obj is Libp2p {
119
+ return obj?.services != null
120
+ }
121
+
122
+ function isKadDHT (obj?: any): obj is { validators: Record<string, ValidateFn>, selectors: Record<string, SelectFn> } {
123
+ return obj?.validators != null && obj?.selectors != null
124
+ }
@@ -0,0 +1,39 @@
1
+ // https://github.com/ipfs/boxo/blob/main/ipns/pb/record.proto
2
+
3
+ syntax = "proto3";
4
+
5
+ message IpnsEntry {
6
+ enum ValidityType {
7
+ // setting an EOL says "this record is valid until..."
8
+ EOL = 0;
9
+ }
10
+
11
+ // legacy V1 copy of data[Value]
12
+ optional bytes value = 1;
13
+
14
+ // legacy V1 field, verify 'signatureV2' instead
15
+ optional bytes signatureV1 = 2;
16
+
17
+ // legacy V1 copies of data[ValidityType] and data[Validity]
18
+ optional ValidityType validityType = 3;
19
+ optional bytes validity = 4;
20
+
21
+ // legacy V1 copy of data[Sequence]
22
+ optional uint64 sequence = 5;
23
+
24
+ // legacy V1 copy copy of data[TTL]
25
+ optional uint64 ttl = 6;
26
+
27
+ // Optional Public Key to be used for signature verification.
28
+ // Used for big keys such as old RSA keys. Including the public key as part of
29
+ // the record itself makes it verifiable in offline mode, without any additional lookup.
30
+ // For newer Ed25519 keys, the public key is small enough that it can be embedded in the
31
+ // IPNS Name itself, making this field unnecessary.
32
+ optional bytes publicKey = 7;
33
+
34
+ // (mandatory V2) signature of the IPNS record
35
+ optional bytes signatureV2 = 8;
36
+
37
+ // (mandatory V2) extensible record data in DAG-CBOR format
38
+ optional bytes data = 9;
39
+ }