diff --git a/src/algorithms.js b/src/algorithms.js index 4ee496b..6ad344f 100644 --- a/src/algorithms.js +++ b/src/algorithms.js @@ -1,87 +1,234 @@ -// export class DigitalSignature { -// /** -// * export class to perform digital signature and verification using the ECDSA scheme. -// * @param {number} private_key - The private key used for generating a signature. -// * @param {string} curve_name - The name of the elliptic curve to use. Defaults to 'secp192k1'. -// */ -// constructor(private_key, curve_name = 'secp192k1') { -// this.private_key = private_key -// this.curve_name = curve_name -// this._curve = null // Placeholder for the elliptic curve object -// this._public_key = null // Placeholder for the public key -// this._signature_cache = new Map() // Cache for generated signatures -// } - -// /** -// * Retrieves the elliptic curve associated with this `DigitalSignature` instance. -// * The `curve_name` attribute is used to fetch the corresponding elliptic curve object. -// * @returns {EllipticCurve} The elliptic curve object used for ECDSA operations. -// */ -// get curve() { -// if (!this._curve) { -// this._curve = get_curve(this.curve_name) -// } -// return this._curve -// } - -// /** -// * Computes and returns the public key corresponding to the private key. -// * @returns {Point} The public key point on the elliptic curve associated with this instance. -// */ -// get public_key() { -// if (!this._public_key) { -// this._public_key = this.curve.multiply_point( -// this.private_key, -// this.curve.G, -// ) -// } -// return this._public_key -// } - -// /** -// * Generates an ECDSA signature for a given message hash using the private key. -// * @param {number} message_hash - The hash of the message to be signed. -// * @returns {Array} The ECDSA signature as an array containing two integers [r, s]. -// */ -// generate_signature(message_hash) { -// if (!this._signature_cache.has(message_hash)) { -// let r = 0, -// s = 0 -// while (r === 0 || s === 0) { -// const k = bigInt.randBetween(1, this.curve.n.minus(1)) -// const p = this.curve.multiply_point(k, this.curve.G) -// r = p.x.mod(this.curve.n) -// s = message_hash -// .plus(r.times(this.private_key)) -// .times(k.modInv(this.curve.n)) -// .mod(this.curve.n) -// } -// this._signature_cache.set(message_hash, [r, s]) -// } -// return this._signature_cache.get(message_hash) -// } - -// /** -// * Verifies the authenticity of an ECDSA signature against a public key and message hash. -// * @param {Point} public_key - The public key associated with the signer. -// * @param {number} message_hash - The hash of the message that was supposedly signed. -// * @param {number} r - The first component (r) of the ECDSA signature. -// * @param {number} s - The second component (s) of the ECDSA signature. -// * @returns {boolean} True if the signature is valid, false otherwise. -// */ -// verify_signature(public_key, message_hash, r, s) { -// if (!(1 <= r < this.curve.n && 1 <= s < this.curve.n)) { -// throw new Error('r or s are not in the valid range [1, curve order - 1].') -// } - -// const w = s.modInv(this.curve.n) -// const u_1 = message_hash.times(w).mod(this.curve.n) -// const u_2 = r.times(w).mod(this.curve.n) -// const p = this.curve.add_points( -// this.curve.multiply_point(u_1, this.curve.G), -// this.curve.multiply_point(u_2, public_key), -// ) -// const v = p.x.mod(this.curve.n) -// return v.eq(r) -// } -// } +import { get as get_curve } from './curves' +import { Point } from './core' +// const { Worker, isMainThread, parentPort } = require(''); +// const { Point } = require('./ecutils/core'); // Assuming the Point class is exported from ecutils/core + +export class DigitalSignature { + /** + * export class to perform digital signature and verification using the ECDSA scheme. + * @param {number} private_key - The private key used for generating a signature. + * @param {string} curve_name - The name of the elliptic curve to use. Defaults to 'secp192k1'. + */ + constructor(private_key, curve_name = 'secp192k1') { + this.private_key = private_key + this.curve_name = curve_name + this._curve = null // Placeholder for the elliptic curve object + this._public_key = null // Placeholder for the public key + this._signature_cache = new Map() // Cache for generated signatures + } + + /** + * Retrieves the elliptic curve associated with this `DigitalSignature` instance. + * The `curve_name` attribute is used to fetch the corresponding elliptic curve object. + * @returns {EllipticCurve} The elliptic curve object used for ECDSA operations. + */ + get curve() { + if (!this._curve) { + this._curve = get_curve(this.curve_name) + } + return this._curve + } + + /** + * Computes and returns the public key corresponding to the private key. + * @returns {Point} The public key point on the elliptic curve associated with this instance. + */ + get public_key() { + if (!this._public_key) { + this._public_key = this.curve.multiply_point( + this.private_key, + this.curve.G, + ) + } + return this._public_key + } + + /** + * Generates an ECDSA signature for a given message hash using the private key. + * @param {number} message_hash - The hash of the message to be signed. + * @returns {Array} The ECDSA signature as an array containing two integers [r, s]. + */ + generate_signature(message_hash) { + const getRandomInt = (min, max) => { + min = BigInt(min) + max = BigInt(max) + const range = max - min + BigInt(1) + const randomBigInt = BigInt( + Math.floor(Number(Math.random()) * Number(range)), + ) + return BigInt((randomBigInt + min).toString()) + } + + if (!this._signature_cache.has(message_hash)) { + let r = 0, + s = 0 + while (r === 0 || s === 0) { + const k = getRandomInt(1, this.curve.n - 1n) + const p = this.curve.multiply_point(k, this.curve.G) + r = this.curve.modulus(p.x, this.curve.n) + s = this.curve.modulus( + (message_hash + r * this.private_key) * + this.curve.extended_gcd(k, this.curve.n)[1], + this.curve.n, + ) + } + this._signature_cache.set(message_hash, [BigInt(r), BigInt(s)]) + } + return this._signature_cache.get(message_hash) + } + + /** + * Verifies the authenticity of an ECDSA signature against a public key and message hash. + * @param {Point} public_key - The public key associated with the signer. + * @param {number} message_hash - The hash of the message that was supposedly signed. + * @param {number} r - The first component (r) of the ECDSA signature. + * @param {number} s - The second component (s) of the ECDSA signature. + * @returns {boolean} True if the signature is valid, false otherwise. + */ + verify_signature(public_key, message_hash, r, s) { + if (!(1 <= r < this.curve.n && 1 <= s < this.curve.n)) { + throw new Error('r or s are not in the valid range [1, curve order - 1].') + } + + const w = this.curve.extended_gcd(s, this.curve.n)[1] + const u_1 = this.curve.modulus(message_hash * w, this.curve.n) + const u_2 = this.curve.modulus(r * w, this.curve.n) + const p = this.curve.add_points( + this.curve.multiply_point(u_1, this.curve.G), + this.curve.multiply_point(u_2, public_key), + ) + const v = this.curve.modulus(p.x, this.curve.n) + return v === r + } +} + +export class Koblitz { + /** + * A class implementing the Koblitz method for encoding and decoding messages using elliptic curves. + * @param {string} curve_name - The name of the elliptic curve to be used. Defaults to 'secp521r1'. + */ + constructor(curve_name = 'secp521r1') { + this.curve_name = curve_name + this._curve = null // Placeholder for the elliptic curve object + } + + /** + * Lazy-loads and returns the elliptic curve used for encoding and decoding. + * @returns {EllipticCurve} An instance of `EllipticCurve` associated with the specified `curve_name`. + */ + get curve() { + if (!this._curve) { + this._curve = get_curve(this.curve_name) + } + return this._curve + } + + /** + * Encodes a textual message to a curve point using the Koblitz method. + * @param {string} message - The message to be encoded. + * @param {number} alphabet_size - The size of the alphabet/character set to consider for encoding. + * @returns {Array} A tuple with the encoded point on the elliptic curve and an auxiliary value j used in the encoding process. + */ + encode(message, alphabet_size = 256n, lengthy = false) { + if (alphabet_size != 256n && alphabet_size != 65536n) { + throw new Error('Alphabet size not supported') + } + + const bigIntPow = (base, exponent, modulus) => { + if (exponent === 0n) return 1n + let result = 1n + let currentBase = this.curve.modulus(base, modulus) + while (exponent > 0n) { + if (this.curve.modulus(exponent, 2n) === 1n) { + result = this.curve.modulus(result * currentBase, modulus) + } + currentBase = this.curve.modulus(currentBase * currentBase, modulus) + exponent = exponent / 2n + } + return result + } + + let size + if (alphabet_size == 256n) { + size = 64 + } else { + size = 32 + } + + if (!lengthy) { + message = message.slice(0, size) + // Convert the string message to a single large integer + let message_decimal = BigInt(0) + for (let i = 0; i < Math.min(message.length, size); i++) { + message_decimal += + BigInt(message.charCodeAt(i)) * + BigInt(Math.pow(Number(alphabet_size), i)) + } + // Search for a valid curve point using the Koblitz method + const d = 100n + let x, y, j + for (j = 1n; j < d - 1n; j++) { + x = this.curve.modulus(d * message_decimal + j, this.curve.p) + let s = this.curve.modulus( + x ** 3n + this.curve.a * x + this.curve.b, + this.curve.p, + ) + if (s === bigIntPow(s, (this.curve.p + 1n) / 2n, this.curve.p)) { + y = bigIntPow(s, (this.curve.p + 1n) / 4n, this.curve.p) + if ( + this.curve.is_point_on_curve(new Point(x, y)) && + this.decode(new Point(x, y), j, alphabet_size, false) == message + ) { + break + } + } + } + return [new Point(x, y), j] + } + let encoded_messages = [] + + for (let i = 0; i < message.length; i += size) { + encoded_messages.push( + this.encode(message.slice(i, i + size), alphabet_size), + ) + } + return encoded_messages + } + + /** + * Decodes a point on an elliptic curve to a textual message using the Koblitz method. + * @param {Point} point - The encoded point on the elliptic curve. + * @param {number} j - The auxiliary value 'j' used during the encoding process. + * @param {number} alphabet_size - The size of the alphabet/character set considered for decoding. + * @returns {string} The decoded textual message. + */ + decode(encoded, j = 0n, alphabet_size = 256n, lengthy = false) { + // Calculate the original large integer from the point and 'j' + if (alphabet_size != 256n && alphabet_size != 65536n) { + throw new Error('Alphabet size not supported') + } + + if (!lengthy) { + const d = 100n + let message_decimal = (encoded.x - j) / d + + const characters = [] + + while (message_decimal !== 0n) { + characters.push( + String.fromCharCode( + Number(this.curve.modulus(message_decimal, alphabet_size)), + ), + ) + message_decimal = message_decimal / BigInt(alphabet_size) + } + return characters.join('') + } + let characters = [] + for (let i = 0; i < encoded.length; i++) { + let enc = encoded[i] + characters.push(this.decode(enc[0], enc[1], alphabet_size, false)) + } + return characters.join('') + } +} diff --git a/src/algorithms.test.js b/src/algorithms.test.js new file mode 100644 index 0000000..b0949d5 --- /dev/null +++ b/src/algorithms.test.js @@ -0,0 +1,77 @@ +import { test, expect } from '@jest/globals' +import { DigitalSignature, Koblitz } from './algorithms' + +let ds + +const private_key = BigInt(123456789) +ds = new DigitalSignature(private_key) + +test('digital-signature: generate and verify signature', () => { + const message_hash = BigInt(545454445644654n) + const signature = ds.generate_signature(message_hash) + const r = signature[0] + const s = signature[1] + + const is_valid = ds.verify_signature(ds.public_key, message_hash, r, s) + expect(is_valid).toBe(true) +}) + +test('digital-signature: verify signature with invalid inputs', () => { + const message_hash = BigInt(545454445644654n) + // Choose invalid r and s values (outside the range [1, n-1]) + const invalid_r = ds.curve.n + const invalid_s = BigInt(0) + // Check that the appropriate exception is raised for invalid r and s + expect(() => { + ds.verify_signature(ds.public_key, message_hash, invalid_r, invalid_s) + }).toThrowError() +}) + +const encoder = new Koblitz('secp521r1') +const decoder = new Koblitz('secp521r1') + +test('koblitz: encode and decode ascii', () => { + const message = 'Hello, EC!' + const encode = encoder.encode(message, 2n ** 8n) + const encoded_point = encode[0] + const j = encode[1] + const decoded_message = decoder.decode(encoded_point, j, 2n ** 8n) + expect(decoded_message).toBe(message) +}) + +test('koblitz: encode and decode lengthy ascii', () => { + const message = `Morbi nibh dolor, tempus vel arcu eget, sagittis scelerisque mi. Curabitur aliquet tempus odio, vitae rutrum tortor mollis sit amet. Aliquam finibus sapien eu urna efficitur cursus. Nullam ultricies justo et magna molestie, non tincidunt lacus fringilla. Nulla facilisi. Nullam commodo aliquam placerat. Vivamus imperdiet diam id tincidunt ultrices. Nulla vitae odio massa. Nullam rhoncus scelerisque quam vel scelerisque. Duis ac diam quam. Ut volutpat, tellus a vehicula aliquet, ipsum velit maximus ligula, vel fringilla urna libero vel orci. Nulla iaculis tristique sapien in faucibus. Nullam euismod hendrerit sapien, id pulvinar ipsum dictum non. + Suspendisse aliquet leo non vulputate lacinia. Mauris sed malesuada sem, sit amet cursus erat. Donec ac cursus dui. Sed at facilisis arcu. Phasellus at pulvinar lorem, tristique fermentum mauris. Donec commodo consequat eros, ut facilisis risus. Donec eget nunc accumsan, scelerisque lectus non, lobortis urna. Mauris non volutpat enim. Curabitur dignissim lorem lacus, elementum luctus odio bibendum at. Praesent rhoncus magna metus, ut vehicula est sodales a. Donec euismod tristique nisl quis sagittis. + Nam vehicula magna bibendum, posuere enim tempor, ultricies lacus. Vivamus in lorem magna. Nam interdum fringilla laoreet. Nullam convallis dolor quis urna facilisis faucibus a eget mi. Aenean volutpat leo sit amet lorem facilisis malesuada. Suspendisse nec fermentum ex, ut blandit dolor. Aenean id erat sed magna dignissim auctor. Vestibulum a nisi et augue tempus ornare. Maecenas non faucibus purus. Morbi lorem ante, venenatis viverra est a, ultricies auctor enim. + Cras pellentesque porttitor enim, sit amet vulputate sapien ultricies at. Integer ac mattis ante, at suscipit mi. Sed sed est viverra ligula vestibulum rutrum. In interdum ante in mauris posuere vulputate. Cras et neque vel sapien fermentum luctus et eu quam. Etiam vel risus est. Vestibulum vestibulum nisi sed commodo feugiat. Nunc a condimentum quam. Curabitur eget urna libero. Cras ultrices fringilla erat non consequat. In tristique vulputate scelerisque. + Praesent sit amet nunc sit amet tortor condimentum vestibulum sed ac ex. Aliquam sit amet lacinia enim, sed lacinia felis. Cras maximus ornare lorem, ut tempor orci luctus in. Curabitur rutrum ligula eget turpis posuere suscipit. Duis varius ex magna, sed scelerisque odio sagittis non. Ut ut gravida magna, a ultricies nibh. Praesent porttitor dapibus leo vitae rutrum. Quisque gravida finibus lorem. In eu cursus mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Integer in metus convallis, efficitur nulla vel, sollicitudin velit. Suspendisse mattis sit amet odio non volutpat. + Vivamus vulputate quam at hendrerit euismod. Phasellus nec vehicula nisi. Nam at diam libero. Aenean porttitor gravida tortor, eu tristique risus aliquet in. Maecenas eros libero, faucibus vel ornare ut, molestie at massa. Pellentesque semper posuere urna, et commodo nisl porttitor eget. Duis a augue gravida, rutrum lacus eget, fringilla lacus. Curabitur scelerisque rutrum pulvinar. Aliquam sagittis ipsum eget felis lacinia, in aliquam dui congue. Nulla consectetur sit amet dui ut euismod. + Aliquam posuere faucibus semper. Donec ullamcorper dui egestas risus rhoncus mollis. Nunc id metus pulvinar, tempor justo in, ornare tortor. Aenean id venenatis felis, ut euismod nisi. Quisque a ante tristique, blandit risus nec, scelerisque arcu. Praesent placerat efficitur turpis, vitae mattis massa sollicitudin vel. Integer ut lectus efficitur lectus eleifend consectetur et sed tellus. Morbi elementum, nulla sed pulvinar tincidunt, massa nulla sagittis orci, ultricies tincidunt felis arcu commodo turpis. Morbi sollicitudin magna dui, vel iaculis metus ullamcorper sit amet. Donec sit amet volutpat est, et hendrerit massa. Aenean aliquam feugiat erat, ac vehicula mauris dictum vel. Suspendisse aliquet dui ultrices mi ultricies, vel dignissim lectus feugiat. Curabitur sodales nisi eu tellus pharetra, ac dictum tellus pellentesque. Donec egestas sem ac condimentum placerat. + Sed vel eleifend augue. Vestibulum lacinia pellentesque nibh, a scelerisque ante. Sed accumsan, orci vitae commodo consectetur, lorem neque lacinia nisl, a interdum mauris dolor rhoncus libero. Praesent malesuada consectetur semper. Maecenas cursus, diam quis ultrices volutpat, nisi neque imperdiet dolor, non porttitor justo elit id eros. Sed sodales tempus arcu, eget tempor dolor aliquam et. Quisque id tortor a arcu viverra cursus. Suspendisse elementum efficitur ante, id facilisis est consectetur pretium. Nam cursus turpis eu aliquam tempus. Morbi egestas risus sit amet lacinia elementum. Nunc tincidunt sit amet odio at molestie. Maecenas pretium consequat fermentum.` + const encoded_points = encoder.encode(message, 2n ** 8n, true) + const decoded_message = decoder.decode(encoded_points, 0n, 2n ** 8n, true) + expect(decoded_message).toBe(message) +}) + +test('koblitz: encode and decode unicode', () => { + const message = 'Hello, EC!' + const encode = encoder.encode(message, 2n ** 16n) + const encoded_point = encode[0] + const j = encode[1] + const decoded_message = decoder.decode(encoded_point, j, 2n ** 16n) + expect(decoded_message).toBe(message) +}) + +test('koblitz: encode and decode lengthy unicode', () => { + const message = `Morbi nibh dolor, tempus vel arcu eget, sagittis scelerisque mi. Curabitur aliquet tempus odio, vitae rutrum tortor mollis sit amet. Aliquam finibus sapien eu urna efficitur cursus. Nullam ultricies justo et magna molestie, non tincidunt lacus fringilla. Nulla facilisi. Nullam commodo aliquam placerat. Vivamus imperdiet diam id tincidunt ultrices. Nulla vitae odio massa. Nullam rhoncus scelerisque quam vel scelerisque. Duis ac diam quam. Ut volutpat, tellus a vehicula aliquet, ipsum velit maximus ligula, vel fringilla urna libero vel orci. Nulla iaculis tristique sapien in faucibus. Nullam euismod hendrerit sapien, id pulvinar ipsum dictum non. + Suspendisse aliquet leo non vulputate lacinia. Mauris sed malesuada sem, sit amet cursus erat. Donec ac cursus dui. Sed at facilisis arcu. Phasellus at pulvinar lorem, tristique fermentum mauris. Donec commodo consequat eros, ut facilisis risus. Donec eget nunc accumsan, scelerisque lectus non, lobortis urna. Mauris non volutpat enim. Curabitur dignissim lorem lacus, elementum luctus odio bibendum at. Praesent rhoncus magna metus, ut vehicula est sodales a. Donec euismod tristique nisl quis sagittis. + Nam vehicula magna bibendum, posuere enim tempor, ultricies lacus. Vivamus in lorem magna. Nam interdum fringilla laoreet. Nullam convallis dolor quis urna facilisis faucibus a eget mi. Aenean volutpat leo sit amet lorem facilisis malesuada. Suspendisse nec fermentum ex, ut blandit dolor. Aenean id erat sed magna dignissim auctor. Vestibulum a nisi et augue tempus ornare. Maecenas non faucibus purus. Morbi lorem ante, venenatis viverra est a, ultricies auctor enim. + Cras pellentesque porttitor enim, sit amet vulputate sapien ultricies at. Integer ac mattis ante, at suscipit mi. Sed sed est viverra ligula vestibulum rutrum. In interdum ante in mauris posuere vulputate. Cras et neque vel sapien fermentum luctus et eu quam. Etiam vel risus est. Vestibulum vestibulum nisi sed commodo feugiat. Nunc a condimentum quam. Curabitur eget urna libero. Cras ultrices fringilla erat non consequat. In tristique vulputate scelerisque. + Praesent sit amet nunc sit amet tortor condimentum vestibulum sed ac ex. Aliquam sit amet lacinia enim, sed lacinia felis. Cras maximus ornare lorem, ut tempor orci luctus in. Curabitur rutrum ligula eget turpis posuere suscipit. Duis varius ex magna, sed scelerisque odio sagittis non. Ut ut gravida magna, a ultricies nibh. Praesent porttitor dapibus leo vitae rutrum. Quisque gravida finibus lorem. In eu cursus mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Integer in metus convallis, efficitur nulla vel, sollicitudin velit. Suspendisse mattis sit amet odio non volutpat. + Vivamus vulputate quam at hendrerit euismod. Phasellus nec vehicula nisi. Nam at diam libero. Aenean porttitor gravida tortor, eu tristique risus aliquet in. Maecenas eros libero, faucibus vel ornare ut, molestie at massa. Pellentesque semper posuere urna, et commodo nisl porttitor eget. Duis a augue gravida, rutrum lacus eget, fringilla lacus. Curabitur scelerisque rutrum pulvinar. Aliquam sagittis ipsum eget felis lacinia, in aliquam dui congue. Nulla consectetur sit amet dui ut euismod. + Aliquam posuere faucibus semper. Donec ullamcorper dui egestas risus rhoncus mollis. Nunc id metus pulvinar, tempor justo in, ornare tortor. Aenean id venenatis felis, ut euismod nisi. Quisque a ante tristique, blandit risus nec, scelerisque arcu. Praesent placerat efficitur turpis, vitae mattis massa sollicitudin vel. Integer ut lectus efficitur lectus eleifend consectetur et sed tellus. Morbi elementum, nulla sed pulvinar tincidunt, massa nulla sagittis orci, ultricies tincidunt felis arcu commodo turpis. Morbi sollicitudin magna dui, vel iaculis metus ullamcorper sit amet. Donec sit amet volutpat est, et hendrerit massa. Aenean aliquam feugiat erat, ac vehicula mauris dictum vel. Suspendisse aliquet dui ultrices mi ultricies, vel dignissim lectus feugiat. Curabitur sodales nisi eu tellus pharetra, ac dictum tellus pellentesque. Donec egestas sem ac condimentum placerat. + Sed vel eleifend augue. Vestibulum lacinia pellentesque nibh, a scelerisque ante. Sed accumsan, orci vitae commodo consectetur, lorem neque lacinia nisl, a interdum mauris dolor rhoncus libero. Praesent malesuada consectetur semper. Maecenas cursus, diam quis ultrices volutpat, nisi neque imperdiet dolor, non porttitor justo elit id eros. Sed sodales tempus arcu, eget tempor dolor aliquam et. Quisque id tortor a arcu viverra cursus. Suspendisse elementum efficitur ante, id facilisis est consectetur pretium. Nam cursus turpis eu aliquam tempus. Morbi egestas risus sit amet lacinia elementum. Nunc tincidunt sit amet odio at molestie. Maecenas pretium consequat fermentum.` + const encoded_points = encoder.encode(message, 2n ** 16n, true) + const decoded_message = decoder.decode(encoded_points, 0n, 2n ** 16n, true) + expect(decoded_message).toBe(message) +}) diff --git a/src/curves.test.js b/src/curves.test.js new file mode 100644 index 0000000..0191d01 --- /dev/null +++ b/src/curves.test.js @@ -0,0 +1,16 @@ +import { test, expect } from '@jest/globals' +import { get, secp256k1 } from './curves' + +test('get valid curve', () => { + const curveName = 'secp256k1' + const expectedCurve = secp256k1 + const curve = get(curveName) + expect(curve).toBe(expectedCurve) +}) + +test('get invalid curve', () => { + const curveName = 'invalidCurveName' + expect(() => { + get(curveName) + }).toThrowError() +}) diff --git a/src/protocols.js b/src/protocols.js index 7d90cbb..09eec6b 100644 --- a/src/protocols.js +++ b/src/protocols.js @@ -1,144 +1,149 @@ -// export class DiffieHellman { -// /** -// * export class to perform Diffie-Hellman key exchange using elliptic curves. -// * @param {number} private_key - The private key of the user. -// * @param {string} curve_name - Name of the elliptic curve to be used. Defaults to 'secp192k1'. -// */ -// constructor(private_key, curve_name = 'secp192k1') { -// this.private_key = private_key -// this.curve_name = curve_name -// this._curve = null // Placeholder for the elliptic curve object -// this._public_key = null // Placeholder for the public key -// this._shared_secret_cache = new Map() // Cache for shared secrets -// } +import { get as get_curve } from './curves' -// /** -// * Retrieves the elliptic curve associated with this `DiffieHellman` instance. -// * The `curve_name` attribute is used to fetch the corresponding elliptic curve object. -// * @returns {EllipticCurve} The elliptic curve object used for ECDSA operations. -// */ -// get curve() { -// if (!this._curve) { -// this._curve = get_curve(this.curve_name) -// } -// return this._curve -// } +export class DiffieHellman { + /** + * export class to perform Diffie-Hellman key exchange using elliptic curves. + * @param {number} private_key - The private key of the user. + * @param {string} curve_name - Name of the elliptic curve to be used. Defaults to 'secp192k1'. + */ + constructor(private_key, curve_name = 'secp192k1') { + this.private_key = private_key + this.curve_name = curve_name + this._curve = null // Placeholder for the elliptic curve object + this._public_key = null // Placeholder for the public key + this._shared_secret_cache = new Map() // Cache for shared secrets + } -// /** -// * Computes and returns the public key corresponding to the private key. -// * @returns {Point} The public key point on the elliptic curve associated with this instance. -// */ -// get public_key() { -// if (!this._public_key) { -// this._public_key = this.curve.multiply_point( -// this.private_key, -// this.curve.G, -// ) -// } -// return this._public_key -// } + /** + * Retrieves the elliptic curve associated with this `DiffieHellman` instance. + * The `curve_name` attribute is used to fetch the corresponding elliptic curve object. + * @returns {EllipticCurve} The elliptic curve object used for ECDSA operations. + */ + get curve() { + if (!this._curve) { + this._curve = get_curve(this.curve_name) + } + return this._curve + } -// /** -// * Computes the shared secret using the private key and the other party's public key. -// * @param {Point} other_public_key - The other party's public key. -// * @returns {Point} The resulting shared secret as a point on the elliptic curve. -// */ -// compute_shared_secret(other_public_key) { -// const cacheKey = `${other_public_key.x},${other_public_key.y}` -// if (!this._shared_secret_cache.has(cacheKey)) { -// const shared_secret = this.curve.multiply_point( -// this.private_key, -// other_public_key, -// ) -// this._shared_secret_cache.set(cacheKey, shared_secret) -// } -// return this._shared_secret_cache.get(cacheKey) -// } -// } + /** + * Computes and returns the public key corresponding to the private key. + * @returns {Point} The public key point on the elliptic curve associated with this instance. + */ + get public_key() { + if (!this._public_key) { + this._public_key = this.curve.multiply_point( + this.private_key, + this.curve.G, + ) + } + return this._public_key + } -// export class MasseyOmura { -// /** -// * export class to perform Massey-Omura key exchange using elliptic curves. -// * @param {number} private_key - The private key of the user. -// * @param {string} curve_name - Name of the elliptic curve to be used. Defaults to 'secp192k1'. -// */ -// constructor(private_key, curve_name = 'secp192k1') { -// this.private_key = private_key -// this.curve_name = curve_name -// this._curve = null // Placeholder for the elliptic curve object -// this._public_key = null // Placeholder for the public key -// this._encryption_cache = new Map() // Cache for encryption steps -// this._partial_decryption_cache = new Map() // Cache for partial decryption steps -// } + /** + * Computes the shared secret using the private key and the other party's public key. + * @param {Point} other_public_key - The other party's public key. + * @returns {Point} The resulting shared secret as a point on the elliptic curve. + */ + compute_shared_secret(other_public_key) { + const cacheKey = `${other_public_key.x},${other_public_key.y}` + if (!this._shared_secret_cache.has(cacheKey)) { + const shared_secret = this.curve.multiply_point( + this.private_key, + other_public_key, + ) + this._shared_secret_cache.set(cacheKey, shared_secret) + } + return this._shared_secret_cache.get(cacheKey) + } +} -// /** -// * Retrieves the elliptic curve associated with this `MasseyOmura` instance. -// * The `curve_name` attribute is used to fetch the corresponding elliptic curve object. -// * @returns {EllipticCurve} The elliptic curve object used for ECDSA operations. -// */ -// get curve() { -// if (!this._curve) { -// this._curve = get_curve(this.curve_name) -// } -// return this._curve -// } +export class MasseyOmura { + /** + * export class to perform Massey-Omura key exchange using elliptic curves. + * @param {number} private_key - The private key of the user. + * @param {string} curve_name - Name of the elliptic curve to be used. Defaults to 'secp192k1'. + */ + constructor(private_key, curve_name = 'secp192k1') { + this.private_key = private_key + this.curve_name = curve_name + this._curve = null // Placeholder for the elliptic curve object + this._public_key = null // Placeholder for the public key + this._encryption_cache = new Map() // Cache for encryption steps + this._partial_decryption_cache = new Map() // Cache for partial decryption steps + } -// /** -// * Computes and returns the public key corresponding to the private key. -// * @returns {Point} The public key point on the elliptic curve associated with this instance. -// */ -// get public_key() { -// if (!this._public_key) { -// this._public_key = this.curve.multiply_point( -// this.private_key, -// this.curve.G, -// ) -// } -// return this._public_key -// } + /** + * Retrieves the elliptic curve associated with this `MasseyOmura` instance. + * The `curve_name` attribute is used to fetch the corresponding elliptic curve object. + * @returns {EllipticCurve} The elliptic curve object used for ECDSA operations. + */ + get curve() { + if (!this._curve) { + this._curve = get_curve(this.curve_name) + } + return this._curve + } -// /** -// * Encrypts the message with the sender's private key. -// * @param {Point} message - The message point to encrypt. -// * @returns {Point} The encrypted message point. -// */ -// first_encryption_step(message) { -// if (!this._encryption_cache.has(message)) { -// const encrypted_message = this.curve.multiply_point( -// this.private_key, -// message, -// ) -// this._encryption_cache.set(message, encrypted_message) -// } -// return this._encryption_cache.get(message) -// } + /** + * Computes and returns the public key corresponding to the private key. + * @returns {Point} The public key point on the elliptic curve associated with this instance. + */ + get public_key() { + if (!this._public_key) { + this._public_key = this.curve.multiply_point( + this.private_key, + this.curve.G, + ) + } + return this._public_key + } -// /** -// * Applies the receiver's private key on the received encrypted message. -// * @param {Point} received_encrypted_message - The received encrypted message point. -// * @returns {Point} The decrypted message point. -// */ -// second_encryption_step(received_encrypted_message) { -// return this.first_encryption_step(received_encrypted_message) -// } + /** + * Encrypts the message with the sender's private key. + * @param {Point} message - The message point to encrypt. + * @returns {Point} The encrypted message point. + */ + first_encryption_step(message) { + if (!this._encryption_cache.has(message)) { + const encrypted_message = this.curve.multiply_point( + this.private_key, + message, + ) + this._encryption_cache.set(message, encrypted_message) + } + return this._encryption_cache.get(message) + } -// /** -// * Partial decryption using the inverse of the sender's private key. -// * @param {Point} encrypted_message - The encrypted message point. -// * @returns {Point} The partially decrypted message point. -// */ -// partial_decryption_step(encrypted_message) { -// if (!this._partial_decryption_cache.has(encrypted_message)) { -// const inverse_key = bigInt(this.private_key).modInv(this.curve.n).value -// const partially_decrypted_message = this.curve.multiply_point( -// inverse_key, -// encrypted_message, -// ) -// this._partial_decryption_cache.set( -// encrypted_message, -// partially_decrypted_message, -// ) -// } -// return this._partial_decryption_cache.get(encrypted_message) -// } -// } + /** + * Applies the receiver's private key on the received encrypted message. + * @param {Point} received_encrypted_message - The received encrypted message point. + * @returns {Point} The decrypted message point. + */ + second_encryption_step(received_encrypted_message) { + return this.first_encryption_step(received_encrypted_message) + } + + /** + * Partial decryption using the inverse of the sender's private key. + * @param {Point} encrypted_message - The encrypted message point. + * @returns {Point} The partially decrypted message point. + */ + partial_decryption_step(encrypted_message) { + if (!this._partial_decryption_cache.has(encrypted_message)) { + const inverse_key = this.curve.extended_gcd( + this.private_key, + this.curve.n, + )[1] + const partially_decrypted_message = this.curve.multiply_point( + inverse_key, + encrypted_message, + ) + this._partial_decryption_cache.set( + encrypted_message, + partially_decrypted_message, + ) + } + return this._partial_decryption_cache.get(encrypted_message) + } +} diff --git a/src/protocols.test.js b/src/protocols.test.js new file mode 100644 index 0000000..89aad97 --- /dev/null +++ b/src/protocols.test.js @@ -0,0 +1,48 @@ +import { test, expect } from '@jest/globals' +import { DiffieHellman, MasseyOmura } from './protocols' + +test('diffie-hellman: compute shared secret', () => { + const private_key_alice = 12345n + const dh_alice = new DiffieHellman(private_key_alice) + const private_key_bob = 67890n + const dh_bob = new DiffieHellman(private_key_bob) + + // Alice computes the shared secret using Bob's public key + const secret_alice = dh_alice.compute_shared_secret(dh_bob.public_key) + + // Bob computes the shared secret using Alice's public key + const secret_bob = dh_bob.compute_shared_secret(dh_alice.public_key) + + // The secrets should match + expect(secret_alice).toStrictEqual(secret_bob) +}) + +test('massey-omura: encryption decryption', () => { + const private_key_sender = 123456n + const mo_sender = new MasseyOmura(private_key_sender) + + const private_key_receiver = 654321n + const mo_receiver = new MasseyOmura(private_key_receiver) + + const message = mo_sender.curve.G // Using the curve's generator point for simplicity + + // Sender encrypts the message + const encrypted_by_sender = mo_sender.first_encryption_step(message) + + // Receiver encrypts the already encrypted message + const encrypted_by_receiver = + mo_receiver.second_encryption_step(encrypted_by_sender) + + // Sender decrypts the message partly + const partially_decrypted_by_sender = mo_sender.partial_decryption_step( + encrypted_by_receiver, + ) + + // Receiver completes decryption + const fully_decrypted_message = mo_receiver.partial_decryption_step( + partially_decrypted_by_sender, + ) + + // The fully decrypted message should match the original message + expect(fully_decrypted_message).toStrictEqual(message) +})