tidecloak-js
Version:
TideCloak client side JS SDK
243 lines (233 loc) • 12.8 kB
JavaScript
//
// Tide Protocol - Infrastructure for a TRUE Zero-Trust paradigm
// Copyright (C) 2022 Tide Foundation Ltd
//
// This program is free software and is subject to the terms of
// the Tide Community Open Code License as published by the
// Tide Foundation Limited. You may modify it and redistribute
// it in accordance with and subject to the terms of that License.
// This program is distributed WITHOUT WARRANTY of any kind,
// including without any implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
// See the Tide Community Open Code License for more details.
// You should have received a copy of the Tide Community Open
// Code License along with this program.
// If not, see https://tide.org/licenses_tcoc2-0-0-en
//
// Some parts of the code were taken from @noble/curves project and are protected under the following license:
//
// The MIT License (MIT)
//
// Copyright (c) 2022 Paul Miller (https://paulmillr.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import { Point } from "../Ed25519.js";
import { ConcatUint8Arrays, BigIntFromByteArray } from "../Serialization.js";
import { mod, mod_inv } from "../Math.js";
import { SHA512_Digest } from "./Hash.js";
const curveP = BigInt("57896044618658097711785492504343953926634992332820282019728792003956564819949");
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2);
function Fp_FpInvertBatch(nums) {
const tmp = new Array(nums.length);
// Walk from first to last, multiply them by each other MOD p
const lastMultiplied = nums.reduce((acc, num, i) => {
if (num===_0n)
return acc;
tmp[i] = acc;
return multiply_nums(acc, num);
}, _1n);
// Invert last element
const inverted = mod_inv(lastMultiplied,curveP);
// Walk from last to first, multiply them by inverted each other MOD p
nums.reduceRight((acc, num, i) => {
if (num===_0n)
return acc;
tmp[i] = multiply_nums(acc, tmp[i]);
return multiply_nums(acc, num);
}, inverted);
return tmp;
};
//functions from field
function cmov(a,b,c){return(c ? b : a);}; //returns b if c is true and returns a if c is false
function add_nums(num1,num2,modulus=curveP){return mod(num1+num2,modulus);}; //adds 2 numbers together then uses mod to ensure that they are not greater than the curveP value
function multiply_nums(num1,num2,modulus=curveP){return mod(BigInt(num1*num2),modulus);}; //multiplies numbers then reduces them below curveP
function to_the_power_of(number,power,modulus=curveP){
if (power < _0n)
throw new Error('Expected power > 0');
if (power === _0n)
return _1n;
if (power === _1n)
return number;
let p = _1n;
let d = number;
while (power > _0n) {
if (power & _1n)
p = multiply_nums(p, d, modulus);
d = multiply_nums(d, d, modulus);
power >>= _1n;
};
return p;
};// raises a number to a certain power while keeping values below curveP
//constants used in the map_to_curve functions
const ELL2_C1_EDWARDS = BigInt('6853475219497561581579357271197624642482790079785650197046958215289687604742')//hard coded as the field only has one value in this case Fp = Field(ED25519_P, undefined, true); ELL2_C1_EDWARDS = FpSqrtEven(Fp, Fp.neg(BigInt(486664))); // sgn0(c1) MUST equal 0
const ELL2_C1 = (curveP + BigInt(3)) / BigInt(8); // 1. c1 = (q + 3) / 8 # Integer arithmetic
const ELL2_C2 = to_the_power_of(_2n, ELL2_C1); // 2. c2 = 2^c1
const ELL2_C3 = BigInt('38214883241950591754978413199355411911188925816896391856984770930832735035197');//hard coded sqrt value of ELL2_C3 = Fp.sqrt(Fp.neg(Fp.ONE)); // 3. c3 = sqrt(-1)
const ELL2_C4 = (curveP - BigInt(5)) / BigInt(8); // 4. c4 = (q - 5) / 8 # Integer arithmetic
const ELL2_J = BigInt(486662);
function map_to_curve_elligator2_curve25519_(u) {
let tv1 = multiply_nums(u,u); // 1. tv1 = u^2
tv1 = multiply_nums(tv1, _2n); // 2. tv1 = 2 * tv1
let xd = add_nums(tv1,_1n); // 3. xd = tv1 + 1 # Nonzero: -1 is square (mod p), tv1 is not
let x1n = -ELL2_J; // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2)
let tv2 = multiply_nums(xd,xd); // 5. tv2 = xd^2
let gxd = multiply_nums(tv2, xd); // 6. gxd = tv2 * xd # gxd = xd^3
let gx1 = multiply_nums(tv1, ELL2_J); // 7. gx1 = J * tv1 # x1n + J * xd
gx1 = multiply_nums(gx1, x1n); // 8. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd
gx1 = add_nums(gx1, tv2); // 9. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2
gx1 = multiply_nums(gx1, x1n); // 10. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2
let tv3 = multiply_nums(gxd,gxd); // 11. tv3 = gxd^2
tv2 = multiply_nums(tv3,tv3); // 12. tv2 = tv3^2 # gxd^4
tv3 = multiply_nums(tv3, gxd); // 13. tv3 = tv3 * gxd # gxd^3
tv3 = multiply_nums(tv3, gx1); // 14. tv3 = tv3 * gx1 # gx1 * gxd^3
tv2 = multiply_nums(tv2, tv3); // 15. tv2 = tv2 * tv3 # gx1 * gxd^7
let y11 = to_the_power_of(tv2, ELL2_C4); // 16. y11 = tv2^c4 # (gx1 * gxd^7)^((p - 5) / 8)
y11 = multiply_nums(y11, tv3); // 17. y11 = y11 * tv3 # gx1*gxd^3*(gx1*gxd^7)^((p-5)/8)
let y12 = multiply_nums(y11, ELL2_C3); // 18. y12 = y11 * c3
tv2 = multiply_nums(y11,y11); // 19. tv2 = y11^2
tv2 = multiply_nums(tv2, gxd); // 20. tv2 = tv2 * gxd
let e1 = (tv2 === gx1); // 21. e1 = tv2 == gx1
let y1 = cmov(y12, y11, e1); // 22. y1 = CMOV(y12, y11, e1) # If g(x1) is square, this is its sqrt
let x2n = multiply_nums(x1n, tv1); // 23. x2n = x1n * tv1 # x2 = x2n / xd = 2 * u^2 * x1n / xd
let y21 = multiply_nums(y11, u); // 24. y21 = y11 * u
y21 = multiply_nums(y21, ELL2_C2); // 25. y21 = y21 * c2
let y22 = multiply_nums(y21, ELL2_C3); // 26. y22 = y21 * c3
let gx2 = multiply_nums(gx1, tv1); // 27. gx2 = gx1 * tv1 # g(x2) = gx2 / gxd = 2 * u^2 * g(x1)
tv2 = multiply_nums(y21,y21); // 28. tv2 = y21^2
tv2 = multiply_nums(tv2, gxd); // 29. tv2 = tv2 * gxd
let e2 = (tv2 === gx2); // 30. e2 = tv2 == gx2
let y2 = cmov(y22, y21, e2); // 31. y2 = CMOV(y22, y21, e2) # If g(x2) is square, this is its sqrt
tv2 = multiply_nums(y1,y1); // 32. tv2 = y1^2
tv2 = multiply_nums(tv2, gxd); // 33. tv2 = tv2 * gxd
let e3 = (tv2 === gx1); // 34. e3 = tv2 == gx1
let xn = cmov(x2n, x1n, e3); // 35. xn = CMOV(x2n, x1n, e3) # If e3, x = x1, else x = x2
let y = cmov(y2, y1, e3); // 36. y = CMOV(y2, y1, e3) # If e3, y = y1, else y = y2
let e4 = ((y&_1n)===_1n); // 37. e4 = sgn0(y) == 1 # Fix sign of y
y = cmov(y, -y, e3 !== e4); // 38. y = CMOV(y, -y, e3 XOR e4)
return { xMn: xn, xMd: xd, yMn: y, yMd: _1n }; // 39. return (xn, xd, y, 1)
}
function map_to_curve_elligator2_edwards25519_(u) {
const { xMn, xMd, yMn, yMd } = map_to_curve_elligator2_curve25519_(u); // 1. (xMn, xMd, yMn, yMd) =
// map_to_curve_elligator2_curve25519(u)
let xn = multiply_nums(xMn, yMd); // 2. xn = xMn * yMd
xn = multiply_nums(xn, ELL2_C1_EDWARDS); // 3. xn = xn * c1
let xd = multiply_nums(xMd, yMn); // 4. xd = xMd * yMn # xn / xd = c1 * xM / yM
let yn = mod(xMn - xMd,curveP); // 5. yn = xMn - xMd
let yd = add_nums(xMn, xMd); // 6. yd = xMn + xMd # (n / d - 1) / (n / d + 1) = (n - d) / (n + d)
let tv1 = multiply_nums(xd, yd); // 7. tv1 = xd * yd
let e = (tv1 === _0n); // 8. e = tv1 == 0
xn = cmov(xn, _0n, e); // 9. xn = CMOV(xn, 0, e)
xd = cmov(xd, _1n, e); // 10. xd = CMOV(xd, 1, e)
yn = cmov(yn, _1n, e); // 11. yn = CMOV(yn, 1, e)
yd = cmov(yd, _1n, e); // 12. yd = CMOV(yd, 1, e)
const inv = Fp_FpInvertBatch([xd, yd]); // batch division
return { x: multiply_nums(xn, inv[0]), y: multiply_nums(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)
}
function i2osp(value, length) {
if (value < 0 || value >= 1 << (8 * length)) {
throw new Error(`bad I2OSP call: value=${value} length=${length}`);
}
const res = Array.from({ length }).fill(0);
for (let i = length - 1; i >= 0; i--) {
res[i] = value & 0xff;
value >>>= 8;
}
return new Uint8Array(res);
}//takes a value and a length, an array is created with that length. Then takes the smallest 8 bits from the value and places it at the end of the array.
//Repeats this with the next 8 bits and places them in the next last value in the array for the rest of the value
function strxor(a, b) {
const arr = new Uint8Array(a.length);
for (let i = 0; i < a.length; i++) {
arr[i] = a[i] ^ b[i];
}
return arr;
}; //does bitwise xor on all values in 2 arrays and returns a new array with the results
/**
*
* @param {Uint8Array} msg
* @param {Uint8Array} DST
* @param {number} len_in_bytes
* @returns
*/
async function expand_message_xmd(msg, DST, len_in_bytes){
const b_in_bytes = 64;
const r_in_bytes = 128;
const ell = Math.ceil(len_in_bytes/b_in_bytes);
if (ell > 255) throw new Error('Invalid xmd length');
const DST_prime = ConcatUint8Arrays([DST, i2osp(DST.length, 1)]);
const Z_pad = i2osp(0,r_in_bytes);
const len_in_bytes_str = i2osp(len_in_bytes,2);
const b = new Array(ell);
const arr = ConcatUint8Arrays([Z_pad, msg, len_in_bytes_str, i2osp(0, 1), DST_prime])
const b_0 = await SHA512_Digest(arr);
const promise_b = SHA512_Digest((ConcatUint8Arrays([b_0, i2osp(1, 1), DST_prime])));
b[0] = await promise_b
for (let i = 1; i <= ell; i++){
const args = [strxor(b_0, b[i-1]),i2osp(i+1, 1), DST_prime];
b[i] = await SHA512_Digest(ConcatUint8Arrays(args));
}
const pseudo_random_bytes = ConcatUint8Arrays(b);
return pseudo_random_bytes.slice(0, len_in_bytes);
}; //a message and a DST that are encoded into Uint8arrays are hashed into a certain number of values according to len_in_bytes
async function hashtofield(msg){
const _DST = 'QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_RO_', m = 1, count = 2, k = 128, p = BigInt('57896044618658097711785492504343953926634992332820282019728792003956564819949');
const DST = new TextEncoder().encode(_DST);
const log2p = p.toString(2).length;
const L = Math.ceil((log2p+k)/8);
const len_in_bytes = count * m * L;
let prb = await expand_message_xmd(msg, DST, len_in_bytes);
const u = new Array(count)
for (let i = 0; i < count; i++) {
const e = new Array(m);
for (let j = 0; j < m; j++) {
const elm_offset = L * (j + i * m);
const tv = prb.subarray(elm_offset, elm_offset + L);
e[j] = mod(BigIntFromByteArray(tv.reverse()), p);
}
u[i] = e;
}
return u;
}; //takes in a message hashes it with expand_message_xmd and splits the resulting value into 2 parts
/**
* Hashes a msg to a point on the ed25519 curve.
* @param {string|Uint8Array} msg
* @returns {Promise<Point>}
*/
export default async function HashToPoint(msg){
const arr = typeof (msg) === 'string' ? new TextEncoder().encode(msg) : msg;
const u = await hashtofield(arr)
const x0y0 = map_to_curve_elligator2_edwards25519_(u[0][0]);
const x1y1 = map_to_curve_elligator2_edwards25519_(u[1][0]);
const p0 = Point.fromAffine(x0y0);
const p1 = Point.fromAffine(x1y1);
const P = p0.add(p1).clearCofactor();
return P;
}; //hashtofield takes an Uint8array encoded message and gives back 2 values. the map_to_curve function then uses those values to generate 2 x and y values
// 2 Points are created using the x and y values. The points are added to each other and then are muliplied by 8 to give the final point.