Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 15.0.0 #138

Merged
merged 69 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
1789692
[IRT-741] feat: Enable share import to call only to a proxy server in…
huggingbot Sep 8, 2023
b624844
[IRT-741] fix: Use hash of verifier and verifierId rather thn pub keys
huggingbot Oct 2, 2023
7d2fc60
[IRT-741] fix: Remove sorting shares as we will map data on backend t…
huggingbot Oct 3, 2023
99bc575
[IRT-741] fix: Remove unused constant
huggingbot Oct 3, 2023
a957ebf
import share tests fixed
Oct 24, 2023
02168d0
Merge branch 'master' into feat/atomic-share-import
Oct 24, 2023
8586ecc
add ed25519 key support
Nov 29, 2023
4e603df
Merge branch 'master' into fix/ed25519-keys
Dec 10, 2023
3066336
add key type support for ed25519
Dec 11, 2023
badf280
fix ed25519 imports
Dec 12, 2023
d20b635
skip ed25519 as us region is not updated yet
Dec 12, 2023
fe96658
tests fixed
Dec 12, 2023
6760d85
add method to login with non dkg keys
Dec 21, 2023
0b9aff1
fix review comments
Dec 21, 2023
d9ac909
readd one key tests
Dec 21, 2023
331a9ef
fix generate priv key
Jan 2, 2024
c8ed4d4
fix celeste tests
Jan 2, 2024
c70b131
fix key type
Jan 9, 2024
119c01c
make import as default for ed25519
Jan 15, 2024
585b89b
merge with alpha
Mar 8, 2024
52d93e9
helpers functions to generate ed25519 key data
Mar 10, 2024
12fb612
working ed25519 key pairs
Mar 15, 2024
6950486
fix tests for ed25519 keys
Mar 18, 2024
855aca0
return pubkey in b58
Mar 19, 2024
d9dab31
fix tests
Mar 19, 2024
bd59e70
fix all tests
Mar 20, 2024
60824d3
Merge pull request #126 from torusresearch/fix/ed25519-keys
himanshuchawla009 Mar 20, 2024
3107b99
package lock updated
Mar 20, 2024
02bb409
13.0.0-alpha.0
Mar 20, 2024
802f9fc
Release 13.0.0-alpha.1
Mar 20, 2024
04c151c
fix metadata signing key for ed25519
Apr 4, 2024
0acead9
Release 13.0.0-alpha.2
Apr 4, 2024
5252dff
return final ed25519 key in seed hex format
Apr 4, 2024
a911948
Release 13.0.0-alpha.3
Apr 4, 2024
5fd555d
fix retrieve shares for test tss verifier
Apr 23, 2024
48cf350
Release 13.0.0-alpha.4
Apr 23, 2024
6b5261f
retry decryption with unpadded cipher txt
Apr 30, 2024
37f69ca
skip celeste tests because of recent migration
Apr 30, 2024
b55aaba
pad cipher text post error
May 2, 2024
e7887fe
Merge pull request #139 from torusresearch/fix/cipher-padding
chaitanyapotti May 6, 2024
99052f0
merge with master
May 8, 2024
a52abb1
temp
May 8, 2024
c4a5aaf
error log fixes
May 8, 2024
9cca752
better error msgs
May 9, 2024
49094e4
return postbox key data obj
May 9, 2024
99b7a77
Release 13.0.0-alpha.5
May 9, 2024
68d1bd2
update function to return correct postbox key
May 14, 2024
8f930ea
fix metadata msg expiry test case
May 14, 2024
11a4384
Release 13.0.0-alpha.6
May 16, 2024
9c8d4c6
add sapphire mainnet support for ed25519
May 31, 2024
6c7e323
disable logging
May 31, 2024
30cf1d9
Release 13.0.0-alpha.7
May 31, 2024
a102e65
merge with master
Jun 18, 2024
2b39412
enable mainnet import test
Jun 18, 2024
a981bc2
merge with masteR
Jul 11, 2024
7d0f687
fix http calls
Jul 11, 2024
abdd7a5
use constants for curves
Jul 11, 2024
74e5536
remove unused function
Jul 11, 2024
4b98a63
readd encode point function
Jul 11, 2024
2f98f9c
merge with masteR
Jul 16, 2024
095e77f
fix node details import in tests
Jul 16, 2024
b509d35
15.0.0-alpha.0
Jul 22, 2024
974cbc1
Release 15.0.0-alpha.1
Jul 22, 2024
43f7444
refactor: add types for extra params
metalurgical Jul 24, 2024
3bbba20
Merge pull request #153 from torusresearch/extra_param_types
himanshuchawla009 Jul 29, 2024
141e936
update param structure for retrieve shares and import key
Jul 29, 2024
7fa95fb
fix test
Jul 30, 2024
fcaba4b
minor refactor
chaitanyapotti Jul 30, 2024
7df0374
Merge pull request #154 from torusresearch/feat/review-changes
himanshuchawla009 Jul 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@toruslabs/http-helpers": "^7.0.0",
"bn.js": "^5.2.1",
"elliptic": "^6.5.5",
"bs58": "^5.0.0",
"ethereum-cryptography": "^2.2.1",
"json-stable-stringify": "^1.1.1",
"loglevel": "^1.9.1"
Expand Down
6 changes: 5 additions & 1 deletion src/Point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ class Point {
encode(enc: string): Buffer {
switch (enc) {
case "arr":
return Buffer.concat([Buffer.from("04", "hex"), Buffer.from(this.x.toString("hex"), "hex"), Buffer.from(this.y.toString("hex"), "hex")]);
return Buffer.concat([
Buffer.from("04", "hex"),
chaitanyapotti marked this conversation as resolved.
Show resolved Hide resolved
Buffer.from(this.x.toString("hex", 64), "hex"),
Buffer.from(this.y.toString("hex", 64), "hex"),
]);
case "elliptic-compressed": {
const key = this.ecCurve.keyFromPublic({ x: this.x.toString("hex", 64), y: this.y.toString("hex", 64) }, "hex");
return Buffer.from(key.getPublic(true, "hex"));
Expand Down
4 changes: 2 additions & 2 deletions src/Share.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ class Share {

toJSON(): StringifiedType {
return {
share: this.share.toString("hex"),
shareIndex: this.shareIndex.toString("hex"),
share: this.share.toString("hex", 64),
chaitanyapotti marked this conversation as resolved.
Show resolved Hide resolved
shareIndex: this.shareIndex.toString("hex", 64),
};
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const JRPC_METHODS = {
GET_OR_SET_KEY: "GetPubKeyOrKeyAssign",
COMMITMENT_REQUEST: "CommitmentRequest",
IMPORT_SHARE: "ImportShare",
IMPORT_SHARES: "ImportShares",
GET_SHARE_OR_KEY_ASSIGN: "GetShareOrKeyAssign",
};

Expand Down
22 changes: 20 additions & 2 deletions src/helpers/common.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { JRPCResponse } from "@toruslabs/constants";
import { JRPCResponse, KEY_TYPE } from "@toruslabs/constants";
import { Ecies } from "@toruslabs/eccrypto";
import { BN } from "bn.js";
import { ec as EC } from "elliptic";
import JsonStringify from "json-stable-stringify";

import { CommitmentRequestResult, EciesHex, VerifierLookupResponse } from "../interfaces";
import { CommitmentRequestResult, EciesHex, KeyType, VerifierLookupResponse } from "../interfaces";
import { keccak256 } from "./keyUtils";

export const getKeyCurve = (keyType: KeyType) => {
if (keyType === KEY_TYPE.ED25519) {
return new EC(KEY_TYPE.ED25519);
} else if (keyType === KEY_TYPE.SECP256K1) {
return new EC(KEY_TYPE.SECP256K1);
}
throw new Error(`Invalid keyType: ${keyType}`);
};
// this function normalizes the result from nodes before passing the result to threshold check function
// For ex: some fields returns by nodes might be different from each other
// like created_at field might vary and nonce_data might not be returned by all nodes because
Expand Down Expand Up @@ -86,6 +97,13 @@ export function encParamsHexToBuf(eciesData: Omit<EciesHex, "ciphertext">): Omit
};
}

export function getProxyCoordinatorEndpointIndex(endpoints: string[], verifier: string, verifierId: string) {
const verifierIdStr = `${verifier}${verifierId}`;
const hashedVerifierId = keccak256(Buffer.from(verifierIdStr, "utf8")).slice(2);
const proxyEndpointNum = new BN(hashedVerifierId, "hex").mod(new BN(endpoints.length)).toNumber();
chaitanyapotti marked this conversation as resolved.
Show resolved Hide resolved
return proxyEndpointNum;
}

export function calculateMedian(arr: number[]): number {
const arrSize = arr.length;

Expand Down
243 changes: 233 additions & 10 deletions src/helpers/keyUtils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { INodePub, KEY_TYPE } from "@toruslabs/constants";
import { Ecies, encrypt } from "@toruslabs/eccrypto";
import BN from "bn.js";
import { ec } from "elliptic";
import base58 from "bs58";
import { curve, ec as EC } from "elliptic";
import { keccak256 as keccakHash } from "ethereum-cryptography/keccak";
import { sha512 } from "ethereum-cryptography/sha512";
import stringify from "json-stable-stringify";
import log from "loglevel";

import { EncryptedSeed, ImportedShare, KeyType, PrivateKeyData } from "../interfaces";
import { encParamsBufToHex, getKeyCurve } from "./common";
import { generateRandomPolynomial } from "./langrangeInterpolatePoly";
import { generateNonceMetadataParams } from "./metadataUtils";

export function keccak256(a: Buffer): string {
const hash = Buffer.from(keccakHash(a)).toString("hex");
return `0x${hash}`;
}

export const generatePrivateKey = (ecCurve: EC, buf: typeof Buffer): Buffer => {
return ecCurve.genKeyPair().getPrivate().toArrayLike(buf);
};

export function stripHexPrefix(str: string): string {
return str.startsWith("0x") ? str.slice(2) : str;
}
Expand All @@ -29,22 +44,230 @@ export function toChecksumAddress(hexAddress: string): string {
return ret;
}

export function generateAddressFromPrivKey(ecCurve: ec, privateKey: BN): string {
function adjustScalarBytes(bytes: Buffer): Buffer {
// Section 5: For X25519, in order to decode 32 random bytes as an integer scalar,
// set the three least significant bits of the first byte
bytes[0] &= 248; // 0b1111_1000
// and the most significant bit of the last to zero,
bytes[31] &= 127; // 0b0111_1111
// set the second most significant bit of the last byte to 1
bytes[31] |= 64; // 0b0100_0000
return bytes;
}

/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
export function getEd25519ExtendedPublicKey(keyBuffer: Buffer): {
scalar: BN;
point: curve.base.BasePoint;
} {
const ed25519Curve = getKeyCurve(KEY_TYPE.ED25519);
const len = 32;
const G = ed25519Curve.g;
const N = ed25519Curve.n;

if (keyBuffer.length !== 32) {
log.error("Invalid seed for ed25519 key derivation", keyBuffer.length);
throw new Error("Invalid seed for ed25519 key derivation");
}
// Hash private key with curve's hash function to produce uniformingly random input
// Check byte lengths: ensure(64, h(ensure(32, key)))
const hashed = sha512(keyBuffer);
if (hashed.length !== 64) {
throw new Error("Invalid hash length for ed25519 seed");
}
const head = new BN(adjustScalarBytes(Buffer.from(hashed.slice(0, len))), "le");
const scalar = new BN(head.umod(N), "le"); // The actual private scalar
const point = G.mul(scalar) as curve.base.BasePoint; // Point on Edwards curve aka public key
return { scalar, point };
}

export const getSecpKeyFromEd25519 = (
ed25519Scalar: BN
): {
scalar: BN;
point: curve.base.BasePoint;
} => {
const secp256k1Curve = getKeyCurve(KEY_TYPE.SECP256K1);

const ed25519Key = ed25519Scalar.toString("hex", 64);
const keyHash = keccakHash(Buffer.from(ed25519Key, "hex"));
const secpKey = new BN(keyHash).umod(secp256k1Curve.curve.n).toString("hex", 64);
const bufferKey = Buffer.from(secpKey, "hex");

const secpKeyPair = secp256k1Curve.keyFromPrivate(bufferKey);

if (bufferKey.length < 32) {
throw new Error("Invalid key length, please try again");
}
return {
scalar: secpKeyPair.getPrivate(),
point: secpKeyPair.getPublic(),
};
};

export function encodeEd25519Point(point: curve.base.BasePoint) {
const ed25519Curve = getKeyCurve(KEY_TYPE.ED25519);

const encodingLength = Math.ceil(ed25519Curve.n.bitLength() / 8);
const enc = point.getY().toArrayLike(Buffer, "le", encodingLength);
enc[encodingLength - 1] |= point.getX().isOdd() ? 0x80 : 0;
return enc;
}

export const generateEd25519KeyData = async (ed25519Seed: Buffer): Promise<PrivateKeyData> => {
const ed25519Curve = getKeyCurve(KEY_TYPE.ED25519);

const finalEd25519Key = getEd25519ExtendedPublicKey(ed25519Seed);
const encryptionKey = getSecpKeyFromEd25519(finalEd25519Key.scalar);
const encryptedSeed = await encrypt(Buffer.from(encryptionKey.point.encodeCompressed("hex"), "hex"), ed25519Seed);
const encData: EncryptedSeed = {
enc_text: encryptedSeed.ciphertext.toString("hex"),
metadata: encParamsBufToHex(encryptedSeed),
public_key: encodeEd25519Point(finalEd25519Key.point).toString("hex"),
};

const encDataBase64 = Buffer.from(JSON.stringify(encData), "utf-8").toString("base64");
const metadataPrivNonce = ed25519Curve.genKeyPair().getPrivate();
const oauthKey = finalEd25519Key.scalar.sub(metadataPrivNonce).umod(ed25519Curve.n);
const oauthKeyPair = ed25519Curve.keyFromPrivate(oauthKey.toArrayLike(Buffer));
const metadataSigningKey = getSecpKeyFromEd25519(oauthKeyPair.getPrivate());
return {
oAuthKeyScalar: oauthKeyPair.getPrivate(),
oAuthPubX: oauthKeyPair.getPublic().getX(),
oAuthPubY: oauthKeyPair.getPublic().getY(),
SigningPubX: metadataSigningKey.point.getX(),
SigningPubY: metadataSigningKey.point.getY(),
metadataNonce: metadataPrivNonce,
metadataSigningKey: metadataSigningKey.scalar,
encryptedSeed: encDataBase64,
finalUserPubKeyPoint: finalEd25519Key.point,
};
};

export const generateSecp256k1KeyData = async (scalarBuffer: Buffer): Promise<PrivateKeyData> => {
const secp256k1Curve = getKeyCurve(KEY_TYPE.SECP256K1);

const scalar = new BN(scalarBuffer);
const randomNonce = new BN(generatePrivateKey(secp256k1Curve, Buffer));
const oAuthKey = scalar.sub(randomNonce).umod(secp256k1Curve.curve.n);
const oAuthKeyPair = secp256k1Curve.keyFromPrivate(oAuthKey.toString("hex").padStart(64, "0"));
const oAuthPubKey = oAuthKeyPair.getPublic();

const finalUserKeyPair = secp256k1Curve.keyFromPrivate(scalar.toString("hex", 64));

return {
oAuthKeyScalar: oAuthKeyPair.getPrivate(),
oAuthPubX: oAuthPubKey.getX(),
oAuthPubY: oAuthPubKey.getY(),
SigningPubX: oAuthPubKey.getX(),
SigningPubY: oAuthPubKey.getY(),
metadataNonce: randomNonce,
encryptedSeed: "",
metadataSigningKey: oAuthKeyPair.getPrivate(),
finalUserPubKeyPoint: finalUserKeyPair.getPublic(),
};
};

export function generateAddressFromPrivKey(keyType: KeyType, privateKey: BN): string {
const ecCurve = getKeyCurve(keyType);
const key = ecCurve.keyFromPrivate(privateKey.toString("hex", 64), "hex");
const publicKey = key.getPublic().encode("hex", false).slice(2);
const evmAddressLower = `0x${keccak256(Buffer.from(publicKey, "hex")).slice(64 - 38)}`;
return toChecksumAddress(evmAddressLower);
if (keyType === KEY_TYPE.SECP256K1) {
const publicKey = key.getPublic().encode("hex", false).slice(2);
const evmAddressLower = `0x${keccak256(Buffer.from(publicKey, "hex")).slice(64 - 38)}`;
return toChecksumAddress(evmAddressLower);
} else if (keyType === KEY_TYPE.ED25519) {
const publicKey = encodeEd25519Point(key.getPublic());
const address = base58.encode(publicKey);
return address;
}
throw new Error(`Invalid keyType: ${keyType}`);
}

export function generateAddressFromPubKey(ecCurve: ec, publicKeyX: BN, publicKeyY: BN): string {
export function generateAddressFromPubKey(keyType: KeyType, publicKeyX: BN, publicKeyY: BN): string {
const ecCurve = getKeyCurve(keyType);
const key = ecCurve.keyFromPublic({ x: publicKeyX.toString("hex", 64), y: publicKeyY.toString("hex", 64) });
const publicKey = key.getPublic().encode("hex", false).slice(2);
const evmAddressLower = `0x${keccak256(Buffer.from(publicKey, "hex")).slice(64 - 38)}`;
return toChecksumAddress(evmAddressLower);
if (keyType === KEY_TYPE.SECP256K1) {
const publicKey = key.getPublic().encode("hex", false).slice(2);
const evmAddressLower = `0x${keccak256(Buffer.from(publicKey, "hex")).slice(64 - 38)}`;
return toChecksumAddress(evmAddressLower);
} else if (keyType === KEY_TYPE.ED25519) {
const publicKey = encodeEd25519Point(key.getPublic());
const address = base58.encode(publicKey);
return address;
}
throw new Error(`Invalid keyType: ${keyType}`);
}

export function getPostboxKeyFrom1OutOf1(ecCurve: ec, privKey: string, nonce: string): string {
export function getPostboxKeyFrom1OutOf1(ecCurve: EC, privKey: string, nonce: string): string {
const privKeyBN = new BN(privKey, 16);
const nonceBN = new BN(nonce, 16);
return privKeyBN.sub(nonceBN).umod(ecCurve.curve.n).toString("hex");
}

export function derivePubKey(ecCurve: EC, sk: BN): curve.base.BasePoint {
const skHex = sk.toString(16, 64);
return ecCurve.keyFromPrivate(skHex).getPublic();
}

export const getEncryptionEC = (): EC => {
return new EC("secp256k1");
};

export const generateShares = async (
ecCurve: EC,
keyType: KeyType,
serverTimeOffset: number,
nodeIndexes: number[],
nodePubkeys: INodePub[],
privKey: Buffer
) => {
const keyData = keyType === KEY_TYPE.ED25519 ? await generateEd25519KeyData(privKey) : await generateSecp256k1KeyData(privKey);
const { metadataNonce, oAuthKeyScalar: oAuthKey, encryptedSeed, metadataSigningKey } = keyData;
const threshold = ~~(nodePubkeys.length / 2) + 1;
const degree = threshold - 1;
const nodeIndexesBn: BN[] = [];

for (const nodeIndex of nodeIndexes) {
nodeIndexesBn.push(new BN(nodeIndex));
}
const oAuthPubKey = ecCurve.keyFromPrivate(oAuthKey.toString("hex").padStart(64, "0")).getPublic();
const poly = generateRandomPolynomial(ecCurve, degree, oAuthKey);
const shares = poly.generateShares(nodeIndexesBn);
const nonceParams = generateNonceMetadataParams(serverTimeOffset, "getOrSetNonce", metadataSigningKey, keyType, metadataNonce, encryptedSeed);
const nonceData = Buffer.from(stringify(nonceParams.set_data), "utf8").toString("base64");
const sharesData: ImportedShare[] = [];
const encPromises: Promise<Ecies>[] = [];
for (let i = 0; i < nodeIndexesBn.length; i++) {
const shareJson = shares[nodeIndexesBn[i].toString("hex", 64)].toJSON() as Record<string, string>;
if (!nodePubkeys[i]) {
throw new Error(`Missing node pub key for node index: ${nodeIndexesBn[i].toString("hex", 64)}`);
}
const nodePubKey = getEncryptionEC().keyFromPublic({ x: nodePubkeys[i].X, y: nodePubkeys[i].Y });
encPromises.push(
encrypt(Buffer.from(nodePubKey.getPublic().encodeCompressed("hex"), "hex"), Buffer.from(shareJson.share.padStart(64, "0"), "hex"))
);
}
const encShares = await Promise.all(encPromises);
for (let i = 0; i < nodeIndexesBn.length; i++) {
const shareJson = shares[nodeIndexesBn[i].toString("hex", 64)].toJSON() as Record<string, string>;
const encParams = encShares[i];
const encParamsMetadata = encParamsBufToHex(encParams);
const shareData: ImportedShare = {
encrypted_seed: keyData.encryptedSeed,
final_user_point: keyData.finalUserPubKeyPoint,
oauth_pub_key_x: oAuthPubKey.getX().toString("hex"),
oauth_pub_key_y: oAuthPubKey.getY().toString("hex"),
signing_pub_key_x: keyData.SigningPubX.toString("hex"),
signing_pub_key_y: keyData.SigningPubY.toString("hex"),
encrypted_share: encParamsMetadata.ciphertext,
encrypted_share_metadata: encParamsMetadata,
node_index: Number.parseInt(shareJson.shareIndex, 16),
key_type: keyType,
nonce_data: nonceData,
nonce_signature: nonceParams.signature,
};
sharesData.push(shareData);
}

return sharesData;
};
Loading