PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-api/src/v1
Просмотр файла: travelRule.ts
/**
* @hidden
*/
/**
*/
//
// TravelRule Object
// BitGo accessor for a specific enterprise
//
// Copyright 2014, BitGo, Inc. All Rights Reserved.
//
import { common, getNetwork, getSharedSecret, makeRandomKey, sanitizeLegacyPath } from '@bitgo/sdk-core';
import { bip32, BIP32Interface } from '@bitgo/utxo-lib';
import * as utxolib from '@bitgo/utxo-lib';
import _ from 'lodash';
interface DecryptReceivedTravelRuleOptions {
tx?: {
receivedTravelInfo?: {
toPubKeyPath: string;
fromPubKey: string;
encryptedTravelInfo: string;
travelInfo: string;
transactionId: string;
outputIndex: number;
}[];
};
keychain?: {
xprv?: string;
};
hdnode?: BIP32Interface;
}
interface Recipient {
enterprise: string;
pubKey: string;
outputIndex: string;
}
//
// Constructor
//
const TravelRule = function (bitgo) {
// @ts-expect-error - no implicit this
this.bitgo = bitgo;
};
TravelRule.prototype.url = function (extra) {
extra = extra || '';
return this.bitgo.url('/travel/' + extra);
};
/**
* Get available travel-rule info recipients for a transaction
* @param params
* txid: transaction id
* @param callback
* @returns {*}
*/
TravelRule.prototype.getRecipients = function (params, callback) {
params = params || {};
params.txid = params.txid || params.hash;
common.validateParams(params, ['txid'], [], callback);
const url = this.url(params.txid + '/recipients');
return Promise.resolve(this.bitgo.get(url).result('recipients')).then(callback).catch(callback);
};
TravelRule.prototype.validateTravelInfo = function (info) {
const fields = {
amount: { type: 'number' },
toAddress: { type: 'string' },
toEnterprise: { type: 'string' },
fromUserName: { type: 'string' },
fromUserAccount: { type: 'string' },
fromUserAddress: { type: 'string' },
toUserName: { type: 'string' },
toUserAccount: { type: 'string' },
toUserAddress: { type: 'string' },
extra: { type: 'object' },
};
_.forEach(fields, function (field: any, fieldName) {
// No required fields yet -- should there be?
if (field.required) {
if (info[fieldName] === undefined) {
throw new Error('missing required field ' + fieldName + ' in travel info');
}
}
if (info[fieldName] && typeof info[fieldName] !== field.type) {
throw new Error('incorrect type for field ' + fieldName + ' in travel info, expected ' + field.type);
}
});
// Strip out any other fields we don't know about
const result = _.pick(info, _.keys(fields));
if (_.isEmpty(result)) {
throw new Error('empty travel data');
}
return result;
};
/**
* Takes a transaction object as returned by getTransaction or listTransactions, along
* with a keychain (or hdnode object), and attempts to decrypt any encrypted travel
* info included in the transaction's receivedTravelInfo field.
* Parameters:
* tx: a transaction object
* keychain: keychain object (with xprv)
* Returns:
* the tx object, augmented with decrypted travelInfo fields
*/
TravelRule.prototype.decryptReceivedTravelInfo = function (params: DecryptReceivedTravelRuleOptions = {}) {
const tx = params.tx;
if (!_.isObject(tx)) {
throw new Error('expecting tx param to be object');
}
if (!tx.receivedTravelInfo || !tx.receivedTravelInfo.length) {
return tx;
}
const keychain = params.keychain;
if (!_.isObject(keychain) || !_.isString(keychain.xprv)) {
throw new Error('expecting keychain param with xprv');
}
const hdNode = bip32.fromBase58(keychain.xprv);
tx.receivedTravelInfo.forEach((info) => {
const key = hdNode.derivePath(sanitizeLegacyPath(info.toPubKeyPath));
const secret = getSharedSecret(key, Buffer.from(info.fromPubKey, 'hex')).toString('hex');
try {
const decrypted = this.bitgo.decrypt({
input: info.encryptedTravelInfo,
password: secret,
});
info.travelInfo = JSON.parse(decrypted);
} catch (err) {
console.error('failed to decrypt or parse travel info for ', info.transactionId + ':' + info.outputIndex);
}
});
return tx;
};
TravelRule.prototype.prepareParams = function (params) {
params = params || {};
params.txid = params.txid || params.hash;
common.validateParams(params, ['txid'], ['fromPrivateInfo']);
const txid = params.txid;
const recipient: Recipient | undefined = params.recipient;
let travelInfo = params.travelInfo;
if (!recipient || !_.isObject(recipient)) {
throw new Error('invalid or missing recipient');
}
if (!travelInfo || !_.isObject(travelInfo)) {
throw new Error('invalid or missing travelInfo');
}
if (!params.noValidate) {
travelInfo = this.validateTravelInfo(travelInfo);
}
// Fill in toEnterprise if not already filled
if (!travelInfo.toEnterprise && recipient.enterprise) {
travelInfo.toEnterprise = recipient.enterprise;
}
// If a key was not provided, create a new random key
let fromKey = params.fromKey && utxolib.ECPair.fromWIF(params.fromKey, getNetwork() as utxolib.BitcoinJSNetwork);
if (!fromKey) {
fromKey = makeRandomKey();
}
// Compute the shared key for encryption
const sharedSecret = getSharedSecret(fromKey, Buffer.from(recipient.pubKey, 'hex')).toString('hex');
// JSON-ify and encrypt the payload
const travelInfoJSON = JSON.stringify(travelInfo);
const encryptedTravelInfo = this.bitgo.encrypt({
input: travelInfoJSON,
password: sharedSecret,
});
const result = {
txid: txid,
outputIndex: recipient.outputIndex,
toPubKey: recipient.pubKey,
fromPubKey: fromKey.publicKey.toString('hex'),
encryptedTravelInfo: encryptedTravelInfo,
fromPrivateInfo: undefined,
};
if (params.fromPrivateInfo) {
result.fromPrivateInfo = params.fromPrivateInfo;
}
return result;
};
/**
* Send travel data to the server for a transaction
*/
TravelRule.prototype.send = function (params, callback) {
params = params || {};
params.txid = params.txid || params.hash;
common.validateParams(
params,
['txid', 'toPubKey', 'encryptedTravelInfo'],
['fromPubKey', 'fromPrivateInfo'],
callback
);
if (!_.isNumber(params.outputIndex)) {
throw new Error('invalid outputIndex');
}
return Promise.resolve(
this.bitgo
.post(this.url(params.txid + '/' + params.outputIndex))
.send(params)
.result()
)
.then(callback)
.catch(callback);
};
/**
* Send multiple travel rule infos for the outputs of a single transaction.
* Parameters:
* - txid (or hash): txid of the transaction (must be a sender of the tx)
* - travelInfos: array of travelInfo objects which look like the following:
* {
* outputIndex: number, // tx output index
* fromUserName: string, // name of the sending user
* fromUserAccount: string, // account id of the sending user
* fromUserAddress: string, // mailing address of the sending user
* toUserName: string, // name of the receiving user
* toUserAccount: string, // account id of the receiving user
* toUserAddress: string // mailing address of the receiving user
* }
* All fields aside from outputIndex are optional, but at least one must
* be defined.
*
* It is not necessary to provide travelInfo for all output indices.
* End-to-end encryption of the travel info is handled automatically by this method.
*
*/
TravelRule.prototype.sendMany = function (params, callback) {
params = params || {};
params.txid = params.txid || params.hash;
common.validateParams(params, ['txid'], callback);
const travelInfos = params.travelInfos;
if (!_.isArray(travelInfos)) {
throw new Error('expected parameter travelInfos to be array');
}
const self = this;
const travelInfoMap = _(travelInfos)
.keyBy('outputIndex')
.mapValues(function (travelInfo) {
return self.validateTravelInfo(travelInfo);
})
.value();
return self.getRecipients({ txid: params.txid }).then(function (recipients) {
// Build up data to post
const sendParamsList: any[] = [];
// don't regenerate a new random key for each recipient
const fromKey = params.fromKey || makeRandomKey().toWIF();
recipients.forEach(function (recipient) {
const outputIndex = recipient.outputIndex;
const info = travelInfoMap[outputIndex];
if (info) {
if (info.amount && info.amount !== recipient.amount) {
throw new Error('amount did not match for output index ' + outputIndex);
}
const sendParams = self.prepareParams({
txid: params.txid,
recipient: recipient,
travelInfo: info,
fromKey: fromKey,
noValidate: true, // don't re-validate
});
sendParamsList.push(sendParams);
}
});
const result: {
matched: number;
results: {
result?: any;
error?: string;
}[];
} = {
matched: sendParamsList.length,
results: [],
};
const sendSerial = function () {
const sendParams = sendParamsList.shift();
if (!sendParams) {
return result;
}
return self
.send(sendParams)
.then(function (res) {
result.results.push({ result: res });
return sendSerial();
})
.catch(function (err) {
result.results.push({ error: err.toString() });
return sendSerial();
});
};
return sendSerial();
});
};
module.exports = TravelRule;
Выполнить команду
Для локальной разработки. Не используйте в интернете!