Skip to content

Commit 2b96d75

Browse files
committed
restored metadata utils
1 parent 2a0d258 commit 2b96d75

File tree

4 files changed

+294
-23
lines changed

4 files changed

+294
-23
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/helpers/metadataUtils.ts

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import { encParamsHexToBuf, getKeyCurve, keccak256, NonceMetadataParams, SetNonceData } from "@toruslabs/common-lib";
2+
import { KEY_TYPE, LEGACY_NETWORKS_ROUTE_MAP, TORUS_LEGACY_NETWORK_TYPE, TORUS_NETWORK_TYPE, TORUS_SAPPHIRE_NETWORK } from "@toruslabs/constants";
3+
import { decrypt } from "@toruslabs/eccrypto";
4+
import { Data, post } from "@toruslabs/http-helpers";
5+
import BN from "bn.js";
6+
import { curve, ec as EC } from "elliptic";
7+
import { keccak256 as keccakHash } from "ethereum-cryptography/keccak";
8+
import stringify from "json-stable-stringify";
9+
import log from "loglevel";
10+
11+
import { SAPPHIRE_DEVNET_METADATA_URL, SAPPHIRE_METADATA_URL } from "../constants";
12+
import { EciesHex, EncryptedSeed, GetOrSetNonceResult, KeyType, MetadataParams, SapphireMetadataParams } from "../interfaces";
13+
14+
export class GetOrSetNonceError extends Error {}
15+
16+
export const getSecpKeyFromEd25519 = (
17+
ed25519Scalar: BN
18+
): {
19+
scalar: BN;
20+
point: curve.base.BasePoint;
21+
} => {
22+
const secp256k1Curve = getKeyCurve(KEY_TYPE.SECP256K1);
23+
24+
const ed25519Key = ed25519Scalar.toString("hex", 64);
25+
const keyHash: Uint8Array = keccakHash(Buffer.from(ed25519Key, "hex"));
26+
const secpKey = new BN(keyHash).umod(secp256k1Curve.n).toString("hex", 64);
27+
const bufferKey = Buffer.from(secpKey, "hex");
28+
29+
const secpKeyPair = secp256k1Curve.keyFromPrivate(bufferKey);
30+
31+
if (bufferKey.length !== 32) {
32+
throw new Error(`Key length must be equal to 32. got ${bufferKey.length}`);
33+
}
34+
return {
35+
scalar: secpKeyPair.getPrivate(),
36+
point: secpKeyPair.getPublic(),
37+
};
38+
};
39+
40+
export function convertMetadataToNonce(params: { message?: string }) {
41+
if (!params || !params.message) {
42+
return new BN(0);
43+
}
44+
return new BN(params.message, 16);
45+
}
46+
47+
export async function decryptNodeData(eciesData: EciesHex, ciphertextHex: string, privKey: Buffer): Promise<Buffer> {
48+
const metadata = encParamsHexToBuf(eciesData);
49+
const decryptedSigBuffer = await decrypt(privKey, {
50+
...metadata,
51+
ciphertext: Buffer.from(ciphertextHex, "hex"),
52+
});
53+
return decryptedSigBuffer;
54+
}
55+
56+
export async function decryptNodeDataWithPadding(eciesData: EciesHex, ciphertextHex: string, privKey: Buffer): Promise<Buffer> {
57+
const metadata = encParamsHexToBuf(eciesData);
58+
try {
59+
const decryptedSigBuffer = await decrypt(privKey, {
60+
...metadata,
61+
ciphertext: Buffer.from(ciphertextHex, "hex"),
62+
});
63+
return decryptedSigBuffer;
64+
} catch (error) {
65+
// ciphertext can be any length. not just 64. depends on input. we have this for legacy reason
66+
const ciphertextHexPadding = ciphertextHex.padStart(64, "0");
67+
68+
log.warn("Failed to decrypt padded share cipher", error);
69+
// try without cipher text padding
70+
return decrypt(privKey, { ...metadata, ciphertext: Buffer.from(ciphertextHexPadding, "hex") });
71+
}
72+
}
73+
74+
export function generateMetadataParams(ecCurve: EC, serverTimeOffset: number, message: string, privateKey: BN): MetadataParams {
75+
const key = ecCurve.keyFromPrivate(privateKey.toString("hex", 64), "hex");
76+
const setData = {
77+
data: message,
78+
timestamp: new BN(~~(serverTimeOffset + Date.now() / 1000)).toString(16),
79+
};
80+
const sig = key.sign(keccak256(Buffer.from(stringify(setData), "utf8")).slice(2));
81+
return {
82+
pub_key_X: key.getPublic().getX().toString("hex"), // DO NOT PAD THIS. BACKEND DOESN'T
83+
pub_key_Y: key.getPublic().getY().toString("hex"), // DO NOT PAD THIS. BACKEND DOESN'T
84+
set_data: setData,
85+
signature: Buffer.from(sig.r.toString(16, 64) + sig.s.toString(16, 64) + new BN("").toString(16, 2), "hex").toString("base64"),
86+
};
87+
}
88+
89+
export async function getMetadata(
90+
legacyMetadataHost: string,
91+
data: Omit<MetadataParams, "set_data" | "signature">,
92+
options: RequestInit = {}
93+
): Promise<BN> {
94+
try {
95+
const metadataResponse = await post<{ message?: string }>(`${legacyMetadataHost}/get`, data, options, { useAPIKey: true });
96+
if (!metadataResponse || !metadataResponse.message) {
97+
return new BN(0);
98+
}
99+
return new BN(metadataResponse.message, 16); // nonce
100+
} catch (error) {
101+
log.error("get metadata error", error);
102+
return new BN(0);
103+
}
104+
}
105+
106+
export function generateNonceMetadataParams(
107+
serverTimeOffset: number,
108+
operation: string,
109+
privateKey: BN,
110+
keyType: KeyType,
111+
nonce?: BN,
112+
seed?: string
113+
): NonceMetadataParams {
114+
// // metadata only uses secp for sig validation
115+
// const key = getKeyCurve(KEY_TYPE.SECP256K1).keyFromPrivate(privateKey.toString("hex", 64), "hex");
116+
// const setData: Partial<SetNonceData> = {
117+
// operation,
118+
// timestamp: new BN(~~(serverTimeOffset + Date.now() / 1000)).toString(16),
119+
// };
120+
121+
// if (nonce) {
122+
// setData.data = nonce.toString("hex", 64);
123+
// }
124+
125+
// if (seed) {
126+
// setData.seed = seed;
127+
// } else {
128+
// setData.seed = ""; // setting it as empty to keep ordering same while serializing the data on backend.
129+
// }
130+
131+
// const sig = key.sign(keccak256(Buffer.from(stringify(setData), "utf8")).slice(2));
132+
// return {
133+
// pub_key_X: key.getPublic().getX().toString("hex", 64),
134+
// pub_key_Y: key.getPublic().getY().toString("hex", 64),
135+
// set_data: setData,
136+
// key_type: keyType,
137+
// signature: Buffer.from(sig.r.toString(16, 64) + sig.s.toString(16, 64) + new BN("").toString(16, 2), "hex").toString("base64"),
138+
// };
139+
// metadata only uses secp for sig validation
140+
const key = getKeyCurve(KEY_TYPE.SECP256K1).keyFromPrivate(privateKey.toString("hex", 64), "hex");
141+
const setData: Partial<SetNonceData> = {
142+
operation,
143+
timestamp: new BN(~~(serverTimeOffset + Date.now() / 1000)).toString(16),
144+
};
145+
146+
if (nonce) {
147+
setData.data = nonce.toString("hex", 64);
148+
}
149+
150+
if (seed) {
151+
setData.seed = seed;
152+
} else {
153+
setData.seed = ""; // setting it as empty to keep ordering same while serializing the data on backend.
154+
}
155+
156+
const sig = key.sign(keccak256(Buffer.from(stringify(setData), "utf8")).slice(2));
157+
return {
158+
pub_key_X: key.getPublic().getX().toString("hex", 64),
159+
pub_key_Y: key.getPublic().getY().toString("hex", 64),
160+
set_data: setData,
161+
key_type: keyType,
162+
signature: Buffer.from(sig.r.toString(16, 64) + sig.s.toString(16, 64) + new BN("").toString(16, 2), "hex").toString("base64"),
163+
};
164+
}
165+
166+
export async function getOrSetNonce(
167+
metadataHost: string,
168+
ecCurve: EC,
169+
serverTimeOffset: number,
170+
X: string,
171+
Y: string,
172+
privKey?: BN,
173+
getOnly = false,
174+
isLegacyMetadata = true,
175+
nonce = new BN(0),
176+
keyType: KeyType = "secp256k1",
177+
seed = ""
178+
): Promise<GetOrSetNonceResult> {
179+
// for legacy metadata
180+
if (isLegacyMetadata) {
181+
let data: Data;
182+
const msg = getOnly ? "getNonce" : "getOrSetNonce";
183+
if (privKey) {
184+
data = generateMetadataParams(ecCurve, serverTimeOffset, msg, privKey);
185+
} else {
186+
data = {
187+
pub_key_X: X,
188+
pub_key_Y: Y,
189+
set_data: { data: msg },
190+
};
191+
}
192+
return post<GetOrSetNonceResult>(`${metadataHost}/get_or_set_nonce`, data, undefined, { useAPIKey: true });
193+
}
194+
195+
// for sapphire metadata
196+
const operation = getOnly ? "getNonce" : "getOrSetNonce";
197+
if (operation === "getOrSetNonce") {
198+
if (!privKey) {
199+
throw new Error("privKey is required while `getOrSetNonce` for non legacy metadata");
200+
}
201+
if (nonce.cmp(new BN(0)) === 0) {
202+
throw new Error("nonce is required while `getOrSetNonce` for non legacy metadata");
203+
}
204+
if (keyType === KEY_TYPE.ED25519 && !seed) {
205+
throw new Error("seed is required while `getOrSetNonce` for non legacy metadata for ed25519 key type");
206+
}
207+
const data = generateNonceMetadataParams(serverTimeOffset, operation, privKey, keyType, nonce, seed);
208+
209+
return post<GetOrSetNonceResult>(`${metadataHost}/get_or_set_nonce`, data, undefined, { useAPIKey: true });
210+
}
211+
const data = {
212+
pub_key_X: X,
213+
pub_key_Y: Y,
214+
set_data: { operation },
215+
key_type: keyType,
216+
};
217+
return post<GetOrSetNonceResult>(`${metadataHost}/get_or_set_nonce`, data, undefined, { useAPIKey: true });
218+
}
219+
export async function getNonce(
220+
legacyMetadataHost: string,
221+
ecCurve: EC,
222+
serverTimeOffset: number,
223+
X: string,
224+
Y: string,
225+
privKey?: BN
226+
): Promise<GetOrSetNonceResult> {
227+
return getOrSetNonce(legacyMetadataHost, ecCurve, serverTimeOffset, X, Y, privKey, true);
228+
}
229+
230+
export const decryptSeedData = async (seedBase64: string, finalUserKey: BN) => {
231+
const decryptionKey = getSecpKeyFromEd25519(finalUserKey);
232+
const seedUtf8 = Buffer.from(seedBase64, "base64").toString("utf-8");
233+
const seedJson = JSON.parse(seedUtf8) as EncryptedSeed;
234+
const bufferMetadata = { ...encParamsHexToBuf(seedJson.metadata), mode: "AES256" };
235+
const bufferKey = decryptionKey.scalar.toArrayLike(Buffer, "be", 32);
236+
const decText = await decrypt(bufferKey, {
237+
...bufferMetadata,
238+
ciphertext: Buffer.from(seedJson.enc_text, "hex"),
239+
});
240+
241+
return decText;
242+
};
243+
244+
export async function getOrSetSapphireMetadataNonce(
245+
network: TORUS_NETWORK_TYPE,
246+
X: string,
247+
Y: string,
248+
serverTimeOffset?: number,
249+
privKey?: BN
250+
): Promise<GetOrSetNonceResult> {
251+
if (LEGACY_NETWORKS_ROUTE_MAP[network as TORUS_LEGACY_NETWORK_TYPE]) {
252+
throw new Error("getOrSetSapphireMetadataNonce should only be used for sapphire networks");
253+
}
254+
let data: SapphireMetadataParams = {
255+
pub_key_X: X,
256+
pub_key_Y: Y,
257+
key_type: "secp256k1",
258+
set_data: { operation: "getOrSetNonce" },
259+
};
260+
if (privKey) {
261+
const key = getKeyCurve(KEY_TYPE.SECP256K1).keyFromPrivate(privKey.toString("hex", 64), "hex");
262+
263+
const setData = {
264+
operation: "getOrSetNonce",
265+
timestamp: new BN(~~(serverTimeOffset + Date.now() / 1000)).toString(16),
266+
};
267+
const sig = key.sign(keccak256(Buffer.from(stringify(setData), "utf8")).slice(2));
268+
data = {
269+
...data,
270+
set_data: setData,
271+
signature: Buffer.from(sig.r.toString(16, 64) + sig.s.toString(16, 64) + new BN("").toString(16, 2), "hex").toString("base64"),
272+
};
273+
}
274+
275+
const metadataUrl = network === TORUS_SAPPHIRE_NETWORK.SAPPHIRE_DEVNET ? SAPPHIRE_DEVNET_METADATA_URL : SAPPHIRE_METADATA_URL;
276+
277+
return post<GetOrSetNonceResult>(`${metadataUrl}/get_or_set_nonce`, data, undefined, { useAPIKey: true });
278+
}

src/helpers/nodeUtils.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
import {
22
calculateMedian,
3-
decryptNodeData,
4-
decryptNodeDataWithPadding,
5-
decryptSeedData,
63
derivePubKey,
74
generate32BytesPrivateKeyBuffer,
85
generateAddressFromPrivKey,
96
generateAddressFromPubKey,
107
generateShares,
11-
getMetadata,
12-
getOrSetNonce,
13-
getOrSetSapphireMetadataNonce,
148
getProxyCoordinatorEndpointIndex,
159
getSecpKeyFromEd25519,
1610
kCombinations,
@@ -52,6 +46,14 @@ import {
5246
} from "../interfaces";
5347
import log from "../loglevel";
5448
import { TorusUtilsExtraParams } from "../TorusUtilsExtraParams";
49+
import {
50+
decryptNodeData,
51+
decryptNodeDataWithPadding,
52+
decryptSeedData,
53+
getMetadata,
54+
getOrSetNonce,
55+
getOrSetSapphireMetadataNonce,
56+
} from "./metadataUtils";
5557

5658
export const GetPubKeyOrKeyAssign = async (params: {
5759
endpoints: string[];
@@ -349,6 +351,7 @@ const commitmentRequest = async (params: {
349351
.catch(reject);
350352
});
351353
};
354+
352355
export async function retrieveOrImportShare(params: {
353356
legacyMetadataHost: string;
354357
serverTimeOffset: number;
@@ -560,7 +563,7 @@ export async function retrieveOrImportShare(params: {
560563
serverTimeOffsetResponse?: number;
561564
}
562565
| undefined
563-
>(promiseArrRequest, async (shareResponseResult, sharedState) => {
566+
>(promiseArrRequest, async (shareResponseResult) => {
564567
let thresholdNonceData: GetOrSetNonceResult;
565568
let shareResponses: (void | JRPCResponse<ShareRequestResult>)[] = [];
566569
// for import shares case, where result is an array
@@ -727,8 +730,6 @@ export async function retrieveOrImportShare(params: {
727730
});
728731
});
729732

730-
if (sharedState?.resolved) return undefined;
731-
732733
const decryptedShares = sharesResolved.reduce(
733734
(acc, curr, index) => {
734735
if (curr) {

src/torus.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,4 @@
1-
import {
2-
encodeEd25519Point,
3-
generateAddressFromPubKey,
4-
generateShares,
5-
getEd25519ExtendedPublicKey,
6-
getKeyCurve,
7-
getMetadata,
8-
getOrSetNonce,
9-
GetOrSetNonceError,
10-
} from "@toruslabs/common-lib";
1+
import { encodeEd25519Point, generateAddressFromPubKey, generateShares, getEd25519ExtendedPublicKey, getKeyCurve } from "@toruslabs/common-lib";
112
import {
123
INodePub,
134
KEY_TYPE,
@@ -23,6 +14,7 @@ import { curve, ec as EC } from "elliptic";
2314

2415
import { config } from "./config";
2516
import { GetPubKeyOrKeyAssign, retrieveOrImportShare } from "./helpers";
17+
import { getMetadata, getOrSetNonce, GetOrSetNonceError } from "./helpers/metadataUtils";
2618
import {
2719
GetOrSetNonceResult,
2820
ImportKeyParams,

0 commit comments

Comments
 (0)