1
- import { publicKeyFromMultihash , publicKeyFromProtobuf } from '@libp2p/crypto/keys'
1
+ import { publicKeyFromProtobuf } from '@libp2p/crypto/keys'
2
2
import { InvalidMultihashError } from '@libp2p/interface'
3
3
import { logger } from '@libp2p/logger'
4
4
import * as cborg from 'cborg'
@@ -12,17 +12,19 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
12
12
import { InvalidRecordDataError , InvalidValueError , SignatureVerificationError , UnsupportedValidityError } from './errors.js'
13
13
import { IpnsEntry } from './pb/ipns.js'
14
14
import type { IPNSRecord , IPNSRecordV2 , IPNSRecordData } from './index.js'
15
- import type { PublicKey , Ed25519PublicKey , Secp256k1PublicKey } from '@libp2p/interface'
15
+ import type { PublicKey } from '@libp2p/interface'
16
16
17
17
const log = logger ( 'ipns:utils' )
18
18
const IPNS_PREFIX = uint8ArrayFromString ( '/ipns/' )
19
- const LIBP2P_CID_CODEC = 114
19
+ const LIBP2P_CID_CODEC = 0x72
20
+ const IDENTITY_CODEC = 0x0
21
+ const SHA2_256_CODEC = 0x12
20
22
21
23
/**
22
24
* Extracts a public key from the passed PeerId, falling back to the pubKey
23
25
* embedded in the ipns record
24
26
*/
25
- export const extractPublicKeyFromIPNSRecord = ( record : IPNSRecord | IPNSRecordV2 ) : PublicKey | undefined => {
27
+ export function extractPublicKeyFromIPNSRecord ( record : IPNSRecord | IPNSRecordV2 ) : PublicKey | undefined {
26
28
let pubKey : PublicKey | undefined
27
29
28
30
if ( record . pubKey != null ) {
@@ -42,7 +44,7 @@ export const extractPublicKeyFromIPNSRecord = (record: IPNSRecord | IPNSRecordV2
42
44
/**
43
45
* Utility for creating the record data for being signed
44
46
*/
45
- export const ipnsRecordDataForV1Sig = ( value : Uint8Array , validityType : IpnsEntry . ValidityType , validity : Uint8Array ) : Uint8Array => {
47
+ export function ipnsRecordDataForV1Sig ( value : Uint8Array , validityType : IpnsEntry . ValidityType , validity : Uint8Array ) : Uint8Array {
46
48
const validityTypeBuffer = uint8ArrayFromString ( validityType )
47
49
48
50
return uint8ArrayConcat ( [ value , validity , validityTypeBuffer ] )
@@ -51,13 +53,13 @@ export const ipnsRecordDataForV1Sig = (value: Uint8Array, validityType: IpnsEntr
51
53
/**
52
54
* Utility for creating the record data for being signed
53
55
*/
54
- export const ipnsRecordDataForV2Sig = ( data : Uint8Array ) : Uint8Array => {
56
+ export function ipnsRecordDataForV2Sig ( data : Uint8Array ) : Uint8Array {
55
57
const entryData = uint8ArrayFromString ( 'ipns-signature:' )
56
58
57
59
return uint8ArrayConcat ( [ entryData , data ] )
58
60
}
59
61
60
- export const marshalIPNSRecord = ( obj : IPNSRecord | IPNSRecordV2 ) : Uint8Array => {
62
+ export function marshalIPNSRecord ( obj : IPNSRecord | IPNSRecordV2 ) : Uint8Array {
61
63
if ( 'signatureV1' in obj ) {
62
64
return IpnsEntry . encode ( {
63
65
value : uint8ArrayFromString ( obj . value ) ,
@@ -100,7 +102,7 @@ export function unmarshalIPNSRecord (buf: Uint8Array): IPNSRecord {
100
102
}
101
103
102
104
const data = parseCborData ( message . data )
103
- const value = normalizeValue ( data . Value )
105
+ const value = normalizeByteValue ( data . Value )
104
106
const validity = uint8ArrayToString ( data . Validity )
105
107
106
108
if ( message . value != null && message . signatureV1 != null ) {
@@ -135,36 +137,24 @@ export function unmarshalIPNSRecord (buf: Uint8Array): IPNSRecord {
135
137
}
136
138
}
137
139
138
- export const publicKeyToIPNSRoutingKey = ( publicKey : PublicKey ) : Uint8Array => {
139
- return multihashToIPNSRoutingKey ( publicKey . toMultihash ( ) )
140
- }
141
-
142
- export const multihashToIPNSRoutingKey = ( digest : MultihashDigest < 0x00 | 0x12 > ) : Uint8Array => {
140
+ export function multihashToIPNSRoutingKey ( digest : MultihashDigest < 0x00 | 0x12 > ) : Uint8Array {
143
141
return uint8ArrayConcat ( [
144
142
IPNS_PREFIX ,
145
143
digest . bytes
146
144
] )
147
145
}
148
146
149
- export const publicKeyFromIPNSRoutingKey = ( key : Uint8Array ) : Ed25519PublicKey | Secp256k1PublicKey | undefined => {
150
- try {
151
- // @ts -expect-error digest code may not be 0
152
- return publicKeyFromMultihash ( multihashFromIPNSRoutingKey ( key ) )
153
- } catch { }
154
- }
155
-
156
- export const multihashFromIPNSRoutingKey = ( key : Uint8Array ) : MultihashDigest < 0x00 | 0x12 > => {
147
+ export function multihashFromIPNSRoutingKey ( key : Uint8Array ) : MultihashDigest < 0x00 | 0x12 > {
157
148
const digest = Digest . decode ( key . slice ( IPNS_PREFIX . length ) )
158
149
159
- if ( digest . code !== 0x00 && digest . code !== 0x12 ) {
150
+ if ( ! isCodec ( digest , IDENTITY_CODEC ) && ! isCodec ( digest , SHA2_256_CODEC ) ) {
160
151
throw new InvalidMultihashError ( 'Multihash in IPNS key was not identity or sha2-256' )
161
152
}
162
153
163
- // @ts -expect-error digest may not have correct code even though we just checked
164
154
return digest
165
155
}
166
156
167
- export const createCborData = ( value : Uint8Array , validityType : IpnsEntry . ValidityType , validity : Uint8Array , sequence : bigint , ttl : bigint ) : Uint8Array => {
157
+ export function createCborData ( value : Uint8Array , validityType : IpnsEntry . ValidityType , validity : Uint8Array , sequence : bigint , ttl : bigint ) : Uint8Array {
168
158
let ValidityType
169
159
170
160
if ( validityType === IpnsEntry . ValidityType . EOL ) {
@@ -184,7 +174,7 @@ export const createCborData = (value: Uint8Array, validityType: IpnsEntry.Validi
184
174
return cborg . encode ( data )
185
175
}
186
176
187
- export const parseCborData = ( buf : Uint8Array ) : IPNSRecordData => {
177
+ export function parseCborData ( buf : Uint8Array ) : IPNSRecordData {
188
178
const data = cborg . decode ( buf )
189
179
190
180
if ( data . ValidityType === 0 ) {
@@ -206,35 +196,40 @@ export const parseCborData = (buf: Uint8Array): IPNSRecordData => {
206
196
return data
207
197
}
208
198
199
+ export function normalizeByteValue ( value : Uint8Array ) : string {
200
+ const string = uint8ArrayToString ( value ) . trim ( )
201
+
202
+ // if we have a path, check it is a valid path
203
+ if ( string . startsWith ( '/' ) ) {
204
+ return string
205
+ }
206
+
207
+ // try parsing what we have as CID bytes or a CID string
208
+ try {
209
+ return `/ipfs/${ CID . decode ( value ) . toV1 ( ) . toString ( ) } `
210
+ } catch {
211
+ // fall through
212
+ }
213
+
214
+ try {
215
+ return `/ipfs/${ CID . parse ( string ) . toV1 ( ) . toString ( ) } `
216
+ } catch {
217
+ // fall through
218
+ }
219
+
220
+ throw new InvalidValueError ( 'Value must be a valid content path starting with /' )
221
+ }
222
+
209
223
/**
210
224
* Normalizes the given record value. It ensures it is a PeerID, a CID or a
211
225
* string starting with '/'. PeerIDs become `/ipns/${cidV1Libp2pKey}`,
212
226
* CIDs become `/ipfs/${cidAsV1}`.
213
227
*/
214
- export const normalizeValue = ( value ?: CID | PublicKey | string | Uint8Array ) : string => {
228
+ export function normalizeValue ( value ?: CID | PublicKey | MultihashDigest < 0x00 | 0x12 > | string ) : string {
215
229
if ( value != null ) {
216
- // if we have a PeerId, turn it into an ipns path
217
- if ( hasToCID ( value ) ) {
218
- return `/ipns/${ value . toCID ( ) . toString ( base36 ) } `
219
- }
220
-
221
- // if the value is bytes, stringify it and see if we have a path
222
- if ( value instanceof Uint8Array ) {
223
- const string = uint8ArrayToString ( value )
224
-
225
- if ( string . startsWith ( '/' ) ) {
226
- value = string
227
- }
228
- }
229
-
230
- // if we have a path, check it is a valid path
231
- const string = value . toString ( ) . trim ( )
232
- if ( string . startsWith ( '/' ) && string . length > 1 ) {
233
- return string
234
- }
230
+ const cid = asCID ( value )
235
231
236
232
// if we have a CID, turn it into an ipfs path
237
- const cid = CID . asCID ( value )
238
233
if ( cid != null ) {
239
234
// PeerID encoded as a CID
240
235
if ( cid . code === LIBP2P_CID_CODEC ) {
@@ -244,22 +239,22 @@ export const normalizeValue = (value?: CID | PublicKey | string | Uint8Array): s
244
239
return `/ipfs/${ cid . toV1 ( ) . toString ( ) } `
245
240
}
246
241
247
- // try parsing what we have as CID bytes or a CID string
248
- try {
249
- if ( value instanceof Uint8Array ) {
250
- return `/ipfs/${ CID . decode ( value ) . toV1 ( ) . toString ( ) } `
251
- }
242
+ if ( hasBytes ( value ) ) {
243
+ return `/ipns/${ base36 . encode ( value . bytes ) } `
244
+ }
245
+
246
+ // if we have a path, check it is a valid path
247
+ const string = value . toString ( ) . trim ( )
252
248
253
- return `/ipfs/${ CID . parse ( string ) . toV1 ( ) . toString ( ) } `
254
- } catch {
255
- // fall through
249
+ if ( string . startsWith ( '/' ) && string . length > 1 ) {
250
+ return string
256
251
}
257
252
}
258
253
259
254
throw new InvalidValueError ( 'Value must be a valid content path starting with /' )
260
255
}
261
256
262
- const validateCborDataMatchesPbData = ( entry : IpnsEntry ) : void => {
257
+ function validateCborDataMatchesPbData ( entry : IpnsEntry ) : void {
263
258
if ( entry . data == null ) {
264
259
throw new InvalidRecordDataError ( 'Record data is missing' )
265
260
}
@@ -287,6 +282,29 @@ const validateCborDataMatchesPbData = (entry: IpnsEntry): void => {
287
282
}
288
283
}
289
284
285
+ function hasBytes ( obj ?: any ) : obj is { bytes : Uint8Array } {
286
+ return obj . bytes instanceof Uint8Array
287
+ }
288
+
290
289
function hasToCID ( obj ?: any ) : obj is { toCID ( ) : CID } {
291
290
return typeof obj ?. toCID === 'function'
292
291
}
292
+
293
+ function asCID ( obj ?: any ) : CID | null {
294
+ if ( hasToCID ( obj ) ) {
295
+ return obj . toCID ( )
296
+ }
297
+
298
+ // try parsing as a CID string
299
+ try {
300
+ return CID . parse ( obj )
301
+ } catch {
302
+ // fall through
303
+ }
304
+
305
+ return CID . asCID ( obj )
306
+ }
307
+
308
+ export function isCodec < T extends number > ( digest : MultihashDigest , codec : T ) : digest is MultihashDigest < T > {
309
+ return digest . code === codec
310
+ }
0 commit comments