UNPKG

ox

Version:

Ethereum Standard Library

613 lines (530 loc) 17.5 kB
import { Bytes, Hex, P256 } from 'ox' import { describe, expect, test } from 'vp/test' import { accounts } from '../../../test/constants/accounts.js' describe('getPublicKey', () => { const privateKey = '0xdde57ae9b9ed6f76fa5358c24d5ca2057ebc1ece18b7273121450a29c96ec8e5' test('default', () => { { const publicKey = P256.getPublicKey({ privateKey, }) expect(publicKey).toMatchInlineSnapshot( ` { "prefix": 4, "x": "0x1753ed8e23fd6e17922ebdeed8ebe8043e34cd10118271cf2acdee88c1d58307", "y": "0xc3357f052ea5e9a67625fa723ca0e7bc8ce9d069bea5b8b397137f991284b68c", } `, ) } { const publicKey = P256.getPublicKey({ privateKey: Bytes.fromHex(accounts[0].privateKey), }) expect(publicKey).toMatchInlineSnapshot( ` { "prefix": 4, "x": "0xa43b66d1eaee03f07d64920491f8b3487a90f527f2342c8caccd55d506508449", "y": "0x6c57d409d6db06faefd8a0aa1106acd69501134e11cf74b2e95c81b451da3433", } `, ) } }) }) describe('createKeyPair', () => { test('default', () => { const keyPair = P256.createKeyPair() expect(keyPair).toHaveProperty('privateKey') expect(keyPair).toHaveProperty('publicKey') expect(typeof keyPair.privateKey).toBe('string') expect(keyPair.privateKey).toMatch(/^0x[0-9a-f]{64}$/) expect(keyPair.privateKey.length).toBe(66) expect(keyPair.publicKey).toHaveProperty('prefix') expect(keyPair.publicKey).toHaveProperty('x') expect(keyPair.publicKey).toHaveProperty('y') expect(keyPair.publicKey.prefix).toBe(4) expect(typeof keyPair.publicKey.x).toBe('string') expect(typeof keyPair.publicKey.y).toBe('string') }) test('behavior: deterministic public key derivation', () => { const keyPair = P256.createKeyPair() const derivedPublicKey = P256.getPublicKey({ privateKey: keyPair.privateKey, }) expect(keyPair.publicKey).toEqual(derivedPublicKey) }) test('behavior: unique key pairs', () => { const keyPair1 = P256.createKeyPair() const keyPair2 = P256.createKeyPair() expect(keyPair1.privateKey).not.toEqual(keyPair2.privateKey) expect(keyPair1.publicKey).not.toEqual(keyPair2.publicKey) }) test('behavior: valid for signing and verification', () => { const keyPair = P256.createKeyPair() const payload = '0xdeadbeef' const signature = P256.sign({ payload, privateKey: keyPair.privateKey }) const isValid = P256.verify({ publicKey: keyPair.publicKey, payload, signature, }) expect(isValid).toBe(true) }) test('behavior: valid for ECDH key agreement', () => { const keyPairA = P256.createKeyPair() const keyPairB = P256.createKeyPair() const sharedSecretA = P256.getSharedSecret({ privateKey: keyPairA.privateKey, publicKey: keyPairB.publicKey, }) const sharedSecretB = P256.getSharedSecret({ privateKey: keyPairB.privateKey, publicKey: keyPairA.publicKey, }) expect(sharedSecretA).toEqual(sharedSecretB) expect(typeof sharedSecretA).toBe('string') expect(sharedSecretA).toMatch(/^0x[0-9a-f]{66}$/) }) test('options: as (Hex)', () => { const keyPair = P256.createKeyPair({ as: 'Hex' }) expect(typeof keyPair.privateKey).toBe('string') expect(keyPair.privateKey).toMatch(/^0x[0-9a-f]{64}$/) expect(keyPair.privateKey.length).toBe(66) }) test('options: as (Bytes)', () => { const keyPair = P256.createKeyPair({ as: 'Bytes' }) expect(keyPair.privateKey).toBeInstanceOf(Uint8Array) expect(keyPair.privateKey.length).toBe(32) expect(keyPair.publicKey).toHaveProperty('prefix') expect(keyPair.publicKey.prefix).toBe(4) }) test('behavior: bytes format works with other functions', () => { const keyPair = P256.createKeyPair({ as: 'Bytes' }) const derivedPublicKey = P256.getPublicKey({ privateKey: keyPair.privateKey, }) expect(keyPair.publicKey).toEqual(derivedPublicKey) }) test('behavior: public key recovery', () => { const keyPair = P256.createKeyPair() const payload = '0xdeadbeef' const signature = P256.sign({ payload, privateKey: keyPair.privateKey }) const recoveredPublicKey = P256.recoverPublicKey({ payload, signature }) expect(recoveredPublicKey).toEqual(keyPair.publicKey) }) }) describe('getSharedSecret', () => { test('default', () => { const { privateKey: privateKeyA, publicKey: publicKeyA } = P256.createKeyPair() const { privateKey: privateKeyB, publicKey: publicKeyB } = P256.createKeyPair() // Compute shared secret from A's perspective const sharedSecretA = P256.getSharedSecret({ privateKey: privateKeyA, publicKey: publicKeyB, }) // Compute shared secret from B's perspective const sharedSecretB = P256.getSharedSecret({ privateKey: privateKeyB, publicKey: publicKeyA, }) // ECDH property: both should be equal expect(sharedSecretA).toEqual(sharedSecretB) expect(typeof sharedSecretA).toBe('string') expect(sharedSecretA).toMatch(/^0x[0-9a-f]{66}$/) }) test('behavior: known test vectors', () => { // Use fixed keys for deterministic testing const privateKeyA = '0xdde57ae9b9ed6f76fa5358c24d5ca2057ebc1ece18b7273121450a29c96ec8e5' const privateKeyB = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' const publicKeyA = P256.getPublicKey({ privateKey: privateKeyA }) const publicKeyB = P256.getPublicKey({ privateKey: privateKeyB }) const sharedSecretA = P256.getSharedSecret({ privateKey: privateKeyA, publicKey: publicKeyB, }) const sharedSecretB = P256.getSharedSecret({ privateKey: privateKeyB, publicKey: publicKeyA, }) expect(sharedSecretA).toEqual(sharedSecretB) expect(sharedSecretA).toMatchInlineSnapshot( '"0x02e0ad69b35f127c5d81d1aababa10ec6b3579ba9413d9b561203ddfa1df257af6"', ) }) test('behavior: different input types', () => { const { privateKey: privateKeyA } = P256.createKeyPair() const { publicKey: publicKeyB } = P256.createKeyPair() // Test with Hex private key const sharedSecret1 = P256.getSharedSecret({ privateKey: privateKeyA, publicKey: publicKeyB, }) // Test with Bytes private key const sharedSecret2 = P256.getSharedSecret({ privateKey: Bytes.fromHex(privateKeyA), publicKey: publicKeyB, }) expect(sharedSecret1).toEqual(sharedSecret2) }) test('behavior: uncompressed public key', () => { const { privateKey: privateKeyA } = P256.createKeyPair() const { publicKey: publicKeyB } = P256.createKeyPair() // Ensure the public key is uncompressed (prefix 4) expect(publicKeyB.prefix).toBe(4) const sharedSecret = P256.getSharedSecret({ privateKey: privateKeyA, publicKey: publicKeyB, }) expect(typeof sharedSecret).toBe('string') expect(sharedSecret).toMatch(/^0x[0-9a-f]{66}$/) }) test('options: as', () => { const { privateKey: privateKeyA } = P256.createKeyPair() const { publicKey: publicKeyB } = P256.createKeyPair() // Test Hex output (default) const sharedSecretHex = P256.getSharedSecret({ privateKey: privateKeyA, publicKey: publicKeyB, }) // Test Bytes output const sharedSecretBytes = P256.getSharedSecret({ privateKey: privateKeyA, publicKey: publicKeyB, as: 'Bytes', }) // Verify formats expect(typeof sharedSecretHex).toBe('string') expect(sharedSecretHex).toMatch(/^0x[0-9a-f]{66}$/) expect(sharedSecretBytes).toBeInstanceOf(Uint8Array) expect(sharedSecretBytes.length).toBe(33) // 33 bytes for compressed point // Verify they represent the same data expect(Hex.fromBytes(sharedSecretBytes)).toEqual(sharedSecretHex) }) test('behavior: deterministic', () => { const privateKeyA = P256.randomPrivateKey() const privateKeyB = P256.randomPrivateKey() const publicKeyB = P256.getPublicKey({ privateKey: privateKeyB }) const sharedSecret1 = P256.getSharedSecret({ privateKey: privateKeyA, publicKey: publicKeyB, }) const sharedSecret2 = P256.getSharedSecret({ privateKey: privateKeyA, publicKey: publicKeyB, }) // Should be deterministic - same inputs produce same output expect(sharedSecret1).toEqual(sharedSecret2) }) test('behavior: different key pairs produce different secrets', () => { const privateKeyA = P256.randomPrivateKey() const privateKeyB = P256.randomPrivateKey() const privateKeyC = P256.randomPrivateKey() const publicKeyB = P256.getPublicKey({ privateKey: privateKeyB }) const publicKeyC = P256.getPublicKey({ privateKey: privateKeyC }) const sharedSecretAB = P256.getSharedSecret({ privateKey: privateKeyA, publicKey: publicKeyB, }) const sharedSecretAC = P256.getSharedSecret({ privateKey: privateKeyA, publicKey: publicKeyC, }) // Different key pairs should produce different shared secrets expect(sharedSecretAB).not.toEqual(sharedSecretAC) }) test('error: invalid private key', () => { const publicKeyB = P256.getPublicKey({ privateKey: P256.randomPrivateKey(), }) expect(() => P256.getSharedSecret({ privateKey: '0x00', publicKey: publicKeyB, }), ).toThrow() }) test('error: invalid public key', () => { const privateKeyA = P256.randomPrivateKey() const invalidPublicKey = { prefix: 4, x: '0x0000000000000000000000000000000000000000000000000000000000000000', y: '0x0000000000000000000000000000000000000000000000000000000000000000', } as const expect(() => P256.getSharedSecret({ privateKey: privateKeyA, publicKey: invalidPublicKey, }), ).toThrow() }) }) describe('randomPrivateKey', () => { test('default', () => { const privateKey = P256.randomPrivateKey() expect(privateKey.length).toBe(66) }) test('options: as', () => { const privateKey = P256.randomPrivateKey({ as: 'Bytes' }) expect(privateKey.length).toBe(32) }) }) describe('recoverPublicKey', () => { const privateKey = P256.randomPrivateKey() test('default', () => { const payload = '0xdeadbeef' const signature = P256.sign({ payload, privateKey }) expect(P256.recoverPublicKey({ payload, signature })).toStrictEqual( P256.getPublicKey({ privateKey }), ) }) }) describe('sign', () => { const privateKey = '0xdde57ae9b9ed6f76fa5358c24d5ca2057ebc1ece18b7273121450a29c96ec8e5' const publicKey = P256.getPublicKey({ privateKey }) test('default', async () => { { const signature = P256.sign({ payload: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', privateKey, }) expect(signature).toMatchInlineSnapshot( ` { "r": "0x38cfa1c681a8a8a2dedc9657eda39fce932517ddf6e9595b7da89b400a3af75d", "s": "0x61fc9d2f84a253fe75e8fbd4a6d24a8d5f29bf664ebe5009f827a246807cc400", "yParity": 1, } `, ) expect( P256.verify({ publicKey, payload: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', signature, }), ).toBe(true) expect( P256.verify({ publicKey, payload: '0xbeef', signature, }), ).toBe(false) } { const signature = P256.sign({ payload: Bytes.fromHex( '0xdeadbeef1aaaa22111231241220000123aaaaabbccababab2211', ), privateKey, }) expect(signature).toMatchInlineSnapshot( ` { "r": "0xf130f7c7f3c62f6cf141874ccb17c9bd8eba4ea80c078f2354f9bb664137a314", "s": "0x0ecf68f998506c55189f0542198848b979bd727999a60ac573910c5962bf5ae4", "yParity": 0, } `, ) expect( P256.verify({ publicKey, payload: '0xdeadbeef1aaaa22111231241220000123aaaaabbccababab2211', signature, }), ).toBe(true) expect( P256.verify({ publicKey, payload: '0xbeef', signature, }), ).toBe(false) } }) test('options: hash', () => { const signature = P256.sign({ hash: true, payload: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', privateKey, }) expect(signature).toMatchInlineSnapshot( ` { "r": "0x5550cfd3b935570c4e9ace7d36d733368c7a7f755de10c783020b0ff0b74e95a", "s": "0x0071e8aca10952c56847d3a599b62ba0d2ab42985b993f77d762514ee427bbe4", "yParity": 0, } `, ) expect( P256.verify({ hash: true, publicKey, payload: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', signature, }), ).toBe(true) expect( P256.verify({ hash: true, publicKey, payload: '0xbeef', signature, }), ).toBe(false) }) test('options: extraEntropy', () => { { const signature_1 = P256.sign({ extraEntropy: false, payload: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', privateKey: accounts[0].privateKey, }) const signature_2 = P256.sign({ extraEntropy: false, payload: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', privateKey: accounts[0].privateKey, }) expect(signature_1).toEqual(signature_2) } { const signature_1 = P256.sign({ extraEntropy: Hex.random(32), payload: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', privateKey: accounts[0].privateKey, }) const signature_2 = P256.sign({ extraEntropy: Hex.random(32), payload: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', privateKey: accounts[0].privateKey, }) expect(signature_1).not.toEqual(signature_2) } { const signature_1 = P256.sign({ extraEntropy: Bytes.random(32), payload: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', privateKey: accounts[0].privateKey, }) const signature_2 = P256.sign({ extraEntropy: Bytes.random(32), payload: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', privateKey: accounts[0].privateKey, }) expect(signature_1).not.toEqual(signature_2) } }) }) describe('verify', () => { const privateKey = accounts[0].privateKey test('default', () => { const payload = '0xdeadbeef' const { r, s } = P256.sign({ payload, privateKey }) const publicKey = P256.getPublicKey({ privateKey }) expect(P256.verify({ publicKey, payload, signature: { r, s } })).toBe(true) }) test('behavior: bytes payload', () => { const payload = '0xdeadbeef' const { r, s } = P256.sign({ payload, privateKey }) const publicKey = P256.getPublicKey({ privateKey }) expect( P256.verify({ publicKey, payload: Bytes.fromHex(payload), signature: { r, s }, }), ).toBe(true) }) test('options: hash', () => { const payload = '0xdeadbeef' const { r, s } = P256.sign({ hash: true, payload, privateKey }) const publicKey = P256.getPublicKey({ privateKey }) expect( P256.verify({ hash: true, publicKey, payload, signature: { r, s } }), ).toBe(true) }) }) test('exports', () => { expect(Object.keys(P256)).toMatchInlineSnapshot(` [ "noble", "createKeyPair", "getPublicKey", "getSharedSecret", "randomPrivateKey", "recoverPublicKey", "sign", "verify", ] `) }) describe('as option', () => { test("sign + getPublicKey + recoverPublicKey: as 'Hex'", () => { const { privateKey, publicKey } = P256.createKeyPair() const sigHex = P256.sign({ payload: '0xdeadbeef', privateKey, as: 'Hex', }) expect(typeof sigHex).toBe('string') const pkHex = P256.getPublicKey({ privateKey, as: 'Hex' }) expect(typeof pkHex).toBe('string') const recoveredHex = P256.recoverPublicKey({ payload: '0xdeadbeef', signature: sigHex, as: 'Hex', }) expect(recoveredHex).toBe(pkHex) expect( P256.verify({ payload: '0xdeadbeef', publicKey, signature: sigHex }), ).toBe(true) }) test("sign + getPublicKey: as 'Bytes'", () => { const { privateKey } = P256.createKeyPair() const sigBytes = P256.sign({ payload: '0xdeadbeef', privateKey, as: 'Bytes', }) expect(sigBytes).toBeInstanceOf(Uint8Array) const pkBytes = P256.getPublicKey({ privateKey, as: 'Bytes' }) expect(pkBytes).toBeInstanceOf(Uint8Array) }) test('verify accepts Hex signature + Hex publicKey', () => { const { privateKey } = P256.createKeyPair() const pkHex = P256.getPublicKey({ privateKey, as: 'Hex' }) const sigHex = P256.sign({ payload: '0xdeadbeef', privateKey, as: 'Hex', }) expect( P256.verify({ payload: '0xdeadbeef', publicKey: pkHex, signature: sigHex, }), ).toBe(true) }) })