|
| 1 | +import sodium from 'libsodium-wrappers'; |
| 2 | +import { StealthKeyConverter } from '../src/crypto/stealth-keys'; |
| 3 | + |
| 4 | +function hexToU8(hex: string): Uint8Array { |
| 5 | + const clean = hex.replace(/\s+/g, ''); |
| 6 | + const out = new Uint8Array(clean.length / 2); |
| 7 | + for (let i = 0; i < out.length; i++) { |
| 8 | + out[i] = parseInt(clean.substr(i * 2, 2), 16); |
| 9 | + } |
| 10 | + return out; |
| 11 | +} |
| 12 | + |
| 13 | +function u8ToHex(u: Uint8Array): string { |
| 14 | + return Array.from(u).map(b => b.toString(16).padStart(2, '0')).join(''); |
| 15 | +} |
| 16 | + |
| 17 | +describe('StealthKeyConverter + RFC7748 vectors', () => { |
| 18 | + beforeAll(async () => { |
| 19 | + await sodium.ready; |
| 20 | + }); |
| 21 | + |
| 22 | + test('RFC7748 X25519 Alice/Bob vector', async () => { |
| 23 | + const aHex = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a'; |
| 24 | + const bHex = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb'; |
| 25 | + const Aexp = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a'; |
| 26 | + const Bexp = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f'; |
| 27 | + const Kexp = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742'; |
| 28 | + |
| 29 | + const a = hexToU8(aHex); |
| 30 | + const b = hexToU8(bHex); |
| 31 | + const basepoint = new Uint8Array(32); |
| 32 | + basepoint[0] = 9; |
| 33 | + |
| 34 | + const A = sodium.crypto_scalarmult(a, basepoint); |
| 35 | + const B = sodium.crypto_scalarmult(b, basepoint); |
| 36 | + |
| 37 | + expect(u8ToHex(A)).toBe(Aexp); |
| 38 | + expect(u8ToHex(B)).toBe(Bexp); |
| 39 | + |
| 40 | + const K1 = sodium.crypto_scalarmult(a, B); |
| 41 | + const K2 = sodium.crypto_scalarmult(b, A); |
| 42 | + |
| 43 | + expect(u8ToHex(K1)).toBe(Kexp); |
| 44 | + expect(u8ToHex(K2)).toBe(Kexp); |
| 45 | + }); |
| 46 | + |
| 47 | + test('Ed25519 -> Curve25519 conversion round-trip (shared secret equality)', async () => { |
| 48 | + // Deterministic seeds for reproducibility |
| 49 | + const seedA = new Uint8Array(32); |
| 50 | + const seedB = new Uint8Array(32); |
| 51 | + for (let i = 0; i < 32; i++) { |
| 52 | + seedA[i] = i; |
| 53 | + seedB[i] = 255 - i; |
| 54 | + } |
| 55 | + |
| 56 | + // Create Ed25519 keypairs |
| 57 | + const kpA = sodium.crypto_sign_seed_keypair(seedA); |
| 58 | + const kpB = sodium.crypto_sign_seed_keypair(seedB); |
| 59 | + |
| 60 | + // Convert to Curve25519 keys |
| 61 | + const curveSkA = await StealthKeyConverter.ed25519SecretToCurve25519(kpA.privateKey); |
| 62 | + const curvePkA = await StealthKeyConverter.ed25519PublicKeyToCurve25519(kpA.publicKey); |
| 63 | + |
| 64 | + const curveSkB = await StealthKeyConverter.ed25519SecretToCurve25519(kpB.privateKey); |
| 65 | + const curvePkB = await StealthKeyConverter.ed25519PublicKeyToCurve25519(kpB.publicKey); |
| 66 | + |
| 67 | + const shared1 = await StealthKeyConverter.deriveSharedSecret(curveSkA, curvePkB); |
| 68 | + const shared2 = await StealthKeyConverter.deriveSharedSecret(curveSkB, curvePkA); |
| 69 | + |
| 70 | + expect(u8ToHex(shared1)).toBe(u8ToHex(shared2)); |
| 71 | + // Also ensure non-zero |
| 72 | + expect(u8ToHex(shared1)).not.toBe('0000000000000000000000000000000000000000000000000000000000000000'); |
| 73 | + }); |
| 74 | +}); |
0 commit comments