New Ethereum talks, every Monday. The week's conference uploads by event, in your inbox.

Loading player…

Approaching Secure Signature Validation in Smart Contracts

BaseTue, Dec 9, 2025, 12:00 AM

These workshops are run by our internal security team, designed to teach you best practices coming from real-world Coinbase experience. #OnchainAppInsights #web3 #accountabstraction #erc4337 #onchain #onchainApps #gasless #base #basechain #coinbasewallet #crypto #web3 #apps #blockchainrewards

Transcript

Hello everyone. My name is Dan Johnson and I'm on the protocol security team here at Coinbase. Uh joined here today with Opel Graham, a blockchain security engineer on our team. Protocol security is responsible for the security of all onchain protocols here at Coinbase. Today we're serving up another episode of our ongoing Coinbase security series where every two weeks a security engineer will present on a topic of blockchain security or developing safely in the space.

Uh, as is the case with all of these talks, please note the disclaimer that's at the bottom of the screen. And, uh, with that, I will let Opel take it away. Thanks, Dan. Um, all right. So, we're going to be talking about approaching secure signature validation in smart contracts today.

Uh just to give an overview of what we'll be talking about, we're first going to go through what signature validation in smart contracts looks like. And then we'll get into the meat of what are some of the things that we need to consider uh as vulnerabilities and security risks when we're looking at signature validation and smart contracts. And we'll wrap it up with key takeaways at the end. So let's first talk about what signature validation in smart contracts looks like. Uh and so for just a note for a context, we're going to be talking about EVM based signature validation.

Uh so EVM based chains specifically just in case it makes a difference. Um and so there's a very so we have this method the uh signature validation method um which is a method for demonstrating an account signed a message for use in a smart contract. Um and specifically that account doesn't have to directly interact with the smart contract itself. So for instance uh I have to decide this diagram of Alice and Bob and there's a message that we have and then a smart contract and Alice is going to sign that message to give approval to that message and say I sign off on this message and then she's going to give that message to Bob and Bob is going to take that message and use it along with the signature in the smart contract and the smart contract will go through its programming to uh recover Alice's address and that will give evidence that Alice approved of that message in the first place. Um where might we see examples of this like signature validation use in smart contracts?

So a big uh usual example that people give is the idea of onboarding first-time users. Uh people that particularly might not have the native token of the blockchain at that point. Um they can sign uh a message that says that they approve of what's happening and then that um approval can then be executed by a second a third party um to make it happen. Um, and there's also other situations in which you might see signature validation used. Um, one of them being multi-IG wallet actions.

U, another being if you want to vote um, within a you want to make your you want to sign that you voted a certain way. Um, maybe you're a validator on a bridge. Maybe you're giving approval um to a certain action across multiple chains. or maybe you want to okay some token movement on your behalf. Um and in general just general centralized actions can be done through signatures as well.

Um so yeah those are some examples in which we might see those types of things. Um what are we using when we're doing the signature validation? So when we're signing something, we're signing something as an externally owned account. There's two types of accounts in um in on a blockchain. There's a smart contract and then there are externally owned accounts.

And uh smart contracts are dictated by their logic. But externally owned accounts are when we the way we decide whether or not an externally owned account gives an okay to an action is through this signature use. Um and so the way that this occurs is through the fact that externally owned accounts have a public and private key pair. Um and the public key is used for public reference while the private key is used for proof of transaction approval. So for instance, we might have a public address that looks like this.

Um that's attached to something we wouldn't want to give away to people, a private key that might look like this. Um, and this is the part that we keep secret to ourselves because uh in signature recovery, the private key is what's used to create a signature. So if somebody has your private key, then they're able to uh sign something on your behalf. Um so as far as what processes being used to create these signatures um we use on EVMbased chains uh ECDSA which is elliptic curve digital signature algorithm and um the signing process uh can be implemented and used through tools like ethersjs, web3js or foundry Uh, and just again pointing out that Coinbase doesn't endorse these like tools or products in particular, but they're just examples of ways in which you might sign something. Um, on that side, this is like this is the off-chain side.

They're implementing a cryptographic process that looks at a very high level like taking a message um and hashing it to be signed and we'll call this message H. um then go through the process of generating a random number x value that we'll call R on an elliptic curve, hence the ECDSA name. Um and then from there uh we take that message hash H, the random X value R and your private key and that gets used in a formula to create a signature proof S. Um, and it's this S with R along with another value V that we'll talk about later that composes your signature. And an important thing to note about this VRS combination is that it's knowing that information doesn't reveal what your private key is.

um a big basis of the algorithm this cryptographic algorithm in the first place is um that it's difficult to go back backwards in reverse to recover this information. So that's the offchain side um but the onchain side uh might look let's talk about what that might look like. So um in terms of like implementation of signature validation on the onchain side, we are now going to take a signature and validate it on a smart contract. And um so the way that that might look is that a third party would hash the same message and a lot of that uh logic might be done for instance in the smart contract itself and then um every smart contract has available to it this pre-ompile called EC recover which takes as input that hashed message along with the provided signature and throws it through the function and out comes an address that we call like in this in this particular snapshot here, SER. Um, so it's it's always outputting an address.

Uh, importantly, it's either the address that signed that message or if the address didn't sign that message, then it returns address zero, meaning that the message signature is invalid for that message. Um just again to name a couple tools that we have available to us um in terms of immediately implementing use of EC recover. Um there's a couple um examples that I have here. Open Zeppelin and Solady um both have libraries that uh implement ECDSA um logic. It's a one important thing to point out here is that with these libraries, there's a lot that is implemented in them that we are covering today, but not always.

And it's important to see to always check like whatever library you're using, like to check what it does versus what you'll need to do yourself. Um, all right. So let's talk about some security considerations for using EC recover on in smart contracts onchain. So I'm going to say we don't need to worry about for our purposes exposing our private key for the reasons that we discussed before. We're not worried that if we have that VRS that we can get back to our private key.

Um and we're also not going to worry about whether or not EC recover is working correctly. We'll assume that that's the case as well. Um what we do need to worry about on our side of things is the following a at least the following for consideration. Um and this list here is composed basically of what we see for this AC recover function. It's considering its input, considering its output and then also the context in which it's used.

So first up we have here that we need to consider whether or not the address that's being released from EC recover is validated. Um we also need to consider whether the input message hash can be manipulated in any kind of way. Um we need to consider whether or not the input signature values can be adjusted. Um which we'll talk about at the end. And then these last few here are concerning signature replay.

Um, and they're just based upon again the context in which you would use EC Recover. You want to be thinking about who's going to be using your signature, where it's going to be used, and where can get very specific. Um, whether or not you have a deadline for whether or not that signature makes sense and so on. So, let's first start with this aspect of assessing the recovered address from EC Recover. Um biggest first thing is that you need to make sure that it's that the recovered address is not address zero.

Um this I mean because that implies right that it's not a valid message if that's the case um for the signature that was provided. Um sorry say that backwards the signature is not valid for the message provided. Um so if you don't have within your smart contract logic uh a revert case in that case then you can have uh a situation in which an invalid [clears throat] signature can be accepted for uh using different message parameters. Um more specifically you should check that address against the address you expect to see. And it would follow that if you check against the address you expect to see then you're also ensuring that um you're not getting address zero back.

Um so one solution could be to check the EC recover address against what the expected value is. So looking at the smart contract example I have to the side here. Um let's just break break down some of what we see. Um we do have within withdraw with signature uh require that that the recovered address is equivalent to the signer. Um but one thing I want to point out that is sometimes overlooked is that if you're not ensuring that the serer isn't address zero in the first place then this is a check that doesn't um do what you hope it to do.

So the situation that I have as an example here is uh a possibility of say you're doing an upgrade to your project and um during that upgrade you accidentally set the signer to address zero. Um this isn't an infeasible thing to happen. This type of thing has happened in the past. And when something like this occurs, now you suddenly have this requirement that the recovered address from EC recover is equivalent to address zero, which is exactly the opposite of what we're require to require. Um, we're now saying that this message has [laughter] not been signed by an EOA that we care about.

Um, and so now our example here accepts invalid um, signatures. for different like you can just sort of pick what your message is. Um so how do we fix this? We can we can make an adjustment within our uh input parameters or we can make a check to our input parameters this to make sure that the serer is not address zero in the first place. And then we now have um something that does what we're expecting it to do.

Uh another note here that again UCDSA libraries will commonly add in this check that it's not the zero address but also commonly we need to check that it's a specific address too. So there's nothing wrong with having like this extra um bit of of checks in there for it. Let's go on to replay. Um so what is replay even? So for the consideration of this talk, let's consider replay to be um the idea of taking a signature that's meant for one context and being able to use it again in another context.

And that other context might even be the same context again. So replay might be reusing the same message in the same way several times. Um, but it also might be using that message in different places than was expected along with the original place. Um, so in order to prevent uh replay from occurring, we need to be thinking about the specific context in which we expect our signature to be used. because if not if we don't have that very specific context laid out within our smart contract logic then we stand the possibility of um of reusing that signature elsewhere.

So um within this um smart contract example that we have to the side we've modified it a little bit from the last example. Now we have included a nons in there instead of just recipient and amount. That's nice because previously when somebody was sign when somebody was given a signature to um transfer out some amount from this contract, they could only ever do it once before it got logged and then they would never be able to do it again. With this inclusion of a nons, now we're able to be more specific about which time we're expecting this recipient to be able to transfer out this amount. So they might have a signature that's based on a non of one.

Maybe they're the first person to be able to transfer that amount. But if they have that recipient an exact amount again, um they can then produce a second nons to sign with and use that secondary signature without worrying about either of them being replayed because the knots increases every time that it's being consumed by a signature. So, um, nonsas can help us be specific about, um, first off, I guess, whether we want a signature to be used more than once or just once. Um, and then also specifically the ordering in which these um, signatures get used too. some other context to consider that often get um that often are not thought of as much of as the knots.

Um we have function replay. This is where I'm talking about knots. So we just talked about that one. I'll move to the next um smart contract replay. the idea the the concept of possibly having two different functions within one smart contract that use the same signature message setup and therefore have the possibility of taking one signature that's meant for one function and using it again within another function.

Um how do we prevent against something like that? This is where we start getting into concepts of type hash type hashes and domain separators. So type hashes can be used to say the specific context in which you the specific function context in which you want to um provide a signature. So in this case we see to the right that we have a withdrawal type hash where the information we would be providing would be provided within this type hash that we'll see in a bit here. Um, another possibility is that you are running a protocol or interacting with a protocol that has two smart contracts on the same chain um that both accept signatures from the same person.

This is also something that needs to be considered. And if that's a possibility for any protocol that you're interacting with, then it's important that the signatures that are provided include a reference to address this within what's called a domain separator. A domain separator is providing context on often the chain and the address that's being used and sometimes even um the version of your contract for upgrade. for instance. So, uh let's talk about that blockchain ID being included within this.

That's for if you have signatures uh if you are providing as a val as a signer signatures across multiple chains that might look the same, then you would want to make sure that the block chain ID is included so that whatever you're signing is specific to that chain ID. And again, this right here is specifically usually for like versions. Say you have that uh contract that you want to upgrade and a signature is only meant for one specific version and you change to a different version, you might not want that signature to be valid anymore. Yeah. So if we put this all together in terms of like what we would expect for replay protection within our contract um we have a non protecting um using the signature more than once within that function.

We have this strruct hash being created out of this withdrawal type hash which will take the recipient amount and knots and wrap it within this strct hash here to say that it's specifically being used for this function. And that together with the domain separator says not just for this function but also specifically for this contract address and on this chain. So altogether that can give us a pretty specific context in which we are signing. This is what ends up getting used as a digest within this recover signer. So again it's still just a hash message that's um being used along with a signature to get a recovered address.

Um, one other like one other consideration for um being specific within the message um as to what your what your message is meant for is who is uh who is your message meant for. So sometimes you have sets setups for uh projects where you mean for somebody to have be privy to a specific um like for instance perk and in this case for instance we have that you can purchase something with a voucher um meaning that you're going to get a discount but what we can see within this logic here is that the message that's created just takes a voucher ID and it takes a discount amount out and it has [clears throat] nothing to do with who is being who's inputting it. And so what can happen here is that somebody can be waiting to see what's what's being called to purchase with voucher um spot it in the mele and then um can pay more in fees so that they can frontr run that call with the exact same values and therefore they're able to consume that signature first themselves. meaning that they'll be privy to the discounted amount rather than the person who it was meant for. So as a solution, what is what what's what do we need here?

We need an extra inclusion of who this uh signature is meant for. And in this case, we would expect that if somebody is um if some if somebody's being given a discount that they're the ones that are consuming it. So the message sender being the authorized user user in this case. Okay. So let's move on to hash collisions um that can occur through parsing.

So I have an example just simple here of what hash collision is and this is occurring specifically through that step right before hashing where you're going to encode the information that's part of your message. Um people you sometimes use encode packed AI encode packact to do this. Um and the problem arises when you're doing that with dynamic variable types like strings or bytes because when you're using encode packact um there's no padding between the values and so the hashing ends up looking exactly the same regardless of how you parse that that information. So within our example to the side here, we have this contract where you have multiple tokens involved and these tokens are being specified in a mapping token balance balances with some strings and those strings some of them look very similar like for instance USDT and SDT and the problem arises within this example um because we have also So the um username is also a string. So somebody is identifying themselves using a username that's a string and they can use that to their advantage to uh manipulate what they have been signed to take as their allowance.

So let's say for instance that Alice um was given an allowance through a signature for 100 USDT but she spots this opportunity to rewrite her username as Alice U and then get 100 SDT which let's say is worth more than 100 USDT then now um when they do this encode pact um part and hash it we end up with this value here Um and that happens to be exactly the same as it would um with SDT. Um so these two look exactly the same and so the signature will treat it the same. The solution for taking care of this is to change encode packact to AI.incode encode. That's one possible solution.

Um we can see in the example below how that changes things that now that we're using AI.enccode and code. Um, if we try to do the same trick again, we're getting two different things. Um, the other other considerations that you can make are do you really need to have uh dynamic uh types for the data types that you're signing for. Um, maybe it's warranted that you need to make a change to that.

Um, and then if not, if you really have to use it, um, also just be very conscious of placing two dynamic types next to each other. Um, because that's when this manipulation can occur. Um, last one we will talk about here is signature malleability. Um, so this is coming from this VRS combination for our signature. Anytime we have a signature VRS um for a signer in a message, there's actually another combination of VP prime R and S-prime which is also a a valid signature for the same signer in the same message.

Um, and so if we're not doing any checks against what these values are, uh, when we're doing the CC recover process, then it might have an impact on what checks you make above in in the other parts of the signature validation process. Is here we can see that we are requiring that a used a signature has not been used previously. Um, but if we have more than one signature that ends up encoding the same information, then we can get around that check by using the other signature. So, what does this V prime R and S-prime look like? What's happening here?

And again, I'm just going to go at a very high level through um cryptographically what's happening. Um so when we have um when we're given this VRS, there's actually um two places on the elliptic curve that have the same x value r. So we can see that on our on our graph here that we have uh the r above and we have another point below that has the same r values for its x coordinate, but they're at two different y-coordinates. Um so with that when we have a signature VRS um corresponding to one of these points then that Value is either TW uh this one of two numbers 27 or 28 um in order to get the other point then whatever you had for your Value previously you'll switch to the other one and whatever you had for your S your S prime is going to be this weird looking thing which is basically just the order of the elliptic curve um which is some value minus s and that value happens to look like this. Um so how do we prevent if we know exactly what this other point looks like that provides another signature?

We have a way of finding it every time. How do we prevent it from being used? um the solution ends up looking like um requiring that the s that it takes in is no more than half of that order value because if it's no more than half of that then when you're subtracting it from the order value then you know that the resulting value will be more than half. If it's strictly less than half you know that the other part has to be at least half as well. Um so then that ends up yielding that that s prime that's more than a half will not work for um for your code.

So that's what we see to the right here is that we've implemented this check that the s value um has to be less than or equal to half of the order value. Um again a lot of ECDSA libraries make this check. you should double check that if you're using one that it covers that. Um, another part of the solution is just in general, it's good advice not to log a signature as a means of preventing replay. Um, so to wrap up, um, when we're considering using signatures through a signature validation, through smart contracts, we should be considering, um, that more context is key when constructing a good signed message.

Um, that you should be validating the return address and that we should be limiting ways in which input may be ambiguous. That's it. That's all. All right, awesome stuff, Opel. And I am looking at the chat here and I do see that we've had some people send some questions through.

Uh first one, uh what does this mean for us vibe coders? And I'll reframe this question, be a little bit more uh specific. Um, you know, I guess you know, what are some of the most important areas to focus on as a developer when it comes to preventing signature validation issues?

Yeah, those are great questions. So, what does it mean for us as vibe coders? Um, it means that you should be paying attention to the what your what your project needs. Um, what are the needs of your project? Um because you might not need everything we've discussed.

Um for instance, maybe you are using signatures as a means of whitelisting who gains access to your protocol, but maybe you don't need a non to restrict it only being used once. Maybe once somebody has access, then they have access from then on. Um it so it ends up meaning that you just need to really be paying attention to the context in which um your project exists and what your needs are at that high level of um of of asking um what where where do I expect to use my signature? Um and that's going to tell you which of these you need to pay attention to. And then as a vibe coder, you can then take that um and ask that your um the agent that you're working with is um taking that into consideration as well.

Sure. Yeah. Um and then see another one. Uh so after deployment what onchain measures can be taken uh to detect signature validation abuse you know out in the wild uh post deployment and you know what are some good tools or solutions that exist for projects that are already live

I'd say for uh projects that have deployed that you would want to make sure that you have done your due diligence in the previous step of making sure that you have everything there that you But then also um as far as like monitoring goes um some of the things that you can look through look for like say through hexagate um is um to look for things like is the same information uh being like processed more than once for a signature meaning the exact same information. That's what I would look for in terms of something like replay. um am I seeing the exact same information come through um like more than once? And if so, that might be a suspicious behavior to me. Um, you might also be looking for like based upon the amount of um, signatures you've provided and what you expect that to correspond to, are you seeing volumes of movement that are higher than the than what reflects the amount of signatures that you have put out.

Those are some things to consider, I'd say.

Awesome. Well, uh, it does look like we have, uh, gone, uh, slightly over our time, so I guess I'll wrap things up by just thanking you all for being in attendance. Awesome job, Opel. And we very much look forward to having you join us for our next session that'll be uh, on November 14th. So, thank you all very much, and I hope you have a happy Halloween.

See you later. Thanks.

Automatic transcript — names and jargon may be misspelled.