/packages/utxo-lib/src/address.ts

https://github.com/trezor/trezor-suite · TypeScript · 172 lines · 133 code · 26 blank · 13 comment · 37 complexity · b2b13278133dc7ea9e0a6ba01dd4ee28 MD5 · raw file

  1. // upstream: https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/address.ts
  2. // differences:
  3. // - `fromBase58Check` method is using additional "network" param and bs58check.decodeAddress instead of bs58check.decode. checking multibyte version (Zcash and Decred support).
  4. // - `toBase58Check` method is using additional "network" param and bs58check.encodeAddress instead of bs58check.encode.
  5. import { bech32, bech32m } from 'bech32';
  6. import * as bs58check from './bs58check';
  7. import * as typeforce from 'typeforce';
  8. import * as bscript from './script';
  9. import * as payments from './payments';
  10. import { bitcoin as BITCOIN_NETWORK } from './networks';
  11. import * as types from './types';
  12. export interface Base58CheckResult {
  13. hash: Buffer;
  14. version: number;
  15. }
  16. export interface Bech32Result {
  17. version: number;
  18. prefix: string;
  19. data: Buffer;
  20. }
  21. export function fromBase58Check(address: string, network = BITCOIN_NETWORK): Base58CheckResult {
  22. return bs58check.decodeAddress(address, network);
  23. }
  24. export function fromBech32(address: string): Bech32Result {
  25. let result: ReturnType<typeof bech32.decode> | undefined;
  26. let version: number;
  27. try {
  28. result = bech32.decode(address);
  29. } catch (e) {
  30. // silent
  31. }
  32. if (result) {
  33. [version] = result.words;
  34. if (version !== 0) throw new TypeError(`${address} uses wrong encoding`);
  35. } else {
  36. result = bech32m.decode(address);
  37. [version] = result.words;
  38. if (version === 0) throw new TypeError(`${address} uses wrong encoding`);
  39. }
  40. const data = bech32.fromWords(result.words.slice(1));
  41. return {
  42. version,
  43. prefix: result.prefix,
  44. data: Buffer.from(data),
  45. };
  46. }
  47. export function toBase58Check(hash: Buffer, version: number, network = BITCOIN_NETWORK): string {
  48. typeforce(types.tuple(types.Hash160bit, types.UInt16), [hash, version]);
  49. return bs58check.encodeAddress(hash, version, network);
  50. }
  51. export function toBech32(data: Buffer, version: number, prefix: string) {
  52. const words = bech32.toWords(data);
  53. words.unshift(version);
  54. return version === 0 ? bech32.encode(prefix, words) : bech32m.encode(prefix, words);
  55. }
  56. const FUTURE_SEGWIT_MAX_SIZE = 40;
  57. const FUTURE_SEGWIT_MIN_SIZE = 2;
  58. const FUTURE_SEGWIT_MAX_VERSION = 16;
  59. const FUTURE_SEGWIT_MIN_VERSION = 1;
  60. const FUTURE_SEGWIT_VERSION_DIFF = 0x50;
  61. function toFutureSegwitAddress(output: Buffer, network = BITCOIN_NETWORK) {
  62. const data = output.slice(2);
  63. if (data.length < FUTURE_SEGWIT_MIN_SIZE || data.length > FUTURE_SEGWIT_MAX_SIZE)
  64. throw new TypeError('Invalid program length for segwit address');
  65. const version = output[0] - FUTURE_SEGWIT_VERSION_DIFF;
  66. if (version < FUTURE_SEGWIT_MIN_VERSION || version > FUTURE_SEGWIT_MAX_VERSION)
  67. throw new TypeError('Invalid version for segwit address');
  68. if (output[1] !== data.length) throw new TypeError('Invalid script for segwit address');
  69. return toBech32(data, version, network.bech32);
  70. }
  71. export function fromOutputScript(output: Buffer, network = BITCOIN_NETWORK) {
  72. try {
  73. return payments.p2pkh({ output, network }).address as string;
  74. } catch (e) {
  75. // empty
  76. }
  77. try {
  78. return payments.p2sh({ output, network }).address as string;
  79. } catch (e) {
  80. // empty
  81. }
  82. try {
  83. return payments.p2wpkh({ output, network }).address as string;
  84. } catch (e) {
  85. // empty
  86. }
  87. try {
  88. return payments.p2wsh({ output, network }).address as string;
  89. } catch (e) {
  90. // empty
  91. }
  92. try {
  93. return payments.p2tr({ output, network }).address as string;
  94. } catch (e) {
  95. // empty
  96. }
  97. try {
  98. return toFutureSegwitAddress(output, network);
  99. } catch (e) {
  100. // empty
  101. }
  102. throw new Error(`${bscript.toASM(output)} has no matching Address`);
  103. }
  104. export function toOutputScript(address: string, network = BITCOIN_NETWORK) {
  105. let decodeBase58: Base58CheckResult | undefined;
  106. let decodeBech32: Bech32Result | undefined;
  107. try {
  108. decodeBase58 = fromBase58Check(address, network);
  109. } catch (e) {
  110. // silent error
  111. }
  112. if (decodeBase58) {
  113. if (decodeBase58.version === network.pubKeyHash)
  114. return payments.p2pkh({ hash: decodeBase58.hash }).output as Buffer;
  115. if (decodeBase58.version === network.scriptHash)
  116. return payments.p2sh({ hash: decodeBase58.hash }).output as Buffer;
  117. } else {
  118. try {
  119. decodeBech32 = fromBech32(address);
  120. } catch (e) {
  121. // silent error
  122. }
  123. if (decodeBech32) {
  124. if (decodeBech32.prefix !== network.bech32)
  125. throw new Error(`${address} has an invalid prefix`);
  126. if (decodeBech32.version === 0) {
  127. if (decodeBech32.data.length === 20)
  128. return payments.p2wpkh({ hash: decodeBech32.data }).output as Buffer;
  129. if (decodeBech32.data.length === 32)
  130. return payments.p2wsh({ hash: decodeBech32.data }).output as Buffer;
  131. }
  132. if (
  133. decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION &&
  134. decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&
  135. decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE &&
  136. decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE
  137. ) {
  138. return bscript.compile([
  139. decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF,
  140. decodeBech32.data,
  141. ]);
  142. }
  143. }
  144. }
  145. throw new Error(`${address} has no matching Script`);
  146. }