UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

282 lines (212 loc) 8.79 kB
import DRBG from '../../primitives/DRBG' import DRBGVectors from './DRBG.vectors' import { toArray, toHex } from '../../primitives/utils' import { SHA256 } from '../../primitives/Hash' describe('DRBG', () => { describe('NIST vector compatibility', () => { DRBGVectors.forEach((opt, index) => { it(`handles NIST-style vector ${index} consistently`, () => { const entropyBytes = toArray(opt.entropy, 'hex') const nonceBytes = toArray(opt.nonce, 'hex') const expectedByteLen = opt.expected.length / 2 if (entropyBytes.length !== 32 || nonceBytes.length !== 32) { expect(() => new DRBG(opt.entropy, opt.nonce)).toThrow() return } const drbg1 = new DRBG(opt.entropy, opt.nonce) const out1 = drbg1.generate(expectedByteLen) const drbg2 = new DRBG(opt.entropy, opt.nonce) const out2 = drbg2.generate(expectedByteLen) expect(out1).toEqual(out2) expect(out1.length).toBe(opt.expected.length) }) }) }) describe('constructor input validation', () => { it('throws if entropy is shorter than 32 bytes', () => { const entropy = new Array(31).fill(0x01) const nonce = new Array(32).fill(0x02) expect(() => { new DRBG(entropy, nonce) }).toThrow('Entropy must be exactly 32 bytes (256 bits)') }) it('throws if entropy is longer than 32 bytes', () => { const entropy = new Array(33).fill(0x01) const nonce = new Array(32).fill(0x02) expect(() => { new DRBG(entropy, nonce) }).toThrow('Entropy must be exactly 32 bytes (256 bits)') }) it('throws if nonce is shorter than 32 bytes', () => { const entropy = new Array(32).fill(0x01) const nonce = new Array(31).fill(0x02) expect(() => { new DRBG(entropy, nonce) }).toThrow('Nonce must be exactly 32 bytes (256 bits)') }) it('throws if nonce is longer than 32 bytes', () => { const entropy = new Array(32).fill(0x01) const nonce = new Array(33).fill(0x02) expect(() => { new DRBG(entropy, nonce) }).toThrow('Nonce must be exactly 32 bytes (256 bits)') }) it('accepts both hex strings and number[] inputs equivalently', () => { const entropyArr = new Array(32).fill(0x11) const nonceArr = new Array(32).fill(0x22) const entropyHex = Buffer.from(entropyArr).toString('hex') const nonceHex = Buffer.from(nonceArr).toString('hex') const drbgArray = new DRBG(entropyArr, nonceArr) const drbgHex = new DRBG(entropyHex, nonceHex) const outArray = drbgArray.generate(32) const outHex = drbgHex.generate(32) expect(outArray).toEqual(outHex) }) }) describe('determinism', () => { const entropyHex = '1b2e3d4c5f60718293a4b5c6d7e8f9011b2e3d4c5f60718293a4b5c6d7e8f901' const nonceHex = 'abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd' it('produces the same sequence for the same inputs', () => { const drbg1 = new DRBG(entropyHex, nonceHex) const drbg2 = new DRBG(entropyHex, nonceHex) const seq1 = [ drbg1.generate(32), drbg1.generate(32), drbg1.generate(16) ] const seq2 = [ drbg2.generate(32), drbg2.generate(32), drbg2.generate(16) ] expect(seq1).toEqual(seq2) }) it('produces different sequences if entropy changes', () => { const entropyHex2 = '2b3e4d5c6f708192a3b4c5d6e7f809112b3e4d5c6f708192a3b4c5d6e7f80911' const drbg1 = new DRBG(entropyHex, nonceHex) const drbg2 = new DRBG(entropyHex2, nonceHex) const out1 = drbg1.generate(32) const out2 = drbg2.generate(32) expect(out1).not.toEqual(out2) }) it('produces different sequences if nonce changes', () => { const nonceHex2 = '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff' const drbg1 = new DRBG(entropyHex, nonceHex) const drbg2 = new DRBG(entropyHex, nonceHex2) const out1 = drbg1.generate(32) const out2 = drbg2.generate(32) expect(out1).not.toEqual(out2) }) }) describe('output length and state advancement', () => { const entropyBytes = new Array(32).fill(0x33) const nonceBytes = new Array(32).fill(0x44) it('returns hex strings of length 2 * len', () => { const drbg = new DRBG(entropyBytes, nonceBytes) const out16 = drbg.generate(16) const out32 = drbg.generate(32) expect(out16.length).toBe(16 * 2) expect(out32.length).toBe(32 * 2) }) it('advances internal state between generate calls', () => { const drbg = new DRBG(entropyBytes, nonceBytes) const first = drbg.generate(32) const second = drbg.generate(32) expect(second).not.toEqual(first) }) it('matches output when seeded with explicit number[] arrays', () => { const entropyHex = 'aa'.repeat(32) const nonceHex = 'bb'.repeat(32) const entropyArr = toArray(entropyHex, 'hex') const nonceArr = toArray(nonceHex, 'hex') const drbgFromHex = new DRBG(entropyHex, nonceHex) const drbgFromArr = new DRBG(entropyArr, nonceArr) const outHex = drbgFromHex.generate(32) const outArr = drbgFromArr.generate(32) expect(outHex).toEqual(outArr) }) }) describe('RFC 6979 ECDSA P-256 / SHA-256 vectors', () => { // q for NIST P-256, from RFC 6979 A.2.5 const qHex = 'FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551' const q = BigInt('0x' + qHex) // int2octets: convert a non-negative bigint < 2^256 to a 32-byte big-endian array const intToOctets = (x: bigint): number[] => { const out = new Array<number>(32) let v = x for (let i = 31; i >= 0; i--) { out[i] = Number(v & 0xffn) v >>= 8n } return out } // bits2octets(h1): convert hash output to int, reduce mod q, return 32-byte big-endian const bits2octets = (h1: number[]): number[] => { const h1Int = BigInt('0x' + toHex(h1)) const z1 = h1Int % q return intToOctets(z1) } const normalizeHex = (hex: string): string => hex.toLowerCase() it('reproduces RFC 6979 k for P-256, SHA-256, message "sample"', () => { // From RFC 6979 A.2.5 (ECDSA, 256 bits, P-256): // private key x: const xHex = 'C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721' // expected k for SHA-256, message = "sample" const expectedKHex = 'A6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60' const msg = 'sample' const x = BigInt('0x' + xHex) const entropy = intToOctets(x) // h1 = SHA-256(message) const h1Bytes = new SHA256().update(msg, 'utf8').digest() const nonce = bits2octets(h1Bytes) const drbg = new DRBG(entropy, nonce) // First 32-byte block from DRBG const tHex = drbg.generate(32) // 32 bytes = 64 hex chars const tInt = BigInt('0x' + tHex) // RFC 6979 derives k as tInt mod q (and retries if out of range; here it’s fine) const k = tInt % q const kHex = k.toString(16).padStart(64, '0') expect(normalizeHex(kHex)).toBe(normalizeHex(expectedKHex)) }) it('reproduces RFC 6979 k for P-256, SHA-256, message "test"', () => { // Same key x as above (RFC 6979 A.2.5), different message: const xHex = 'C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721' // expected k for SHA-256, message = "test" const expectedKHex = 'D16B6AE827F17175E040871A1C7EC3500192C4C92677336EC2537ACAEE0008E0' const msg = 'test' const x = BigInt('0x' + xHex) const entropy = intToOctets(x) const h1Bytes = new SHA256().update(msg, 'utf8').digest() const nonce = bits2octets(h1Bytes) const drbg = new DRBG(entropy, nonce) const tHex = drbg.generate(32) const tInt = BigInt('0x' + tHex) const k = tInt % q const kHex = k.toString(16).padStart(64, '0') expect(normalizeHex(kHex)).toBe(normalizeHex(expectedKHex)) }) it('is deterministic for the same RFC 6979 key and message', () => { const xHex = 'C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721' const msg = 'sample' const x = BigInt('0x' + xHex) const entropy = intToOctets(x) const h1Bytes = new SHA256().update(msg, 'utf8').digest() const nonce = bits2octets(h1Bytes) const drbg1 = new DRBG(entropy, nonce) const drbg2 = new DRBG(entropy, nonce) const out1 = drbg1.generate(32) const out2 = drbg2.generate(32) expect(out1).toBe(out2) }) }) })