as a learning exercise, I am trying to code the first point doubling (Base point P -> 2P) for the Secp256k1 Elliptic Curve. I am using Javascript, and the ethers package for BigNumber. Frustratingly, I am running into a problem where the result I am getting for 2P doesn't appear to lie on the curve. Can someone please help me determine where I am making a mistake?
The coordinates I'm getting as a result are:
X: 0xf1b9e9c77c87bf0ac622382b581826898cfc9232e025d86d904bfd33375faf1a
Y: 0x8162c7b446b54638e9181b71770b2d718e6953a360625a02392097c7db09c608
Which returns false from my isPointOnCurve() method. As a sanity check, I checked the base point in the isPointOnCurve() method, and that returns true (thankfully).
Please see my code below:
const { ethers, BigNumber } = require('ethers');
//variable initialization found from https://en.bitcoin.it/wiki/Secp256k1
bigZero = BigNumber.from(0);
bigTwo = BigNumber.from(2);
bigThree = BigNumber.from(3);
ellipticCurveB = BigNumber.from(7);
generatorPrime = BigNumber.from("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F");
order = BigNumber.from("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");
baseXCoord = BigNumber.from("0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798");
baseYCoord = BigNumber.from("0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8");
// slope = ( (3*x^2) * (2*y)^-1 ) mod order
// 2Px = slope^2 - 2*baseXCoord
// 2Py = slope * ( 2Px - baseXCoord ) - baseYCoord
m = (bigThree.mul(baseXCoord.pow(bigTwo)).mul(modinv(bigTwo.mul(baseYCoord), order))).mod(order);
TwoPx = (m.pow(bigTwo).sub(bigTwo.mul(baseXCoord))).mod(order);
TwoPy = ((m.mul(baseXCoord.sub(TwoPx))).sub(baseYCoord)).mod(order);
console.log(TwoPx);
console.log(TwoPy);
console.log(isPointOnCurve(TwoPx, TwoPy));
// Helper Functions:
// Check if point is on Curve, Calculate extended GCD, modular inverse
function isPointOnCurve(x,y){
b = ellipticCurveB;
p = generatorPrime;
rem = (y.pow(bigTwo).sub(x.pow(bigThree)).sub(b)).mod(p);
return rem.eq(bigZero);
}
function egcd(a, b) {
var s = BigNumber.from(0), t = BigNumber.from(1), u = BigNumber.from(1), v = BigNumber.from(0);
while (!a.eq(BigNumber.from(0))) {
var q = b.div(a) | BigNumber.from(0), r = b.mod(a);
var m = s.sub(u.mul(q)), n = t.sub(v.mul(q));
b = a;
a = r;
s = u;
t = v;
u = m;
v = n;
}
return [b, s, t];
}
function mod(x, y) {
return (x.mod(y).add(y)).mod(y);
}
function modinv(x, y) {
var tuple = egcd(x.mod(y), y);
if (!tuple[0].eq(BigNumber.from(1))) {
return null;
}
return mod(tuple[1], y);
}
As kelalaka pointed out in a comment on the original post, I was confusing the the order of the group and the finite field Fp. I was getting values modulo the Group Order, when I should've been using the values modulo prime p used to define the finite field.
The new and correct result I get is:
X: 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
Y: 0x1ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a
If anyone would like to use this code, I've updated it to be correct, and cleaned it up to make it a little more readable:
bigZero = BigNumber.from(0);
bigTwo = BigNumber.from(2);
bigThree = BigNumber.from(3);
ellipticCurveB = BigNumber.from(7);
generatorPrime = BigNumber.from("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F");
baseXCoord = BigNumber.from("0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798");
baseYCoord = BigNumber.from("0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8");
// slope = ( (3*x^2) * (2*y)^-1 ) mod order
threeXSquared = bigThree.mul(baseXCoord.pow(bigTwo));
modInv2y = modinv(bigTwo.mul(baseYCoord), generatorPrime);
m = threeXSquared.mul(modInv2y).mod(generatorPrime);
// 2Px = slope^2 - 2*baseXCoord
mSquared = m.pow(bigTwo);
twoXbase = bigTwo.mul(baseXCoord);
TwoPx = (mSquared.sub(twoXbase)).mod(generatorPrime);
// 2Py = slope * ( 2Px - baseXCoord ) - baseYCoord
pointSlopeX = m.mul(baseXCoord.sub(TwoPx));
TwoPy = (pointSlopeX).sub(baseYCoord).mod(generatorPrime);
console.log(TwoPx);
console.log(TwoPy);
console.log(isPointOnCurve(TwoPx, TwoPy));
// Helper Functions:
// Check if point is on Curve, Calculate extended GCD, modular inverse
function isPointOnCurve(x,y){
b = ellipticCurveB;
p = generatorPrime;
rem = (y.pow(bigTwo).sub(x.pow(bigThree)).sub(b)).mod(p);
return rem.eq(bigZero);
}
function egcd(a, b) {
var s = BigNumber.from(0), t = BigNumber.from(1), u = BigNumber.from(1), v = BigNumber.from(0);
while (!a.eq(BigNumber.from(0))) {
var q = b.div(a) | BigNumber.from(0), r = b.mod(a);
var m = s.sub(u.mul(q)), n = t.sub(v.mul(q));
b = a;
a = r;
s = u;
t = v;
u = m;
v = n;
}
return [b, s, t];
}
function modulus(x, y) {
return (x.mod(y).add(y)).mod(y);
}
function modinv(x, y) {
var tuple = egcd(x.mod(y), y);
if (!tuple[0].eq(BigNumber.from(1))) {
return null;
}
return modulus(tuple[1], y);
}