Skip to content

Commit 8fb7a55

Browse files
authored
Optimize crypto module for bytecode size and gas usage (#7151)
## Description This PR optimizes the `std::crypto` module for bytecode size and gas cost by using optimization techniques similar to those used in #7087, #7092, and #7096. The optimized public functions are listed in the below code snippet. **The bytecode size of that code got reduced from 46104 bytes to 44288 bytes.** We actually expect a bigger gain here. At one point in the optimization, the bytecode size went down to 33896 bytes. Bouncing back to ~44600 is related to the issue described in #7150. Once that issue is solved, we expect even smaller bytcode sizes. ```sway script; use std::crypto::ed25519::*; use std::crypto::point2d::*; use std::crypto::public_key::*; use std::crypto::scalar::*; use std::crypto::secp256k1::*; use std::crypto::secp256r1::*; use std::crypto::signature::*; use std::vm::evm::evm_address::EvmAddress; fn main() { let ed25519 = std::crypto::ed25519::Ed25519::new(); let _ = ed25519 == ed25519; let point2d = std::crypto::point2d::Point2D::new(); let _ = point2d == point2d; let public_key = std::crypto::public_key::PublicKey::new(); let _ = public_key == public_key; let _ = public_key.is_zero(); let scalar = std::crypto::scalar::Scalar::new(); let _ = scalar == scalar; let message = std::crypto::message::Message::new(); let secp256k1 = std::crypto::secp256k1::Secp256k1::new(); let _ = secp256k1 == secp256k1; let _ = secp256k1.address(message); let _ = secp256k1.evm_address(message); let _ = secp256k1.verify(public_key, message); let _ = secp256k1.verify_address(Address::zero(), message); let _ = secp256k1.verify_evm_address(EvmAddress::zero(), message); let secp256r1 = std::crypto::secp256r1::Secp256r1::new(); let _ = secp256r1 == secp256r1; let _ = secp256r1.address(message); let _ = secp256r1.evm_address(message); let _ = secp256r1.verify(public_key, message); let _ = secp256r1.verify_address(Address::zero(), message); let _ = secp256r1.verify_evm_address(EvmAddress::zero(), message); } ``` ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [ ] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [ ] If my change requires substantial documentation changes, I have [requested support from the DevRel team](https://github.com/FuelLabs/devrel-requests/issues/new/choose) - [ ] I have added tests that prove my fix is effective or that my feature works. - [ ] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers.
1 parent f9fcae9 commit 8fb7a55

File tree

8 files changed

+161
-199
lines changed

8 files changed

+161
-199
lines changed

sway-lib-std/src/bytes.sw

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,41 @@ impl Bytes {
956956
index: 0,
957957
}
958958
}
959+
960+
/// Returns true if all the bytes within the `Bytes` are zero.
961+
///
962+
/// # Additional Information
963+
///
964+
/// If `Bytes` is empty, this function will return `true`.
965+
///
966+
/// # Examples
967+
///
968+
/// ```sway
969+
/// fn foo() {
970+
/// let bytes = Bytes::new();
971+
/// bytes.resize(10, 0u8);
972+
/// assert(bytes.are_all_zero() == true);
973+
///
974+
/// bytes.resize(20, 42u8);
975+
/// assert(bytes.are_all_zero() == false);
976+
///
977+
/// bytes.resize(0, 42u8);
978+
/// assert(bytes.are_all_zero() == true);
979+
/// }
980+
/// ```
981+
pub fn are_all_zero(self) -> bool {
982+
let mut iter = 0;
983+
while iter < self.len {
984+
let item_ptr = self.buf.ptr().add_uint_offset(iter);
985+
let item = item_ptr.read_byte();
986+
if item != 0 {
987+
return false;
988+
}
989+
iter += 1;
990+
}
991+
992+
true
993+
}
959994
}
960995

961996
impl PartialEq for Bytes {
@@ -981,6 +1016,8 @@ impl AsRawSlice for Bytes {
9811016
}
9821017
}
9831018

1019+
// TODO: Once const generics are available implement `From<[u8; N]>`.
1020+
9841021
/// Methods for converting between the `Bytes` and the `b256` types.
9851022
impl From<b256> for Bytes {
9861023
fn from(b: b256) -> Self {

sway-lib-std/src/crypto/ed25519.sw

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,10 @@ impl Into<Bytes> for Ed25519 {
194194

195195
impl PartialEq for Ed25519 {
196196
fn eq(self, other: Self) -> bool {
197-
let mut iter = 0;
198-
while iter < 64 {
199-
if self.bits[iter] != other.bits[iter] {
200-
return false;
201-
}
202-
iter += 1;
197+
asm(result, r2: self.bits, r3: other.bits, r4: 64) {
198+
meq result r2 r3 r4;
199+
result: bool
203200
}
204-
205-
true
206201
}
207202
}
208203
impl Eq for Ed25519 {}

sway-lib-std/src/crypto/message.sw

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,7 @@ impl TryInto<b256> for Message {
8888

8989
impl PartialEq for Message {
9090
fn eq(self, other: Self) -> bool {
91-
if self.bytes.len() != other.bytes.len() {
92-
return false;
93-
}
94-
95-
let mut iter = 0;
96-
while iter < self.bytes.len() {
97-
if self.bytes.get(iter).unwrap() != other.bytes.get(iter).unwrap()
98-
{
99-
return false;
100-
}
101-
iter += 1;
102-
}
103-
104-
true
91+
self.bytes == other.bytes
10592
}
10693
}
10794
impl Eq for Message {}

sway-lib-std/src/crypto/point2d.sw

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,12 @@ pub struct Point2D {
2424
// All points must be of length 32
2525
impl PartialEq for Point2D {
2626
fn eq(self, other: Self) -> bool {
27-
if self.x.len() != 32
28-
|| self.y.len() != 32
29-
|| other.x.len() != 32
30-
|| other.y.len() != 32
31-
{
32-
return false;
33-
}
34-
35-
let mut iter = 0;
36-
while iter < 32 {
37-
if self.x.get(iter).unwrap() != other.x.get(iter).unwrap() {
38-
return false;
39-
} else if self.y.get(iter).unwrap() != other.y.get(iter).unwrap() {
40-
return false;
41-
}
42-
43-
iter += 1;
44-
}
45-
true
27+
self.x.len() == 32 && self.y.len() == 32 && self.x == other.x && self.y == other.y
4628
}
4729
}
48-
impl Eq for Point2D {}
30+
// Note that `Point2D` implements `PartialEq` but not `Eq`,
31+
// because an uninitialized `Point2D`, created by `Point2D::new`
32+
// is not equal to any other point, including itself.
4933

5034
impl Point2D {
5135
/// Returns a new, uninitialized Point2D.
@@ -111,7 +95,13 @@ impl Point2D {
11195
/// }
11296
/// ```
11397
pub fn is_zero(self) -> bool {
114-
self == Self::zero()
98+
// Note that we could simply return `self == Self::zero()` here,
99+
// but this would cause creating a new `Point2D` zero instance
100+
// every time we call this function. `Self::zero()` is expensive
101+
// both in terms of gas and allocated memory.
102+
// In cases like calling this function in a loop, the performance
103+
// impact would be significant.
104+
self.x.len() == 32 && self.y.len() == 32 && self.x.are_all_zero() && self.y.are_all_zero()
115105
}
116106

117107
/// Returns the minimum point.
@@ -217,6 +207,9 @@ impl From<(u256, u256)> for Point2D {
217207

218208
impl From<[u8; 64]> for Point2D {
219209
fn from(bytes: [u8; 64]) -> Self {
210+
// TODO: Once const generics are available directly call `From<[u8; N]>` on `Bytes`
211+
// instead of having a loop.
212+
// Use allocation free `asm` cast to convert the single array to two arrays.
220213
let mut x = Bytes::with_capacity(32);
221214
let mut y = Bytes::with_capacity(32);
222215

sway-lib-std/src/crypto/public_key.sw

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,33 @@ impl PublicKey {
5757
///
5858
/// fn foo() -> {
5959
/// let new_key = PublicKey::new();
60-
/// assert(new_key.bytes().len() == 64);
60+
/// assert(new_key.bytes().len() == 0);
6161
/// }
6262
/// ```
6363
pub fn bytes(self) -> Bytes {
6464
self.bytes
6565
}
6666

67+
/// Returns the length of the public key in bytes.
68+
///
69+
/// # Returns
70+
///
71+
/// * [u64] - The length of the public key.
72+
///
73+
/// # Examples
74+
///
75+
/// ```sway
76+
/// use std::crypto::PublicKey;
77+
///
78+
/// fn foo() -> {
79+
/// let new_key = PublicKey::new();
80+
/// assert(new_key.len() == 0);
81+
/// }
82+
/// ```
83+
pub fn len(self) -> u64 {
84+
self.bytes.len()
85+
}
86+
6787
/// Returns whether the public key is the zero public key.
6888
///
6989
/// # Returns
@@ -81,15 +101,7 @@ impl PublicKey {
81101
/// }
82102
/// ```
83103
pub fn is_zero(self) -> bool {
84-
let mut iter = 0;
85-
while iter < self.bytes.len() {
86-
if self.bytes.get(iter).unwrap() != 0 {
87-
return false;
88-
}
89-
iter += 1;
90-
}
91-
92-
true
104+
self.bytes.are_all_zero()
93105
}
94106
}
95107

@@ -177,20 +189,7 @@ impl TryInto<b256> for PublicKey {
177189

178190
impl PartialEq for PublicKey {
179191
fn eq(self, other: Self) -> bool {
180-
if self.bytes.len() != other.bytes.len() {
181-
return false;
182-
}
183-
184-
let mut iter = 0;
185-
while iter < self.bytes.len() {
186-
if self.bytes.get(iter).unwrap() != other.bytes.get(iter).unwrap()
187-
{
188-
return false;
189-
}
190-
iter += 1;
191-
}
192-
193-
true
192+
self.bytes == other.bytes
194193
}
195194
}
196195
impl Eq for PublicKey {}

sway-lib-std/src/crypto/scalar.sw

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,12 @@ pub struct Scalar {
1717
// All scalars must be of length 32
1818
impl PartialEq for Scalar {
1919
fn eq(self, other: Self) -> bool {
20-
if self.bytes.len() != 32 || other.bytes.len() != 32 {
21-
return false;
22-
}
23-
24-
let mut iter = 0;
25-
while iter < 32 {
26-
if self.bytes.get(iter).unwrap() != other.bytes.get(iter).unwrap()
27-
{
28-
return false;
29-
}
30-
31-
iter += 1;
32-
}
33-
true
20+
self.bytes.len() == 32 && self.bytes == other.bytes
3421
}
3522
}
36-
impl Eq for Scalar {}
23+
// Note that `Scalar` implements `PartialEq` but not `Eq`,
24+
// because an uninitialized `Scalar`, created by `Scalar::new`
25+
// is not equal to any other scalar, including itself.
3726

3827
impl Scalar {
3928
/// Returns a new, uninitialized Scalar.
@@ -118,7 +107,13 @@ impl Scalar {
118107
/// }
119108
/// ```
120109
pub fn is_zero(self) -> bool {
121-
self == Self::zero()
110+
// Note that we could simply return `self == Self::zero()` here,
111+
// but this would cause creating a new `Scalar` zero instance
112+
// every time we call this function. `Self::zero()` is expensive
113+
// both in terms of gas and allocated memory.
114+
// In cases like calling this function in a loop, the performance
115+
// impact would be significant.
116+
self.bytes.len() == 32 && self.bytes.are_all_zero()
122117
}
123118

124119
/// Returns the underlying bytes of the scalar.
@@ -160,6 +155,8 @@ impl From<b256> for Scalar {
160155

161156
impl From<[u8; 32]> for Scalar {
162157
fn from(bytes_array: [u8; 32]) -> Self {
158+
// TODO: Once const generics are available directly call `From<[u8; N]>` on `Bytes`
159+
// instead of having a loop.
163160
let mut bytes = Bytes::with_capacity(32);
164161

165162
let mut iter = 0;

0 commit comments

Comments
 (0)