"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.BitcoinAddress = void 0;
exports.decode = decode;
exports.decodeTo5BitArray = decodeTo5BitArray;
exports.encode = encode;
exports.encode5BitArray = encode5BitArray;
exports.from5BitArray = from5BitArray;
exports.to5BitArray = to5BitArray;
var _bitConverter = require("./bit-converter");
var _encoding = require("./encoding");
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
// Maximum encoded message length
var MAX_ENC_LENGTH = Number.MAX_SAFE_INTEGER;
/**
 * Converts a Uint8Array into a Uint8Array variant, in which each element
 * encodes 5 bits of the original byte array.
 *
 * @param {Uint8Array} src
 *   Input to convert
 * @param {?Uint8Array} dst
 *   Optional output buffer. If specified, the sequence of 5-bit chunks will be written there;
 *   if not specified, the output buffer will be created from scratch. The length
 *   of `outBuffer` is not checked.
 * @returns {Uint8Array}
 *   Output buffer consisting of 5-bit chunks
 *
 * @api public
 */
function to5BitArray(src, dst) {
  var len = Math.ceil(src.length * 8 / 5);
  var realDst = dst || (0, _bitConverter.createBitArray)(len);
  return (0, _bitConverter.toBits)(src, 5, realDst);
}
function from5BitArray(src, dst) {
  var len = Math.floor(src.length * 5 / 8);
  var realDst = dst || new Uint8Array(len);
  return (0, _bitConverter.fromBits)(src, 5, realDst);
}

/**
 * Encodes binary data into Bech32 encoding.
 *
 * The case is preserved: if the prefix is uppercase, then the output will be uppercase
 * as well; otherwise, the output will be lowercase (including the case when the prefix does
 * not contain any letters).
 *
 * Ordinarily, you may want to use [`encode`](#encode) because it converts
 * binary data to an array of 5-bit integers automatically.
 *
 * @param {string} prefix
 *   Human-readable prefix to place at the beginning of the encoding
 * @param {Uint8Array} data
 *   Array of 5-bit integers with data to encode
 * @param {Encoding} encoding
 *   Encoding to use; influences the checksum computation. If not specified,
 *   Bech32 encoding will be used.
 * @returns {string}
 *   Bech32 encoding of data in the form `<prefix>1<base32 of data><checksum>`
 * @throws If the prefix is mixed-case or contains chars that are not eligible for Bech32 encoding
 *
 * @api public
 */
function encode5BitArray(prefix, data) {
  var _detectCase;
  var encoding = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'bech32';
  // 1. Allocate buffer for all operations
  var len = 2 * prefix.length + 1 // expanded prefix
  + data.length // five-bit data encoding
  + _encoding.CHECKSUM_LENGTH; // checksum

  if (len - prefix.length > MAX_ENC_LENGTH) {
    throw new Error("Message to be produced is too long (max ".concat(MAX_ENC_LENGTH, " supported)"));
  }
  var prefixCase = (_detectCase = (0, _encoding.detectCase)(prefix, 'prefix')) !== null && _detectCase !== void 0 ? _detectCase : 'lower';
  var buffer = (0, _bitConverter.createBitArray)(len);

  // 2. Expand the human-readable prefix into the beginning of the buffer
  (0, _encoding.expandPrefix)(prefix.toLowerCase(), buffer.subarray(0, 2 * prefix.length + 1));

  // 3. Copy `data` into the output
  var dataBuffer = buffer.subarray(2 * prefix.length + 1, buffer.length - _encoding.CHECKSUM_LENGTH);
  dataBuffer.set(data);

  // 4. Create the checksum
  (0, _encoding.createChecksum)(buffer, encoding);

  // 5. Convert into string
  var encoded = (0, _encoding.encode)(buffer.subarray(2 * prefix.length + 1));
  if (prefixCase === 'upper') {
    encoded = encoded.toUpperCase();
  }
  return "".concat(prefix, "1").concat(encoded);
}

/**
 * Encodes binary data into Bech32 encoding.
 *
 * The case is preserved: if the prefix is uppercase, then the output will be uppercase
 * as well; otherwise, the output will be lowercase (including the case when the prefix does
 * not contain any letters).
 *
 * @param {string} prefix
 *   Human-readable prefix to place at the beginning of the encoding
 * @param {Uint8Array} data
 *   Binary data to encode
 * @param {Encoding} encoding
 *   Encoding to use; influences the checksum computation. If not specified,
 *   Bech32 encoding will be used.
 * @returns {string}
 *   Bech32 encoding of data in the form `<prefix>1<base32 of data><checksum>`
 * @throws If the prefix is mixed-case or contains chars that are not eligible for Bech32 encoding
 *
 * @api public
 */
function encode(prefix, data) {
  var encoding = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'bech32';
  return encode5BitArray(prefix, to5BitArray(data), encoding);
}

/**
 * Decodes data from Bech32 encoding into an array of 5-bit integers.
 *
 * Ordinarily, you may want to use [`decode`](#decode) because it automatically
 * converts the array of 5-bit integers into an ordinary `Uint8Array`.
 *
 * @param {string} message
 *   Bech32-encoded message
 * @returns {DecodeResult<FiveBitArray>}
 *   Decoded object with `prefix` and `data` fields, which contain the human-readable
 *   prefix and the array of 5-bit integers respectively.
 *
 * @api public
 */
function decodeTo5BitArray(message) {
  // Check preconditions

  // 1. Message length
  if (message.length > MAX_ENC_LENGTH) {
    throw new TypeError("Message too long; max ".concat(MAX_ENC_LENGTH, " expected"));
  }

  // 2. Mixed case
  (0, _encoding.detectCase)(message); // we don't care about the result, only about checks.
  var lowerCaseMsg = message.toLowerCase();

  // 4. Existence of the separator char
  var sepIdx = lowerCaseMsg.lastIndexOf('1');
  if (sepIdx < 0) {
    throw new Error('No separator char ("1") found');
  }

  // 5. Placing of the separator char in the message
  if (sepIdx > message.length - _encoding.CHECKSUM_LENGTH - 1) {
    throw new Error("Data part of the message too short (at least ".concat(_encoding.CHECKSUM_LENGTH, " chars expected)"));
  }
  var prefix = lowerCaseMsg.substring(0, sepIdx);

  // Checked within `decodeWithPrefix`:
  // 6. Invalid chars in the data part of the message
  var bitArray = (0, _encoding.decodeWithPrefix)(prefix, lowerCaseMsg.substring(sepIdx + 1));

  // 7. Checksum
  var encoding = (0, _encoding.verifyChecksum)(bitArray);
  if (encoding === undefined) {
    throw new Error('Invalid checksum');
  }
  return {
    prefix: prefix,
    encoding: encoding,
    // Strip off the prefix from the front and the checksum from the end
    data: bitArray.subarray(2 * prefix.length + 1, bitArray.length - _encoding.CHECKSUM_LENGTH)
  };
}

/**
 * Decodes data from Bech32 encoding into an array of 5-bit integers.
 *
 * @param {string} message
 *   Bech32-encoded message
 * @returns {DecodeResult}
 *   Decoded object with `prefix` and `data` fields, which contain the human-readable
 *   prefix and the decoded binary data respectively.
 *
 * @api public
 */
function decode(message) {
  var _decodeTo5BitArray = decodeTo5BitArray(message),
    prefix = _decodeTo5BitArray.prefix,
    encoding = _decodeTo5BitArray.encoding,
    bitArray = _decodeTo5BitArray.data;
  return {
    prefix: prefix,
    encoding: encoding,
    data: from5BitArray(bitArray)
  };
}

/**
 * Bitcoin address.
 */
var BitcoinAddress = /*#__PURE__*/function () {
  function BitcoinAddress(prefix, scriptVersion, data) {
    _classCallCheck(this, BitcoinAddress);
    if (prefix !== 'bc' && prefix !== 'tb') {
      throw new Error('Invalid human-readable prefix, "bc" or "tb" expected');
    }
    if (scriptVersion < 0 || scriptVersion > 16) {
      throw new RangeError('Invalid scriptVersion, value in range [0, 16] expected');
    }
    if (data.length < 2 || data.length > 40) {
      throw new RangeError('Invalid script length: expected 2 to 40 bytes');
    }
    if (scriptVersion === 0 && data.length !== 20 && data.length !== 32) {
      throw new Error('Invalid v0 script length: expected 20 or 32 bytes');
    }
    this.prefix = prefix;
    this.scriptVersion = scriptVersion;
    this.data = data;
  }

  /**
   * Guesses the address type based on its internal structure.
   *
   * @returns {void | 'p2wpkh' | 'p2wsh'}
   */
  _createClass(BitcoinAddress, [{
    key: "type",
    value: function type() {
      if (this.scriptVersion !== 0) {
        return undefined;
      }
      switch (this.data.length) {
        case 20:
          return 'p2wpkh';
        case 32:
          return 'p2wsh';

        // should be unreachable, but it's JS, so you never know
        default:
          return undefined;
      }
    }

    /**
     * Encodes this address in Bech32 or Bech32m format, depending on the script version.
     * Version 0 scripts are encoded using original Bech32 encoding as per BIP 173,
     * while versions 1-16 are encoded using the modified encoding as per BIP 350.
     *
     * @returns {string}
     *   Bech32(m)-encoded address
     */
  }, {
    key: "encode",
    value: function encode() {
      // Bitcoin addresses use Bech32 in a peculiar way - script version is
      // not a part of the serialized binary data, but is rather prepended as 5-bit value
      // before the rest of the script. This necessitates some plumbing here.
      var len = Math.ceil(this.data.length * 8 / 5);
      var converted = (0, _bitConverter.createBitArray)(len + 1);
      converted[0] = this.scriptVersion;
      to5BitArray(this.data, converted.subarray(1));
      var encoding = this.scriptVersion === 0 ? 'bech32' : 'bech32m';
      return encode5BitArray(this.prefix, converted, encoding);
    }
  }], [{
    key: "decode",
    value:
    /**
     * Human-readable prefix. Equal to `'bc'` (for mainnet addresses)
     * or `'tb'` (for testnet addresses).
     */

    /**
     * Script version. An integer between 0 and 16 (inclusive).
     */

    /**
     * Script data. A byte string with length 2 to 40 (inclusive).
     */

    /**
     * Decodes a Bitcoin address from a Bech32(m) string.
     * As per BIP 350, the original encoding is expected for version 0 scripts, while
     * other script versions expect the modified encoding.
     *
     * This method does not check whether the address is well-formed;
     * use `type()` method on returned address to find that out.
     *
     * @param {string} message
     * @returns {BitcoinAddress}
     */
    function decode(message) {
      var _decodeTo5BitArray2 = decodeTo5BitArray(message),
        prefix = _decodeTo5BitArray2.prefix,
        data = _decodeTo5BitArray2.data,
        encoding = _decodeTo5BitArray2.encoding;

      // Extra check to satisfy Flow.
      if (prefix !== 'bc' && prefix !== 'tb') {
        throw new Error('Invalid human-readable prefix, "bc" or "tb" expected');
      }
      var scriptVersion = data[0];
      if (scriptVersion === 0 && encoding !== 'bech32') {
        throw Error("Unexpected encoding ".concat(encoding, " used for version 0 script"));
      }
      if (scriptVersion > 0 && encoding !== 'bech32m') {
        throw Error("Unexpected encoding ".concat(encoding, " used for version ").concat(scriptVersion, " script"));
      }
      return new this(prefix, scriptVersion, from5BitArray(data.subarray(1)));
    }
  }]);
  return BitcoinAddress;
}();
exports.BitcoinAddress = BitcoinAddress;