Skip to content

Instantly share code, notes, and snippets.

@bellbind
Last active March 28, 2020 18:43
Show Gist options
  • Save bellbind/085aea2769f398003eea4f2f0b2d6c5a to your computer and use it in GitHub Desktop.
Save bellbind/085aea2769f398003eea4f2f0b2d6c5a to your computer and use it in GitHub Desktop.
[ECMAScript][BigInt] elliptic curve cryptography impl with ECMAScript BigInt proposal
// - required node.js >= 10.0.0
// $ npm i elliptic
// $ node --harmony-bigint --experimental-modules check-secp256k1.mjs
import crypto from "crypto";
import elliptic from "elliptic";
import {EC, ECC, secp256k1, a2bi, bi2a, randint} from "./ecc.mjs";
// With real standard curve: secp256k1
// check EC params validity
const ec = new EC(secp256k1.a, secp256k1.b, secp256k1.p);
console.assert(secp256k1.a < secp256k1.p && secp256k1.b < secp256k1.p);
console.assert(secp256k1.g.x < secp256k1.p && secp256k1.g.y < secp256k1.p);
console.assert(secp256k1.n < secp256k1.p);
console.assert(ec.eq(ec.mul(secp256k1.g, secp256k1.n), ec.zero)); // ng == 0
// check compatibilities between our ECC and elliptic ecdsa
// random bytes by node.js crypto
const priv = crypto.randomBytes(32); // randint as private key
// elliptic pubkey generation
const eec = new elliptic.ec("secp256k1");
const key = eec.keyFromPrivate(priv);
const pbuf = Buffer.from(key.getPublic().encode());
console.log(`pubkey by elliptic:`);
console.table({
x: pbuf.slice(1, 33).toString("hex"), y: pbuf.slice(33).toString("hex")});
// our ECC pubkey generation
const bnPriv = a2bi(priv); //=> array to BigInt
const ecc = new ECC(secp256k1);
const pk = ecc.pubkey(bnPriv);
console.log(`pubkey by our ECC:`);
console.table({x: pk.x.toString(16), y: pk.y.toString(16)}); // same as above
// sig by elliptic
const sig = key.sign(priv);
console.log(`sign by elliptic:`);
console.table({r: sig.r.toString("hex"), s: sig.s.toString("hex")});
// verify with our ECC
const bnSig = {r: a2bi(sig.r.toArray()), s: a2bi(sig.s.toArray())};
console.log(`verify with our ECC:`, ecc.verify(bnPriv, bnSig, pk)); //=> true
// sig by our ECC
const rand = randint(1n, secp256k1.n);
const bnSig2 = ecc.sign(bnPriv, rand, bnPriv);
console.log(`sign by our ECC:`);
console.table({r: bnSig2.r.toString(16), s: bnSig2.s.toString(16)});
// verify with elliptic
const sig2 = {r: bi2a(32, bnSig2.r), s: bi2a(32, bnSig2.s)};
console.log(`verify with elliptic:`, key.verify(priv, sig2)); //=> true
// ECDH derived key for self pubkey
console.log(`ECDH with elliptic:`);
console.log(key.derive(key.getPublic()).toString(16));
console.log(`ECDH with our ECC:`);
console.log(ecc.derive(bnPriv, pk).toString(16).padStart(64, "0"));
// [elliptic curve cryptography impl with ECMAScript BigInt proposal]
// - required node.js >= 10.0.0
// $ node --harmony-bigint --experimental-modules check-simple-params.mjs
import {EC, ECC} from "./ecc.mjs";
// simple ECC example
// define Elliptic Curve system
const a = 1n, b = 18n, p = 19n;
console.log(`y^2 = x^3 + ax + b mod p:`, {a, b, p});
const ec = new EC(a, b, p);
const [g, gm] = ec.points(7n);
console.log(`generator g:`, g); //=> {x: 7, y: 11}
const n = ec.order(g);
console.log(`order n for g:`, n); //=> 19
// ECC and generate pubkey
const ecc = new ECC({a, b, p, g, n, hash: 1n});
const priv = 11n;
const pub = ecc.pubkey(priv);
console.log(`pub key:`, pub); //=> {x: 1, y: 18}
// sign and verify
const hash = 128n;
const rand = 7n;
const sig = ecc.sign(hash, rand, priv);
console.log(`sign:`, sig); //=>{r: 15, s: 12}
console.log(`verified`, ecc.verify(hash, sig, pub)); //=> true
// - required node.js >= 10.0.0
// $ npm i elliptic
// $ node --harmony-bigint --experimental-modules check-standard-p256.mjs
import crypto from "crypto";
import elliptic from "elliptic";
import {EC, ECC, p256, a2bi, bi2a, randint} from "./ecc.mjs";
// With real standard curve: P-256
// check EC params validity
const ec = new EC(p256.a, p256.b, p256.p);
console.assert(p256.a < p256.p && p256.b < p256.p);
console.assert(p256.g.x < p256.p && p256.g.y < p256.p);
console.assert(p256.n < p256.p);
console.assert(ec.eq(ec.mul(p256.g, p256.n), ec.zero)); // ng == 0
// check compatibilities between our ECC and elliptic ecdsa
// random bytes by node.js crypto
const priv = crypto.randomBytes(32); // randint as private key
// elliptic pubkey generation
const eec = new elliptic.ec("p256");
const key = eec.keyFromPrivate(priv);
const pbuf = Buffer.from(key.getPublic().encode());
console.log(`pubkey by elliptic:`);
console.table({
x: pbuf.slice(1, 33).toString("hex"), y: pbuf.slice(33).toString("hex")});
// our ECC pubkey generation
const bnPriv = a2bi(priv); //=> array to BigInt
const ecc = new ECC(p256);
const pk = ecc.pubkey(bnPriv);
console.log(`pubkey by our ECC:`);
console.table({x: pk.x.toString(16), y: pk.y.toString(16)}); // same as above
// sig by elliptic
const sig = key.sign(priv);
console.log(`sign by elliptic:`);
console.table({r: sig.r.toString("hex"), s: sig.s.toString("hex")});
// verify with our ECC
const bnSig = {r: a2bi(sig.r.toArray()), s: a2bi(sig.s.toArray())};
console.log(`verify with our ECC:`, ecc.verify(bnPriv, bnSig, pk)); //=> true
// sig by our ECC
const rand = randint(1n, p256.n);
const bnSig2 = ecc.sign(bnPriv, rand, bnPriv);
console.log(`sign by our ECC:`);
console.table({r: bnSig2.r.toString(16), s: bnSig2.s.toString(16)});
// verify with elliptic
const sig2 = {r: bi2a(32, bnSig2.r), s: bi2a(32, bnSig2.s)};
console.log(`verify with elliptic:`, key.verify(priv, sig2)); //=> true
// ECDH derived key for self pubkey
console.log(`ECDH with elliptic:`);
console.log(key.derive(key.getPublic()).toString(16));
console.log(`ECDH with our ECC:`);
console.log(ecc.derive(bnPriv, pk).toString(16).padStart(64, "0"));
// - required node.js >= 10.0.0
// $ npm i elliptic
// $ node --harmony-bigint --experimental-modules check-standard-p384.mjs
import crypto from "crypto";
import elliptic from "elliptic";
import {EC, ECC, p384, a2bi, bi2a, randint} from "./ecc.mjs";
// With real standard curve: P-256
// check EC params validity
const ec = new EC(p384.a, p384.b, p384.p);
console.assert(p384.a < p384.p && p384.b < p384.p);
console.assert(p384.g.x < p384.p && p384.g.y < p384.p);
console.assert(p384.n < p384.p);
console.assert(ec.eq(ec.mul(p384.g, p384.n), ec.zero)); // ng == 0
// check compatibilities between our ECC and elliptic ecdsa
// random bytes by node.js crypto
const priv = crypto.randomBytes(48); // randint as private key
// elliptic pubkey generation
const eec = new elliptic.ec("p384");
const key = eec.keyFromPrivate(priv);
const pbuf = Buffer.from(key.getPublic().encode());
console.log(`pubkey by elliptic:`);
console.table({
x: pbuf.slice(1, 49).toString("hex"), y: pbuf.slice(49).toString("hex")});
// our ECC pubkey generation
const bnPriv = a2bi(priv); //=> array to BigInt
const ecc = new ECC(p384);
const pk = ecc.pubkey(bnPriv);
console.log(`pubkey by our ECC:`);
console.table({x: pk.x.toString(16), y: pk.y.toString(16)}); // same as above
// sig by elliptic
const sig = key.sign(priv);
console.log(`sign by elliptic:`);
console.table({r: sig.r.toString("hex"), s: sig.s.toString("hex")});
// verify with our ECC
const bnSig = {r: a2bi(sig.r.toArray()), s: a2bi(sig.s.toArray())};
console.log(`verify with our ECC:`, ecc.verify(bnPriv, bnSig, pk)); //=> true
// sig by our ECC
const rand = randint(1n, p384.n);
const bnSig2 = ecc.sign(bnPriv, rand, bnPriv);
console.log(`sign by our ECC:`);
console.table({r: bnSig2.r.toString(16), s: bnSig2.s.toString(16)});
// verify with elliptic
const sig2 = {r: bi2a(48, bnSig2.r), s: bi2a(48, bnSig2.s)};
console.log(`verify with elliptic:`, key.verify(priv, sig2)); //=> true
// ECDH derived key for self pubkey
console.log(`ECDH with elliptic:`);
console.log(key.derive(key.getPublic()).toString(16));
console.log(`ECDH with our ECC:`);
console.log(ecc.derive(bnPriv, pk).toString(16).padStart(96, "0"));
// - required node.js >= 10.0.0
// $ npm i elliptic
// $ node --harmony-bigint --experimental-modules check-standard-p521.mjs
import crypto from "crypto";
import elliptic from "elliptic";
import {EC, ECC, p521, a2bi, bi2a, randint} from "./ecc.mjs";
// With real standard curve: P-256
// check EC params validity
const ec = new EC(p521.a, p521.b, p521.p);
console.assert(p521.a < p521.p && p521.b < p521.p);
console.assert(p521.g.x < p521.p && p521.g.y < p521.p);
console.assert(p521.n < p521.p);
console.assert(ec.eq(ec.mul(p521.g, p521.n), ec.zero)); // ng == 0
// check compatibilities between our ECC and elliptic ecdsa
// random bytes by node.js crypto
const priv = crypto.randomBytes(66); // randint as private key
// elliptic pubkey generation
const eec = new elliptic.ec("p521");
const key = eec.keyFromPrivate(priv);
const pbuf = Buffer.from(key.getPublic().encode());
console.log(`pubkey by elliptic:`);
console.table({
x: pbuf.slice(1, 67).toString("hex"), y: pbuf.slice(67).toString("hex")});
// our ECC pubkey generation
const bnPriv = a2bi(priv); //=> array to BigInt
const ecc = new ECC(p521);
const pk = ecc.pubkey(bnPriv);
console.log(`pubkey by our ECC:`);
console.table({x: pk.x.toString(16), y: pk.y.toString(16)}); // same as above
// truncate key(66bytes) to hash size(64bytes)
const hash = priv.slice(2);
const bnHash = a2bi(hash);
// sig by elliptic
const sig = key.sign(hash);
console.log(`sign by elliptic:`);
console.table({r: sig.r.toString("hex"), s: sig.s.toString("hex")});
// verify with our ECC
const bnSig = {r: a2bi(sig.r.toArray()), s: a2bi(sig.s.toArray())};
console.log(`verify with our ECC:`, ecc.verify(bnHash, bnSig, pk)); //=> true
// sig by our ECC
const rand = randint(1n, p521.n);
const bnSig2 = ecc.sign(bnHash, rand, bnPriv);
console.log(`sign by our ECC:`);
console.table({r: bnSig2.r.toString(16), s: bnSig2.s.toString(16)});
// verify with elliptic
const sig2 = {r: bi2a(66, bnSig2.r), s: bi2a(66, bnSig2.s)};
console.log(`verify with elliptic:`, key.verify(hash, sig2)); //=> true
// ECDH derived key for self pubkey
console.log(`ECDH with elliptic:`);
console.log(key.derive(key.getPublic()).toString(16));
console.log(`ECDH with our ECC:`);
console.log(ecc.derive(bnPriv, pk).toString(16).padStart(130, "0"));
// $ node --no-warnings --experimental-modules --harmony-bigint commutative.mjs
// commutative relation and adding crypted values to decrypt
import crypto from "crypto";
import {EC, ECC, secp256k1, a2bi, bi2a, randint} from "./ecc.mjs";
const ecc = new ECC(secp256k1);
{
//[case 1] varidating sum of encrypted values
// - receiver do not know each values, but they can check total of them
// 0. receiver prepares a key pair
const priv = a2bi(crypto.randomBytes(32));
const pub = ecc.pubkey(priv);
// 1. sender: values to EC points
const p1 = ecc.ec.mul(ecc.g, 2n);
const p2 = ecc.ec.mul(ecc.g, 4n);
//console.log(p1);
//console.log(p2);
//console.log(ecc.ec.add(p1, p2));
// 2. sender: encrypt values as points to pass
const r1 = a2bi(crypto.randomBytes(32));
const [c1l, c1r] = ecc.encode(p1, r1, pub);
const r2 = a2bi(crypto.randomBytes(32));
const [c2l, c2r] = ecc.encode(p2, r2, pub);
// 3. receiver: adding (passed) as point pairs
const csuml = ecc.ec.add(c1l, c2l);
const csumr = ecc.ec.add(c1r, c2r);
// 4. receiver: decode to sum points
const dsum = ecc.decode([csuml, csumr], priv);
//console.log(dsum);
console.assert(ecc.ec.eq(dsum, ecc.ec.add(p1, p2)));
// 5. receiver: check with sum of values
console.assert(ecc.ec.eq(dsum, ecc.ec.mul(ecc.g, 6n)));
//[proof]
// (priv = p, pub = pG)
// c1 = [r1G, p1+r1pG]
// c2 = [r2G, p2+r2pG]
// csum = [(r1+r2)G, (p1+p2) + (r1+r2)pG]
// dsum = (p1+p2)+(r1+r2)pG - p(r1+r2)G = p1+p2
}
{
//[case 2] three-pass protocol
// - pass message without any key exchange
// sender and receiver prepare each key-pair
const spriv = a2bi(crypto.randomBytes(32));
const spub = ecc.pubkey(spriv);
const rpriv = a2bi(crypto.randomBytes(32));
const rpub = ecc.pubkey(rpriv);
//0a. sender: convert message to point
const msg = a2bi(crypto.randomBytes(31));
console.assert(msg < ecc.ec.q); // msg must smaller than modulus q
const [mp] = ecc.ec.points(msg);
//console.log(mp);
console.assert(mp.x === msg);
//0b. sender: encrypt the message point with its pubkey to share
const r1 = a2bi(crypto.randomBytes(32));
const [sl, sr] = ecc.encode(mp, r1, spub);
//1. receiver: encrypt crypted msg with its pubkey to request with it
const r2 = a2bi(crypto.randomBytes(32));
const [rl, rr] = ecc.encode(sr, r2, rpub);
//2. sender: decrypt receiver crypted points with its privkey to respond
const rmp = ecc.decode([sl, rr], spriv);
//3. receiver: receive as decrypt reponse points with its privkey
const dmp = ecc.decode([rl, rmp], rpriv);
//console.log(dmp);
console.assert(dmp.x === msg);
//[proof]
// sender(s, r1) shared s = [r1G, p+r1sG]
// receiver(r, r2) request r = [r1G, p+r1sG + r2rG]
// sender response rmp = p+r2rG
}
// [elliptic curve cryptography impl with ECMAScript BigInt proposal]
// - required node.js >= 10.0.0 with "--harmony-bigint" option
// byte array to BigInt
export function a2bi(ba) {
return new Uint8Array(ba).reduce((r, v) => (r << 8n) | BigInt(v), 0n);
}
export function bi2a(size, n) {
const a = new Uint8Array(size);
for (let i = 0; i < size; i++) {
a[size - i - 1] = Number((n >> BigInt(8 * i)) & 0xffn);
}
return a;
}
// Extended GCD
// - [s, t, gcd] = egcd(a, b); a * s + b * t === gcd
export function egcd(a, b) {
let [s0, s1, t0, t1] = [1n, 0n, 0n, 1n];
while (b > 0n) {
const r = a % b, q = (a - r) / b;
[a, b] = [b, r];
[s0, s1, t0, t1] = [s1, s0 - s1 * q, t1, t0 - t1 * q];
}
return [s0, t0, a];
}
// fast jacobi symbol
// - Algorithm 2.149 of http://www.cacr.math.uwaterloo.ca/hac/about/chap2.pdf
// Jacobi symbol
// - if common divisor of a and q > 1, 0
// - some x exists such as x^2 mod q = a then 1, otherwise -1
export function jacobi(a, q) {
if (a === 0n) return 0n;
if (a === 1n) return 1n;
let [a1, e] = [a, 0n];
while ((a1 & 1n) === 0n) [a1, e] = [a1 >> 1n, e + 1n];
const m8 = q % 8n, ne = q % 4n === 3n && a1 % 4n === 3n ? -1n : 1n;
const s = ne * ((e & 1n) === 0n || m8 === 1n || m8 === 7n ? 1n : -1n);
return a1 === 1n ? s : s * jacobi(q % a1, a1);
}
// (non strict) randint [s, e) for BigInt
export function randint(s, e) {
const w = e - s;
let bytes = 0n;
for (let v = w; v > 0n; v >>= 1n) bytes++;
const rand = a2bi([...Array(bytes)].map(_ => 256 * Math.random() >>> 0));
return s + rand % w;
}
// inv and sqrt on modulus
export function inv(n, q) {
// s * n === -t * q + 1 => s * n % q = 1
const [s] = egcd(n, q);
return s < 0n ? q + s : s;
}
// non-negative mod: ECMAScript % would become minus values
export function mod(n, m) {
const s = n % m;
return s < 0n ? m + s : s;
}
// mod exp
export function exp(b, e, m) {
let r = 1n, s = b;
while (e > 0n) {
if (e & 1n) r = r * s % m;
[e, s] = [e >> 1n, s * s % m];
}
return r < 0n ? m + r : r;
}
// fast sqrt
// - Algorithm 3.34 of http://www.cacr.math.uwaterloo.ca/hac/about/chap3.pdf
export function sqrt(n, q) {
let b = randint(1n, q);
while (jacobi(b, q) !== -1n) b = randint(1n, q);
let [t, s] = [q - 1n, 0n];
while ((t & 1n) === 0n) [t, s] = [t >> 1n, s + 1n];
const ni = inv(n, q);
let c = exp(b, t, q), r = exp(n, (t + 1n) >> 1n, q);
for (let i = 1n; i < s; i++) {
const d = exp(ni * r ** 2n, 1n << (s - i - 1n), q);
if (d === q - 1n) r = r * c % q;
c = c ** 2n % q;
}
return [r, q - r];
}
// original python version: https://gist.github.com/bellbind/1414867
export class EC {
constructor(a, b, q) {
this.a = a;
this.b = b;
this.q = q;
this.zero = {x: 0n, y: 0n};
}
left(y) {
return mod(y ** 2n, this.q);
}
right(x) {
const {a, b, q} = this;
return mod(x ** 3n + a * x + b, q);
}
grad(x, y) {
// f(x,y) = x^3 + ax + b - y^2 = 0 (constant)
// => df(x,y) = (3x^2 + a) dx - 2y dy = 0
// => dy/dx = (3x^2 + a) / 2y
const {a, b, q} = this;
return mod((3n * x ** 2n + a) * inv(2n * y % q, q), q);
}
points(x) {
const [y, my] = sqrt(this.right(x), this.q);
return [{x, y}, {x, y: my}];
}
eq({x: x1, y: y1}, {x: x2, y: y2}) {
return x1 === x2 && y1 === y2;
}
isValid(p) {
return this.eq(p, this.zero) || this.left(p.y) === this.right(p.x);
}
neg({x, y}) {
return {x, y: this.q - y};
}
add(p1, p2) {
const {a, q, zero} = this;
if (this.eq(p1, zero)) return p2;
if (this.eq(p2, zero)) return p1;
const [{x: x1, y: y1}, {x: x2, y: y2}] = [p1, p2];
if (x1 === x2 && (y1 !== y2 || y1 === 0n)) return zero;
const l = this.eq(p1, p2) ? this.grad(x1, y1) :
mod((y2 - y1) * inv(mod(x2 - x1, q), q), q);
// line: y = lx + c, c = -(lx1 - y1)
// y^2 = l^2 x^2 + 2lcx + c^2 = x^3 + ax + b
// => x^3 - l^2 x^2 + (a - 2lc)x - (c^2 - b) = 0
// (x-x1)(x-x2)(x-x3) = 0
// => x^3 - (x1+x2+x3) x^2 + (x1x2 + x2x3 + x3x1)x - x1x2x3 = 0
// (relation from coefficient of x^2)
// l^2 = x1+x2+x3
// result x as x3 = l^2 - x1 - x2
const x = mod(l ** 2n - x1 - x2, q);
// result y as negate of y3( = lx3 - (lx1 - y1))
const y = mod(l * (x1 - x) - y1, q);
return {x, y};
}
mul(p, n) {
let [r, m2] = [this.zero, p];
while (n > 0n) {
if ((n & 1n) === 1n) r = this.add(r, m2);
[n, m2] = [n >> 1n, this.add(m2, m2)];
}
return r;
}
order(g) {
const {q, zero} = this;
for (let i = 1n; i <= q; i++) {
if (this.eq(this.mul(g, i), zero)) return i;
}
throw TypeError("invalid generator");
}
}
export class ECC {
constructor({a, b, p, g, n, hash}) {
this.ec = new EC(a, b, p);
if (!this.ec.eq(this.ec.mul(g, n), this.ec.zero)) {
throw TypeError(`invalid g and n value: n*g should be 0 in EC`);
}
this.g = g;
this.n = n;
this.hashMax = 1n << (hash * 8n);
}
pubkey(priv) {
return this.ec.mul(this.g, priv % this.n);
}
derive(priv, pub) {
return this.ec.mul(pub, priv % this.n).x;
}
encode(p, rand, pub) {
const {ec, g} = this;
return [ec.mul(g, rand), ec.add(p, ec.mul(pub, rand))];
}
decode([c1, c2], priv) {
const {ec, g, n} = this;
return ec.add(c2, ec.neg(ec.mul(c1, priv % n)));
}
sign(hash, rand, priv) {
if (hash >= this.hashMax) throw TypeError(`hash is too large: ${hash}`);
const {ec, g, n} = this;
const {x: r} = ec.mul(g, rand);
const s = mod(inv(rand, n) * (hash + r * priv), n);
return {r, s};
}
verify(hash, {r, s}, pub) {
if (hash >= this.hashMax) throw TypeError(`hash is too large: ${hash}`);
const {ec, g, n} = this;
const w = inv(s, n);
const [u1, u2] = [hash * w % n, r * w % n];
const {x} = ec.add(ec.mul(g, u1), ec.mul(pub, u2));
return x % n === r;
}
}
// P-256 params (referred from elliptic: curves.js)
export const p256 = {
a: 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffcn,
b: 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604bn,
p: 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn,
g: {
x: 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296n,
y: 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5n,
},
n: 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n,
hash: 32n,
};
export const p384 = {
a: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffcn,
b: 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aefn,
p: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffffn,
g: {
x: 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7n,
y: 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5fn,
},
n: 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973n,
hash: 48n,
};
export const p521 = {
a: 0x000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcn,
b: 0x00000051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00n,
p: 0x000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn,
g: {
x: 0x000000c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66n,
y: 0x0000011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650n,
},
n: 0x000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409n,
hash: 64n,
};
// secp256k1 params (referred from https://en.bitcoin.it/wiki/Secp256k1)
export const secp256k1 = {
a: 0n,
b: 7n,
p: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2Fn,
g: {
x: 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798n,
y: 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8n,
},
n: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n,
hash: 32n,
};
import elliptic from "https://dev.jspm.io/elliptic";
import {
EC, ECC, a2bi, bi2a, randint, p256, p384, p521, secp256k1
} from "./ecc.mjs";
console.log(`// simple ECC example`);
{
// define Elliptic Curve system
const a = 1n, b = 18n, p = 19n;
console.log(`y^2 = x^3 + ax + b mod p:`, {a, b, p});
const ec = new EC(a, b, p);
const [g, gm] = ec.points(7n);
console.log(`generator g:`, g); //=> {x: 7, y: 11}
const n = ec.order(g);
console.log(`order n for g:`, n); //=> 19
// ECC and generate pubkey
const ecc = new ECC({a, b, p, g, n, hash: 1n});
const priv = 11n;
const pub = ecc.pubkey(priv);
console.log(`pub key:`, pub); //=> {x: 1, y: 18}
// sign and verify
const hash = 128n;
const rand = 7n;
const sig = ecc.sign(hash, rand, priv);
console.log(`sign:`, sig); //=>{r: 15, s: 12}
console.log(`verified`, ecc.verify(hash, sig, pub)); //=> true
}
console.log(`// Compare elliptic result with real standard curve: P-256`);
{
// check EC params validity
const ec = new EC(p256.a, p256.b, p256.p);
console.assert(p256.a < p256.p && p256.b < p256.p);
console.assert(p256.g.x < p256.p && p256.g.y < p256.p);
console.assert(p256.n < p256.p);
console.assert(ec.eq(ec.mul(p256.g, p256.n), ec.zero)); // ng == 0
// check compatibilities between our ECC and elliptic ecdsa
const bnPriv = randint(0n, 1n << 256n);
const priv = bi2a(32, bnPriv);
// elliptic pubkey generation
const eec = new elliptic.ec("p256");
const key = eec.keyFromPrivate(priv);
console.log(key.getPublic().encode());
const pbuf = new Uint8Array(key.getPublic().encode());
console.log(`[p256] pubkey by elliptic:`);
console.table({
x: a2bi(pbuf.slice(1, 33)).toString(16),
y: a2bi(pbuf.slice(33)).toString(16)});
// our ECC pubkey generation
const ecc = new ECC(p256);
const pk = ecc.pubkey(bnPriv);
console.log(`[p256] pubkey by our ECC:`);
console.table({x: pk.x.toString(16), y: pk.y.toString(16)});
// sig by elliptic
const sig = key.sign(priv);
console.log(`[p256] sign by elliptic:`);
console.table({r: sig.r.toString("hex"), s: sig.s.toString("hex")});
// verify with our ECC
const bnSig = {r: a2bi(sig.r.toArray()), s: a2bi(sig.s.toArray())};
console.log(`[p256] verify with our ECC:`, ecc.verify(bnPriv, bnSig, pk));
// sig by our ECC
const rand = randint(1n, p256.n);
const bnSig2 = ecc.sign(bnPriv, rand, bnPriv);
console.log(`[p256] sign by our ECC:`);
console.table({r: bnSig2.r.toString(16), s: bnSig2.s.toString(16)});
// verify with elliptic
const sig2 = {r: bi2a(32, bnSig2.r), s: bi2a(32, bnSig2.s)};
console.log(`[p256] verify with elliptic:`, key.verify(priv, sig2));
}
console.log(`// Compare elliptic result with real standard curve: P-384`);
{
// check EC params validity
const ec = new EC(p384.a, p384.b, p384.p);
console.assert(p384.a < p384.p && p384.b < p384.p);
console.assert(p384.g.x < p384.p && p384.g.y < p384.p);
console.assert(p384.n < p384.p);
console.assert(ec.eq(ec.mul(p384.g, p384.n), ec.zero)); // ng == 0
// check compatibilities between our ECC and elliptic ecdsa
// random bytes by node.js crypto
const bnPriv = randint(0n, 1n << 384n);
const priv = bi2a(48, bnPriv);
// elliptic pubkey generation
const eec = new elliptic.ec("p384");
const key = eec.keyFromPrivate(priv);
const pbuf = new Uint8Array(key.getPublic().encode());
console.log(`[p384] pubkey by elliptic:`);
console.table({
x: a2bi(pbuf.slice(1, 49)).toString(16),
y: a2bi(pbuf.slice(49)).toString(16)});
// our ECC pubkey generation
const ecc = new ECC(p384);
const pk = ecc.pubkey(bnPriv);
console.log(`[p384] pubkey by our ECC:`);
console.table({x: pk.x.toString(16), y: pk.y.toString(16)});
// sig by elliptic
const sig = key.sign(priv);
console.log(`[p384] sign by elliptic:`);
console.table({r: sig.r.toString("hex"), s: sig.s.toString("hex")});
// verify with our ECC
const bnSig = {r: a2bi(sig.r.toArray()), s: a2bi(sig.s.toArray())};
console.log(`[p384] verify with our ECC:`, ecc.verify(bnPriv, bnSig, pk));
// sig by our ECC
const rand = randint(1n, p384.n);
const bnSig2 = ecc.sign(bnPriv, rand, bnPriv);
console.log(`[p384] sign by our ECC:`);
console.table({r: bnSig2.r.toString(16), s: bnSig2.s.toString(16)});
// verify with elliptic
const sig2 = {r: bi2a(48, bnSig2.r), s: bi2a(48, bnSig2.s)};
console.log(`[p384] verify with elliptic`, key.verify(priv, sig2));
}
console.log(`// Compare elliptic result with real standard curve: P-521`);
{
// check EC params validity
const ec = new EC(p521.a, p521.b, p521.p);
console.assert(p521.a < p521.p && p521.b < p521.p);
console.assert(p521.g.x < p521.p && p521.g.y < p521.p);
console.assert(p521.n < p521.p);
console.assert(ec.eq(ec.mul(p521.g, p521.n), ec.zero)); // ng == 0
// check compatibilities between our ECC and elliptic ecdsa
// random bytes by node.js crypto
const bnPriv = randint(0n, 1n << 521n);
const priv = bi2a(66, bnPriv);
// elliptic pubkey generation
const eec = new elliptic.ec("p521");
const key = eec.keyFromPrivate(priv);
const pbuf = new Uint8Array(key.getPublic().encode());
console.log(`[p521] pubkey by elliptic:`);
console.table({
x: a2bi(pbuf.slice(1, 67)).toString(16),
y: a2bi(pbuf.slice(67)).toString(16)});
// our ECC pubkey generation
const ecc = new ECC(p521);
const pk = ecc.pubkey(bnPriv);
console.log(`[p521] pubkey by our ECC:`);
console.table({x: pk.x.toString(16), y: pk.y.toString(16)});
// truncate key(66bytes) to hash size(64bytes)
const hash = priv.slice(2);
const bnHash = a2bi(hash);
// sig by elliptic
const sig = key.sign(hash);
console.log(`[p521] sign by elliptic:`);
console.table({r: sig.r.toString("hex"), s: sig.s.toString("hex")});
// verify with our ECC
const bnSig = {r: a2bi(sig.r.toArray()), s: a2bi(sig.s.toArray())};
console.log(`[p521] verify with our ECC:`, ecc.verify(bnHash, bnSig, pk));
// sig by our ECC
const rand = randint(1n, p521.n);
const bnSig2 = ecc.sign(bnHash, rand, bnPriv);
console.log(`[p521] sign by our ECC:`);
console.table({r: bnSig2.r.toString(16), s: bnSig2.s.toString(16)});
// verify with elliptic
const sig2 = {r: bi2a(66, bnSig2.r), s: bi2a(66, bnSig2.s)};
console.log(`[p521] verify with elliptic:`, key.verify(hash, sig2));
}
console.log(`// Compare elliptic result with real standard curve: secp256k1`);
{
// check EC params validity
const ec = new EC(secp256k1.a, secp256k1.b, secp256k1.p);
console.assert(secp256k1.a < secp256k1.p && secp256k1.b < secp256k1.p);
console.assert(secp256k1.g.x < secp256k1.p && secp256k1.g.y < secp256k1.p);
console.assert(secp256k1.n < secp256k1.p);
console.assert(ec.eq(ec.mul(secp256k1.g, secp256k1.n), ec.zero));
// check compatibilities between our ECC and elliptic ecdsa
// random bytes by node.js crypto
const bnPriv = randint(0n, 1n << 256n);
const priv = bi2a(32, bnPriv);
// elliptic pubkey generation
const eec = new elliptic.ec("secp256k1");
const key = eec.keyFromPrivate(priv);
const pbuf = new Uint8Array(key.getPublic().encode());
console.log(`[secp256k1] pubkey by elliptic:`);
console.table({
x: a2bi(pbuf.slice(1, 33)).toString(16),
y: a2bi(pbuf.slice(33)).toString(16)});
// our ECC pubkey generation
const ecc = new ECC(secp256k1);
const pk = ecc.pubkey(bnPriv);
console.log(`[secp256k1] pubkey by our ECC:`);
console.table({x: pk.x.toString(16), y: pk.y.toString(16)});
// sig by elliptic
const sig = key.sign(priv);
console.log(`[secp256k1] sign by elliptic:`);
console.table({r: sig.r.toString("hex"), s: sig.s.toString("hex")});
// verify with our ECC
const bnSig = {r: a2bi(sig.r.toArray()), s: a2bi(sig.s.toArray())};
console.log(`[secp256k1] verify with our ECC:`,
ecc.verify(bnPriv, bnSig, pk));
// sig by our ECC
const rand = randint(1n, secp256k1.n);
const bnSig2 = ecc.sign(bnPriv, rand, bnPriv);
console.log(`[secp256k1] sign by our ECC:`);
console.table({r: bnSig2.r.toString(16), s: bnSig2.s.toString(16)});
// verify with elliptic
const sig2 = {r: bi2a(32, bnSig2.r), s: bi2a(32, bnSig2.s)};
console.log(`[secp256k1] verify with elliptic:`, key.verify(priv, sig2));
}
<!doctype html>
<html>
<head>
<script type="module" src="./main.m.js"></script>
</head>
<body>
<h1>
Check outputs with Web Console with BigInt enabled browsers as chrome
</h1>
</body>
</html>
@bellbind
Copy link
Author

@bellbind
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment