diff --git a/src/Bech32m.sol b/src/Bech32m.sol index c8ddaa3..2101f74 100644 --- a/src/Bech32m.sol +++ b/src/Bech32m.sol @@ -7,17 +7,16 @@ pragma solidity ^0.8.18; error EncodingIsUnknown(); library Bech32m { + enum BechEncoding { // Used is SegWit v.0 BECH32, // Used in SegWit v.1, e.g. Taproot BECH32M, - // Specifies an unknown encoding - // Usually it means some error + // Specifies an unknown encoding, usually it means some error UNKNOWN } - // TODO(mkl): add comment about errors. enum DecodeError { NoError, IncorrectPadding, @@ -28,6 +27,7 @@ library Bech32m { TooShortChecksum, InputIsTooLong, NotBech32Character, + // Network prefix is not set HRPIsEmpty, NoDelimiter, // decoded HRP is different from expected HRP @@ -49,101 +49,25 @@ library Bech32m { // TODO(mkl): implement ExplainDecodeError(DecodeError err) -> string - // using BytesLib for bytes; - - // CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" - bytes public constant CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + // Possible characters for Bitcoin address + bytes internal constant CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; - // generatated by gen_reverse_charset.py // index is character code in ASCII // value is Bech32 character value // if value is 0x7f=127 then character is not in the Bech32 charset - bytes public constant REVERSE_CHARSET = + bytes internal constant REVERSE_CHARSET = hex"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f0f7f0a1115141a1e07057f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f1d7f180d190908177f12161f1b137f010003100b1c0c0e0604027f7f7f7f7f"; - // BECH32M_CONST = 0x2bc830a3 - uint256 public constant BECH32M_CONST = 0x2bc830a3; + // Bech32m constant + uint256 internal constant BECH32M_CONST = 0x2bc830a3; // 1 byte for the separator - bytes1 public constant SEPARATOR = bytes1(0x31); - - // TODO(mkl): port implementation from C++ reference because it is more readable - // https://github.com/sipa/bech32/blob/master/ref/c%2B%2B/bech32.cpp - // /** This function will compute what 6 5-bit values to XOR into the last 6 input values, in order to - // * make the checksum 0. These 6 values are packed together in a single 30-bit integer. The higher - // * bits correspond to earlier values. */ - // uint32_t polymod(const data& values) - // { - // // The input is interpreted as a list of coefficients of a polynomial over F = GF(32), with an - // // implicit 1 in front. If the input is [v0,v1,v2,v3,v4], that polynomial is v(x) = - // // 1*x^5 + v0*x^4 + v1*x^3 + v2*x^2 + v3*x + v4. The implicit 1 guarantees that - // // [v0,v1,v2,...] has a distinct checksum from [0,v0,v1,v2,...]. - - // // The output is a 30-bit integer whose 5-bit groups are the coefficients of the remainder of - // // v(x) mod g(x), where g(x) is the Bech32 generator, - // // x^6 + {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18}. g(x) is chosen in such a way - // // that the resulting code is a BCH code, guaranteeing detection of up to 3 errors within a - // // window of 1023 characters. Among the various possible BCH codes, one was selected to in - // // fact guarantee detection of up to 4 errors within a window of 89 characters. - - // // Note that the coefficients are elements of GF(32), here represented as decimal numbers - // // between {}. In this finite field, addition is just XOR of the corresponding numbers. For - // // example, {27} + {13} = {27 ^ 13} = {22}. Multiplication is more complicated, and requires - // // treating the bits of values themselves as coefficients of a polynomial over a smaller field, - // // GF(2), and multiplying those polynomials mod a^5 + a^3 + 1. For example, {5} * {26} = - // // (a^2 + 1) * (a^4 + a^3 + a) = (a^4 + a^3 + a) * a^2 + (a^4 + a^3 + a) = a^6 + a^5 + a^4 + a - // // = a^3 + 1 (mod a^5 + a^3 + 1) = {9}. - - // // During the course of the loop below, `c` contains the bitpacked coefficients of the - // // polynomial constructed from just the values of v that were processed so far, mod g(x). In - // // the above example, `c` initially corresponds to 1 mod g(x), and after processing 2 inputs of - // // v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value - // // for `c`. - // uint32_t c = 1; - // for (const auto v_i : values) { - // // We want to update `c` to correspond to a polynomial with one extra term. If the initial - // // value of `c` consists of the coefficients of c(x) = f(x) mod g(x), we modify it to - // // correspond to c'(x) = (f(x) * x + v_i) mod g(x), where v_i is the next input to - // // process. Simplifying: - // // c'(x) = (f(x) * x + v_i) mod g(x) - // // ((f(x) mod g(x)) * x + v_i) mod g(x) - // // (c(x) * x + v_i) mod g(x) - // // If c(x) = c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5, we want to compute - // // c'(x) = (c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5) * x + v_i mod g(x) - // // = c0*x^6 + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i mod g(x) - // // = c0*(x^6 mod g(x)) + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i - // // If we call (x^6 mod g(x)) = k(x), this can be written as - // // c'(x) = (c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i) + c0*k(x) - - // // First, determine the value of c0: - // uint8_t c0 = c >> 25; - - // // Then compute c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i: - // c = ((c & 0x1ffffff) << 5) ^ v_i; - - // // Finally, for each set bit n in c0, conditionally add {2^n}k(x): - // if (c0 & 1) c ^= 0x3b6a57b2; // k(x) = {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18} - // if (c0 & 2) c ^= 0x26508e6d; // {2}k(x) = {19}x^5 + {5}x^4 + x^3 + {3}x^2 + {19}x + {13} - // if (c0 & 4) c ^= 0x1ea119fa; // {4}k(x) = {15}x^5 + {10}x^4 + {2}x^3 + {6}x^2 + {15}x + {26} - // if (c0 & 8) c ^= 0x3d4233dd; // {8}k(x) = {30}x^5 + {20}x^4 + {4}x^3 + {12}x^2 + {30}x + {29} - // if (c0 & 16) c ^= 0x2a1462b3; // {16}k(x) = {21}x^5 + x^4 + {8}x^3 + {24}x^2 + {21}x + {19} - // } - // return c; - // } - - // def bech32_polymod(values): - // """Internal function that computes the Bech32 checksum.""" - // generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] - // chk = 1 - // for value in values: - // top = chk >> 25 - // chk = (chk & 0x1ffffff) << 5 ^ value - // for i in range(5): - // chk ^= generator[i] if ((top >> i) & 1) else 0 - // return chk - // TODO(mkl): what this function is actually doing? - // TODO(mkl): should values be bytes? - function polymod(uint[] memory values) public pure returns (uint) { + bytes1 internal constant SEPARATOR = bytes1(0x31); + + // Internal function that computes the Bech32 checksum. + function polymod(uint[] memory values) internal pure returns (uint) { + + // Generator constants uint32[5] memory GENERATOR = [ 0x3b6a57b2, 0x26508e6d, @@ -151,6 +75,7 @@ library Bech32m { 0x3d4233dd, 0x2a1462b3 ]; + uint chk = 1; for (uint p = 0; p < values.length; p++) { uint top = chk >> 25; @@ -164,15 +89,10 @@ library Bech32m { return chk; } - // def bech32_hrp_expand(hrp): - // """Expand the HRP into values for checksum computation.""" - // return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] - // According to https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki - // The human-readable part, which is intended to convey the type of data, or anything else that is relevant to the reader. - // This part MUST contain 1 to 83 US-ASCII characters, with each character having a value in the range [33-126]. - // HRP validity may be further restricted by specific applications. + // Expand the HRP into values for checksum computation. // hrpExpand DOES NOT check the validity of the HRP function hrpExpand(bytes memory hrp) public pure returns (bytes memory) { + bytes memory a = new bytes(hrp.length + hrp.length + 1); for (uint i = 0; i < hrp.length; i += 1) { a[i] = hrp[i] >> 5; @@ -182,17 +102,13 @@ library Bech32m { return a; } - // def bech32_create_checksum(hrp, data, spec): - // """Compute the checksum values given HRP and data.""" - // values = bech32_hrp_expand(hrp) + data - // const = BECH32M_CONST if spec == Encoding.BECH32M else 1 - // polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const - // return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] + // Compute the checksum values given HRP and data. function createChecksum( bytes memory hrp, bytes memory data, BechEncoding spec ) public pure returns (bytes memory) { + // TODO(mkl): add check for UNKNOWN encoding uint const = spec == BechEncoding.BECH32M ? BECH32M_CONST : 1; bytes memory hrpExpandBytes = hrpExpand(hrp); @@ -209,26 +125,24 @@ library Bech32m { uint polymodVal = polymod(polymodArg) ^ const; - // TODO(mkl): unroll the loop bytes memory chk = new bytes(6); - for (uint p = 0; p < 6; p += 1) { - chk[p] = bytes1(uint8((polymodVal >> (5 * (5 - p))) & 31)); - } + + chk[0] = bytes1(uint8((polymodVal >> (5 * (5))) & 31)); + chk[1] = bytes1(uint8((polymodVal >> (5 * (4))) & 31)); + chk[2] = bytes1(uint8((polymodVal >> (5 * (3))) & 31)); + chk[3] = bytes1(uint8((polymodVal >> (5 * (2))) & 31)); + chk[4] = bytes1(uint8((polymodVal >> (5 * (1))) & 31)); + chk[5] = bytes1(uint8((polymodVal >> (5 * (0))) & 31)); + return chk; } - // def bech32_verify_checksum(hrp, data): - // """Verify a checksum given HRP and converted data characters.""" - // const = bech32_polymod(bech32_hrp_expand(hrp) + data) - // if const == 1: - // return Encoding.BECH32 - // if const == BECH32M_CONST: - // return Encoding.BECH32M - // return None + // Verify a checksum given HRP and converted data characters. function verifyChecksum( bytes memory hrp, bytes memory data - ) public pure returns (BechEncoding) { + ) internal pure returns (BechEncoding) { + bytes memory hrpExpandBytes = hrpExpand(hrp); uint[] memory polymodArg = new uint[]( @@ -252,16 +166,13 @@ library Bech32m { return BechEncoding.UNKNOWN; } - // def bech32_encode(hrp, data, spec): - // """Compute a Bech32 string given HRP and data values.""" - // combined = data + bech32_create_checksum(hrp, data, spec) - // return hrp + '1' + ''.join([CHARSET[d] for d in combined]) - // data shold be converted to 5bit format before calling this function + // Compute a Bech32 string given HRP and data values. function bech32Encode( bytes memory hrp, bytes memory data, BechEncoding spec ) public pure returns (bytes memory) { + if (spec == BechEncoding.UNKNOWN) { revert EncodingIsUnknown(); } @@ -285,8 +196,8 @@ library Bech32m { return bytes.concat(hrp, SEPARATOR, data, chk); } - // with padding - function conver8To5(bytes memory a) public pure returns (bytes memory) { + // Convert 5 bytes to 8 groups of 5 bits, with padding + function conver8To5(bytes memory a) internal pure returns (bytes memory) { // We group the data into 5-byte groups // Because 5-byte group has the bitlength 40 // Thus it can be easily converted to 8 5-bit groups. @@ -364,7 +275,7 @@ library Bech32m { bytes memory hrp, uint8 witVer, bytes memory witProg - ) public pure returns (bytes memory) { + ) internal pure returns (bytes memory) { BechEncoding spec = witVer == 0 ? BechEncoding.BECH32 : BechEncoding.BECH32M; @@ -375,6 +286,7 @@ library Bech32m { return bech32Encode(hrp, encArg, spec); } + // Convert 8 groups of 5 bits to 5 bytes function convert5to8( bytes memory data5Bits ) public pure returns (bytes memory, DecodeError) { @@ -659,22 +571,7 @@ library Bech32m { return true; } - // def decode(hrp, addr): - // """Decode a segwit address.""" - // hrpgot, data, spec = bech32_decode(addr) - // if hrpgot != hrp: - // return (None, None) - // decoded = convertbits(data[1:], 5, 8, False) - // if decoded is None or len(decoded) < 2 or len(decoded) > 40: - // return (None, None) - // if data[0] > 16: - // return (None, None) - // if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: - // return (None, None) - // if data[0] == 0 and spec != Encoding.BECH32 or data[0] != 0 and spec != Encoding.BECH32M: - // return (None, None) - // return (data[0], decoded) - // returns witVer, witProg, err + // Decode a segwit address function decodeSegwitAddress( bytes calldata expectedHrp, bytes calldata addr