UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

102 lines (89 loc) 4.27 kB
import crypto from 'crypto' import Secp256r1 from '../Secp256r1.js' import { sha256 } from '../Hash.js' const curve = new Secp256r1() const TWO_G = '047cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc4766997807775510db8ed040293d9ac69f7430dbba7dade63ce982299e04b79d227873d1' const THREE_G = '045ecbe4d1a6330a44c8f7ef951d4bf165e6c6b721efada985fb41661bc6e7fd6c8734640c4998ff7e374b06ce1a64a2ecd82ab036384fb83d9a79b127a27d5032' const toBase64Url = (hex: string): string => Buffer.from(hex, 'hex').toString('base64url') describe('Secp256r1', () => { test('base point multiplication matches known coordinates and handles infinity', () => { const twoG = curve.multiplyBase(2n) const threeG = curve.multiplyBase(3n) expect(curve.pointToHex(twoG)).toBe(TWO_G) expect(curve.pointToHex(threeG)).toBe(THREE_G) expect(curve.multiplyBase(curve.n)).toBeNull() expect(curve.multiply(null, 5n)).toBeNull() }) test('public key generation stays on-curve, supports compression, and rejects bad encodings', () => { const priv = curve.generatePrivateKeyHex() const pub = curve.publicKeyFromPrivate(priv) expect(curve.isOnCurve(pub)).toBe(true) const compressed = curve.pointToHex(pub, true) const roundTrip = curve.pointFromHex(compressed) expect(roundTrip).toEqual(pub) expect(() => curve.pointFromHex('05abcdef')).toThrow() expect(() => curve.pointFromHex('')).toThrow() }) test('adding inverse points yields infinity', () => { const p = curve.multiplyBase(9n) const neg = { x: p!.x, y: curve.p - p!.y } expect(curve.add(p, neg)).toBeNull() expect(curve.add(null, p)).toEqual(p) }) test('ECDSA sign and verify round-trip, low-s enforced, rejects malformed inputs', () => { const priv = '1'.repeat(64) const pub = curve.publicKeyFromPrivate(priv) const message = Buffer.from('p256 check') const signature = curve.sign(message, priv) const sVal = BigInt('0x' + signature.s) expect(sVal <= curve.n / 2n).toBe(true) expect(curve.verify(message, signature, pub)).toBe(true) expect(curve.verify(Buffer.from('different'), signature, pub)).toBe(false) const tampered = { r: signature.r, s: signature.s.slice(0, 62) + '00' } expect(curve.verify(message, tampered, pub)).toBe(false) const zeroR = { r: '0'.repeat(64), s: signature.s } expect(curve.verify(message, zeroR, pub)).toBe(false) const zeroS = { r: signature.r, s: '0'.repeat(64) } expect(curve.verify(message, zeroS, pub)).toBe(false) expect(curve.verify(message, signature, '02deadbeef')).toBe(false) }) test('deterministic nonce is stable across calls and message changes', () => { const priv = '2'.repeat(64) const pub = curve.publicKeyFromPrivate(priv) const message = Buffer.from('deterministic nonce') const sig1 = curve.sign(message, priv) const sig2 = curve.sign(message, priv) expect(sig1).toEqual(sig2) const sig3 = curve.sign(Buffer.from('deterministic nonce v2'), priv) expect(sig3).not.toEqual(sig1) expect(curve.verify(message, sig1, pub)).toBe(true) }) test('prehashed signing path matches explicit hashing input', () => { const priv = '4'.repeat(64) const pub = curve.publicKeyFromPrivate(priv) const message = Buffer.from('prehashed path') const digest = new Uint8Array(sha256(message)) const sig1 = curve.sign(message, priv) const sig2 = curve.sign(digest, priv, { prehashed: true }) expect(sig1).toEqual(sig2) expect(curve.verify(digest, sig2, pub, { prehashed: true })).toBe(true) }) test('signatures interoperate with Node crypto (ieee-p1363 encoding)', () => { const priv = '3'.repeat(64) const pub = curve.publicKeyFromPrivate(priv) const message = Buffer.from('interop check') const signature = curve.sign(message, priv) const sigBuf = Buffer.concat([Buffer.from(signature.r, 'hex'), Buffer.from(signature.s, 'hex')]) const pubHex = curve.pointToHex(pub) const jwk = { kty: 'EC', crv: 'P-256', x: toBase64Url(pubHex.slice(2, 66)), y: toBase64Url(pubHex.slice(66)) } const ok = crypto.verify('sha256', message, { key: jwk, format: 'jwk', dsaEncoding: 'ieee-p1363' }, sigBuf) expect(ok).toBe(true) }) })