<canvas id="canvas"></canvas>
<div class="parameters">
  <div class="row">
    <div class="col-md-5 form-group">
      <div class="input-group">
        <span class="input-group-addon">a</span>
        <input id="eqn-a" type="number" class="form-control" value="-7">
      </div>
    </div>
    <div class="col-md-5 form-group">
      <div class="input-group">
        <span class="input-group-addon">b</span>
        <input id="eqn-b" type="number" class="form-control" value=0>
      </div>
    </div>
  </div>
  <div class="row">
    <div class="col-md-5 form-group">
      <div class="input-group">
        <span class="input-group-addon">p</span>
        <input id="eqn-p" type="number" class="form-control" value="67">
      </div>
    </div>
  </div>
  <div class="row">
    <div class="col-md-10 form-group">
      <p id="eqn"></p>
      <p id="addition"></p>
    </div>
  </div>
  <div class="row">
    <div class="col-md-10 form-group">
      <p id="instructions"></p>
    </div>
  </div>
  <div class="row">
    <div class="col-md-10 form-group">
      <button id="btn-plot" class="btn btn-primary">1. Plot</button>
      <button id="btn-add" class="btn" disabled>2. Add</button>
      <button id="btn-reset" class="btn btn-danger">Reset</button>
    </div>
  </div>
<!-- <div id="points-to-add" style="display:none">
    <div class="row">
      <div class="col-md-5 form-group">
        <div class="input-group">
          <span class="input-group-addon">x1</span>
          <input id="x1" type="number" class="form-control">
        </div>
      </div>
      <div class="col-md-5 form-group">
        <div class="input-group">
          <span class="input-group-addon">y1</span>
          <input id="y1" type="number" class="form-control">
        </div>
      </div>
    </div>
    <div class="row">
      <div class="col-md-5 form-group">
        <div class="input-group">
          <span class="input-group-addon">x2</span>
          <input id="x2" type="number" class="form-control">
        </div>
      </div>
      <div class="col-md-5 form-group">
        <div class="input-group">
          <span class="input-group-addon">y2</span>
          <input id="y2" type="number" class="form-control">
        </div>
      </div>
    </div>
    <div class="row">
      <div class="col-md-5 form-group">
        <button id="btn-add" class="btn btn-primary">Add</button>
      </div>
    </div>
  </div> -->
</div>
<div class="tooltip">(88, 88)</div>
#canvas {
  border: solid 1px #ccc;
  margin: 10px;
}
.parameters {
  position: absolute;
  left: 440px;
  top: 10px;
}
.tooltip {
  position: absolute;
  z-index: 2;
  border-radius: 2px;
  background: #0069dc;
  color: #fff;
  padding: 2px 4px;
  opacity: 0;
  transition: opacity 0.2s;
}
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);
Run Pen

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.3/paper-full.min.js