JS function: Bech32 address to hex

A bech32 address like bc1qjpjanssen2x7586qzr8fnmk0epfdttqwfkkvlx is encoded as hex (809065d9c2199a8dea1f4010ce99eecfc852d5ac0e) inside the Counterparty data.

See Counterparty Decoder

The encoded hex address corresponds to step 3 in this guide (with prefix 80 being the version byte).

I’ve written a function that converts address to hex:

      function bech32toHex(address) { //https://en.bitcoin.it/wiki/Bech32
        const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
        address = address.toLowerCase();

        let data = [];
        for (let p = 4; p < address.length; ++p) {
          let d = CHARSET.indexOf(address.charAt(p));
          if (d === -1) {
            return null;
          }
          data.push(d);
        }

        let bin5 = [];
        for (let i = 0; i <= 32; i++) {
          bin5.push(data[i].toString(2).padStart(5, '0'));
        }

        let binString = ''
        for (let i = 0; i < bin5.length; i++) {
          binString += bin5[i];
        }

        let bin8 = binString.match(/.{8}/g);

        let hex = '';
        for (let i = 0; i < bin8.length; i++) {
          hex += parseInt(bin8[i], 2).toString(16).padStart(2, '0');
        }
        return hex;
      }

It’s on my TODO to write the reverse function, hexToBech32() but it’s not a priority right now. If someone feels like trying, go ahead – I’d be grateful if you share the result.

Here is the reverse function :partying_face:

Converts hex to bech32 address.

E.g input 809065d9c2199a8dea1f4010ce99eecfc852d5ac0e returns bc1qjpjanssen2x7586qzr8fnmk0epfdttqwfkkvlx

(I included a function for standard base58 addresses too.)

      function hex_to_address(hex) { //21 byte hex encoded in cntrprty message 
        let version_byte = hex.substring(0,2);
        if (version_byte == '00' || version_byte == '05') {
          return hex_to_base58addr(hex);
        }
        if (version_byte == '80') {
          return hex_to_bech32addr(hex); 
        }
        return 'cannot decode address';
      }

      function hex_to_base58addr(hex) {
        const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
        const base = BigInt(58);
        let checksum = sha256(hex2a(hex));
        checksum = sha256(hex2a(checksum));
        checksum = checksum.substring(0,8);
        hex += checksum
        let decimal = BigInt('0x' + hex);
        let output = '';
        while (decimal > 0) {
          let rem = decimal % base;
          decimal = BigInt(decimal / base);
          output = ALPHABET[Number(rem)] + output;
        }
        //Leading 00's must be converted to 1's
        let numLeadingZeros = Math.floor(hex.match(/^0+/)[0].length / 2);
        for (let i = 0; i < numLeadingZeros; i++) {
          output = "1" + output;
        }
        return output;
      }

      function hex_to_bech32addr(hex) {
        const version = 0;
        const hrp = 'bc';

        //remove version byte ('80') from hex string
        hex = hex.substring(2);

        //the rest follows step 3 on https://en.bitcoin.it/wiki/Bech32
        // convert hex string to binary format
        const binaryString = hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16).toString(2).padStart(8, '0')).join('');

        //Split binary string into 5-bit chunks and convert to integer array
        const intArray = binaryString.match(/.{1,5}/g).map(chunk => parseInt(chunk, 2));

        //Add the witness version byte in front
        intArray.unshift(version);

        //Calculate checksum
        let chk = bech32_checksum(hrp, intArray);

        //Append checksum
        intArray.push(...chk);

        //Map to bech32 charset
        const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
        let addr = hrp + '1';
        for (let i = 0; i < intArray.length; i++) {
          addr += charset.charAt(intArray[i]);
        }
        return addr;
      }

      //Calculate bech32 checksum
      //Copied from https://github.com/sipa/bech32/blob/master/ref/javascript/bech32.js
      //Modified to assume BECH32 encoding (not BECH32M)
      function bech32_checksum(hrp, data) {
        var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
        var mod = polymod(values) ^ 1;
        var ret = [];
        for (var p = 0; p < 6; ++p) {
          ret.push((mod >> 5 * (5 - p)) & 31);
        }
        return ret;
      }
      function polymod(values) {
        const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
        var chk = 1;
        for (var p = 0; p < values.length; ++p) {
          var top = chk >> 25;
          chk = (chk & 0x1ffffff) << 5 ^ values[p];
          for (var i = 0; i < 5; ++i) {
            if ((top >> i) & 1) {
              chk ^= GENERATOR[i];
            }
          }
        }
        return chk;
      }
      function hrpExpand(hrp) {
        var ret = [];
        var p;
        for (p = 0; p < hrp.length; ++p) {
          ret.push(hrp.charCodeAt(p) >> 5);
        }
        ret.push(0);
        for (p = 0; p < hrp.length; ++p) {
          ret.push(hrp.charCodeAt(p) & 31);
        }
        return ret;
      }