Skip to content

Commit 4ff61f8

Browse files
VectorizedAmxx
andauthored
✨ Base58 (#1441)
Co-authored-by: amxx <hadrien.croubois@gmail.com>
1 parent 6da40ac commit 4ff61f8

File tree

5 files changed

+392
-0
lines changed

5 files changed

+392
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ tokens
6060
├─ ERC721 — "Simple ERC721 implementation with storage hitchhiking"
6161
├─ WETH — "Simple Wrapped Ether implementation"
6262
utils
63+
├─ Base58 — "Library for Base58 encoding and decoding"
6364
├─ Base64 — "Library for Base64 encoding and decoding"
6465
├─ CallContextChecker — "Call context checker mixin"
6566
├─ CREATE3 — "Deterministic deployments agnostic to the initialization code"

docs/utils/base58.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Base58
2+
3+
Library to encode strings in Base58.
4+
5+
6+
7+
8+
9+
10+
<!-- customintro:start --><!-- customintro:end -->
11+
12+
## Custom Errors
13+
14+
### Base58DecodingError()
15+
16+
```solidity
17+
error Base58DecodingError()
18+
```
19+
20+
An unrecognized character was encountered during decoding.
21+
22+
## Encoding / Decoding
23+
24+
### encode(bytes)
25+
26+
```solidity
27+
function encode(bytes memory data)
28+
internal
29+
pure
30+
returns (string memory result)
31+
```
32+
33+
Encodes `data` into a Base58 string.
34+
35+
### decode(string)
36+
37+
```solidity
38+
function decode(string memory encoded)
39+
internal
40+
pure
41+
returns (bytes memory result)
42+
```
43+
44+
Decodes `encoded`, a Base58 string, into the original bytes.

src/Milady.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import "./tokens/ERC4626.sol";
2323
import "./tokens/ERC6909.sol";
2424
import "./tokens/ERC721.sol";
2525
import "./tokens/WETH.sol";
26+
import "./utils/Base58.sol";
2627
import "./utils/Base64.sol";
2728
import "./utils/CREATE3.sol";
2829
import "./utils/CallContextChecker.sol";

src/utils/Base58.sol

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
/// @notice Library to encode strings in Base58.
5+
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base58.sol)
6+
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Base58.sol)
7+
library Base58 {
8+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9+
/* CUSTOM ERRORS */
10+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11+
12+
/// @dev An unrecognized character was encountered during decoding.
13+
error Base58DecodingError();
14+
15+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
16+
/* ENCODING / DECODING */
17+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
18+
19+
/// @dev Encodes `data` into a Base58 string.
20+
function encode(bytes memory data) internal pure returns (string memory result) {
21+
uint256 l = data.length;
22+
if (l == uint256(0)) return result;
23+
/// @solidity memory-safe-assembly
24+
assembly {
25+
let b := add(data, 0x20) // Start of `data` bytes.
26+
let z := 0 // Number of leading zero bytes in `data`.
27+
// Count leading zero bytes.
28+
for {} lt(byte(0, mload(add(b, z))), lt(z, l)) {} { z := add(1, z) }
29+
30+
// Start the output offset by an over-estimate of the length.
31+
let o := add(add(mload(0x40), 0x20), add(z, add(1, div(mul(sub(l, z), 8351), 6115))))
32+
let e := o
33+
34+
let limbs := o
35+
let limbsEnd := limbs
36+
// Populate the uint248 limbs.
37+
for {
38+
let i := mod(l, 31)
39+
if i {
40+
mstore(limbsEnd, shr(shl(3, add(1, sub(31, i))), mload(b)))
41+
limbsEnd := add(limbsEnd, 0x20)
42+
}
43+
} lt(i, l) { i := add(i, 31) } {
44+
mstore(limbsEnd, shr(8, mload(add(b, i))))
45+
limbsEnd := add(limbsEnd, 0x20)
46+
}
47+
48+
// Use the extended scratch space for the lookup. We'll restore 0x40 later.
49+
mstore(0x1f, "123456789ABCDEFGHJKLMNPQRSTUVWXY")
50+
mstore(0x3f, "Zabcdefghijkmnopqrstuvwxyz")
51+
52+
let w := not(0) // -1.
53+
for {} 1 {} {
54+
let i := limbs
55+
for {} 1 {} {
56+
if mload(i) { break }
57+
i := add(i, 0x20)
58+
if eq(i, limbsEnd) { break }
59+
}
60+
if eq(i, limbsEnd) { break }
61+
62+
let carry := 0
63+
for { i := limbs } 1 {} {
64+
let acc := add(shl(248, carry), mload(i))
65+
mstore(i, div(acc, 58))
66+
carry := mod(acc, 58)
67+
i := add(i, 0x20)
68+
if eq(i, limbsEnd) { break }
69+
}
70+
o := add(o, w)
71+
mstore8(o, mload(carry))
72+
}
73+
// We probably can optimize this more by writing 32 bytes at a time.
74+
for {} z { z := add(z, w) } {
75+
o := add(o, w)
76+
mstore8(o, 49) // '1' in ASCII.
77+
}
78+
79+
let n := sub(e, o) // Compute the final length.
80+
result := sub(o, 0x20) // Move back one word for the length.
81+
mstore(result, n) // Store the length.
82+
mstore(add(add(result, 0x20), n), 0) // Zeroize the slot after the bytes.
83+
mstore(0x40, add(add(result, 0x40), n)) // Allocate memory.
84+
}
85+
}
86+
87+
/// @dev Decodes `encoded`, a Base58 string, into the original bytes.
88+
function decode(string memory encoded) internal pure returns (bytes memory result) {
89+
uint256 n = bytes(encoded).length;
90+
if (n == uint256(0)) return result;
91+
/// @solidity memory-safe-assembly
92+
assembly {
93+
let s := add(encoded, 0x20)
94+
let z := 0 // Number of leading '1' in `data`.
95+
// Count leading '1'.
96+
for {} and(eq(49, byte(0, mload(add(s, z)))), lt(z, n)) {} { z := add(1, z) }
97+
98+
// Start the output offset by an over-estimate of the length.
99+
let o := add(add(mload(0x40), 0x20), add(z, add(1, div(mul(sub(n, z), 7736), 10000))))
100+
let e := o
101+
let limbs := o
102+
let limbsEnd := limbs
103+
let limbMask := shr(8, not(0))
104+
// Use the extended scratch space for the lookup. We'll restore 0x40 later.
105+
mstore(0x2a, 0x30313233343536373839)
106+
mstore(0x20, 0x1718191a1b1c1d1e1f20ffffffffffff2122232425262728292a2bff2c2d2e2f)
107+
mstore(0x00, 0x000102030405060708ffffffffffffff090a0b0c0d0e0f10ff1112131415ff16)
108+
109+
for { let j := 0 } 1 {} {
110+
let c := sub(byte(0, mload(add(s, j))), 49)
111+
// Check if the input character is valid.
112+
if iszero(and(shl(c, 1), 0x3fff7ff03ffbeff01ff)) {
113+
mstore(0x00, 0xe8fad793) // `Base58DecodingError()`.
114+
revert(0x1c, 0x04)
115+
}
116+
let carry := byte(0, mload(c))
117+
for { let i := limbs } iszero(eq(i, limbsEnd)) { i := add(i, 0x20) } {
118+
let acc := add(carry, mul(58, mload(i)))
119+
mstore(i, and(limbMask, acc))
120+
carry := shr(248, acc)
121+
}
122+
// Carry will always be < 58.
123+
if carry {
124+
mstore(limbsEnd, carry)
125+
limbsEnd := add(limbsEnd, 0x20)
126+
}
127+
j := add(j, 1)
128+
if eq(j, n) { break }
129+
}
130+
// Copy and compact the uint248 limbs.
131+
for { let i := limbs } iszero(eq(i, limbsEnd)) { i := add(i, 0x20) } {
132+
o := sub(o, 31)
133+
mstore(sub(o, 1), mload(i))
134+
}
135+
// Strip any leading zeros from the limbs.
136+
for {} lt(byte(0, mload(o)), lt(o, e)) {} { o := add(o, 1) }
137+
o := sub(o, z) // Move back for the leading zero bytes.
138+
calldatacopy(o, calldatasize(), z) // Fill the leading zero bytes.
139+
140+
let l := sub(e, o) // Compute the final length.
141+
result := sub(o, 0x20) // Move back one word for the length.
142+
mstore(result, l) // Store the length.
143+
mstore(add(add(result, 0x20), l), 0) // Zeroize the slot after the bytes.
144+
mstore(0x40, add(add(result, 0x40), l)) // Allocate memory.
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)