Last active
March 28, 2020 18:43
-
-
Save bellbind/085aea2769f398003eea4f2f0b2d6c5a to your computer and use it in GitHub Desktop.
[ECMAScript][BigInt] elliptic curve cryptography impl with ECMAScript BigInt proposal
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// - 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")); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// [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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// - 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")); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// - 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")); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// - 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")); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// $ 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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// [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, | |
}; | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
BigInt enabled browser(as chrome) demo:
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Spec of BigInt, see: https://github.com/tc39/proposal-bigint