forked from OpenZeppelin/openzeppelin-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Base64 library to utils (OpenZeppelin#2884)
* Add Base64 library to utils * Fix typo on Base64 padding * Added documentation for Base64 and references from ERC1155 and ERC721 * Updated Changelog * Fix typo in utilities doc * use mstore8 to improve memory accesses * use shorter strings with encodePacked * do not use using-for syntax, for clarity Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
- Loading branch information
1 parent
da3a9ae
commit ef0273f
Showing
8 changed files
with
185 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "../utils/Base64.sol"; | ||
|
||
contract Base64Mock { | ||
function encode(bytes memory value) external pure returns (string memory) { | ||
return Base64.encode(value); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
/** | ||
* @dev Provides a set of functions to operate with Base64 strings. | ||
*/ | ||
library Base64 { | ||
/** | ||
* @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 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)); | ||
|
||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
const { expect } = require('chai'); | ||
|
||
const Base64Mock = artifacts.require('Base64Mock'); | ||
|
||
contract('Strings', function () { | ||
beforeEach(async function () { | ||
this.base64 = await Base64Mock.new(); | ||
}); | ||
|
||
describe('from bytes - base64', function () { | ||
it('converts to base64 encoded string with double padding', async function () { | ||
const TEST_MESSAGE = 'test'; | ||
const input = web3.utils.asciiToHex(TEST_MESSAGE); | ||
expect(await this.base64.encode(input)).to.equal('dGVzdA=='); | ||
}); | ||
|
||
it('converts to base64 encoded string with single padding', async function () { | ||
const TEST_MESSAGE = 'test1'; | ||
const input = web3.utils.asciiToHex(TEST_MESSAGE); | ||
expect(await this.base64.encode(input)).to.equal('dGVzdDE='); | ||
}); | ||
|
||
it('converts to base64 encoded string without padding', async function () { | ||
const TEST_MESSAGE = 'test12'; | ||
const input = web3.utils.asciiToHex(TEST_MESSAGE); | ||
expect(await this.base64.encode(input)).to.equal('dGVzdDEy'); | ||
}); | ||
}); | ||
}); |