-
Notifications
You must be signed in to change notification settings - Fork 13
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
ed25519 keys support and import keys new flow #126
Changes from 17 commits
1789692
b624844
7d2fc60
99bc575
a957ebf
02168d0
8586ecc
4e603df
3066336
badf280
d20b635
fe96658
6760d85
0b9aff1
d9ac909
331a9ef
c8ed4d4
c70b131
119c01c
585b89b
52d93e9
12fb612
6950486
855aca0
d9dab31
bd59e70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"editor.codeActionsOnSave": { | ||
"source.fixAll": true | ||
"source.fixAll": "explicit" | ||
}, | ||
"cSpell.words": ["Mutex", "Mutexes", "toruslabs"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
export const JRPC_METHODS = { | ||
GET_OR_SET_KEY: "GetPubKeyOrKeyAssign", | ||
COMMITMENT_REQUEST: "CommitmentRequest", | ||
IMPORT_SHARE: "ImportShare", | ||
IMPORT_SHARES: "ImportShares", | ||
GET_SHARE_OR_KEY_ASSIGN: "GetShareOrKeyAssign", | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,23 @@ | ||
import { INodePub } from "@toruslabs/constants"; | ||
import { Ecies, encrypt } from "@toruslabs/eccrypto"; | ||
import BN from "bn.js"; | ||
import { ec } from "elliptic"; | ||
import { curve, ec as EC } from "elliptic"; | ||
import { keccak256 as keccakHash } from "ethereum-cryptography/keccak"; | ||
import stringify from "json-stable-stringify"; | ||
|
||
import { ImportedShare, KeyType } from ".."; | ||
import log from "../loglevel"; | ||
import { encParamsBufToHex, generateNonceMetadataParams, generateRandomPolynomial } from "."; | ||
|
||
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, "le", 32); | ||
}; | ||
|
||
export function stripHexPrefix(str: string): string { | ||
return str.startsWith("0x") ? str.slice(2) : str; | ||
} | ||
|
@@ -31,24 +40,89 @@ export function toChecksumAddress(hexAddress: string): string { | |
return ret; | ||
} | ||
|
||
export function generateAddressFromPrivKey(ecCurve: ec, privateKey: BN): string { | ||
export function generateAddressFromPrivKey(ecCurve: EC, privateKey: BN): string { | ||
const key = ecCurve.keyFromPrivate(privateKey.toString("hex", 64), "hex"); | ||
const publicKey = key.getPublic().encode("hex", false).slice(2); | ||
log.info(publicKey, "public key"); | ||
const evmAddressLower = `0x${keccak256(Buffer.from(publicKey, "hex")).slice(64 - 38)}`; | ||
return toChecksumAddress(evmAddressLower); | ||
} | ||
|
||
export function generateAddressFromPubKey(ecCurve: ec, publicKeyX: BN, publicKeyY: BN): string { | ||
export function generateAddressFromPubKey(ecCurve: EC, publicKeyX: BN, publicKeyY: BN): string { | ||
const key = ecCurve.keyFromPublic({ x: publicKeyX.toString("hex", 64), y: publicKeyY.toString("hex", 64) }); | ||
const publicKey = key.getPublic().encode("hex", false).slice(2); | ||
log.info(key.getPublic().encode("hex", false), "public key"); | ||
const evmAddressLower = `0x${keccak256(Buffer.from(publicKey, "hex")).slice(64 - 38)}`; | ||
return toChecksumAddress(evmAddressLower); | ||
} | ||
|
||
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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. some users / libraries may expect the hex to be 32 bytes long (64 characters). ideally should use a global function to encode to bytes / hex, so that we don't have to do and check this manually every time. (even better might be to create a package that can be used across libraries.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will address these suggestions to make test cases modular in other PRs, thanks for suggesting these improvement |
||
} | ||
|
||
export function derivePubKey(ecCurve: EC, sk: BN): curve.base.BasePoint { | ||
const skHex = sk.toString(16, 64); | ||
return ecCurve.keyFromPrivate(skHex).getPublic(); | ||
} | ||
|
||
export const encryptionEC = new EC("secp256k1"); | ||
|
||
export const generateShares = async ( | ||
ecCurve: EC, | ||
keyType: KeyType, | ||
serverTimeOffset: number, | ||
nodeIndexes: number[], | ||
nodePubkeys: INodePub[], | ||
privKey: string | ||
) => { | ||
const key = ecCurve.keyFromPrivate(privKey.padStart(64, "0"), "hex"); | ||
|
||
const threshold = ~~(nodePubkeys.length / 2) + 1; | ||
const degree = threshold - 1; | ||
const nodeIndexesBn: BN[] = []; | ||
|
||
for (const nodeIndex of nodeIndexes) { | ||
nodeIndexesBn.push(new BN(nodeIndex)); | ||
} | ||
const privKeyBn = key.getPrivate(); | ||
const randomNonce = new BN(generatePrivateKey(ecCurve, Buffer)); | ||
const oAuthKey = privKeyBn.sub(randomNonce).umod(ecCurve.curve.n); | ||
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(ecCurve, serverTimeOffset, "getOrSetNonce", oAuthKey, keyType, randomNonce); | ||
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 = encryptionEC.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 = { | ||
pub_key_x: oAuthPubKey.getX().toString("hex", 64), | ||
pub_key_y: oAuthPubKey.getY().toString("hex", 64), | ||
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; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are we using only the first key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So there are cases where it is possible that a user can have multiple keys but all the nodes might not have all those shares/pub keys for ex i have seen these cases when a node is temp down but key was assigned successfully by other threshold number of nodes.
and as a result we should only check threshold for first key rather than checking older keys which are not even being used on frontend and can have mismatch in threshold when some nodes have more keys assigned for a user than others.