Skip to content

Commit 9e9eb8e

Browse files
truschwischlintn-x2
committed
feat: support linking ethereum accounts to DIDs
Co-authored-by: William Freudenberger <william@kilt.io> Co-authored-by: Antonio <antonio@kilt.io>
1 parent 40c48d5 commit 9e9eb8e

14 files changed

Lines changed: 777 additions & 176 deletions

File tree

pallets/pallet-did-lookup/Cargo.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@ pallet-balances = {branch = "polkadot-v0.9.24", git = "https://github.com/parity
1616
sp-core = {branch = "polkadot-v0.9.24", git = "https://github.com/paritytech/substrate"}
1717
sp-io = {branch = "polkadot-v0.9.24", git = "https://github.com/paritytech/substrate"}
1818
sp-keystore = {branch = "polkadot-v0.9.24", git = "https://github.com/paritytech/substrate"}
19+
rand = "0.8"
1920

2021
[dependencies]
2122
codec = {package = "parity-scale-codec", version = "3.1.2", default-features = false, features = ["derive"]}
23+
hex = { version = "0.4", default-features = false }
24+
impl-serde = { version = "0.3.1", default-features = false }
25+
libsecp256k1 = { version = "0.7", default-features = false, features = [ "hmac" ] }
2226
log = "0.4.17"
2327
scale-info = {version = "2.1.1", default-features = false, features = ["derive"]}
28+
serde = { version = "1.0.101", default-features = false, features = [ "derive" ] }
29+
sha3 = { version = "0.10", default-features = false }
2430

2531
# KILT
2632
kilt-support = {default-features = false, path = "../../support"}
@@ -30,17 +36,17 @@ frame-support = {branch = "polkadot-v0.9.24", default-features = false, git = "h
3036
frame-system = {branch = "polkadot-v0.9.24", default-features = false, git = "https://github.com/paritytech/substrate"}
3137
sp-runtime = {branch = "polkadot-v0.9.24", default-features = false, git = "https://github.com/paritytech/substrate"}
3238
sp-std = {branch = "polkadot-v0.9.24", default-features = false, git = "https://github.com/paritytech/substrate"}
39+
sp-core = {branch = "polkadot-v0.9.24", default-features = false, git = "https://github.com/paritytech/substrate"}
40+
sp-io = {branch = "polkadot-v0.9.24", default-features = false, git = "https://github.com/paritytech/substrate"}
3341

3442
# benchmarking
3543
frame-benchmarking = {branch = "polkadot-v0.9.24", optional = true, default-features = false, git = "https://github.com/paritytech/substrate"}
36-
sp-io = {branch = "polkadot-v0.9.24", optional = true, default-features = false, git = "https://github.com/paritytech/substrate"}
3744

3845
[features]
3946
default = ["std"]
4047

4148
runtime-benchmarks = [
4249
"frame-benchmarking",
43-
"sp-io",
4450
"kilt-support/runtime-benchmarks",
4551
"frame-support/runtime-benchmarks",
4652
"frame-system/runtime-benchmarks",
@@ -55,6 +61,7 @@ std = [
5561
"scale-info/std",
5662
"sp-runtime/std",
5763
"sp-std/std",
64+
"impl-serde/std",
5865
]
5966

6067
try-runtime = [
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
// KILT Blockchain – https://botlabs.org
2+
// Copyright (C) 2019-2022 BOTLabs GmbH
3+
4+
// The KILT Blockchain is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
9+
// The KILT Blockchain is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
14+
// You should have received a copy of the GNU General Public License
15+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
// If you feel like getting in touch with us, you can do so at info@botlabs.org
18+
19+
// This code originally came from the purestake/moonbeam repo.
20+
21+
//! The Ethereum Signature implementation.
22+
//!
23+
//! It includes the Verify and IdentifyAccount traits for the AccountId20
24+
25+
#![cfg_attr(not(feature = "std"), no_std)]
26+
27+
use codec::{Decode, Encode, MaxEncodedLen};
28+
use frame_support::{RuntimeDebug, crypto::ecdsa::ECDSAExt};
29+
use scale_info::TypeInfo;
30+
use sha3::{Digest, Keccak256};
31+
use sp_core::{ecdsa, H160, H256};
32+
33+
#[cfg(feature = "std")]
34+
pub use sp_runtime::serde::{de::DeserializeOwned, Deserialize, Serialize};
35+
36+
//TODO Maybe this should be upstreamed into Frontier (And renamed accordingly)
37+
// so that it can be used in palletEVM as well. It may also need more traits
38+
// such as AsRef, AsMut, etc like AccountId32 has.
39+
40+
/// The account type to be used in Moonbeam. It is a wrapper for 20 fixed bytes.
41+
/// We prefer to use a dedicated type to prevent using arbitrary 20 byte arrays
42+
/// were AccountIds are expected. With the introduction of the `scale-info`
43+
/// crate this benefit extends even to non-Rust tools like Polkadot JS.
44+
45+
#[derive(Eq, PartialEq, Copy, Clone, Encode, Decode, TypeInfo, MaxEncodedLen, Default, PartialOrd, Ord)]
46+
pub struct AccountId20(pub [u8; 20]);
47+
48+
#[cfg(feature = "std")]
49+
impl_serde::impl_fixed_hash_serde!(AccountId20, 20);
50+
51+
#[cfg(feature = "std")]
52+
impl std::fmt::Display for AccountId20 {
53+
//TODO This is a pretty quck-n-dirty implementation. Perhaps we should add
54+
// checksum casing here? I bet there is a crate for that.
55+
// Maybe this one https://github.com/miguelmota/rust-eth-checksum
56+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57+
write!(f, "{:?}", self.0)
58+
}
59+
}
60+
61+
impl core::fmt::Debug for AccountId20 {
62+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
63+
write!(f, "{:?}", H160(self.0))
64+
}
65+
}
66+
67+
impl From<[u8; 20]> for AccountId20 {
68+
fn from(bytes: [u8; 20]) -> Self {
69+
Self(bytes)
70+
}
71+
}
72+
73+
impl From<AccountId20> for [u8; 20] {
74+
fn from(id: AccountId20) -> Self {
75+
id.0
76+
}
77+
}
78+
79+
impl From<H160> for AccountId20 {
80+
fn from(h160: H160) -> Self {
81+
Self(h160.0)
82+
}
83+
}
84+
85+
impl From<AccountId20> for H160 {
86+
fn from(id: AccountId20) -> Self {
87+
H160(id.0)
88+
}
89+
}
90+
91+
#[cfg(feature = "std")]
92+
impl std::str::FromStr for AccountId20 {
93+
type Err = &'static str;
94+
fn from_str(input: &str) -> Result<Self, Self::Err> {
95+
H160::from_str(input)
96+
.map(Into::into)
97+
.map_err(|_| "invalid hex address.")
98+
}
99+
}
100+
101+
/// Public key for an Ethereum / Moonbeam compatible account
102+
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
103+
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
104+
pub struct EthereumSigner([u8; 20]);
105+
106+
impl sp_runtime::traits::IdentifyAccount for EthereumSigner {
107+
type AccountId = AccountId20;
108+
fn into_account(self) -> AccountId20 {
109+
AccountId20(self.0)
110+
}
111+
}
112+
113+
impl From<[u8; 20]> for EthereumSigner {
114+
fn from(x: [u8; 20]) -> Self {
115+
EthereumSigner(x)
116+
}
117+
}
118+
119+
impl From<ecdsa::Public> for EthereumSigner {
120+
fn from(x: ecdsa::Public) -> Self {
121+
Self(x.to_eth_address().unwrap())
122+
}
123+
}
124+
125+
impl From<libsecp256k1::PublicKey> for EthereumSigner {
126+
fn from(x: libsecp256k1::PublicKey) -> Self {
127+
let mut m = [0u8; 64];
128+
m.copy_from_slice(&x.serialize()[1..65]);
129+
let account = H160::from(H256::from_slice(Keccak256::digest(&m).as_slice()));
130+
EthereumSigner(account.into())
131+
}
132+
}
133+
134+
#[cfg(feature = "std")]
135+
impl std::fmt::Display for EthereumSigner {
136+
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
137+
write!(fmt, "ethereum signature: {:?}", H160::from_slice(&self.0))
138+
}
139+
}
140+
141+
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
142+
#[derive(Eq, PartialEq, Clone, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)]
143+
pub struct EthereumSignature(ecdsa::Signature);
144+
145+
impl From<ecdsa::Signature> for EthereumSignature {
146+
fn from(x: ecdsa::Signature) -> Self {
147+
EthereumSignature(x)
148+
}
149+
}
150+
151+
impl sp_runtime::traits::Verify for EthereumSignature {
152+
type Signer = EthereumSigner;
153+
fn verify<L: sp_runtime::traits::Lazy<[u8]>>(&self, mut msg: L, signer: &AccountId20) -> bool {
154+
let mut hashed_message_buffer = [0u8; 32];
155+
hashed_message_buffer.copy_from_slice(Keccak256::digest(msg.get()).as_slice());
156+
match sp_io::crypto::secp256k1_ecdsa_recover(self.0.as_ref(), &hashed_message_buffer) {
157+
Ok(pubkey) => {
158+
// TODO This conversion could use a comment. Why H256 first, then H160?
159+
// TODO actually, there is probably just a better way to go from Keccak digest.
160+
AccountId20(H160::from(H256::from_slice(Keccak256::digest(&pubkey).as_slice())).0) == *signer
161+
}
162+
Err(sp_io::EcdsaVerifyError::BadRS) => {
163+
log::trace!(target: "evm", "Error recovering: Incorrect value of R or S");
164+
false
165+
}
166+
Err(sp_io::EcdsaVerifyError::BadV) => {
167+
log::error!(target: "evm", "Error recovering: Incorrect value of V");
168+
false
169+
}
170+
Err(sp_io::EcdsaVerifyError::BadSignature) => {
171+
log::error!(target: "evm", "Error recovering: Invalid signature");
172+
false
173+
}
174+
}
175+
}
176+
}
177+
178+
#[cfg(test)]
179+
mod tests {
180+
use super::*;
181+
use sp_core::{ecdsa, Pair};
182+
use sp_runtime::traits::IdentifyAccount;
183+
184+
#[test]
185+
fn test_account_derivation_1() {
186+
// Test from https://asecuritysite.com/encryption/ethadd
187+
// This page generates a private ethereum key, a public key and computes an
188+
// address from it. We take those values as reference point to proof that our
189+
// implementation is correct.
190+
let secret_key = hex::decode("502f97299c472b88754accd412b7c9a6062ef3186fba0c0388365e1edec24875").unwrap();
191+
let mut expected_hex_account = [0u8; 20];
192+
hex::decode_to_slice("976f8456e4e2034179b284a23c0e0c8f6d3da50c", &mut expected_hex_account)
193+
.expect("example data is 20 bytes of valid hex");
194+
195+
let public_key = ecdsa::Pair::from_seed_slice(&secret_key).unwrap().public();
196+
let account: EthereumSigner = public_key.into();
197+
let expected_account = AccountId20::from(expected_hex_account);
198+
assert_eq!(account.into_account(), expected_account);
199+
}
200+
#[test]
201+
fn test_account_derivation_2() {
202+
// Test from https://asecuritysite.com/encryption/ethadd
203+
let secret_key = hex::decode("0f02ba4d7f83e59eaa32eae9c3c4d99b68ce76decade21cdab7ecce8f4aef81a").unwrap();
204+
let mut expected_hex_account = [0u8; 20];
205+
hex::decode_to_slice("420e9f260b40af7e49440cead3069f8e82a5230f", &mut expected_hex_account)
206+
.expect("example data is 20 bytes of valid hex");
207+
208+
let public_key = ecdsa::Pair::from_seed_slice(&secret_key).unwrap().public();
209+
let account: EthereumSigner = public_key.into();
210+
let expected_account = AccountId20::from(expected_hex_account);
211+
assert_eq!(account.into_account(), expected_account);
212+
}
213+
}

0 commit comments

Comments
 (0)