2019-01-07 11:33:07 +01:00
|
|
|
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
|
|
// This file is part of Parity Ethereum.
|
2018-11-28 12:18:43 +01:00
|
|
|
|
2019-01-07 11:33:07 +01:00
|
|
|
// Parity Ethereum is free software: you can redistribute it and/or modify
|
2018-11-28 12:18:43 +01:00
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
2019-01-07 11:33:07 +01:00
|
|
|
// Parity Ethereum is distributed in the hope that it will be useful,
|
2018-11-28 12:18:43 +01:00
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
2019-01-07 11:33:07 +01:00
|
|
|
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
2018-11-28 12:18:43 +01:00
|
|
|
|
2019-02-25 14:27:28 +01:00
|
|
|
use ethereum_types::{H256, U64};
|
2020-08-05 06:08:03 +02:00
|
|
|
use ethkey::{public_to_address, recover, Signature};
|
2018-11-28 12:18:43 +01:00
|
|
|
use hash::keccak;
|
2020-08-05 06:08:03 +02:00
|
|
|
use jsonrpc_core::Result;
|
|
|
|
use v1::{
|
|
|
|
helpers::{dispatch::eth_data_hash, errors},
|
|
|
|
types::{Bytes, RecoveredAccount},
|
|
|
|
};
|
2018-11-28 12:18:43 +01:00
|
|
|
|
|
|
|
/// helper method for parity_verifySignature
|
|
|
|
pub fn verify_signature(
|
2020-08-05 06:08:03 +02:00
|
|
|
is_prefixed: bool,
|
|
|
|
message: Bytes,
|
|
|
|
r: H256,
|
|
|
|
s: H256,
|
|
|
|
v: U64,
|
|
|
|
chain_id: Option<u64>,
|
2018-11-28 12:18:43 +01:00
|
|
|
) -> Result<RecoveredAccount> {
|
2020-08-05 06:08:03 +02:00
|
|
|
let hash = if is_prefixed {
|
|
|
|
eth_data_hash(message.0)
|
|
|
|
} else {
|
|
|
|
keccak(message.0)
|
|
|
|
};
|
|
|
|
let v = v.as_u64();
|
|
|
|
let is_valid_for_current_chain = match (chain_id, v) {
|
|
|
|
(None, v) if v == 0 || v == 1 => true,
|
|
|
|
(Some(chain_id), v) if v >= 35 => (v - 35) / 2 == chain_id,
|
|
|
|
_ => false,
|
|
|
|
};
|
|
|
|
|
|
|
|
let v = if v >= 35 { (v - 1) % 2 } else { v };
|
|
|
|
|
|
|
|
let signature = Signature::from_rsv(&r, &s, v as u8);
|
|
|
|
let public_key = recover(&signature, &hash).map_err(errors::encryption)?;
|
|
|
|
let address = public_to_address(&public_key);
|
|
|
|
Ok(RecoveredAccount {
|
|
|
|
address,
|
|
|
|
public_key,
|
|
|
|
is_valid_for_current_chain,
|
|
|
|
})
|
2018-11-28 12:18:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-08-05 06:08:03 +02:00
|
|
|
use super::*;
|
|
|
|
use ethereum_types::{H160, U64};
|
|
|
|
use ethkey::Generator;
|
|
|
|
|
|
|
|
pub fn add_chain_replay_protection(v: u64, chain_id: Option<u64>) -> u64 {
|
|
|
|
v + if let Some(n) = chain_id {
|
|
|
|
35 + n * 2
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct TestCase {
|
|
|
|
should_prefix: bool,
|
|
|
|
signing_chain_id: Option<u64>,
|
|
|
|
rpc_chain_id: Option<u64>,
|
|
|
|
is_valid_for_current_chain: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// mocked signer
|
|
|
|
fn sign(
|
|
|
|
should_prefix: bool,
|
|
|
|
data: Vec<u8>,
|
|
|
|
signing_chain_id: Option<u64>,
|
|
|
|
) -> (H160, [u8; 32], [u8; 32], U64) {
|
|
|
|
let hash = if should_prefix {
|
|
|
|
eth_data_hash(data)
|
|
|
|
} else {
|
|
|
|
keccak(data)
|
|
|
|
};
|
|
|
|
let account = ethkey::Random.generate().unwrap();
|
|
|
|
let address = account.address();
|
|
|
|
let sig = ethkey::sign(account.secret(), &hash).unwrap();
|
|
|
|
let (r, s, v) = (sig.r(), sig.s(), sig.v());
|
|
|
|
let v = add_chain_replay_protection(v as u64, signing_chain_id);
|
|
|
|
let (r_buf, s_buf) = {
|
|
|
|
let (mut r_buf, mut s_buf) = ([0u8; 32], [0u8; 32]);
|
|
|
|
r_buf.copy_from_slice(r);
|
|
|
|
s_buf.copy_from_slice(s);
|
|
|
|
(r_buf, s_buf)
|
|
|
|
};
|
|
|
|
(address.into(), r_buf, s_buf, v.into())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run_test(test_case: TestCase) {
|
|
|
|
let TestCase {
|
|
|
|
should_prefix,
|
|
|
|
signing_chain_id,
|
|
|
|
rpc_chain_id,
|
|
|
|
is_valid_for_current_chain,
|
|
|
|
} = test_case;
|
|
|
|
let data = vec![5u8];
|
|
|
|
|
|
|
|
let (address, r, s, v) = sign(should_prefix, data.clone(), signing_chain_id);
|
|
|
|
let account = verify_signature(
|
|
|
|
should_prefix,
|
|
|
|
data.into(),
|
|
|
|
r.into(),
|
|
|
|
s.into(),
|
|
|
|
v,
|
|
|
|
rpc_chain_id,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(account.address, address.into());
|
|
|
|
assert_eq!(
|
|
|
|
account.is_valid_for_current_chain,
|
|
|
|
is_valid_for_current_chain
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_verify_signature_prefixed_mainnet() {
|
|
|
|
run_test(TestCase {
|
|
|
|
should_prefix: true,
|
|
|
|
signing_chain_id: Some(1),
|
|
|
|
rpc_chain_id: Some(1),
|
|
|
|
is_valid_for_current_chain: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_verify_signature_not_prefixed_mainnet() {
|
|
|
|
run_test(TestCase {
|
|
|
|
should_prefix: false,
|
|
|
|
signing_chain_id: Some(1),
|
|
|
|
rpc_chain_id: Some(1),
|
|
|
|
is_valid_for_current_chain: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_verify_signature_incompatible_chain_id() {
|
|
|
|
run_test(TestCase {
|
|
|
|
should_prefix: false,
|
|
|
|
signing_chain_id: Some(65),
|
|
|
|
rpc_chain_id: Some(1),
|
|
|
|
is_valid_for_current_chain: false,
|
|
|
|
});
|
|
|
|
run_test(TestCase {
|
|
|
|
should_prefix: true,
|
|
|
|
signing_chain_id: Some(65),
|
|
|
|
rpc_chain_id: Some(1),
|
|
|
|
is_valid_for_current_chain: false,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_verify_signature_no_signing_chain_id() {
|
|
|
|
run_test(TestCase {
|
|
|
|
should_prefix: false,
|
|
|
|
signing_chain_id: None,
|
|
|
|
rpc_chain_id: Some(1),
|
|
|
|
is_valid_for_current_chain: false,
|
|
|
|
});
|
|
|
|
run_test(TestCase {
|
|
|
|
should_prefix: true,
|
|
|
|
signing_chain_id: None,
|
|
|
|
rpc_chain_id: Some(1),
|
|
|
|
is_valid_for_current_chain: false,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_verify_signature_no_rpc_chain_id() {
|
|
|
|
run_test(TestCase {
|
|
|
|
should_prefix: false,
|
|
|
|
signing_chain_id: Some(1),
|
|
|
|
rpc_chain_id: None,
|
|
|
|
is_valid_for_current_chain: false,
|
|
|
|
});
|
|
|
|
run_test(TestCase {
|
|
|
|
should_prefix: true,
|
|
|
|
signing_chain_id: Some(1),
|
|
|
|
rpc_chain_id: None,
|
|
|
|
is_valid_for_current_chain: false,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_verify_signature_no_chain_replay_protection() {
|
|
|
|
run_test(TestCase {
|
|
|
|
should_prefix: false,
|
|
|
|
signing_chain_id: None,
|
|
|
|
rpc_chain_id: None,
|
|
|
|
is_valid_for_current_chain: true,
|
|
|
|
});
|
|
|
|
run_test(TestCase {
|
|
|
|
should_prefix: true,
|
|
|
|
signing_chain_id: None,
|
|
|
|
rpc_chain_id: None,
|
|
|
|
is_valid_for_current_chain: true,
|
|
|
|
});
|
|
|
|
}
|
2018-11-28 12:18:43 +01:00
|
|
|
}
|