|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +pragma solidity ^0.8.4; |
| 3 | + |
| 4 | +/// @notice Library to encode strings in Base58. |
| 5 | +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base58.sol) |
| 6 | +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Base58.sol) |
| 7 | +library Base58 { |
| 8 | + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ |
| 9 | + /* CUSTOM ERRORS */ |
| 10 | + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ |
| 11 | + |
| 12 | + /// @dev An unrecognized character was encountered during decoding. |
| 13 | + error Base58DecodingError(); |
| 14 | + |
| 15 | + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ |
| 16 | + /* ENCODING / DECODING */ |
| 17 | + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ |
| 18 | + |
| 19 | + /// @dev Encodes `data` into a Base58 string. |
| 20 | + function encode(bytes memory data) internal pure returns (string memory result) { |
| 21 | + uint256 l = data.length; |
| 22 | + if (l == uint256(0)) return result; |
| 23 | + /// @solidity memory-safe-assembly |
| 24 | + assembly { |
| 25 | + let b := add(data, 0x20) // Start of `data` bytes. |
| 26 | + let z := 0 // Number of leading zero bytes in `data`. |
| 27 | + // Count leading zero bytes. |
| 28 | + for {} lt(byte(0, mload(add(b, z))), lt(z, l)) {} { z := add(1, z) } |
| 29 | + |
| 30 | + // Start the output offset by an over-estimate of the length. |
| 31 | + let o := add(add(mload(0x40), 0x20), add(z, add(1, div(mul(sub(l, z), 8351), 6115)))) |
| 32 | + let e := o |
| 33 | + |
| 34 | + let limbs := o |
| 35 | + let limbsEnd := limbs |
| 36 | + // Populate the uint248 limbs. |
| 37 | + for { |
| 38 | + let i := mod(l, 31) |
| 39 | + if i { |
| 40 | + mstore(limbsEnd, shr(shl(3, add(1, sub(31, i))), mload(b))) |
| 41 | + limbsEnd := add(limbsEnd, 0x20) |
| 42 | + } |
| 43 | + } lt(i, l) { i := add(i, 31) } { |
| 44 | + mstore(limbsEnd, shr(8, mload(add(b, i)))) |
| 45 | + limbsEnd := add(limbsEnd, 0x20) |
| 46 | + } |
| 47 | + |
| 48 | + // Use the extended scratch space for the lookup. We'll restore 0x40 later. |
| 49 | + mstore(0x1f, "123456789ABCDEFGHJKLMNPQRSTUVWXY") |
| 50 | + mstore(0x3f, "Zabcdefghijkmnopqrstuvwxyz") |
| 51 | + |
| 52 | + let w := not(0) // -1. |
| 53 | + for {} 1 {} { |
| 54 | + let i := limbs |
| 55 | + for {} 1 {} { |
| 56 | + if mload(i) { break } |
| 57 | + i := add(i, 0x20) |
| 58 | + if eq(i, limbsEnd) { break } |
| 59 | + } |
| 60 | + if eq(i, limbsEnd) { break } |
| 61 | + |
| 62 | + let carry := 0 |
| 63 | + for { i := limbs } 1 {} { |
| 64 | + let acc := add(shl(248, carry), mload(i)) |
| 65 | + mstore(i, div(acc, 58)) |
| 66 | + carry := mod(acc, 58) |
| 67 | + i := add(i, 0x20) |
| 68 | + if eq(i, limbsEnd) { break } |
| 69 | + } |
| 70 | + o := add(o, w) |
| 71 | + mstore8(o, mload(carry)) |
| 72 | + } |
| 73 | + // We probably can optimize this more by writing 32 bytes at a time. |
| 74 | + for {} z { z := add(z, w) } { |
| 75 | + o := add(o, w) |
| 76 | + mstore8(o, 49) // '1' in ASCII. |
| 77 | + } |
| 78 | + |
| 79 | + let n := sub(e, o) // Compute the final length. |
| 80 | + result := sub(o, 0x20) // Move back one word for the length. |
| 81 | + mstore(result, n) // Store the length. |
| 82 | + mstore(add(add(result, 0x20), n), 0) // Zeroize the slot after the bytes. |
| 83 | + mstore(0x40, add(add(result, 0x40), n)) // Allocate memory. |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + /// @dev Decodes `encoded`, a Base58 string, into the original bytes. |
| 88 | + function decode(string memory encoded) internal pure returns (bytes memory result) { |
| 89 | + uint256 n = bytes(encoded).length; |
| 90 | + if (n == uint256(0)) return result; |
| 91 | + /// @solidity memory-safe-assembly |
| 92 | + assembly { |
| 93 | + let s := add(encoded, 0x20) |
| 94 | + let z := 0 // Number of leading '1' in `data`. |
| 95 | + // Count leading '1'. |
| 96 | + for {} and(eq(49, byte(0, mload(add(s, z)))), lt(z, n)) {} { z := add(1, z) } |
| 97 | + |
| 98 | + // Start the output offset by an over-estimate of the length. |
| 99 | + let o := add(add(mload(0x40), 0x20), add(z, add(1, div(mul(sub(n, z), 7736), 10000)))) |
| 100 | + let e := o |
| 101 | + let limbs := o |
| 102 | + let limbsEnd := limbs |
| 103 | + let limbMask := shr(8, not(0)) |
| 104 | + // Use the extended scratch space for the lookup. We'll restore 0x40 later. |
| 105 | + mstore(0x2a, 0x30313233343536373839) |
| 106 | + mstore(0x20, 0x1718191a1b1c1d1e1f20ffffffffffff2122232425262728292a2bff2c2d2e2f) |
| 107 | + mstore(0x00, 0x000102030405060708ffffffffffffff090a0b0c0d0e0f10ff1112131415ff16) |
| 108 | + |
| 109 | + for { let j := 0 } 1 {} { |
| 110 | + let c := sub(byte(0, mload(add(s, j))), 49) |
| 111 | + // Check if the input character is valid. |
| 112 | + if iszero(and(shl(c, 1), 0x3fff7ff03ffbeff01ff)) { |
| 113 | + mstore(0x00, 0xe8fad793) // `Base58DecodingError()`. |
| 114 | + revert(0x1c, 0x04) |
| 115 | + } |
| 116 | + let carry := byte(0, mload(c)) |
| 117 | + for { let i := limbs } iszero(eq(i, limbsEnd)) { i := add(i, 0x20) } { |
| 118 | + let acc := add(carry, mul(58, mload(i))) |
| 119 | + mstore(i, and(limbMask, acc)) |
| 120 | + carry := shr(248, acc) |
| 121 | + } |
| 122 | + // Carry will always be < 58. |
| 123 | + if carry { |
| 124 | + mstore(limbsEnd, carry) |
| 125 | + limbsEnd := add(limbsEnd, 0x20) |
| 126 | + } |
| 127 | + j := add(j, 1) |
| 128 | + if eq(j, n) { break } |
| 129 | + } |
| 130 | + // Copy and compact the uint248 limbs. |
| 131 | + for { let i := limbs } iszero(eq(i, limbsEnd)) { i := add(i, 0x20) } { |
| 132 | + o := sub(o, 31) |
| 133 | + mstore(sub(o, 1), mload(i)) |
| 134 | + } |
| 135 | + // Strip any leading zeros from the limbs. |
| 136 | + for {} lt(byte(0, mload(o)), lt(o, e)) {} { o := add(o, 1) } |
| 137 | + o := sub(o, z) // Move back for the leading zero bytes. |
| 138 | + calldatacopy(o, calldatasize(), z) // Fill the leading zero bytes. |
| 139 | + |
| 140 | + let l := sub(e, o) // Compute the final length. |
| 141 | + result := sub(o, 0x20) // Move back one word for the length. |
| 142 | + mstore(result, l) // Store the length. |
| 143 | + mstore(add(add(result, 0x20), l), 0) // Zeroize the slot after the bytes. |
| 144 | + mstore(0x40, add(add(result, 0x40), l)) // Allocate memory. |
| 145 | + } |
| 146 | + } |
| 147 | +} |
0 commit comments