-
Notifications
You must be signed in to change notification settings - Fork 12k
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
Add isValidSignatureAndData
to Bouncer to verify method calls
#973
Add isValidSignatureAndData
to Bouncer to verify method calls
#973
Conversation
test/access/SignatureBouncer.test.js
Outdated
}); | ||
it('should accept valid method with valid params for valid user', async function () { | ||
const methodId = getMethodId('checkValidSignatureAndData', 'address', 'uint256', 'bytes'); | ||
const val = 23; |
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.
let's de-duplicate these variables by bringing them either into a context variable using a beforeEach
within a context
or just making them constants above these methods
Can we also add a |
…about onlyValidSignatureAndData last paramater
*/ | ||
contract SignatureBouncer is Ownable, RBAC { | ||
using ECRecovery for bytes32; | ||
|
||
string public constant ROLE_BOUNCER = "bouncer"; | ||
uint constant METHOD_ID_SIZE = 4; | ||
uint constant SIG_SIZE = 160; |
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.
@shrugs I forgot that I wanted to comment on this variable. The signature value is always 160 bytes within msg.data
even though _sig.length
is always much smaller (64 bytes I think?). I guess it pads this data to the max size? Regardless, it's always correct but I wanted to call attention to the fact that I wasn't totally sure why.
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 spent a bit of time playing around with the abi documentation and realized that bytes
are encoded as follows
[distance to the beginning of the argument data] -> 32 bytes
[the boolean true] -> 32 bytes
[length of the data] -> 32 bytes
[the data, right-padded to a multiple of 32] -> 64 bytes
and there we have our 160
.
I have a question, though, after looking at the abi documentation: can we add a test case to see if it's possible to have a dynamic argument before the final bytes
argument? i.e. can we sign a bouncer signature for
function test(bytes _someBytes, bytes _sig)
onlyValidSignatureAndData(_sig)
{
}
my hunch is that this won't work immediately because with dynamically sized arguments, their location in the transaction data is included in the tx data before the argument itself.
i.e. I think a transaction for the above function looks like
0x12345678 // 8 bytes
[ location of data for _someBytes ] // (8 bytes + 32 + 32)
[ location of data for _sig ] // (8 + 32 + 32 + 32 + len(_someBytes))
[ enc(len(_someBytes) ]
[ _someBytes right-padded to modulo 32 ]
[ enc(len(_sig)) ]
[ _sig right-padded to module 32 ] // (64 bytes long in our case)
which means we're lucky that the examples only use fixed-sized arguments so we don't have to calculate stuff like "at what offset will the encoded signature be in the transaction data for this function call".
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.
Thanks for shedding some light on this. I'll add that test and see what we get
question about non-fixed-sized arguments before _sig
Ok, I altered those tests to verify that variable sized parameters will validate correctly. The only change to the |
Looks like it is necessary to construct the full tx data, sans signature, and then sign it, which is a little cumbersome in practice; it means we won't be able to use the default web3 tx construction code. Looks like this calls for a little javascript helper in the future, as a separate package most likely. This looks good to me! @frangio can you take a look as well? |
* @dev See the tests Bouncer.test.js for specific usage examples. | ||
* @notice A method that uses the `onlyValidSignatureAndData` modifier must make the _sig | ||
* @notice parameter the "last" parameter. You cannot sign a message that has its own | ||
* @notice signature in it so the last 160 bytes is of msg.data (which represents _sig) |
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.
Can we update this to the new value of 128 and also mention that for this to be easy to construct signatures for, your arguments must be fixed-sized?
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.
(sorry, just took one last pass through it and I think it's worth adding a comment about this)
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.
@shrugs Ah, sorry, my bad. I'll get that changed. I actually went and posted a detailed answer on a stack overflow thread about how msg.data
is constructed https://ethereum.stackexchange.com/a/50616/40921 . Do you want me to link this thread in the comments?
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.
sure, that's a solid answer
Still want a second pair of eyes; will ask the maintainers and hopefully get this in soon; I want to start using it immediately :D |
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 is very interesting. I couldn't find anything wrong on the code, but I recommend to get an extra review from somebody more experienced than me. Maybe @frangio can do at least a safety check.
As an aside, @aflesher @shrugs would you write a blog post explaining this feature? I think it would be a nice complement to get more people using and testing it.
* @notice signature in it so the last 128 bytes is of msg.data (which represents the | ||
* @notice length of the _sig data and the _sig data itself) is ignored when validating. | ||
* @notice Also non fixed sized parameters make constructing the data in the signature | ||
* @notice much more complex. See https://bit.ly/2sGSPBc for more details. |
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 prefer the original URLs instead of shorteners. Please update the link to:
https://ethereum.stackexchange.com/a/50616
* @notice A method that uses the `onlyValidSignatureAndData` modifier must make the _sig | ||
* @notice parameter the "last" parameter. You cannot sign a message that has its own | ||
* @notice signature in it so the last 128 bytes is of msg.data (which represents the | ||
* @notice length of the _sig data and the _sig data itself) is ignored when validating. |
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 having problems parsing this:
so the last 128 bytes is of msg.data [...] is ignored.
The two "is" verbs confuse me, is that a typo?
*/ | ||
contract SignatureBouncer is Ownable, RBAC { | ||
using ECRecovery for bytes32; | ||
|
||
string public constant ROLE_BOUNCER = "bouncer"; | ||
uint constant METHOD_ID_SIZE = 4; | ||
// (signature length size) 32 bytes + (signature size 65 bytes padded) 96 bytes | ||
uint constant SIG_SIZE = 128; |
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.
why SIG
instead of SIGNATURE
?
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 was just keeping this variable name consistent with our _sig
params naming. SIGNATURE
does make more sense though, I'll change it.
/** | ||
* @dev requires that a valid signature with a specifed method of a bouncer was provided | ||
*/ | ||
modifier onlyValidSignatureAndMethod(bytes _sig) |
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 would use _signature
here too. But for consistency, maybe it's better to leave it and update it in a future branch.
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.
let's follow this up with a change to _signature
, yeah.
@ElOpio A blog post explaining this feature is a great idea. I was thinking about writing a javascript helper function to construct this bytecode as well. Would web3.js be the most logical place to propose this feature? Or maybe ethereumjs-util? |
It would be neat to have to bundled in the core utils, but I doubt it's general-case enough that they'd merge it. Perhaps ethjs-utils? it may fit into the web3-utils sub-package of web3, though. Hard to say |
Sorry for taking a while to get to this. Great work @aflesher! I have some comments/ideas about this but I will open another issue to discuss there. |
Actualy, I feel more comfortable discussing here a bit because I don't have a concrete proposal, and I have some questions. Is this complex procedure really necessary? I saw in the comments here that the web3 tx construction code couldn't be used, but I was actually able to use this.bouncer.contract.checkValidSignatureAndData.getData(
authorizedUser,
this.bytesValue,
this.uintValue,
'0x00000000000000000000000000000000'
).slice(2, -128); (The Before realizing that this was possible I was going to say that the huge complexity to use these functions was indicative of an API design problem. There's still something that feels odd to me, though it was already in the previous Just wanna hear your thoughts and see if we can come up with some improvements to the usage of these new functions. |
@frangio that's actually super smart, I'm amazed we missed that 😨 So we can definitely use the built-in data construction code, but can't use the built-in transaction signing code. So the utility function for this should look something like: const voucherForCallTo = (fromAddress, contract, method, ...args) => {
const data = contract.methods[method](...args).encodeABI().slice(0, -120)
const hash = web3.utils.soliditySha3(`${fromAddress}${contract.address}${data}`)
return web3.eth.personalSign(hash)
} which looks super nice (but uses web3-1.0, so we have to update that syntax for the current version on a separate note, I want to explore the idea proposed by @PhABC where a contract can be the authority, but delegate authorizations to other arbitrary logic so something like:
|
@aflesher it's more of an extension to the authorization pattern; the ability to defer the "isValidSignature" logic to a contract rather than just an account. This is actually something like 0x is doing as part of v2, so I think it's a good add. I'll be cleaning up the signature construction part of the tests later on when we decide how to structure the contract-as-bouncer logic. Also in that PR I'm going to update the language because I've been using |
I'd like to discuss that idea elsewhere, is there an issue already? |
Fixes #891
🚀 Description
SignatureBouncer.sol
has two additional signature verification methods:isValidSignatureAndMethod
which validates that the external method called matches the methodId in the signatures message.isValidSignatureAndData
which validates that the external method called with params matches the methodId and param values in the signatures method.Details
msg.data
for any given external call is comprised of themethodId
followed by the values of each parameter. A signature can append the required methodId and optionally the required parameter values to it's message.isValidSignatureAndMethod
andisValidSignatureAndData
validate that msg.data matches the signed data.SignatureBouncer.test.js
demonstrates how to assemble the expectedmsg.data
correctly and add it to the signature.npm run lint:all:fix
).