const WIDTH = 400;
const HEIGHT = 400;
const canvas = document.getElementById("canvas");
canvas.width = WIDTH;
canvas.height = HEIGHT;
paper.setup(canvas);
const tooltip = document.querySelector(".tooltip");
let mode = 0; // 0: select; 1: add; 2: result
const instructions = [
"Enter values of a, b, p and click Plot",
"Select two points on the plot and click Add",
"Click Reset to start over"
];
function mod(n, m) {
return ((n % m) + m) % m;
}
class ECFF {
constructor(a, b, p) {
this.p = p;
this.a = a;
this.b = b;
this.selected = [];
}
f(x) {
return mod(Math.pow(x, 3) + this.a*x + this.b, this.p);
}
_inverse(x) {
for(let i=1; i<this.p; i++) {
if(mod(i*x, this.p) == 1)
return i;
}
throw new Error(`inv(${x}) mod ${this.p} does not exist`);
}
_plotPoint(x, y, fillColor, radius, hover, onClick) {
const point = new paper.Path.Circle(
new paper.Point(x*WIDTH/this.p, HEIGHT-y*HEIGHT/this.p),
radius
);
point.fillColor = fillColor;
point.data.x = x;
point.data.y = y;
if(hover) {
point.onMouseEnter = () => {
if(!hover())
return;
tooltip.style.left = point.position.x + "px";
tooltip.style.top = (point.position.y - 20) + "px";
tooltip.innerHTML = `${x}, ${y}`;
tooltip.style.opacity = 1;
tooltip.setAttribute("data-id", point.id);
};
point.onMouseLeave = () => {
if(!hover())
return;
tooltip.style.opacity = 0;
};
}
if(onClick) {
point.onClick = () => onClick(point, x, y);
}
}
plotField() {
for(let x=0; x<this.p; x++) {
for(let y=0; y<this.p; y++) {
if(mod(y*y, this.p) == this.f(x)) {
this._plotPoint(
x, y, "#999999", 4,
() => mode == 1,
(point, x, y) => {
if(mode != 1)
return;
if(this.selected.length==2)
return;
this.selected.push({
x: x,
y: y
});
point.fillColor = "red";
}
);
}
}
}
return true;
}
add() {
if(this.selected.length<2) {
alert("Please select two points first");
return;
}
const x1 = this.selected[0].x;
const y1 = this.selected[0].y;
const x2 = this.selected[1].x;
const y2 = this.selected[1].y;
const slope = mod((y2-y1) * this._inverse(x2-x1), this.p);
let path = new paper.Path();
path.strokeColor = "red";
let py = null;
let ppy = null
console.log(slope)
for(let x=0; x<this.p; x++) {
let y = mod(slope*x - slope*x1 + y1, this.p);
console.log(x, y);
if(py!==null && ppy != null && y - py != py - ppy) {
path = new paper.Path();
path.strokeColor = "red";
ppy = null;
py = null;
console.log("BRK")
}
ppy = py;
py = y;
const point = new paper.Point(x*WIDTH/this.p, HEIGHT-y*HEIGHT/this.p);
path.add(point);
if(mod(y*y, this.p) == this.f(x)) {
this._plotPoint(
x, y, "red", 6,
() => mode == 2
);
}
}
const x3 = mod(slope*slope - x1 - x2, this.p);
const y3 = mod(slope * (x1 - x3) - y1, this.p);
this._plotPoint(
x3, y3, "green", 6,
() => mode == 2
);
return [
this.selected[0],
this.selected[1],
{x: x3, y: y3}
];
}
clear() {
paper.project.clear();
}
}
let ecff;
const btnPlot = document.getElementById("btn-plot");
const btnAdd = document.getElementById("btn-add");
const btnReset = document.getElementById("btn-reset");
const pInstr = document.getElementById("instructions");
const pEqn = document.getElementById("eqn");
const pAddition = document.getElementById("addition");
pInstr.innerHTML = instructions[mode];
function toggleBtn(btn, enabled) {
if(!enabled) {
btn.className = btn.className.replace("btn-primary", "");
btn.setAttribute("disabled", "disabled");
} else {
btn.className = btn.className + " btn-primary";
btn.removeAttribute("disabled");
}
}
btnPlot.addEventListener("click", e => {
const a = parseInt(document.getElementById("eqn-a").value);
const b = parseInt(document.getElementById("eqn-b").value);
const p = parseInt(document.getElementById("eqn-p").value);
ecff = new ECFF(a, b, p);
if(ecff.plotField()) {
toggleBtn(btnPlot, false);
toggleBtn(btnAdd, true);
mode = 1;
pInstr.innerHTML = instructions[mode];
pEqn.innerHTML = "y^2 = x^3" +
(a ? " + " + a + "x" : "") +
(b ? " + " + b : "") +
" mod " + p;
}
}, false);
btnAdd.addEventListener("click", e => {
const points = ecff.add();
if(points) {
toggleBtn(btnAdd, false);
toggleBtn(btnReset, true);
mode = 2;
pInstr.innerHTML = instructions[mode];
pAddition.innerHTML = `P1(${points[0].x}, ${points[0].y}) + P2(${points[1].x}, ${points[1].y}) = P3(${points[2].x}, ${points[2].y})`;
}
}, false);
btnReset.addEventListener("click", e => {
ecff.clear();
ecff = null;
toggleBtn(btnPlot, true);
toggleBtn(btnAdd, false);
mode = 1;
pInstr.innerHTML = instructions[mode];
pAddition.innerHTML = "";
}, false);