Skip to content

Commit 67bdddf

Browse files
committed
chore: simplify
1 parent 927cc47 commit 67bdddf

7 files changed

+132
-98
lines changed

src/index.ts

+10-11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { IpnsEntry } from './pb/ipns.js'
88
import { createCborData, ipnsRecordDataForV1Sig, ipnsRecordDataForV2Sig, normalizeValue } from './utils.js'
99
import type { PrivateKey, PublicKey } from '@libp2p/interface'
1010
import type { CID } from 'multiformats/cid'
11+
import type { MultihashDigest } from 'multiformats/hashes/interface'
1112

1213
const log = logger('ipns')
1314
const DEFAULT_TTL_NS = 60 * 60 * 1e+9 // 1 Hour or 3600 Seconds
@@ -157,10 +158,10 @@ const defaultCreateOptions: CreateOptions = {
157158
* @param {number} lifetime - lifetime of the record (in milliseconds).
158159
* @param {CreateOptions} options - additional create options.
159160
*/
160-
export async function createIPNSRecord (privateKey: PrivateKey, value: CID | PublicKey | string, seq: number | bigint, lifetime: number, options?: CreateV2OrV1Options): Promise<IPNSRecordV1V2>
161-
export async function createIPNSRecord (privateKey: PrivateKey, value: CID | PublicKey | string, seq: number | bigint, lifetime: number, options: CreateV2Options): Promise<IPNSRecordV2>
162-
export async function createIPNSRecord (privateKey: PrivateKey, value: CID | PublicKey | string, seq: number | bigint, lifetime: number, options: CreateOptions): Promise<IPNSRecordV1V2>
163-
export async function createIPNSRecord (privateKey: PrivateKey, value: CID | PublicKey | string, seq: number | bigint, lifetime: number, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> {
161+
export async function createIPNSRecord (privateKey: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, seq: number | bigint, lifetime: number, options?: CreateV2OrV1Options): Promise<IPNSRecordV1V2>
162+
export async function createIPNSRecord (privateKey: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, seq: number | bigint, lifetime: number, options: CreateV2Options): Promise<IPNSRecordV2>
163+
export async function createIPNSRecord (privateKey: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, seq: number | bigint, lifetime: number, options: CreateOptions): Promise<IPNSRecordV1V2>
164+
export async function createIPNSRecord (privateKey: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, seq: number | bigint, lifetime: number, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> {
164165
// Validity in ISOString with nanoseconds precision and validity type EOL
165166
const expirationDate = new NanoDate(Date.now() + Number(lifetime))
166167
const validityType = IpnsEntry.ValidityType.EOL
@@ -185,18 +186,18 @@ export async function createIPNSRecord (privateKey: PrivateKey, value: CID | Pub
185186
* @param {string} expiration - expiration datetime for record in the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision.
186187
* @param {CreateOptions} options - additional creation options.
187188
*/
188-
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: CID | PublicKey | string, seq: number | bigint, expiration: string, options?: CreateV2OrV1Options): Promise<IPNSRecordV1V2>
189-
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: CID | PublicKey | string, seq: number | bigint, expiration: string, options: CreateV2Options): Promise<IPNSRecordV2>
190-
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: CID | PublicKey | string, seq: number | bigint, expiration: string, options: CreateOptions): Promise<IPNSRecordV1V2>
191-
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: CID | PublicKey | string, seq: number | bigint, expiration: string, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> {
189+
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, seq: number | bigint, expiration: string, options?: CreateV2OrV1Options): Promise<IPNSRecordV1V2>
190+
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, seq: number | bigint, expiration: string, options: CreateV2Options): Promise<IPNSRecordV2>
191+
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, seq: number | bigint, expiration: string, options: CreateOptions): Promise<IPNSRecordV1V2>
192+
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, seq: number | bigint, expiration: string, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> {
192193
const expirationDate = NanoDate.fromString(expiration)
193194
const validityType = IpnsEntry.ValidityType.EOL
194195
const ttlNs = BigInt(options.ttlNs ?? DEFAULT_TTL_NS)
195196

196197
return _create(privateKey, value, seq, validityType, expirationDate.toString(), ttlNs, options)
197198
}
198199

199-
const _create = async (privateKey: PrivateKey, value: CID | PublicKey | string, seq: number | bigint, validityType: IpnsEntry.ValidityType, validity: string, ttl: bigint, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> => {
200+
const _create = async (privateKey: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, seq: number | bigint, validityType: IpnsEntry.ValidityType, validity: string, ttl: bigint, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> => {
200201
seq = BigInt(seq)
201202
const isoValidity = uint8ArrayFromString(validity)
202203
const normalizedValue = normalizeValue(value)
@@ -254,8 +255,6 @@ export { unmarshalIPNSRecord } from './utils.js'
254255
export { marshalIPNSRecord } from './utils.js'
255256
export { multihashToIPNSRoutingKey } from './utils.js'
256257
export { multihashFromIPNSRoutingKey } from './utils.js'
257-
export { publicKeyToIPNSRoutingKey } from './utils.js'
258-
export { publicKeyFromIPNSRoutingKey } from './utils.js'
259258
export { extractPublicKeyFromIPNSRecord } from './utils.js'
260259

261260
/**

src/utils.ts

+73-55
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { publicKeyFromMultihash, publicKeyFromProtobuf } from '@libp2p/crypto/keys'
1+
import { publicKeyFromProtobuf } from '@libp2p/crypto/keys'
22
import { InvalidMultihashError } from '@libp2p/interface'
33
import { logger } from '@libp2p/logger'
44
import * as cborg from 'cborg'
@@ -12,17 +12,19 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
1212
import { InvalidRecordDataError, InvalidValueError, SignatureVerificationError, UnsupportedValidityError } from './errors.js'
1313
import { IpnsEntry } from './pb/ipns.js'
1414
import type { IPNSRecord, IPNSRecordV2, IPNSRecordData } from './index.js'
15-
import type { PublicKey, Ed25519PublicKey, Secp256k1PublicKey } from '@libp2p/interface'
15+
import type { PublicKey } from '@libp2p/interface'
1616

1717
const log = logger('ipns:utils')
1818
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
2022

2123
/**
2224
* Extracts a public key from the passed PeerId, falling back to the pubKey
2325
* embedded in the ipns record
2426
*/
25-
export const extractPublicKeyFromIPNSRecord = (record: IPNSRecord | IPNSRecordV2): PublicKey | undefined => {
27+
export function extractPublicKeyFromIPNSRecord (record: IPNSRecord | IPNSRecordV2): PublicKey | undefined {
2628
let pubKey: PublicKey | undefined
2729

2830
if (record.pubKey != null) {
@@ -42,7 +44,7 @@ export const extractPublicKeyFromIPNSRecord = (record: IPNSRecord | IPNSRecordV2
4244
/**
4345
* Utility for creating the record data for being signed
4446
*/
45-
export const ipnsRecordDataForV1Sig = (value: Uint8Array, validityType: IpnsEntry.ValidityType, validity: Uint8Array): Uint8Array => {
47+
export function ipnsRecordDataForV1Sig (value: Uint8Array, validityType: IpnsEntry.ValidityType, validity: Uint8Array): Uint8Array {
4648
const validityTypeBuffer = uint8ArrayFromString(validityType)
4749

4850
return uint8ArrayConcat([value, validity, validityTypeBuffer])
@@ -51,13 +53,13 @@ export const ipnsRecordDataForV1Sig = (value: Uint8Array, validityType: IpnsEntr
5153
/**
5254
* Utility for creating the record data for being signed
5355
*/
54-
export const ipnsRecordDataForV2Sig = (data: Uint8Array): Uint8Array => {
56+
export function ipnsRecordDataForV2Sig (data: Uint8Array): Uint8Array {
5557
const entryData = uint8ArrayFromString('ipns-signature:')
5658

5759
return uint8ArrayConcat([entryData, data])
5860
}
5961

60-
export const marshalIPNSRecord = (obj: IPNSRecord | IPNSRecordV2): Uint8Array => {
62+
export function marshalIPNSRecord (obj: IPNSRecord | IPNSRecordV2): Uint8Array {
6163
if ('signatureV1' in obj) {
6264
return IpnsEntry.encode({
6365
value: uint8ArrayFromString(obj.value),
@@ -100,7 +102,7 @@ export function unmarshalIPNSRecord (buf: Uint8Array): IPNSRecord {
100102
}
101103

102104
const data = parseCborData(message.data)
103-
const value = normalizeValue(data.Value)
105+
const value = normalizeByteValue(data.Value)
104106
const validity = uint8ArrayToString(data.Validity)
105107

106108
if (message.value != null && message.signatureV1 != null) {
@@ -135,36 +137,24 @@ export function unmarshalIPNSRecord (buf: Uint8Array): IPNSRecord {
135137
}
136138
}
137139

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 {
143141
return uint8ArrayConcat([
144142
IPNS_PREFIX,
145143
digest.bytes
146144
])
147145
}
148146

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> {
157148
const digest = Digest.decode(key.slice(IPNS_PREFIX.length))
158149

159-
if (digest.code !== 0x00 && digest.code !== 0x12) {
150+
if (!isCodec(digest, IDENTITY_CODEC) && !isCodec(digest, SHA2_256_CODEC)) {
160151
throw new InvalidMultihashError('Multihash in IPNS key was not identity or sha2-256')
161152
}
162153

163-
// @ts-expect-error digest may not have correct code even though we just checked
164154
return digest
165155
}
166156

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 {
168158
let ValidityType
169159

170160
if (validityType === IpnsEntry.ValidityType.EOL) {
@@ -184,7 +174,7 @@ export const createCborData = (value: Uint8Array, validityType: IpnsEntry.Validi
184174
return cborg.encode(data)
185175
}
186176

187-
export const parseCborData = (buf: Uint8Array): IPNSRecordData => {
177+
export function parseCborData (buf: Uint8Array): IPNSRecordData {
188178
const data = cborg.decode(buf)
189179

190180
if (data.ValidityType === 0) {
@@ -206,35 +196,40 @@ export const parseCborData = (buf: Uint8Array): IPNSRecordData => {
206196
return data
207197
}
208198

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+
209223
/**
210224
* Normalizes the given record value. It ensures it is a PeerID, a CID or a
211225
* string starting with '/'. PeerIDs become `/ipns/${cidV1Libp2pKey}`,
212226
* CIDs become `/ipfs/${cidAsV1}`.
213227
*/
214-
export const normalizeValue = (value?: CID | PublicKey | string | Uint8Array): string => {
228+
export function normalizeValue (value?: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string): string {
215229
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)
235231

236232
// if we have a CID, turn it into an ipfs path
237-
const cid = CID.asCID(value)
238233
if (cid != null) {
239234
// PeerID encoded as a CID
240235
if (cid.code === LIBP2P_CID_CODEC) {
@@ -244,22 +239,22 @@ export const normalizeValue = (value?: CID | PublicKey | string | Uint8Array): s
244239
return `/ipfs/${cid.toV1().toString()}`
245240
}
246241

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()
252248

253-
return `/ipfs/${CID.parse(string).toV1().toString()}`
254-
} catch {
255-
// fall through
249+
if (string.startsWith('/') && string.length > 1) {
250+
return string
256251
}
257252
}
258253

259254
throw new InvalidValueError('Value must be a valid content path starting with /')
260255
}
261256

262-
const validateCborDataMatchesPbData = (entry: IpnsEntry): void => {
257+
function validateCborDataMatchesPbData (entry: IpnsEntry): void {
263258
if (entry.data == null) {
264259
throw new InvalidRecordDataError('Record data is missing')
265260
}
@@ -287,6 +282,29 @@ const validateCborDataMatchesPbData = (entry: IpnsEntry): void => {
287282
}
288283
}
289284

285+
function hasBytes (obj?: any): obj is { bytes: Uint8Array } {
286+
return obj.bytes instanceof Uint8Array
287+
}
288+
290289
function hasToCID (obj?: any): obj is { toCID(): CID } {
291290
return typeof obj?.toCID === 'function'
292291
}
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+
}

src/validator.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import { publicKeyFromMultihash } from '@libp2p/crypto/keys'
12
import { logger } from '@libp2p/logger'
23
import NanoDate from 'timestamp-nano'
34
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
45
import { InvalidEmbeddedPublicKeyError, RecordExpiredError, RecordTooLargeError, SignatureVerificationError, UnsupportedValidityError } from './errors.js'
56
import { IpnsEntry } from './pb/ipns.js'
6-
import { extractPublicKeyFromIPNSRecord, ipnsRecordDataForV2Sig, publicKeyFromIPNSRoutingKey, publicKeyToIPNSRoutingKey, unmarshalIPNSRecord } from './utils.js'
7+
import { extractPublicKeyFromIPNSRecord, ipnsRecordDataForV2Sig, isCodec, multihashFromIPNSRoutingKey, multihashToIPNSRoutingKey, unmarshalIPNSRecord } from './utils.js'
78
import type { PublicKey } from '@libp2p/interface'
89

910
const log = logger('ipns:validator')
@@ -58,7 +59,13 @@ export async function ipnsValidator (key: Uint8Array, marshalledData: Uint8Array
5859
}
5960

6061
// try to extract public key from routing key
61-
const routingPubKey = publicKeyFromIPNSRoutingKey(key)
62+
const routingMultihash = multihashFromIPNSRoutingKey(key)
63+
let routingPubKey: PublicKey | undefined
64+
65+
// identity hash
66+
if (isCodec(routingMultihash, 0x0)) {
67+
routingPubKey = publicKeyFromMultihash(routingMultihash)
68+
}
6269

6370
// extract public key from record
6471
const receivedRecord = unmarshalIPNSRecord(marshalledData)
@@ -68,7 +75,7 @@ export async function ipnsValidator (key: Uint8Array, marshalledData: Uint8Array
6875
throw new InvalidEmbeddedPublicKeyError('Could not extract public key from IPNS record or routing key')
6976
}
7077

71-
const routingKey = publicKeyToIPNSRoutingKey(recordPubKey)
78+
const routingKey = multihashToIPNSRoutingKey(recordPubKey.toMultihash())
7279

7380
if (!uint8ArrayEquals(key, routingKey)) {
7481
throw new InvalidEmbeddedPublicKeyError('Embedded public key did not match routing key')

0 commit comments

Comments
 (0)