Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Base64Url encoding #4822

Merged
merged 17 commits into from
Jan 16, 2024
Prev Previous commit
Next Next commit
refactor base64 encoding
  • Loading branch information
Amxx committed Jan 10, 2024
commit b217af39eee2ac2f4c386f0004fe3d4c1fcf74b3
107 changes: 29 additions & 78 deletions contracts/utils/Base64.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

pragma solidity ^0.8.20;

import "hardhat/console.sol";

Check failure on line 6 in contracts/utils/Base64.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path hardhat/console.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

/**
* @dev Provides a set of functions to operate with Base64 strings.
*/
Expand All @@ -18,95 +20,33 @@
* @dev Converts a `bytes` to its Bytes64 `string` representation.
*/
function encode(bytes memory data) internal pure returns (string memory) {
/**
* Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
* https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
*/
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
string memory result = new string(4 * ((data.length + 2) / 3));

/// @solidity memory-safe-assembly
assembly {
// Prepare the lookup table (skip the first "length" byte)
let tablePtr := add(table, 1)

// Prepare result pointer, jump over length
let resultPtr := add(result, 32)

// Run over the input, 3 bytes at a time
for {
let dataPtr := data
let endPtr := add(data, mload(data))
} lt(dataPtr, endPtr) {

} {
// Advance 3 bytes
dataPtr := add(dataPtr, 3)
let input := mload(dataPtr)

// To write each character, shift the 3 bytes (18 bits) chunk
// 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
// and apply logical AND with 0x3F which is the number of
// the previous character in the ASCII table prior to the Base64 Table
// The result is then added to the table to get the character to write,
// and finally write it in the result pointer but with a left shift
// of 256 (1 byte) - 8 (1 ASCII char) = 248 bits

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;
return _encode(data, _TABLE, true);
}

/**
* @dev Converts a `bytes` to its Bytes64Url `string` representation.
*/
function encodeUrl(bytes memory data) internal pure returns (string memory) {
return _encode(data, _TABLE_URL, false);
}

/**
* @dev Internal table-agnostic conversion
*/
function _encode(bytes memory data, string memory table, bool withPadding) private pure returns (string memory) {
/**
* Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
* https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
*/
if (data.length == 0) return "";

// Loads the table into memory
string memory table = _TABLE_URL;

// Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
// and split into 4 numbers of 6 bits.
// Rounding here is different from the one performed in {encode} because we don't want padding
string memory result = new string((4 * data.length + 2) / 3);
// Encoding takes 3 bytes chunks of binary data from `bytes` data parameter and split into 4 numbers of 6 bits.
// If padding is enabled, 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
// If padding is not enable, then the length is rounded up differently
string memory result = new string(withPadding ? 4 * ((data.length + 2) / 3) : (4 * data.length + 2) / 3);

/// @solidity memory-safe-assembly
assembly {
Expand Down Expand Up @@ -148,7 +88,18 @@
resultPtr := add(resultPtr, 1) // Advance
}

// No `=` padding here
if withPadding {
// 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;
Expand Down
Loading