-
Notifications
You must be signed in to change notification settings - Fork 140
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
NEP-364: Efficient signature verification host functions #364
Conversation
bump 😇 |
Co-authored-by: Marcelo Fornet <mfornet94@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Largely just wording and structure nits from me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This NEP seems to imply that compiling these functions to wasm is actually feasible, though not desirable, for IBC use cases. I'd like to see a more detailed discussion on the alternatives and cost of not introducing this change. In general, we should be very carefully with adding more host functions.
neps/nep-0364.md
Outdated
Signature verification and hashing are ubiquitous operations in light clients, | ||
especially in PoS consensus mechanisms. Our rough numbers say that we’ll be | ||
verifying ~200 signatures every minute (Polkadot’s authority set is 300 signers). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does "we" here refer to? How is the number 200 calculated?
With `iterations = 130` **all these calls return ExecutionError**: `'Exceeded the maximum amount of gas allowed to burn per contract.'` | ||
With iterations = 50 these are the results: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is one iteration one signature verification?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. For each host function, please explain why exactly they are needed and their specification
I've added a reference in the motivation section. Would you like it to be more specific, being included in the comment of each host function?
neps/nep-0364.md
Outdated
Adding exisiting proved and vetted crypto crates into the runtime is a safe workaround. It will boost performance | ||
between 20-25x according to our benchmarks. This will both reduce operation costs significantly and will also | ||
enable the contract to verify all the signatures in one transaction, which will simplify the contract design. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't necessarily agree that it is a safe workaround. The cost of introducing a new host function is actually quite high:
- A bug in the host function code would cause the entire network to go down
- Once the host function is introduced, one cannot remove it as that could break contracts which already rely on the behavior of the host functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe "safe workaround" isn't the right way of putting it. However, these are highly adopted libraries with operations that are part of multiple services and blockchains. In that way, I was trying to convey the fact that these are no new functions that haven't been battle-tested. If you have any suggestions on how to communicate it differently I'd be happy to update it.
Thanks for the feedback. I've incorporated your comments. I've removed the hashing function from this NEP so that it only focuses on signature verification. We'll likely need another one for introducing new hashes that aren't supported. |
Co-authored-by: Simonas Kazlauskas <github@kazlauskas.me>
Co-authored-by: Simonas Kazlauskas <github@kazlauskas.me>
I've mentioned the size of the validator set, and how there are plans to increase it. With the current gas costs and performance of the wasm code, we can only verify 130 signatures on one transaction using the most expensive scheme. Based on this discussed proposal the validator set can be increased to 1000, which will require verifying at least 667 signatures. Based on the performance improvement of 20-25x, doing this natively gives us sufficient breathing room for these numbers and makes the infrastructure a bit more "future-proof". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm totally fine with adding Ed25519 (specifically, the variant that NEAR is already using), and we already have a host function for secp256k1. However, I am against the idea of adding Sr25519.
In general, I think that we should strive to reduce the number of host functions that we have, and only add new ones if there is a compelling need. Currently, almost all of the Math API host functions implement functionality that is available in the EVM. EVM is used in many blockchains, and is de facto a standard. Ed25519 is also widely used in NEAR and elsewhere. Sr25519, however, has very little usage. We don't want to add every cryptographic function existing somewhere, as this will increase our maintenance burden too much. In fact, I wasn't even able to find a specification for this function, and we certainly don't want to add a function without a specification.
I would recommend @blasrodri to explore the avenues for optimizing a pure-WASM implementation instead. It seems that Sr25519 implements some sort of aggregate signatures, which could be verified more quickly than verifying each signature individually. Also, it is probably possible to do batch verification, similar to Ed25519. I couldn't find it implemented though, which is yet another sign that it is not mature and/or popular enough.
Thanks for your input. I've cleaned up and left only |
@blasrodri The current update is good in terms of the functionality. There are some technical changes that are needed, though. In particular, the type of the arguments and the return value of host functions must be |
Implementing the following host functions according to the design proposed in [NEP-364](near/NEPs#364) ```rs pub fn ed25519_verify( &mut self, sig_len: u64, sig_ptr: u64, msg_len: u64, msg_ptr: u64, pub_key_len: u64, pub_key_ptr: u64, register_id: u64, ) -> Result<()>; ``` - [x] added unit tests - [x] added costs (this needs to be reviewed, since I'm not sure how to infer the right numbers)
One interesting unresolved question came up during implementation: what's the proper error handling for invalid keys and signatures? Not every byte sequence is a syntactically valid key/sig, so we have to do something about that. Our choices are:
There are two reasons why a key or a signature might be invalid. First, the byte length could be wrong. I think it's pretty clear that this should just be programming error -- the contract should pass Second, even if the length is correct, the key or signature might still be considered invalid due to this conditions:
In the case of invalid signature, I am somewhat confident that the right approach is just I don't know what's the right answer for invalid public key. On the one hand, public keys is usually something you get from the store, so you can consider them valid. On the other hand, if someone creates a public key, you'd have to have some mechanism to ensuring that it is in fact valid. I think In any case, error handling behavior should be documented as a part of the nep. |
@blasrodri Please, revert the changes to the README.md file. We will automate the process of listing NEPs, but for now, I will update it manually after we merge the NEP. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you to everyone who attended the first Protocol Working Group meeting today! The working group members reviewed the NEP and reached the following consensus:
Status: Approved with a request to address remaining comments (see below)
Short summary: All the decisions and major concerns were raised and addressed before the meeting, so the call was short and productive. It was clarified again that this NEP will allow IBC implementations to have a simpler design (as stated in the Summary and Motivation sections of this NEP). There were no blocking concerns to approving this NEP, so the Protocol Working Group approved it with a request to specify the corner cases in the NEP explicitly (see the list below) before merging it.
Meeting Recording: https://youtu.be/Uzvv6i1zKJY
@blasrodri Thank you for your contribution to the future of NEAR Protocol!
Remaining requests to @blasrodri:
- Address the comment from @matklad: NEP-364: Efficient signature verification host functions #364 (comment) (specify the invalid input behavior)
- Address the comment from @abacabadabacaba: NEP-364: Efficient signature verification host functions #364 (review) (specify the exact ED25519 mode that is expected to be used)
Next steps:
- @blasrodri should address the remaining comments
- @frol should merge NEP and list it in the README
- nearcore team should track the implementation and stabilization: Tracking issue for NEP-364: ed25519_verify host function nearcore#7567
near#364 (comment) and specifics of the crate being used
@matklad @abacabadabacaba I've addressed your comments on commit |
neps/nep-0364.md
Outdated
The current implementation is imported from the crate `ed25519-dalek`, version 1. It uses no feature flags | ||
except from the default one. This is the exact same setup used in the crate `near-crypto`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@abacabadabacaba Can you maybe help to identify how to state this requirement in a crate-agnostic way, so we don't get tied to the exact implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@frol It is difficult to describe the specific behavior concisely without referring to a specific implementation. This blog post describes the various ways in which the existing Ed25519 implementations differ in practice. The behavior that we are using, which is shared by Go crypto/ed25519
, Rust ed25519-dalek
(using verify
function with legacy_compatibility
feature turned off) and several others, makes the following decisions:
- The encoding of the values
$R$ and$s$ must be canonical, while the encoding of$A$ doesn't need to. - The verification equation is
$R=[s]B-[k]A$ . - No additional checks are performed. In particular, the points outside of the order-
$l$ subgroup are accepted, as are the points in the torsion subgroup.
While the above hopefully describes the behavior that we seek in an unambiguous manner, we may still want to point to a specific implementation to refer to in case of an ambiguity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@abacabadabacaba Thank you!
@blasrodri Please, include the proposed details into the NEP
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@frol updated it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@blasrodri Probably Security Implications is not the most appropriate place for this, this should probably rather go just above this thread. Also, whenever the ed25519-dalek
crate is mentioned, it should be specified that verify
function should be used. This is because there are other verification functions in this crate (verify_strict
, verify_batch
) that behave differently.
neps/nep-0364.md
Outdated
/// SIGNATURE_LENGTH, then the function returns HostError::Ed25519VerifyInvalidInput with the message "invalid signature length". | ||
/// | ||
/// A similar case occurs with the public key. Its size is known and its equal to PUBLIC_KEY_LENGTH (32 bytes). In case the | ||
/// input length provided for the publc key doesn't match PUBLIC_KEY_LENGTH, the function will return the following error: | ||
/// HostError::Ed25519VerifyInvalidInput with the message "invalid public key length". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@matklad Do you think it is a good direction to go with extending HostError
type or should we keep the scope of potential errors minimal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this doesn't matter much: from the protocol perspective, how exactly HostError
looks is unobservable. Implementation wise, adding a new variant there is OK.
But this actually points to the problem with wording of this NEP: what is written here is how the function would look inside logic.rs
, but that's an impl detail which is completely irrelevant from the specification point of view.
NEPs should say how the WASM import looks, not how it is implemented. For the time being, I'd rephrase this section as:
== Specification
The following host function is added
extern "C"{
/// Verify an ED25519 signature given a message and a public key.
/// # Returns
/// - 1 if the signature was properly verified
/// - 0 if the signature failed to be verified
///
/// # Cost
///
/// Each input can either be in memory or in a register. Set the length of the input to `u64::MAX`
/// to declare that the input is a register number and not a pointer.
/// Each input has a gas cost input_cost(num_bytes) that depends on whether it is from memory
/// or from a register. It is either read_memory_base + num_bytes * read_memory_byte in the
/// former case or read_register_base + num_bytes * read_register_byte in the latter. This function
/// is labeled as `input_cost` below.
///
/// `input_cost(num_bytes_signature + num_bytes_message + num_bytes_public_key) +
/// ed25519_verify_base + ed25519_verify_byte * num_bytes_message`
///
/// # Errors
///
/// If the signature size is not equal to 64 bytes, or public key length is not equal to 32 bytes, contract execution is terminated with an error.
fn ed25519_verify(
sig_len: u64,
sig_ptr: u64,
msg_len: u64,
msg_ptr: u64,
pub_key_len: u64,
pub_key_ptr: u64,
) -> u64;
}
That is:
- write the exten function from perspecive of the contract
- don't mention HostError, as that is unobservable to contracts (it is more or less like panic)
We could perhaps consider using proper wasm for the spec here
(module
(import "env" "ed25519_verify" (func (param 64) ... (result i64)))
)
but I feel that Rust-syntax to experss that is more accessible, and better matches what we've been doing historically
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated it
@blasrodri It seems there is one last remaining suggestion from @abacabadabacaba. @matklad Do you feel all your comments were addressed and we are good to merge this NEP? |
Yeah, this looks good to me. |
thanks for following up. I've updated @abacabadabacaba comments. Hopefully that's what he meant. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
neps/nep-0364.md
Outdated
The current implementation is imported from the crate `ed25519-dalek`, version 1. It uses no feature flags | ||
except from the default one. This is the exact same setup used in the crate `near-crypto`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sentence now seems redundant, given that the expected behavior is described below more thoroughly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Congrats everyone! I am starting the merge process now
@blasrodri Thanks for championing this effort!
Thanks to all the reviewers for your thorough work!
Implementing the following host functions according to the design proposed in [NEP-364](near/NEPs#364) ```rs pub fn ed25519_verify( &mut self, sig_len: u64, sig_ptr: u64, msg_len: u64, msg_ptr: u64, pub_key_len: u64, pub_key_ptr: u64, register_id: u64, ) -> Result<()>; ``` - [x] added unit tests - [x] added costs (this needs to be reviewed, since I'm not sure how to infer the right numbers)
Stabilization has just been merged: near/nearcore#8098 Expect this to land in testnet with the next testnet release. |
Efficient signature verification and hashing precompile functions