adds parity_verifySignature RPC method #9507
Conversation
It looks like @seunlanlege signed our Contributor License Agreement. 👍 Many thanks, Parity Technologies CLA Bot |
rpc/src/v1/helpers/mod.rs
Outdated
@@ -34,8 +34,10 @@ mod signer; | |||
mod signing_queue; | |||
mod subscribers; | |||
mod subscription_manager; | |||
mod verify_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.
this file doesn't exist change it to mod signature
or change the file name itself?
rpc/src/v1/helpers/errors.rs
Outdated
pub fn verification_error<T: fmt::Display>(data: T) -> Error { | ||
Error { | ||
code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), | ||
message: format!("{}", data).into(), |
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.
Needless into()
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.
Looks promising, but would be nice to re-use existing methods.
rpc/src/v1/helpers/signature.rs
Outdated
pub fn verify_signature(is_prefixed: bool, message: Bytes, signature: H520) -> Result<RichBasicAccount> { | ||
let mut buf = [0; 32]; | ||
buf.copy_from_slice(&message.0[..]); | ||
let mut message = H256(buf); |
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.
Message is pre-maturely casted to H256
, it should be just keccaked without prefix.
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.
did not think of this 😅
rpc/src/v1/helpers/signature.rs
Outdated
|
||
if is_prefixed { | ||
let mut buf = [0; 32]; | ||
let mut keccak = Keccak::new_keccak256(); |
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.
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!
rpc/src/v1/helpers/signature.rs
Outdated
use ethereum_types::H256; | ||
use tiny_keccak::Keccak; | ||
|
||
pub fn verify_signature(is_prefixed: bool, message: Bytes, signature: H520) -> Result<RichBasicAccount> { |
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.
The method should support chain replay protected signatures with chain_id encoded in the value of v
.
rpc/src/v1/helpers/signature.rs
Outdated
let signature = Signature::from(signature.0); | ||
let is_valid_for_current_chain = chain_id.map(|chain| { | ||
let v = signature.v(); | ||
if v > 1 && (v as u64) == chain { |
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 pretty sure this is wrong
rpc/src/v1/helpers/signature.rs
Outdated
let mut hash = keccak(message.0.clone()); | ||
if is_prefixed { | ||
hash = eth_data_hash(message.0); | ||
} |
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.
Since you are not using the message after you have computed the hash
the clone
seems needless!
Consider:
let hash = if is_prefixed { eth_data_hash(message.0) } else { keccak(message.0) };
Also I think keccak supports slices so should be possible to pass-by-reference too!
rpc/src/v1/impls/light/parity.rs
Outdated
use v1::helpers::dispatch::LightDispatcher; | ||
use v1::helpers::light_fetch::LightFetch; | ||
use v1::metadata::Metadata; | ||
use v1::traits::Parity; | ||
use v1::types::{ | ||
Bytes, U256, U64, H160, H256, H512, CallRequest, | ||
Bytes, U256, U64, H160,H520, H256, H512, CallRequest, |
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.
space after comma 😸
@tomusdrw @niklasad1 i think this is ready to be merged |
rpc/src/v1/helpers/signature.rs
Outdated
let is_valid_for_current_chain = chain_id.map(|chain_id| { | ||
let v = signature.v() as u64; | ||
if v > 36 { | ||
let decoded_v = (v - 35) - (chain_id * 2); |
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 can still overflow if chain_id*2 is bigger than v-35
(maybe only theoretical but still ...)
Consider to use saturating_sub/checked_sub
and saturated_mul
for this use-case instead!
EDIT:
for example v.saturating_sub(35).saturating_sub(chain_id.saturating_mul(2)) < 2
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.
not sure why we need saturating_mul with chain_id, isn't it supposed to be a compile-time constant?
rpc/src/v1/helpers/signature.rs
Outdated
let hash = if is_prefixed { | ||
eth_data_hash(message.0) | ||
} else { | ||
keccak(message.0.clone()) |
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 to nag but please remove clone()
here 🥇
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.
Minor things to address, see comments below
rpc/src/v1/helpers/signature.rs
Outdated
@@ -9,16 +9,16 @@ pub fn verify_signature(is_prefixed: bool, message: Bytes, signature: H520, chai | |||
let hash = if is_prefixed { | |||
eth_data_hash(message.0) | |||
} else { | |||
keccak(message.0.clone()) | |||
keccak(message.0) | |||
}; | |||
|
|||
let signature = Signature::from(signature.0); | |||
let is_valid_for_current_chain = chain_id.map(|chain_id| { | |||
let v = signature.v() as u64; | |||
if v > 36 { |
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 if expression is unnecessary now so remove it and return false from the match instead 👍
Also I would prefer if you can perform saturated_mul
on chain_id * 2 because it can also overflow sorry if I was unclear
rpc/src/v1/helpers/signature.rs
Outdated
|
||
let signature = Signature::from(signature.0); | ||
let is_valid_for_current_chain = chain_id.map(|chain_id| { | ||
let result = (signature.v() as u64) |
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.
Should be just (signature.v().checked_sub(35) / 2) as u64 == chain_id
- it's simpler, doesn't overflow and does not require matching 0
and 1
.
Also if chain_id.is_none()
the signature is valid if it's not replay-protected (currently we return false
).
BTW This function needs tests, we should also clearly document what format of signature we expect here. (I know there is one RPC test for this, but we should just test this function with different signatures to make sure to cover all branches and that replay protection part works correctly)
Note that signature in transaction is actually using u64
+ U256
+ U256
to store the replay protection, we later remove replay protection (subtract and divide) and convert the entire signature to H520
.
So the fact that we require replay protection to fit to u8
when submitted over RPC limits the number of chain_ids one can safely use for this scheme.
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.
To make it clear: have a look at ec_recover
method that we have in personal
namespace.
It expects signature in electrum notation and doesn't handle replay protection (I think replay protection is not implemented for prefixed signing (i.e. eth_sign
- please check)).
This method should work exactly like ec_recover
for prefixed data, but for non-prefixed data (i.e. transaction hash) we might consider checking replay-protection (but then we need to change the input type to something else than H520
) or we need to clearly state that the signature should be passed in electrum format.
rpc/src/v1/impls/light/parity.rs
Outdated
Peers, Transaction, RpcSettings, Histogram, | ||
TransactionStats, LocalTransactionStatus, | ||
BlockNumber, ConsensusCapability, VersionInfo, | ||
OperationsInfo, ChainStatus, | ||
AccountInfo, HwAccountInfo, Header, RichHeader, | ||
}; | ||
use Host; | ||
use v1::types::RichBasicAccount; |
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.
Move to other v1::types
imports
rpc/src/v1/helpers/signature.rs
Outdated
let public = recover(&signature, &hash).map_err(errors::encryption)?; | ||
let address = public_to_address(&public); | ||
let account = BasicAccount { address, public_key: public, is_valid_for_current_chain }; | ||
Ok(RichBasicAccount { inner: account, extra_info: Default::default() }) |
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 do we return RichBasicAccount
if extra_info
is always empty?
rpc/src/v1/types/account_info.rs
Outdated
pub struct BasicAccount { | ||
pub address: Address, | ||
pub public_key: Public, | ||
pub is_valid_for_current_chain: Option<bool> |
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.
missing trailing comma & docs
ba9d6bc
to
4d05109
Compare
@@ -0,0 +1,57 @@ | |||
use std::sync::Arc; |
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.
New files need a preamble, but I think it would be much better to add the tests in the same file as the function-under-test. Just add #[cfg(test)] mod tests {
and put the content there.
|
||
#[test] | ||
fn test_verify_signature_prefixed_mainnet() { | ||
setup(true, Some(1), Some(1), true) |
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.
Test in a current form is pretty unreadable - I think it would be way better to avoid passing so many bools and numbers without some additional description.
I think the setup
should only be producing the signature, the assertion and call to verify_signature
should be inlined and duplicated in every test, like this:
#[test]
fn test_verify_signature_not_prefixed_mainnet() {
let should_prefix = true;
let data = vec![5u8];
let chain_id = Some(1);
let (address, v, r, s) = sign(should_prefix, &data, chain_id);
let account = verify_signature(should_prefix, &data, v, r, s, chain_id).unwrap();
assert_eq!(account.inner.address, address);
assert_eq!(account.inner.is_valid_for_current_chain, true);
}
IMHO that would really help readability/understandability of the test. If you want to keep the de-duplication, then consider using this:
#[test]
fn test_verify_signature_not_prefixed_mainnet() {
run_test(TestCase {
should_prefix: true,
signing_chain_id: Some(1),
rpc_chain_id: Some(1),
is_valid_for_current_chain: true,
});
}
rpc/src/v1/tests/mocked/parity.rs
Outdated
"0xe552acf4caabe9661893fd48c7b5e68af20bf007193442f8ca05ce836699d75e", | ||
"0x2089e84151c3cdc45255c07557b349f5bf2ed3e68f6098723eaa90a0f8b2b3e5", | ||
"0x5f70e8df7bd0c4417afb5f5a39d82e15d03adeff8796725d8b14889ed1d1aa8a", | ||
1 |
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.
In RPC we only use non hex-encoded numbers for indices/limits. For constistency this should be U64
type (hex-encoded ser/de)
rpc/src/v1/traits/parity.rs
Outdated
|
||
/// ecrecover signature | ||
#[rpc(name = "parity_verifySignature")] | ||
fn verify_signature(&self, bool, Bytes, H256, H256, u64) -> Result<RichBasicAccount>; |
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 still don't understand why we need RichBasicAccount
here, if we don't pass any "rich" data. Just return the BasicAccount
and get rid of the to_rich_struct
helper func.
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.
👌
Please, rebase on master |
2f2fa1e
to
48ec65e
Compare
rpc/src/v1/helpers/signature.rs
Outdated
use v1::helpers::dispatch::eth_data_hash; | ||
use hash::keccak; | ||
|
||
pub fn verify_signature(is_prefixed: bool, message: Bytes, v: U64, r: H256, s: H256, chain_id: Option<u64>) -> Result<BasicAccount> { |
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.
Need docs (anything pub
does). Consider moving params to own lines as well, and perhaps use the rsv
order to make it clearer what the params are?
rpc/src/v1/helpers/signature.rs
Outdated
|
||
let v = if v > 36 { | ||
(v - 1) % 2 | ||
} else { v }; |
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.
Funny whitespace here. Either put it all on one line or both arms on own lines.
rpc/src/v1/helpers/signature.rs
Outdated
let v: u64 = v.into(); | ||
let is_valid_for_current_chain = match (chain_id, v) { | ||
(None, v) if v == 0 || v == 1 => true, | ||
(Some(chain_id), v) if v > 36 => (v - 35) / 2 == chain_id, |
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 you help me understand what is going on here?
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.
if there's no chain_id
, then v
must either be 1/0 to be a valid for the current chain.
otherwise, if there's a chain_id
and v
> 36; (37 is the lowest value, where v
= 0, chain_id
= 1)
we check if the encoded chain_id
is valid for the current chain.
(v
- 35) / 2 == chain_id
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 related to extract to chain_id and remove the replay protection right?
This code is hard to read, can you add an inline comment to refer to EIP 155
and perhaps add a one-liner function named remove_replay_protection
or something similar?
Also, I think it should be v >= 35
to support chain_id 0
rpc/src/v1/helpers/signature.rs
Outdated
let accounts = Arc::new(AccountProvider::transient_provider()); | ||
let address = accounts.new_account(&"password123".into()).unwrap(); | ||
let sig = accounts.sign(address, Some("password123".into()), hash).unwrap(); | ||
let (v, r, s) = (sig.v(), sig.r(), sig.s()); |
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'd prefer let (r, s, v) = …
for consistency with other code, e.g. from_rsv()
rpc/src/v1/traits/parity.rs
Outdated
@@ -218,5 +217,9 @@ build_rpc_trait! { | |||
/// Call contract, returning the output data. | |||
#[rpc(name = "parity_call")] | |||
fn call(&self, Vec<CallRequest>, Trailing<BlockNumber>) -> Result<Vec<Bytes>>; | |||
|
|||
/// ecrecover 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.
The docs here are a bit…short? I don't see the params being documented, but perhaps you'll address that in a separate PR? Maybe something like: "/// Extracts Address and public key from signature using the r, s and v params. Equivalent to Solidity erecover"?
rpc/src/v1/types/account_info.rs
Outdated
/// account derived from a signature | ||
/// as well as information that tells if it is valid for | ||
/// the current chain | ||
#[derive(Debug, Clone, Serialize, PartialEq, Eq)] |
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.
Do we really need all these derives? It's hard to tell from the code in this PR if that is the case. Thanks!
rpc/src/v1/types/account_info.rs
Outdated
pub public_key: Public, | ||
/// if the signature contains chain replay protection | ||
/// and the chain_id encoded within the signature matches the current chain | ||
/// this would be true, otherwise false. |
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.
Good docs on this type; would be great if you could fix the line lengths and start all sentences with a capital letter.
@@ -14,6 +14,8 @@ | |||
// You should have received a copy of the GNU General Public License | |||
// along with Parity. If not, see <http://www.gnu.org/licenses/>. | |||
|
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.
Would be great with some module docs here; I think these types are all return types for RPC calls? If so, perhaps "Return types for RPC calls" is enough.
28cc5f0
to
9d1bc22
Compare
@dvdplm Can you have a look at this one again? |
Happy to merge this if you could resolve the conflicts again |
moved verify_signature tests to live in the same file.
9d1bc22
to
0da1766
Compare
* closes #7009 adds parity_verifySignature RPC method * removed unneccesary into * adds support for chain replay protected signatures * corrected chain replay protection check * corrected possible overflow * added tests * use checked_sub to prevent possible overflow * use saturating_mul to prevent possible overflow * changed method signature to accept r,s,v components * added unit tests * more tests * refactored tests for better readability moved verify_signature tests to live in the same file. * removed unneccesary imports * fixed PR grumbles * fixed rsv notation * fixed rsv notation * renamed BasicAcount to RecoveredAccount, Support zero chain_id * fixed compile errors * fixed tests * doc comment
Closes #7009