mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
17fa85a2fb
Adds two implementations of Base64 encoding as specified in RFC4648. Implementation (1) uses inline assembly, while Implementation (2) is written purely in Solidity. Assertions are added to replicate the test vectors specified in the RFC for Base64 to ensure both implementations to specification.
97 lines
3.4 KiB
Solidity
97 lines
3.4 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
|
|
pragma solidity ^0.8.0;
|
|
|
|
/**
|
|
* @dev Provides a set of functions to operate with Base64 strings.
|
|
*/
|
|
library InlineAsmBase64 {
|
|
/**
|
|
* @dev Base64 Encoding/Decoding Table
|
|
*/
|
|
string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
/**
|
|
* @dev Converts a `bytes` to its Bytes64 `string` representation.
|
|
*/
|
|
function encode(bytes memory data) internal pure returns (string memory) {
|
|
/**
|
|
* Inspired by OpenZepplin Base64 implementation
|
|
* https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2884/commits/157c32b65a15cb0b58257543643cafa1cebf883a
|
|
*/
|
|
if (data.length == 0) return "";
|
|
|
|
// Loads the table into memory
|
|
string memory table = _TABLE;
|
|
|
|
// Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
|
|
// and split into 4 numbers of 6 bits.
|
|
// The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
|
|
// - `data.length + 2` -> Round up
|
|
// - `/ 3` -> Number of 3-bytes chunks
|
|
// - `4 *` -> 4 characters for each chunk
|
|
uint256 encodedLen = 4 * ((data.length + 2) / 3);
|
|
|
|
// Add some extra buffer at the end required for the writing
|
|
string memory result = new string(encodedLen);
|
|
|
|
assembly {
|
|
// Store the actual result length in memory
|
|
mstore(result, encodedLen)
|
|
|
|
// Prepare the lookup table
|
|
let tablePtr := add(table, 1)
|
|
|
|
// Prepare input pointer
|
|
let dataPtr := data
|
|
let endPtr := add(dataPtr, mload(data))
|
|
|
|
// Prepare result pointer, jump over length
|
|
let resultPtr := add(result, 32)
|
|
|
|
// Run over the input, 3 bytes at a time
|
|
for {
|
|
|
|
} lt(dataPtr, endPtr) {
|
|
|
|
} {
|
|
// Advance 3 bytes
|
|
dataPtr := add(dataPtr, 3)
|
|
let input := mload(dataPtr)
|
|
|
|
// To write each character, shift the 3 bytes (24 bits) chunk 4
|
|
// times in blocks of 6 bits for each character (18, 12, 6, 0)
|
|
// and apply logical AND with 0x3F to extract the 6-bit group.
|
|
// Add the 6-bit group with the table ptr to index into the
|
|
// table and acquire the character to write. Finally, write
|
|
// the character to the result pointer.
|
|
|
|
mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
|
|
resultPtr := add(resultPtr, 1) // Advance
|
|
|
|
mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
|
|
resultPtr := add(resultPtr, 1) // Advance
|
|
|
|
mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
|
|
resultPtr := add(resultPtr, 1) // Advance
|
|
|
|
mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
|
|
resultPtr := add(resultPtr, 1) // Advance
|
|
}
|
|
|
|
// When data `bytes` is not exactly 3 bytes long
|
|
// it is padded with `=` characters at the end
|
|
switch mod(mload(data), 3)
|
|
case 1 {
|
|
mstore8(sub(resultPtr, 1), 0x3d)
|
|
mstore8(sub(resultPtr, 2), 0x3d)
|
|
}
|
|
case 2 {
|
|
mstore8(sub(resultPtr, 1), 0x3d)
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|