Skip to content
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

EC Recover is slow #534

Closed
recmo opened this issue Aug 13, 2021 · 1 comment · Fixed by #537
Closed

EC Recover is slow #534

recmo opened this issue Aug 13, 2021 · 1 comment · Fixed by #537

Comments

@recmo
Copy link

recmo commented Aug 13, 2021

The recover function is unnecessarily slow. On my machine it takes about 7 ms instead of 60 μs.

The cause is this line:

        let public_key = Secp256k1::verification_only()
            .recover(&message, &signature)

This calls Secp256k1::verification_only() for each recover, creating a new context and recomputing all the lookup tables each time. This is documented in secp256k1:

[...] the library uses context objects that contain precomputation tables which are created on object construction. Since this is a slow operation (10+ milliseconds, vs ~50 microseconds for typical crypto operations, on a 2.70 Ghz i7-6820HQ) the tables are optional, giving a performance boost for users who only care about signing, only care about verification, or only care about parsing [...]

So we should statically allocate the context and initialize it once:

use once_cell::sync::Lazy;
use secp256k1::{
    recovery::{RecoverableSignature, RecoveryId},
    Error, Message, Secp256k1, VerifyOnly,
};
use sha3::{Digest, Keccak256};
use web3::types::Address;

static CONTEXT: Lazy<Secp256k1<VerifyOnly>> = Lazy::new(Secp256k1::verification_only);

pub fn recover(message: &[u8], signature: &[u8], recovery_id: i32) -> Result<Address, Error> {
    // Recover public key
    let message = Message::from_slice(message)?;
    let recovery_id = RecoveryId::from_i32(recovery_id)?;
    let signature = RecoverableSignature::from_compact(signature, recovery_id)?;
    let public_key = CONTEXT.recover(&message, &signature)?;

    // Hash public key into address
    let public_key = public_key.serialize_uncompressed();
    debug_assert_eq!(public_key[0], 0x04);
    let hash = {
        let mut hasher = Keccak256::new();
        hasher.update(&public_key[1..]);
        hasher.finalize()
    };
    let address = Address::from_slice(&hash[12..]);
    Ok(address)
}

Benchmarking the above shows a 100x improvement:

web3_recover            time:   [7.0468 ms 7.1243 ms 7.2109 ms]                         
my_recover              time:   [58.773 us 63.094 us 67.377 us]                          
                        change: [-99.225% -99.186% -99.147%] (p = 0.00 < 0.05)
@recmo
Copy link
Author

recmo commented Aug 16, 2021

(Note that the same argument likely applies for the two usages of Secp256k1::signing_only())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant