index.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. /*! scure-bip32 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */
  2. import { hmac } from '@noble/hashes/hmac';
  3. import { ripemd160 } from '@noble/hashes/ripemd160';
  4. import { sha256 } from '@noble/hashes/sha256';
  5. import { sha512 } from '@noble/hashes/sha512';
  6. import { bytes as assertBytes } from '@noble/hashes/_assert';
  7. import { bytesToHex, concatBytes, createView, hexToBytes, utf8ToBytes } from '@noble/hashes/utils';
  8. import { secp256k1 as secp } from '@noble/curves/secp256k1';
  9. import { mod } from '@noble/curves/abstract/modular';
  10. import { createBase58check } from '@scure/base';
  11. const Point = secp.ProjectivePoint;
  12. const base58check = createBase58check(sha256);
  13. function bytesToNumber(bytes) {
  14. return BigInt(`0x${bytesToHex(bytes)}`);
  15. }
  16. function numberToBytes(num) {
  17. return hexToBytes(num.toString(16).padStart(64, '0'));
  18. }
  19. const MASTER_SECRET = utf8ToBytes('Bitcoin seed');
  20. // Bitcoin hardcoded by default
  21. const BITCOIN_VERSIONS = { private: 0x0488ade4, public: 0x0488b21e };
  22. export const HARDENED_OFFSET = 0x80000000;
  23. const hash160 = (data) => ripemd160(sha256(data));
  24. const fromU32 = (data) => createView(data).getUint32(0, false);
  25. const toU32 = (n) => {
  26. if (!Number.isSafeInteger(n) || n < 0 || n > 2 ** 32 - 1) {
  27. throw new Error(`Invalid number=${n}. Should be from 0 to 2 ** 32 - 1`);
  28. }
  29. const buf = new Uint8Array(4);
  30. createView(buf).setUint32(0, n, false);
  31. return buf;
  32. };
  33. export class HDKey {
  34. get fingerprint() {
  35. if (!this.pubHash) {
  36. throw new Error('No publicKey set!');
  37. }
  38. return fromU32(this.pubHash);
  39. }
  40. get identifier() {
  41. return this.pubHash;
  42. }
  43. get pubKeyHash() {
  44. return this.pubHash;
  45. }
  46. get privateKey() {
  47. return this.privKeyBytes || null;
  48. }
  49. get publicKey() {
  50. return this.pubKey || null;
  51. }
  52. get privateExtendedKey() {
  53. const priv = this.privateKey;
  54. if (!priv) {
  55. throw new Error('No private key');
  56. }
  57. return base58check.encode(this.serialize(this.versions.private, concatBytes(new Uint8Array([0]), priv)));
  58. }
  59. get publicExtendedKey() {
  60. if (!this.pubKey) {
  61. throw new Error('No public key');
  62. }
  63. return base58check.encode(this.serialize(this.versions.public, this.pubKey));
  64. }
  65. static fromMasterSeed(seed, versions = BITCOIN_VERSIONS) {
  66. assertBytes(seed);
  67. if (8 * seed.length < 128 || 8 * seed.length > 512) {
  68. throw new Error(`HDKey: wrong seed length=${seed.length}. Should be between 128 and 512 bits; 256 bits is advised)`);
  69. }
  70. const I = hmac(sha512, MASTER_SECRET, seed);
  71. return new HDKey({
  72. versions,
  73. chainCode: I.slice(32),
  74. privateKey: I.slice(0, 32),
  75. });
  76. }
  77. static fromExtendedKey(base58key, versions = BITCOIN_VERSIONS) {
  78. // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)
  79. const keyBuffer = base58check.decode(base58key);
  80. const keyView = createView(keyBuffer);
  81. const version = keyView.getUint32(0, false);
  82. const opt = {
  83. versions,
  84. depth: keyBuffer[4],
  85. parentFingerprint: keyView.getUint32(5, false),
  86. index: keyView.getUint32(9, false),
  87. chainCode: keyBuffer.slice(13, 45),
  88. };
  89. const key = keyBuffer.slice(45);
  90. const isPriv = key[0] === 0;
  91. if (version !== versions[isPriv ? 'private' : 'public']) {
  92. throw new Error('Version mismatch');
  93. }
  94. if (isPriv) {
  95. return new HDKey({ ...opt, privateKey: key.slice(1) });
  96. }
  97. else {
  98. return new HDKey({ ...opt, publicKey: key });
  99. }
  100. }
  101. static fromJSON(json) {
  102. return HDKey.fromExtendedKey(json.xpriv);
  103. }
  104. constructor(opt) {
  105. this.depth = 0;
  106. this.index = 0;
  107. this.chainCode = null;
  108. this.parentFingerprint = 0;
  109. if (!opt || typeof opt !== 'object') {
  110. throw new Error('HDKey.constructor must not be called directly');
  111. }
  112. this.versions = opt.versions || BITCOIN_VERSIONS;
  113. this.depth = opt.depth || 0;
  114. this.chainCode = opt.chainCode || null;
  115. this.index = opt.index || 0;
  116. this.parentFingerprint = opt.parentFingerprint || 0;
  117. if (!this.depth) {
  118. if (this.parentFingerprint || this.index) {
  119. throw new Error('HDKey: zero depth with non-zero index/parent fingerprint');
  120. }
  121. }
  122. if (opt.publicKey && opt.privateKey) {
  123. throw new Error('HDKey: publicKey and privateKey at same time.');
  124. }
  125. if (opt.privateKey) {
  126. if (!secp.utils.isValidPrivateKey(opt.privateKey)) {
  127. throw new Error('Invalid private key');
  128. }
  129. this.privKey =
  130. typeof opt.privateKey === 'bigint' ? opt.privateKey : bytesToNumber(opt.privateKey);
  131. this.privKeyBytes = numberToBytes(this.privKey);
  132. this.pubKey = secp.getPublicKey(opt.privateKey, true);
  133. }
  134. else if (opt.publicKey) {
  135. this.pubKey = Point.fromHex(opt.publicKey).toRawBytes(true); // force compressed point
  136. }
  137. else {
  138. throw new Error('HDKey: no public or private key provided');
  139. }
  140. this.pubHash = hash160(this.pubKey);
  141. }
  142. derive(path) {
  143. if (!/^[mM]'?/.test(path)) {
  144. throw new Error('Path must start with "m" or "M"');
  145. }
  146. if (/^[mM]'?$/.test(path)) {
  147. return this;
  148. }
  149. const parts = path.replace(/^[mM]'?\//, '').split('/');
  150. // tslint:disable-next-line
  151. let child = this;
  152. for (const c of parts) {
  153. const m = /^(\d+)('?)$/.exec(c);
  154. const m1 = m && m[1];
  155. if (!m || m.length !== 3 || typeof m1 !== 'string') {
  156. throw new Error(`Invalid child index: ${c}`);
  157. }
  158. let idx = +m1;
  159. if (!Number.isSafeInteger(idx) || idx >= HARDENED_OFFSET) {
  160. throw new Error('Invalid index');
  161. }
  162. // hardened key
  163. if (m[2] === "'") {
  164. idx += HARDENED_OFFSET;
  165. }
  166. child = child.deriveChild(idx);
  167. }
  168. return child;
  169. }
  170. deriveChild(index) {
  171. if (!this.pubKey || !this.chainCode) {
  172. throw new Error('No publicKey or chainCode set');
  173. }
  174. let data = toU32(index);
  175. if (index >= HARDENED_OFFSET) {
  176. // Hardened
  177. const priv = this.privateKey;
  178. if (!priv) {
  179. throw new Error('Could not derive hardened child key');
  180. }
  181. // Hardened child: 0x00 || ser256(kpar) || ser32(index)
  182. data = concatBytes(new Uint8Array([0]), priv, data);
  183. }
  184. else {
  185. // Normal child: serP(point(kpar)) || ser32(index)
  186. data = concatBytes(this.pubKey, data);
  187. }
  188. const I = hmac(sha512, this.chainCode, data);
  189. const childTweak = bytesToNumber(I.slice(0, 32));
  190. const chainCode = I.slice(32);
  191. if (!secp.utils.isValidPrivateKey(childTweak)) {
  192. throw new Error('Tweak bigger than curve order');
  193. }
  194. const opt = {
  195. versions: this.versions,
  196. chainCode,
  197. depth: this.depth + 1,
  198. parentFingerprint: this.fingerprint,
  199. index,
  200. };
  201. try {
  202. // Private parent key -> private child key
  203. if (this.privateKey) {
  204. const added = mod(this.privKey + childTweak, secp.CURVE.n);
  205. if (!secp.utils.isValidPrivateKey(added)) {
  206. throw new Error('The tweak was out of range or the resulted private key is invalid');
  207. }
  208. opt.privateKey = added;
  209. }
  210. else {
  211. const added = Point.fromHex(this.pubKey).add(Point.fromPrivateKey(childTweak));
  212. // Cryptographically impossible: hmac-sha512 preimage would need to be found
  213. if (added.equals(Point.ZERO)) {
  214. throw new Error('The tweak was equal to negative P, which made the result key invalid');
  215. }
  216. opt.publicKey = added.toRawBytes(true);
  217. }
  218. return new HDKey(opt);
  219. }
  220. catch (err) {
  221. return this.deriveChild(index + 1);
  222. }
  223. }
  224. sign(hash) {
  225. if (!this.privateKey) {
  226. throw new Error('No privateKey set!');
  227. }
  228. assertBytes(hash, 32);
  229. return secp.sign(hash, this.privKey).toCompactRawBytes();
  230. }
  231. verify(hash, signature) {
  232. assertBytes(hash, 32);
  233. assertBytes(signature, 64);
  234. if (!this.publicKey) {
  235. throw new Error('No publicKey set!');
  236. }
  237. let sig;
  238. try {
  239. sig = secp.Signature.fromCompact(signature);
  240. }
  241. catch (error) {
  242. return false;
  243. }
  244. return secp.verify(sig, hash, this.publicKey);
  245. }
  246. wipePrivateData() {
  247. this.privKey = undefined;
  248. if (this.privKeyBytes) {
  249. this.privKeyBytes.fill(0);
  250. this.privKeyBytes = undefined;
  251. }
  252. return this;
  253. }
  254. toJSON() {
  255. return {
  256. xpriv: this.privateExtendedKey,
  257. xpub: this.publicExtendedKey,
  258. };
  259. }
  260. serialize(version, key) {
  261. if (!this.chainCode) {
  262. throw new Error('No chainCode set');
  263. }
  264. assertBytes(key, 33);
  265. // version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)
  266. return concatBytes(toU32(version), new Uint8Array([this.depth]), toU32(this.parentFingerprint), toU32(this.index), this.chainCode, key);
  267. }
  268. }
  269. //# sourceMappingURL=index.js.map