|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | + |
| 3 | +pragma solidity ^0.8.0; |
| 4 | + |
| 5 | +/** |
| 6 | + * @dev Provides a set of functions to operate with Base64 strings. |
| 7 | + */ |
| 8 | +library InlineAsmBase64 { |
| 9 | + /** |
| 10 | + * @dev Base64 Encoding/Decoding Table |
| 11 | + */ |
| 12 | + string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| 13 | + |
| 14 | + /** |
| 15 | + * @dev Converts a `bytes` to its Bytes64 `string` representation. |
| 16 | + */ |
| 17 | + function encode(bytes memory data) internal pure returns (string memory) { |
| 18 | + /** |
| 19 | + * Inspired by OpenZepplin Base64 implementation |
| 20 | + * https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2884/commits/157c32b65a15cb0b58257543643cafa1cebf883a |
| 21 | + */ |
| 22 | + if (data.length == 0) return ""; |
| 23 | + |
| 24 | + // Loads the table into memory |
| 25 | + string memory table = _TABLE; |
| 26 | + |
| 27 | + // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter |
| 28 | + // and split into 4 numbers of 6 bits. |
| 29 | + // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up |
| 30 | + // - `data.length + 2` -> Round up |
| 31 | + // - `/ 3` -> Number of 3-bytes chunks |
| 32 | + // - `4 *` -> 4 characters for each chunk |
| 33 | + uint256 encodedLen = 4 * ((data.length + 2) / 3); |
| 34 | + |
| 35 | + // Add some extra buffer at the end required for the writing |
| 36 | + string memory result = new string(encodedLen); |
| 37 | + |
| 38 | + assembly { |
| 39 | + // Store the actual result length in memory |
| 40 | + mstore(result, encodedLen) |
| 41 | + |
| 42 | + // Prepare the lookup table |
| 43 | + let tablePtr := add(table, 1) |
| 44 | + |
| 45 | + // Prepare input pointer |
| 46 | + let dataPtr := data |
| 47 | + let endPtr := add(dataPtr, mload(data)) |
| 48 | + |
| 49 | + // Prepare result pointer, jump over length |
| 50 | + let resultPtr := add(result, 32) |
| 51 | + |
| 52 | + // Run over the input, 3 bytes at a time |
| 53 | + for { |
| 54 | + |
| 55 | + } lt(dataPtr, endPtr) { |
| 56 | + |
| 57 | + } { |
| 58 | + // Advance 3 bytes |
| 59 | + dataPtr := add(dataPtr, 3) |
| 60 | + let input := mload(dataPtr) |
| 61 | + |
| 62 | + // To write each character, shift the 3 bytes (24 bits) chunk 4 |
| 63 | + // times in blocks of 6 bits for each character (18, 12, 6, 0) |
| 64 | + // and apply logical AND with 0x3F to extract the 6-bit group. |
| 65 | + // Add the 6-bit group with the table ptr to index into the |
| 66 | + // table and acquire the character to write. Finally, write |
| 67 | + // the character to the result pointer. |
| 68 | + |
| 69 | + mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F)))) |
| 70 | + resultPtr := add(resultPtr, 1) // Advance |
| 71 | + |
| 72 | + mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F)))) |
| 73 | + resultPtr := add(resultPtr, 1) // Advance |
| 74 | + |
| 75 | + mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F)))) |
| 76 | + resultPtr := add(resultPtr, 1) // Advance |
| 77 | + |
| 78 | + mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F)))) |
| 79 | + resultPtr := add(resultPtr, 1) // Advance |
| 80 | + } |
| 81 | + |
| 82 | + // When data `bytes` is not exactly 3 bytes long |
| 83 | + // it is padded with `=` characters at the end |
| 84 | + switch mod(mload(data), 3) |
| 85 | + case 1 { |
| 86 | + mstore8(sub(resultPtr, 1), 0x3d) |
| 87 | + mstore8(sub(resultPtr, 2), 0x3d) |
| 88 | + } |
| 89 | + case 2 { |
| 90 | + mstore8(sub(resultPtr, 1), 0x3d) |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + return result; |
| 95 | + } |
| 96 | +} |
0 commit comments