Source: wallet/path.js

/*!
 * path.js - path object for wallets
 * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
 * https://github.com/bcoin-org/bcoin
 */

'use strict';

const assert = require('bsert');
const bio = require('bufio');
const Address = require('../primitives/address');
const {encoding} = bio;
const {inspectSymbol} = require('../utils');

/**
 * Path
 * @alias module:wallet.Path
 * @property {String} name - Account name.
 * @property {Number} account - Account index.
 * @property {Number} branch - Branch index.
 * @property {Number} index - Address index.
 */

class Path {
  /**
   * Create a path.
   * @constructor
   * @param {Object?} options
   */

  constructor(options) {
    this.keyType = Path.types.HD;

    this.name = null; // Passed in by caller.
    this.account = 0;

    this.type = Address.types.PUBKEYHASH;
    this.version = -1;

    this.branch = -1;
    this.index = -1;

    this.encrypted = false;
    this.data = null;

    this.hash = null; // Passed in by caller.

    if (options)
      this.fromOptions(options);
  }

  /**
   * Instantiate path from options object.
   * @private
   * @param {Object} options
   * @returns {Path}
   */

  fromOptions(options) {
    this.keyType = options.keyType;

    this.name = options.name;
    this.account = options.account;
    this.branch = options.branch;
    this.index = options.index;

    this.encrypted = options.encrypted;
    this.data = options.data;

    this.type = options.type;
    this.version = options.version;
    this.hash = options.hash;

    return this;
  }

  /**
   * Instantiate path from options object.
   * @param {Object} options
   * @returns {Path}
   */

  static fromOptions(options) {
    return new this().fromOptions(options);
  }

  /**
   * Clone the path object.
   * @returns {Path}
   */

  clone() {
    const path = new this.constructor();

    path.keyType = this.keyType;

    path.name = this.name;
    path.account = this.account;
    path.branch = this.branch;
    path.index = this.index;

    path.encrypted = this.encrypted;
    path.data = this.data;

    path.type = this.type;
    path.version = this.version;
    path.hash = this.hash;

    return path;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    const br = bio.read(data);

    this.account = br.readU32();
    this.keyType = br.readU8();

    const flags = br.readU8();

    this.type = flags & 7;
    this.version = flags >>> 3;

    if (this.version === 0x1f)
      this.version = -1;

    switch (this.keyType) {
      case Path.types.HD:
        this.branch = br.readU32();
        this.index = br.readU32();
        break;
      case Path.types.KEY:
        this.encrypted = br.readU8() === 1;
        this.data = br.readVarBytes();
        break;
      case Path.types.ADDRESS:
        // Hash will be passed in by caller.
        break;
      default:
        assert(false);
        break;
    }

    return this;
  }

  /**
   * Instantiate path from serialized data.
   * @param {Buffer} data
   * @returns {Path}
   */

  static fromRaw(data) {
    return new this().fromRaw(data);
  }

  /**
   * Calculate serialization size.
   * @returns {Number}
   */

  getSize() {
    let size = 0;

    size += 6;

    switch (this.keyType) {
      case Path.types.HD:
        size += 8;
        break;
      case Path.types.KEY:
        size += 1;
        size += encoding.sizeVarBytes(this.data);
        break;
    }

    return size;
  }

  /**
   * Serialize path.
   * @returns {Buffer}
   */

  toRaw() {
    const size = this.getSize();
    const bw = bio.write(size);

    bw.writeU32(this.account);
    bw.writeU8(this.keyType);

    let version = this.version;

    if (version === -1)
      version = 0x1f;

    const flags = (version << 3) | this.type;

    bw.writeU8(flags);

    switch (this.keyType) {
      case Path.types.HD:
        assert(!this.data);
        assert(this.index !== -1);
        bw.writeU32(this.branch);
        bw.writeU32(this.index);
        break;
      case Path.types.KEY:
        assert(this.data);
        assert(this.index === -1);
        bw.writeU8(this.encrypted ? 1 : 0);
        bw.writeVarBytes(this.data);
        break;
      case Path.types.ADDRESS:
        assert(!this.data);
        assert(this.index === -1);
        break;
      default:
        assert(false);
        break;
    }

    return bw.render();
  }

  /**
   * Inject properties from address.
   * @private
   * @param {Account} account
   * @param {Address} address
   */

  fromAddress(account, address) {
    this.keyType = Path.types.ADDRESS;
    this.name = account.name;
    this.account = account.accountIndex;
    this.version = address.version;
    this.type = address.type;
    this.hash = address.getHash();
    return this;
  }

  /**
   * Instantiate path from address.
   * @param {Account} account
   * @param {Address} address
   * @returns {Path}
   */

  static fromAddress(account, address) {
    return new this().fromAddress(account, address);
  }

  /**
   * Convert path object to string derivation path.
   * @returns {String}
   */

  toPath() {
    if (this.keyType !== Path.types.HD)
      return null;

    return `m/${this.account}'/${this.branch}/${this.index}`;
  }

  /**
   * Convert path object to an address (currently unused).
   * @returns {Address}
   */

  toAddress() {
    return Address.fromHash(this.hash, this.type, this.version);
  }

  /**
   * Convert path to a json-friendly object.
   * @returns {Object}
   */

  toJSON() {
    return {
      name: this.name,
      account: this.account,
      change: this.branch === 1,
      derivation: this.toPath()
    };
  }

  /**
   * Inspect the path.
   * @returns {String}
   */

  [inspectSymbol]() {
    return `<Path: ${this.name}:${this.toPath()}>`;
  }
}

/**
 * Path types.
 * @enum {Number}
 * @default
 */

Path.types = {
  HD: 0,
  KEY: 1,
  ADDRESS: 2
};

/**
 * Path types.
 * @enum {Number}
 * @default
 */

Path.typesByVal = [
  'HD',
  'KEY',
  'ADDRESS'
];

/**
 * Expose
 */

module.exports = Path;