Skip to content

Commit c0a29d9

Browse files
committed
refactor: enhance utility functions and type handling in common, keyUtils, and metadataUtils
1 parent 20f5c41 commit c0a29d9

File tree

3 files changed

+184
-159
lines changed

3 files changed

+184
-159
lines changed

src/helpers/common.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import { invert, mod } from "@noble/curves/abstract/modular.js";
22
import { ed25519 } from "@noble/curves/ed25519.js";
33
import { secp256k1 } from "@noble/curves/secp256k1.js";
4-
import { bytesToHex, bytesToNumberBE, concatBytes, hexToBytes, hexToNumber, numberToBytesBE, numberToHexUnpadded } from "@noble/curves/utils.js";
4+
import {
5+
bytesToHex,
6+
bytesToNumberBE,
7+
bytesToNumberLE,
8+
concatBytes,
9+
hexToBytes,
10+
hexToNumber,
11+
numberToBytesBE,
12+
numberToHexUnpadded,
13+
} from "@noble/curves/utils.js";
514
import { JRPCResponse, KEY_TYPE } from "@toruslabs/constants";
615
import { Ecies } from "@toruslabs/eccrypto";
716
import { keccak256 as keccakHash } from "ethereum-cryptography/keccak";
@@ -12,7 +21,7 @@ import { CommitmentRequestResult, EciesHex, GetORSetKeyResponse, KeyType, Verifi
1221
export type Curve = typeof secp256k1 | typeof ed25519;
1322

1423
// Re-export noble utilities for use across the codebase
15-
export { bytesToHex, bytesToNumberBE, concatBytes, hexToBytes, invert, mod, numberToBytesBE };
24+
export { bytesToHex, bytesToNumberBE, bytesToNumberLE, concatBytes, hexToBytes, invert, mod, numberToBytesBE };
1625

1726
// Convert a hex string or bigint to bigint. Wraps noble's hexToNumber with empty-string safety.
1827
export function toBigIntBE(val: string | bigint): bigint {

src/helpers/keyUtils.ts

Lines changed: 97 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
1+
import { mod } from "@noble/curves/abstract/modular.js";
12
import { bs58 } from "@toruslabs/bs58";
23
import { INodePub, KEY_TYPE } from "@toruslabs/constants";
34
import { Ecies, encrypt } from "@toruslabs/eccrypto";
4-
import BN from "bn.js";
5-
import { curve, ec as EC } from "elliptic";
65
import { keccak256 as keccakHash } from "ethereum-cryptography/keccak";
76
import { sha512 } from "ethereum-cryptography/sha512";
87
import stringify from "json-stable-stringify";
98
import log from "loglevel";
109

11-
import { EncryptedSeed, ImportedShare, KeyType, PrivateKeyData } from "../interfaces";
12-
import { encParamsBufToHex, generatePrivateKey, getKeyCurve, keccak256 } from "./common";
10+
import { EncryptedSeed, ImportedShare, KeyType, Point2D, PrivateKeyData } from "../interfaces";
11+
import {
12+
bigintToHex,
13+
bytesToBase64,
14+
bytesToHex,
15+
bytesToNumberBE,
16+
bytesToNumberLE,
17+
Curve,
18+
encParamsBufToHex,
19+
generatePrivateKey,
20+
getKeyCurve,
21+
getSecp256k1,
22+
hexToBytes,
23+
keccak256,
24+
toBigIntBE,
25+
utf8ToBytes,
26+
} from "./common";
1327
import { generateRandomPolynomial } from "./langrangeInterpolatePoly";
1428
import { generateNonceMetadataParams, getSecpKeyFromEd25519 } from "./metadataUtils";
1529

@@ -20,8 +34,7 @@ export function stripHexPrefix(str: string): string {
2034
export function toChecksumAddress(hexAddress: string): string {
2135
const address = stripHexPrefix(hexAddress).toLowerCase();
2236

23-
const buf = Buffer.from(address, "utf8");
24-
const hash = Buffer.from(keccakHash(buf)).toString("hex");
37+
const hash = bytesToHex(keccakHash(utf8ToBytes(address)));
2538
let ret = "0x";
2639

2740
for (let i = 0; i < address.length; i++) {
@@ -35,7 +48,7 @@ export function toChecksumAddress(hexAddress: string): string {
3548
return ret;
3649
}
3750

38-
function adjustScalarBytes(bytes: Buffer): Buffer {
51+
function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
3952
// Section 5: For X25519, in order to decode 32 random bytes as an integer scalar,
4053
// set the three least significant bits of the first byte
4154
bytes[0] &= 248; // 0b1111_1000
@@ -47,14 +60,13 @@ function adjustScalarBytes(bytes: Buffer): Buffer {
4760
}
4861

4962
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
50-
export function getEd25519ExtendedPublicKey(keyBuffer: Buffer): {
51-
scalar: BN;
52-
point: curve.base.BasePoint;
63+
export function getEd25519ExtendedPublicKey(keyBuffer: Uint8Array): {
64+
scalar: bigint;
65+
point: Point2D;
5366
} {
5467
const ed25519Curve = getKeyCurve(KEY_TYPE.ED25519);
5568
const len = 32;
56-
const G = ed25519Curve.g;
57-
const N = ed25519Curve.n;
69+
const N = ed25519Curve.Point.CURVE().n;
5870

5971
if (keyBuffer.length !== 32) {
6072
log.error("Invalid seed for ed25519 key derivation", keyBuffer.length);
@@ -66,161 +78,152 @@ export function getEd25519ExtendedPublicKey(keyBuffer: Buffer): {
6678
if (hashed.length !== 64) {
6779
throw new Error("Invalid hash length for ed25519 seed");
6880
}
69-
const head = new BN(adjustScalarBytes(Buffer.from(hashed.slice(0, len))), "le");
70-
const scalar = new BN(head.umod(N), "le"); // The actual private scalar
71-
const point = G.mul(scalar) as curve.base.BasePoint; // Point on Edwards curve aka public key
81+
const head = bytesToNumberLE(adjustScalarBytes(new Uint8Array(hashed.slice(0, len))));
82+
const scalar = mod(head, N); // The actual private scalar
83+
const point = ed25519Curve.Point.BASE.multiply(scalar).toAffine(); // Point on Edwards curve aka public key
7284
return { scalar, point };
7385
}
7486

75-
export function encodeEd25519Point(point: curve.base.BasePoint) {
87+
export function encodeEd25519Point(point: Point2D): Uint8Array {
7688
const ed25519Curve = getKeyCurve(KEY_TYPE.ED25519);
77-
78-
const encodingLength = Math.ceil(ed25519Curve.n.bitLength() / 8);
79-
const enc = point.getY().toArrayLike(Buffer, "le", encodingLength);
80-
enc[encodingLength - 1] |= point.getX().isOdd() ? 0x80 : 0;
81-
return enc;
89+
return ed25519Curve.Point.fromAffine(point).toBytes();
8290
}
8391

84-
export const generateEd25519KeyData = async (ed25519Seed: Buffer): Promise<PrivateKeyData> => {
92+
export const generateEd25519KeyData = async (ed25519Seed: Uint8Array): Promise<PrivateKeyData> => {
8593
const ed25519Curve = getKeyCurve(KEY_TYPE.ED25519);
94+
const N = ed25519Curve.Point.CURVE().n;
8695

8796
const finalEd25519Key = getEd25519ExtendedPublicKey(ed25519Seed);
8897
const encryptionKey = getSecpKeyFromEd25519(finalEd25519Key.scalar);
89-
const encryptedSeed = await encrypt(Buffer.from(encryptionKey.point.encodeCompressed("hex"), "hex"), ed25519Seed);
98+
99+
const encPubKeyBytes = getSecp256k1().Point.fromAffine(encryptionKey.point).toBytes(true);
100+
const encryptedSeed = await encrypt(encPubKeyBytes, ed25519Seed);
90101
const encData: EncryptedSeed = {
91-
enc_text: encryptedSeed.ciphertext.toString("hex"),
102+
enc_text: bytesToHex(encryptedSeed.ciphertext),
92103
metadata: encParamsBufToHex(encryptedSeed),
93-
public_key: encodeEd25519Point(finalEd25519Key.point).toString("hex"),
104+
public_key: bytesToHex(encodeEd25519Point(finalEd25519Key.point)),
94105
};
95106

96-
const encDataBase64 = Buffer.from(JSON.stringify(encData), "utf-8").toString("base64");
97-
const metadataPrivNonce = ed25519Curve.genKeyPair().getPrivate();
98-
const oauthKey = finalEd25519Key.scalar.sub(metadataPrivNonce).umod(ed25519Curve.n);
99-
const oauthKeyPair = ed25519Curve.keyFromPrivate(oauthKey.toArrayLike(Buffer));
100-
const metadataSigningKey = getSecpKeyFromEd25519(oauthKeyPair.getPrivate());
107+
const encDataBase64 = bytesToBase64(utf8ToBytes(JSON.stringify(encData)));
108+
const metadataPrivNonce = bytesToNumberBE(generatePrivateKey(KEY_TYPE.ED25519));
109+
const oauthKey = mod(finalEd25519Key.scalar - metadataPrivNonce, N);
110+
const oauthPub = ed25519Curve.Point.BASE.multiply(oauthKey).toAffine();
111+
const metadataSigningKey = getSecpKeyFromEd25519(oauthKey);
101112
return {
102-
oAuthKeyScalar: oauthKeyPair.getPrivate(),
103-
oAuthPubX: oauthKeyPair.getPublic().getX(),
104-
oAuthPubY: oauthKeyPair.getPublic().getY(),
105-
SigningPubX: metadataSigningKey.point.getX(),
106-
SigningPubY: metadataSigningKey.point.getY(),
113+
oAuthKeyScalar: oauthKey,
114+
oAuthPubX: oauthPub.x,
115+
oAuthPubY: oauthPub.y,
116+
SigningPubX: metadataSigningKey.point.x,
117+
SigningPubY: metadataSigningKey.point.y,
107118
metadataNonce: metadataPrivNonce,
108119
metadataSigningKey: metadataSigningKey.scalar,
109120
encryptedSeed: encDataBase64,
110121
finalUserPubKeyPoint: finalEd25519Key.point,
111122
};
112123
};
113124

114-
export const generateSecp256k1KeyData = async (scalarBuffer: Buffer): Promise<PrivateKeyData> => {
125+
export const generateSecp256k1KeyData = async (scalarBuffer: Uint8Array): Promise<PrivateKeyData> => {
115126
const secp256k1Curve = getKeyCurve(KEY_TYPE.SECP256K1);
127+
const N = secp256k1Curve.Point.CURVE().n;
116128

117-
const scalar = new BN(scalarBuffer);
118-
const randomNonce = new BN(generatePrivateKey(secp256k1Curve, Buffer));
119-
const oAuthKey = scalar.sub(randomNonce).umod(secp256k1Curve.n);
120-
const oAuthKeyPair = secp256k1Curve.keyFromPrivate(oAuthKey.toArrayLike(Buffer));
121-
const oAuthPubKey = oAuthKeyPair.getPublic();
129+
const scalar = bytesToNumberBE(scalarBuffer);
130+
const randomNonce = bytesToNumberBE(generatePrivateKey(KEY_TYPE.SECP256K1));
131+
const oAuthKey = mod(scalar - randomNonce, N);
132+
const oAuthPub = secp256k1Curve.Point.BASE.multiply(oAuthKey).toAffine();
122133

123-
const finalUserKeyPair = secp256k1Curve.keyFromPrivate(scalar.toString("hex", 64), "hex");
134+
const finalUserPub = secp256k1Curve.Point.BASE.multiply(scalar).toAffine();
124135

125136
return {
126-
oAuthKeyScalar: oAuthKeyPair.getPrivate(),
127-
oAuthPubX: oAuthPubKey.getX(),
128-
oAuthPubY: oAuthPubKey.getY(),
129-
SigningPubX: oAuthPubKey.getX(),
130-
SigningPubY: oAuthPubKey.getY(),
137+
oAuthKeyScalar: oAuthKey,
138+
oAuthPubX: oAuthPub.x,
139+
oAuthPubY: oAuthPub.y,
140+
SigningPubX: oAuthPub.x,
141+
SigningPubY: oAuthPub.y,
131142
metadataNonce: randomNonce,
132143
encryptedSeed: "",
133-
metadataSigningKey: oAuthKeyPair.getPrivate(),
134-
finalUserPubKeyPoint: finalUserKeyPair.getPublic(),
144+
metadataSigningKey: oAuthKey,
145+
finalUserPubKeyPoint: finalUserPub,
135146
};
136147
};
137148

138-
function generateAddressFromEcKey(keyType: KeyType, key: EC.KeyPair): string {
149+
function generateAddressFromPoint(keyType: KeyType, point: Point2D): string {
139150
if (keyType === KEY_TYPE.SECP256K1) {
140-
const publicKey = key.getPublic().encode("hex", false).slice(2);
141-
const evmAddressLower = `0x${keccak256(Buffer.from(publicKey, "hex")).slice(64 - 38)}`;
151+
const uncompressed = bytesToHex(getSecp256k1().Point.fromAffine(point).toBytes(false));
152+
const publicKey = uncompressed.slice(2); // remove 04 prefix
153+
const evmAddressLower = `0x${keccak256(hexToBytes(publicKey)).slice(64 - 38)}`;
142154
return toChecksumAddress(evmAddressLower);
143155
} else if (keyType === KEY_TYPE.ED25519) {
144-
const publicKey = encodeEd25519Point(key.getPublic());
156+
const publicKey = encodeEd25519Point(point);
145157
const address = bs58.encode(publicKey);
146158
return address;
147159
}
148160
throw new Error(`Invalid keyType: ${keyType}`);
149161
}
150162

151-
export function generateAddressFromPrivKey(keyType: KeyType, privateKey: BN): string {
163+
export function generateAddressFromPrivKey(keyType: KeyType, privateKey: bigint): string {
152164
const ecCurve = getKeyCurve(keyType);
153-
const key = ecCurve.keyFromPrivate(privateKey.toString("hex", 64), "hex");
154-
return generateAddressFromEcKey(keyType, key);
165+
const point = ecCurve.Point.BASE.multiply(privateKey).toAffine();
166+
return generateAddressFromPoint(keyType, point);
155167
}
156168

157-
export function generateAddressFromPubKey(keyType: KeyType, publicKeyX: BN, publicKeyY: BN): string {
158-
const ecCurve = getKeyCurve(keyType);
159-
const key = ecCurve.keyFromPublic({ x: publicKeyX.toString("hex", 64), y: publicKeyY.toString("hex", 64) });
160-
return generateAddressFromEcKey(keyType, key);
169+
export function generateAddressFromPubKey(keyType: KeyType, publicKeyX: bigint, publicKeyY: bigint): string {
170+
return generateAddressFromPoint(keyType, { x: publicKeyX, y: publicKeyY });
161171
}
162172

163-
export function getPostboxKeyFrom1OutOf1(ecCurve: EC, privKey: string, nonce: string): string {
164-
const privKeyBN = new BN(privKey, 16);
165-
const nonceBN = new BN(nonce, 16);
166-
return privKeyBN.sub(nonceBN).umod(ecCurve.n).toString("hex");
173+
export function getPostboxKeyFrom1OutOf1(ecCurve: Curve, privKey: string, nonce: string): string {
174+
const privKeyBI = toBigIntBE(privKey);
175+
const nonceBI = toBigIntBE(nonce);
176+
return bigintToHex(mod(privKeyBI - nonceBI, ecCurve.Point.CURVE().n));
167177
}
168178

169-
export function derivePubKey(ecCurve: EC, sk: BN): curve.base.BasePoint {
170-
const skHex = sk.toString(16, 64);
171-
return ecCurve.keyFromPrivate(skHex, "hex").getPublic();
179+
export function derivePubKey(ecCurve: Curve, sk: bigint): Point2D {
180+
return ecCurve.Point.BASE.multiply(sk).toAffine();
172181
}
173182

174-
export const getEncryptionEC = (): EC => {
175-
return new EC("secp256k1");
176-
};
177-
178183
export const generateShares = async (
179-
ecCurve: EC,
184+
ecCurve: Curve,
180185
keyType: KeyType,
181186
serverTimeOffset: number,
182187
nodeIndexes: number[],
183188
nodePubkeys: INodePub[],
184-
privKey: Buffer
189+
privKey: Uint8Array
185190
) => {
186191
const keyData = keyType === KEY_TYPE.ED25519 ? await generateEd25519KeyData(privKey) : await generateSecp256k1KeyData(privKey);
187192
const { metadataNonce, oAuthKeyScalar: oAuthKey, encryptedSeed, metadataSigningKey } = keyData;
188193
const threshold = ~~(nodePubkeys.length / 2) + 1;
189194
const degree = threshold - 1;
190-
const nodeIndexesBn: BN[] = [];
195+
const nodeIndexesBigInt: bigint[] = nodeIndexes.map((i) => BigInt(i));
191196

192-
for (const nodeIndex of nodeIndexes) {
193-
nodeIndexesBn.push(new BN(nodeIndex));
194-
}
195-
const oAuthPubKey = ecCurve.keyFromPrivate(oAuthKey.toString("hex", 64), "hex").getPublic();
196-
const poly = generateRandomPolynomial(ecCurve, degree, oAuthKey);
197-
const shares = poly.generateShares(nodeIndexesBn);
197+
const oAuthPub = ecCurve.Point.BASE.multiply(oAuthKey).toAffine();
198+
const poly = generateRandomPolynomial(ecCurve, keyType, degree, oAuthKey);
199+
const shares = poly.generateShares(nodeIndexesBigInt);
198200
const nonceParams = generateNonceMetadataParams(serverTimeOffset, "getOrSetNonce", metadataSigningKey, keyType, metadataNonce, encryptedSeed);
199-
const nonceData = Buffer.from(stringify(nonceParams.set_data), "utf8").toString("base64");
201+
const nonceData = bytesToBase64(utf8ToBytes(stringify(nonceParams.set_data)));
200202
const sharesData: ImportedShare[] = [];
201203
const encPromises: Promise<Ecies>[] = [];
202-
for (let i = 0; i < nodeIndexesBn.length; i++) {
203-
const shareJson = shares[nodeIndexesBn[i].toString("hex", 64)].toJSON() as Record<string, string>;
204+
for (let i = 0; i < nodeIndexesBigInt.length; i++) {
205+
const shareJson = shares[bigintToHex(nodeIndexesBigInt[i])].toJSON() as Record<string, string>;
204206
if (!nodePubkeys[i]) {
205-
throw new Error(`Missing node pub key for node index: ${nodeIndexesBn[i].toString("hex", 64)}`);
207+
throw new Error(`Missing node pub key for node index: ${bigintToHex(nodeIndexesBigInt[i])}`);
206208
}
207-
const nodePubKey = getEncryptionEC().keyFromPublic({ x: nodePubkeys[i].X, y: nodePubkeys[i].Y });
208-
encPromises.push(
209-
encrypt(Buffer.from(nodePubKey.getPublic().encodeCompressed("hex"), "hex"), Buffer.from(shareJson.share.padStart(64, "0"), "hex"))
210-
);
209+
const nodePubPoint = getSecp256k1().Point.fromAffine({
210+
x: toBigIntBE(nodePubkeys[i].X),
211+
y: toBigIntBE(nodePubkeys[i].Y),
212+
});
213+
encPromises.push(encrypt(nodePubPoint.toBytes(), hexToBytes(shareJson.share.padStart(64, "0"))));
211214
}
212215
const encShares = await Promise.all(encPromises);
213-
for (let i = 0; i < nodeIndexesBn.length; i += 1) {
214-
const shareJson = shares[nodeIndexesBn[i].toString("hex", 64)].toJSON() as Record<string, string>;
216+
for (let i = 0; i < nodeIndexesBigInt.length; i += 1) {
217+
const shareJson = shares[bigintToHex(nodeIndexesBigInt[i])].toJSON() as Record<string, string>;
215218
const encParams = encShares[i];
216219
const encParamsMetadata = encParamsBufToHex(encParams);
217220
const shareData: ImportedShare = {
218221
encrypted_seed: keyData.encryptedSeed,
219222
final_user_point: keyData.finalUserPubKeyPoint,
220-
oauth_pub_key_x: oAuthPubKey.getX().toString("hex"),
221-
oauth_pub_key_y: oAuthPubKey.getY().toString("hex"),
222-
signing_pub_key_x: keyData.SigningPubX.toString("hex"),
223-
signing_pub_key_y: keyData.SigningPubY.toString("hex"),
223+
oauth_pub_key_x: bigintToHex(oAuthPub.x),
224+
oauth_pub_key_y: bigintToHex(oAuthPub.y),
225+
signing_pub_key_x: bigintToHex(keyData.SigningPubX),
226+
signing_pub_key_y: bigintToHex(keyData.SigningPubY),
224227
encrypted_share: encParamsMetadata.ciphertext,
225228
encrypted_share_metadata: encParamsMetadata,
226229
node_index: Number.parseInt(shareJson.shareIndex, 16),

0 commit comments

Comments
 (0)