From 13703dc1efd2ac32aeafbb972904e52dab074eda Mon Sep 17 00:00:00 2001 From: cristianizzo Date: Sun, 26 Jul 2026 01:06:29 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9E=20Fix=20Base58=20`decodeWord`=20sa?= =?UTF-8?q?nitizer=20ordering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `decodeWord` performed `mload(c)` before validating the character, so an input byte below `'1'` (0x31) underflowed the lookup index and expanded memory, reverting with out-of-gas instead of a clean `Base58DecodingError`. Validate the character before the load (as `decode` already does), then perform the multiplication/addition overflow check. --- src/utils/Base58.sol | 11 +++++++++-- test/Base58.t.sol | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/utils/Base58.sol b/src/utils/Base58.sol index a6f6d391f..e3af96294 100644 --- a/src/utils/Base58.sol +++ b/src/utils/Base58.sol @@ -189,10 +189,17 @@ library Base58 { for { let j := 0 } 1 {} { let c := sub(byte(0, mload(add(s, j))), 49) + // Check if the input character is valid before `mload(c)`. + // Otherwise an out-of-bounds `c` expands memory and reverts + // with out-of-gas instead of `Base58DecodingError`. + if iszero(and(shl(c, 1), 0x3fff7ff03ffbeff01ff)) { + mstore(0x00, 0xe8fad793) // `Base58DecodingError()`. + revert(0x1c, 0x04) + } let p := mul(result, 58) let acc := add(byte(0, mload(c)), p) - // Check if the input character is valid. - if iszero(and(0x3fff7ff03ffbeff01ff, shl(c, lt(lt(acc, p), lt(result, t))))) { + // Check for multiplication or addition overflow. + if iszero(lt(lt(acc, p), lt(result, t))) { mstore(0x00, 0xe8fad793) // `Base58DecodingError()`. revert(0x1c, 0x04) } diff --git a/test/Base58.t.sol b/test/Base58.t.sol index f39dfbe6e..83c392d45 100644 --- a/test/Base58.t.sol +++ b/test/Base58.t.sol @@ -259,6 +259,21 @@ contract Base58Test is SoladyTest { this.decodeWord("JEKNVnkbo3jma5nREBBJCDoXFVeKkD56V3xKrvRmWxFH@"); } + function testDecodeWordLowCharacterReverts() public { + // Characters below '1' (0x31) underflow the lookup index. The + // sanitizer must run before `mload(c)`, otherwise the out-of-bounds + // load expands memory and reverts with out-of-gas instead of a clean + // `Base58DecodingError`. See https://github.com/Vectorized/solady/issues/1543. + vm.expectRevert(Base58.Base58DecodingError.selector); + this.decodeWord("0"); + vm.expectRevert(Base58.Base58DecodingError.selector); + this.decodeWord("\x00"); + // Also cover an underflowing byte after a valid character, where + // `result` is already nonzero (loop position > 0). + vm.expectRevert(Base58.Base58DecodingError.selector); + this.decodeWord("z0"); + } + function decodeWord(string memory encoded) public pure returns (bytes32) { return Base58.decodeWord(encoded); }