From 940c0328e638292d05cb54d5c669c5b9f8467ac1 Mon Sep 17 00:00:00 2001 From: Viacheslav Zhygulin Date: Wed, 24 Jul 2024 13:59:39 +0300 Subject: [PATCH 1/5] make functions in lib internal for upgradability safety --- src/BitcoinNetworkEncoder.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BitcoinNetworkEncoder.sol b/src/BitcoinNetworkEncoder.sol index 29cb6b7..b30983d 100644 --- a/src/BitcoinNetworkEncoder.sol +++ b/src/BitcoinNetworkEncoder.sol @@ -22,7 +22,7 @@ library BitcoinNetworkEncoder { Simnet } - function getBtcBech32Prefix(Network _network) public pure returns (bytes memory) { + function getBtcBech32Prefix(Network _network) internal pure returns (bytes memory) { if (_network == Network.Mainnet) { return BTC_BECH32_MAINNET_BYTES; } else if (_network == Network.Regtest) { @@ -36,7 +36,7 @@ library BitcoinNetworkEncoder { } } - function getNetworkPrefix(Network _network) public pure returns (string memory) { + function getNetworkPrefix(Network _network) internal pure returns (string memory) { if (_network == Network.Mainnet) { return BTC_BECH32_MAINNET; } else if (_network == Network.Testnet) { From 98b49f3f8e1fbff07a4a72cbb25cd8e0f47b027e Mon Sep 17 00:00:00 2001 From: Viacheslav Zhygulin Date: Wed, 24 Jul 2024 16:14:51 +0300 Subject: [PATCH 2/5] fix bug in simnet address encodings --- src/BitcoinUtils.sol | 13 ++++++------ test/BitcoinUtils_Simnet.t.sol | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 test/BitcoinUtils_Simnet.t.sol diff --git a/src/BitcoinUtils.sol b/src/BitcoinUtils.sol index 5fda5bb..6f42949 100644 --- a/src/BitcoinUtils.sol +++ b/src/BitcoinUtils.sol @@ -84,10 +84,10 @@ contract BitcoinUtils { bytes constant BTC_P2SH_TESTNET = hex"6d"; // prefix = m bytes constant BTC_P2PKH_REGTEST = hex"32"; // prefix = 2 bytes constant BTC_P2SH_REGTEST = hex"6d"; // prefix = m - bytes constant BTC_P2PKH_SIMNET = hex"3f"; // prefix = S - bytes constant BTC_P2SH_SIMNET = hex"7b"; // prefix = s + bytes constant BTC_P2PKH_SIMNET = hex"53"; // prefix = S + bytes constant BTC_P2SH_SIMNET = hex"73"; // prefix = s - function getBtcBase58_P2PKH(BitcoinNetworkEncoder.Network network) public pure returns (bytes memory) { + function getBtcBase58_P2PKH(BitcoinNetworkEncoder.Network network) internal pure returns (bytes memory) { if (network == BitcoinNetworkEncoder.Network.Mainnet) { return BTC_P2PKH_MAINNET; } else if (network == BitcoinNetworkEncoder.Network.Regtest) { @@ -101,7 +101,7 @@ contract BitcoinUtils { } } - function getBtcBase58_P2SH(BitcoinNetworkEncoder.Network network) public pure returns (bytes memory) { + function getBtcBase58_P2SH(BitcoinNetworkEncoder.Network network) internal pure returns (bytes memory) { if (network == BitcoinNetworkEncoder.Network.Mainnet) { return BTC_P2SH_MAINNET; } else if (network == BitcoinNetworkEncoder.Network.Regtest) { @@ -129,8 +129,10 @@ contract BitcoinUtils { bytes memory BTC_P2PKH = getBtcBase58_P2PKH(network); bytes memory BTC_P2SH = getBtcBase58_P2SH(network); + bytes memory prefix = BitcoinNetworkEncoder.getBtcBech32Prefix(network); - if (equalBytes(bytes(BTCAddress)[: 1], BTC_P2PKH) || equalBytes(bytes(BTCAddress)[: 1], BTC_P2SH)) { + if (equalBytes(bytes(BTCAddress)[: 1], BTC_P2PKH) || equalBytes(bytes(BTCAddress)[: 1], BTC_P2SH) && + !equalBytes(bytes(BTCAddress)[: 3], prefix)) { if (bytes(BTCAddress).length < 26 || bytes(BTCAddress).length > 35 || !alphabetCheck(bytes(BTCAddress))) { return false; } @@ -139,7 +141,6 @@ contract BitcoinUtils { return validateBase58Checksum(BTCAddress); } - bytes memory prefix = BitcoinNetworkEncoder.getBtcBech32Prefix(network); if (equalBytes(bytes(BTCAddress)[: prefix.length], prefix)) { if (network == BitcoinNetworkEncoder.Network.Regtest) { if (bytes(BTCAddress).length < 43 || bytes(BTCAddress).length > 63) return false; diff --git a/test/BitcoinUtils_Simnet.t.sol b/test/BitcoinUtils_Simnet.t.sol new file mode 100644 index 0000000..0ac56ca --- /dev/null +++ b/test/BitcoinUtils_Simnet.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; + +import "../src/BitcoinUtils.sol"; +import "../src/BitcoinNetworkEncoder.sol"; + +// See also https://en.bitcoin.it/wiki/List_of_address_prefixes + +contract BitcoinUtils_Testnet_Test is Test { + BitcoinNetworkEncoder.Network private network = BitcoinNetworkEncoder.Network.Simnet; + BitcoinUtils private utils = new BitcoinUtils(); + + + function testValidAddress() public { + assertTrue(utils.validateBitcoinAddress(network, "ScuV2eqXfQCPcpxqqVSFtMVwkfqcwnQKB1")); + //assertTrue(utils.validateBitcoinAddress(network, "mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r")); + //assertTrue(utils.validateBitcoinAddress(network, "2NFPLS6TQVVvic6Nh85PGfcYesbGdm1fjpo")); + } + + function testInvalidAddress() public { + assertFalse(utils.validateBitcoinAddress(network, "")); + assertFalse(utils.validateBitcoinAddress(network, "7SeEnXWPaCCALbVrTnszCVGfRU8cGfx")); + assertFalse(utils.validateBitcoinAddress(network, "j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe")); + } + + function testBech32ValidAddress() public { + assertTrue(utils.validateBitcoinAddress(network, "sb1p5z8wl5tu7m0d79vzqqsl9gu0x4fkjug857fusx4fl4kfgwh5j25sxv5dv3")); + assertTrue(utils.validateBitcoinAddress(network, "sb1pfusykjdt46ktwq03d20uqqf94uh9487344wr3q5v9szzsxnjdfkszvtlt8")); + } + + function testBech32ValidMainnetAddressIsNotValidForTestnet() public { + assertFalse(utils.validateBitcoinAddress(network, "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0")); + assertFalse(utils.validateBitcoinAddress(network, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")); + } +} From d11eb46b66d8df7597d13e2369ce93c41d0c636e Mon Sep 17 00:00:00 2001 From: Viacheslav Zhygulin Date: Wed, 24 Jul 2024 16:22:02 +0300 Subject: [PATCH 3/5] add more tests --- test/BitcoinUtils_Simnet.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/BitcoinUtils_Simnet.t.sol b/test/BitcoinUtils_Simnet.t.sol index 0ac56ca..cee0f77 100644 --- a/test/BitcoinUtils_Simnet.t.sol +++ b/test/BitcoinUtils_Simnet.t.sol @@ -16,8 +16,7 @@ contract BitcoinUtils_Testnet_Test is Test { function testValidAddress() public { assertTrue(utils.validateBitcoinAddress(network, "ScuV2eqXfQCPcpxqqVSFtMVwkfqcwnQKB1")); - //assertTrue(utils.validateBitcoinAddress(network, "mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r")); - //assertTrue(utils.validateBitcoinAddress(network, "2NFPLS6TQVVvic6Nh85PGfcYesbGdm1fjpo")); + assertTrue(utils.validateBitcoinAddress(network, "SYi7rot5GKoyuRNUnjrfKYRBL7F4e9L8bN")); } function testInvalidAddress() public { From 9ecc968a96fee3a86355ac2f466d34bb6c4b59ae Mon Sep 17 00:00:00 2001 From: Viacheslav Zhygulin Date: Wed, 24 Jul 2024 16:50:43 +0300 Subject: [PATCH 4/5] fix bug with bitcoin address prefix check --- src/BitcoinUtils.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BitcoinUtils.sol b/src/BitcoinUtils.sol index 6f42949..1e6e04f 100644 --- a/src/BitcoinUtils.sol +++ b/src/BitcoinUtils.sol @@ -132,7 +132,7 @@ contract BitcoinUtils { bytes memory prefix = BitcoinNetworkEncoder.getBtcBech32Prefix(network); if (equalBytes(bytes(BTCAddress)[: 1], BTC_P2PKH) || equalBytes(bytes(BTCAddress)[: 1], BTC_P2SH) && - !equalBytes(bytes(BTCAddress)[: 3], prefix)) { + !equalBytes(bytes(BTCAddress)[: prefix.length], prefix)) { if (bytes(BTCAddress).length < 26 || bytes(BTCAddress).length > 35 || !alphabetCheck(bytes(BTCAddress))) { return false; } From 42d3f5dfcc82093574aa5c44bb60ef6e5b548966 Mon Sep 17 00:00:00 2001 From: Viacheslav Zhygulin Date: Wed, 24 Jul 2024 16:53:13 +0300 Subject: [PATCH 5/5] refactor network encoder --- src/BitcoinNetworkEncoder.sol | 35 +++++++++++++++++++++++++++++++ src/BitcoinUtils.sol | 39 ++--------------------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/BitcoinNetworkEncoder.sol b/src/BitcoinNetworkEncoder.sol index b30983d..0bd6831 100644 --- a/src/BitcoinNetworkEncoder.sol +++ b/src/BitcoinNetworkEncoder.sol @@ -14,6 +14,15 @@ library BitcoinNetworkEncoder { string constant BTC_BECH32_REGTEST = 'brct'; string constant BTC_BECH32_SIMNET = 'sb'; + bytes constant BTC_P2PKH_MAINNET = hex"31"; // prefix = 1 + bytes constant BTC_P2SH_MAINNET = hex"33"; // prefix = 3 + bytes constant BTC_P2PKH_TESTNET = hex"32"; // prefix = 2 + bytes constant BTC_P2SH_TESTNET = hex"6d"; // prefix = m + bytes constant BTC_P2PKH_REGTEST = hex"32"; // prefix = 2 + bytes constant BTC_P2SH_REGTEST = hex"6d"; // prefix = m + bytes constant BTC_P2PKH_SIMNET = hex"53"; // prefix = S + bytes constant BTC_P2SH_SIMNET = hex"73"; // prefix = s + //NB: don't forget to update `lnbtc_ext.go` when changing this enum! enum Network { Mainnet, @@ -50,5 +59,31 @@ library BitcoinNetworkEncoder { } } + function getBtcBase58_P2PKH(BitcoinNetworkEncoder.Network network) internal pure returns (bytes memory) { + if (network == BitcoinNetworkEncoder.Network.Mainnet) { + return BTC_P2PKH_MAINNET; + } else if (network == BitcoinNetworkEncoder.Network.Regtest) { + return BTC_P2PKH_REGTEST; + } else if (network == BitcoinNetworkEncoder.Network.Testnet) { + return BTC_P2PKH_TESTNET; + } else if (network == BitcoinNetworkEncoder.Network.Simnet) { + return BTC_P2PKH_SIMNET; + } else { + revert("Unknown network type"); + } + } + function getBtcBase58_P2SH(BitcoinNetworkEncoder.Network network) internal pure returns (bytes memory) { + if (network == BitcoinNetworkEncoder.Network.Mainnet) { + return BTC_P2SH_MAINNET; + } else if (network == BitcoinNetworkEncoder.Network.Regtest) { + return BTC_P2SH_REGTEST; + } else if (network == BitcoinNetworkEncoder.Network.Testnet) { + return BTC_P2SH_TESTNET; + } else if (network == BitcoinNetworkEncoder.Network.Simnet) { + return BTC_P2SH_SIMNET; + } else { + revert("Unknown network type"); + } + } } \ No newline at end of file diff --git a/src/BitcoinUtils.sol b/src/BitcoinUtils.sol index 1e6e04f..9143970 100644 --- a/src/BitcoinUtils.sol +++ b/src/BitcoinUtils.sol @@ -78,42 +78,7 @@ contract BitcoinUtils { // ALPHABET_MAP[x] = z; // } - bytes constant BTC_P2PKH_MAINNET = hex"31"; // prefix = 1 - bytes constant BTC_P2SH_MAINNET = hex"33"; // prefix = 3 - bytes constant BTC_P2PKH_TESTNET = hex"32"; // prefix = 2 - bytes constant BTC_P2SH_TESTNET = hex"6d"; // prefix = m - bytes constant BTC_P2PKH_REGTEST = hex"32"; // prefix = 2 - bytes constant BTC_P2SH_REGTEST = hex"6d"; // prefix = m - bytes constant BTC_P2PKH_SIMNET = hex"53"; // prefix = S - bytes constant BTC_P2SH_SIMNET = hex"73"; // prefix = s - - function getBtcBase58_P2PKH(BitcoinNetworkEncoder.Network network) internal pure returns (bytes memory) { - if (network == BitcoinNetworkEncoder.Network.Mainnet) { - return BTC_P2PKH_MAINNET; - } else if (network == BitcoinNetworkEncoder.Network.Regtest) { - return BTC_P2PKH_REGTEST; - } else if (network == BitcoinNetworkEncoder.Network.Testnet) { - return BTC_P2PKH_TESTNET; - } else if (network == BitcoinNetworkEncoder.Network.Simnet) { - return BTC_P2PKH_SIMNET; - } else { - revert("Unknown network type"); - } - } - function getBtcBase58_P2SH(BitcoinNetworkEncoder.Network network) internal pure returns (bytes memory) { - if (network == BitcoinNetworkEncoder.Network.Mainnet) { - return BTC_P2SH_MAINNET; - } else if (network == BitcoinNetworkEncoder.Network.Regtest) { - return BTC_P2SH_REGTEST; - } else if (network == BitcoinNetworkEncoder.Network.Testnet) { - return BTC_P2SH_TESTNET; - } else if (network == BitcoinNetworkEncoder.Network.Simnet) { - return BTC_P2SH_SIMNET; - } else { - revert("Unknown network type"); - } - } function validateBitcoinAddress( BitcoinNetworkEncoder.Network network, @@ -127,8 +92,8 @@ contract BitcoinUtils { console.log("\nraw address data"); console.logBytes(bytes(BTCAddress)); - bytes memory BTC_P2PKH = getBtcBase58_P2PKH(network); - bytes memory BTC_P2SH = getBtcBase58_P2SH(network); + bytes memory BTC_P2PKH = BitcoinNetworkEncoder.getBtcBase58_P2PKH(network); + bytes memory BTC_P2SH = BitcoinNetworkEncoder.getBtcBase58_P2SH(network); bytes memory prefix = BitcoinNetworkEncoder.getBtcBech32Prefix(network); if (equalBytes(bytes(BTCAddress)[: 1], BTC_P2PKH) || equalBytes(bytes(BTCAddress)[: 1], BTC_P2SH) &&