diff --git a/Cargo.lock b/Cargo.lock index 0833edfc39..a1a6149842 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1514,6 +1514,8 @@ dependencies = [ "frame-support", "frame-system", "impl-serde", + "libsecp256k1", + "log", "numtoa", "parity-scale-codec", "scale-info", @@ -3744,6 +3746,7 @@ dependencies = [ "sp-block-builder", "sp-consensus-aura", "sp-core", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.13.0)", "sp-genesis-builder", "sp-inherents", "sp-io", diff --git a/Cargo.toml b/Cargo.toml index e9c4ab451a..3d01bbf80a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ thiserror = "1.0.40" apache-avro = { version = "0.14.0", default-features = false } rand = "0.8.5" parking_lot = "0.12.1" +libsecp256k1 = { version = "0.7", default-features = false } # substrate wasm parity-scale-codec = { version = "3.6.12", default-features = false } diff --git a/common/primitives/Cargo.toml b/common/primitives/Cargo.toml index 94b4cdef51..ce363befaf 100644 --- a/common/primitives/Cargo.toml +++ b/common/primitives/Cargo.toml @@ -30,11 +30,14 @@ sp-std = { workspace = true } numtoa = { workspace = true } sp-externalities = { workspace = true } sp-runtime-interface = { workspace = true } +libsecp256k1 = { workspace = true, features = ["hmac"] } +log = "0.4.22" [features] default = ['std'] runtime-benchmarks = [] std = [ + 'libsecp256k1/std', 'parity-scale-codec/std', 'frame-support/std', 'frame-system/std', @@ -47,4 +50,4 @@ std = [ 'sp-externalities/std', 'sp-runtime-interface/std' ] -test = [] \ No newline at end of file +test = [] diff --git a/common/primitives/src/lib.rs b/common/primitives/src/lib.rs index e232c2e9cc..86739bc265 100644 --- a/common/primitives/src/lib.rs +++ b/common/primitives/src/lib.rs @@ -41,3 +41,6 @@ pub mod offchain; #[cfg(feature = "runtime-benchmarks")] /// Benchmarking helper trait pub mod benchmarks; + +/// Signatures +pub mod signatures; diff --git a/common/primitives/src/node.rs b/common/primitives/src/node.rs index f137246997..0813b00b16 100644 --- a/common/primitives/src/node.rs +++ b/common/primitives/src/node.rs @@ -6,6 +6,7 @@ pub use sp_runtime::{ }; use sp_std::{boxed::Box, vec::Vec}; +use crate::signatures::UnifiedSignature; use frame_support::dispatch::DispatchResultWithPostInfo; /// Some way of identifying an account on the chain. We intentionally make it equivalent @@ -31,7 +32,7 @@ pub type BlockNumber = u32; pub type Header = generic::Header; /// Alias to 512-bit hash when used in the context of a transaction signature on the chain. -pub type Signature = MultiSignature; +pub type Signature = UnifiedSignature; /// Index of a transaction in the chain. pub type Index = u32; diff --git a/common/primitives/src/signatures.rs b/common/primitives/src/signatures.rs new file mode 100644 index 0000000000..996121f13a --- /dev/null +++ b/common/primitives/src/signatures.rs @@ -0,0 +1,255 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "serde")] +use frame_support::{Deserialize, Serialize}; +use frame_support::{ + __private::{codec, RuntimeDebug}, + pallet_prelude::{Decode, Encode, MaxEncodedLen, TypeInfo}, +}; +use sp_core::{ + crypto, + crypto::{AccountId32, FromEntropy}, + ecdsa, ed25519, + hexdisplay::HexDisplay, + sr25519, ByteArray, H256, +}; +use scale_info::prelude::format; +use sp_runtime::{ + traits, + traits::{Lazy, Verify}, + MultiSignature, +}; + +/// Signature verify that can work with any known signature types. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Eq, PartialEq, Clone, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)] +pub enum UnifiedSignature { + /// An Ed25519 signature. + Ed25519(ed25519::Signature), + /// An Sr25519 signature. + Sr25519(sr25519::Signature), + /// An ECDSA/SECP256k1 signature. + Ecdsa(ecdsa::Signature), +} + +impl From for UnifiedSignature { + fn from(x: ed25519::Signature) -> Self { + Self::Ed25519(x) + } +} + +impl TryFrom for ed25519::Signature { + type Error = (); + fn try_from(m: UnifiedSignature) -> Result { + if let UnifiedSignature::Ed25519(x) = m { + Ok(x) + } else { + Err(()) + } + } +} + +impl From for UnifiedSignature { + fn from(x: sr25519::Signature) -> Self { + Self::Sr25519(x) + } +} + +impl TryFrom for sr25519::Signature { + type Error = (); + fn try_from(m: UnifiedSignature) -> Result { + if let UnifiedSignature::Sr25519(x) = m { + Ok(x) + } else { + Err(()) + } + } +} + +impl From for UnifiedSignature { + fn from(x: ecdsa::Signature) -> Self { + Self::Ecdsa(x) + } +} + +impl TryFrom for ecdsa::Signature { + type Error = (); + fn try_from(m: UnifiedSignature) -> Result { + if let UnifiedSignature::Ecdsa(x) = m { + Ok(x) + } else { + Err(()) + } + } +} + +impl Verify for UnifiedSignature { + type Signer = UnifiedSigner; + fn verify>(&self, mut msg: L, signer: &AccountId32) -> bool { + match (self, signer) { + (Self::Ed25519(ref sig), who) => match ed25519::Public::from_slice(who.as_ref()) { + Ok(signer) => sig.verify(msg, &signer), + Err(()) => false, + }, + (Self::Sr25519(ref sig), who) => match sr25519::Public::from_slice(who.as_ref()) { + Ok(signer) => sig.verify(msg, &signer), + Err(()) => false, + }, + (Self::Ecdsa(ref sig), who) => { + log::info!(target:"ETHEREUM", "inside ecdsa signature verifier 0x{:?}",HexDisplay::from(&msg.get())); + let m = eth_message(&format!("0x{:?}", HexDisplay::from(&msg.get()))); + log::info!(target:"ETHEREUM", "prefixed hashed 0x{:?}",HexDisplay::from(&m)); + match sp_io::crypto::secp256k1_ecdsa_recover(sig.as_ref(), &m) { + Ok(pubkey) => { + let mut hashed = sp_io::hashing::keccak_256(pubkey.as_ref()); + hashed[..12].fill(0); + log::info!(target:"ETHEREUM", "eth hashed={:?} who={:?}", + HexDisplay::from(&hashed),HexDisplay::from(>::as_ref(who)), + ); + &hashed == >::as_ref(who) + }, + _ => false, + } + }, + } + } +} +fn eth_message(message: &str) -> [u8; 32] { + let prefixed = format!("{}{}{}", "\x19Ethereum Signed Message:\n", message.len(), message); + log::info!(target:"ETHEREUM", "prefixed {:?}",prefixed); + sp_io::hashing::keccak_256(prefixed.as_bytes()) +} +/// Public key for any known crypto algorithm. +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum UnifiedSigner { + /// An Ed25519 identity. + Ed25519(ed25519::Public), + /// An Sr25519 identity. + Sr25519(sr25519::Public), + /// An SECP256k1/ECDSA identity (12 bytes of zeros + 20 bytes of ethereum address). + Ecdsa(ecdsa::Public), +} + +impl FromEntropy for UnifiedSigner { + fn from_entropy(input: &mut impl codec::Input) -> Result { + Ok(match input.read_byte()? % 3 { + 0 => Self::Ed25519(FromEntropy::from_entropy(input)?), + 1 => Self::Sr25519(FromEntropy::from_entropy(input)?), + 2.. => Self::Ecdsa(FromEntropy::from_entropy(input)?), + }) + } +} + +/// NOTE: This implementations is required by `SimpleAddressDeterminer`, +/// we convert the hash into some AccountId, it's fine to use any scheme. +impl> crypto::UncheckedFrom for UnifiedSigner { + fn unchecked_from(x: T) -> Self { + ed25519::Public::unchecked_from(x.into()).into() + } +} + +impl AsRef<[u8]> for UnifiedSigner { + fn as_ref(&self) -> &[u8] { + match *self { + Self::Ed25519(ref who) => who.as_ref(), + Self::Sr25519(ref who) => who.as_ref(), + Self::Ecdsa(ref who) => who.as_ref(), + } + } +} + +impl traits::IdentifyAccount for UnifiedSigner { + type AccountId = AccountId32; + fn into_account(self) -> AccountId32 { + match self { + Self::Ed25519(who) => <[u8; 32]>::from(who).into(), + Self::Sr25519(who) => <[u8; 32]>::from(who).into(), + Self::Ecdsa(who) => { + log::info!(target:"ETHEREUM", "inside ecdsa into_account"); + let decompressed = libsecp256k1::PublicKey::parse_slice( + who.as_ref(), + Some(libsecp256k1::PublicKeyFormat::Compressed), + ) + .expect("Wrong compressed public key provided") + .serialize(); + let mut m = [0u8; 64]; + m.copy_from_slice(&decompressed[1..65]); + let mut hashed = sp_io::hashing::keccak_256(m.as_ref()); + hashed[..12].fill(0); + hashed.into() + }, + } + } +} + +impl From for UnifiedSigner { + fn from(x: ed25519::Public) -> Self { + Self::Ed25519(x) + } +} + +impl TryFrom for ed25519::Public { + type Error = (); + fn try_from(m: UnifiedSigner) -> Result { + if let UnifiedSigner::Ed25519(x) = m { + Ok(x) + } else { + Err(()) + } + } +} + +impl From for UnifiedSigner { + fn from(x: sr25519::Public) -> Self { + Self::Sr25519(x) + } +} + +impl TryFrom for sr25519::Public { + type Error = (); + fn try_from(m: UnifiedSigner) -> Result { + if let UnifiedSigner::Sr25519(x) = m { + Ok(x) + } else { + Err(()) + } + } +} + +impl From for UnifiedSigner { + fn from(x: ecdsa::Public) -> Self { + Self::Ecdsa(x) + } +} + +impl TryFrom for ecdsa::Public { + type Error = (); + fn try_from(m: UnifiedSigner) -> Result { + if let UnifiedSigner::Ecdsa(x) = m { + Ok(x) + } else { + Err(()) + } + } +} + +#[cfg(feature = "std")] +impl std::fmt::Display for UnifiedSigner { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + Self::Ed25519(ref who) => write!(fmt, "ed25519: {}", who), + Self::Sr25519(ref who) => write!(fmt, "sr25519: {}", who), + Self::Ecdsa(ref who) => write!(fmt, "ecdsa: {}", who), + } + } +} + +impl Into for MultiSignature { + fn into(self: MultiSignature) -> UnifiedSignature { + match self { + MultiSignature::Ed25519(who) => UnifiedSignature::Ed25519(who), + MultiSignature::Sr25519(who) => UnifiedSignature::Sr25519(who), + MultiSignature::Ecdsa(who) => UnifiedSignature::Ecdsa(who), + } + } +} diff --git a/e2e/.mocharc.json b/e2e/.mocharc.json index 1beef2d557..705e9226c5 100644 --- a/e2e/.mocharc.json +++ b/e2e/.mocharc.json @@ -4,6 +4,6 @@ "parallel": true, "require": ["scaffolding/globalHooks.ts", "scaffolding/rootHooks.ts", "scaffolding/extrinsicHelpers.ts"], "import": "tsx/esm", - "spec": ["./{,!(node_modules|load-tests)/**}/*.test.ts"], + "spec": ["./passkey/*.test.ts"], "timeout": 20000 } diff --git a/e2e/package-lock.json b/e2e/package-lock.json index c54baff74a..e523019334 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -2196,11 +2196,12 @@ "node_modules/@frequency-chain/api-augment": { "version": "0.0.0", "resolved": "file:../js/api-augment/dist/frequency-chain-api-augment-0.0.0.tgz", - "integrity": "sha512-pvf6V+LO0zFtPbqDEXrOxeWEu1+NvKhFEcZdYIiIZcUM9MXtyt8KC1Zf8uzBrmuG2sxBeuMCTQ3ODkviCi+JKg==", + "integrity": "sha512-S7XXfydWYUDOkZ9ua06PyDYVx+ymgcyWdwa4kI7BVnYSXQIOIvseCLMJrP1nwfvLtngqS4bVb3U5qPWBhSJ/9g==", + "license": "Apache-2.0", "dependencies": { - "@polkadot/api": "^14.2.1", - "@polkadot/rpc-provider": "^14.2.1", - "@polkadot/types": "^14.2.1", + "@polkadot/api": "^14.2.2", + "@polkadot/rpc-provider": "^14.2.2", + "@polkadot/types": "^14.2.2", "globals": "^15.11.0" } }, @@ -3433,7 +3434,7 @@ "@polkadot-api/utils": "0.1.2" } }, - "node_modules/@polkadot-api/merkleize-metadata/node_modules/@polkadot-api/metadata-builders": { + "node_modules/@polkadot-api/metadata-builders": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.9.1.tgz", "integrity": "sha512-yZPm9KKn7QydbjMQMzhKHekDuQSdSZXYdCyqGt74HSNz9DdJSdpFNwHv0p+vmp+9QDlVsKK7nbUTjYxLZT4vCA==", @@ -3442,32 +3443,6 @@ "@polkadot-api/utils": "0.1.2" } }, - "node_modules/@polkadot-api/merkleize-metadata/node_modules/@polkadot-api/substrate-bindings": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.9.3.tgz", - "integrity": "sha512-ygaZo8+xssTdb6lj9mA8RTlanDfyd0iMex3aBFC1IzOSm08XUWdRpuSLRuerFCimLzKuz/oBOTKdqBFGb7ybUQ==", - "dependencies": { - "@noble/hashes": "^1.4.0", - "@polkadot-api/utils": "0.1.2", - "@scure/base": "^1.1.7", - "scale-ts": "^1.6.1" - } - }, - "node_modules/@polkadot-api/merkleize-metadata/node_modules/@polkadot-api/utils": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.1.2.tgz", - "integrity": "sha512-yhs5k2a8N1SBJcz7EthZoazzLQUkZxbf+0271Xzu42C5AEM9K9uFLbsB+ojzHEM72O5X8lPtSwGKNmS7WQyDyg==" - }, - "node_modules/@polkadot-api/metadata-builders": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.3.2.tgz", - "integrity": "sha512-TKpfoT6vTb+513KDzMBTfCb/ORdgRnsS3TDFpOhAhZ08ikvK+hjHMt5plPiAX/OWkm1Wc9I3+K6W0hX5Ab7MVg==", - "optional": true, - "dependencies": { - "@polkadot-api/substrate-bindings": "0.6.0", - "@polkadot-api/utils": "0.1.0" - } - }, "node_modules/@polkadot-api/observable-client": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.3.2.tgz", @@ -3483,7 +3458,17 @@ "rxjs": ">=7.8.0" } }, - "node_modules/@polkadot-api/substrate-bindings": { + "node_modules/@polkadot-api/observable-client/node_modules/@polkadot-api/metadata-builders": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.3.2.tgz", + "integrity": "sha512-TKpfoT6vTb+513KDzMBTfCb/ORdgRnsS3TDFpOhAhZ08ikvK+hjHMt5plPiAX/OWkm1Wc9I3+K6W0hX5Ab7MVg==", + "optional": true, + "dependencies": { + "@polkadot-api/substrate-bindings": "0.6.0", + "@polkadot-api/utils": "0.1.0" + } + }, + "node_modules/@polkadot-api/observable-client/node_modules/@polkadot-api/substrate-bindings": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.6.0.tgz", "integrity": "sha512-lGuhE74NA1/PqdN7fKFdE5C1gNYX357j1tWzdlPXI0kQ7h3kN0zfxNOpPUN7dIrPcOFZ6C0tRRVrBylXkI6xPw==", @@ -3495,6 +3480,23 @@ "scale-ts": "^1.6.0" } }, + "node_modules/@polkadot-api/observable-client/node_modules/@polkadot-api/utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.1.0.tgz", + "integrity": "sha512-MXzWZeuGxKizPx2Xf/47wx9sr/uxKw39bVJUptTJdsaQn/TGq+z310mHzf1RCGvC1diHM8f593KrnDgc9oNbJA==", + "optional": true + }, + "node_modules/@polkadot-api/substrate-bindings": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.9.3.tgz", + "integrity": "sha512-ygaZo8+xssTdb6lj9mA8RTlanDfyd0iMex3aBFC1IzOSm08XUWdRpuSLRuerFCimLzKuz/oBOTKdqBFGb7ybUQ==", + "dependencies": { + "@noble/hashes": "^1.4.0", + "@polkadot-api/utils": "0.1.2", + "@scure/base": "^1.1.7", + "scale-ts": "^1.6.1" + } + }, "node_modules/@polkadot-api/substrate-client": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.1.4.tgz", @@ -3505,12 +3507,17 @@ "@polkadot-api/utils": "0.1.0" } }, - "node_modules/@polkadot-api/utils": { + "node_modules/@polkadot-api/substrate-client/node_modules/@polkadot-api/utils": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.1.0.tgz", "integrity": "sha512-MXzWZeuGxKizPx2Xf/47wx9sr/uxKw39bVJUptTJdsaQn/TGq+z310mHzf1RCGvC1diHM8f593KrnDgc9oNbJA==", "optional": true }, + "node_modules/@polkadot-api/utils": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.1.2.tgz", + "integrity": "sha512-yhs5k2a8N1SBJcz7EthZoazzLQUkZxbf+0271Xzu42C5AEM9K9uFLbsB+ojzHEM72O5X8lPtSwGKNmS7WQyDyg==" + }, "node_modules/@polkadot/api": { "version": "14.2.2", "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-14.2.2.tgz", @@ -5114,9 +5121,9 @@ } }, "node_modules/@scure/base": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.8.tgz", - "integrity": "sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", "funding": { "url": "https://paulmillr.com/funding/" } @@ -5215,15 +5222,15 @@ } }, "node_modules/@substrate/connect-extension-protocol": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-2.1.0.tgz", - "integrity": "sha512-Wz5Cbn6S6P4vWfHyrsnPW7g15IAViMaXCk+jYkq4nNEMmzPtTKIEbtxrdDMBKrouOFtYKKp0znx5mh9KTCNqlA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-2.2.0.tgz", + "integrity": "sha512-8b5bN/jo6qD4vcnoWr3T+Nn2u1XLRkJTsEt8b9iGvPPZ1cFcPCVQVpn3lP3U3WqbuSLiVkh0CjX5TW+aCUAi3g==", "optional": true }, "node_modules/@substrate/connect-known-chains": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@substrate/connect-known-chains/-/connect-known-chains-1.4.0.tgz", - "integrity": "sha512-p/mxn1GobtxJ+7xbIkUH4+/njH1neRHHKTcSGHNOC78Cf6Ch1Xzp082+nMjOBDLQLmraK5PF74AKV3WXHGuALw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@substrate/connect-known-chains/-/connect-known-chains-1.6.0.tgz", + "integrity": "sha512-ImPIaaQjSs07qI+gfP6sV/HnupexqgPnyicsPax3Pc6mqDp2HUNMDVdaoWjR84yPbgN8+un/P4KOEb5g4wqHSg==", "optional": true }, "node_modules/@substrate/light-client-extension-helpers": { @@ -11987,9 +11994,9 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz", + "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", "dev": true, "engines": { "node": ">=16" diff --git a/e2e/passkey/passkeyProxy.ecdsa.test.ts b/e2e/passkey/passkeyProxy.ecdsa.test.ts new file mode 100644 index 0000000000..01493f8e3e --- /dev/null +++ b/e2e/passkey/passkeyProxy.ecdsa.test.ts @@ -0,0 +1,114 @@ +import '@frequency-chain/api-augment'; +import assert from 'assert'; +import { + createAndFundKeypair, fundKeypair, + getBlockNumber, + getNonce +} from '../scaffolding/helpers'; +import { KeyringPair } from '@polkadot/keyring/types'; +import {Extrinsic, ExtrinsicHelper} from '../scaffolding/extrinsicHelpers'; +import { getFundingSource } from '../scaffolding/funding'; +import {hexToU8a, u8aToHex, u8aWrapBytes} from '@polkadot/util'; +import { createPassKeyAndSignAccount, createPassKeyCall, createPasskeyPayload } from '../scaffolding/P256'; +import { + getConvertedEthereumPublicKey, + getEthereumStyleSigner, getEthereumStyleSignerTest, + getKeyringPairFromSecp256k1PrivateKey, + getUnifiedAddress +} from "../scaffolding/ethereum"; +import {Keyring} from "@polkadot/api"; +const fundingSource = getFundingSource('passkey-proxy'); + +describe('Passkey Pallet Tests for ECDSA keys', function () { + describe('proxy basic tests for ECDSA keys', function () { + let fundedKeysSr25519: KeyringPair; + let fundedEthereumKeys: KeyringPair; + let ethereumKeysFromPrivateKey: KeyringPair; + let receiverEthereumKeys: KeyringPair; + + before(async function () { + // fundedKeysSr25519 = await createAndFundKeypair(fundingSource, 300_000_000n); + const keyring = new Keyring({ type: 'sr25519'}); + fundedKeysSr25519 = keyring.addFromUri('//Eve'); + + // fundedEthereumKeys = await createAndFundKeypair(fundingSource, 300_000_000n, undefined, undefined, 'ethereum'); + ethereumKeysFromPrivateKey = getKeyringPairFromSecp256k1PrivateKey(hexToU8a('0x4fa1fa06b8ad980d739473280ab1c362c425fa5883dd661a5d90e57e6d2969ce')); + // receiverEthereumKeys = await createAndFundKeypair(fundingSource, undefined, undefined, undefined, 'ethereum'); + // console.log(`fundedEthereumKeys ${JSON.stringify(fundedEthereumKeys.toJson())} ${u8aToHex(fundedEthereumKeys.publicKey)} ${u8aToHex(fundedEthereumKeys.addressRaw)} ${getUnifiedAddress(fundedEthereumKeys)}`); + // console.log(`receiverEthereumKeys ${JSON.stringify(receiverEthereumKeys.toJson())} ${u8aToHex(receiverEthereumKeys.publicKey)} ${u8aToHex(receiverEthereumKeys.addressRaw)} ${getUnifiedAddress(receiverEthereumKeys)}`); + }); + + // it ('should transfer from sr25519 to ethereum style key', async function () { + // const extrinsic = new Extrinsic( + // () => ExtrinsicHelper.api.tx.balances.transferKeepAlive(getUnifiedAddress(receiverEthereumKeys), 33_000_000n), + // fundedKeysSr25519, + // ExtrinsicHelper.api.events.balances.Transfer + // ); + // const {target} = await extrinsic.signAndSend(); + // assert.notEqual(target, undefined, 'should have returned Transfer event'); + // }) + + it ('should transfer from metamask injected signature to sr25519', async function () { + const unifiedAddress = getUnifiedAddress(ethereumKeysFromPrivateKey); + const extrinsic = ExtrinsicHelper.apiPromise.tx.balances.transferKeepAlive(getUnifiedAddress(fundedKeysSr25519), 33_000_000n); + await extrinsic.signAndSend(unifiedAddress, { signer: getEthereumStyleSignerTest( + "0x0a0300e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e0229de0715000000008300000001000000c7b499ff539de473280e53ab077e100f7152a76f4d24025e9b56bebf74024ffd7cc514216c7605c9965a8740d74c4e8fd70066603ba54edc03a55a1e546e3bde00", + "0x0c8dcadc5671485638b43a0f953ecd0e0eb6fa16213a6b605a7290e1df8b49db365c3f7c82eb9d0b857e97ad4307b0f423cae19cfd9a4b602ba8f06335cad3cc1c" + ) }, (status) => { + console.log(status.toHuman()); + }); + }) + + // it ('should transfer from an ethereum key created from private key to sr25519', async function () { + // const unifiedAddress = getUnifiedAddress(ethereumKeysFromPrivateKey); + // await fundKeypair(fundedKeysSr25519, ethereumKeysFromPrivateKey, 100_000_000n); + // const extrinsic = ExtrinsicHelper.apiPromise.tx.balances.transferKeepAlive(getUnifiedAddress(fundedKeysSr25519), 44_000_000n); + // await extrinsic.signAndSend(unifiedAddress, { signer: getEthereumStyleSigner(ethereumKeysFromPrivateKey) }, (status) => { + // console.log(status.toHuman()); + // }); + // }) + + // it('should transfer via passkeys with root sr25519 key into an ethereum style account', async function () { + // const initialReceiverBalance = await ExtrinsicHelper.getAccountInfo(getUnifiedAddress(receiverEthereumKeys)); + // const accountPKey = fundedKeysSr25519.publicKey; + // const nonce = await getNonce(fundedKeysSr25519); + // const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive(getUnifiedAddress(receiverEthereumKeys), 55_000_000n); + // const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); + // const accountSignature = fundedKeysSr25519.sign(u8aWrapBytes(passKeyPublicKey)); + // const passkeyCall = await createPassKeyCall(accountPKey, nonce, { Sr25519: accountSignature} as MultiSignature, transferCalls); + // const passkeyPayload = await createPasskeyPayload(passKeyPrivateKey, passKeyPublicKey, passkeyCall, false); + // const passkeyProxy = ExtrinsicHelper.executePassKeyProxy(fundedKeysSr25519, passkeyPayload); + // assert.doesNotReject(passkeyProxy.fundAndSendUnsigned(fundingSource)); + // await ExtrinsicHelper.waitForFinalization((await getBlockNumber()) + 2); + // const receiverBalance = await ExtrinsicHelper.getAccountInfo(getUnifiedAddress(receiverEthereumKeys)); + // // adding some delay before fetching the nonce to ensure it is updated + // await new Promise((resolve) => setTimeout(resolve, 1000)); + // const nonceAfter = (await ExtrinsicHelper.getAccountInfo(getUnifiedAddress(fundedKeysSr25519))).nonce.toNumber(); + // assert.equal(nonce + 1, nonceAfter); + // assert(receiverBalance.data.free.toBigInt() - initialReceiverBalance.data.free.toBigInt() > 0n); + // }); + // + // it ('should transfer via passkeys with root ethereum style key into another one', async function () { + // const initialReceiverBalance = await ExtrinsicHelper.getAccountInfo(getUnifiedAddress(receiverEthereumKeys)); + // const accountPKey = getConvertedEthereumPublicKey(fundedEthereumKeys); + // console.log(`accountPKey ${u8aToHex(accountPKey)}`); + // const nonce = await getNonce(fundedEthereumKeys); + // const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive(getUnifiedAddress(receiverEthereumKeys), 66_000_000n); + // const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); + // const accountSignature = fundedEthereumKeys.sign(u8aWrapBytes(passKeyPublicKey)); + // console.log(`accountSignature ${u8aToHex(accountSignature)}`); + // const passkeyCall = await createPassKeyCall(accountPKey, nonce, { Ecdsa: accountSignature} as MultiSignature, transferCalls); + // const passkeyPayload = await createPasskeyPayload(passKeyPrivateKey, passKeyPublicKey, passkeyCall, false); + // const passkeyProxy = ExtrinsicHelper.executePassKeyProxy(fundingSource, passkeyPayload); + // assert.doesNotReject(passkeyProxy.sendUnsigned()); + // await ExtrinsicHelper.waitForFinalization((await getBlockNumber()) + 2); + // const receiverBalance = await ExtrinsicHelper.getAccountInfo(getUnifiedAddress(receiverEthereumKeys)); + // // adding some delay before fetching the nonce to ensure it is updated + // await new Promise((resolve) => setTimeout(resolve, 1000)); + // const nonceAfter = (await ExtrinsicHelper.getAccountInfo(getUnifiedAddress(fundedEthereumKeys))).nonce.toNumber(); + // assert.equal(nonce + 1, nonceAfter); + // assert(receiverBalance.data.free.toBigInt() - initialReceiverBalance.data.free.toBigInt() > 0n); + // }) + }); +}); + diff --git a/e2e/passkey/passkeyProxy.test.ts b/e2e/passkey/passkeyProxy.test.ts index 8ce4236217..718fed147e 100644 --- a/e2e/passkey/passkeyProxy.test.ts +++ b/e2e/passkey/passkeyProxy.test.ts @@ -6,6 +6,7 @@ import { ExtrinsicHelper } from '../scaffolding/extrinsicHelpers'; import { getFundingSource } from '../scaffolding/funding'; import { u8aWrapBytes } from '@polkadot/util'; import { createPassKeyAndSignAccount, createPassKeyCall, createPasskeyPayload } from '../scaffolding/P256'; +import { MultiSignature } from "@polkadot/types/interfaces"; const fundingSource = getFundingSource('passkey-proxy'); describe('Passkey Pallet Tests', function () { @@ -25,7 +26,7 @@ describe('Passkey Pallet Tests', function () { const remarksCalls = ExtrinsicHelper.api.tx.system.remark('passkey-test'); const { passKeyPrivateKey, passKeyPublicKey, passkeySignature } = createPassKeyAndSignAccount(accountPKey); const accountSignature = fundedKeys.sign(u8aWrapBytes(passKeyPublicKey)); - const passkeyCall = await createPassKeyCall(accountPKey, nonce, accountSignature, remarksCalls); + const passkeyCall = await createPassKeyCall(accountPKey, nonce, { Sr25519: accountSignature} as MultiSignature, remarksCalls); const passkeyPayload = await createPasskeyPayload(passKeyPrivateKey, passKeyPublicKey, passkeyCall, false); const passkeyProxy = ExtrinsicHelper.executePassKeyProxy(fundedKeys, passkeyPayload); @@ -38,7 +39,7 @@ describe('Passkey Pallet Tests', function () { const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive(receiverKeys.publicKey, 0n); const { passKeyPrivateKey, passKeyPublicKey, passkeySignature } = createPassKeyAndSignAccount(accountPKey); const accountSignature = fundedKeys.sign('badPasskeyPublicKey'); - const passkeyCall = await createPassKeyCall(accountPKey, nonce, accountSignature, transferCalls); + const passkeyCall = await createPassKeyCall(accountPKey, nonce, { Sr25519: accountSignature} as MultiSignature, transferCalls); const passkeyPayload = await createPasskeyPayload(passKeyPrivateKey, passKeyPublicKey, passkeyCall, false); const passkeyProxy = ExtrinsicHelper.executePassKeyProxy(fundedKeys, passkeyPayload); @@ -51,7 +52,7 @@ describe('Passkey Pallet Tests', function () { const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive(receiverKeys.publicKey, 0n); const { passKeyPrivateKey, passKeyPublicKey, passkeySignature } = createPassKeyAndSignAccount(accountPKey); const accountSignature = fundedKeys.sign(u8aWrapBytes(passKeyPublicKey)); - const passkeyCall = await createPassKeyCall(accountPKey, nonce, accountSignature, transferCalls); + const passkeyCall = await createPassKeyCall(accountPKey, nonce, { Sr25519: accountSignature} as MultiSignature, transferCalls); const passkeyPayload = await createPasskeyPayload(passKeyPrivateKey, passKeyPublicKey, passkeyCall, true); const passkeyProxy = ExtrinsicHelper.executePassKeyProxy(fundedKeys, passkeyPayload); @@ -64,7 +65,7 @@ describe('Passkey Pallet Tests', function () { const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive(receiverKeys.publicKey, 100_000_000n); const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); const accountSignature = fundedKeys.sign(u8aWrapBytes(passKeyPublicKey)); - const passkeyCall = await createPassKeyCall(accountPKey, nonce, accountSignature, transferCalls); + const passkeyCall = await createPassKeyCall(accountPKey, nonce, { Sr25519: accountSignature} as MultiSignature, transferCalls); const passkeyPayload = await createPasskeyPayload(passKeyPrivateKey, passKeyPublicKey, passkeyCall, false); const passkeyProxy = ExtrinsicHelper.executePassKeyProxy(fundedKeys, passkeyPayload); assert.doesNotReject(passkeyProxy.fundAndSendUnsigned(fundingSource)); diff --git a/e2e/scaffolding/P256.ts b/e2e/scaffolding/P256.ts index 7c97794f93..69d6a7f259 100644 --- a/e2e/scaffolding/P256.ts +++ b/e2e/scaffolding/P256.ts @@ -5,6 +5,7 @@ import { ISubmittableResult } from '@polkadot/types/types'; import { u8aWrapBytes } from '@polkadot/util'; import { ExtrinsicHelper } from './extrinsicHelpers'; import { sha256 } from '@noble/hashes/sha256'; +import {MultiSignature} from "@polkadot/types/interfaces"; export function createPassKeyAndSignAccount(accountPKey: Uint8Array) { const passKeyPrivateKey = secp256r1.utils.randomPrivateKey(); @@ -16,16 +17,14 @@ export function createPassKeyAndSignAccount(accountPKey: Uint8Array) { export async function createPassKeyCall( accountPKey: Uint8Array, nonce: number, - accountSignature: Uint8Array, + accountSignature: MultiSignature, call: SubmittableExtrinsic<'rxjs', ISubmittableResult> ) { const ext_call_type = ExtrinsicHelper.api.registry.createType('Call', call); const passkeyCall = { accountId: accountPKey, accountNonce: nonce, - accountOwnershipProof: { - Sr25519: accountSignature, - }, + accountOwnershipProof: accountSignature, call: ext_call_type, }; diff --git a/e2e/scaffolding/autoNonce.ts b/e2e/scaffolding/autoNonce.ts index 562a7988f5..e158effcec 100644 --- a/e2e/scaffolding/autoNonce.ts +++ b/e2e/scaffolding/autoNonce.ts @@ -7,26 +7,27 @@ import type { KeyringPair } from '@polkadot/keyring/types'; import { ExtrinsicHelper } from './extrinsicHelpers'; +import {getUnifiedAddress} from "./ethereum"; export type AutoNonce = number | 'auto' | 'current'; const nonceCache = new Map(); const getNonce = async (keys: KeyringPair) => { - return (await ExtrinsicHelper.getAccountInfo(keys.address)).nonce.toNumber(); + return (await ExtrinsicHelper.getAccountInfo(getUnifiedAddress(keys))).nonce.toNumber(); }; const reset = (keys: KeyringPair) => { - nonceCache.delete(keys.address); + nonceCache.delete(getUnifiedAddress(keys)); }; const current = async (keys: KeyringPair): Promise => { - return nonceCache.get(keys.address) || (await getNonce(keys)); + return nonceCache.get(getUnifiedAddress(keys)) || (await getNonce(keys)); }; const increment = async (keys: KeyringPair) => { const nonce = await current(keys); - nonceCache.set(keys.address, nonce + 1); + nonceCache.set(getUnifiedAddress(keys), nonce + 1); return nonce; }; @@ -46,7 +47,7 @@ const auto = (keys: KeyringPair, inputNonce: AutoNonce = 'auto'): Promise => { + console.log(`raw_payload: ${payload.data}`); + const sig = ethereumPair.sign(wrapCustomEthereumTags(payload.data)); + const prefixedSignature = new Uint8Array(sig.length + 1); + prefixedSignature[0]=2; + prefixedSignature.set(sig, 1); + const hex = u8aToHex(prefixedSignature); + return { + signature: hex, + } as SignerResult; + }, + } +} + +export function getEthereumStyleSignerTest(expectedPayloadHex: string, injectedSignatureHex: string) : Signer { + return { + signRaw: async (payload): Promise => { + console.log(`raw_payload: ${payload.data}`); + assert.equal(payload.data, expectedPayloadHex); + const sig = hexToU8a(injectedSignatureHex); + const prefixedSignature = new Uint8Array(sig.length + 1); + prefixedSignature[0]=2; + prefixedSignature.set(sig, 1); + const hex = u8aToHex(prefixedSignature); + return { + signature: hex, + } as SignerResult; + }, + } +} + +function wrapCustomEthereumTags(hexPayload: string) : Uint8Array { + // wrapping in frequency tags to show this is a Frequency related payload + const frequencyWrapped = `${hexPayload.toLowerCase()}` + // prefixing with the EIP-191 for personal_sign messages (this gets wrapped automatically in metamask) + const wrapped = `\x19Ethereum Signed Message:\n${frequencyWrapped.length}${frequencyWrapped}` + console.log(`wrapped ${wrapped}`); + const buffer = Buffer.from(wrapped, "utf-8"); + return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.length); +} + +export function getAccountId20MultiAddress(pair: KeyringPair): MultiAddress { + const etheAddress = ethereumEncode(pair.publicKey); + let ethAddress20 = Array.from(hexToU8a(etheAddress)); + return { + Address20: ethAddress20 + } as MultiAddress; +} + +export function getConvertedEthereumPublicKey(pair: KeyringPair): Uint8Array { + const publicKeyBytes = hexToU8a(ethereumEncode(pair.publicKey)); + const result = new Uint8Array(32); + result.fill(0, 0, 12); + result.set(publicKeyBytes, 12); + return result; +} + +function getConvertedEthereumAccount( + accountId20Hex: string +) : string { + const addressBytes = hexToU8a(accountId20Hex); + const result = new Uint8Array(32); + result.fill(0, 0, 12); + result.set(addressBytes, 12); + return encodeAddress(result); +} + +/** + * + * @param secretKey of secp256k1 keypair exported from any wallet (should be 32 bytes) + */ +export function getKeyringPairFromSecp256k1PrivateKey(secretKey: Uint8Array): KeyringPair { + const publicKey = secp256k1.getPublicKey(secretKey, true); + const keypair: Keypair = { + secretKey, + publicKey + }; + const keyring = new Keyring({ type: 'ethereum' }); + return keyring.addFromPair(keypair, undefined, 'ethereum' ) +} diff --git a/e2e/scaffolding/extrinsicHelpers.ts b/e2e/scaffolding/extrinsicHelpers.ts index ad003c670c..5bd2127035 100644 --- a/e2e/scaffolding/extrinsicHelpers.ts +++ b/e2e/scaffolding/extrinsicHelpers.ts @@ -4,7 +4,7 @@ import { ApiTypes, AugmentedEvent, SubmittableExtrinsic, SignerOptions } from '@ import { KeyringPair } from '@polkadot/keyring/types'; import { Compact, u128, u16, u32, u64, Vec, Option, Bool } from '@polkadot/types'; import { FrameSystemAccountInfo, PalletPasskeyPasskeyPayload, SpRuntimeDispatchError } from '@polkadot/types/lookup'; -import { AnyJson, AnyNumber, AnyTuple, Codec, IEvent, ISubmittableResult } from '@polkadot/types/types'; +import {AnyJson, AnyNumber, AnyTuple, Codec, IEvent, ISubmittableResult, SignerPayloadRaw} from '@polkadot/types/types'; import { firstValueFrom, filter, map, pipe, tap } from 'rxjs'; import { getBlockNumber, getExistentialDeposit, getFinalizedBlockNumber, log, Sr25519Signature } from './helpers'; import autoNonce, { AutoNonce } from './autoNonce'; @@ -24,6 +24,7 @@ import { u8aToHex } from '@polkadot/util/u8a/toHex'; import { u8aWrapBytes } from '@polkadot/util'; import type { AccountId32, Call, H256 } from '@polkadot/types/interfaces/runtime'; import { hasRelayChain } from './env'; +import {getUnifiedAddress} from "./ethereum"; export interface ReleaseSchedule { start: number; @@ -267,6 +268,20 @@ export class Extrinsic | AnyNumber) { return new Extrinsic( - () => ExtrinsicHelper.api.tx.balances.transferKeepAlive(dest.address, amount), + () => ExtrinsicHelper.api.tx.balances.transferKeepAlive(getUnifiedAddress(dest), amount), source, ExtrinsicHelper.api.events.balances.Transfer ); diff --git a/e2e/scaffolding/funding.ts b/e2e/scaffolding/funding.ts index 3dcfb0dc32..297e2aa00a 100644 --- a/e2e/scaffolding/funding.ts +++ b/e2e/scaffolding/funding.ts @@ -9,33 +9,33 @@ const keyring = new Keyring({ type: 'sr25519' }); // New ones should be added to support additional parallel testing // tldr: Each test file should have a separate funding source listed below export const fundingSources = [ - 'capacity-replenishment', - 'capacity-provider-boost', - 'capacity-list-unclaimed-rewards', - 'capacity-change-staking-target', - 'capacity-rpcs', - 'capacity-staking', - 'capacity-transactions', - 'capacity-transactions-batch', - 'capacity-transactions-fail', - 'capacity-unstaking', - 'check-metadata-hash', - 'frequency-misc', - 'handles', - 'load-signature-registry', - 'messages-add-ipfs', - 'misc-util-batch', - 'msa-create-msa', - 'msa-key-management', + // 'capacity-replenishment', + // 'capacity-provider-boost', + // 'capacity-list-unclaimed-rewards', + // 'capacity-change-staking-target', + // 'capacity-rpcs', + // 'capacity-staking', + // 'capacity-transactions', + // 'capacity-transactions-batch', + // 'capacity-transactions-fail', + // 'capacity-unstaking', + // 'check-metadata-hash', + // 'frequency-misc', + // 'handles', + // 'load-signature-registry', + // 'messages-add-ipfs', + // 'misc-util-batch', + // 'msa-create-msa', + // 'msa-key-management', 'passkey-proxy', - 'proxy-pallet', - 'scenarios-grant-delegation', - 'schemas-create', - 'stateful-storage-handle-itemized', - 'stateful-storage-handle-paginated', - 'stateful-storage-handle-sig-req', - 'sudo-transactions', - 'time-release', + // 'proxy-pallet', + // 'scenarios-grant-delegation', + // 'schemas-create', + // 'stateful-storage-handle-itemized', + // 'stateful-storage-handle-paginated', + // 'stateful-storage-handle-sig-req', + // 'sudo-transactions', + // 'time-release', ] as const; // Get the correct key for this Funding Source diff --git a/e2e/scaffolding/globalHooks.ts b/e2e/scaffolding/globalHooks.ts index 33314d4c7f..d8a18e88ed 100644 --- a/e2e/scaffolding/globalHooks.ts +++ b/e2e/scaffolding/globalHooks.ts @@ -35,9 +35,9 @@ async function devSudoActions() { } function drainAllSources() { - const keys = fundingSources.map((source) => getFundingSource(source)); - const root = getRootFundingSource().keys; - return drainKeys(keys, root.address); + // const keys = fundingSources.map((source) => getFundingSource(source)); + // const root = getRootFundingSource().keys; + // return drainKeys(keys, root.address); } export async function mochaGlobalSetup() { diff --git a/e2e/scaffolding/helpers.ts b/e2e/scaffolding/helpers.ts index a87a3e6be0..5b745c36fb 100644 --- a/e2e/scaffolding/helpers.ts +++ b/e2e/scaffolding/helpers.ts @@ -3,8 +3,8 @@ import { KeyringPair } from '@polkadot/keyring/types'; import { u16, u32, u64, Option, Bytes } from '@polkadot/types'; import type { FrameSystemAccountInfo, PalletCapacityCapacityDetails } from '@polkadot/types/lookup'; import { Codec } from '@polkadot/types/types'; -import { u8aToHex, u8aWrapBytes } from '@polkadot/util'; -import { mnemonicGenerate } from '@polkadot/util-crypto'; +import {hexToU8a, u8aToHex, u8aWrapBytes} from '@polkadot/util'; +import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto'; import { verbose, getGraphChangeSchema, @@ -37,6 +37,8 @@ import assert from 'assert'; import { AVRO_GRAPH_CHANGE } from '../schemas/fixtures/avroGraphChangeSchemaType'; import { PARQUET_BROADCAST } from '../schemas/fixtures/parquetBroadcastSchemaType'; import { AVRO_CHAT_MESSAGE } from '../stateful-pallet-storage/fixtures/itemizedSchemaType'; +import type { KeypairType } from "@polkadot/util-crypto/types"; +import { getUnifiedAddress } from "./ethereum"; export interface Account { uri: string; @@ -237,12 +239,12 @@ export function drainFundedKeys(dest: string) { return drainKeys([...createdKeys.values()], dest); } -export function createKeys(name: string = 'first pair'): KeyringPair { +export function createKeys(name: string = 'first pair', keyType: KeypairType = 'sr25519'): KeyringPair { const mnemonic = mnemonicGenerate(); // create & add the pair to the keyring with the type and some additional // metadata specified - const keyring = new Keyring({ type: 'sr25519' }); - const keypair = keyring.addFromUri(mnemonic, { name }, 'sr25519'); + const keyring = new Keyring({ type: keyType }); + const keypair = keyring.addFromUri(mnemonic, { name }, keyType); createdKeys.set(keypair.address, keypair); return keypair; @@ -284,10 +286,10 @@ export async function createAndFundKeypair( source: KeyringPair, amount?: bigint, keyName?: string, - nonce?: number + nonce?: number, + keyType: KeypairType = 'sr25519', ): Promise { - const keypair = createKeys(keyName); - + const keypair = createKeys(keyName, keyType); await fundKeypair(source, keypair, amount || (await getExistentialDeposit()), nonce); log('Funded', `Name: ${keyName || 'None provided'}`, `Address: ${keypair.address}`); @@ -618,7 +620,7 @@ export async function getCapacity(providerId: u64): Promise { - const nonce = await ExtrinsicHelper.apiPromise.call.accountNonceApi.accountNonce(keys.address); + const nonce = await ExtrinsicHelper.apiPromise.call.accountNonceApi.accountNonce(getUnifiedAddress(keys)); return nonce.toNumber(); } diff --git a/e2e/scaffolding/rootHooks.ts b/e2e/scaffolding/rootHooks.ts index f01495e371..9d0314b79a 100644 --- a/e2e/scaffolding/rootHooks.ts +++ b/e2e/scaffolding/rootHooks.ts @@ -27,7 +27,7 @@ export const mochaHooks = { // Any key created using helpers `createKeys` is kept in the module // then any value remaining is drained here at the end const rootAddress = getRootFundingSource().keys.address; - await drainFundedKeys(rootAddress); + // await drainFundedKeys(rootAddress); console.log('ENDING ROOT hook shutdown', testSuite); } catch (e) { console.error('Failed to run afterAll root hook: ', testSuite, e); diff --git a/pallets/frequency-tx-payment/src/lib.rs b/pallets/frequency-tx-payment/src/lib.rs index 0f802279da..6ee330e88e 100644 --- a/pallets/frequency-tx-payment/src/lib.rs +++ b/pallets/frequency-tx-payment/src/lib.rs @@ -23,6 +23,7 @@ use frame_system::pallet_prelude::*; use pallet_transaction_payment::{FeeDetails, InclusionFee, OnChargeTransaction}; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; +use sp_core::hexdisplay::HexDisplay; use sp_runtime::{ traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension, Zero}, transaction_validity::{TransactionValidity, TransactionValidityError}, @@ -420,12 +421,15 @@ where if fee.is_zero() { return Ok((fee, InitialPayment::Free)) } - + log::info!(target: "ETHEREUM", "fee is {:?} top: {:?} info={:?} call={:?}", fee, tip, info, HexDisplay::from(&call.encode())); as OnChargeTransaction>::withdraw_fee( who, call, info, fee, tip, ) .map(|i| (fee, InitialPayment::Token(i))) - .map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() }) + .map_err(|e| -> TransactionValidityError { + log::error!(target: "ETHEREUM", "withdraw_token_fee {:?}", e); + TransactionValidityError::Invalid(InvalidTransaction::Payment) + }) } } @@ -477,8 +481,9 @@ where info: &DispatchInfoOf, len: usize, ) -> TransactionValidity { + log::info!(target: "ETHEREUM", "trx validate 0x{:?}", HexDisplay::from(&who.encode())); let (fee, _) = self.withdraw_fee(who, call, info, len)?; - + log::info!(target: "ETHEREUM", "trx after validate {:?}", who); let priority = pallet_transaction_payment::ChargeTransactionPayment::::get_priority( info, len, diff --git a/pallets/passkey/src/lib.rs b/pallets/passkey/src/lib.rs index ee4a4a6796..8178afacde 100644 --- a/pallets/passkey/src/lib.rs +++ b/pallets/passkey/src/lib.rs @@ -16,7 +16,7 @@ rustdoc::invalid_codeblock_attributes, missing_docs )] -use common_runtime::{extensions::check_nonce::CheckNonce, signature::check_signature}; +use common_runtime::extensions::check_nonce::CheckNonce; use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::*, @@ -28,7 +28,7 @@ use sp_runtime::{ generic::Era, traits::{Convert, Dispatchable, SignedExtension, Zero}, transaction_validity::{TransactionValidity, TransactionValidityError}, - AccountId32, MultiSignature, + AccountId32, }; use sp_std::{vec, vec::Vec}; @@ -50,9 +50,11 @@ mod tests; pub mod weights; pub use weights::*; +use common_primitives::{signatures::UnifiedSignature, utils::wrap_binary_data}; #[cfg(feature = "runtime-benchmarks")] use frame_support::traits::tokens::fungible::Mutate; use frame_system::CheckWeight; +use sp_runtime::traits::Verify; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -274,7 +276,7 @@ impl PasskeySignatureCheck { let signature = self.0.passkey_call.account_ownership_proof.clone(); let signer = &self.0.passkey_call.account_id; - Self::check_account_signature(signer, &signed_data.inner().to_vec(), &signature) + Self::check_account_signature(signer, &signed_data.inner().to_vec(), &signature.into()) .map_err(|_e| TransactionValidityError::Invalid(InvalidTransaction::BadSigner))?; // checking the passkey signature to ensure access to the passkey @@ -311,16 +313,31 @@ impl PasskeySignatureCheck { fn check_account_signature( signer: &T::AccountId, signed_data: &Vec, - signature: &MultiSignature, + signature: &UnifiedSignature, ) -> DispatchResult { let key = T::ConvertIntoAccountId32::convert((*signer).clone()); - if !check_signature(signature, key, signed_data.clone()) { + if !Self::check_signature(signature, key, signed_data.clone()) { return Err(Error::::InvalidAccountSignature.into()); } Ok(()) } + + fn check_signature( + signature: &UnifiedSignature, + signer: AccountId32, + payload: Vec, + ) -> bool { + let verify_signature = |payload: &[u8]| signature.verify(payload, &signer.clone().into()); + + if verify_signature(&payload) { + return true; + } + + let wrapped_payload = wrap_binary_data(payload); + verify_signature(&wrapped_payload) + } } /// Passkey related tx payment diff --git a/runtime/frequency/Cargo.toml b/runtime/frequency/Cargo.toml index b904dae80a..6b8ab82112 100644 --- a/runtime/frequency/Cargo.toml +++ b/runtime/frequency/Cargo.toml @@ -101,6 +101,7 @@ cumulus-primitives-timestamp = { workspace = true } cumulus-primitives-aura = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } +sp-debug-derive = { package = "sp-debug-derive", git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0", default-features = false } [features] default = ["std"] @@ -176,6 +177,9 @@ std = [ "substrate-wasm-builder", "frame-metadata-hash-extension/std", ] +force-debug=[ + "sp-debug-derive/force-debug", +] runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", diff --git a/runtime/frequency/src/eth.rs b/runtime/frequency/src/eth.rs new file mode 100644 index 0000000000..b0ee185160 --- /dev/null +++ b/runtime/frequency/src/eth.rs @@ -0,0 +1,50 @@ +use parity_scale_codec::Codec; +use scale_info::StaticTypeInfo; +use sp_core::hexdisplay::HexDisplay; +use sp_runtime::{ + traits::{LookupError, StaticLookup}, + MultiAddress, +}; +use sp_std::{fmt::Debug, marker::PhantomData}; + +/// A lookup implementation returning the `AccountId` from a `MultiAddress`. +pub struct EthCompatibleAccountIdLookup( + PhantomData<(AccountId, AccountIndex)>, +); +impl StaticLookup for EthCompatibleAccountIdLookup +where + AccountId: Codec + Clone + PartialEq + Debug, + AccountIndex: Codec + Clone + PartialEq + Debug, + MultiAddress: Codec + StaticTypeInfo, +{ + type Source = MultiAddress; + type Target = AccountId; + fn lookup(x: Self::Source) -> Result { + match x { + MultiAddress::Id(i) => Ok(i), + MultiAddress::Address20(acc20) => { + log::info!(target: "ETHEREUM", "lookup 0x{:?}", HexDisplay::from(&acc20)); + let mut buffer = [0u8; 32]; + buffer[12..].copy_from_slice(&acc20); + let decoded = Self::Target::decode(&mut &buffer[..]).map_err(|_| LookupError)?; + Ok(decoded) + }, + _ => Err(LookupError), + } + } + fn unlookup(x: Self::Target) -> Self::Source { + MultiAddress::Id(x) + // This should probably leave commented out since we are always dealing with 32 byte accounts + // let encoded = x.encode(); + // match encoded[..12].eq(&[0u8; 12]) { + // true => { + // log::info!(target: "ETHEREUM", "unlookup before 0x{:?}", HexDisplay::from(&encoded)); + // let mut address20 = [0u8; 20]; + // address20[..].copy_from_slice(&encoded[12..]); + // log::info!(target: "ETHEREUM", "unlookup after 0x{:?}", HexDisplay::from(&address20)); + // MultiAddress::Address20(address20) + // }, + // false => MultiAddress::Id(x), + // } + } +} diff --git a/runtime/frequency/src/lib.rs b/runtime/frequency/src/lib.rs index 026f04a641..bad5445049 100644 --- a/runtime/frequency/src/lib.rs +++ b/runtime/frequency/src/lib.rs @@ -19,14 +19,12 @@ pub fn wasm_binary_unwrap() -> &'static [u8] { #[cfg(any(not(feature = "frequency-no-relay"), feature = "frequency-lint-check"))] use cumulus_pallet_parachain_system::{RelayNumberMonotonicallyIncreases, RelaychainDataProvider}; +use cumulus_primitives_core::BlockT; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{ - AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, - IdentityLookup, - }, + traits::{AccountIdConversion, BlakeTwo256, ConvertInto, IdentityLookup}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, DispatchError, }; @@ -369,6 +367,8 @@ impl OnRuntimeUpgrade for MigratePalletsCu } } +pub mod eth; + /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know /// the specifics of the runtime. They can then be made to be agnostic over specific formats /// of data like extrinsics, allowing for them to continue syncing the network through upgrades @@ -474,7 +474,7 @@ impl frame_system::Config for Runtime { /// The aggregated dispatch type that is available for extrinsics. type RuntimeCall = RuntimeCall; /// The lookup mechanism to get account ID from whatever is passed in dispatchers. - type Lookup = AccountIdLookup; + type Lookup = eth::EthCompatibleAccountIdLookup; /// The index type for storing how many extrinsics an account has signed. type Nonce = Index; /// The block type. diff --git a/scripts/init.sh b/scripts/init.sh index 520518f05a..808c0fbdd4 100755 --- a/scripts/init.sh +++ b/scripts/init.sh @@ -106,7 +106,7 @@ start-paseo-collator-bob) start-frequency-instant) printf "\nBuilding Frequency without relay. Running with instant sealing ...\n" - cargo build --features frequency-no-relay + cargo build --features frequency-no-relay,force-debug parachain_dir=$base_dir/parachain/${para_id} mkdir -p $parachain_dir; @@ -121,7 +121,7 @@ start-frequency-instant) --state-pruning archive \ -lbasic-authorship=debug \ -ltxpool=debug \ - -lruntime=debug \ + -lruntime=trace \ --sealing=instant \ --wasm-execution=compiled \ --no-telemetry \