Source: wallet/path.js

  1. /*!
  2. * path.js - path object for wallets
  3. * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
  4. * https://github.com/bcoin-org/bcoin
  5. */
  6. 'use strict';
  7. const assert = require('bsert');
  8. const bio = require('bufio');
  9. const Address = require('../primitives/address');
  10. const {encoding} = bio;
  11. const {inspectSymbol} = require('../utils');
  12. /**
  13. * Path
  14. * @alias module:wallet.Path
  15. * @property {String} name - Account name.
  16. * @property {Number} account - Account index.
  17. * @property {Number} branch - Branch index.
  18. * @property {Number} index - Address index.
  19. */
  20. class Path {
  21. /**
  22. * Create a path.
  23. * @constructor
  24. * @param {Object?} options
  25. */
  26. constructor(options) {
  27. this.keyType = Path.types.HD;
  28. this.name = null; // Passed in by caller.
  29. this.account = 0;
  30. this.type = Address.types.PUBKEYHASH;
  31. this.version = -1;
  32. this.branch = -1;
  33. this.index = -1;
  34. this.encrypted = false;
  35. this.data = null;
  36. this.hash = null; // Passed in by caller.
  37. if (options)
  38. this.fromOptions(options);
  39. }
  40. /**
  41. * Instantiate path from options object.
  42. * @private
  43. * @param {Object} options
  44. * @returns {Path}
  45. */
  46. fromOptions(options) {
  47. this.keyType = options.keyType;
  48. this.name = options.name;
  49. this.account = options.account;
  50. this.branch = options.branch;
  51. this.index = options.index;
  52. this.encrypted = options.encrypted;
  53. this.data = options.data;
  54. this.type = options.type;
  55. this.version = options.version;
  56. this.hash = options.hash;
  57. return this;
  58. }
  59. /**
  60. * Instantiate path from options object.
  61. * @param {Object} options
  62. * @returns {Path}
  63. */
  64. static fromOptions(options) {
  65. return new this().fromOptions(options);
  66. }
  67. /**
  68. * Clone the path object.
  69. * @returns {Path}
  70. */
  71. clone() {
  72. const path = new this.constructor();
  73. path.keyType = this.keyType;
  74. path.name = this.name;
  75. path.account = this.account;
  76. path.branch = this.branch;
  77. path.index = this.index;
  78. path.encrypted = this.encrypted;
  79. path.data = this.data;
  80. path.type = this.type;
  81. path.version = this.version;
  82. path.hash = this.hash;
  83. return path;
  84. }
  85. /**
  86. * Inject properties from serialized data.
  87. * @private
  88. * @param {Buffer} data
  89. */
  90. fromRaw(data) {
  91. const br = bio.read(data);
  92. this.account = br.readU32();
  93. this.keyType = br.readU8();
  94. const flags = br.readU8();
  95. this.type = flags & 7;
  96. this.version = flags >>> 3;
  97. if (this.version === 0x1f)
  98. this.version = -1;
  99. switch (this.keyType) {
  100. case Path.types.HD:
  101. this.branch = br.readU32();
  102. this.index = br.readU32();
  103. break;
  104. case Path.types.KEY:
  105. this.encrypted = br.readU8() === 1;
  106. this.data = br.readVarBytes();
  107. break;
  108. case Path.types.ADDRESS:
  109. // Hash will be passed in by caller.
  110. break;
  111. default:
  112. assert(false);
  113. break;
  114. }
  115. return this;
  116. }
  117. /**
  118. * Instantiate path from serialized data.
  119. * @param {Buffer} data
  120. * @returns {Path}
  121. */
  122. static fromRaw(data) {
  123. return new this().fromRaw(data);
  124. }
  125. /**
  126. * Calculate serialization size.
  127. * @returns {Number}
  128. */
  129. getSize() {
  130. let size = 0;
  131. size += 6;
  132. switch (this.keyType) {
  133. case Path.types.HD:
  134. size += 8;
  135. break;
  136. case Path.types.KEY:
  137. size += 1;
  138. size += encoding.sizeVarBytes(this.data);
  139. break;
  140. }
  141. return size;
  142. }
  143. /**
  144. * Serialize path.
  145. * @returns {Buffer}
  146. */
  147. toRaw() {
  148. const size = this.getSize();
  149. const bw = bio.write(size);
  150. bw.writeU32(this.account);
  151. bw.writeU8(this.keyType);
  152. let version = this.version;
  153. if (version === -1)
  154. version = 0x1f;
  155. const flags = (version << 3) | this.type;
  156. bw.writeU8(flags);
  157. switch (this.keyType) {
  158. case Path.types.HD:
  159. assert(!this.data);
  160. assert(this.index !== -1);
  161. bw.writeU32(this.branch);
  162. bw.writeU32(this.index);
  163. break;
  164. case Path.types.KEY:
  165. assert(this.data);
  166. assert(this.index === -1);
  167. bw.writeU8(this.encrypted ? 1 : 0);
  168. bw.writeVarBytes(this.data);
  169. break;
  170. case Path.types.ADDRESS:
  171. assert(!this.data);
  172. assert(this.index === -1);
  173. break;
  174. default:
  175. assert(false);
  176. break;
  177. }
  178. return bw.render();
  179. }
  180. /**
  181. * Inject properties from address.
  182. * @private
  183. * @param {Account} account
  184. * @param {Address} address
  185. */
  186. fromAddress(account, address) {
  187. this.keyType = Path.types.ADDRESS;
  188. this.name = account.name;
  189. this.account = account.accountIndex;
  190. this.version = address.version;
  191. this.type = address.type;
  192. this.hash = address.getHash();
  193. return this;
  194. }
  195. /**
  196. * Instantiate path from address.
  197. * @param {Account} account
  198. * @param {Address} address
  199. * @returns {Path}
  200. */
  201. static fromAddress(account, address) {
  202. return new this().fromAddress(account, address);
  203. }
  204. /**
  205. * Convert path object to string derivation path.
  206. * @returns {String}
  207. */
  208. toPath() {
  209. if (this.keyType !== Path.types.HD)
  210. return null;
  211. return `m/${this.account}'/${this.branch}/${this.index}`;
  212. }
  213. /**
  214. * Convert path object to an address (currently unused).
  215. * @returns {Address}
  216. */
  217. toAddress() {
  218. return Address.fromHash(this.hash, this.type, this.version);
  219. }
  220. /**
  221. * Convert path to a json-friendly object.
  222. * @returns {Object}
  223. */
  224. toJSON() {
  225. return {
  226. name: this.name,
  227. account: this.account,
  228. change: this.branch === 1,
  229. derivation: this.toPath()
  230. };
  231. }
  232. /**
  233. * Inspect the path.
  234. * @returns {String}
  235. */
  236. [inspectSymbol]() {
  237. return `<Path: ${this.name}:${this.toPath()}>`;
  238. }
  239. }
  240. /**
  241. * Path types.
  242. * @enum {Number}
  243. * @default
  244. */
  245. Path.types = {
  246. HD: 0,
  247. KEY: 1,
  248. ADDRESS: 2
  249. };
  250. /**
  251. * Path types.
  252. * @enum {Number}
  253. * @default
  254. */
  255. Path.typesByVal = [
  256. 'HD',
  257. 'KEY',
  258. 'ADDRESS'
  259. ];
  260. /**
  261. * Expose
  262. */
  263. module.exports = Path;