ox
Version:
Ethereum Standard Library
439 lines (377 loc) • 14 kB
text/typescript
import { Bytes, Ed25519, Hex, X25519 } from 'ox'
import { describe, expect, test } from 'vp/test'
describe('createKeyPair', () => {
test('default', () => {
const keyPair = Ed25519.createKeyPair()
expect(keyPair).toHaveProperty('privateKey')
expect(keyPair).toHaveProperty('publicKey')
expect(typeof keyPair.privateKey).toBe('string')
expect(typeof keyPair.publicKey).toBe('string')
expect(keyPair.privateKey).toMatch(/^0x[0-9a-fA-F]{64}$/)
expect(keyPair.publicKey).toMatch(/^0x[0-9a-fA-F]{64}$/)
})
test('with as: Hex', () => {
const keyPair = Ed25519.createKeyPair({ as: 'Hex' })
expect(keyPair).toHaveProperty('privateKey')
expect(keyPair).toHaveProperty('publicKey')
expect(typeof keyPair.privateKey).toBe('string')
expect(typeof keyPair.publicKey).toBe('string')
expect(keyPair.privateKey).toMatch(/^0x[0-9a-fA-F]{64}$/)
expect(keyPair.publicKey).toMatch(/^0x[0-9a-fA-F]{64}$/)
})
test('with as: Bytes', () => {
const keyPair = Ed25519.createKeyPair({ as: 'Bytes' })
expect(keyPair).toHaveProperty('privateKey')
expect(keyPair).toHaveProperty('publicKey')
expect(keyPair.privateKey).toBeInstanceOf(Uint8Array)
expect(keyPair.publicKey).toBeInstanceOf(Uint8Array)
expect(keyPair.privateKey).toHaveLength(32)
expect(keyPair.publicKey).toHaveLength(32)
})
test('unique keys', () => {
const keyPair1 = Ed25519.createKeyPair()
const keyPair2 = Ed25519.createKeyPair()
expect(keyPair1.privateKey).not.toBe(keyPair2.privateKey)
expect(keyPair1.publicKey).not.toBe(keyPair2.publicKey)
})
})
describe('getPublicKey', () => {
const privateKey =
'0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'
const expectedPublicKey =
'0xd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'
test('default (Hex)', () => {
const publicKey = Ed25519.getPublicKey({ privateKey })
expect(publicKey).toBe(expectedPublicKey)
})
test('with as: Hex', () => {
const publicKey = Ed25519.getPublicKey({ privateKey, as: 'Hex' })
expect(publicKey).toBe(expectedPublicKey)
})
test('with as: Bytes', () => {
const publicKey = Ed25519.getPublicKey({ privateKey, as: 'Bytes' })
expect(publicKey).toBeInstanceOf(Uint8Array)
expect(publicKey).toHaveLength(32)
expect(Hex.fromBytes(publicKey)).toBe(expectedPublicKey)
})
test('with Bytes private key', () => {
const privateKeyBytes = Bytes.fromHex(privateKey)
const publicKey = Ed25519.getPublicKey({ privateKey: privateKeyBytes })
expect(publicKey).toBe(expectedPublicKey)
})
test('with Bytes private key, as: Bytes', () => {
const privateKeyBytes = Bytes.fromHex(privateKey)
const publicKey = Ed25519.getPublicKey({
privateKey: privateKeyBytes,
as: 'Bytes',
})
expect(publicKey).toBeInstanceOf(Uint8Array)
expect(publicKey).toHaveLength(32)
expect(Hex.fromBytes(publicKey)).toBe(expectedPublicKey)
})
})
describe('randomPrivateKey', () => {
test('default (Hex)', () => {
const privateKey = Ed25519.randomPrivateKey()
expect(typeof privateKey).toBe('string')
expect(privateKey).toMatch(/^0x[0-9a-fA-F]{64}$/)
})
test('with as: Hex', () => {
const privateKey = Ed25519.randomPrivateKey({ as: 'Hex' })
expect(typeof privateKey).toBe('string')
expect(privateKey).toMatch(/^0x[0-9a-fA-F]{64}$/)
})
test('with as: Bytes', () => {
const privateKey = Ed25519.randomPrivateKey({ as: 'Bytes' })
expect(privateKey).toBeInstanceOf(Uint8Array)
expect(privateKey).toHaveLength(32)
})
test('unique keys', () => {
const privateKey1 = Ed25519.randomPrivateKey()
const privateKey2 = Ed25519.randomPrivateKey()
expect(privateKey1).not.toBe(privateKey2)
})
})
describe('sign', () => {
const privateKey =
'0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'
const payload = '0xdeadbeef'
test('default (Hex)', () => {
const signature = Ed25519.sign({ payload, privateKey })
expect(typeof signature).toBe('string')
expect(signature).toMatch(/^0x[0-9a-fA-F]{128}$/)
})
test('with as: Hex', () => {
const signature = Ed25519.sign({ payload, privateKey, as: 'Hex' })
expect(typeof signature).toBe('string')
expect(signature).toMatch(/^0x[0-9a-fA-F]{128}$/)
})
test('with as: Bytes', () => {
const signature = Ed25519.sign({ payload, privateKey, as: 'Bytes' })
expect(signature).toBeInstanceOf(Uint8Array)
expect(signature).toHaveLength(64)
})
test('with Bytes payload', () => {
const payloadBytes = Bytes.fromHex(payload)
const signature = Ed25519.sign({ payload: payloadBytes, privateKey })
expect(typeof signature).toBe('string')
expect(signature).toMatch(/^0x[0-9a-fA-F]{128}$/)
})
test('with Bytes private key', () => {
const privateKeyBytes = Bytes.fromHex(privateKey)
const signature = Ed25519.sign({ payload, privateKey: privateKeyBytes })
expect(typeof signature).toBe('string')
expect(signature).toMatch(/^0x[0-9a-fA-F]{128}$/)
})
test('with all Bytes', () => {
const payloadBytes = Bytes.fromHex(payload)
const privateKeyBytes = Bytes.fromHex(privateKey)
const signature = Ed25519.sign({
payload: payloadBytes,
privateKey: privateKeyBytes,
as: 'Bytes',
})
expect(signature).toBeInstanceOf(Uint8Array)
expect(signature).toHaveLength(64)
})
test('deterministic', () => {
const signature1 = Ed25519.sign({ payload, privateKey })
const signature2 = Ed25519.sign({ payload, privateKey })
expect(signature1).toBe(signature2)
})
test('known test vector', () => {
// From RFC 8032 test vectors
const testPrivateKey =
'0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'
const testPayload = '0x' // empty message
const signature = Ed25519.sign({
payload: testPayload,
privateKey: testPrivateKey,
})
expect(signature).toBe(
'0xe5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b',
)
})
describe('integration', () => {
test('sign and verify cycle', () => {
const { privateKey, publicKey } = Ed25519.createKeyPair()
const payload = '0x48656c6c6f2c20576f726c6421'
const signature = Ed25519.sign({ payload, privateKey })
const isValid = Ed25519.verify({ payload, publicKey, signature })
expect(isValid).toBe(true)
})
test('sign and verify cycle with Bytes', () => {
const { privateKey, publicKey } = Ed25519.createKeyPair({ as: 'Bytes' })
const payload = Bytes.fromString('Hello, World!')
const signature = Ed25519.sign({ payload, privateKey, as: 'Bytes' })
const isValid = Ed25519.verify({ payload, publicKey, signature })
expect(isValid).toBe(true)
})
test('multiple signatures with same key', () => {
const { privateKey, publicKey } = Ed25519.createKeyPair()
const payloads = [
'0x48656c6c6f',
'0x576f726c64',
'0x546573744d657373616765',
] as const
for (const payload of payloads) {
const signature = Ed25519.sign({ payload, privateKey })
const isValid = Ed25519.verify({ payload, publicKey, signature })
expect(isValid).toBe(true)
}
})
})
})
describe('verify', () => {
const privateKey =
'0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'
const publicKey =
'0xd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'
const payload = '0xdeadbeef'
test('valid signature', () => {
const signature = Ed25519.sign({ payload, privateKey })
const isValid = Ed25519.verify({ payload, publicKey, signature })
expect(isValid).toBe(true)
})
test('invalid signature', () => {
const signature = ('0x' + '00'.repeat(64)) as Hex.Hex
const isValid = Ed25519.verify({ payload, publicKey, signature })
expect(isValid).toBe(false)
})
test('wrong payload', () => {
const signature = Ed25519.sign({ payload, privateKey })
const isValid = Ed25519.verify({
payload: '0xcafebabe',
publicKey,
signature,
})
expect(isValid).toBe(false)
})
test('wrong public key', () => {
const signature = Ed25519.sign({ payload, privateKey })
const wrongPublicKey = ('0x' + '11'.repeat(32)) as Hex.Hex
const isValid = Ed25519.verify({
payload,
publicKey: wrongPublicKey,
signature,
})
expect(isValid).toBe(false)
})
test('with Bytes payload', () => {
const payloadBytes = Bytes.fromHex(payload)
const signature = Ed25519.sign({ payload, privateKey })
const isValid = Ed25519.verify({
payload: payloadBytes,
publicKey,
signature,
})
expect(isValid).toBe(true)
})
test('with Bytes public key', () => {
const publicKeyBytes = Bytes.fromHex(publicKey)
const signature = Ed25519.sign({ payload, privateKey })
const isValid = Ed25519.verify({
payload,
publicKey: publicKeyBytes,
signature,
})
expect(isValid).toBe(true)
})
test('with Bytes signature', () => {
const signatureHex = Ed25519.sign({ payload, privateKey })
const signatureBytes = Bytes.fromHex(signatureHex)
const isValid = Ed25519.verify({
payload,
publicKey,
signature: signatureBytes,
})
expect(isValid).toBe(true)
})
test('with all Bytes', () => {
const payloadBytes = Bytes.fromHex(payload)
const publicKeyBytes = Bytes.fromHex(publicKey)
const signatureBytes = Ed25519.sign({
payload: payloadBytes,
privateKey,
as: 'Bytes',
})
const isValid = Ed25519.verify({
payload: payloadBytes,
publicKey: publicKeyBytes,
signature: signatureBytes,
})
expect(isValid).toBe(true)
})
test('known test vector', () => {
// From RFC 8032 test vectors
const testPublicKey =
'0xd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'
const testPayload = '0x' // empty message
const testSignature =
'0xe5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b'
const isValid = Ed25519.verify({
payload: testPayload,
publicKey: testPublicKey,
signature: testSignature,
})
expect(isValid).toBe(true)
})
})
describe('toX25519PublicKey', () => {
test('default (Hex)', () => {
const { publicKey } = Ed25519.createKeyPair()
const x25519PublicKey = Ed25519.toX25519PublicKey({ publicKey })
expect(typeof x25519PublicKey).toBe('string')
expect(x25519PublicKey).toMatch(/^0x[0-9a-fA-F]{64}$/)
})
test('with as: Bytes', () => {
const { publicKey } = Ed25519.createKeyPair()
const x25519PublicKey = Ed25519.toX25519PublicKey({
publicKey,
as: 'Bytes',
})
expect(x25519PublicKey).toBeInstanceOf(Uint8Array)
expect(x25519PublicKey).toHaveLength(32)
})
test('with Bytes public key', () => {
const { publicKey } = Ed25519.createKeyPair({ as: 'Bytes' })
const x25519PublicKey = Ed25519.toX25519PublicKey({ publicKey })
expect(typeof x25519PublicKey).toBe('string')
expect(x25519PublicKey).toMatch(/^0x[0-9a-fA-F]{64}$/)
})
test('deterministic', () => {
const { publicKey } = Ed25519.createKeyPair()
const a = Ed25519.toX25519PublicKey({ publicKey })
const b = Ed25519.toX25519PublicKey({ publicKey })
expect(a).toBe(b)
})
test('different from ed25519 public key', () => {
const { publicKey } = Ed25519.createKeyPair()
const x25519PublicKey = Ed25519.toX25519PublicKey({ publicKey })
expect(x25519PublicKey).not.toBe(publicKey)
})
})
describe('toX25519PrivateKey', () => {
test('default (Hex)', () => {
const { privateKey } = Ed25519.createKeyPair()
const x25519PrivateKey = Ed25519.toX25519PrivateKey({ privateKey })
expect(typeof x25519PrivateKey).toBe('string')
expect(x25519PrivateKey).toMatch(/^0x[0-9a-fA-F]{64}$/)
})
test('with as: Bytes', () => {
const { privateKey } = Ed25519.createKeyPair()
const x25519PrivateKey = Ed25519.toX25519PrivateKey({
privateKey,
as: 'Bytes',
})
expect(x25519PrivateKey).toBeInstanceOf(Uint8Array)
expect(x25519PrivateKey).toHaveLength(32)
})
test('with Bytes private key', () => {
const { privateKey } = Ed25519.createKeyPair({ as: 'Bytes' })
const x25519PrivateKey = Ed25519.toX25519PrivateKey({ privateKey })
expect(typeof x25519PrivateKey).toBe('string')
expect(x25519PrivateKey).toMatch(/^0x[0-9a-fA-F]{64}$/)
})
test('deterministic', () => {
const { privateKey } = Ed25519.createKeyPair()
const a = Ed25519.toX25519PrivateKey({ privateKey })
const b = Ed25519.toX25519PrivateKey({ privateKey })
expect(a).toBe(b)
})
})
describe('toX25519 integration', () => {
test('converted keys produce valid X25519 shared secret', () => {
const alice = Ed25519.createKeyPair()
const bob = Ed25519.createKeyPair()
const aliceX25519Priv = Ed25519.toX25519PrivateKey({
privateKey: alice.privateKey,
})
const bobX25519Pub = Ed25519.toX25519PublicKey({
publicKey: bob.publicKey,
})
const bobX25519Priv = Ed25519.toX25519PrivateKey({
privateKey: bob.privateKey,
})
const aliceX25519Pub = Ed25519.toX25519PublicKey({
publicKey: alice.publicKey,
})
const sharedA = X25519.getSharedSecret({
privateKey: aliceX25519Priv,
publicKey: bobX25519Pub,
})
const sharedB = X25519.getSharedSecret({
privateKey: bobX25519Priv,
publicKey: aliceX25519Pub,
})
expect(sharedA).toBe(sharedB)
})
})
describe('noble export', () => {
test('exports noble curves ed25519', () => {
expect(Ed25519.noble).toBeDefined()
expect(Ed25519.noble.sign).toBeDefined()
expect(Ed25519.noble.verify).toBeDefined()
expect(Ed25519.noble.getPublicKey).toBeDefined()
})
})