Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/bech32m #9

Merged
merged 6 commits into from
May 29, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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