@@ -5,22 +5,35 @@ pragma solidity ^0.8.4;
55/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base58.sol)
66/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Base58.sol)
77library Base58 {
8- /// @dev Encodes `data` into a base58 string.
8+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9+ /* CUSTOM ERRORS */
10+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11+
12+ /// @dev An unrecognized character was encountered or the carry has overflowed.
13+ error Base58DecodingError ();
14+
15+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
16+ /* ENCODING / DECODING */
17+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
18+
19+ /// @dev Encodes `data` into a Base58 string.
920 function encode (bytes memory data ) internal pure returns (string memory result ) {
21+ uint256 l = data.length ;
22+ if (l == uint256 (0 )) return result;
1023 /// @solidity memory-safe-assembly
1124 assembly {
12- let l := mload (data) // `data.length`.
1325 let b := add (data, 0x20 ) // Start of `data` bytes.
1426 let z := 0 // Number of leading zero bytes in `data`.
27+ // Count leading zero bytes.
1528 for {} lt (byte (0 , mload (add (b, z))), lt (z, l)) {} { z := add (1 , z) }
1629
1730 // Start the output offset by an over-estimate of the length.
18- let o := add (add (mload (0x40 ), 0x21 ), add (z, div (mul (sub (l, z), 8351 ), 6115 )))
31+ let o := add (add (mload (0x40 ), 0x20 ), add (z, add ( 1 , div (mul (sub (l, z), 8351 ), 6115 ) )))
1932 let e := o
2033
2134 let limbs := o
2235 let limbsEnd := limbs
23-
36+ // Populate the uint248 limbs.
2437 for {
2538 let i := mod (l, 31 )
2639 if i {
@@ -32,44 +45,106 @@ library Base58 {
3245 limbsEnd := add (limbsEnd, 0x20 )
3346 }
3447
35- // Use the scratch space for the lookup. We'll restore 0x40 later.
48+ // Use the extended scratch space for the lookup. We'll restore 0x40 later.
3649 mstore (0x1f , "123456789ABCDEFGHJKLMNPQRSTUVWXY " )
3750 mstore (0x3f , "Zabcdefghijkmnopqrstuvwxyz " )
3851
39- if iszero (eq (limbs, limbsEnd)) {
40- for {} 1 {} {
41- let anyNonZero := 0
42- for { let i := limbs } 1 {} {
43- if mload (i) {
44- anyNonZero := 1
45- break
46- }
47- i := add (i, 0x20 )
48- if eq (i, limbsEnd) { break }
52+ for {} 1 {} {
53+ let anyNonZero := 0
54+ for { let i := limbs } 1 {} {
55+ if mload (i) {
56+ anyNonZero := 1
57+ break
4958 }
50- if iszero (anyNonZero) { break }
51-
52- let carry := 0
53- for { let i := limbs } 1 {} {
54- let acc := add (shl (248 , carry), mload (i))
55- mstore (i, div (acc, 58 ))
56- carry := mod (acc, 58 )
57- i := add (i, 0x20 )
58- if eq (i, limbsEnd) { break }
59- }
60- o := sub (o, 1 )
61- mstore8 (o, mload (carry))
59+ i := add (i, 0x20 )
60+ if eq (i, limbsEnd) { break }
6261 }
63- for { let i := 0 } iszero (eq (i, z)) { i := add (i, 1 ) } {
64- o := sub (o, 1 )
65- mstore8 (o, 49 ) // '1' in ASCII.
62+ if iszero (anyNonZero) { break }
63+
64+ let carry := 0
65+ for { let i := limbs } 1 {} {
66+ let acc := add (shl (248 , carry), mload (i))
67+ mstore (i, div (acc, 58 ))
68+ carry := mod (acc, 58 )
69+ i := add (i, 0x20 )
70+ if eq (i, limbsEnd) { break }
6671 }
72+ o := sub (o, 1 )
73+ mstore8 (o, mload (carry))
74+ }
75+ // We probably can optimize this more by writing 32 bytes at a time.
76+ for { let i := 0 } iszero (eq (i, z)) { i := add (i, 1 ) } {
77+ o := sub (o, 1 )
78+ mstore8 (o, 49 ) // '1' in ASCII.
6779 }
80+
6881 let n := sub (e, o) // Compute the final length.
6982 result := sub (o, 0x20 ) // Move back one word for the length.
7083 mstore (result, n) // Store the length.
7184 mstore (add (add (result, 0x20 ), n), 0 ) // Zeroize the slot after the bytes.
7285 mstore (0x40 , add (add (result, 0x40 ), n)) // Allocate memory.
7386 }
7487 }
88+
89+ /// @dev Decodes `encoded`, a Base58 string, into the original bytes.
90+ function decode (string memory encoded ) internal pure returns (bytes memory result ) {
91+ uint256 n = bytes (encoded).length ;
92+ if (n == uint256 (0 )) return result;
93+ /// @solidity memory-safe-assembly
94+ assembly {
95+ let s := add (encoded, 0x20 )
96+ let z := 0 // Number of leading '1' in `data`.
97+ // Count leading '1'.
98+ for {} and (eq (49 , byte (0 , mload (add (s, z)))), lt (z, n)) {} { z := add (1 , z) }
99+
100+ // Start the output offset by an over-estimate of the length.
101+ let o := add (add (mload (0x40 ), 0x20 ), add (z, add (1 , div (mul (sub (n, z), 7736 ), 10000 ))))
102+ let e := o
103+ let limbs := o
104+ let limbsEnd := limbs
105+ // Use the extended scratch space for the lookup. We'll restore 0x40 later.
106+ mstore (0x2a , 0x30313233343536373839 )
107+ mstore (0x20 , 0x1718191a1b1c1d1e1f20ffffffffffff2122232425262728292a2bff2c2d2e2f )
108+ mstore (0x00 , 0x000102030405060708ffffffffffffff090a0b0c0d0e0f10ff1112131415ff16 )
109+
110+ for { let j := 0 } 1 {} {
111+ let c := sub (byte (0 , mload (add (s, j))), 49 )
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, shr (8 , shl (8 , acc)))
120+ carry := shr (248 , acc)
121+ }
122+ if carry {
123+ if iszero (lt (carry, 58 )) {
124+ mstore (0x00 , 0xe8fad793 ) // `Base58DecodingError()`.
125+ revert (0x1c , 0x04 )
126+ }
127+ mstore (limbsEnd, carry)
128+ limbsEnd := add (limbsEnd, 0x20 )
129+ }
130+ j := add (j, 1 )
131+ if eq (j, n) { break }
132+ }
133+ // Copy and compact the uint248 limbs.
134+ for { let i := limbs } iszero (eq (i, limbsEnd)) { i := add (i, 0x20 ) } {
135+ o := sub (o, 31 )
136+ mstore (sub (o, 1 ), mload (i))
137+ }
138+ // Strip any leading zeros from the limbs.
139+ for {} lt (byte (0 , mload (o)), lt (o, e)) {} { o := add (o, 1 ) }
140+ o := sub (o, z) // Move back for the leading zero bytes.
141+ calldatacopy (o, calldatasize (), z) // Fill the leading zero bytes.
142+
143+ let l := sub (e, o) // Compute the final length.
144+ result := sub (o, 0x20 ) // Move back one word for the length.
145+ mstore (result, l) // Store the length.
146+ mstore (add (add (result, 0x20 ), l), 0 ) // Zeroize the slot after the bytes.
147+ mstore (0x40 , add (add (result, 0x40 ), l)) // Allocate memory.
148+ }
149+ }
75150}
0 commit comments