PHP WebShell

Текущая директория: /opt/bitgo-express-backup-20251206-1327/node_modules/bitgo/src

Просмотр файла: travelRule.js

//
// TravelRule Object
// BitGo accessor for a specific enterprise
//
// Copyright 2014, BitGo, Inc.  All Rights Reserved.
//

var Util = require('./util');

var assert = require('assert');
var bitcoin = require('./bitcoin');
var common = require('./common');
var networks = require('bitcoinjs-lib/src/networks');
var Q = require('q');
var _ = require('lodash');
var sjcl = require('./sjcl.min');

//
// Constructor
//
var TravelRule = function(bitgo) {
  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);

  var url = this.url(params.txid + '/recipients');
  return this.bitgo.get(url)
  .result('recipients')
  .nodeify(callback);
};

TravelRule.prototype.validateTravelInfo = function(info) {
  var 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, 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
  var 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)
 *   hdnode: a bitcoin.HDNode object (may be provided instead of keychain)
 * Returns:
 *   the tx object, augmented with decrypted travelInfo fields
 */
TravelRule.prototype.decryptReceivedTravelInfo = function(params) {
  params = params || {};

  var tx = params.tx;
  if (typeof(tx) != 'object') {
    throw new Error('expecting tx param to be object');
  }

  if (!tx.receivedTravelInfo || !tx.receivedTravelInfo.length) {
    return tx;
  }

  var hdNode;
  // Passing in hdnode is faster because it doesn't reconstruct the key every time
  if (params.hdnode) {
    hdNode = params.hdnode;
  } else {
    var keychain = params.keychain;
    if (typeof(keychain) != 'object' || typeof(keychain.xprv) != 'string') {
      throw new Error('expecting keychain param with xprv');
    }
    hdNode = bitcoin.HDNode.fromBase58(keychain.xprv);
  }

  var self = this;
  var hdPath = bitcoin.hdPath(hdNode);
  tx.receivedTravelInfo.forEach(function(info) {
    var key = hdPath.deriveKey(info.toPubKeyPath);
    var secret = self.bitgo.getECDHSecret({
      eckey: key,
      otherPubKeyHex: info.fromPubKey
    });
    try {
      var decrypted = sjcl.decrypt(secret, info.encryptedTravelInfo);
      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']);
  var txid = params.txid;
  var recipient = params.recipient;
  var travelInfo = params.travelInfo;
  if (!recipient || typeof(recipient) !== 'object') {
    throw new Error('invalid or missing recipient');
  }
  if (!travelInfo || typeof(travelInfo) !== 'object') {
    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
  var fromKey = params.fromKey && bitcoin.ECPair.fromWIF(params.fromKey, bitcoin.getNetwork());
  if (!fromKey) {
    fromKey = bitcoin.makeRandomKey();
  }

  // Compute the shared key for encryption
  var sharedSecret = this.bitgo.getECDHSecret({
    eckey: fromKey,
    otherPubKeyHex: recipient.pubKey
  });

  // JSON-ify and encrypt the payload
  var travelInfoJSON = JSON.stringify(travelInfo);
  var encryptedTravelInfo = sjcl.encrypt(sharedSecret, travelInfoJSON);

  var result = {
    txid: txid,
    outputIndex: recipient.outputIndex,
    toPubKey: recipient.pubKey,
    fromPubKey: fromKey.getPublicKeyBuffer().toString('hex'),
    encryptedTravelInfo: encryptedTravelInfo
  };

  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 (typeof(params.outputIndex) !== 'number') {
    throw new Error('invalid outputIndex');
  }

  return this.bitgo.post(this.url(params.txid + '/' + params.outputIndex))
  .send(params)
  .result()
  .nodeify(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);

  var travelInfos = params.travelInfos;
  if (!_.isArray(travelInfos)) {
    throw new Error('expected parameter travelInfos to be array');
  }

  var self = this;
  var 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
    var sendParamsList = [];
    // don't regenerate a new random key for each recipient
    var fromKey = params.fromKey || bitcoin.makeRandomKey().toWIF();

    recipients.forEach(function(recipient) {
      var outputIndex = recipient.outputIndex;
      var info = travelInfoMap[outputIndex];
      if (info) {
        if (info.amount && info.amount !== recipient.amount) {
          throw new Error('amount did not match for output index ' + outputIndex);
        }
        var sendParams = self.prepareParams({
          txid: params.txid,
          recipient: recipient,
          travelInfo: info,
          fromKey: fromKey,
          noValidate: true // don't re-validate
        });
        sendParamsList.push(sendParams);
      }
    });

    var results = [];
    var errors = [];

    var result = {
      matched: sendParamsList.length,
      results: []
    };

    var sendSerial = function() {
      var 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;

Выполнить команду


Для локальной разработки. Не используйте в интернете!