-
Notifications
You must be signed in to change notification settings - Fork 86
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
Optimize Base-64 Encoding Algorithm #289
Comments
akshay-ap
added a commit
that referenced
this issue
Mar 11, 2024
akshay-ap
added a commit
that referenced
this issue
Mar 12, 2024
akshay-ap
added a commit
that referenced
this issue
Mar 13, 2024
akshay-ap
added a commit
that referenced
this issue
Mar 13, 2024
nlordell
added a commit
that referenced
this issue
Mar 18, 2024
This PR refactors the WebAuthn implementation into a library instead of split into multiple contracts. This removes the amount of `CALL`s required for verifying a signature. I did some analysis on the deployment vs. signature verification costs comparing this code, `main` and this change incorporating optimizations from #289 and found this: | setup | deployment | verification (dummy) | break even | | ------------------------------------ | ---------- | -------------------- | ---------- | | main (viaIR = false) | 373543 | 17789 | 0 | | main (viaIR = true) | 393494 | 16443 | 15 | | this branch (viaIR = false) | 612123 | 13835 | 61 | | this branch (viaIR = true) | 594058 | 12551 | 43 | | base64 optimizations (viaIR = false) | 442545 | 12965 | 15 | | base64 optimizations (viaIR = true) | 414941 | 11102 | 7 | Meaning that after 7 signatures, even with the slightly larger deployment costs, we would break even by avoiding the extra `CALL` (noting that ABI-encoding the call to the `WebAuthnVerifier` is not particularly efficient in the first place).
mmv08
added a commit
that referenced
this issue
Mar 27, 2024
This PR implements #289 The algorithm is implemented as a library instead of the suggested in-place adjustment from the original issue. Akshay's original implementation was doing it in-place (which can still be found in the commit history). While it was efficient code size-wise, but gas-wise, it consumed more gas than FCL's implementation, probably due to some inefficient stack manipulations. I tested it with Solady's [library](https://github.com/vectorized/solady/blob/main/src/utils/Base64.sol) then, and it turned out to be much more efficient even though it wasn't doing the encoding in place. The implementation I'm proposing is based on Solady's efficient algorithm. It seems to me that it's not adjustable to in-place encoding due to the fact the algorithm takes advantage of optimizing stack operations by writing X bytes to the scratch space and then writing them to the string at position Y+(I*N) (I=Iteration, Y=start, N=bytes written in an iteration) as a 32-byte word. Because it writes them as a 32-byte word, it’s not suitable for modifying a string in place because it will overwrite the existing string bytes with empty characters. (I'm happy if I'm wrong though) **Changes in PR**: - Add `codesize` task - Add a `Base64Url.sol` library with a Base64Url encoding algorithm that is optimized for encoding `bytes32` data. - Add tests for the base64 library - Edit the typescript configuration to include the hardhat.config.ts so all the type extensions are picked up. Necessary to get the project compiled. ### Code size change This PR: `WebAuthnSigner 2693 bytes (limit is 24576)` Default main branch: `WebAuthnSigner 2817 bytes (limit is 24576)` ### Gas Usage - Needs additional test for benchmarking **Solady**: ``` Gas Benchmarking WebAuthnSigner ⛽ deployment: 550077 ✔ Benchmark signer deployment cost ⛽ verification (FreshCryptoLib): 216563 ✔ Benchmark signer verification cost with FreshCryptoLib verifier (70ms) ⛽ verification (daimo-eth): 347246 ✔ Benchmark signer verification cost with daimo-eth verifier (116ms) ⛽ verification (Dummy): 12713 ✔ Benchmark signer verification cost with Dummy verifier ``` **FCL**: ``` Gas Benchmarking WebAuthnSigner ⛽ deployment: 612929 ✔ Benchmark signer deployment cost ⛽ verification (FreshCryptoLib): 217272 ✔ Benchmark signer verification cost with FreshCryptoLib verifier (70ms) ⛽ verification (daimo-eth): 347636 ✔ Benchmark signer verification cost with daimo-eth verifier (108ms) ⛽ verification (Dummy): 13844 ✔ Benchmark signer verification cost with Dummy verifier ``` **This PR**: ``` Gas Benchmarking WebAuthnSigner ⛽ deployment: 533456 ✔ Benchmark signer deployment cost ⛽ verification (FreshCryptoLib): 210645 ✔ Benchmark signer verification cost with FreshCryptoLib verifier (64ms) ⛽ verification (daimo-eth): 337941 ✔ Benchmark signer verification cost with daimo-eth verifier (108ms) ⛽ verification (Dummy): 12281 ✔ Benchmark signer verification cost with Dummy verifier ``` **This PR (viaIR)**: ``` Gas Benchmarking WebAuthnSigner ⛽ deployment: 544844 ✔ Benchmark signer deployment cost ⛽ verification (FreshCryptoLib): 294440 ✔ Benchmark signer verification cost with FreshCryptoLib verifier (108ms) ⛽ verification (daimo-eth): 335453 ✔ Benchmark signer verification cost with daimo-eth verifier (117ms) ⛽ verification (Dummy): 10352 ✔ Benchmark signer verification cost with Dummy verifier ``` --------- Co-authored-by: Mikhail Mikheev <[email protected]> Co-authored-by: Nicholas Rodrigues Lordello <[email protected]>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The WebAuthn signing process requires encoding the
challenge
to base64. The "challenge" is a relying party chosen value for replay protection. In the passkey design we use, this is the hash of the data we are verifying for, and assume replay protection already applies to it (for example, thenonce
is included in the Safe transaction hash which gets verified and ensures replay protection).The issue is that the challenge is computed as a
bytes32
on-chain, but needs to be encoded in base64 for computing theclientDataJSON
which is ultimately used in computing the "signing message" (i.e. what is actually signed by the private key).Currently, the base64 implementation that is used in FCL and in the experimental contracts first:
bytes32
to abytes memory
array (thus incurring unnecessary allocations)bytes memory
arrays (thus adding code complexity and marginal runtime costs)string memory
which needs to be concatenated to an existingbytes memory
(which incurs a copy loop - at least untilmcopy
is available - which has a non-negligible runtime and codesize cost).The proposal is to instead compute the base64 incoding in-place with an algorithm specialized for 32 bytes of data, thus avoiding memory allocations and copies, and additional complexity for supporting arbitrary sized arrays.
The expected outcome is a specialized base64 encoding implementation for use in the WebAuthn signature validator contract.
The text was updated successfully, but these errors were encountered: