From a60379495b6c6fb9e89bde3bb3f58449107af408 Mon Sep 17 00:00:00 2001 From: anshumancanrock Date: Thu, 28 May 2026 10:28:30 +0530 Subject: [PATCH] test: optimize nip05.spec.ts & nip03.spec.ts resource management --- .changeset/optimize-nip03-nip05-tests.md | 10 +++ test/unit/utils/nip03.spec.ts | 97 +++++++++++--------- test/unit/utils/nip05.spec.ts | 108 +++++++++-------------- 3 files changed, 110 insertions(+), 105 deletions(-) create mode 100644 .changeset/optimize-nip03-nip05-tests.md diff --git a/.changeset/optimize-nip03-nip05-tests.md b/.changeset/optimize-nip03-nip05-tests.md new file mode 100644 index 00000000..39c583ac --- /dev/null +++ b/.changeset/optimize-nip03-nip05-tests.md @@ -0,0 +1,10 @@ +--- +"nostream": patch +--- + +test: optimize nip05.spec.ts & nip03.spec.ts resource management + +- Lift sinon stub to `before`/`after` in verifyNip05Identifier tests (create once, reset between tests) +- Extract SSRF guard callback once in `before` instead of per-test `beforeEach` +- Pre-build shared OTS buffers and attestations at module scope to eliminate redundant Buffer.concat calls +- Add shared event factory for extractNip05FromEvent tests diff --git a/test/unit/utils/nip03.spec.ts b/test/unit/utils/nip03.spec.ts index 2d0690df..95eec2c9 100644 --- a/test/unit/utils/nip03.spec.ts +++ b/test/unit/utils/nip03.spec.ts @@ -150,6 +150,42 @@ function buildOts(digest: Buffer, subItems: Buffer[]): Buffer { const EVENT_ID = 'e71c6ea722987debdb60f81f9ea4f604b5ac0664120dd64fb9d23abc4ec7c323' const DIGEST = Buffer.from(EVENT_ID, 'hex') +const BITCOIN_ATTESTATION_810391 = bitcoinAttestation(810391) +const BITCOIN_ATTESTATION_1 = bitcoinAttestation(1) +const BITCOIN_ATTESTATION_42 = bitcoinAttestation(42) +const PENDING_ATTESTATION_DEFAULT = pendingAttestation('https://a.pool.opentimestamps.org') + +const MINIMAL_BITCOIN_OTS = buildOts(DIGEST, [BITCOIN_ATTESTATION_810391]) + +const OTS_WITH_APPEND_OP = (() => { + const opAppend = Buffer.concat([Buffer.from([OP_APPEND]), writeVarBytes(Buffer.from([0xde, 0xad, 0xbe, 0xef]))]) + const tree = Buffer.concat([opAppend, BITCOIN_ATTESTATION_1]) + return buildOts(DIGEST, [tree]) +})() + +const OTS_PENDING_AND_BITCOIN = buildOts(DIGEST, [PENDING_ATTESTATION_DEFAULT, BITCOIN_ATTESTATION_42]) + +const OTS_LITECOIN_AND_UNKNOWN = buildOts(DIGEST, [litecoinAttestation(2500000), unknownAttestation(Buffer.from([1, 2, 3]))]) + +const OTS_ETHEREUM = buildOts(DIGEST, [ethereumAttestation(18_000_000)]) + +const OTS_PREPEND = (() => { + const prepend = Buffer.concat([Buffer.from([OP_PREPEND]), writeVarBytes(Buffer.from([0x01]))]) + return buildOts(DIGEST, [Buffer.concat([prepend, bitcoinAttestation(4)])]) +})() + +const OTS_REVERSE_HEXLIFY = (() => { + const revThenHex = Buffer.concat([Buffer.from([OP_REVERSE]), Buffer.from([OP_HEXLIFY]), bitcoinAttestation(5)]) + return buildOts(DIGEST, [revThenHex]) +})() + +const OTS_TRUNCATED_ATTESTATION = (() => { + const broken = Buffer.concat([Buffer.from([TAG_ATTESTATION]), BITCOIN_TAG, writeVarUint(0)]) + return buildOts(DIGEST, [broken]) +})() + +const VALID_PROOF_BASE64 = MINIMAL_BITCOIN_OTS.toString('base64') + describe('OtsReader', () => { it('readBytes rejects a negative length', () => { const r = new OtsReader(Buffer.from([1, 2, 3])) @@ -166,9 +202,7 @@ describe('OtsReader', () => { describe('NIP-03 — OpenTimestamps', () => { describe('parseOtsFile', () => { it('parses a minimal proof with a single bitcoin attestation', () => { - const buf = buildOts(DIGEST, [bitcoinAttestation(810391)]) - - const result = expectSuccess(parseOtsFile(buf)) + const result = expectSuccess(parseOtsFile(MINIMAL_BITCOIN_OTS)) expect(result.summary.version).to.equal(1) expect(result.summary.fileHashOp).to.equal('sha256') @@ -178,27 +212,19 @@ describe('NIP-03 — OpenTimestamps', () => { }) it('parses a proof with ops that wrap an attestation', () => { - const opAppend = Buffer.concat([Buffer.from([OP_APPEND]), writeVarBytes(Buffer.from([0xde, 0xad, 0xbe, 0xef]))]) - const tree = Buffer.concat([opAppend, bitcoinAttestation(1)]) - const buf = buildOts(DIGEST, [tree]) - - const result = expectSuccess(parseOtsFile(buf)) + const result = expectSuccess(parseOtsFile(OTS_WITH_APPEND_OP)) expect(result.summary.attestations.map((a) => a.kind)).to.deep.equal(['bitcoin']) }) it('parses a proof with multiple attestations (pending + bitcoin)', () => { - const buf = buildOts(DIGEST, [pendingAttestation('https://a.pool.opentimestamps.org'), bitcoinAttestation(42)]) - - const result = expectSuccess(parseOtsFile(buf)) + const result = expectSuccess(parseOtsFile(OTS_PENDING_AND_BITCOIN)) const kinds = result.summary.attestations.map((a) => a.kind).sort() expect(kinds).to.deep.equal(['bitcoin', 'pending']) }) it('classifies litecoin and unknown attestations correctly', () => { - const buf = buildOts(DIGEST, [litecoinAttestation(2500000), unknownAttestation(Buffer.from([1, 2, 3]))]) - - const result = expectSuccess(parseOtsFile(buf)) + const result = expectSuccess(parseOtsFile(OTS_LITECOIN_AND_UNKNOWN)) const kinds = result.summary.attestations.map((a) => a.kind).sort() expect(kinds).to.deep.equal(['litecoin', 'unknown']) }) @@ -210,28 +236,26 @@ describe('NIP-03 — OpenTimestamps', () => { }) it('rejects an unsupported file hash op', () => { - const parts = [MAGIC, writeVarUint(1), Buffer.from([0x55]), Buffer.alloc(32), bitcoinAttestation(1)] + const parts = [MAGIC, writeVarUint(1), Buffer.from([0x55]), Buffer.alloc(32), BITCOIN_ATTESTATION_1] const buf = Buffer.concat(parts) const result = expectFailure(parseOtsFile(buf)) expect(result.reason).to.match(/unsupported file hash op/) }) it('rejects an unsupported ots file version', () => { - const parts = [MAGIC, writeVarUint(2), Buffer.from([OP_SHA256]), DIGEST, bitcoinAttestation(1)] + const parts = [MAGIC, writeVarUint(2), Buffer.from([OP_SHA256]), DIGEST, BITCOIN_ATTESTATION_1] const buf = Buffer.concat(parts) const result = expectFailure(parseOtsFile(buf)) expect(result.reason).to.match(/unsupported ots version/) }) it('rejects truncated proofs without crashing', () => { - const good = buildOts(DIGEST, [bitcoinAttestation(1)]) - const truncated = good.subarray(0, good.length - 3) + const truncated = MINIMAL_BITCOIN_OTS.subarray(0, MINIMAL_BITCOIN_OTS.length - 3) expectFailure(parseOtsFile(truncated)) }) it('rejects proofs with trailing garbage', () => { - const good = buildOts(DIGEST, [bitcoinAttestation(1)]) - const withGarbage = Buffer.concat([good, Buffer.from([0x00, 0x11, 0x22])]) + const withGarbage = Buffer.concat([MINIMAL_BITCOIN_OTS, Buffer.from([0x00, 0x11, 0x22])]) const result = expectFailure(parseOtsFile(withGarbage)) expect(result.reason).to.match(/trailing bytes/) }) @@ -252,7 +276,7 @@ describe('NIP-03 — OpenTimestamps', () => { it('parses sha1 file digest (20-byte) proofs', () => { const digest = Buffer.alloc(20, 0xab) - const buf = buildOtsWithFileHashOp(OP_SHA1, digest, [bitcoinAttestation(1)]) + const buf = buildOtsWithFileHashOp(OP_SHA1, digest, [BITCOIN_ATTESTATION_1]) const result = expectSuccess(parseOtsFile(buf)) expect(result.summary.fileHashOp).to.equal('sha1') expect(result.summary.digest).to.equal(digest.toString('hex')) @@ -273,31 +297,24 @@ describe('NIP-03 — OpenTimestamps', () => { }) it('parses prepend binary op in the commitment tree', () => { - const prepend = Buffer.concat([Buffer.from([OP_PREPEND]), writeVarBytes(Buffer.from([0x01]))]) - const buf = buildOts(DIGEST, [Buffer.concat([prepend, bitcoinAttestation(4)])]) - const result = expectSuccess(parseOtsFile(buf)) + const result = expectSuccess(parseOtsFile(OTS_PREPEND)) expect(result.summary.attestations.some((a) => a.kind === 'bitcoin')).to.equal(true) }) it('parses reverse and hexlify unary ops wrapping an attestation', () => { - const revThenHex = Buffer.concat([Buffer.from([OP_REVERSE]), Buffer.from([OP_HEXLIFY]), bitcoinAttestation(5)]) - const buf = buildOts(DIGEST, [revThenHex]) - const result = expectSuccess(parseOtsFile(buf)) + const result = expectSuccess(parseOtsFile(OTS_REVERSE_HEXLIFY)) expect(result.summary.attestations[0].kind).to.equal('bitcoin') }) it('classifies ethereum block header attestations', () => { - const buf = buildOts(DIGEST, [ethereumAttestation(18_000_000)]) - const result = expectSuccess(parseOtsFile(buf)) + const result = expectSuccess(parseOtsFile(OTS_ETHEREUM)) const eth = result.summary.attestations.find((a) => a.kind === 'ethereum') expect(eth).to.exist expect(eth?.height).to.equal(18_000_000) }) it('treats a truncated bitcoin attestation payload as height-less', () => { - const broken = Buffer.concat([Buffer.from([TAG_ATTESTATION]), BITCOIN_TAG, writeVarUint(0)]) - const buf = buildOts(DIGEST, [broken]) - const result = expectSuccess(parseOtsFile(buf)) + const result = expectSuccess(parseOtsFile(OTS_TRUNCATED_ATTESTATION)) expect(result.summary.attestations[0]).to.include({ kind: 'bitcoin' }) expect(result.summary.attestations[0].height).to.equal(undefined) }) @@ -343,14 +360,12 @@ describe('NIP-03 — OpenTimestamps', () => { }) describe('validateOtsProof', () => { - const validProof = buildOts(DIGEST, [bitcoinAttestation(810391)]).toString('base64') - it('accepts a well-formed bitcoin-anchored proof whose digest matches', () => { - expect(validateOtsProof(validProof, EVENT_ID)).to.equal(undefined) + expect(validateOtsProof(VALID_PROOF_BASE64, EVENT_ID)).to.equal(undefined) }) it('accepts uppercase hex target ids and normalizes them', () => { - expect(validateOtsProof(validProof, EVENT_ID.toUpperCase())).to.equal(undefined) + expect(validateOtsProof(VALID_PROOF_BASE64, EVENT_ID.toUpperCase())).to.equal(undefined) }) it('rejects empty content', () => { @@ -363,26 +378,26 @@ describe('NIP-03 — OpenTimestamps', () => { it('rejects proofs whose digest does not match the event id', () => { const other = '0'.repeat(64) - expect(validateOtsProof(validProof, other)).to.match(/digest does not match/) + expect(validateOtsProof(VALID_PROOF_BASE64, other)).to.match(/digest does not match/) }) it('rejects target ids that are not 32-byte hex', () => { - expect(validateOtsProof(validProof, 'not-an-id')).to.match(/not a 32-byte hex/) + expect(validateOtsProof(VALID_PROOF_BASE64, 'not-an-id')).to.match(/not a 32-byte hex/) }) it('rejects a non-string target event id', () => { - expect(validateOtsProof(validProof, null as unknown as string)).to.match(/not a 32-byte hex/) + expect(validateOtsProof(VALID_PROOF_BASE64, null as unknown as string)).to.match(/not a 32-byte hex/) }) it('rejects proofs without any bitcoin attestation', () => { - const onlyPending = buildOts(DIGEST, [pendingAttestation('https://a.pool.opentimestamps.org')]).toString('base64') + const onlyPending = buildOts(DIGEST, [PENDING_ATTESTATION_DEFAULT]).toString('base64') expect(validateOtsProof(onlyPending, EVENT_ID)).to.match(/bitcoin attestation/) }) it('rejects a proof digested with a non-sha256 hash op', () => { // Construct a manual RIPEMD160 file (20-byte digest) — this should fail // the sha256 requirement even though the parser would otherwise accept it. - const parts = [MAGIC, writeVarUint(1), Buffer.from([0x03]), Buffer.alloc(20, 0xaa), bitcoinAttestation(1)] + const parts = [MAGIC, writeVarUint(1), Buffer.from([0x03]), Buffer.alloc(20, 0xaa), BITCOIN_ATTESTATION_1] const ripemd = Buffer.concat(parts).toString('base64') expect(validateOtsProof(ripemd, 'aa'.repeat(32))).to.match(/sha256 file hash op/) }) diff --git a/test/unit/utils/nip05.spec.ts b/test/unit/utils/nip05.spec.ts index 5e38193a..901788c3 100644 --- a/test/unit/utils/nip05.spec.ts +++ b/test/unit/utils/nip05.spec.ts @@ -18,6 +18,23 @@ import { EventKinds } from '../../../src/constants/base' const { expect } = chai +const PUBKEY_A = 'a'.repeat(64) +const PUBKEY_B = 'b'.repeat(64) +const SIG_C = 'c'.repeat(128) +const ID_A = PUBKEY_A + +function makeEvent(kind: number, content: string): Event { + return { + id: ID_A, + pubkey: PUBKEY_B, + created_at: 1234567890, + kind, + tags: [], + content, + sig: SIG_C, + } +} + describe('NIP-05 utils', () => { describe('parseNip05Identifier', () => { it('returns parsed identifier for valid input', () => { @@ -75,81 +92,39 @@ describe('NIP-05 utils', () => { describe('extractNip05FromEvent', () => { it('extracts nip05 from kind 0 event', () => { - const event: Event = { - id: 'a'.repeat(64), - pubkey: 'b'.repeat(64), - created_at: 1234567890, - kind: EventKinds.SET_METADATA, - tags: [], - content: JSON.stringify({ name: 'alice', nip05: 'alice@example.com' }), - sig: 'c'.repeat(128), - } - expect(extractNip05FromEvent(event)).to.equal('alice@example.com') + expect(extractNip05FromEvent( + makeEvent(EventKinds.SET_METADATA, JSON.stringify({ name: 'alice', nip05: 'alice@example.com' })), + )).to.equal('alice@example.com') }) it('returns undefined for non-kind-0 event', () => { - const event: Event = { - id: 'a'.repeat(64), - pubkey: 'b'.repeat(64), - created_at: 1234567890, - kind: EventKinds.TEXT_NOTE, - tags: [], - content: JSON.stringify({ nip05: 'alice@example.com' }), - sig: 'c'.repeat(128), - } - expect(extractNip05FromEvent(event)).to.be.undefined + expect(extractNip05FromEvent( + makeEvent(EventKinds.TEXT_NOTE, JSON.stringify({ nip05: 'alice@example.com' })), + )).to.be.undefined }) it('returns undefined when nip05 is not in content', () => { - const event: Event = { - id: 'a'.repeat(64), - pubkey: 'b'.repeat(64), - created_at: 1234567890, - kind: EventKinds.SET_METADATA, - tags: [], - content: JSON.stringify({ name: 'alice' }), - sig: 'c'.repeat(128), - } - expect(extractNip05FromEvent(event)).to.be.undefined + expect(extractNip05FromEvent( + makeEvent(EventKinds.SET_METADATA, JSON.stringify({ name: 'alice' })), + )).to.be.undefined }) it('returns undefined for invalid JSON content', () => { - const event: Event = { - id: 'a'.repeat(64), - pubkey: 'b'.repeat(64), - created_at: 1234567890, - kind: EventKinds.SET_METADATA, - tags: [], - content: 'not json', - sig: 'c'.repeat(128), - } - expect(extractNip05FromEvent(event)).to.be.undefined + expect(extractNip05FromEvent( + makeEvent(EventKinds.SET_METADATA, 'not json'), + )).to.be.undefined }) it('returns undefined when nip05 is empty string', () => { - const event: Event = { - id: 'a'.repeat(64), - pubkey: 'b'.repeat(64), - created_at: 1234567890, - kind: EventKinds.SET_METADATA, - tags: [], - content: JSON.stringify({ nip05: '' }), - sig: 'c'.repeat(128), - } - expect(extractNip05FromEvent(event)).to.be.undefined + expect(extractNip05FromEvent( + makeEvent(EventKinds.SET_METADATA, JSON.stringify({ nip05: '' })), + )).to.be.undefined }) it('returns undefined when nip05 is not a string', () => { - const event: Event = { - id: 'a'.repeat(64), - pubkey: 'b'.repeat(64), - created_at: 1234567890, - kind: EventKinds.SET_METADATA, - tags: [], - content: JSON.stringify({ nip05: 42 }), - sig: 'c'.repeat(128), - } - expect(extractNip05FromEvent(event)).to.be.undefined + expect(extractNip05FromEvent( + makeEvent(EventKinds.SET_METADATA, JSON.stringify({ nip05: 42 })), + )).to.be.undefined }) }) @@ -190,13 +165,17 @@ describe('NIP-05 utils', () => { describe('verifyNip05Identifier', () => { let axiosGetStub: Sinon.SinonStub - const pubkey = 'a'.repeat(64) + const pubkey = PUBKEY_A - beforeEach(() => { + before(() => { axiosGetStub = Sinon.stub(axios, 'get') }) afterEach(() => { + axiosGetStub.reset() + }) + + after(() => { axiosGetStub.restore() }) @@ -241,7 +220,7 @@ describe('NIP-05 utils', () => { }) it('returns mismatch when pubkey does not match', async () => { - axiosGetStub.resolves({ data: { names: { alice: 'b'.repeat(64) } } }) + axiosGetStub.resolves({ data: { names: { alice: PUBKEY_B } } }) const outcome = await verifyNip05Identifier('alice@example.com', pubkey) @@ -286,7 +265,8 @@ describe('NIP-05 utils', () => { describe('beforeRedirect SSRF guard', () => { let guard: (options: { href?: string; protocol?: string; hostname?: string }) => void - beforeEach(async () => { + before(async () => { + axiosGetStub.reset() axiosGetStub.resolves({ data: { names: { alice: pubkey } } }) await verifyNip05Identifier('alice@example.com', pubkey) guard = axiosGetStub.firstCall.args[1].beforeRedirect