tidecloak-js
Version:
TideCloak client side JS SDK
254 lines (220 loc) • 10.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
//
import NodeClient from "../Clients/NodeClient.js";
import OrkInfo from "../Models/Infos/OrkInfo.js";
import { CommitShardPrep, ProcessShards } from "../Math/KeyGeneration.js";
import { CurrentTime, Max, Threshold, WaitForNumberofORKs, randomiseEmails, sortORKs } from "../Tools/Utils.js";
import { RandomBigInt } from "../Cryptide/Math.js";
import { Point } from "../Cryptide/Ed25519.js";
import { bitArrayAND, bitArrayToUint8Array, bytesToBase64, deserializeBitArray, serializeBitArray, uint8ArrayToBitArray } from "../Cryptide/Serialization.js";
import { AES, DH } from "../Cryptide/index.js";
import VoucherFlow from "./VoucherFlows/VoucherFlow.js";
import NetworkClient from "../Clients/NetworkClient.js";
import SimulatorFlow from "./SimulatorFlow.js";
import { Ed25519PublicComponent } from "../Cryptide/Components/Schemes/Ed25519/Ed25519Components.js";
export default class dKeyGenerationFlow{
/**
* @param {string} uid
* @param {string} gVRK
* @param {OrkInfo[]} orks
* @param {Uint8Array} sessKey
* @param {Point} gSessKeyPub
* @param {string} purpose
* @param {string} voucherURL
* @param {string[]} emails
* @param {(0|1)[]} bitwise
* @param {string[]} selfRequesti
* @param {Point} userPublic
* @param {number} expired
*/
constructor(uid, gVRK, orks, sessKey, gSessKeyPub, purpose, voucherURL, emails=null, bitwise=null, selfRequesti=[], userPublic=null, expired=null) {
if(expired != null) if(expired < CurrentTime()) throw Error("Time has expired. Try again");
if(orks.length < Max) throw Error("Not enough orks available to create an account");
this.uid = uid;
this.gVRK = Point.fromBase64(gVRK);
this.bitwise = bitwise == null ? null : bitwise;
this.orks = sortORKs(orks);
this.selfRequesti = selfRequesti;
this.userPublic = userPublic;
this.sessKey = sessKey;
this.gSessKeyPub = gSessKeyPub;
this.purpose = purpose;
this.voucherURL = voucherURL;
this.emails = emails;
this.getVouchersFunction = null;
this.savedOrkPublics = this.orks.map(o => o.orkPublic);
this.orksToWaitFor = purpose == "NEW" ? Max : Threshold;
}
static async ReserveUID(uid, voucherURL, gSessKeyPub){
const simClient = new NetworkClient();
const availableOrks = (await simClient.FindReservers(uid));
const pre_activeOrks = SimulatorFlow.FilterInactiveOrks(availableOrks);
const reservers = availableOrks.slice(0, 5); // super unlikely all 5 orks are down
const voucherFlow = new VoucherFlow(reservers.map(o => o.orkPaymentPublic), voucherURL, "reserve");
const vouchers = (await voucherFlow.GetVouchers()).vouchers;
const reserveClients = reservers.map(r => new NodeClient(r.orkURL));
const pre_ReserveResponses = reserveClients.map((client, i) => client.ReserveUID(i, uid, 'SESSIONID', vouchers.toORK(i), gSessKeyPub))
const {fulfilledResponses} = await WaitForNumberofORKs(reservers, pre_ReserveResponses, "NEW", 1);
const lowestProximityresConf = fulfilledResponses.reduce((prev, curr) => (curr.proximity < prev.proximity ? curr : prev)); // get closest proximity
return {
reservationConfirmation: lowestProximityresConf.toString(),
activeOrks: (await pre_activeOrks)
}
}
/**
* @param {(request: string) => Promise<string> } getVouchersFunction
* @returns {dKeyGenerationFlow}
*/
setVoucherRetrievalFunction(getVouchersFunction){
this.getVouchersFunction = getVouchersFunction;
return this;
}
/**
* @param {number} numKeys
* @param {Point[]} gMultipliers
* @param {string} reservationAuth
*/
async GenShard(numKeys, gMultipliers, reservationAuth=null) {
const clients = this.orks.map(ork => new NodeClient(ork.orkURL)) // create node clients
let vouchers;
if(this.purpose == "NEW"){
if(reservationAuth == null) throw Error("reservationAuth must not be null for new keys");
const voucherFlow = new VoucherFlow(this.orks.map(o => o.orkPaymentPublic), this.voucherURL, "signup");
vouchers = (await voucherFlow.GetVouchers()).vouchers;
}
let blurs = [];
const gBluredMultipliers = gMultipliers.map(gMul => {
if(gMul != null){
const b = RandomBigInt();
blurs.push(b);
return gMul.mul(b);
}else{
blurs.push(null);
return null;
}
})
const ids = this.orks.map(ork => ork.orkID);
let count = 0;
const pre_GenShardResponses = clients.map((client, i) => {
let auth = "";
if(this.bitwise != null){
if(this.bitwise[i] == true){
auth = this.selfRequesti[count];
count++;
}
}
if(this.purpose == "NEW") return client.GenShard(i, this.uid, vouchers.toORK(i), reservationAuth, this.purpose, ids, numKeys, gBluredMultipliers, this.gSessKeyPub);
else return client.UpdateShard(i, this.uid, this.purpose, gBluredMultipliers, auth, this.gSessKeyPub, auth == "");
});
// create prkECDHi here to save time
const prkECHi = await DH.generateECDHi(this.orks.map(o => o.orkPublic), this.sessKey);
const {fulfilledResponses, bitwise} = await WaitForNumberofORKs(
this.orks,
pre_GenShardResponses,
this.purpose,
this.orksToWaitFor,
null,
prkECHi,
null,
this.purpose == "NEW" ? null : (result) => {
if(result.tag == true) return false; // tag == "", inactive ork, don't add to promises to wait for
else return true; // active ork
});
this.gState = {
bitwise: bitwise,
keyUse: ["", ""], // not VVK, doesn't matter
prkECHi,
... await ProcessShards(fulfilledResponses, bitwise, this.sessKey)
};
const UnblurredGMultipled = this.gState.gMultiplied.map((gMultiplied, i) => {
if(gMultiplied != null){
return gMultiplied.public.divide(blurs[i]);
}else{
return null;
}
});
return {gMultiplied: UnblurredGMultipled, gK: this.gState.gK}
}
/**
* @param {string} auth
* @param {string} authSig
*/
async GenVVKShard(auth, authSig) {
const clients = this.orks.map(ork => new NodeClient(ork.orkURL)) // create node clients
const {vouchers} = await (new VoucherFlow(this.orks.map(o => o.orkPaymentPublic), this.voucherURL, "vendorcreation").GetVouchers(this.getVouchersFunction));
const ids = this.orks.map(ork => ork.orkID);
const pre_GenShardResponses = clients.map((client, i) => client.GenVVKShard(this.uid, this.gVRK, auth, authSig, ids, vouchers.toORK(i)));
// create prkECDHi here to save time
const prkECHi = await DH.generateECDHi(this.orks.map(o => o.orkPublic), this.sessKey);
const fulfilledResponses = await Promise.all(pre_GenShardResponses);
const bitwise = new Array(Max).fill(1); // all must respond
this.gState = {
bitwise: bitwise,
prkECHi,
... await ProcessShards(fulfilledResponses, bitwise, this.sessKey, true)
};
return {gK: this.gState.gK}
}
/**
* @param {string} authorizer
* @param {string} keyType
*/
async SetShard(authorizer, keyType) {
if(this.gState == undefined) throw Error("GState is not defined");
const clients = this.orks.map(ork => new NodeClient(ork.orkURL)) // create node clients
const randomisedEmails = randomiseEmails(this.emails);
// No pretty place to put this line
const pre_encAuthi = this.gState.prkECHi.map(async (dh, i) => await AES.encryptData(JSON.stringify({
'GR': this.gState.gR.toBase64(),
'Auth': authorizer,
'Email': randomisedEmails[i],
'InitGVRK_GR': keyType == "VVK" ? this.gState.VRK_gR.map(gr => gr.toBase64()) : [],
}), dh));
const encAuthi = await Promise.all(pre_encAuthi);
const pre_SendShardResponses = clients.map((client, i) => client.SetShard(this.uid, this.gState.sortedShares[i], encAuthi[i], this.gSessKeyPub, keyType))
const SendShardResponses = await Promise.all(pre_SendShardResponses);
this.sState = await CommitShardPrep(
this.uid,
SendShardResponses,
this.savedOrkPublics,
this.gState.timestamp,
this.gState.gR,
this.userPublic == null ? this.gState.gK : this.userPublic,
this.bitwise == null ? this.gState.bitwise : bitArrayAND(this.gState.bitwise, this.bitwise), // given a list of orks that responded to genshard, and a list that were requested to be active, determine the particpating orks
this.purpose,
this.sessKey,
this.gSessKeyPub,
keyType == "VVK" ? this.gState.VRK_gR : null,
keyType == "VVK" ? authorizer : null,
);
this.gState = undefined;
const resp = {
"VRK_SIGNATURE_TO_STORE": this.sState.vrkSignatureToStore ,
"M": bytesToBase64(this.sState.M),
"FIRST_ADMIN": this.sState.firstAdmin
}
return resp;
}
/**
*/
async Commit() {
if(this.sState == undefined) throw Error("SState is undefined");
const clients = this.orks.map(ork => new NodeClient(ork.orkURL)) // create node clients
const pre_CommitResponses = clients.map(client => client.Commit(this.uid, this.sState.S, this.sState.gSessKeyPub));
await Promise.all(pre_CommitResponses);
}
}