diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 703403a3e2..beb9e2c896 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -58,11 +58,21 @@ jobs: - name: Cargo check all targets. run: cargo check --all-targets - # Next, check each feature on its own and compile each crate separately. This is no good - # for subxt/examples, which expect default features to be enabled, hence the above check - # and why we don'#t do `--all-targets` for this one. + # Next, check subxt features. + # - `native` feature must always be enabled + # - `web` feature is always ignored. + # - This means, don't check --no-default-features and don't try enabling --all-features; both will fail + - name: Cargo hack; check each subxt feature + run: cargo hack -p subxt --each-feature check --exclude-no-default-features --exclude-all-features --exclude-features web --features native + + # Subxt-signer has the "subxt" features enabled in the "check all targets" test. Run it on its own to + # check it without. We can't enable subxt or web features here, so no cargo hack. + - name: Cargo check subxt-signer + run: cargo check -p subxt-signer + + # Next, check each other package in isolation. - name: Cargo hack; check each feature/crate on its own - run: cargo hack --exclude-all-features --each-feature check --workspace + run: cargo hack --exclude subxt --exclude subxt-signer --exclude-all-features --each-feature check --workspace fmt: name: Cargo fmt @@ -215,10 +225,16 @@ jobs: mkdir -p ~/.local/bin mv substrate ~/.local/bin - - name: Run WASM tests + - name: Run subxt WASM tests run: | substrate --dev --tmp > /dev/null 2>&1 & wasm-pack test --headless --firefox wasm-pack test --headless --chrome pkill substrate working-directory: testing/wasm-tests + + - name: Run subxt-signer WASM tests + run: | + wasm-pack test --headless --firefox + wasm-pack test --headless --chrome + working-directory: signer/wasm-tests diff --git a/Cargo.lock b/Cargo.lock index 542f2185b1..091e7f169d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,6 +271,23 @@ dependencies = [ "serde", ] +[[package]] +name = "bip39" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +dependencies = [ + "bitcoin_hashes", + "serde", + "unicode-normalization", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" + [[package]] name = "bitflags" version = "1.3.2" @@ -768,6 +785,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", +] + [[package]] name = "darling" version = "0.14.4" @@ -1588,12 +1618,12 @@ dependencies = [ "regex", "scale-info", "sp-core", - "sp-keyring", "sp-runtime", "substrate-runner", "subxt", "subxt-codegen", "subxt-metadata", + "subxt-signer", "syn 2.0.18", "test-runtime", "tokio", @@ -1904,6 +1934,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "miniz_oxide" version = "0.6.2" @@ -2118,6 +2160,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "pbkdf2" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ca0b5a68607598bf3bad68f32227a8164f6254833f84eafaac409cd6746c31" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "percent-encoding" version = "2.2.0" @@ -2694,7 +2745,7 @@ dependencies = [ "arrayvec 0.5.2", "curve25519-dalek 2.1.3", "getrandom 0.1.16", - "merlin", + "merlin 2.0.1", "rand 0.7.3", "rand_core 0.5.1", "sha2 0.8.2", @@ -2702,6 +2753,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "schnorrkel" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "844b7645371e6ecdf61ff246ba1958c29e802881a749ae3fb1993675d210d28d" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "curve25519-dalek-ng", + "merlin 3.0.0", + "rand_core 0.6.4", + "serde_bytes", + "sha2 0.9.9", + "subtle-ng", + "zeroize", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -2783,6 +2851,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.164" @@ -2968,7 +3045,7 @@ dependencies = [ "lazy_static", "libsecp256k1", "log", - "merlin", + "merlin 2.0.1", "parity-scale-codec", "parking_lot", "paste", @@ -2976,7 +3053,7 @@ dependencies = [ "rand 0.8.5", "regex", "scale-info", - "schnorrkel", + "schnorrkel 0.9.1", "secp256k1", "secrecy", "serde", @@ -3369,7 +3446,7 @@ checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" dependencies = [ "hmac 0.11.0", "pbkdf2 0.8.0", - "schnorrkel", + "schnorrkel 0.9.1", "sha2 0.9.9", "zeroize", ] @@ -3384,6 +3461,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + [[package]] name = "subxt" version = "0.29.0" @@ -3416,6 +3499,7 @@ dependencies = [ "sp-version", "subxt-macro", "subxt-metadata", + "subxt-signer", "thiserror", "tokio", "tracing", @@ -3485,6 +3569,28 @@ dependencies = [ "thiserror", ] +[[package]] +name = "subxt-signer" +version = "0.29.0" +dependencies = [ + "bip39", + "getrandom 0.2.10", + "hex", + "hmac 0.12.1", + "parity-scale-codec", + "pbkdf2 0.12.1", + "regex", + "schnorrkel 0.10.2", + "secrecy", + "sha2 0.10.6", + "sp-core", + "sp-core-hashing", + "sp-keyring", + "subxt", + "thiserror", + "zeroize", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 3b4e514cc8..f1539e2139 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,13 +8,18 @@ members = [ "testing/ui-tests", "macro", "metadata", + "signer", "subxt" ] -# This cannot be a workspace dependency, because it requires -# mutually exclusive jsonrpsee features to work, and workspaces -# will aggregate features used across crates: -exclude = ["testing/wasm-tests", "examples/wasm-example"] +# We exclude any crates that would depend on non mutually +# exclusive feature flags and thus can't compile with the +# workspace: +exclude = [ + "testing/wasm-tests", + "signer/wasm-tests", + "examples/wasm-example" +] resolver = "2" [workspace.package] @@ -42,7 +47,7 @@ derivative = "2.2.0" either = "1.8.1" frame-metadata = { version = "15.1.0", features = ["v14", "v15-unstable", "std"] } futures = { version = "0.3.27", default-features = false, features = ["std"] } -getrandom = "0.2" +getrandom = { version = "0.2", default-features = false } hex = "0.4.3" heck = "0.4.1" impl-serde = { version = "0.4.0" } @@ -79,9 +84,19 @@ sp-runtime = "24.0.0" sp-version = "22.0.0" # Subxt workspace crates: -subxt = { version = "0.29.0", path = "subxt" } +subxt = { version = "0.29.0", path = "subxt", default-features = false } subxt-macro = { version = "0.29.0", path = "macro" } subxt-metadata = { version = "0.29.0", path = "metadata" } subxt-codegen = { version = "0.29.0", path = "codegen" } +subxt-signer = { version = "0.29.0", path = "signer" } test-runtime = { path = "testing/test-runtime" } substrate-runner = { path = "testing/substrate-runner" } + +# subxt-signer deps that I expect aren't useful anywhere else: +bip39 = "2.0.0" +hmac = "0.12.1" +pbkdf2 = { version = "0.12.1", default-features = false } +schnorrkel = "0.10.2" +secrecy = "0.8.0" +sha2 = "0.10.6" +zeroize = { version = "1", default-features = false } \ No newline at end of file diff --git a/RELEASING.md b/RELEASING.md index 22c98b4702..e8d4b8d138 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -65,7 +65,6 @@ We also assume that ongoing work done is being merged directly to the `master` b 2. Perform a final sanity check that everything looks ok. ``` - cargo hack --exclude-all-features --each-feature check --all-targets --workspace cargo test --all-targets ``` @@ -77,12 +76,10 @@ We also assume that ongoing work done is being merged directly to the `master` b (cd codegen && cargo publish) && \ (cd macro && cargo publish) && \ (cd subxt && cargo publish) && \ + (cd signer && cargo publish) && \ (cd cli && cargo publish); ``` - If you run into any issues regarding crates not being able to find suitable versions of other `subxt-*` crates, - you may just need to wait a little longer and then run the remaining portion of that command. - 10. If the release was successful, tag the commit that we released in the `master` branch with the version that we just released, for example: diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 136cab5dba..d3d54acc64 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -19,7 +19,7 @@ path = "src/main.rs" [dependencies] subxt-codegen = { workspace = true } subxt-metadata = { workspace = true } -subxt = { workspace = true } +subxt = { workspace = true, features = ["native", "jsonrpsee"] } clap = { workspace = true } serde = { workspace = true, features = ["derive"] } color-eyre = { workspace = true } diff --git a/signer/Cargo.toml b/signer/Cargo.toml new file mode 100644 index 0000000000..8ee9e3bee2 --- /dev/null +++ b/signer/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "subxt-signer" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +publish = true + +license.workspace = true +readme = "README.md" +repository.workspace = true +documentation.workspace = true +homepage.workspace = true +description = "Sign extrinsics to be submitted by Subxt" +keywords = ["parity", "subxt", "extrinsic", "signer"] + +[features] +default = [] + +# Make the keypair algorithms here compatible with Subxt's Signer trait, +# so that they can be used to sign transactions for compatible chains. +subxt = ["dep:subxt"] + +# The getrandom package is used via schnorrkel. We need to enable the JS +# feature on it if compiling for the web. +web = ["getrandom/js"] + +[dependencies] +regex = { workspace = true } +subxt = { workspace = true, optional = true } +hex = { workspace = true } +codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] } +sp-core-hashing = { workspace = true } +thiserror = { workspace = true } +pbkdf2 = { workspace = true } +sha2 = { workspace = true } +hmac = { workspace = true } +zeroize = { workspace = true } +bip39 = { workspace = true } +schnorrkel = { workspace = true } +secrecy = { workspace = true } + +# We only pull this in to enable the JS flag for schnorrkel to use. +getrandom = { workspace = true, optional = true } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +sp-keyring = { workspace = true } diff --git a/signer/README.md b/signer/README.md new file mode 100644 index 0000000000..01d8a21bdd --- /dev/null +++ b/signer/README.md @@ -0,0 +1,3 @@ +# Subxt-signer + +This library exposes a small, WASM compatible signer implementation which can be used in conjunction with Subxt to sign transactions. \ No newline at end of file diff --git a/signer/src/crypto/derive_junction.rs b/signer/src/crypto/derive_junction.rs new file mode 100644 index 0000000000..87beee1717 --- /dev/null +++ b/signer/src/crypto/derive_junction.rs @@ -0,0 +1,106 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +use codec::{Decode, Encode}; + +// This code is taken from sp_core::crypto::DeriveJunction. The logic should be identical, +// though the API is tweaked a touch. + +/// The length of the junction identifier. Note that this is also referred to as the +/// `CHAIN_CODE_LENGTH` in the context of Schnorrkel. +pub const JUNCTION_ID_LEN: usize = 32; + +/// A since derivation junction description. It is the single parameter used when creating +/// a new secret key from an existing secret key and, in the case of `SoftRaw` and `SoftIndex` +/// a new public key from an existing public key. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Encode, Decode)] +pub enum DeriveJunction { + /// Soft (vanilla) derivation. Public keys have a correspondent derivation. + Soft([u8; JUNCTION_ID_LEN]), + /// Hard ("hardened") derivation. Public keys do not have a correspondent derivation. + Hard([u8; JUNCTION_ID_LEN]), +} + +impl DeriveJunction { + /// Consume self to return a soft derive junction with the same chain code. + pub fn soften(self) -> Self { + DeriveJunction::Soft(self.into_inner()) + } + + /// Consume self to return a hard derive junction with the same chain code. + pub fn harden(self) -> Self { + DeriveJunction::Hard(self.into_inner()) + } + + /// Create a new soft (vanilla) DeriveJunction from a given, encodable, value. + /// + /// If you need a hard junction, use `hard()`. + pub fn soft(index: T) -> Self { + let mut cc: [u8; JUNCTION_ID_LEN] = Default::default(); + index.using_encoded(|data| { + if data.len() > JUNCTION_ID_LEN { + cc.copy_from_slice(&sp_core_hashing::blake2_256(data)); + } else { + cc[0..data.len()].copy_from_slice(data); + } + }); + DeriveJunction::Soft(cc) + } + + /// Create a new hard (hardened) DeriveJunction from a given, encodable, value. + /// + /// If you need a soft junction, use `soft()`. + pub fn hard(index: T) -> Self { + Self::soft(index).harden() + } + + /// Consume self to return the chain code. + pub fn into_inner(self) -> [u8; JUNCTION_ID_LEN] { + match self { + DeriveJunction::Hard(c) | DeriveJunction::Soft(c) => c, + } + } + + /// Get a reference to the inner junction id. + pub fn inner(&self) -> &[u8; JUNCTION_ID_LEN] { + match self { + DeriveJunction::Hard(ref c) | DeriveJunction::Soft(ref c) => c, + } + } + + /// Return `true` if the junction is soft. + pub fn is_soft(&self) -> bool { + matches!(*self, DeriveJunction::Soft(_)) + } + + /// Return `true` if the junction is hard. + pub fn is_hard(&self) -> bool { + matches!(*self, DeriveJunction::Hard(_)) + } +} + +impl> From for DeriveJunction { + fn from(j: T) -> DeriveJunction { + let j = j.as_ref(); + let (code, hard) = if let Some(stripped) = j.strip_prefix('/') { + (stripped, true) + } else { + (j, false) + }; + + let res = if let Ok(n) = str::parse::(code) { + // number + DeriveJunction::soft(n) + } else { + // something else + DeriveJunction::soft(code) + }; + + if hard { + res.harden() + } else { + res + } + } +} diff --git a/signer/src/crypto/mod.rs b/signer/src/crypto/mod.rs new file mode 100644 index 0000000000..c47dfe08a6 --- /dev/null +++ b/signer/src/crypto/mod.rs @@ -0,0 +1,13 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +// The crypto module contains code adapted from sp_core::crypto. + +mod derive_junction; +mod secret_uri; +mod seed_from_entropy; + +pub use derive_junction::DeriveJunction; +pub use secret_uri::{SecretUri, SecretUriError, DEV_PHRASE}; +pub use seed_from_entropy::seed_from_entropy; diff --git a/signer/src/crypto/secret_uri.rs b/signer/src/crypto/secret_uri.rs new file mode 100644 index 0000000000..7be43b8286 --- /dev/null +++ b/signer/src/crypto/secret_uri.rs @@ -0,0 +1,156 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +use super::DeriveJunction; +use regex::Regex; +use secrecy::SecretString; + +// This code is taken from sp_core::crypto::DeriveJunction. The logic should be identical, +// though the code is tweaked a touch! + +/// A secret uri (`SURI`) that can be used to generate a key pair. +/// +/// The `SURI` can be parsed from a string. The string takes this form: +/// +/// ```text +/// phrase/path0/path1///password +/// 111111 22222 22222 33333333 +/// ``` +/// +/// Where: +/// - 1 denotes a phrase or hex string. If this is not provided, the [`DEV_PHRASE`] is used +/// instead. +/// - 2's denote optional "derivation junctions" which are used to derive keys. Each of these is +/// separated by "/". A derivation junction beginning with "/" (ie "//" in the original string) +/// is a "hard" path. +/// - 3 denotes an optional password which is used in conjunction with the phrase provided in 1 +/// to generate an initial key. If hex is provided for 1, it's ignored. +/// +/// Notes: +/// - If 1 is a `0x` prefixed 64-digit hex string, then we'll interpret it as hex, and treat the hex bytes +/// as a seed/MiniSecretKey directly, ignoring any password. +/// - Else if the phrase part is a valid BIP-39 phrase, we'll use the phrase (and password, if provided) +/// to generate a seed/MiniSecretKey. +/// - Uris like "//Alice" correspond to keys derived from a DEV_PHRASE, since no phrase part is given. +/// +/// There is no correspondence mapping between `SURI` strings and the keys they represent. +/// Two different non-identical strings can actually lead to the same secret being derived. +/// Notably, integer junction indices may be legally prefixed with arbitrary number of zeros. +/// Similarly an empty password (ending the `SURI` with `///`) is perfectly valid and will +/// generally be equivalent to no password at all. +/// +/// # Examples +/// +/// Parse [`DEV_PHRASE`] secret URI with junction: +/// +/// ``` +/// # use subxt_signer::{SecretUri, DeriveJunction, DEV_PHRASE, ExposeSecret}; +/// # use std::str::FromStr; +/// let suri = SecretUri::from_str("//Alice").expect("Parse SURI"); +/// +/// assert_eq!(vec![DeriveJunction::from("Alice").harden()], suri.junctions); +/// assert_eq!(DEV_PHRASE, suri.phrase.expose_secret()); +/// assert!(suri.password.is_none()); +/// ``` +/// +/// Parse [`DEV_PHRASE`] secret URI with junction and password: +/// +/// ``` +/// # use subxt_signer::{SecretUri, DeriveJunction, DEV_PHRASE, ExposeSecret}; +/// # use std::str::FromStr; +/// let suri = SecretUri::from_str("//Alice///SECRET_PASSWORD").expect("Parse SURI"); +/// +/// assert_eq!(vec![DeriveJunction::from("Alice").harden()], suri.junctions); +/// assert_eq!(DEV_PHRASE, suri.phrase.expose_secret()); +/// assert_eq!("SECRET_PASSWORD", suri.password.unwrap().expose_secret()); +/// ``` +/// +/// Parse [`DEV_PHRASE`] secret URI with hex phrase and junction: +/// +/// ``` +/// # use subxt_signer::{SecretUri, DeriveJunction, DEV_PHRASE, ExposeSecret}; +/// # use std::str::FromStr; +/// let suri = SecretUri::from_str("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a//Alice").expect("Parse SURI"); +/// +/// assert_eq!(vec![DeriveJunction::from("Alice").harden()], suri.junctions); +/// assert_eq!("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a", suri.phrase.expose_secret()); +/// assert!(suri.password.is_none()); +/// ``` +pub struct SecretUri { + /// The phrase to derive the private key. + /// + /// This can either be a 64-bit hex string or a BIP-39 key phrase. + pub phrase: SecretString, + /// Optional password as given as part of the uri. + pub password: Option, + /// The junctions as part of the uri. + pub junctions: Vec, +} + +impl std::str::FromStr for SecretUri { + type Err = SecretUriError; + + fn from_str(s: &str) -> Result { + let cap = secret_phrase_regex() + .captures(s) + .ok_or(SecretUriError::InvalidFormat)?; + + let junctions = junction_regex() + .captures_iter(&cap["path"]) + .map(|f| DeriveJunction::from(&f[1])) + .collect::>(); + + let phrase = cap.name("phrase").map(|r| r.as_str()).unwrap_or(DEV_PHRASE); + let password = cap.name("password"); + + Ok(Self { + phrase: SecretString::from_str(phrase).expect("Returns infallible error; qed"), + password: password.map(|v| { + SecretString::from_str(v.as_str()).expect("Returns infallible error; qed") + }), + junctions, + }) + } +} + +/// This is returned if `FromStr` cannot parse a string into a `SecretUri`. +#[derive(Debug, Copy, Clone, PartialEq, thiserror::Error)] +pub enum SecretUriError { + /// Parsing the secret URI from a string failed; wrong format. + #[error("Invalid secret phrase format")] + InvalidFormat, +} + +once_static! { + /// Interpret a phrase like: + /// + /// ```text + /// foo bar wibble /path0/path1///password + /// 11111111111111 222222222222 33333333 + /// ``` + /// Where 1 is the phrase, 2 the path and 3 the password. + /// Taken from `sp_core::crypto::SECRET_PHRASE_REGEX`. + fn secret_phrase_regex() -> regex::Regex { + Regex::new(r"^(?P[\d\w ]+)?(?P(//?[^/]+)*)(///(?P.*))?$").unwrap() + } + + /// Interpret a part of a path into a "junction": + /// + /// ```text + /// //foo/bar/wibble + /// 1111 222 333333 + /// ``` + /// Where the numbers denote matching junctions. + /// + /// The leading "/" deliminates each part, and then a "/" beginning + /// a path piece denotes that it's a "hard" path. Taken from + /// `sp_core::crypto::JUNCTION_REGEX`. + fn junction_regex() -> regex::Regex { + Regex::new(r"/(/?[^/]+)").unwrap() + } +} + +/// The root phrase for our publicly known keys. +pub const DEV_PHRASE: &str = + "bottom drive obey lake curtain smoke basket hold race lonely fit walk"; diff --git a/signer/src/crypto/seed_from_entropy.rs b/signer/src/crypto/seed_from_entropy.rs new file mode 100644 index 0000000000..ba51080659 --- /dev/null +++ b/signer/src/crypto/seed_from_entropy.rs @@ -0,0 +1,29 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +use hmac::Hmac; +use pbkdf2::pbkdf2; +use sha2::Sha512; +use zeroize::Zeroize; + +/// This is taken from `substrate-bip39` so that we can keep dependencies in line, and +/// is the same logic that sp-core uses to go from mnemonic entropy to seed. Returns +/// `None` if invalid length. +pub fn seed_from_entropy(entropy: &[u8], password: &str) -> Option<[u8; 64]> { + if entropy.len() < 16 || entropy.len() > 32 || entropy.len() % 4 != 0 { + return None; + } + + let mut salt = String::with_capacity(8 + password.len()); + salt.push_str("mnemonic"); + salt.push_str(password); + + let mut seed = [0u8; 64]; + + pbkdf2::>(entropy, salt.as_bytes(), 2048, &mut seed).ok()?; + + salt.zeroize(); + + Some(seed) +} diff --git a/signer/src/lib.rs b/signer/src/lib.rs new file mode 100644 index 0000000000..1598c364a1 --- /dev/null +++ b/signer/src/lib.rs @@ -0,0 +1,35 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! # Subxt-signer +//! +//! The main output from this crate is the [`sr25519::Keypair`], which can +//! be constructed from a bip39 phrase, secret URI or raw seed, and used to +//! sign and verify arbitrary messages. This crate is aligned with how Substrate's +//! `sp_core` crate constructs and signs keypairs, but is lighter on dependencies +//! and can support compilation to WASM with the `web` feature. +//! +//! Enable the `subxt` feature to enable use of this [`sr25519::Keypair`] in signing +//! subxt transactions for chains supporting sr25519 signatures. + +#![deny(missing_docs)] + +#[macro_use] +mod utils; +mod crypto; + +// An sr25519 key pair implementation. +pub mod sr25519; + +// Re-export useful bits and pieces for generating a Pair from a phrase, +// namely the Mnemonic struct. +pub use bip39; + +// Used to hold strings in a more secure manner in memory for a little extra +// protection. +pub use secrecy::{ExposeSecret, SecretString}; + +// SecretUri's can be parsed from strings and used to generate key pairs. +// DeriveJunctions are the "path" part of these SecretUris. +pub use crypto::{DeriveJunction, SecretUri, SecretUriError, DEV_PHRASE}; diff --git a/signer/src/sr25519.rs b/signer/src/sr25519.rs new file mode 100644 index 0000000000..559e16e3b0 --- /dev/null +++ b/signer/src/sr25519.rs @@ -0,0 +1,417 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! An sr25519 keypair implementation. + +use crate::crypto::{seed_from_entropy, DeriveJunction, SecretUri}; +use hex::FromHex; +use schnorrkel::{ + derive::{ChainCode, Derivation}, + ExpansionMode, MiniSecretKey, +}; +use secrecy::ExposeSecret; + +const SEED_LENGTH: usize = schnorrkel::keys::MINI_SECRET_KEY_LENGTH; +const SIGNING_CTX: &[u8] = b"substrate"; + +/// Seed bytes used to generate a key pair. +pub type Seed = [u8; SEED_LENGTH]; + +/// A signature generated by [`Keypair::sign()`]. These bytes are equivalent +/// to a Substrate `MultiSignature::sr25519(bytes)`. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Signature(pub [u8; 64]); + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +/// The public key for an [`Keypair`] key pair. This is equivalent to a +/// Substrate `AccountId32`. +pub struct PublicKey(pub [u8; 32]); + +impl AsRef<[u8]> for PublicKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +/// An sr25519 keypair implementation. While the API is slightly different, the logic for +/// this has been taken from `sp_core::sr25519` and we test against this to ensure conformity. +#[derive(Debug, Clone)] +pub struct Keypair(schnorrkel::Keypair); + +impl Keypair { + /// Create am sr25519 keypair from a [`SecretUri`]. See the [`SecretUri`] docs for more. + /// + /// # Example + /// + /// ```rust + /// use subxt_signer::{ SecretUri, sr25519::Keypair }; + /// use std::str::FromStr; + /// + /// let uri = SecretUri::from_str("//Alice").unwrap(); + /// let keypair = Keypair::from_uri(&uri).unwrap(); + /// + /// keypair.sign(b"Hello world!"); + /// ``` + pub fn from_uri(uri: &SecretUri) -> Result { + let SecretUri { + junctions, + phrase, + password, + } = uri; + + // If the phrase is hex, convert bytes directly into a seed, ignoring password. + // Else, parse the phrase string taking the password into account. This is + // the same approach taken in sp_core::crypto::Pair::from_string_with_seed. + let key = if let Some(hex_str) = phrase.expose_secret().strip_prefix("0x") { + let seed = Seed::from_hex(hex_str)?; + Self::from_seed(seed)? + } else { + let phrase = bip39::Mnemonic::parse(phrase.expose_secret().as_str())?; + let pass_str = password.as_ref().map(|p| p.expose_secret().as_str()); + Self::from_phrase(&phrase, pass_str)? + }; + + // Now, use any "junctions" to derive a new key from this root key. + Ok(key.derive(junctions.iter().copied())) + } + + /// Create am sr25519 keypair from a BIP-39 mnemonic phrase and optional password. + /// + /// # Example + /// + /// ```rust + /// use subxt_signer::{ bip39::Mnemonic, sr25519::Keypair }; + /// + /// let phrase = "bottom drive obey lake curtain smoke basket hold race lonely fit walk"; + /// let mnemonic = Mnemonic::parse(phrase).unwrap(); + /// let keypair = Keypair::from_phrase(&mnemonic, None).unwrap(); + /// + /// keypair.sign(b"Hello world!"); + /// ``` + pub fn from_phrase(mnemonic: &bip39::Mnemonic, password: Option<&str>) -> Result { + let big_seed = seed_from_entropy(&mnemonic.to_entropy(), password.unwrap_or("")) + .ok_or(Error::InvalidSeed)?; + + let seed: Seed = big_seed[..SEED_LENGTH] + .try_into() + .expect("should be valid Seed"); + + Self::from_seed(seed) + } + + /// Turn a 32 byte seed into a keypair. + /// + /// # Warning + /// + /// This will only be secure if the seed is secure! + pub fn from_seed(seed: Seed) -> Result { + let keypair = MiniSecretKey::from_bytes(&seed) + .map_err(|_| Error::InvalidSeed)? + .expand_to_keypair(ExpansionMode::Ed25519); + + Ok(Keypair(keypair)) + } + + /// Derive a child key from this one given a series of junctions. + /// + /// # Example + /// + /// ```rust + /// use subxt_signer::{ bip39::Mnemonic, sr25519::Keypair, DeriveJunction }; + /// + /// let phrase = "bottom drive obey lake curtain smoke basket hold race lonely fit walk"; + /// let mnemonic = Mnemonic::parse(phrase).unwrap(); + /// let keypair = Keypair::from_phrase(&mnemonic, None).unwrap(); + /// + /// // Equivalent to the URI path '//Alice/stash': + /// let new_keypair = keypair.derive([ + /// DeriveJunction::hard("Alice"), + /// DeriveJunction::soft("stash") + /// ]); + /// ``` + pub fn derive>(&self, junctions: Js) -> Self { + let init = self.0.secret.clone(); + let result = junctions.into_iter().fold(init, |acc, j| match j { + DeriveJunction::Soft(cc) => acc.derived_key_simple(ChainCode(cc), []).0, + DeriveJunction::Hard(cc) => { + let seed = acc.hard_derive_mini_secret_key(Some(ChainCode(cc)), b"").0; + seed.expand(ExpansionMode::Ed25519) + } + }); + Self(result.into()) + } + + /// Obtain the [`PublicKey`] part of this key pair, which can be used in calls to [`verify()`]. + /// or otherwise converted into an address. The public key bytes are equivalent to a Substrate + /// `AccountId32`. + pub fn public_key(&self) -> PublicKey { + PublicKey(self.0.public.to_bytes()) + } + + /// Sign some message. These bytes can be used directly in a Substrate `MultiSignature::sr25519(..)`. + pub fn sign(&self, message: &[u8]) -> Signature { + let context = schnorrkel::signing_context(SIGNING_CTX); + let signature = self.0.sign(context.bytes(message)); + Signature(signature.to_bytes()) + } +} + +/// Verify that some signature for a message was created by the owner of the [`PublicKey`]. +/// +/// ```rust +/// use subxt_signer::{ bip39::Mnemonic, sr25519 }; +/// +/// let keypair = sr25519::dev::alice(); +/// let message = b"Hello!"; +/// +/// let signature = keypair.sign(message); +/// let public_key = keypair.public_key(); +/// assert!(sr25519::verify(&signature, message, &public_key)); +/// ``` +pub fn verify>(sig: &Signature, message: M, pubkey: &PublicKey) -> bool { + let Ok(signature) = schnorrkel::Signature::from_bytes(&sig.0) else { + return false + }; + let Ok(public) = schnorrkel::PublicKey::from_bytes(&pubkey.0) else { + return false + }; + public + .verify_simple(SIGNING_CTX, message.as_ref(), &signature) + .is_ok() +} + +/// An error handed back if creating a keypair fails. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Invalid seed. + #[error("Invalid seed (was it the wrong length?)")] + InvalidSeed, + /// Invalid phrase. + #[error("Cannot parse phrase: {0}")] + Phrase(#[from] bip39::Error), + /// Invalid hex. + #[error("Cannot parse hex string: {0}")] + Hex(#[from] hex::FromHexError), +} + +/// Dev accounts, helpful for testing but not to be used in production, +/// since the secret keys are known. +pub mod dev { + use super::*; + use std::str::FromStr; + + once_static_cloned! { + /// Equivalent to `{DEV_PHRASE}//Alice`. + pub fn alice() -> Keypair { + Keypair::from_uri(&SecretUri::from_str("//Alice").unwrap()).unwrap() + } + /// Equivalent to `{DEV_PHRASE}//Bob`. + pub fn bob() -> Keypair { + Keypair::from_uri(&SecretUri::from_str("//Bob").unwrap()).unwrap() + } + /// Equivalent to `{DEV_PHRASE}//Charlie`. + pub fn charlie() -> Keypair { + Keypair::from_uri(&SecretUri::from_str("//Charlie").unwrap()).unwrap() + } + /// Equivalent to `{DEV_PHRASE}//Dave`. + pub fn dave() -> Keypair { + Keypair::from_uri(&SecretUri::from_str("//Dave").unwrap()).unwrap() + } + /// Equivalent to `{DEV_PHRASE}//Eve`. + pub fn eve() -> Keypair { + Keypair::from_uri(&SecretUri::from_str("//Eve").unwrap()).unwrap() + } + /// Equivalent to `{DEV_PHRASE}//Ferdie`. + pub fn ferdie() -> Keypair { + Keypair::from_uri(&SecretUri::from_str("//Ferdie").unwrap()).unwrap() + } + /// Equivalent to `{DEV_PHRASE}//One`. + pub fn one() -> Keypair { + Keypair::from_uri(&SecretUri::from_str("//One").unwrap()).unwrap() + } + /// Equivalent to `{DEV_PHRASE}//Two`. + pub fn two() -> Keypair { + Keypair::from_uri(&SecretUri::from_str("//Two").unwrap()).unwrap() + } + } +} + +// Make `Keypair` usable to sign transactions in Subxt. This is optional so that +// `subxt-signer` can be used entirely independently of Subxt. +#[cfg(feature = "subxt")] +mod subxt_compat { + use super::*; + + use subxt::config::Config; + use subxt::tx::Signer as SignerT; + use subxt::utils::{AccountId32, MultiAddress, MultiSignature}; + + impl From for MultiSignature { + fn from(value: Signature) -> Self { + MultiSignature::Sr25519(value.0) + } + } + impl From for AccountId32 { + fn from(value: PublicKey) -> Self { + value.to_account_id() + } + } + impl From for MultiAddress { + fn from(value: PublicKey) -> Self { + value.to_address() + } + } + + impl PublicKey { + /// A shortcut to obtain an [`AccountId32`] from a [`PublicKey`]. + /// We often want this type, and using this method avoids any + /// ambiguous type resolution issues. + pub fn to_account_id(self) -> AccountId32 { + AccountId32(self.0) + } + /// A shortcut to obtain a [`MultiAddress`] from a [`PublicKey`]. + /// We often want this type, and using this method avoids any + /// ambiguous type resolution issues. + pub fn to_address(self) -> MultiAddress { + MultiAddress::Id(self.to_account_id()) + } + } + + impl SignerT for Keypair + where + T::AccountId: From, + T::Address: From, + T::Signature: From, + { + fn account_id(&self) -> T::AccountId { + self.public_key().into() + } + + fn address(&self) -> T::Address { + self.public_key().into() + } + + fn sign(&self, signer_payload: &[u8]) -> T::Signature { + self.sign(signer_payload).into() + } + } +} + +#[cfg(test)] +mod test { + use std::str::FromStr; + + use super::*; + + use sp_core::crypto::Pair as _; + use sp_core::sr25519::Pair as SpPair; + + #[test] + fn check_from_phrase_matches() { + for _ in 0..20 { + let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(None); + let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected"); + let pair = Keypair::from_phrase(&phrase, None).expect("should be valid"); + + assert_eq!(sp_pair.public().0, pair.public_key().0); + } + } + + #[test] + fn check_from_phrase_with_password_matches() { + for _ in 0..20 { + let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some("Testing")); + let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected"); + let pair = Keypair::from_phrase(&phrase, Some("Testing")).expect("should be valid"); + + assert_eq!(sp_pair.public().0, pair.public_key().0); + } + } + + #[test] + fn check_from_secret_uri_matches() { + // Some derive junctions to check that the logic there aligns: + let uri_paths = [ + "/foo", + "//bar", + "/1", + "/0001", + "//1", + "//0001", + "//foo//bar/wibble", + "//foo//001/wibble", + ]; + + for i in 0..2 { + for path in &uri_paths { + // Build an sp_core::Pair that includes a phrase, path and password: + let password = format!("Testing{i}"); + let (_sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some(&password)); + let uri = format!("{phrase}{path}///{password}"); + let sp_pair = SpPair::from_string(&uri, None).expect("should be valid"); + + // Now build a local Keypair using the equivalent API: + let uri = SecretUri::from_str(&uri).expect("should be valid secret URI"); + let pair = Keypair::from_uri(&uri).expect("should be valid"); + + // They should match: + assert_eq!(sp_pair.public().0, pair.public_key().0); + } + } + } + + #[test] + fn check_dev_accounts_match() { + use sp_keyring::sr25519::Keyring::*; + + assert_eq!(dev::alice().public_key().0, Alice.public().0); + assert_eq!(dev::bob().public_key().0, Bob.public().0); + assert_eq!(dev::charlie().public_key().0, Charlie.public().0); + assert_eq!(dev::dave().public_key().0, Dave.public().0); + assert_eq!(dev::eve().public_key().0, Eve.public().0); + assert_eq!(dev::ferdie().public_key().0, Ferdie.public().0); + assert_eq!(dev::one().public_key().0, One.public().0); + assert_eq!(dev::two().public_key().0, Two.public().0); + } + + #[test] + fn check_signing_and_verifying_matches() { + use sp_core::sr25519::Signature as SpSignature; + + for _ in 0..20 { + let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some("Testing")); + let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected"); + let pair = Keypair::from_phrase(&phrase, Some("Testing")).expect("should be valid"); + + let message = b"Hello world"; + let sp_sig = sp_pair.sign(message).0; + let sig = pair.sign(message).0; + + assert!(SpPair::verify( + &SpSignature(sig), + message, + &sp_pair.public() + )); + assert!(verify(&Signature(sp_sig), message, &pair.public_key())); + } + } + + #[test] + fn check_hex_uris() { + // Hex URIs seem to ignore the password on sp_core and here. Check that this is consistent. + let uri_str = + "0x1122334455667788112233445566778811223344556677881122334455667788///SomePassword"; + + let uri = SecretUri::from_str(uri_str).expect("should be valid"); + let pair = Keypair::from_uri(&uri).expect("should be valid"); + let sp_pair = SpPair::from_string(uri_str, None).expect("should be valid"); + + assert_eq!(pair.public_key().0, sp_pair.public().0); + } +} diff --git a/signer/src/utils.rs b/signer/src/utils.rs new file mode 100644 index 0000000000..200392f723 --- /dev/null +++ b/signer/src/utils.rs @@ -0,0 +1,39 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/// Use like: +/// +/// ```rust,ignore +/// once_static!{ +/// /// Some documentation. +/// fn foo() -> Vec { +/// vec![1,2,3,4] +/// } +/// } +/// ``` +macro_rules! once_static { + ($($(#[$attr:meta])* $vis:vis fn $name:ident() -> $ty:ty { $expr:expr } )+) => { + $( + $(#[$attr])* + $vis fn $name() -> &'static $ty { + static VAR: std::sync::OnceLock<$ty> = std::sync::OnceLock::new(); + VAR.get_or_init(|| { $expr }) + } + )+ + }; +} + +/// Like `once_static!` but clones the item out of static storage. Useful if it +/// takes a while to create the item but cloning it is fairly cheap. +macro_rules! once_static_cloned { + ($($(#[$attr:meta])* $vis:vis fn $name:ident() -> $ty:ty { $expr:expr } )+) => { + $( + $(#[$attr])* + $vis fn $name() -> $ty { + static VAR: std::sync::OnceLock<$ty> = std::sync::OnceLock::new(); + VAR.get_or_init(|| { $expr }).clone() + } + )+ + }; +} diff --git a/signer/wasm-tests/.gitignore b/signer/wasm-tests/.gitignore new file mode 100644 index 0000000000..869df07dae --- /dev/null +++ b/signer/wasm-tests/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock \ No newline at end of file diff --git a/signer/wasm-tests/Cargo.toml b/signer/wasm-tests/Cargo.toml new file mode 100644 index 0000000000..ac92982240 --- /dev/null +++ b/signer/wasm-tests/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "wasm-tests" +version = "0.1.0" +edition = "2021" +publish = false + +[dev-dependencies] +wasm-bindgen-test = "0.3.24" +tracing-wasm = "0.2.1" +console_error_panic_hook = "0.1.7" +serde_json = "1" + +# This crate is not a part of the workspace, because we want to +# enable the "web" feature here but don't want it enabled as part +# of workspace builds. Also disable the "subxt" feature here because +# we want to ensure it works in isolation of that. +subxt-signer = { path = "..", default-features = false, features = ["web"] } + +# this shouldn't be needed, it's in workspace.exclude, but still +# I get the complaint unless I add it... +[workspace] \ No newline at end of file diff --git a/signer/wasm-tests/tests/wasm.rs b/signer/wasm-tests/tests/wasm.rs new file mode 100644 index 0000000000..e393b285a0 --- /dev/null +++ b/signer/wasm-tests/tests/wasm.rs @@ -0,0 +1,26 @@ +#![cfg(target_arch = "wasm32")] + +use subxt_signer::sr25519; +use wasm_bindgen_test::*; + +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +// Run the tests by calling: +// +// ```text +// wasm-pack test --firefox --headless` +// ``` +// +// These are independent of any other package to ensure that nothing +// else enabled the same feature flag that subxt-signer needs to work ok +// (subxt seems to, for instance). + +#[wasm_bindgen_test] +async fn wasm_signing_works() { + let alice = sr25519::dev::alice(); + + // There's some non-determinism in the signing, so this ensures that + // the rand stuff is configured properly to run ok in wasm. + let signature = alice.sign(b"Hello there"); + assert!(sr25519::verify(&signature, b"Hello there", &alice.public_key())); +} diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index 2264bfa9dc..287d3db8ff 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -15,31 +15,49 @@ description = "Submit extrinsics (transactions) to a substrate node via RPC" keywords = ["parity", "substrate", "blockchain"] [features] -default = ["jsonrpsee-ws", "substrate-compat"] +# For dev and documentation reasons we enable more features than are often desired. +# it's recommended to use `--no-default-features` and then select what you need. +default = ["jsonrpsee", "native", "substrate-compat"] -# Activate this feature to pull in extra Substrate dependencies which make it -# possible to provide a proper extrinsic Signer implementation (PairSigner). +# Enable this for native (ie non web/wasm builds). +# Exactly 1 of "web" and "native" is expected. +native = [ + "jsonrpsee?/async-client", + "jsonrpsee?/client-ws-transport" +] + +# Enable this for web/wasm builds. +# Exactly 1 of "web" and "native" is expected. +web = [ + "jsonrpsee?/async-wasm-client", + "jsonrpsee?/client-web-transport", + "getrandom/js" +] + +# Enable this to use jsonrpsee (allowing for example `OnlineClient::from_url`). +jsonrpsee = [ + "dep:jsonrpsee" +] + +# Enable this to pull in extra Substrate dependencies which make it possible to +# use the `sp_core::crypto::Pair` Signer implementation, as well as adding some +# `From` impls for types like `AccountId32`. Cannot be used with "web". substrate-compat = [ "sp-core", "sp-runtime" ] -# Activate this to expose functionality only used for integration testing. -# The exposed functionality is subject to breaking changes at any point, -# and should not be relied upon. -integration-tests = [] - -# Jsonrpsee if the default RPC provider used in Subxt. However, it can be -# swapped out for an alternative implementation, and so is optional. -jsonrpsee-ws = ["jsonrpsee/async-client", "jsonrpsee/client-ws-transport"] -jsonrpsee-web = ["jsonrpsee/async-wasm-client", "jsonrpsee/client-web-transport"] - -# Activate this to fetch and utilize the latest unstabl metadata from a node. +# Enable this to fetch and utilize the latest unstable metadata from a node. # The unstable metadata is subject to breaking changes and the subxt might # fail to decode the metadata properly. Use this to experiment with the # latest features exposed by the metadata. unstable-metadata = [] +# Enable this to expose functionality only used for integration testing. +# The exposed functionality is subject to breaking changes at any point, +# and should not be relied upon. +integration-tests = [] + [dependencies] codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] } scale-info = { workspace = true } @@ -66,10 +84,10 @@ sp-core-hashing = { workspace = true } base58 = { workspace = true } blake2 = { workspace = true } -# Included if one of the jsonrpsee features is enabled. +# Included if the "jsonrpsee" feature is enabled. jsonrpsee = { workspace = true, optional = true, features = ["jsonrpsee-types"] } -# These are only included is "substrate-compat" is enabled. +# Included if the "substrate-compat" feature is enabled. sp-core = { workspace = true, optional = true } sp-runtime = { workspace = true, optional = true } @@ -77,8 +95,8 @@ sp-runtime = { workspace = true, optional = true } subxt-macro = { workspace = true } subxt-metadata = { workspace = true } -[target.wasm32-unknown-unknown.dependencies] -getrandom = { workspace = true, features = ["js"] } +# Included if "web" feature is enabled, to enable its js feature. +getrandom = { workspace = true, optional = true } [dev-dependencies] bitvec = { workspace = true } @@ -90,3 +108,4 @@ sp-runtime = { workspace = true } sp-keyring = { workspace = true } sp-version = { workspace = true } assert_matches = { workspace = true } +subxt-signer = { workspace = true, features = ["subxt"] } \ No newline at end of file diff --git a/subxt/examples/runtime_apis_dynamic.rs b/subxt/examples/runtime_apis_dynamic.rs index 85bcf2f07e..f7e8a894d7 100644 --- a/subxt/examples/runtime_apis_dynamic.rs +++ b/subxt/examples/runtime_apis_dynamic.rs @@ -1,6 +1,6 @@ -use sp_keyring::AccountKeyring; use subxt::dynamic::Value; use subxt::{config::PolkadotConfig, OnlineClient}; +use subxt_signer::sr25519::dev; #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] pub mod polkadot {} @@ -12,7 +12,7 @@ async fn main() -> Result<(), Box> { // Create a dynamically runtime API payload that calls the // `AccountNonceApi_account_nonce` function. - let account = AccountKeyring::Alice.to_account_id(); + let account = dev::alice().public_key(); let runtime_api_call = subxt::dynamic::runtime_api_call( "AccountNonceApi", "account_nonce", diff --git a/subxt/examples/runtime_apis_static.rs b/subxt/examples/runtime_apis_static.rs index 53732ce817..a41c20e244 100644 --- a/subxt/examples/runtime_apis_static.rs +++ b/subxt/examples/runtime_apis_static.rs @@ -1,5 +1,5 @@ -use sp_keyring::AccountKeyring; use subxt::{config::PolkadotConfig, OnlineClient}; +use subxt_signer::sr25519::dev; #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] pub mod polkadot {} @@ -11,7 +11,7 @@ async fn main() -> Result<(), Box> { // Create a runtime API payload that calls into // `AccountNonceApi_account_nonce` function. - let account = AccountKeyring::Alice.to_account_id().into(); + let account = dev::alice().public_key().into(); let runtime_api_call = polkadot::apis().account_nonce_api().account_nonce(account); // Submit the call and get back a result. diff --git a/subxt/examples/storage_fetch.rs b/subxt/examples/storage_fetch.rs index 5674d51ff7..69e674407a 100644 --- a/subxt/examples/storage_fetch.rs +++ b/subxt/examples/storage_fetch.rs @@ -1,5 +1,5 @@ -use sp_keyring::AccountKeyring; use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; // Generate an interface that we can use from the node's metadata. #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] @@ -11,7 +11,7 @@ async fn main() -> Result<(), Box> { let api = OnlineClient::::new().await?; // Build a storage query to access account information. - let account = AccountKeyring::Alice.to_account_id().into(); + let account = dev::alice().public_key().into(); let storage_query = polkadot::storage().system().account(&account); // Use that query to `fetch` a result. This returns an `Option<_>`, which will be diff --git a/subxt/examples/storage_fetch_dynamic.rs b/subxt/examples/storage_fetch_dynamic.rs index 43325770ea..0d2a5edff7 100644 --- a/subxt/examples/storage_fetch_dynamic.rs +++ b/subxt/examples/storage_fetch_dynamic.rs @@ -1,6 +1,6 @@ -use sp_keyring::AccountKeyring; use subxt::dynamic::{At, Value}; use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; #[tokio::main] async fn main() -> Result<(), Box> { @@ -8,7 +8,7 @@ async fn main() -> Result<(), Box> { let api = OnlineClient::::new().await?; // Build a dynamic storage query to access account information. - let account = AccountKeyring::Alice.to_account_id(); + let account = dev::alice().public_key(); let storage_query = subxt::dynamic::storage("System", "Account", vec![Value::from_bytes(account)]); diff --git a/subxt/examples/tx_basic.rs b/subxt/examples/tx_basic.rs index 2f31e2476e..53818b5ece 100644 --- a/subxt/examples/tx_basic.rs +++ b/subxt/examples/tx_basic.rs @@ -1,5 +1,5 @@ -use sp_keyring::AccountKeyring; -use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig}; +use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; // Generate an interface that we can use from the node's metadata. #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] @@ -11,12 +11,12 @@ async fn main() -> Result<(), Box> { let api = OnlineClient::::new().await?; // Build a balance transfer extrinsic. - let dest = AccountKeyring::Bob.to_account_id().into(); + let dest = dev::bob().public_key().into(); let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000); // Submit the balance transfer extrinsic from Alice, and wait for it to be successful // and in a finalized block. We get back the extrinsic events if all is well. - let from = PairSigner::new(AccountKeyring::Alice.pair()); + let from = dev::alice(); let events = api .tx() .sign_and_submit_then_watch_default(&balance_transfer_tx, &from) diff --git a/subxt/examples/tx_status_stream.rs b/subxt/examples/tx_status_stream.rs index 47d0b9196c..66466174d8 100644 --- a/subxt/examples/tx_status_stream.rs +++ b/subxt/examples/tx_status_stream.rs @@ -1,9 +1,6 @@ use futures::StreamExt; -use sp_keyring::AccountKeyring; -use subxt::{ - tx::{PairSigner, TxStatus}, - OnlineClient, PolkadotConfig, -}; +use subxt::{tx::TxStatus, OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; // Generate an interface that we can use from the node's metadata. #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] @@ -15,15 +12,15 @@ async fn main() -> Result<(), Box> { let api = OnlineClient::::new().await?; // Build a balance transfer extrinsic. - let dest = AccountKeyring::Bob.to_account_id().into(); + let dest = dev::bob().public_key().into(); let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000); // Submit the balance transfer extrinsic from Alice, and then monitor the // progress of it. - let signer = PairSigner::new(AccountKeyring::Alice.pair()); + let from = dev::alice(); let mut balance_transfer_progress = api .tx() - .sign_and_submit_then_watch_default(&balance_transfer_tx, &signer) + .sign_and_submit_then_watch_default(&balance_transfer_tx, &from) .await?; while let Some(status) = balance_transfer_progress.next().await { diff --git a/subxt/examples/tx_with_params.rs b/subxt/examples/tx_with_params.rs index c64dcc2029..f1295d0810 100644 --- a/subxt/examples/tx_with_params.rs +++ b/subxt/examples/tx_with_params.rs @@ -1,6 +1,6 @@ -use sp_keyring::AccountKeyring; use subxt::config::polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params}; -use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig}; +use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] pub mod polkadot {} @@ -11,7 +11,7 @@ async fn main() -> Result<(), Box> { let api = OnlineClient::::new().await?; // Build a balance transfer extrinsic. - let dest = AccountKeyring::Bob.to_account_id().into(); + let dest = dev::bob().public_key().into(); let tx = polkadot::tx().balances().transfer(dest, 10_000); // Configure the transaction parameters; for Polkadot the tip and era: @@ -20,7 +20,7 @@ async fn main() -> Result<(), Box> { .era(Era::Immortal, api.genesis_hash()); // submit the transaction: - let from = PairSigner::new(AccountKeyring::Alice.pair()); + let from = dev::alice(); let hash = api.tx().sign_and_submit(&tx, &from, tx_params).await?; println!("Balance transfer extrinsic submitted with hash : {hash}"); diff --git a/subxt/src/book/usage/constants.rs b/subxt/src/book/usage/constants.rs index 4b21010c6b..927e7c4335 100644 --- a/subxt/src/book/usage/constants.rs +++ b/subxt/src/book/usage/constants.rs @@ -16,8 +16,6 @@ //! We can use the statically generated interface to build constant queries: //! //! ```rust,no_run -//! use sp_keyring::AccountKeyring; -//! //! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")] //! pub mod polkadot {} //! @@ -27,10 +25,8 @@ //! Alternately, we can dynamically construct a constant query: //! //! ```rust,no_run -//! use sp_keyring::AccountKeyring; //! use subxt::dynamic::Value; //! -//! let account = AccountKeyring::Alice.to_account_id(); //! let storage_query = subxt::dynamic::constant("System", "BlockLength"); //! ``` //! diff --git a/subxt/src/book/usage/runtime_apis.rs b/subxt/src/book/usage/runtime_apis.rs index 4fdc19f722..30f8cab9df 100644 --- a/subxt/src/book/usage/runtime_apis.rs +++ b/subxt/src/book/usage/runtime_apis.rs @@ -22,8 +22,6 @@ //! We can use the statically generated interface to build runtime calls: //! //! ```rust,no_run -//! use sp_keyring::AccountKeyring; -//! //! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] //! pub mod polkadot {} //! @@ -33,10 +31,8 @@ //! Alternately, we can dynamically construct a runtime call: //! //! ```rust,no_run -//! use sp_keyring::AccountKeyring; //! use subxt::dynamic::Value; //! -//! let account = AccountKeyring::Alice.to_account_id(); //! let runtime_call = subxt::dynamic::runtime_api_call( //! "Metadata", //! "metadata_versions", diff --git a/subxt/src/book/usage/storage.rs b/subxt/src/book/usage/storage.rs index 4ffc73d162..2bde301bfa 100644 --- a/subxt/src/book/usage/storage.rs +++ b/subxt/src/book/usage/storage.rs @@ -16,12 +16,12 @@ //! We can use the statically generated interface to build storage queries: //! //! ```rust,no_run -//! use sp_keyring::AccountKeyring; +//! use subxt_signer::sr25519::dev; //! //! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] //! pub mod polkadot {} //! -//! let account = AccountKeyring::Alice.to_account_id().into(); +//! let account = dev::alice().public_key().into(); //! let storage_query = polkadot::storage().system().account(&account); //! ``` //! @@ -29,10 +29,10 @@ //! validated until it's submitted: //! //! ```rust,no_run -//! use sp_keyring::AccountKeyring; +//! use subxt_signer::sr25519::dev; //! use subxt::dynamic::Value; //! -//! let account = AccountKeyring::Alice.to_account_id(); +//! let account = dev::alice().public_key(); //! let storage_query = subxt::dynamic::storage("System", "Account", vec![ //! Value::from_bytes(account) //! ]); @@ -43,8 +43,6 @@ //! will only be available on static constructors when iteration is actually possible): //! //! ```rust,no_run -//! use sp_keyring::AccountKeyring; -//! //! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] //! pub mod polkadot {} //! diff --git a/subxt/src/book/usage/transactions.rs b/subxt/src/book/usage/transactions.rs index ebabbbb944..ab560bd54d 100644 --- a/subxt/src/book/usage/transactions.rs +++ b/subxt/src/book/usage/transactions.rs @@ -60,28 +60,54 @@ //! ## Signing it //! //! You'll normally need to sign an extrinsic to prove that it originated from an account that you -//! control. To do this, you will typically first create an [`crate::tx::Signer`], which tells Subxt -//! who the extrinsic is from, and takes care of signing the relevant details to prove this. +//! control. To do this, you will typically first create a [`crate::tx::Signer`] instance, which tells +//! Subxt who the extrinsic is from, and takes care of signing the relevant details to prove this. //! -//! Subxt provides a [`crate::tx::PairSigner`] which implements this trait (if the -//! `substrate-compat` feature is enabled) which accepts any valid [`sp_core::Pair`] and uses that -//! to sign transactions: +//! There are two main ways to create a compatible signer instance: +//! 1. The `subxt_signer` crate provides a WASM compatible implementation of [`crate::tx::Signer`] +//! for chains which require sr25519 signatures (requires the `subxt` feature to be enabled). +//! 2. Alternately, Subxt can use instances of Substrate's [`sp_core::Pair`] to sign things by wrapping +//! them in a [`crate::tx::PairSigner`] (requires the `substrate-compat` feature to be enabled). +//! +//! Going for 1 leads to fewer dependencies being imported and WASM compatibility out of the box via +//! the `web` feature flag. Going for 2 is useful if you're already using the Substrate dependencies or +//! need additional signing algorithms that `subxt_signer` doesn't support, and don't care about WASM +//! compatibility. +//! +//! Let's see how to use each of these approaches: //! //! ```rust +//! use subxt::config::PolkadotConfig; +//! use std::str::FromStr; +//! +//! //// 1. Use a `subxt_signer` impl: +//! use subxt_signer::{ SecretUri, sr25519 }; +//! +//! // Get hold of a `Signer` for a test account: +//! let alice = sr25519::dev::alice(); +//! +//! // Or generate a keypair, here from an SURI: +//! let uri = SecretUri::from_str("vessel ladder alter error federal sibling chat ability sun glass valve picture/0/1///Password") +//! .expect("valid URI"); +//! let keypair = sr25519::Keypair::from_uri(&uri) +//! .expect("valid keypair"); +//! +//! //// 2. Use the corresponding `sp_core::Pair` impl: //! use subxt::tx::PairSigner; //! use sp_core::Pair; -//! use subxt::config::PolkadotConfig; //! -//! // Get hold of a `Signer` given a test account: -//! let pair = sp_keyring::AccountKeyring::Alice.pair(); -//! let signer = PairSigner::::new(pair); +//! // Get hold of a `Signer` for a test account: +//! let alice = sp_keyring::AccountKeyring::Alice.pair(); +//! let alice = PairSigner::::new(alice); //! -//! // Or generate an `sr25519` keypair to use: -//! let (pair, _, _) = sp_core::sr25519::Pair::generate_with_phrase(Some("password")); -//! let signer = PairSigner::::new(pair); +//! // Or generate a keypair, here from an SURI: +//! let keypair = sp_core::sr25519::Pair::from_string("vessel ladder alter error federal sibling chat ability sun glass valve picture/0/1///Password", None) +//! .expect("valid URI"); +//! let keypair = PairSigner::::new(keypair); //! ``` //! -//! See the [`sp_core::Pair`] docs for more ways to generate them. +//! See the `subxt_signer::sr25519::Keypair` or the [`sp_core::Pair`] docs for more ways to construct +//! and work with key pairs. //! //! If this isn't suitable/available, you can either implement [`crate::tx::Signer`] yourself to use //! custom signing logic, or you can use some external signing logic, like so: @@ -118,10 +144,9 @@ //! let signature; //! let address; //! # use subxt::tx::Signer; -//! # let pair = sp_keyring::AccountKeyring::Alice.pair(); -//! # let signer = subxt::tx::PairSigner::::new(pair); -//! # signature = signer.sign(&signer_payload); -//! # address = signer.address(); +//! # let signer = subxt_signer::sr25519::dev::alice(); +//! # signature = signer.sign(&signer_payload).into(); +//! # address = signer.public_key().to_address(); //! //! // Now we can build an tx, which one can call `submit` or `submit_and_watch` //! // on to submit to a node and optionally watch the status. diff --git a/subxt/src/client/mod.rs b/subxt/src/client/mod.rs index e2a36f0baf..0b30b00f75 100644 --- a/subxt/src/client/mod.rs +++ b/subxt/src/client/mod.rs @@ -16,8 +16,5 @@ pub use online_client::{ ClientRuntimeUpdater, OnlineClient, OnlineClientT, RuntimeUpdaterStream, Update, UpgradeError, }; -#[cfg(any( - feature = "jsonrpsee-ws", - all(feature = "jsonrpsee-web", target_arch = "wasm32") -))] +#[cfg(feature = "jsonrpsee")] pub use online_client::default_rpc_client; diff --git a/subxt/src/client/online_client.rs b/subxt/src/client/online_client.rs index 5eeb9ef182..244370a29d 100644 --- a/subxt/src/client/online_client.rs +++ b/subxt/src/client/online_client.rs @@ -55,10 +55,7 @@ impl std::fmt::Debug for OnlineClient { } /// The default RPC client that's used (based on [`jsonrpsee`]). -#[cfg(any( - feature = "jsonrpsee-ws", - all(feature = "jsonrpsee-web", target_arch = "wasm32") -))] +#[cfg(feature = "jsonrpsee")] pub async fn default_rpc_client>(url: U) -> Result { let client = jsonrpsee_helpers::client(url.as_ref()) .await @@ -67,10 +64,7 @@ pub async fn default_rpc_client>(url: U) -> Result OnlineClient { /// Construct a new [`OnlineClient`] using default settings which /// point to a locally running node on `ws://127.0.0.1:9944`. @@ -428,7 +422,7 @@ impl Update { } // helpers for a jsonrpsee specific OnlineClient. -#[cfg(feature = "jsonrpsee-ws")] +#[cfg(all(feature = "jsonrpsee", feature = "native"))] mod jsonrpsee_helpers { pub use jsonrpsee::{ client_transport::ws::{InvalidUri, Receiver, Sender, Uri, WsTransportClientBuilder}, @@ -458,7 +452,7 @@ mod jsonrpsee_helpers { } // helpers for a jsonrpsee specific OnlineClient. -#[cfg(all(feature = "jsonrpsee-web", target_arch = "wasm32"))] +#[cfg(all(feature = "jsonrpsee", feature = "web"))] mod jsonrpsee_helpers { pub use jsonrpsee::{ client_transport::web, diff --git a/subxt/src/events/events_type.rs b/subxt/src/events/events_type.rs index e487dfc940..01bb5b8d06 100644 --- a/subxt/src/events/events_type.rs +++ b/subxt/src/events/events_type.rs @@ -19,7 +19,7 @@ use std::sync::Arc; /// A collection of events obtained from a block, bundled with the necessary /// information needed to decode and iterate over them. #[derive(Derivative)] -#[derivative(Debug(bound = ""), Clone(bound = ""))] +#[derivative(Clone(bound = ""))] pub struct Events { metadata: Metadata, block_hash: T::Hash, @@ -31,6 +31,18 @@ pub struct Events { num_events: u32, } +// Ignore the Metadata when debug-logging events; it's big and distracting. +impl std::fmt::Debug for Events { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Events") + .field("block_hash", &self.block_hash) + .field("event_bytes", &self.event_bytes) + .field("start_idx", &self.start_idx) + .field("num_events", &self.num_events) + .finish() + } +} + impl Events { pub(crate) fn new(metadata: Metadata, block_hash: T::Hash, event_bytes: Vec) -> Self { // event_bytes is a SCALE encoded vector of events. So, pluck the diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index 0711057ccf..4dbd4e9274 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -33,23 +33,31 @@ )] #![allow(clippy::type_complexity)] +#[cfg(any( + all(feature = "web", feature = "native"), + not(any(feature = "web", feature = "native")) +))] +compile_error!("subxt: exactly one of the 'web' and 'native' features should be used."); + +#[cfg(all(feature = "web", feature = "substrate-compat"))] +compile_error!("subxt: the 'substrate-compat' feature is not compatible with the 'web' feature."); + // The guide is here. pub mod book; // Suppress an unused dependency warning because tokio is // only used in example code snippets at the time of writing. #[cfg(test)] -use tokio as _; +mod only_used_in_docs_or_tests { + use subxt_signer as _; + use tokio as _; +} // Used to enable the js feature for wasm. -#[cfg(target_arch = "wasm32")] +#[cfg(feature = "web")] +#[allow(unused_imports)] pub use getrandom as _; -#[cfg(all(feature = "jsonrpsee-ws", feature = "jsonrpsee-web"))] -std::compile_error!( - "Both the features `jsonrpsee-ws` and `jsonrpsee-web` are enabled which are mutually exclusive" -); - pub mod blocks; pub mod client; pub mod config; diff --git a/subxt/src/tx/signer.rs b/subxt/src/tx/signer.rs index 8f3b40abc4..487df6af72 100644 --- a/subxt/src/tx/signer.rs +++ b/subxt/src/tx/signer.rs @@ -12,7 +12,7 @@ use crate::Config; /// as well as actually signing a SCALE encoded payload. pub trait Signer { /// Return the "from" account ID. - fn account_id(&self) -> &T::AccountId; + fn account_id(&self) -> T::AccountId; /// Return the "from" address. fn address(&self) -> T::Address; @@ -83,8 +83,8 @@ mod pair_signer { Pair: PairT, Pair::Signature: Into, { - fn account_id(&self) -> &T::AccountId { - &self.account_id + fn account_id(&self) -> T::AccountId { + self.account_id.clone() } fn address(&self) -> T::Address { diff --git a/subxt/src/tx/tx_client.rs b/subxt/src/tx/tx_client.rs index 5f3ce2943d..d2202f8188 100644 --- a/subxt/src/tx/tx_client.rs +++ b/subxt/src/tx/tx_client.rs @@ -211,7 +211,7 @@ where Call: TxPayload, Signer: SignerT, { - let account_nonce = self.account_nonce(signer.account_id()).await?; + let account_nonce = self.account_nonce(&signer.account_id()).await?; self.create_signed_with_nonce(call, signer, account_nonce, other_params) } diff --git a/testing/integration-tests/Cargo.toml b/testing/integration-tests/Cargo.toml index 81239856fb..8d49576130 100644 --- a/testing/integration-tests/Cargo.toml +++ b/testing/integration-tests/Cargo.toml @@ -25,9 +25,9 @@ regex = { workspace = true } scale-info = { workspace = true, features = ["bit-vec"] } sp-core = { workspace = true } sp-runtime = { workspace = true } -sp-keyring = { workspace = true } syn = { workspace = true } -subxt = { workspace = true, features = ["unstable-metadata"] } +subxt = { workspace = true, features = ["unstable-metadata", "native", "jsonrpsee", "substrate-compat"] } +subxt-signer = { workspace = true, features = ["subxt"] } subxt-codegen = { workspace = true } subxt-metadata = { workspace = true } test-runtime = { workspace = true } diff --git a/testing/integration-tests/src/blocks/mod.rs b/testing/integration-tests/src/blocks/mod.rs index e2b3b914c1..141cbf09e8 100644 --- a/testing/integration-tests/src/blocks/mod.rs +++ b/testing/integration-tests/src/blocks/mod.rs @@ -2,12 +2,12 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::{pair_signer, test_context, utils::node_runtime}; +use crate::{test_context, utils::node_runtime}; use codec::{Compact, Encode}; use futures::StreamExt; -use sp_keyring::AccountKeyring; use subxt::blocks::BlocksClient; use subxt_metadata::Metadata; +use subxt_signer::sr25519::dev; // Check that we can subscribe to non-finalized blocks. #[tokio::test] @@ -122,13 +122,13 @@ async fn decode_extrinsics() { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); - let bob = pair_signer(AccountKeyring::Bob.pair()); + let alice = dev::alice(); + let bob = dev::bob(); // Generate a block that has unsigned and signed transactions. let tx = node_runtime::tx() .balances() - .transfer(bob.account_id().clone().into(), 10_000); + .transfer(bob.public_key().into(), 10_000); let signed_extrinsic = api .tx() diff --git a/testing/integration-tests/src/client/mod.rs b/testing/integration-tests/src/client/mod.rs index a33d13d64b..65f9ea4ec1 100644 --- a/testing/integration-tests/src/client/mod.rs +++ b/testing/integration-tests/src/client/mod.rs @@ -3,31 +3,29 @@ // see LICENSE for license details. use crate::{ - pair_signer, test_context, test_context_with, + test_context, test_context_with, utils::{node_runtime, wait_for_blocks}, }; use assert_matches::assert_matches; use codec::{Compact, Decode, Encode}; use sp_core::storage::well_known_keys; -use sp_core::{sr25519::Pair as Sr25519Pair, Pair}; -use sp_keyring::AccountKeyring; use subxt::{ error::{DispatchError, Error, TokenError}, rpc::types::{ ChainHeadEvent, DryRunResult, DryRunResultBytes, FollowEvent, Initialized, RuntimeEvent, RuntimeVersionEvent, }, - tx::Signer, utils::AccountId32, }; use subxt_metadata::Metadata; +use subxt_signer::sr25519::dev; #[tokio::test] async fn insert_key() { - let ctx = test_context_with(AccountKeyring::Bob).await; + let ctx = test_context_with("bob".to_string()).await; let api = ctx.client(); - let public = AccountKeyring::Alice.public().as_array_ref().to_vec(); + let public = dev::alice().public_key().as_ref().to_vec(); api.rpc() .insert_key( "aura".to_string(), @@ -156,14 +154,14 @@ async fn dry_run_passes() { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); - let bob = pair_signer(AccountKeyring::Bob.pair()); + let alice = dev::alice(); + let bob = dev::bob(); wait_for_blocks(&api).await; let tx = node_runtime::tx() .balances() - .transfer(bob.account_id().clone().into(), 10_000); + .transfer(bob.public_key().into(), 10_000); let signed_extrinsic = api .tx() @@ -192,11 +190,11 @@ async fn dry_run_fails() { wait_for_blocks(&api).await; - let alice = pair_signer(AccountKeyring::Alice.pair()); - let hans = pair_signer(Sr25519Pair::generate().0); + let alice = dev::alice(); + let bob = dev::bob(); let tx = node_runtime::tx().balances().transfer( - hans.account_id().clone().into(), + bob.public_key().into(), // 7 more than the default amount Alice has, so this should fail; insufficient funds: 1_000_000_000_000_000_000_007, ); @@ -295,14 +293,14 @@ async fn dry_run_result_is_substrate_compatible() { async fn external_signing() { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); + let alice = dev::alice(); // Create a partial extrinsic. We can get the signer payload at this point, to be // signed externally. let tx = node_runtime::tx().preimage().note_preimage(vec![0u8]); let partial_extrinsic = api .tx() - .create_partial_signed(&tx, alice.account_id(), Default::default()) + .create_partial_signed(&tx, &alice.public_key().into(), Default::default()) .await .unwrap(); @@ -311,7 +309,8 @@ async fn external_signing() { // Sign it (possibly externally). let signature = alice.sign(&signer_payload); // Use this to build a signed extrinsic. - let extrinsic = partial_extrinsic.sign_with_address_and_signature(&alice.address(), &signature); + let extrinsic = partial_extrinsic + .sign_with_address_and_signature(&alice.public_key().into(), &signature.into()); // And now submit it. extrinsic @@ -328,7 +327,7 @@ async fn submit_large_extrinsic() { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); + let alice = dev::alice(); // 2 MiB blob of data. let bytes = vec![0_u8; 2 * 1024 * 1024]; @@ -357,8 +356,8 @@ async fn decode_a_module_error() { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); - let alice_addr = alice.account_id().clone().into(); + let alice = dev::alice(); + let alice_addr = alice.public_key().into(); // Trying to work with an asset ID 1 which doesn't exist should return an // "unknown" module error from the assets pallet. @@ -392,13 +391,9 @@ async fn unsigned_extrinsic_is_same_shape_as_polkadotjs() { let ctx = test_context().await; let api = ctx.client(); - let tx = node_runtime::tx().balances().transfer( - pair_signer(AccountKeyring::Alice.pair()) - .account_id() - .clone() - .into(), - 12345000000000000, - ); + let tx = node_runtime::tx() + .balances() + .transfer(dev::alice().public_key().into(), 12345000000000000); let actual_tx = api.tx().create_unsigned(&tx).unwrap(); @@ -545,7 +540,7 @@ async fn chainhead_unstable_storage() { }; let sub_id = blocks.subscription_id().unwrap().clone(); - let alice: AccountId32 = AccountKeyring::Alice.to_account_id().into(); + let alice: AccountId32 = dev::alice().public_key().into(); let addr = node_runtime::storage().system().account(alice); let addr_bytes = api.storage().address_bytes(&addr).unwrap(); @@ -572,7 +567,7 @@ async fn chainhead_unstable_call() { }; let sub_id = blocks.subscription_id().unwrap().clone(); - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = dev::alice().public_key().to_account_id(); let mut sub = api .rpc() .chainhead_unstable_call( @@ -646,12 +641,11 @@ async fn partial_fee_estimate_correct() { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); - let hans = pair_signer(Sr25519Pair::generate().0); - + let alice = dev::alice(); + let bob = dev::bob(); let tx = node_runtime::tx() .balances() - .transfer(hans.account_id().clone().into(), 1_000_000_000_000); + .transfer(bob.public_key().into(), 1_000_000_000_000); let signed_extrinsic = api .tx() diff --git a/testing/integration-tests/src/frame/balances.rs b/testing/integration-tests/src/frame/balances.rs index 2d54355a50..4c4dc99ba8 100644 --- a/testing/integration-tests/src/frame/balances.rs +++ b/testing/integration-tests/src/frame/balances.rs @@ -4,26 +4,29 @@ use crate::{ node_runtime::{self, balances, runtime_types, system}, - pair_signer, test_context, + test_context, }; use codec::Decode; -use sp_core::Pair; -use sp_keyring::AccountKeyring; use subxt::{ error::{DispatchError, Error, TokenError}, utils::{AccountId32, MultiAddress}, }; +use subxt_signer::sr25519::dev; #[tokio::test] async fn tx_basic_transfer() -> Result<(), subxt::Error> { - let alice = pair_signer(AccountKeyring::Alice.pair()); - let bob = pair_signer(AccountKeyring::Bob.pair()); - let bob_address = bob.account_id().clone().into(); + let alice = dev::alice(); + let bob = dev::bob(); + let bob_address = bob.public_key().to_address(); let ctx = test_context().await; let api = ctx.client(); - let alice_account_addr = node_runtime::storage().system().account(alice.account_id()); - let bob_account_addr = node_runtime::storage().system().account(bob.account_id()); + let alice_account_addr = node_runtime::storage() + .system() + .account(alice.public_key().to_account_id()); + let bob_account_addr = node_runtime::storage() + .system() + .account(bob.public_key().to_account_id()); let alice_pre = api .storage() @@ -56,8 +59,8 @@ async fn tx_basic_transfer() -> Result<(), subxt::Error> { .expect("Failed to find ExtrinisicSuccess"); let expected_event = balances::events::Transfer { - from: alice.account_id().clone(), - to: bob.account_id().clone(), + from: alice.public_key().to_account_id(), + to: bob.public_key().to_account_id(), amount: 10_000, }; assert_eq!(event, expected_event); @@ -84,20 +87,20 @@ async fn tx_basic_transfer() -> Result<(), subxt::Error> { async fn tx_dynamic_transfer() -> Result<(), subxt::Error> { use subxt::ext::scale_value::{At, Composite, Value}; - let alice = pair_signer(AccountKeyring::Alice.pair()); - let bob = pair_signer(AccountKeyring::Bob.pair()); + let alice = dev::alice(); + let bob = dev::bob(); let ctx = test_context().await; let api = ctx.client(); let alice_account_addr = subxt::dynamic::storage( "System", "Account", - vec![Value::from_bytes(alice.account_id())], + vec![Value::from_bytes(alice.public_key().to_account_id())], ); let bob_account_addr = subxt::dynamic::storage( "System", "Account", - vec![Value::from_bytes(bob.account_id())], + vec![Value::from_bytes(bob.public_key().to_account_id())], ); let alice_pre = api @@ -117,7 +120,10 @@ async fn tx_dynamic_transfer() -> Result<(), subxt::Error> { "Balances", "transfer", vec![ - Value::unnamed_variant("Id", vec![Value::from_bytes(bob.account_id())]), + Value::unnamed_variant( + "Id", + vec![Value::from_bytes(bob.public_key().to_account_id())], + ), Value::u128(10_000u128), ], ); @@ -140,11 +146,11 @@ async fn tx_dynamic_transfer() -> Result<(), subxt::Error> { let expected_fields = Composite::Named(vec![ ( "from".into(), - Value::unnamed_composite(vec![Value::from_bytes(alice.account_id())]), + Value::unnamed_composite(vec![Value::from_bytes(alice.public_key().to_account_id())]), ), ( "to".into(), - Value::unnamed_composite(vec![Value::from_bytes(bob.account_id())]), + Value::unnamed_composite(vec![Value::from_bytes(bob.public_key().to_account_id())]), ), ("amount".into(), Value::u128(10_000)), ]); @@ -201,13 +207,15 @@ async fn tx_dynamic_transfer() -> Result<(), subxt::Error> { #[tokio::test] async fn multiple_transfers_work_nonce_incremented() -> Result<(), subxt::Error> { - let alice = pair_signer(AccountKeyring::Alice.pair()); - let bob = pair_signer(AccountKeyring::Bob.pair()); - let bob_address: MultiAddress = bob.account_id().clone().into(); + let alice = dev::alice(); + let bob = dev::bob(); + let bob_address: MultiAddress = bob.public_key().into(); let ctx = test_context().await; let api = ctx.client(); - let bob_account_addr = node_runtime::storage().system().account(bob.account_id()); + let bob_account_addr = node_runtime::storage() + .system() + .account(bob.public_key().to_account_id()); let bob_pre = api .storage() @@ -259,9 +267,9 @@ async fn storage_total_issuance() { #[tokio::test] async fn storage_balance_lock() -> Result<(), subxt::Error> { - let bob_signer = pair_signer(AccountKeyring::Bob.pair()); - let bob: AccountId32 = AccountKeyring::Bob.to_account_id().into(); - let charlie: AccountId32 = AccountKeyring::Charlie.to_account_id().into(); + let bob_signer = dev::bob(); + let bob: AccountId32 = dev::bob().public_key().into(); + let charlie: AccountId32 = dev::charlie().public_key().into(); let ctx = test_context().await; let api = ctx.client(); @@ -302,31 +310,33 @@ async fn storage_balance_lock() -> Result<(), subxt::Error> { #[tokio::test] async fn transfer_error() { - let alice = pair_signer(AccountKeyring::Alice.pair()); - let alice_addr = alice.account_id().clone().into(); - let hans = pair_signer(Pair::generate().0); - let hans_address = hans.account_id().clone().into(); + let alice = dev::alice(); + let alice_addr = alice.public_key().into(); + let bob = dev::one(); // some dev account with no funds. + let bob_address = bob.public_key().into(); let ctx = test_context().await; let api = ctx.client(); - let to_hans_tx = node_runtime::tx() + let to_bob_tx = node_runtime::tx() .balances() - .transfer(hans_address, 100_000_000_000_000_000); + .transfer(bob_address, 100_000_000_000_000_000); let to_alice_tx = node_runtime::tx() .balances() .transfer(alice_addr, 100_000_000_000_000_000); api.tx() - .sign_and_submit_then_watch_default(&to_hans_tx, &alice) + .sign_and_submit_then_watch_default(&to_bob_tx, &alice) .await .unwrap() .wait_for_finalized_success() .await .unwrap(); + // When we try giving all of the funds back, Bob doesn't have + // anything left to pay transfer fees, so we hit an error. let res = api .tx() - .sign_and_submit_then_watch_default(&to_alice_tx, &hans) + .sign_and_submit_then_watch_default(&to_alice_tx, &bob) .await .unwrap() .wait_for_finalized_success() @@ -345,8 +355,8 @@ async fn transfer_error() { #[tokio::test] async fn transfer_implicit_subscription() { - let alice = pair_signer(AccountKeyring::Alice.pair()); - let bob: AccountId32 = AccountKeyring::Bob.to_account_id().into(); + let alice = dev::alice(); + let bob: AccountId32 = dev::bob().public_key().into(); let ctx = test_context().await; let api = ctx.client(); @@ -369,7 +379,7 @@ async fn transfer_implicit_subscription() { assert_eq!( event, balances::events::Transfer { - from: alice.account_id().clone(), + from: alice.public_key().to_account_id(), to: bob, amount: 10_000 } diff --git a/testing/integration-tests/src/frame/contracts.rs b/testing/integration-tests/src/frame/contracts.rs index ba6a3a3907..fc2ee41340 100644 --- a/testing/integration-tests/src/frame/contracts.rs +++ b/testing/integration-tests/src/frame/contracts.rs @@ -2,8 +2,6 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use sp_keyring::AccountKeyring; - use crate::{ node_runtime::{ self, @@ -13,16 +11,12 @@ use crate::{ }, test_context, TestContext, }; -use sp_core::sr25519::Pair; -use subxt::{ - tx::{PairSigner, TxProgress}, - utils::MultiAddress, - Config, Error, OnlineClient, SubstrateConfig, -}; +use subxt::{tx::TxProgress, utils::MultiAddress, Config, Error, OnlineClient, SubstrateConfig}; +use subxt_signer::sr25519::{self, dev}; struct ContractsTestContext { cxt: TestContext, - signer: PairSigner, + signer: sr25519::Keypair, } type Hash = ::Hash; @@ -40,7 +34,7 @@ const PROOF_SIZE: u64 = u64::MAX / 2; impl ContractsTestContext { async fn init() -> Self { let cxt = test_context().await; - let signer = PairSigner::new(AccountKeyring::Alice.pair()); + let signer = dev::alice(); Self { cxt, signer } } diff --git a/testing/integration-tests/src/frame/staking.rs b/testing/integration-tests/src/frame/staking.rs index d5e207ebe5..d7eeb06611 100644 --- a/testing/integration-tests/src/frame/staking.rs +++ b/testing/integration-tests/src/frame/staking.rs @@ -11,16 +11,20 @@ use crate::{ }, staking, }, - pair_signer, test_context, + test_context, }; use assert_matches::assert_matches; -use sp_core::{sr25519, Pair}; -use sp_keyring::AccountKeyring; use subxt::error::{DispatchError, Error}; +use subxt_signer::{ + sr25519::{self, dev}, + SecretUri, +}; /// Helper function to generate a crypto pair from seed -fn get_from_seed(seed: &str) -> sr25519::Pair { - sr25519::Pair::from_string(&format!("//{seed}"), None).expect("static values are valid; qed") +fn get_from_seed(seed: &str) -> sr25519::Keypair { + use std::str::FromStr; + let uri = SecretUri::from_str(&format!("//{seed}")).expect("expected to be valid"); + sr25519::Keypair::from_uri(&uri).expect("expected to be valid") } fn default_validator_prefs() -> ValidatorPrefs { @@ -35,7 +39,7 @@ async fn validate_with_controller_account() { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); + let alice = dev::alice(); let tx = node_runtime::tx() .staking() @@ -55,7 +59,7 @@ async fn validate_not_possible_for_stash_account() -> Result<(), Error> { let ctx = test_context().await; let api = ctx.client(); - let alice_stash = pair_signer(get_from_seed("Alice//stash")); + let alice_stash = get_from_seed("Alice//stash"); let tx = node_runtime::tx() .staking() @@ -80,12 +84,12 @@ async fn nominate_with_controller_account() { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); - let bob = pair_signer(AccountKeyring::Bob.pair()); + let alice = dev::alice(); + let bob = dev::bob(); let tx = node_runtime::tx() .staking() - .nominate(vec![bob.account_id().clone().into()]); + .nominate(vec![bob.public_key().to_address()]); api.tx() .sign_and_submit_then_watch_default(&tx, &alice) @@ -101,12 +105,12 @@ async fn nominate_not_possible_for_stash_account() -> Result<(), Error> { let ctx = test_context().await; let api = ctx.client(); - let alice_stash = pair_signer(get_from_seed("Alice//stash")); - let bob = pair_signer(AccountKeyring::Bob.pair()); + let alice_stash = get_from_seed("Alice//stash"); + let bob = dev::bob(); let tx = node_runtime::tx() .staking() - .nominate(vec![bob.account_id().clone().into()]); + .nominate(vec![bob.public_key().to_address()]); let nomination = api .tx() @@ -129,21 +133,23 @@ async fn chill_works_for_controller_only() -> Result<(), Error> { let ctx = test_context().await; let api = ctx.client(); - let alice_stash = pair_signer(get_from_seed("Alice//stash")); - let bob_stash = pair_signer(get_from_seed("Bob//stash")); - let alice = pair_signer(AccountKeyring::Alice.pair()); + let alice_stash = get_from_seed("Alice//stash"); + let bob_stash = get_from_seed("Bob//stash"); + let alice = dev::alice(); // this will fail the second time, which is why this is one test, not two let nominate_tx = node_runtime::tx() .staking() - .nominate(vec![bob_stash.account_id().clone().into()]); + .nominate(vec![bob_stash.public_key().to_address()]); api.tx() .sign_and_submit_then_watch_default(&nominate_tx, &alice) .await? .wait_for_finalized_success() .await?; - let ledger_addr = node_runtime::storage().staking().ledger(alice.account_id()); + let ledger_addr = node_runtime::storage() + .staking() + .ledger(alice.public_key().to_account_id()); let ledger = api .storage() .at_latest() @@ -151,7 +157,7 @@ async fn chill_works_for_controller_only() -> Result<(), Error> { .fetch(&ledger_addr) .await? .unwrap(); - assert_eq!(alice_stash.account_id(), &ledger.stash); + assert_eq!(alice_stash.public_key().to_account_id(), ledger.stash); let chill_tx = node_runtime::tx().staking().chill(); @@ -185,10 +191,10 @@ async fn tx_bond() -> Result<(), Error> { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); + let alice = dev::alice(); let bond_tx = node_runtime::tx().staking().bond( - AccountKeyring::Bob.to_account_id().into(), + dev::bob().public_key().into(), 100_000_000_000_000, RewardDestination::Stash, ); diff --git a/testing/integration-tests/src/frame/sudo.rs b/testing/integration-tests/src/frame/sudo.rs index 82833f0e1e..30ae462fcb 100644 --- a/testing/integration-tests/src/frame/sudo.rs +++ b/testing/integration-tests/src/frame/sudo.rs @@ -8,9 +8,9 @@ use crate::{ runtime_types::{self, sp_weights::weight_v2::Weight}, sudo, }, - pair_signer, test_context, + test_context, }; -use sp_keyring::AccountKeyring; +use subxt_signer::sr25519::dev; type Call = runtime_types::kitchensink_runtime::RuntimeCall; type BalancesCall = runtime_types::pallet_balances::pallet::Call; @@ -20,8 +20,8 @@ async fn test_sudo() -> Result<(), subxt::Error> { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); - let bob = AccountKeyring::Bob.to_account_id().into(); + let alice = dev::alice(); + let bob = dev::bob().public_key().into(); let call = Call::Balances(BalancesCall::transfer { dest: bob, @@ -46,8 +46,8 @@ async fn test_sudo_unchecked_weight() -> Result<(), subxt::Error> { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); - let bob = AccountKeyring::Bob.to_account_id().into(); + let alice = dev::alice(); + let bob = dev::bob().public_key().into(); let call = Call::Balances(BalancesCall::transfer { dest: bob, diff --git a/testing/integration-tests/src/frame/system.rs b/testing/integration-tests/src/frame/system.rs index b8777025ac..1b3823cead 100644 --- a/testing/integration-tests/src/frame/system.rs +++ b/testing/integration-tests/src/frame/system.rs @@ -4,19 +4,21 @@ use crate::{ node_runtime::{self, system}, - pair_signer, test_context, + test_context, }; use assert_matches::assert_matches; -use sp_keyring::AccountKeyring; +use subxt_signer::sr25519::dev; #[tokio::test] async fn storage_account() -> Result<(), subxt::Error> { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); + let alice = dev::alice(); - let account_info_addr = node_runtime::storage().system().account(alice.account_id()); + let account_info_addr = node_runtime::storage() + .system() + .account(alice.public_key().to_account_id()); let account_info = api .storage() @@ -34,7 +36,7 @@ async fn tx_remark_with_event() -> Result<(), subxt::Error> { let ctx = test_context().await; let api = ctx.client(); - let alice = pair_signer(AccountKeyring::Alice.pair()); + let alice = dev::alice(); let tx = node_runtime::tx() .system() diff --git a/testing/integration-tests/src/runtime_api/mod.rs b/testing/integration-tests/src/runtime_api/mod.rs index c70b1fdfd2..a87be32fc7 100644 --- a/testing/integration-tests/src/runtime_api/mod.rs +++ b/testing/integration-tests/src/runtime_api/mod.rs @@ -2,22 +2,22 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::{node_runtime, pair_signer, test_context}; -use sp_keyring::AccountKeyring; +use crate::{node_runtime, test_context}; use subxt::utils::AccountId32; +use subxt_signer::sr25519::dev; #[tokio::test] async fn account_nonce() -> Result<(), subxt::Error> { let ctx = test_context().await; let api = ctx.client(); - let signer = pair_signer(AccountKeyring::Alice.pair()); - let alice: AccountId32 = AccountKeyring::Alice.to_account_id().into(); + let alice = dev::alice(); + let alice_account_id: AccountId32 = alice.public_key().into(); // Check Alice nonce is starting from 0. let runtime_api_call = node_runtime::apis() .account_nonce_api() - .account_nonce(alice.clone()); + .account_nonce(alice_account_id.clone()); let nonce = api .runtime_api() .at_latest() @@ -29,14 +29,14 @@ async fn account_nonce() -> Result<(), subxt::Error> { // Do some transaction to bump the Alice nonce to 1: let remark_tx = node_runtime::tx().system().remark(vec![1, 2, 3, 4, 5]); api.tx() - .sign_and_submit_then_watch_default(&remark_tx, &signer) + .sign_and_submit_then_watch_default(&remark_tx, &alice) .await? .wait_for_finalized_success() .await?; let runtime_api_call = node_runtime::apis() .account_nonce_api() - .account_nonce(alice); + .account_nonce(alice_account_id); let nonce = api .runtime_api() .at_latest() diff --git a/testing/integration-tests/src/storage/mod.rs b/testing/integration-tests/src/storage/mod.rs index eb2a1021c4..125d44a0a2 100644 --- a/testing/integration-tests/src/storage/mod.rs +++ b/testing/integration-tests/src/storage/mod.rs @@ -2,9 +2,9 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::{node_runtime, pair_signer, test_context, utils::wait_for_blocks}; -use sp_keyring::AccountKeyring; +use crate::{node_runtime, test_context, utils::wait_for_blocks}; use subxt::utils::AccountId32; +use subxt_signer::sr25519::dev; #[tokio::test] async fn storage_plain_lookup() -> Result<(), subxt::Error> { @@ -32,8 +32,8 @@ async fn storage_map_lookup() -> Result<(), subxt::Error> { let ctx = test_context().await; let api = ctx.client(); - let signer = pair_signer(AccountKeyring::Alice.pair()); - let alice: AccountId32 = AccountKeyring::Alice.to_account_id().into(); + let signer = dev::alice(); + let alice: AccountId32 = dev::alice().public_key().into(); // Do some transaction to bump the Alice nonce to 1: let remark_tx = node_runtime::tx().system().remark(vec![1, 2, 3, 4, 5]); @@ -98,9 +98,9 @@ async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error> { // Boilerplate; we create a new asset class with ID 99, and then // we "approveTransfer" of some of this asset class. This gives us an // entry in the `Approvals` StorageNMap that we can try to look up. - let signer = pair_signer(AccountKeyring::Alice.pair()); - let alice: AccountId32 = AccountKeyring::Alice.to_account_id().into(); - let bob: AccountId32 = AccountKeyring::Bob.to_account_id().into(); + let signer = dev::alice(); + let alice: AccountId32 = dev::alice().public_key().into(); + let bob: AccountId32 = dev::bob().public_key().into(); let tx1 = node_runtime::tx() .assets() diff --git a/testing/integration-tests/src/utils/context.rs b/testing/integration-tests/src/utils/context.rs index 6aa1374f64..d816ab7fae 100644 --- a/testing/integration-tests/src/utils/context.rs +++ b/testing/integration-tests/src/utils/context.rs @@ -4,14 +4,12 @@ pub(crate) use crate::{node_runtime, TestNodeProcess}; -use sp_core::sr25519::Pair; -use sp_keyring::AccountKeyring; -use subxt::{tx::PairSigner, SubstrateConfig}; +use subxt::SubstrateConfig; /// substrate node should be installed on the $PATH const SUBSTRATE_NODE_PATH: &str = "substrate"; -pub async fn test_context_with(key: AccountKeyring) -> TestContext { +pub async fn test_context_with(authority: String) -> TestContext { let path = std::env::var("SUBSTRATE_NODE_PATH").unwrap_or_else(|_| { if which::which(SUBSTRATE_NODE_PATH).is_err() { panic!( @@ -23,16 +21,12 @@ pub async fn test_context_with(key: AccountKeyring) -> TestContext { }); let mut proc = TestContext::build(path.as_str()); - proc.with_authority(key); + proc.with_authority(authority); proc.spawn::().await.unwrap() } pub type TestContext = TestNodeProcess; pub async fn test_context() -> TestContext { - test_context_with(AccountKeyring::Alice).await -} - -pub fn pair_signer(pair: Pair) -> PairSigner { - PairSigner::new(pair) + test_context_with("alice".to_string()).await } diff --git a/testing/integration-tests/src/utils/node_proc.rs b/testing/integration-tests/src/utils/node_proc.rs index dd4adfe091..5fdf699ead 100644 --- a/testing/integration-tests/src/utils/node_proc.rs +++ b/testing/integration-tests/src/utils/node_proc.rs @@ -2,7 +2,6 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use sp_keyring::AccountKeyring; use std::ffi::{OsStr, OsString}; use substrate_runner::SubstrateNode; use subxt::{Config, OnlineClient}; @@ -35,7 +34,7 @@ where /// Construct a test node process. pub struct TestNodeProcessBuilder { node_path: OsString, - authority: Option, + authority: Option, } impl TestNodeProcessBuilder { @@ -50,7 +49,7 @@ impl TestNodeProcessBuilder { } /// Set the authority dev account for a node in validator mode e.g. --alice. - pub fn with_authority(&mut self, account: AccountKeyring) -> &mut Self { + pub fn with_authority(&mut self, account: String) -> &mut Self { self.authority = Some(account); self } @@ -64,9 +63,8 @@ impl TestNodeProcessBuilder { node_builder.binary_path(self.node_path); - if let Some(authority) = self.authority { - let authority = format!("{authority:?}"); - node_builder.arg(authority.as_str().to_lowercase()); + if let Some(authority) = &self.authority { + node_builder.arg(authority.to_lowercase()); } // Spawn the node and retrieve a URL to it: diff --git a/testing/test-runtime/Cargo.toml b/testing/test-runtime/Cargo.toml index b8085a4b6c..1638232bb5 100644 --- a/testing/test-runtime/Cargo.toml +++ b/testing/test-runtime/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" publish = false [dependencies] -subxt = { workspace = true } +subxt = { workspace = true, features = ["native"] } [build-dependencies] substrate-runner = { workspace = true } diff --git a/testing/ui-tests/Cargo.toml b/testing/ui-tests/Cargo.toml index a56bf30d0d..980f1b09d3 100644 --- a/testing/ui-tests/Cargo.toml +++ b/testing/ui-tests/Cargo.toml @@ -13,5 +13,5 @@ trybuild = { workspace = true } scale-info = { workspace = true, features = ["bit-vec"] } frame-metadata ={ workspace = true } codec = { package = "parity-scale-codec", workspace = true, features = ["derive", "bit-vec"] } -subxt = { workspace = true } +subxt = { workspace = true, features = ["native", "jsonrpsee"] } subxt-metadata = { workspace = true } diff --git a/testing/wasm-tests/Cargo.lock b/testing/wasm-tests/Cargo.lock index d39a8bc8f7..d0036ffd49 100644 --- a/testing/wasm-tests/Cargo.lock +++ b/testing/wasm-tests/Cargo.lock @@ -988,18 +988,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -1119,23 +1119,24 @@ dependencies = [ [[package]] name = "scale-decode" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e5527e4b3bf079d4c0b2f253418598c380722ba37ef20fac9088081407f2b6" +checksum = "f0459d00b0dbd2e765009924a78ef36b2ff7ba116292d732f00eb0ed8e465d15" dependencies = [ "parity-scale-codec", "primitive-types", "scale-bits", "scale-decode-derive", "scale-info", + "smallvec", "thiserror", ] [[package]] name = "scale-decode-derive" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b38741b2f78e4391b94eac6b102af0f6ea2b0f7fe65adb55d7f4004f507854db" +checksum = "4391f0dfbb6690f035f6d2a15d6a12f88cc5395c36bcc056db07ffa2a90870ec" dependencies = [ "darling 0.14.4", "proc-macro-crate", @@ -1146,23 +1147,24 @@ dependencies = [ [[package]] name = "scale-encode" -version = "0.1.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15546e5efbb45f0fc2291f7e202dee8623274c5d8bbfdf9c6886cc8b44a7ced3" +checksum = "b0401b7cdae8b8aa33725f3611a051358d5b32887ecaa0fda5953a775b2d4d76" dependencies = [ "parity-scale-codec", "primitive-types", "scale-bits", "scale-encode-derive", "scale-info", + "smallvec", "thiserror", ] [[package]] name = "scale-encode-derive" -version = "0.1.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd983cf0a9effd76138554ead18a6de542d1af175ac12fd5e91836c5c0268082" +checksum = "316e0fb10ec0fee266822bd641bab5e332a4ab80ef8c5b5ff35e5401a394f5a6" dependencies = [ "darling 0.14.4", "proc-macro-crate", @@ -1199,10 +1201,12 @@ dependencies = [ [[package]] name = "scale-value" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f549769261561e6764218f847e500588f9a79a289de49ce92f9e26642a3574" +checksum = "f2096d36e94ce9bf87d8addb752423b6b19730dc88edd7cc452bb2b90573f7a7" dependencies = [ + "base58", + "blake2", "either", "frame-metadata", "parity-scale-codec", @@ -1271,18 +1275,18 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", @@ -1352,6 +1356,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + [[package]] name = "socket2" version = "0.4.9" @@ -1379,9 +1389,9 @@ dependencies = [ [[package]] name = "sp-core-hashing" -version = "8.0.0" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27449abdfbe41b473e625bce8113745e81d65777dd1d5a8462cf24137930dad8" +checksum = "2ee599a8399448e65197f9a6cee338ad192e9023e35e31f22382964c3c174c68" dependencies = [ "blake2b_simd", "byteorder", @@ -1394,9 +1404,9 @@ dependencies = [ [[package]] name = "sp-std" -version = "7.0.0" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de8eef39962b5b97478719c493bed2926cf70cb621005bbf68ebe58252ff986" +checksum = "53458e3c57df53698b3401ec0934bea8e8cfce034816873c0b0abbd83d7bac0d" [[package]] name = "spin" @@ -1418,13 +1428,13 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.5.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "subxt" -version = "0.28.0" +version = "0.29.0" dependencies = [ "base58", "blake2", @@ -1454,7 +1464,7 @@ dependencies = [ [[package]] name = "subxt-codegen" -version = "0.28.0" +version = "0.29.0" dependencies = [ "frame-metadata", "heck", @@ -1472,7 +1482,7 @@ dependencies = [ [[package]] name = "subxt-macro" -version = "0.28.0" +version = "0.29.0" dependencies = [ "darling 0.20.1", "proc-macro-error", @@ -1482,7 +1492,7 @@ dependencies = [ [[package]] name = "subxt-metadata" -version = "0.28.0" +version = "0.29.0" dependencies = [ "frame-metadata", "parity-scale-codec", @@ -1841,7 +1851,7 @@ dependencies = [ ] [[package]] -name = "wasm-test" +name = "wasm-tests" version = "0.1.0" dependencies = [ "console_error_panic_hook", diff --git a/testing/wasm-tests/Cargo.toml b/testing/wasm-tests/Cargo.toml index ac1a04d963..396e5be816 100644 --- a/testing/wasm-tests/Cargo.toml +++ b/testing/wasm-tests/Cargo.toml @@ -1,15 +1,16 @@ [package] -name = "wasm-test" +name = "wasm-tests" version = "0.1.0" edition = "2021" publish = false [dev-dependencies] -# This crate is not a part of the workspace, sadly, because it -# requires a mutually exclusive jsonrpsee feature to be enabled on -# subxt to work. So, we can't inherit versions. wasm-bindgen-test = "0.3.24" tracing-wasm = "0.2.1" console_error_panic_hook = "0.1.7" serde_json = "1" -subxt = { path = "../../subxt", default-features = false, features = ["jsonrpsee-web"] } + +# This crate is not a part of the workspace, because it +# requires the "jsonrpsee-web" feature to be enabled, which we don't +# want enabled for workspace builds in general. +subxt = { path = "../../subxt", default-features = false, features = ["web", "jsonrpsee"] } diff --git a/testing/wasm-tests/tests/wasm.rs b/testing/wasm-tests/tests/wasm.rs index ccfac7e763..e91aaed0b9 100644 --- a/testing/wasm-tests/tests/wasm.rs +++ b/testing/wasm-tests/tests/wasm.rs @@ -5,7 +5,13 @@ use wasm_bindgen_test::*; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); -/// Run the tests by `$ wasm-pack test --firefox --headless` +// Run the tests by calling: +// +// ```text +// wasm-pack test --firefox --headless` +// ``` +// +// You'll need to have a substrate/polkadot node running too with eg `substrate --dev`. #[wasm_bindgen_test] async fn wasm_ws_transport_works() {