Skip to content

Commit d4a573b

Browse files
committed
rewrite _encode in assembly
1 parent d09ebfa commit d4a573b

File tree

1 file changed

+100
-22
lines changed

1 file changed

+100
-22
lines changed

contracts/utils/Base58.sol

Lines changed: 100 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,34 +37,112 @@ library Base58 {
3737
return _decode(bytes(data));
3838
}
3939

40-
function _encode(bytes memory data) private pure returns (bytes memory) {
41-
unchecked {
42-
uint256 dataCLZ = data.countLeading(0x00);
43-
uint256 length = dataCLZ + ((data.length - dataCLZ) * 8351) / 6115 + 1;
44-
bytes memory slot = new bytes(length);
45-
46-
uint256 end = length;
47-
for (uint256 i = 0; i < data.length; i++) {
48-
uint256 ptr = length;
49-
for (uint256 carry = _mload8i(data, i); ptr > end || carry != 0; --ptr) {
50-
carry += 256 * _mload8i(slot, ptr - 1);
51-
_mstore8i(slot, ptr - 1, uint8(carry % 58));
52-
carry /= 58;
40+
function _encode(bytes memory data) private pure returns (bytes memory encoded) {
41+
// For reference, solidity implementation
42+
// unchecked {
43+
// uint256 dataCLZ = data.countLeading(0x00);
44+
// uint256 length = dataCLZ + ((data.length - dataCLZ) * 8351) / 6115 + 1;
45+
// encoded = new bytes(length);
46+
// uint256 end = length;
47+
// for (uint256 i = 0; i < data.length; ++i) {
48+
// uint256 ptr = length;
49+
// for (uint256 carry = uint8(data[i]); ptr > end || carry != 0; --ptr) {
50+
// carry += 256 * uint8(encoded[ptr - 1]);
51+
// encoded[ptr - 1] = bytes1(uint8(carry % 58));
52+
// carry /= 58;
53+
// }
54+
// end = ptr;
55+
// }
56+
// uint256 encodedCLZ = encoded.countLeading(0x00);
57+
// length -= encodedCLZ - dataCLZ;
58+
// encoded.splice(encodedCLZ - dataCLZ);
59+
// for (uint256 i = 0; i < length; ++i) {
60+
// encoded[i] = _TABLE[uint8(encoded[i])];
61+
// }
62+
// }
63+
64+
// Assembly is ~50% cheaper for buffers of size 32.
65+
assembly ("memory-safe") {
66+
function clzBytes(ptr, length) -> i {
67+
let chunk
68+
for {
69+
i := 0
70+
} lt(i, length) {
71+
i := add(i, 1)
72+
} {
73+
// Every 32 bytes, load a new chunk
74+
if iszero(mod(i, 0x20)) {
75+
chunk := mload(add(ptr, i))
76+
}
77+
// If the first byte of the chunk is not zero, break
78+
if shr(248, chunk) {
79+
break
80+
}
81+
// Shift chunk
82+
chunk := shl(8, chunk)
83+
}
84+
}
85+
86+
encoded := mload(0x40)
87+
let dataLength := mload(data)
88+
89+
// Count number of zero bytes at the beginning of `data`
90+
let dataCLZ := clzBytes(add(data, 0x20), dataLength)
91+
92+
// Initial encoding
93+
let slotLength := add(add(dataCLZ, div(mul(sub(dataLength, dataCLZ), 8351), 6115)), 1)
94+
95+
// Zero the encoded buffer
96+
for {
97+
let i := 0
98+
} lt(i, slotLength) {
99+
i := add(i, 0x20)
100+
} {
101+
mstore(add(add(encoded, 0x20), i), 0)
102+
}
103+
104+
// Build the "slots"
105+
for {
106+
let i := 0
107+
let end := slotLength
108+
} lt(i, dataLength) {
109+
i := add(i, 1)
110+
} {
111+
let ptr := slotLength
112+
for {
113+
let carry := shr(248, mload(add(add(data, 0x20), i)))
114+
} or(carry, lt(end, ptr)) {
115+
ptr := sub(ptr, 1)
116+
carry := div(carry, 58)
117+
} {
118+
carry := add(carry, mul(256, shr(248, mload(add(add(encoded, 0x1f), ptr)))))
119+
mstore8(add(add(encoded, 0x1f), ptr), mod(carry, 58))
53120
}
54-
end = ptr;
121+
end := ptr
55122
}
56123

57-
uint256 slotCLZ = slot.countLeading(0x00);
58-
length -= slotCLZ - dataCLZ;
59-
slot.splice(slotCLZ - dataCLZ);
124+
// Count number of zero bytes at the beginning of slots
125+
let slotCLZ := clzBytes(add(encoded, 0x20), slotLength)
126+
127+
// Update length
128+
let offset := sub(slotCLZ, dataCLZ)
129+
let encodedLength := sub(slotLength, offset)
130+
131+
// Store the encoding table. This overlaps with the FMP that we are going to reset later anyway.
132+
mstore(0x1f, "123456789ABCDEFGHJKLMNPQRSTUVWXY")
133+
mstore(0x3f, "Zabcdefghijkmnopqrstuvwxyz")
60134

61-
bytes memory cache = _TABLE;
62-
for (uint256 i = 0; i < length; ++i) {
63-
// equivalent to `slot[i] = TABLE[slot[i]];`
64-
_mstore8(slot, i, _mload8(cache, _mload8i(slot, i)));
135+
for {
136+
let i := 0
137+
} lt(i, encodedLength) {
138+
i := add(i, 1)
139+
} {
140+
mstore8(add(add(encoded, 0x20), i), mload(shr(248, mload(add(add(encoded, 0x20), add(offset, i))))))
65141
}
66142

67-
return slot;
143+
// Store length and allocate memory
144+
mstore(encoded, encodedLength)
145+
mstore(0x40, add(encoded, add(0x20, encodedLength)))
68146
}
69147
}
70148

0 commit comments

Comments
 (0)