Skip to content

Commit

Permalink
Refactor/bech32m (#9)
Browse files Browse the repository at this point in the history
* remove long comments bech32m.sol

* remove long comments bech32m.sol

* add explainDecodeError

* change license
  • Loading branch information
szhygulin authored May 29, 2024
1 parent 3eecd05 commit 7f5c314
Showing 1 changed file with 82 additions and 142 deletions.
224 changes: 82 additions & 142 deletions src/Bech32m.sol
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
// SPDX-License-Identifier: MIT
// https://github.com/gregdhill/bech32-sol/blob/master/src/Bech32.sol
// TODO(mkl): what License?
// based on https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
// https://github.com/sipa/bech32/blob/master/ref/python/segwit_addr.py
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,
IncorrectLength,
CharacterOutOfRange,
MixedCase,
//checksum does not match
IncorrectChecksum,
//checksum bytes length is too short
TooShortChecksum,
InputIsTooLong,
NotBech32Character,
// Network prefix is not set
HRPIsEmpty,
NoDelimiter,
// decoded HRP is different from expected HRP
Expand All @@ -47,110 +49,76 @@ library Bech32m {
IncorrectEncodingForSegwitVn
}

// TODO(mkl): implement ExplainDecodeError(DecodeError err) -> string

// using BytesLib for bytes;
function explainDecodeError(DecodeError err) public pure returns (string memory) {
if (err == DecodeError.NoError) {
return string("No error");
} else if (err == DecodeError.IncorrectPadding) {
return string("Incorrect Padding");
} else if (err == DecodeError.IncorrectLength) {
return string("Incorrect address length");
} else if (err == DecodeError.CharacterOutOfRange) {
return string("Address contain character out of range");
} else if (err == DecodeError.MixedCase) {
return string("Address consists of both capital and small letters");
} else if (err == DecodeError.IncorrectChecksum) {
return string("Address checksum does not match");
} else if (err == DecodeError.TooShortChecksum) {
return string("Address checksum is too short");
} else if (err == DecodeError.InputIsTooLong) {
return string("Address is too long");
} else if (err == DecodeError.NotBech32Character) {
return string("Address contains character which is not in bech32 encoding");
} else if (err == DecodeError.HRPIsEmpty) {
return string("Network prefix is empty");
} else if (err == DecodeError.NoDelimiter) {
return string("No prefix delimiter in the address");
} else if (err == DecodeError.HRPMismatch) {
return string("Network prefix is different from expected");
} else if (err == DecodeError.WitnessProgramTooSmall) {
return string("Witness program should be at least 2 bytes");
} else if (err == DecodeError.EmptyData) {
return string("Witness program is empty");
} else if (err == DecodeError.WitnessProgramTooLarge) {
return string("Witness program should be maximum 40 bytes");
} else if (err == DecodeError.SegwitVersionTooLarge) {
return string("Segwit version should be from 0 to 16 (including). Got some larger number.");
} else if (err == DecodeError.IncorrectSegwitV0Program) {
return string("Length of segwit v0 program should be either 20 or 32 bytes");
} else if (err == DecodeError.IncorrectEncodingForSegwitV0) {
return string("Segwit v0 should be encoded using Bech32");
} else if (err == DecodeError.IncorrectEncodingForSegwitVn) {
return string("Segwit with versions 1-16 should be encoded with Bech32m");
}
return "";
}

// 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,
0x1ea119fa,
0x3d4233dd,
0x2a1462b3
];

uint chk = 1;
for (uint p = 0; p < values.length; p++) {
uint top = chk >> 25;
Expand All @@ -164,15 +132,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;
Expand All @@ -182,17 +145,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);
Expand All @@ -209,26 +168,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[](
Expand All @@ -252,16 +209,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();
}
Expand All @@ -285,8 +239,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.
Expand Down Expand Up @@ -364,7 +318,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;
Expand All @@ -375,6 +329,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) {
Expand Down Expand Up @@ -659,22 +614,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
Expand Down

0 comments on commit 7f5c314

Please sign in to comment.