При переносе Ed25519 на JS мы обнаружили, что в некоторых местах обычной 53-битной арифметики без потерь, поддерживаемой нативными JS float, недостаточно, и нам нужен int64.

Поэтому я взял этот полифил: https://github.com/inexorabletash/int64 и немного упростил его: с 24 Кб до 6.

Во-первых, я объединил и Int64, и Uint64 в один тип данных, потому что вопрос подписи — это не проблема хранения (под капотом все равно все хранится как дополнение до 2), а проблема операций.

Например, когда вы выполняете сдвиг вправо, вам нужно указать, хотите ли вы, чтобы ваш знаковый бит был расширен или нет.

В любом случае неподписанные типы данных имеют относительно ограниченное применение, поэтому имеет смысл использовать знаки по умолчанию.

Слияние двух типов позволило объединить функции «базового класса» с функциями, специфичными для типа. Например, в оригинале есть базовая функция uadd() и add() в каждом подтипе.

Работа с подписью на уровне операций также позволила объединить функции и совместно использовать большой объем кода:

Int64.prototype.cmp = function(v, unsign)
{
   var a = (unsign ? this.hi >>> 0 : this.hi | 0);
   var b = (unsign ? v.hi >>> 0 : v.hi | 0);
   if(a < b) return -1;
   if(a > b) return +1;
   if(this.lo < v.lo) return -1;
   if(this.lo > v.lo) return +1;
   return 0;
};

Затем выбрасываем исключения в каждую функцию следующим образом:

if(!(a instanceof Uint64)) throw TypeError(a+‘ is not an Uint64’);

глупо. Большую часть времени вы хотите смешивать Int64 с обычными числами, и каждый раз это будет падать лицом вниз!

Гораздо лучше сначала преобразовать числа в Int64:

if(!(v instanceof Int64)) v = new Int64(v);

Если вы хотите быть параноиком, вы можете проверить тип «v» в одной точке — в конструкторе.

Затем, когда вы создаете такой класс, вам нужно решить, должен ли он быть неизменяемым (как строки JS) или нет.

Быть неизменным удобно:

c = a.add(b);

Действительно, оригинал построен именно так.

Но это не оставляет вам возможности отказаться! Теперь вы не можете выполнять операции на месте, что для Int64 имеет большой смысл.

И если вы сделаете что-то вроде этого:

e = a.add(b).mul(c).shl(d);

вы клонируете объект 3 раза, и это может повлиять на производительность.

Поэтому я решил делать все операции на месте. Это возвращает выбор, когда копировать обратно пользователю:

e.from(a).add(b).mul(c).shl(d);

Остальные упрощения в основном касались более разумного именования. Кто, черт возьми, называет функцию сдвига shiftRightLogical()?! shr() вам не подходит?!

Прекратите использоватьTheFreakingCamelCaseЕго невозможно прочитать!

Но, конечно же, комитет, отвечающий за этот конкретный API, никогда не согласится на «shr»! Просто это не кажется слишком важным.

Почему-то с add() все в порядке, а с shr() — о нет!

В любом случае, теперь у нас есть хороший класс Int64, который можно использовать в любом JS-проекте:

https://github.com/NxtChg/pieces/tree/master/js/int64

портирование Ed25519 завершено на 75%.