You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

283 lines
8.5 KiB

(function () {
randomBytes = (length) => self.crypto.getRandomValues(new Uint8Array(length));
self.Secp256k1 = exports = {};
function uint256(x, base) {
return new BN(x, base);
}
function rnd(P) {
return uint256(randomBytes(32)).umod(P); //TODO red
}
const A = uint256(0);
const B = uint256(7);
const GX = uint256(
"79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
16,
);
const GY = uint256(
"483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8",
16,
);
const P = uint256(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F",
16,
);
const N = uint256(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
16,
);
//const RED = BN.red(P)
const _0 = uint256(0);
const _1 = uint256(1);
// function for elliptic curve multiplication in jacobian coordinates using Double-and-add method
function ecmul(_p, _d) {
let R = [_0, _0, _0];
//return (0,0) if d=0 or (x1,y1)=(0,0)
if (_d == 0 || (_p[0] == 0 && _p[1] == 0)) {
return R;
}
let T = [
_p[0], //x-coordinate temp
_p[1], //y-coordinate temp
_p[2], //z-coordinate temp
];
const d = _d.clone();
while (d != 0) {
if (d.testn(0)) {
//if last bit is 1 add T to result
R = ecadd(T, R);
}
T = ecdouble(T); //double temporary coordinates
d.iushrn(1); //"cut off" last bit
}
return R;
}
function mulmod(a, b, P) {
return a.mul(b).umod(P); //TODO red
}
function addmod(a, b, P) {
return a.add(b).umod(P); //TODO red
}
function invmod(a, P) {
return a.invm(P); //TODO redq
}
function mulG(k) {
const GinJ = AtoJ(GX, GY);
const PUBinJ = ecmul(GinJ, k);
return JtoA(PUBinJ);
}
function assert(cond, msg) {
if (!cond) {
throw Error("assertion failed: " + msg);
}
}
function ecsign(d, z) {
assert(d != 0, "d must not be 0");
assert(z != 0, "z must not be 0");
while (true) {
const k = rnd(P);
const R = mulG(k);
if (R[0] == 0) continue;
const s = mulmod(invmod(k, N), addmod(z, mulmod(R[0], d, N), N), N);
if (s == 0) continue;
//FIXME: why do I need this
if (s.testn(255)) continue;
return { r: toHex(R[0]), s: toHex(s), v: R[1].testn(0) ? 1 : 0 };
}
}
function JtoA(p) {
const zInv = invmod(p[2], P);
const zInv2 = mulmod(zInv, zInv, P);
return [mulmod(p[0], zInv2, P), mulmod(p[1], mulmod(zInv, zInv2, P), P)];
}
//point doubling for elliptic curve in jacobian coordinates
//formula from https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates
function ecdouble(_p) {
if (_p[1] == 0) {
//return point at infinity
return [_1, _1, _0];
}
const z2 = mulmod(_p[2], _p[2], P);
const m = addmod(
mulmod(A, mulmod(z2, z2, P), P),
mulmod(uint256(3), mulmod(_p[0], _p[0], P), P),
P,
);
const y2 = mulmod(_p[1], _p[1], P);
const s = mulmod(uint256(4), mulmod(_p[0], y2, P), P);
const x = addmod(mulmod(m, m, P), negmod(mulmod(s, uint256(2), P), P), P);
return [
x,
addmod(
mulmod(m, addmod(s, negmod(x, P), P), P),
negmod(mulmod(uint256(8), mulmod(y2, y2, P), P), P),
P,
),
mulmod(uint256(2), mulmod(_p[1], _p[2], P), P),
];
}
function negmod(a, P) {
return P.sub(a);
}
// point addition for elliptic curve in jacobian coordinates
// formula from https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates
function ecadd(_p, _q) {
if (_q[0] == 0 && _q[1] == 0 && _q[2] == 0) {
return _p;
}
let z2 = mulmod(_q[2], _q[2], P);
const u1 = mulmod(_p[0], z2, P);
const s1 = mulmod(_p[1], mulmod(z2, _q[2], P), P);
z2 = mulmod(_p[2], _p[2], P);
let u2 = mulmod(_q[0], z2, P);
let s2 = mulmod(_q[1], mulmod(z2, _p[2], P), P);
if (u1.eq(u2)) {
if (!s1.eq(s2)) {
//return point at infinity
return [_1, _1, _0];
} else {
return ecdouble(_p);
}
}
u2 = addmod(u2, negmod(u1, P), P);
z2 = mulmod(u2, u2, P);
const t2 = mulmod(u1, z2, P);
z2 = mulmod(u2, z2, P);
s2 = addmod(s2, negmod(s1, P), P);
const x = addmod(
addmod(mulmod(s2, s2, P), negmod(z2, P), P),
negmod(mulmod(uint256(2), t2, P), P),
P,
);
return [
x,
addmod(
mulmod(s2, addmod(t2, negmod(x, P), P), P),
negmod(mulmod(s1, z2, P), P),
P,
),
mulmod(u2, mulmod(_p[2], _q[2], P), P),
];
}
function AtoJ(x, y) {
return [uint256(x), uint256(y), _1];
}
function isValidPoint(x, y) {
const yy = addmod(mulmod(mulmod(x, x, P), x, P), B, P);
return yy.eq(mulmod(y, y, P));
}
function toHex(bn) {
return (
"00000000000000000000000000000000000000000000000000000000000000000000000000000000" +
bn.toString(16)
).slice(-64);
}
function decompressKey(x, yBit) {
let redP = BN.red("k256");
x = x.toRed(redP);
const y = x.redMul(x).redMul(x).redAdd(B.toRed(redP)).redSqrt();
const sign = y.testn(0);
return (sign != yBit ? y.redNeg() : y).fromRed();
}
function generatePublicKeyFromPrivateKeyData(pk) {
const p = mulG(pk);
return { x: toHex(p[0]), y: toHex(p[1]) };
}
function ecrecover(recId, sigr, sigs, message) {
assert(recId >= 0 && recId <= 3, "recId must be 0..3");
assert(sigr != 0, "sigr must not be 0");
assert(sigs != 0, "sigs must not be 0");
// 1.0 For j from 0 to h (h == recId here and the loop is outside this function)
// 1.1 Let x = r + jn
const x = addmod(uint256(sigr), P.muln(recId >> 1), P);
// 1.2. Convert the integer x to an octet string X of length mlen using the conversion routine
// specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉.
// 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the
// conversion routine specified in Section 2.3.4. If this conversion routine outputs “invalid”, then
// do another iteration of Step 1.
//
// More concisely, what these points mean is to use X as a compressed public key.
if (x.gte(P)) {
// Cannot have point co-ordinates larger than this as everything takes place modulo Q.
return null;
}
// Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities.
// So it's encoded in the recId.
const y = decompressKey(x, (recId & 1) == 1);
// 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility).
// if (!R.mul(N).isInfinity())
// return null
// 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification.
const e = uint256(message);
// 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating recId)
// 1.6.1. Compute a candidate public key as:
// Q = mi(r) * (sR - eG)
//
// Where mi(x) is the modular multiplicative inverse. We transform this into the following:
// Q = (mi(r) * s ** R) + (mi(r) * -e ** G)
// Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). In the above equation
// ** is point multiplication and + is point addition (the EC group operator).
//
// We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive
// inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8.
const eNeg = negmod(e, N);
const rInv = invmod(sigr, N);
const srInv = mulmod(rInv, sigs, N);
const eNegrInv = mulmod(rInv, eNeg, N);
const R = AtoJ(x, y);
const G = AtoJ(GX, GY);
const qinJ = ecadd(ecmul(G, eNegrInv), ecmul(R, srInv));
const p = JtoA(qinJ);
return { x: toHex(p[0]), y: toHex(p[1]) };
}
function ecverify(Qx, Qy, sigr, sigs, z) {
if (sigs == 0 || sigr == 0) {
return false;
}
const w = invmod(sigs, N);
const u1 = mulmod(z, w, N);
const u2 = mulmod(sigr, w, N);
const Q = AtoJ(Qx, Qy);
const G = AtoJ(GX, GY);
const RinJ = ecadd(ecmul(G, u1), ecmul(Q, u2));
const r = JtoA(RinJ);
return sigr.eq(r[0]);
}
exports.uint256 = uint256;
exports.ecsign = ecsign;
exports.ecrecover = ecrecover;
exports.generatePublicKeyFromPrivateKeyData =
generatePublicKeyFromPrivateKeyData;
exports.decompressKey = decompressKey;
exports.isValidPoint = isValidPoint;
exports.ecverify = ecverify;
})();