From 4831f816f21b1dc55bfc28296e60b989e3486136 Mon Sep 17 00:00:00 2001 From: Tadeo Hepperle <62739623+tadeohepperle@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:04:27 +0100 Subject: [PATCH] `no_std` compatibility for `subxt-signer` (#1477) * subxt-signer no-std * impl error * address james comments * fix signer wasm tests * error impl for secret uri error --- Cargo.lock | 15 +- Cargo.toml | 39 ++-- cli/src/commands/explore/runtime_apis/mod.rs | 2 +- codegen/Cargo.toml | 2 +- examples/wasm-example/Cargo.lock | 104 +--------- lightclient/Cargo.toml | 2 +- signer/Cargo.toml | 15 +- signer/src/crypto/secret_uri.rs | 13 +- signer/src/crypto/seed_from_entropy.rs | 1 + signer/src/ecdsa.rs | 44 ++-- signer/src/lib.rs | 9 +- signer/src/sr25519.rs | 29 ++- signer/src/utils.rs | 27 +-- signer/wasm-tests/Cargo.toml | 5 +- subxt/Cargo.toml | 16 +- testing/no-std-tests/Cargo.lock | 204 +++++++++++++++++++ testing/no-std-tests/Cargo.toml | 1 + testing/no-std-tests/src/main.rs | 21 +- 18 files changed, 353 insertions(+), 196 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 467dd253fe..891d3e986b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2865,6 +2865,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.14" @@ -2887,6 +2898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", + "password-hash", ] [[package]] @@ -4660,6 +4672,8 @@ name = "subxt-signer" version = "0.34.0" dependencies = [ "bip39", + "cfg-if", + "derive_more", "getrandom", "hex", "hmac 0.12.1", @@ -4674,7 +4688,6 @@ dependencies = [ "sp-core-hashing", "sp-keyring", "subxt", - "thiserror", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index 4fe151c2ad..c0d7e7f348 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ base58 = { version = "0.2.0" } bitvec = { version = "1", default-features = false } blake2 = { version = "0.10.6", default-features = false } clap = { version = "4.5.2", features = ["derive", "cargo"] } +cfg-if = "1.0.0" criterion = "0.4" codec = { package = "parity-scale-codec", version = "3.6.9", default-features = false } color-eyre = "0.6.1" @@ -71,33 +72,33 @@ console_error_panic_hook = "0.1.7" darling = "0.20.8" derivative = "2.2.0" derive_more = "0.99.17" -either = "1.10.0" +either = { version = "1.10.0", default-features = false } frame-metadata = { version = "16.0.0", default-features = false } futures = { version = "0.3.30", default-features = false, features = ["std"] } getrandom = { version = "0.2", default-features = false } hashbrown = "0.14.3" -hex = "0.4.3" +hex = { version = "0.4.3", default-features = false } heck = "0.4.1" -impl-serde = { version = "0.4.0" } +impl-serde = { version = "0.4.0", default-features = false } indoc = "2" jsonrpsee = { version = "0.22" } pretty_assertions = "1.4.0" -primitive-types = { version = "0.12.2", default-features = false, features = ["codec", "scale-info", "serde"] } +primitive-types = { version = "0.12.2", default-features = false } proc-macro-error = "1.0.4" proc-macro2 = "1.0.78" quote = "1.0.35" -regex = "1.10.3" +regex = { version = "1.10.3", default-features = false } scale-info = { version = "2.10.0", default-features = false } -scale-value = "0.14.1" -scale-bits = "0.5.0" -scale-decode = "0.11.1" -scale-encode = "0.6.0" -serde = { version = "1.0.197" } -serde_json = { version = "1.0.114" } +scale-value = { version = "0.14.1", default-features = false } +scale-bits = { version = "0.5.0", default-features = false } +scale-decode = { version = "0.11.1", default-features = false } +scale-encode = { version = "0.6.0", default-features = false } +serde = { version = "1.0.197", default-features = false, features = ["derive"] } +serde_json = { version = "1.0.114", default-features = false } syn = { version = "2.0.15", features = ["full", "extra-traits"] } thiserror = "1.0.57" tokio = { version = "1.36", default-features = false } -tracing = "0.1.40" +tracing = { version = "0.1.40", default-features = false } tracing-wasm = "0.2.1" tracing-subscriber = "0.3.18" trybuild = "1.0.89" @@ -136,21 +137,21 @@ sp-keyring = "31.0.0" # Subxt workspace crates: subxt = { version = "0.34.0", path = "subxt", default-features = false } subxt-macro = { version = "0.34.0", path = "macro" } -subxt-metadata = { version = "0.34.0", path = "metadata" } +subxt-metadata = { version = "0.34.0", path = "metadata", default-features = false } subxt-codegen = { version = "0.34.0", path = "codegen" } -subxt-signer = { version = "0.34.0", path = "signer" } +subxt-signer = { version = "0.34.0", path = "signer", default-features = false } subxt-lightclient = { version = "0.34.0", path = "lightclient", default-features = false } 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" +bip39 = { version = "2.0.0", default-features = false } +hmac = { version = "0.12.1", default-features = false } pbkdf2 = { version = "0.12.2", default-features = false } -schnorrkel = "0.11.4" -secp256k1 = "0.28.2" +schnorrkel = { version = "0.11.4", default-features = false } +secp256k1 = { version = "0.28.2", default-features = false } secrecy = "0.8.0" -sha2 = "0.10.8" +sha2 = { version = "0.10.8", default-features = false } zeroize = { version = "1", default-features = false } [profile.dev.package.smoldot-light] diff --git a/cli/src/commands/explore/runtime_apis/mod.rs b/cli/src/commands/explore/runtime_apis/mod.rs index e3fed9ac01..cd233f068a 100644 --- a/cli/src/commands/explore/runtime_apis/mod.rs +++ b/cli/src/commands/explore/runtime_apis/mod.rs @@ -19,7 +19,7 @@ use subxt_metadata::RuntimeApiMetadata; /// Runs for a specified runtime API trait. /// Cases to consider: -/// ```norun +/// ```txt /// method is: /// None => Show pallet docs + available methods /// Some (invalid) => Show Error + available methods diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 1cf9638bae..5ffde7e92f 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -26,7 +26,7 @@ syn = { workspace = true } scale-info = { workspace = true } subxt-metadata = { workspace = true } jsonrpsee = { workspace = true, features = ["async-client", "client-ws-transport-native-tls", "http-client"], optional = true } -hex = { workspace = true } +hex = { workspace = true, features = ["std"] } tokio = { workspace = true, features = ["rt-multi-thread"], optional = true } thiserror = { workspace = true } scale-typegen = { workspace = true } diff --git a/examples/wasm-example/Cargo.lock b/examples/wasm-example/Cargo.lock index 69aa797f9a..003fb839bc 100644 --- a/examples/wasm-example/Cargo.lock +++ b/examples/wasm-example/Cargo.lock @@ -464,38 +464,14 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", -] - [[package]] name = "darling" version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ - "darling_core 0.20.8", - "darling_macro 0.20.8", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", + "darling_core", + "darling_macro", ] [[package]] @@ -512,24 +488,13 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core 0.14.4", - "quote", - "syn 1.0.109", -] - [[package]] name = "darling_macro" version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ - "darling_core 0.20.8", + "darling_core", "quote", "syn 2.0.48", ] @@ -653,9 +618,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ - "byteorder", - "rand", - "rustc-hex", "static_assertions", ] @@ -694,7 +656,6 @@ dependencies = [ "cfg-if", "parity-scale-codec", "scale-info", - "serde", ] [[package]] @@ -1227,15 +1188,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "impl-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" -dependencies = [ - "parity-scale-codec", -] - [[package]] name = "impl-serde" version = "0.4.0" @@ -1794,9 +1746,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", - "impl-codec", - "impl-serde", - "scale-info", "uint", ] @@ -1947,12 +1896,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - [[package]] name = "rustc_version" version = "0.4.0" @@ -2102,25 +2045,11 @@ checksum = "afc79ba56a1c742f5aeeed1f1801f3edf51f7e818f0a54582cac6f131364ea7b" dependencies = [ "derive_more", "parity-scale-codec", - "primitive-types", "scale-bits", - "scale-decode-derive", "scale-type-resolver", "smallvec", ] -[[package]] -name = "scale-decode-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5398fdb3c7bea3cb419bac4983aadacae93fe1a7b5f693f4ebd98c3821aad7a5" -dependencies = [ - "darling 0.14.4", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "scale-encode" version = "0.6.0" @@ -2129,26 +2058,11 @@ checksum = "628800925a33794fb5387781b883b5e14d130fece9af5a63613867b8de07c5c7" dependencies = [ "derive_more", "parity-scale-codec", - "primitive-types", "scale-bits", - "scale-encode-derive", "scale-type-resolver", "smallvec", ] -[[package]] -name = "scale-encode-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a304e1af7cdfbe7a24e08b012721456cc8cecdedadc14b3d10513eada63233c" -dependencies = [ - "darling 0.14.4", - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "scale-info" version = "2.10.0" @@ -2204,8 +2118,6 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07ccfee963104335c971aaf8b7b0e749be8569116322df23f1f75c4ca9e4a28" dependencies = [ - "base58", - "blake2", "derive_more", "either", "frame-metadata 15.1.0", @@ -2215,8 +2127,6 @@ dependencies = [ "scale-encode", "scale-info", "scale-type-resolver", - "serde", - "yap", ] [[package]] @@ -2670,7 +2580,7 @@ dependencies = [ name = "subxt-macro" version = "0.34.0" dependencies = [ - "darling 0.20.8", + "darling", "parity-scale-codec", "proc-macro-error", "quote", @@ -3316,12 +3226,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "yap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4524214bc4629eba08d78ceb1d6507070cc0bcbbed23af74e19e6e924a24cf" - [[package]] name = "yew" version = "0.20.0" diff --git a/lightclient/Cargo.toml b/lightclient/Cargo.toml index eb079b3e90..22475b75b9 100644 --- a/lightclient/Cargo.toml +++ b/lightclient/Cargo.toml @@ -58,7 +58,7 @@ web = [ [dependencies] futures = { workspace = true } serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true, features = ["raw_value"] } +serde_json = { workspace = true, features = ["default", "raw_value"] } thiserror = { workspace = true } tracing = { workspace = true } diff --git a/signer/Cargo.toml b/signer/Cargo.toml index 226237c315..7678256460 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -15,7 +15,8 @@ description = "Sign extrinsics to be submitted by Subxt" keywords = ["parity", "subxt", "extrinsic", "signer"] [features] -default = ["sr25519", "ecdsa", "subxt", "native"] +default = ["sr25519", "ecdsa", "subxt", "std", "native"] +std = ["regex/std", "sp-core-hashing/std", "pbkdf2/std", "sha2/std", "hmac/std", "bip39/std", "schnorrkel/std", "secp256k1/std", "sp-core/std"] # Pick the signer implementation(s) you need by enabling the # corresponding features. Note: I had more difficulties getting @@ -34,26 +35,28 @@ web = ["getrandom/js", "subxt?/web"] native = ["subxt?/native"] [dependencies] -subxt = { workspace = true, optional = true, default-features = false } -regex = { workspace = true } +subxt = { workspace = true, optional = true } +regex = { workspace = true, features = ["unicode"] } hex = { workspace = true } +cfg-if = { workspace = true } codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] } sp-core-hashing = { workspace = true } -thiserror = { workspace = true } +derive_more = { workspace = true } pbkdf2 = { workspace = true } sha2 = { workspace = true } hmac = { workspace = true } zeroize = { workspace = true } bip39 = { workspace = true } schnorrkel = { workspace = true, optional = true } -secp256k1 = { workspace = true, features = ["recovery", "global-context"], optional = true } +secp256k1 = { workspace = true, optional = true, features = ["alloc", "recovery"] } 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-core = { workspace = true } sp-keyring = { workspace = true } [package.metadata.cargo-machete] diff --git a/signer/src/crypto/secret_uri.rs b/signer/src/crypto/secret_uri.rs index 7be43b8286..5148311795 100644 --- a/signer/src/crypto/secret_uri.rs +++ b/signer/src/crypto/secret_uri.rs @@ -3,6 +3,8 @@ // see LICENSE for license details. use super::DeriveJunction; +use alloc::vec::Vec; +use derive_more::Display; use regex::Regex; use secrecy::SecretString; @@ -88,7 +90,7 @@ pub struct SecretUri { pub junctions: Vec, } -impl std::str::FromStr for SecretUri { +impl core::str::FromStr for SecretUri { type Err = SecretUriError; fn from_str(s: &str) -> Result { @@ -115,14 +117,17 @@ impl std::str::FromStr for SecretUri { } /// This is returned if `FromStr` cannot parse a string into a `SecretUri`. -#[derive(Debug, Copy, Clone, PartialEq, thiserror::Error)] +#[derive(Debug, Copy, Clone, PartialEq, Display)] pub enum SecretUriError { /// Parsing the secret URI from a string failed; wrong format. - #[error("Invalid secret phrase format")] + #[display(fmt = "Invalid secret phrase format")] InvalidFormat, } -once_static! { +#[cfg(feature = "std")] +impl std::error::Error for SecretUriError {} + +once_static_cloned! { /// Interpret a phrase like: /// /// ```text diff --git a/signer/src/crypto/seed_from_entropy.rs b/signer/src/crypto/seed_from_entropy.rs index 7643ff7835..e665c132ab 100644 --- a/signer/src/crypto/seed_from_entropy.rs +++ b/signer/src/crypto/seed_from_entropy.rs @@ -2,6 +2,7 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +use alloc::string::String; use hmac::Hmac; use pbkdf2::pbkdf2; use sha2::Sha512; diff --git a/signer/src/ecdsa.rs b/signer/src/ecdsa.rs index a76054abf1..a25c8cc776 100644 --- a/signer/src/ecdsa.rs +++ b/signer/src/ecdsa.rs @@ -6,8 +6,10 @@ use codec::Encode; use crate::crypto::{seed_from_entropy, DeriveJunction, SecretUri}; +use core::str::FromStr; +use derive_more::{Display, From}; use hex::FromHex; -use secp256k1::{ecdsa::RecoverableSignature, Message, SecretKey, SECP256K1}; +use secp256k1::{ecdsa::RecoverableSignature, Message, Secp256k1, SecretKey}; use secrecy::ExposeSecret; const SEED_LENGTH: usize = 32; @@ -68,7 +70,7 @@ impl Keypair { let seed = Seed::from_hex(hex_str)?; Self::from_seed(seed)? } else { - let phrase = bip39::Mnemonic::parse(phrase.expose_secret().as_str())?; + let phrase = bip39::Mnemonic::from_str(phrase.expose_secret().as_str())?; let pass_str = password.as_ref().map(|p| p.expose_secret().as_str()); Self::from_phrase(&phrase, pass_str)? }; @@ -91,8 +93,9 @@ impl Keypair { /// 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 (arr, len) = mnemonic.to_entropy_array(); + let big_seed = + seed_from_entropy(&arr[0..len], password.unwrap_or("")).ok_or(Error::InvalidSeed)?; let seed: Seed = big_seed[..SEED_LENGTH] .try_into() @@ -109,7 +112,8 @@ impl Keypair { pub fn from_seed(seed: Seed) -> Result { let secret = SecretKey::from_slice(&seed).map_err(|_| Error::InvalidSeed)?; Ok(Self(secp256k1::Keypair::from_secret_key( - SECP256K1, &secret, + &Secp256k1::signing_only(), + &secret, ))) } @@ -161,9 +165,9 @@ impl Keypair { // From sp_core::ecdsa::sign_prehashed: let wrapped = Message::from_digest_slice(&message_hash).expect("Message is 32 bytes; qed"); let recsig: RecoverableSignature = - SECP256K1.sign_ecdsa_recoverable(&wrapped, &self.0.secret_key()); + Secp256k1::signing_only().sign_ecdsa_recoverable(&wrapped, &self.0.secret_key()); // From sp_core::ecdsa's `impl From for Signature`: - let (recid, sig) = recsig.serialize_compact(); + let (recid, sig): (_, [u8; 64]) = recsig.serialize_compact(); let mut signature_bytes: [u8; 65] = [0; 65]; signature_bytes[..64].copy_from_slice(&sig); signature_bytes[64] = (recid.to_i32() & 0xFF) as u8; @@ -192,31 +196,39 @@ pub fn verify>(sig: &Signature, message: M, pubkey: &PublicKey) - }; let message_hash = sp_core_hashing::blake2_256(message.as_ref()); let wrapped = Message::from_digest_slice(&message_hash).expect("Message is 32 bytes; qed"); - signature.verify(&wrapped, &public).is_ok() + + Secp256k1::verification_only() + .verify_ecdsa(&wrapped, &signature, &public) + .is_ok() } /// An error handed back if creating a keypair fails. -#[derive(Debug, PartialEq, thiserror::Error)] +#[derive(Debug, PartialEq, Display, From)] pub enum Error { /// Invalid seed. - #[error("Invalid seed (was it the wrong length?)")] + #[display(fmt = "Invalid seed (was it the wrong length?)")] + #[from(ignore)] InvalidSeed, /// Invalid seed. - #[error("Invalid seed for ECDSA, contained soft junction")] + #[display(fmt = "Invalid seed for ECDSA, contained soft junction")] + #[from(ignore)] SoftJunction, /// Invalid phrase. - #[error("Cannot parse phrase: {0}")] - Phrase(#[from] bip39::Error), + #[display(fmt = "Cannot parse phrase: {_0}")] + Phrase(bip39::Error), /// Invalid hex. - #[error("Cannot parse hex string: {0}")] - Hex(#[from] hex::FromHexError), + #[display(fmt = "Cannot parse hex string: {_0}")] + Hex(hex::FromHexError), } +#[cfg(feature = "std")] +impl std::error::Error for Error {} + /// 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; + use core::str::FromStr; once_static_cloned! { /// Equivalent to `{DEV_PHRASE}//Alice`. diff --git a/signer/src/lib.rs b/signer/src/lib.rs index 2c79d6a1ec..db13732327 100644 --- a/signer/src/lib.rs +++ b/signer/src/lib.rs @@ -14,6 +14,9 @@ //! subxt transactions for chains supporting sr25519 signatures. #![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; #[macro_use] mod utils; @@ -40,9 +43,3 @@ 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}; - -#[cfg(any( - all(feature = "web", feature = "native"), - not(any(feature = "web", feature = "native")) -))] -compile_error!("subxt-signer: exactly one of the 'web' and 'native' features should be used."); diff --git a/signer/src/sr25519.rs b/signer/src/sr25519.rs index 01e6cc84b1..344e7b2655 100644 --- a/signer/src/sr25519.rs +++ b/signer/src/sr25519.rs @@ -4,7 +4,11 @@ //! An sr25519 keypair implementation. +use core::str::FromStr; + use crate::crypto::{seed_from_entropy, DeriveJunction, SecretUri}; + +use derive_more::{Display, From}; use hex::FromHex; use schnorrkel::{ derive::{ChainCode, Derivation}, @@ -72,7 +76,7 @@ impl Keypair { let seed = Seed::from_hex(hex_str)?; Self::from_seed(seed)? } else { - let phrase = bip39::Mnemonic::parse(phrase.expose_secret().as_str())?; + let phrase = bip39::Mnemonic::from_str(phrase.expose_secret().as_str())?; let pass_str = password.as_ref().map(|p| p.expose_secret().as_str()); Self::from_phrase(&phrase, pass_str)? }; @@ -95,8 +99,9 @@ impl Keypair { /// 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 (arr, len) = mnemonic.to_entropy_array(); + let big_seed = + seed_from_entropy(&arr[0..len], password.unwrap_or("")).ok_or(Error::InvalidSeed)?; let seed: Seed = big_seed[..SEED_LENGTH] .try_into() @@ -187,24 +192,28 @@ pub fn verify>(sig: &Signature, message: M, pubkey: &PublicKey) - } /// An error handed back if creating a keypair fails. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Display, From)] pub enum Error { /// Invalid seed. - #[error("Invalid seed (was it the wrong length?)")] + #[display(fmt = "Invalid seed (was it the wrong length?)")] + #[from(ignore)] InvalidSeed, /// Invalid phrase. - #[error("Cannot parse phrase: {0}")] - Phrase(#[from] bip39::Error), + #[display(fmt = "Cannot parse phrase: {_0}")] + Phrase(bip39::Error), /// Invalid hex. - #[error("Cannot parse hex string: {0}")] - Hex(#[from] hex::FromHexError), + #[display(fmt = "Cannot parse hex string: {_0}")] + Hex(hex::FromHexError), } +#[cfg(feature = "std")] +impl std::error::Error for Error {} + /// 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; + use core::str::FromStr; once_static_cloned! { /// Equivalent to `{DEV_PHRASE}//Alice`. diff --git a/signer/src/utils.rs b/signer/src/utils.rs index 2e12665419..e3cea3cfc8 100644 --- a/signer/src/utils.rs +++ b/signer/src/utils.rs @@ -7,34 +7,29 @@ /// Use like: /// /// ```rust,ignore -/// once_static!{ +/// once_static_cloned!{ /// /// 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 +/// +/// 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() + cfg_if::cfg_if! { + if #[cfg(feature = "std")] { + static VAR: std::sync::OnceLock<$ty> = std::sync::OnceLock::new(); + VAR.get_or_init(|| { $expr }).clone() + } else { + { $expr } + } + } } )+ }; diff --git a/signer/wasm-tests/Cargo.toml b/signer/wasm-tests/Cargo.toml index 008dc84018..5bf218a4c2 100644 --- a/signer/wasm-tests/Cargo.toml +++ b/signer/wasm-tests/Cargo.toml @@ -8,14 +8,13 @@ publish = false 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", "sr25519", "ecdsa"] } +subxt-signer = { path = "..", default-features = false, features = ["web", "sr25519", "ecdsa", "std"] } # 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 +[workspace] diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index 67c553fa50..9bba68432b 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -66,15 +66,15 @@ unstable-light-client = ["subxt-lightclient", "tokio-stream"] [dependencies] async-trait = { workspace = true } codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] } -scale-info = { workspace = true } -scale-value = { workspace = true } -scale-bits = { workspace = true } -scale-decode = { workspace = true } -scale-encode = { workspace = true } +scale-info = { workspace = true, features = ["default"] } +scale-value = { workspace = true, features = ["default"] } +scale-bits = { workspace = true, features = ["default"] } +scale-decode = { workspace = true, features = ["default"] } +scale-encode = { workspace = true, features = ["default"] } futures = { workspace = true } hex = { workspace = true } serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true, features = ["raw_value"] } +serde_json = { workspace = true, features = ["default", "raw_value"] } thiserror = { workspace = true } tracing = { workspace = true } frame-metadata = { workspace = true } @@ -84,7 +84,7 @@ instant = { workspace = true } # Provides some deserialization, types like U256/H256 and hashing impls like twox/blake256: impl-serde = { workspace = true } -primitive-types = { workspace = true } +primitive-types = { workspace = true, features = ["codec", "scale-info", "serde"] } sp-core-hashing = { workspace = true } # For ss58 encoding AccountId32 to serialize them properly: @@ -100,7 +100,7 @@ sp-runtime = { workspace = true, optional = true } # Other subxt crates we depend on. subxt-macro = { workspace = true } -subxt-metadata = { workspace = true } +subxt-metadata = { workspace = true, features = ["std"] } subxt-lightclient = { workspace = true, optional = true, default-features = false } # Light client support: diff --git a/testing/no-std-tests/Cargo.lock b/testing/no-std-tests/Cargo.lock index ee4afadfe7..c04633ecae 100644 --- a/testing/no-std-tests/Cargo.lock +++ b/testing/no-std-tests/Cargo.lock @@ -32,6 +32,21 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "bip39" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +dependencies = [ + "bitcoin_hashes", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" + [[package]] name = "blake2b_simd" version = "1.0.2" @@ -101,6 +116,34 @@ dependencies = [ "typenum", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -122,6 +165,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -130,6 +174,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fiat-crypto" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" + [[package]] name = "frame-metadata" version = "16.0.0" @@ -151,6 +201,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom_or_panic" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" +dependencies = [ + "rand_core", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -161,6 +220,21 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "impl-trait-for-tuples" version = "0.2.2" @@ -209,6 +283,18 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -239,6 +325,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", +] + +[[package]] +name = "platforms" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -277,6 +378,37 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "rustc_version" version = "0.4.0" @@ -310,6 +442,32 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "schnorrkel" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" +dependencies = [ + "arrayref", + "arrayvec", + "curve25519-dalek", + "getrandom_or_panic", + "merlin", + "rand_core", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + [[package]] name = "semver" version = "1.0.21" @@ -357,6 +515,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "subxt-core-no-std-tests" version = "0.0.0" @@ -364,6 +528,7 @@ dependencies = [ "libc_alloc", "parity-scale-codec", "subxt-metadata", + "subxt-signer", ] [[package]] @@ -378,6 +543,25 @@ dependencies = [ "sp-core-hashing", ] +[[package]] +name = "subxt-signer" +version = "0.34.0" +dependencies = [ + "bip39", + "cfg-if", + "derive_more", + "hex", + "hmac", + "parity-scale-codec", + "pbkdf2", + "regex", + "schnorrkel", + "secrecy", + "sha2", + "sp-core-hashing", + "zeroize", +] + [[package]] name = "syn" version = "1.0.109" @@ -485,3 +669,23 @@ dependencies = [ "quote", "syn 2.0.48", ] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] diff --git a/testing/no-std-tests/Cargo.toml b/testing/no-std-tests/Cargo.toml index 503326d5b5..8e53afbb0a 100644 --- a/testing/no-std-tests/Cargo.toml +++ b/testing/no-std-tests/Cargo.toml @@ -7,6 +7,7 @@ resolver = "2" [dependencies] subxt-metadata = { path = "../../metadata", default-features = false } +subxt-signer = { path = "../../signer", default-features = false, features = ["sr25519"] } codec = { package = "parity-scale-codec", version = "3.6.9", default-features = false, features = ["derive"] } libc_alloc = { version = "1.0.6" } diff --git a/testing/no-std-tests/src/main.rs b/testing/no-std-tests/src/main.rs index 09d8775fc5..479ff581b3 100644 --- a/testing/no-std-tests/src/main.rs +++ b/testing/no-std-tests/src/main.rs @@ -34,14 +34,27 @@ extern crate alloc; /// Including code here makes sure it is not pruned. /// We want all code included to compile fine for the `thumbv7em-none-eabi` target. fn compile_test() { - subxt_metadata_compiles(); -} - -fn subxt_metadata_compiles() { + // Subxt Metadata compiles: use codec::Decode; let bytes: alloc::vec::Vec = alloc::vec![0, 1, 2, 3, 4]; subxt_metadata::Metadata::decode(&mut &bytes[..]).expect_err("invalid byte sequence"); const METADATA: &[u8] = include_bytes!("../../../artifacts/polkadot_metadata_small.scale"); subxt_metadata::Metadata::decode(&mut &METADATA[..]).expect("should be valid metadata"); + + // Subxt Signer compiles: + use subxt_signer::sr25519; + let keypair = sr25519::dev::alice(); + let message = b"Hello!"; + let _signature = keypair.sign(message); + let _public_key = keypair.public_key(); + + // Note: `ecdsa` is not compiling for the `thumbv7em-none-eabi` target. + // + // use subxt_signer::ecdsa; + // let keypair = ecdsa::dev::alice(); + // let message = b"Hello!"; + // let _signature = keypair.sign(message); + // let _public_key = keypair.public_key(); + // }