Skip to content

Commit ca23be1

Browse files
test: optimize nip05.spec.ts & nip03.spec.ts resource management (#640)
Co-authored-by: Ricardo Cabral <me@ricardocabral.io>
1 parent 837540b commit ca23be1

3 files changed

Lines changed: 110 additions & 105 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"nostream": patch
3+
---
4+
5+
test: optimize nip05.spec.ts & nip03.spec.ts resource management
6+
7+
- Lift sinon stub to `before`/`after` in verifyNip05Identifier tests (create once, reset between tests)
8+
- Extract SSRF guard callback once in `before` instead of per-test `beforeEach`
9+
- Pre-build shared OTS buffers and attestations at module scope to eliminate redundant Buffer.concat calls
10+
- Add shared event factory for extractNip05FromEvent tests

test/unit/utils/nip03.spec.ts

Lines changed: 56 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,42 @@ function buildOts(digest: Buffer, subItems: Buffer[]): Buffer {
150150
const EVENT_ID = 'e71c6ea722987debdb60f81f9ea4f604b5ac0664120dd64fb9d23abc4ec7c323'
151151
const DIGEST = Buffer.from(EVENT_ID, 'hex')
152152

153+
const BITCOIN_ATTESTATION_810391 = bitcoinAttestation(810391)
154+
const BITCOIN_ATTESTATION_1 = bitcoinAttestation(1)
155+
const BITCOIN_ATTESTATION_42 = bitcoinAttestation(42)
156+
const PENDING_ATTESTATION_DEFAULT = pendingAttestation('https://a.pool.opentimestamps.org')
157+
158+
const MINIMAL_BITCOIN_OTS = buildOts(DIGEST, [BITCOIN_ATTESTATION_810391])
159+
160+
const OTS_WITH_APPEND_OP = (() => {
161+
const opAppend = Buffer.concat([Buffer.from([OP_APPEND]), writeVarBytes(Buffer.from([0xde, 0xad, 0xbe, 0xef]))])
162+
const tree = Buffer.concat([opAppend, BITCOIN_ATTESTATION_1])
163+
return buildOts(DIGEST, [tree])
164+
})()
165+
166+
const OTS_PENDING_AND_BITCOIN = buildOts(DIGEST, [PENDING_ATTESTATION_DEFAULT, BITCOIN_ATTESTATION_42])
167+
168+
const OTS_LITECOIN_AND_UNKNOWN = buildOts(DIGEST, [litecoinAttestation(2500000), unknownAttestation(Buffer.from([1, 2, 3]))])
169+
170+
const OTS_ETHEREUM = buildOts(DIGEST, [ethereumAttestation(18_000_000)])
171+
172+
const OTS_PREPEND = (() => {
173+
const prepend = Buffer.concat([Buffer.from([OP_PREPEND]), writeVarBytes(Buffer.from([0x01]))])
174+
return buildOts(DIGEST, [Buffer.concat([prepend, bitcoinAttestation(4)])])
175+
})()
176+
177+
const OTS_REVERSE_HEXLIFY = (() => {
178+
const revThenHex = Buffer.concat([Buffer.from([OP_REVERSE]), Buffer.from([OP_HEXLIFY]), bitcoinAttestation(5)])
179+
return buildOts(DIGEST, [revThenHex])
180+
})()
181+
182+
const OTS_TRUNCATED_ATTESTATION = (() => {
183+
const broken = Buffer.concat([Buffer.from([TAG_ATTESTATION]), BITCOIN_TAG, writeVarUint(0)])
184+
return buildOts(DIGEST, [broken])
185+
})()
186+
187+
const VALID_PROOF_BASE64 = MINIMAL_BITCOIN_OTS.toString('base64')
188+
153189
describe('OtsReader', () => {
154190
it('readBytes rejects a negative length', () => {
155191
const r = new OtsReader(Buffer.from([1, 2, 3]))
@@ -166,9 +202,7 @@ describe('OtsReader', () => {
166202
describe('NIP-03 — OpenTimestamps', () => {
167203
describe('parseOtsFile', () => {
168204
it('parses a minimal proof with a single bitcoin attestation', () => {
169-
const buf = buildOts(DIGEST, [bitcoinAttestation(810391)])
170-
171-
const result = expectSuccess(parseOtsFile(buf))
205+
const result = expectSuccess(parseOtsFile(MINIMAL_BITCOIN_OTS))
172206

173207
expect(result.summary.version).to.equal(1)
174208
expect(result.summary.fileHashOp).to.equal('sha256')
@@ -178,27 +212,19 @@ describe('NIP-03 — OpenTimestamps', () => {
178212
})
179213

180214
it('parses a proof with ops that wrap an attestation', () => {
181-
const opAppend = Buffer.concat([Buffer.from([OP_APPEND]), writeVarBytes(Buffer.from([0xde, 0xad, 0xbe, 0xef]))])
182-
const tree = Buffer.concat([opAppend, bitcoinAttestation(1)])
183-
const buf = buildOts(DIGEST, [tree])
184-
185-
const result = expectSuccess(parseOtsFile(buf))
215+
const result = expectSuccess(parseOtsFile(OTS_WITH_APPEND_OP))
186216

187217
expect(result.summary.attestations.map((a) => a.kind)).to.deep.equal(['bitcoin'])
188218
})
189219

190220
it('parses a proof with multiple attestations (pending + bitcoin)', () => {
191-
const buf = buildOts(DIGEST, [pendingAttestation('https://a.pool.opentimestamps.org'), bitcoinAttestation(42)])
192-
193-
const result = expectSuccess(parseOtsFile(buf))
221+
const result = expectSuccess(parseOtsFile(OTS_PENDING_AND_BITCOIN))
194222
const kinds = result.summary.attestations.map((a) => a.kind).sort()
195223
expect(kinds).to.deep.equal(['bitcoin', 'pending'])
196224
})
197225

198226
it('classifies litecoin and unknown attestations correctly', () => {
199-
const buf = buildOts(DIGEST, [litecoinAttestation(2500000), unknownAttestation(Buffer.from([1, 2, 3]))])
200-
201-
const result = expectSuccess(parseOtsFile(buf))
227+
const result = expectSuccess(parseOtsFile(OTS_LITECOIN_AND_UNKNOWN))
202228
const kinds = result.summary.attestations.map((a) => a.kind).sort()
203229
expect(kinds).to.deep.equal(['litecoin', 'unknown'])
204230
})
@@ -210,28 +236,26 @@ describe('NIP-03 — OpenTimestamps', () => {
210236
})
211237

212238
it('rejects an unsupported file hash op', () => {
213-
const parts = [MAGIC, writeVarUint(1), Buffer.from([0x55]), Buffer.alloc(32), bitcoinAttestation(1)]
239+
const parts = [MAGIC, writeVarUint(1), Buffer.from([0x55]), Buffer.alloc(32), BITCOIN_ATTESTATION_1]
214240
const buf = Buffer.concat(parts)
215241
const result = expectFailure(parseOtsFile(buf))
216242
expect(result.reason).to.match(/unsupported file hash op/)
217243
})
218244

219245
it('rejects an unsupported ots file version', () => {
220-
const parts = [MAGIC, writeVarUint(2), Buffer.from([OP_SHA256]), DIGEST, bitcoinAttestation(1)]
246+
const parts = [MAGIC, writeVarUint(2), Buffer.from([OP_SHA256]), DIGEST, BITCOIN_ATTESTATION_1]
221247
const buf = Buffer.concat(parts)
222248
const result = expectFailure(parseOtsFile(buf))
223249
expect(result.reason).to.match(/unsupported ots version/)
224250
})
225251

226252
it('rejects truncated proofs without crashing', () => {
227-
const good = buildOts(DIGEST, [bitcoinAttestation(1)])
228-
const truncated = good.subarray(0, good.length - 3)
253+
const truncated = MINIMAL_BITCOIN_OTS.subarray(0, MINIMAL_BITCOIN_OTS.length - 3)
229254
expectFailure(parseOtsFile(truncated))
230255
})
231256

232257
it('rejects proofs with trailing garbage', () => {
233-
const good = buildOts(DIGEST, [bitcoinAttestation(1)])
234-
const withGarbage = Buffer.concat([good, Buffer.from([0x00, 0x11, 0x22])])
258+
const withGarbage = Buffer.concat([MINIMAL_BITCOIN_OTS, Buffer.from([0x00, 0x11, 0x22])])
235259
const result = expectFailure(parseOtsFile(withGarbage))
236260
expect(result.reason).to.match(/trailing bytes/)
237261
})
@@ -252,7 +276,7 @@ describe('NIP-03 — OpenTimestamps', () => {
252276

253277
it('parses sha1 file digest (20-byte) proofs', () => {
254278
const digest = Buffer.alloc(20, 0xab)
255-
const buf = buildOtsWithFileHashOp(OP_SHA1, digest, [bitcoinAttestation(1)])
279+
const buf = buildOtsWithFileHashOp(OP_SHA1, digest, [BITCOIN_ATTESTATION_1])
256280
const result = expectSuccess(parseOtsFile(buf))
257281
expect(result.summary.fileHashOp).to.equal('sha1')
258282
expect(result.summary.digest).to.equal(digest.toString('hex'))
@@ -273,31 +297,24 @@ describe('NIP-03 — OpenTimestamps', () => {
273297
})
274298

275299
it('parses prepend binary op in the commitment tree', () => {
276-
const prepend = Buffer.concat([Buffer.from([OP_PREPEND]), writeVarBytes(Buffer.from([0x01]))])
277-
const buf = buildOts(DIGEST, [Buffer.concat([prepend, bitcoinAttestation(4)])])
278-
const result = expectSuccess(parseOtsFile(buf))
300+
const result = expectSuccess(parseOtsFile(OTS_PREPEND))
279301
expect(result.summary.attestations.some((a) => a.kind === 'bitcoin')).to.equal(true)
280302
})
281303

282304
it('parses reverse and hexlify unary ops wrapping an attestation', () => {
283-
const revThenHex = Buffer.concat([Buffer.from([OP_REVERSE]), Buffer.from([OP_HEXLIFY]), bitcoinAttestation(5)])
284-
const buf = buildOts(DIGEST, [revThenHex])
285-
const result = expectSuccess(parseOtsFile(buf))
305+
const result = expectSuccess(parseOtsFile(OTS_REVERSE_HEXLIFY))
286306
expect(result.summary.attestations[0].kind).to.equal('bitcoin')
287307
})
288308

289309
it('classifies ethereum block header attestations', () => {
290-
const buf = buildOts(DIGEST, [ethereumAttestation(18_000_000)])
291-
const result = expectSuccess(parseOtsFile(buf))
310+
const result = expectSuccess(parseOtsFile(OTS_ETHEREUM))
292311
const eth = result.summary.attestations.find((a) => a.kind === 'ethereum')
293312
expect(eth).to.exist
294313
expect(eth?.height).to.equal(18_000_000)
295314
})
296315

297316
it('treats a truncated bitcoin attestation payload as height-less', () => {
298-
const broken = Buffer.concat([Buffer.from([TAG_ATTESTATION]), BITCOIN_TAG, writeVarUint(0)])
299-
const buf = buildOts(DIGEST, [broken])
300-
const result = expectSuccess(parseOtsFile(buf))
317+
const result = expectSuccess(parseOtsFile(OTS_TRUNCATED_ATTESTATION))
301318
expect(result.summary.attestations[0]).to.include({ kind: 'bitcoin' })
302319
expect(result.summary.attestations[0].height).to.equal(undefined)
303320
})
@@ -343,14 +360,12 @@ describe('NIP-03 — OpenTimestamps', () => {
343360
})
344361

345362
describe('validateOtsProof', () => {
346-
const validProof = buildOts(DIGEST, [bitcoinAttestation(810391)]).toString('base64')
347-
348363
it('accepts a well-formed bitcoin-anchored proof whose digest matches', () => {
349-
expect(validateOtsProof(validProof, EVENT_ID)).to.equal(undefined)
364+
expect(validateOtsProof(VALID_PROOF_BASE64, EVENT_ID)).to.equal(undefined)
350365
})
351366

352367
it('accepts uppercase hex target ids and normalizes them', () => {
353-
expect(validateOtsProof(validProof, EVENT_ID.toUpperCase())).to.equal(undefined)
368+
expect(validateOtsProof(VALID_PROOF_BASE64, EVENT_ID.toUpperCase())).to.equal(undefined)
354369
})
355370

356371
it('rejects empty content', () => {
@@ -363,26 +378,26 @@ describe('NIP-03 — OpenTimestamps', () => {
363378

364379
it('rejects proofs whose digest does not match the event id', () => {
365380
const other = '0'.repeat(64)
366-
expect(validateOtsProof(validProof, other)).to.match(/digest does not match/)
381+
expect(validateOtsProof(VALID_PROOF_BASE64, other)).to.match(/digest does not match/)
367382
})
368383

369384
it('rejects target ids that are not 32-byte hex', () => {
370-
expect(validateOtsProof(validProof, 'not-an-id')).to.match(/not a 32-byte hex/)
385+
expect(validateOtsProof(VALID_PROOF_BASE64, 'not-an-id')).to.match(/not a 32-byte hex/)
371386
})
372387

373388
it('rejects a non-string target event id', () => {
374-
expect(validateOtsProof(validProof, null as unknown as string)).to.match(/not a 32-byte hex/)
389+
expect(validateOtsProof(VALID_PROOF_BASE64, null as unknown as string)).to.match(/not a 32-byte hex/)
375390
})
376391

377392
it('rejects proofs without any bitcoin attestation', () => {
378-
const onlyPending = buildOts(DIGEST, [pendingAttestation('https://a.pool.opentimestamps.org')]).toString('base64')
393+
const onlyPending = buildOts(DIGEST, [PENDING_ATTESTATION_DEFAULT]).toString('base64')
379394
expect(validateOtsProof(onlyPending, EVENT_ID)).to.match(/bitcoin attestation/)
380395
})
381396

382397
it('rejects a proof digested with a non-sha256 hash op', () => {
383398
// Construct a manual RIPEMD160 file (20-byte digest) — this should fail
384399
// the sha256 requirement even though the parser would otherwise accept it.
385-
const parts = [MAGIC, writeVarUint(1), Buffer.from([0x03]), Buffer.alloc(20, 0xaa), bitcoinAttestation(1)]
400+
const parts = [MAGIC, writeVarUint(1), Buffer.from([0x03]), Buffer.alloc(20, 0xaa), BITCOIN_ATTESTATION_1]
386401
const ripemd = Buffer.concat(parts).toString('base64')
387402
expect(validateOtsProof(ripemd, 'aa'.repeat(32))).to.match(/sha256 file hash op/)
388403
})

test/unit/utils/nip05.spec.ts

Lines changed: 44 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,23 @@ import { EventKinds } from '../../../src/constants/base'
1818

1919
const { expect } = chai
2020

21+
const PUBKEY_A = 'a'.repeat(64)
22+
const PUBKEY_B = 'b'.repeat(64)
23+
const SIG_C = 'c'.repeat(128)
24+
const ID_A = PUBKEY_A
25+
26+
function makeEvent(kind: number, content: string): Event {
27+
return {
28+
id: ID_A,
29+
pubkey: PUBKEY_B,
30+
created_at: 1234567890,
31+
kind,
32+
tags: [],
33+
content,
34+
sig: SIG_C,
35+
}
36+
}
37+
2138
describe('NIP-05 utils', () => {
2239
describe('parseNip05Identifier', () => {
2340
it('returns parsed identifier for valid input', () => {
@@ -75,81 +92,39 @@ describe('NIP-05 utils', () => {
7592

7693
describe('extractNip05FromEvent', () => {
7794
it('extracts nip05 from kind 0 event', () => {
78-
const event: Event = {
79-
id: 'a'.repeat(64),
80-
pubkey: 'b'.repeat(64),
81-
created_at: 1234567890,
82-
kind: EventKinds.SET_METADATA,
83-
tags: [],
84-
content: JSON.stringify({ name: 'alice', nip05: 'alice@example.com' }),
85-
sig: 'c'.repeat(128),
86-
}
87-
expect(extractNip05FromEvent(event)).to.equal('alice@example.com')
95+
expect(extractNip05FromEvent(
96+
makeEvent(EventKinds.SET_METADATA, JSON.stringify({ name: 'alice', nip05: 'alice@example.com' })),
97+
)).to.equal('alice@example.com')
8898
})
8999

90100
it('returns undefined for non-kind-0 event', () => {
91-
const event: Event = {
92-
id: 'a'.repeat(64),
93-
pubkey: 'b'.repeat(64),
94-
created_at: 1234567890,
95-
kind: EventKinds.TEXT_NOTE,
96-
tags: [],
97-
content: JSON.stringify({ nip05: 'alice@example.com' }),
98-
sig: 'c'.repeat(128),
99-
}
100-
expect(extractNip05FromEvent(event)).to.be.undefined
101+
expect(extractNip05FromEvent(
102+
makeEvent(EventKinds.TEXT_NOTE, JSON.stringify({ nip05: 'alice@example.com' })),
103+
)).to.be.undefined
101104
})
102105

103106
it('returns undefined when nip05 is not in content', () => {
104-
const event: Event = {
105-
id: 'a'.repeat(64),
106-
pubkey: 'b'.repeat(64),
107-
created_at: 1234567890,
108-
kind: EventKinds.SET_METADATA,
109-
tags: [],
110-
content: JSON.stringify({ name: 'alice' }),
111-
sig: 'c'.repeat(128),
112-
}
113-
expect(extractNip05FromEvent(event)).to.be.undefined
107+
expect(extractNip05FromEvent(
108+
makeEvent(EventKinds.SET_METADATA, JSON.stringify({ name: 'alice' })),
109+
)).to.be.undefined
114110
})
115111

116112
it('returns undefined for invalid JSON content', () => {
117-
const event: Event = {
118-
id: 'a'.repeat(64),
119-
pubkey: 'b'.repeat(64),
120-
created_at: 1234567890,
121-
kind: EventKinds.SET_METADATA,
122-
tags: [],
123-
content: 'not json',
124-
sig: 'c'.repeat(128),
125-
}
126-
expect(extractNip05FromEvent(event)).to.be.undefined
113+
expect(extractNip05FromEvent(
114+
makeEvent(EventKinds.SET_METADATA, 'not json'),
115+
)).to.be.undefined
127116
})
128117

129118
it('returns undefined when nip05 is empty string', () => {
130-
const event: Event = {
131-
id: 'a'.repeat(64),
132-
pubkey: 'b'.repeat(64),
133-
created_at: 1234567890,
134-
kind: EventKinds.SET_METADATA,
135-
tags: [],
136-
content: JSON.stringify({ nip05: '' }),
137-
sig: 'c'.repeat(128),
138-
}
139-
expect(extractNip05FromEvent(event)).to.be.undefined
119+
expect(extractNip05FromEvent(
120+
makeEvent(EventKinds.SET_METADATA, JSON.stringify({ nip05: '' })),
121+
)).to.be.undefined
140122
})
141123

142124
it('returns undefined when nip05 is not a string', () => {
143-
const event: Event = {
144-
id: 'a'.repeat(64),
145-
pubkey: 'b'.repeat(64),
146-
created_at: 1234567890,
147-
kind: EventKinds.SET_METADATA,
148-
tags: [],
149-
content: JSON.stringify({ nip05: 42 }),
150-
sig: 'c'.repeat(128),
151-
}
152-
expect(extractNip05FromEvent(event)).to.be.undefined
125+
expect(extractNip05FromEvent(
126+
makeEvent(EventKinds.SET_METADATA, JSON.stringify({ nip05: 42 })),
127+
)).to.be.undefined
153128
})
154129
})
155130

@@ -190,13 +165,17 @@ describe('NIP-05 utils', () => {
190165

191166
describe('verifyNip05Identifier', () => {
192167
let axiosGetStub: Sinon.SinonStub
193-
const pubkey = 'a'.repeat(64)
168+
const pubkey = PUBKEY_A
194169

195-
beforeEach(() => {
170+
before(() => {
196171
axiosGetStub = Sinon.stub(axios, 'get')
197172
})
198173

199174
afterEach(() => {
175+
axiosGetStub.reset()
176+
})
177+
178+
after(() => {
200179
axiosGetStub.restore()
201180
})
202181

@@ -241,7 +220,7 @@ describe('NIP-05 utils', () => {
241220
})
242221

243222
it('returns mismatch when pubkey does not match', async () => {
244-
axiosGetStub.resolves({ data: { names: { alice: 'b'.repeat(64) } } })
223+
axiosGetStub.resolves({ data: { names: { alice: PUBKEY_B } } })
245224

246225
const outcome = await verifyNip05Identifier('alice@example.com', pubkey)
247226

@@ -286,7 +265,8 @@ describe('NIP-05 utils', () => {
286265
describe('beforeRedirect SSRF guard', () => {
287266
let guard: (options: { href?: string; protocol?: string; hostname?: string }) => void
288267

289-
beforeEach(async () => {
268+
before(async () => {
269+
axiosGetStub.reset()
290270
axiosGetStub.resolves({ data: { names: { alice: pubkey } } })
291271
await verifyNip05Identifier('alice@example.com', pubkey)
292272
guard = axiosGetStub.firstCall.args[1].beforeRedirect

0 commit comments

Comments
 (0)