PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-core/src/bitgo/tss/eddsa
Просмотр файла: eddsa.ts
import assert from 'assert';
import openpgp from 'openpgp';
import sodium from 'libsodium-wrappers-sumo';
import Eddsa, { GShare, JShare, KeyShare, PShare, RShare, SignShare, YShare } from './../../../account-lib/mpc/tss';
import { BitGoBase } from '../../bitgoBase';
import {
DecryptableYShare,
CombinedKey,
SigningMaterial,
EncryptedYShare,
UserSigningMaterial,
BackupSigningMaterial,
} from './types';
import { ShareKeyPosition } from '../types';
import {
encryptAndSignText,
readSignedMessage,
SignatureShareRecord,
SignatureShareType,
RequestType,
CommitmentShareRecord,
CommitmentType,
} from '../../utils';
import { BaseTransaction } from '../../../account-lib';
import { Ed25519Bip32HdTree } from '@bitgo/sdk-lib-mpc';
import _ = require('lodash');
import { commonVerifyWalletSignature, getTxRequest, sendSignatureShare } from '../common';
import { IRequestTracer } from '../../../api';
export { getTxRequest, sendSignatureShare };
/**
* Combines YShares to combine the final TSS key
* This can only be used to create the User or Backup key since it requires the common keychain from BitGo first
*
* @param params.keyShare - TSS key share
* @param params.encryptedYShares - encrypted YShares with information on how to decrypt
* @param params.commonKeychain - expected common keychain of the combined key
* @returns {CombinedKey} combined TSS key
*/
export async function createCombinedKey(params: {
keyShare: KeyShare;
encryptedYShares: DecryptableYShare[];
commonKeychain: string;
}): Promise<CombinedKey> {
await Eddsa.initialize();
const MPC = new Eddsa();
const { keyShare, encryptedYShares, commonKeychain } = params;
const yShares: YShare[] = [];
let bitgoYShare: YShare | undefined;
let userYShare: YShare | undefined;
let backupYShare: YShare | undefined;
for (const encryptedYShare of encryptedYShares) {
const privateShare = await readSignedMessage(
encryptedYShare.yShare.encryptedPrivateShare,
encryptedYShare.senderPublicArmor,
encryptedYShare.recipientPrivateArmor
);
const yShare: YShare = {
i: encryptedYShare.yShare.i,
j: encryptedYShare.yShare.j,
y: encryptedYShare.yShare.publicShare.slice(0, 64),
v: encryptedYShare.yShare.publicShare.slice(64, 128),
u: privateShare.slice(0, 64),
chaincode: privateShare.slice(64),
};
switch (encryptedYShare.yShare.j) {
case 1:
userYShare = yShare;
break;
case 2:
backupYShare = yShare;
break;
case 3:
bitgoYShare = yShare;
break;
default:
throw new Error('Invalid YShare index');
}
yShares.push(yShare);
}
const combinedKey = MPC.keyCombine(keyShare.uShare, yShares);
if (combinedKey.pShare.y + combinedKey.pShare.chaincode !== commonKeychain) {
throw new Error('Common keychains do not match');
}
if (!bitgoYShare) {
throw new Error('Missing BitGo Y Share');
}
const signingMaterial: SigningMaterial = {
uShare: keyShare.uShare,
bitgoYShare,
backupYShare,
userYShare,
};
return {
signingMaterial,
commonKeychain,
};
}
/**
* Creates the User Sign Share containing the User XShare , the User to Bitgo RShare and User to Bitgo commitment
*
* @param {Buffer} signablePayload - the signablePayload as a buffer
* @param {PShare} pShare - User's signing material
* @returns {Promise<SignShare>} - User Sign Share
*/
export async function createUserSignShare(signablePayload: Buffer, pShare: PShare): Promise<SignShare> {
const MPC = await Eddsa.initialize();
if (pShare.i !== ShareKeyPosition.USER) {
throw new Error('Invalid PShare, PShare doesnt belong to the User');
}
const jShare: JShare = { i: ShareKeyPosition.BITGO, j: ShareKeyPosition.USER };
return MPC.signShare(signablePayload, pShare, [jShare]);
}
/**
* Creates the User to Bitgo GShare
*
* @param {SignShare} userSignShare - the User Sign Share
* @param {SignatureShareRecord} bitgoToUserRShare - the Bitgo to User RShare
* @param {YShare} backupToUserYShare - the backup key Y share received during wallet creation
* @param {Buffer} signablePayload - the signable payload from a tx
* @param {CommitmentShareRecord} [bitgoToUserCommitment] - the Bitgo to User Commitment
* @returns {Promise<GShare>} - the User to Bitgo GShare
*/
export async function createUserToBitGoGShare(
userSignShare: SignShare,
bitgoToUserRShare: SignatureShareRecord,
backupToUserYShare: YShare,
bitgoToUserYShare: YShare,
signablePayload: Buffer,
bitgoToUserCommitment: CommitmentShareRecord
): Promise<GShare> {
if (userSignShare.xShare.i !== ShareKeyPosition.USER) {
throw new Error('Invalid XShare, doesnt belong to the User');
}
if (bitgoToUserRShare.from !== SignatureShareType.BITGO || bitgoToUserRShare.to !== SignatureShareType.USER) {
throw new Error('Invalid RShare, is not from Bitgo to User');
}
if (backupToUserYShare.i !== ShareKeyPosition.USER) {
throw new Error('Invalid YShare, doesnt belong to the User');
}
if (backupToUserYShare.j !== ShareKeyPosition.BACKUP) {
throw new Error('Invalid YShare, is not backup key');
}
if (bitgoToUserCommitment.from !== SignatureShareType.BITGO || bitgoToUserCommitment.to !== SignatureShareType.USER) {
throw new Error('Invalid Commitment, is not from Bitgo to User');
}
if (bitgoToUserCommitment.type !== CommitmentType.COMMITMENT) {
throw new Error('Invalid Commitment type, got: ' + bitgoToUserCommitment.type + ' expected: commitment');
}
let v, r, R;
if (bitgoToUserRShare.share.length > 128) {
v = bitgoToUserRShare.share.substring(0, 64);
r = bitgoToUserRShare.share.substring(64, 128);
R = bitgoToUserRShare.share.substring(128, 192);
} else {
r = bitgoToUserRShare.share.substring(0, 64);
R = bitgoToUserRShare.share.substring(64, 128);
}
const MPC = await Eddsa.initialize();
const updatedBitgoToUserRShare: RShare = {
i: ShareKeyPosition.USER,
j: ShareKeyPosition.BITGO,
u: bitgoToUserYShare.u,
v,
r,
R,
commitment: bitgoToUserCommitment.share,
};
return MPC.sign(signablePayload, userSignShare.xShare, [updatedBitgoToUserRShare], [backupToUserYShare]);
}
/**
* Sends the User to Bitgo RShare to Bitgo
* @param {BitGoBase} bitgo - the bitgo instance
* @param {String} walletId - the wallet id
* @param {String} txRequestId - the txRequest Id
* @param {SignShare} userSignShare - the user Sign Share
* @param {String} encryptedSignerShare - signer share encrypted to bitgo key
* @returns {Promise<void>}
* @param {IRequestTracer} reqId - the request tracer request id
*/
export async function offerUserToBitgoRShare(
bitgo: BitGoBase,
walletId: string,
txRequestId: string,
userSignShare: SignShare,
encryptedSignerShare: string,
apiMode: 'full' | 'lite' = 'lite',
reqId?: IRequestTracer
): Promise<void> {
const rShare: RShare = userSignShare.rShares[ShareKeyPosition.BITGO];
if (_.isNil(rShare)) {
throw new Error('userToBitgo RShare not found');
}
if (rShare.i !== ShareKeyPosition.BITGO || rShare.j !== ShareKeyPosition.USER) {
throw new Error('Invalid RShare, is not from User to Bitgo');
}
const signatureShare: SignatureShareRecord = {
from: SignatureShareType.USER,
to: SignatureShareType.BITGO,
share: rShare.r + rShare.R,
};
// TODO (BG-57944): implement message signing for EDDSA
await sendSignatureShare(
bitgo,
walletId,
txRequestId,
signatureShare,
RequestType.tx,
encryptedSignerShare,
'eddsa',
apiMode,
undefined,
reqId
);
}
/**
* Gets the Bitgo to User RShare from Bitgo
*
* @param {BitGoBase} bitgo - the bitgo instance
* @param {String} walletId - the wallet id
* @param {String} txRequestId - the txRequest Id
* @param {IRequestTracer} reqId - the request tracer request id
* @returns {Promise<SignatureShareRecord>} - a Signature Share
*/
export async function getBitgoToUserRShare(
bitgo: BitGoBase,
walletId: string,
txRequestId: string,
reqId?: IRequestTracer
): Promise<SignatureShareRecord> {
const txRequest = await getTxRequest(bitgo, walletId, txRequestId, reqId);
let signatureShares;
if (txRequest.apiVersion === 'full') {
assert(txRequest.transactions, 'transactions required as part of txRequest');
signatureShares = txRequest.transactions[0].signatureShares;
} else {
signatureShares = txRequest.signatureShares;
}
if (_.isNil(signatureShares) || _.isEmpty(signatureShares)) {
throw new Error(`No signatures shares found for id: ${txRequestId}`);
}
// at this point we expect the only share to be the RShare
const bitgoToUserRShare = signatureShares.find(
(sigShare) => sigShare.from === SignatureShareType.BITGO && sigShare.to === SignatureShareType.USER
);
if (_.isNil(bitgoToUserRShare)) {
throw new Error(`Bitgo to User RShare not found for id: ${txRequestId}`);
}
return bitgoToUserRShare;
}
/**
* Sends the User to Bitgo GShare to Bitgo
*
* @param {BitGoBase} bitgo - the bitgo instance
* @param {String} walletId - the wallet id
* @param {String} txRequestId - the txRequest Id
* @param {GShare} userToBitgoGShare - the User to Bitgo GShare
* @param {IRequestTracer} reqId - the request tracer request id
* @returns {Promise<void>}
*/
export async function sendUserToBitgoGShare(
bitgo: BitGoBase,
walletId: string,
txRequestId: string,
userToBitgoGShare: GShare,
apiMode: 'full' | 'lite' = 'lite',
reqId?: IRequestTracer
): Promise<void> {
if (userToBitgoGShare.i !== ShareKeyPosition.USER) {
throw new Error('Invalid GShare, doesnt belong to the User');
}
const signatureShare: SignatureShareRecord = {
from: SignatureShareType.USER,
to: SignatureShareType.BITGO,
share: userToBitgoGShare.R + userToBitgoGShare.gamma,
};
// TODO (BG-57944): implement message signing for EDDSA
await sendSignatureShare(
bitgo,
walletId,
txRequestId,
signatureShare,
RequestType.tx,
undefined,
'eddsa',
apiMode,
undefined,
reqId
);
}
/**
* Prepares a YShare to be exchanged with other key holders.
* Output is in a format that is usable within BitGo's ecosystem.
*
* @param params.keyShare - TSS key share of the party preparing exchange materials
* @param params.recipientIndex - index of the recipient (1, 2, or 3)
* @param params.recipientGpgPublicArmor - recipient's public gpg key in armor format
* @param params.senderGpgPrivateArmor - sender's private gpg key in armor format
* @returns { EncryptedYShare } encrypted Y Share
*/
export async function encryptYShare(params: {
keyShare: KeyShare;
recipientIndex: number;
recipientGpgPublicArmor: string;
senderGpgPrivateArmor: string;
}): Promise<EncryptedYShare> {
const { keyShare, recipientIndex, recipientGpgPublicArmor, senderGpgPrivateArmor } = params;
const yShare = keyShare.yShares[recipientIndex];
if (!yShare) {
throw new Error('Invalid recipient');
}
const publicShare = Buffer.concat([
Buffer.from(keyShare.uShare.y, 'hex'),
Buffer.from(yShare.v!, 'hex'),
Buffer.from(keyShare.uShare.chaincode, 'hex'),
]).toString('hex');
const privateShare = Buffer.concat([Buffer.from(yShare.u, 'hex'), Buffer.from(yShare.chaincode, 'hex')]).toString(
'hex'
);
const encryptedPrivateShare = await encryptAndSignText(privateShare, recipientGpgPublicArmor, senderGpgPrivateArmor);
return {
i: yShare.i,
j: yShare.j,
publicShare,
encryptedPrivateShare,
};
}
/**
*
* Initializes Eddsa instance
*
* @returns {Promise<Eddsa>} the Eddsa instance
*/
export async function getInitializedMpcInstance() {
const hdTree = await Ed25519Bip32HdTree.initialize();
return await Eddsa.initialize(hdTree);
}
/**
*
* Generates a TSS signature using the user and backup key
*
* @param {UserSigningMaterial} userSigningMaterial decrypted user TSS key
* @param {BackupSigningMaterial} backupSigningMaterial decrypted backup TSS key
* @param {string} path bip32 derivation path
* @param {BaseTransaction} transaction the transaction to sign
* @returns {Buffer} the signature
*/
export async function getTSSSignature(
userSigningMaterial: UserSigningMaterial,
backupSigningMaterial: BackupSigningMaterial,
path = 'm/0',
transaction: BaseTransaction
): Promise<Buffer> {
const MPC = await getInitializedMpcInstance();
const userCombine = MPC.keyCombine(userSigningMaterial.uShare, [
userSigningMaterial.bitgoYShare,
userSigningMaterial.backupYShare,
]);
const backupCombine = MPC.keyCombine(backupSigningMaterial.uShare, [
backupSigningMaterial.bitgoYShare,
backupSigningMaterial.userYShare,
]);
const userSubkey = MPC.keyDerive(
userSigningMaterial.uShare,
[userSigningMaterial.bitgoYShare, userSigningMaterial.backupYShare],
path
);
const backupSubkey = MPC.keyCombine(backupSigningMaterial.uShare, [
userSubkey.yShares[2],
backupSigningMaterial.bitgoYShare,
]);
const messageBuffer = transaction.signablePayload;
const userSignShare = MPC.signShare(messageBuffer, userSubkey.pShare, [userCombine.jShares[2]]);
const backupSignShare = MPC.signShare(messageBuffer, backupSubkey.pShare, [backupCombine.jShares[1]]);
const userSign = MPC.sign(
messageBuffer,
userSignShare.xShare,
[backupSignShare.rShares[1]],
[userSigningMaterial.bitgoYShare]
);
const backupSign = MPC.sign(
messageBuffer,
backupSignShare.xShare,
[userSignShare.rShares[2]],
[backupSigningMaterial.bitgoYShare]
);
const signature = MPC.signCombine([userSign, backupSign]);
const result = MPC.verify(messageBuffer, signature);
if (!result) {
throw new Error('Invalid signature');
}
const rawSignature = Buffer.concat([Buffer.from(signature.R, 'hex'), Buffer.from(signature.sigma, 'hex')]);
return rawSignature;
}
/**
* Verifies that a TSS wallet signature was produced with the expected key and that the signed data contains the
* expected common keychain, the expected user and backup key ids as well as the public share that is generated from the
* private share that was passed in.
*/
export async function verifyWalletSignature(params: {
walletSignature: openpgp.Key;
bitgoPub: openpgp.Key;
commonKeychain: string;
userKeyId: string;
backupKeyId: string;
decryptedShare: string;
verifierIndex: 1 | 2; // the index of the verifier, 1 means user, 2 means backup
}): Promise<void> {
const rawNotations = await commonVerifyWalletSignature(params);
const { decryptedShare, verifierIndex } = params;
const publicShare =
Buffer.from(
await sodium.crypto_scalarmult_ed25519_base_noclamp(Buffer.from(decryptedShare.slice(0, 64), 'hex'))
).toString('hex') + decryptedShare.slice(64);
const publicShareRawNotationIndex = 2 + verifierIndex;
assert(
publicShare === Buffer.from(rawNotations[publicShareRawNotationIndex].value).toString(),
'bitgo share mismatch'
);
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!