-
Notifications
You must be signed in to change notification settings - Fork 11.8k
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 Base64 library to utils #2884
Merged
Amxx
merged 11 commits into
OpenZeppelin:master
from
ernestognw:feature/add-base64-library-#2859
Dec 29, 2021
Merged
Changes from 9 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
157c32b
Add Base64 library to utils
ernestognw df3d838
Fix typo on Base64 padding
ernestognw 7c187e0
Added documentation for Base64 and references from ERC1155 and ERC721
ernestognw c8a317c
Updated Changelog
ernestognw 9094dfa
Fix typo in utilities doc
ernestognw 5adf579
Merge branch 'master' into feature/add-base64-library-#2859
Amxx c1060ca
use mstore8 to improve memory accesses
Amxx 703bb20
Merge branch 'master' into feature/add-base64-library-#2859
Amxx c1f86e4
Merge branch 'master' into feature/add-base64-library-#2859
Amxx e48057c
use shorter strings with encodePacked
frangio f0fc459
do not use using-for syntax, for clarity
frangio File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'); | ||
}); | ||
}); | ||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just my 2 cents but as an alternative, you can have the following line at the top of the contract:
using Base64 for bytes;
and then in your encode:
value.encode();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it looks better just using the
.encode
but I'm following the same pattern used for Strings mock. I'll wait if there's somebody from the OZ team who has input on this and why to use one or the other.