Base64.sol 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. // SPDX-License-Identifier: MIT
  2. // OpenZeppelin Contracts (last updated v5.0.2) (utils/Base64.sol)
  3. pragma solidity ^0.8.20;
  4. /**
  5. * @dev Provides a set of functions to operate with Base64 strings.
  6. */
  7. library Base64 {
  8. /**
  9. * @dev Base64 Encoding/Decoding Table
  10. */
  11. string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  12. /**
  13. * @dev Converts a `bytes` to its Bytes64 `string` representation.
  14. */
  15. function encode(bytes memory data) internal pure returns (string memory) {
  16. /**
  17. * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
  18. * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
  19. */
  20. if (data.length == 0) return "";
  21. // Loads the table into memory
  22. string memory table = _TABLE;
  23. // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
  24. // and split into 4 numbers of 6 bits.
  25. // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
  26. // - `data.length + 2` -> Round up
  27. // - `/ 3` -> Number of 3-bytes chunks
  28. // - `4 *` -> 4 characters for each chunk
  29. string memory result = new string(4 * ((data.length + 2) / 3));
  30. /// @solidity memory-safe-assembly
  31. assembly {
  32. // Prepare the lookup table (skip the first "length" byte)
  33. let tablePtr := add(table, 1)
  34. // Prepare result pointer, jump over length
  35. let resultPtr := add(result, 0x20)
  36. let dataPtr := data
  37. let endPtr := add(data, mload(data))
  38. // In some cases, the last iteration will read bytes after the end of the data. We cache the value, and
  39. // set it to zero to make sure no dirty bytes are read in that section.
  40. let afterPtr := add(endPtr, 0x20)
  41. let afterCache := mload(afterPtr)
  42. mstore(afterPtr, 0x00)
  43. // Run over the input, 3 bytes at a time
  44. for {
  45. } lt(dataPtr, endPtr) {
  46. } {
  47. // Advance 3 bytes
  48. dataPtr := add(dataPtr, 3)
  49. let input := mload(dataPtr)
  50. // To write each character, shift the 3 byte (24 bits) chunk
  51. // 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
  52. // and apply logical AND with 0x3F to bitmask the least significant 6 bits.
  53. // Use this as an index into the lookup table, mload an entire word
  54. // so the desired character is in the least significant byte, and
  55. // mstore8 this least significant byte into the result and continue.
  56. mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
  57. resultPtr := add(resultPtr, 1) // Advance
  58. mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
  59. resultPtr := add(resultPtr, 1) // Advance
  60. mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
  61. resultPtr := add(resultPtr, 1) // Advance
  62. mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
  63. resultPtr := add(resultPtr, 1) // Advance
  64. }
  65. // Reset the value that was cached
  66. mstore(afterPtr, afterCache)
  67. // When data `bytes` is not exactly 3 bytes long
  68. // it is padded with `=` characters at the end
  69. switch mod(mload(data), 3)
  70. case 1 {
  71. mstore8(sub(resultPtr, 1), 0x3d)
  72. mstore8(sub(resultPtr, 2), 0x3d)
  73. }
  74. case 2 {
  75. mstore8(sub(resultPtr, 1), 0x3d)
  76. }
  77. }
  78. return result;
  79. }
  80. }