From b5b0b786d1900382335e76563d6deb7348503493 Mon Sep 17 00:00:00 2001 From: Tsachi Herman <24438559+tsachiherman@users.noreply.github.com> Date: Fri, 22 Sep 2023 11:42:13 -0400 Subject: [PATCH] soroban-cli: upgrade ed25519-dalek to version 2.0.0 (#984) * update dalek dependency to use dalek 2.0.0 * clippy --- Cargo.lock | 80 +++---------------- cmd/soroban-cli/Cargo.toml | 2 +- .../src/commands/config/identity/address.rs | 4 +- cmd/soroban-cli/src/commands/config/mod.rs | 2 +- cmd/soroban-cli/src/commands/config/secret.rs | 6 +- cmd/soroban-cli/src/commands/contract/bump.rs | 5 +- .../src/commands/contract/deploy.rs | 9 ++- .../src/commands/contract/install.rs | 11 ++- .../src/commands/contract/invoke.rs | 15 ++-- .../src/commands/contract/restore.rs | 5 +- .../src/commands/lab/token/wrap.rs | 7 +- cmd/soroban-cli/src/rpc/mod.rs | 4 +- cmd/soroban-cli/src/rpc/transaction.rs | 16 ++-- cmd/soroban-cli/src/utils.rs | 17 ++-- 14 files changed, 69 insertions(+), 114 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86bee737e..165fb7c75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -810,8 +810,6 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek 3.2.0", "ed25519 1.5.3", - "rand 0.7.3", - "serde", "sha2 0.9.9", "zeroize", ] @@ -1019,17 +1017,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.10" @@ -1039,7 +1026,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -1559,7 +1546,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys", ] @@ -1888,7 +1875,7 @@ dependencies = [ "anyhow", "base64 0.21.4", "libc", - "rand 0.8.5", + "rand", "sha2 0.10.7", "soroban-env-host", "thiserror", @@ -1932,19 +1919,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -1952,20 +1926,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", + "rand_chacha", "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -1981,9 +1945,6 @@ name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] [[package]] name = "rand_core" @@ -1991,16 +1952,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", + "getrandom", ] [[package]] @@ -2027,7 +1979,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.10", + "getrandom", "redox_syscall 0.2.16", "thiserror", ] @@ -2516,7 +2468,7 @@ dependencies = [ "csv", "dirs", "dotenvy", - "ed25519-dalek 1.0.1", + "ed25519-dalek 2.0.0", "ethnum", "heck", "hex", @@ -2530,7 +2482,7 @@ dependencies = [ "openssl", "pathdiff", "predicates 2.1.5", - "rand 0.8.5", + "rand", "regex", "rpassword", "sep5", @@ -2596,13 +2548,13 @@ source = "git+https://github.com/stellar/rs-soroban-env?rev=8c63bff68a15d79aca3a dependencies = [ "backtrace", "ed25519-dalek 2.0.0", - "getrandom 0.2.10", + "getrandom", "k256", "num-derive", "num-integer", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "sha2 0.10.7", "sha3", "soroban-env-common", @@ -2662,7 +2614,7 @@ dependencies = [ "bytes-lit", "ctor", "ed25519-dalek 2.0.0", - "rand 0.8.5", + "rand", "soroban-env-guest", "soroban-env-host", "soroban-ledger-snapshot", @@ -3055,7 +3007,7 @@ dependencies = [ "hmac 0.12.1", "once_cell", "pbkdf2", - "rand 0.8.5", + "rand", "rustc-hash", "sha2 0.10.7", "thiserror", @@ -3361,12 +3313,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index a4d28c822..8676ea9d4 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -66,7 +66,7 @@ rand = "0.8.5" wasmparser = { workspace = true } sha2 = { workspace = true } csv = "1.1.6" -ed25519-dalek = "1.0.1" +ed25519-dalek = "2.0.0" jsonrpsee-http-client = "0.20.1" jsonrpsee-core = "0.20.1" hyper = "0.14.27" diff --git a/cmd/soroban-cli/src/commands/config/identity/address.rs b/cmd/soroban-cli/src/commands/config/identity/address.rs index 09d50dbf5..c66767581 100644 --- a/cmd/soroban-cli/src/commands/config/identity/address.rs +++ b/cmd/soroban-cli/src/commands/config/identity/address.rs @@ -35,7 +35,7 @@ impl Cmd { Ok(()) } - pub fn private_key(&self) -> Result { + pub fn private_key(&self) -> Result { Ok(if let Some(name) = &self.name { self.locator.read_identity(name)? } else { @@ -46,7 +46,7 @@ impl Cmd { pub fn public_key(&self) -> Result { Ok(stellar_strkey::ed25519::PublicKey::from_payload( - self.private_key()?.public.as_bytes(), + self.private_key()?.verifying_key().as_bytes(), )?) } } diff --git a/cmd/soroban-cli/src/commands/config/mod.rs b/cmd/soroban-cli/src/commands/config/mod.rs index 7f406c264..8b47b32bf 100644 --- a/cmd/soroban-cli/src/commands/config/mod.rs +++ b/cmd/soroban-cli/src/commands/config/mod.rs @@ -76,7 +76,7 @@ pub struct Args { } impl Args { - pub fn key_pair(&self) -> Result { + pub fn key_pair(&self) -> Result { let key = if let Some(source_account) = &self.source_account { self.account(source_account)? } else { diff --git a/cmd/soroban-cli/src/commands/config/secret.rs b/cmd/soroban-cli/src/commands/config/secret.rs index 46f55f3b6..7e6f08a90 100644 --- a/cmd/soroban-cli/src/commands/config/secret.rs +++ b/cmd/soroban-cli/src/commands/config/secret.rs @@ -105,12 +105,12 @@ impl Secret { pub fn public_key(&self, index: Option) -> Result { let key = self.key_pair(index)?; Ok(stellar_strkey::ed25519::PublicKey::from_payload( - key.public.as_bytes(), + key.verifying_key().as_bytes(), )?) } - pub fn key_pair(&self, index: Option) -> Result { - Ok(utils::into_key_pair(&self.private_key(index)?)?) + pub fn key_pair(&self, index: Option) -> Result { + Ok(utils::into_signing_key(&self.private_key(index)?)) } pub fn from_seed(seed: Option<&str>) -> Result { diff --git a/cmd/soroban-cli/src/commands/contract/bump.rs b/cmd/soroban-cli/src/commands/contract/bump.rs index 3ee74af62..c1e24c8fc 100644 --- a/cmd/soroban-cli/src/commands/contract/bump.rs +++ b/cmd/soroban-cli/src/commands/contract/bump.rs @@ -117,12 +117,13 @@ impl Cmd { let ledgers_to_expire = self.ledgers_to_expire(); // Get the account sequence number - let public_strkey = stellar_strkey::ed25519::PublicKey(key.public.to_bytes()).to_string(); + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); let account_details = client.get_account(&public_strkey).await?; let sequence: i64 = account_details.seq_num.into(); let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.public.to_bytes())), + source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), fee: self.fee.fee, seq_num: SequenceNumber(sequence + 1), cond: Preconditions::None, diff --git a/cmd/soroban-cli/src/commands/contract/deploy.rs b/cmd/soroban-cli/src/commands/contract/deploy.rs index d94e60ac9..7c7152bcc 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy.rs @@ -172,7 +172,8 @@ impl Cmd { let key = self.config.key_pair()?; // Get the account sequence number - let public_strkey = stellar_strkey::ed25519::PublicKey(key.public.to_bytes()).to_string(); + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); let account_details = client.get_account(&public_strkey).await?; let sequence: i64 = account_details.seq_num.into(); @@ -197,10 +198,10 @@ fn build_create_contract_tx( fee: u32, network_passphrase: &str, salt: [u8; 32], - key: &ed25519_dalek::Keypair, + key: &ed25519_dalek::SigningKey, ) -> Result<(Transaction, Hash), Error> { let source_account = AccountId(PublicKey::PublicKeyTypeEd25519( - key.public.to_bytes().into(), + key.verifying_key().to_bytes().into(), )); let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress { @@ -220,7 +221,7 @@ fn build_create_contract_tx( }), }; let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.public.to_bytes())), + source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), fee, seq_num: SequenceNumber(sequence), cond: Preconditions::None, diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 80bbacac2..606383ea0 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -85,7 +85,8 @@ impl Cmd { let key = self.config.key_pair()?; // Get the account sequence number - let public_strkey = stellar_strkey::ed25519::PublicKey(key.public.to_bytes()).to_string(); + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); let account_details = client.get_account(&public_strkey).await?; let sequence: i64 = account_details.seq_num.into(); @@ -137,12 +138,14 @@ pub(crate) fn build_install_contract_code_tx( source_code: Vec, sequence: i64, fee: u32, - key: &ed25519_dalek::Keypair, + key: &ed25519_dalek::SigningKey, ) -> Result<(Transaction, Hash), XdrError> { let hash = utils::contract_hash(&source_code)?; let op = Operation { - source_account: Some(MuxedAccount::Ed25519(Uint256(key.public.to_bytes()))), + source_account: Some(MuxedAccount::Ed25519(Uint256( + key.verifying_key().to_bytes(), + ))), body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { host_function: HostFunction::UploadContractWasm(source_code.try_into()?), auth: VecM::default(), @@ -150,7 +153,7 @@ pub(crate) fn build_install_contract_code_tx( }; let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.public.to_bytes())), + source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), fee, seq_num: SequenceNumber(sequence), cond: Preconditions::None, diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index e80c91884..4036cde9a 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -7,7 +7,7 @@ use std::str::FromStr; use std::{fmt::Debug, fs, io, rc::Rc}; use clap::{arg, command, value_parser, Parser}; -use ed25519_dalek::Keypair; +use ed25519_dalek::SigningKey; use heck::ToKebabCase; use soroban_env_host::e2e_invoke::{get_ledger_changes, ExpirationEntryMap}; use soroban_env_host::xdr::ReadXdr; @@ -173,7 +173,7 @@ impl Cmd { &self, contract_id: [u8; 32], spec_entries: &[ScSpecEntry], - ) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { + ) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { let spec = Spec(Some(spec_entries.to_vec())); let mut cmd = clap::Command::new(self.contract_id.clone()) .no_binary_name(true) @@ -189,7 +189,7 @@ impl Cmd { let func = spec.find_function(function)?; // create parsed_args in same order as the inputs to func - let mut signers: Vec = vec![]; + let mut signers: Vec = vec![]; let parsed_args = func .inputs .iter() @@ -291,7 +291,8 @@ impl Cmd { let key = self.config.key_pair()?; // Get the account sequence number - let public_strkey = stellar_strkey::ed25519::PublicKey(key.public.to_bytes()).to_string(); + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); let account_details = client.get_account(&public_strkey).await?; let sequence: i64 = account_details.seq_num.into(); @@ -348,7 +349,7 @@ impl Cmd { // Create source account, adding it to the ledger if not already present. let source_account = AccountId(PublicKey::PublicKeyTypeEd25519(Uint256( - self.config.key_pair()?.public.to_bytes(), + self.config.key_pair()?.verifying_key().to_bytes(), ))); let source_account_ledger_key = LedgerKey::Account(LedgerKeyAccount { account_id: source_account.clone(), @@ -536,7 +537,7 @@ fn build_invoke_contract_tx( parameters: InvokeContractArgs, sequence: i64, fee: u32, - key: &Keypair, + key: &SigningKey, ) -> Result { let op = Operation { source_account: None, @@ -546,7 +547,7 @@ fn build_invoke_contract_tx( }), }; Ok(Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.public.to_bytes())), + source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), fee, seq_num: SequenceNumber(sequence), cond: Preconditions::None, diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index bf1f761d0..b6c8cd385 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -116,12 +116,13 @@ impl Cmd { let key = self.config.key_pair()?; // Get the account sequence number - let public_strkey = stellar_strkey::ed25519::PublicKey(key.public.to_bytes()).to_string(); + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); let account_details = client.get_account(&public_strkey).await?; let sequence: i64 = account_details.seq_num.into(); let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.public.to_bytes())), + source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), fee: self.fee.fee, seq_num: SequenceNumber(sequence + 1), cond: Preconditions::None, diff --git a/cmd/soroban-cli/src/commands/lab/token/wrap.rs b/cmd/soroban-cli/src/commands/lab/token/wrap.rs index cb7fb4a03..4cd2dfb08 100644 --- a/cmd/soroban-cli/src/commands/lab/token/wrap.rs +++ b/cmd/soroban-cli/src/commands/lab/token/wrap.rs @@ -108,7 +108,8 @@ impl Cmd { let key = self.config.key_pair()?; // Get the account sequence number - let public_strkey = stellar_strkey::ed25519::PublicKey(key.public.to_bytes()).to_string(); + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); // TODO: use symbols for the method names (both here and in serve) let account_details = client.get_account(&public_strkey).await?; let sequence: i64 = account_details.seq_num.into(); @@ -152,7 +153,7 @@ fn build_wrap_token_tx( sequence: i64, fee: u32, _network_passphrase: &str, - key: &ed25519_dalek::Keypair, + key: &ed25519_dalek::SigningKey, ) -> Result { let contract = ScAddress::Contract(contract_id.clone()); let mut read_write = vec![ @@ -191,7 +192,7 @@ fn build_wrap_token_tx( }; Ok(Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.public.to_bytes())), + source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), fee, seq_num: SequenceNumber(sequence), cond: Preconditions::None, diff --git a/cmd/soroban-cli/src/rpc/mod.rs b/cmd/soroban-cli/src/rpc/mod.rs index 551186cfc..a2c329292 100644 --- a/cmd/soroban-cli/src/rpc/mod.rs +++ b/cmd/soroban-cli/src/rpc/mod.rs @@ -669,8 +669,8 @@ soroban config identity fund {address} --helper-url "# pub async fn prepare_and_send_transaction( &self, tx_without_preflight: &Transaction, - source_key: &ed25519_dalek::Keypair, - signers: &[ed25519_dalek::Keypair], + source_key: &ed25519_dalek::SigningKey, + signers: &[ed25519_dalek::SigningKey], network_passphrase: &str, log_events: Option, log_resources: Option, diff --git a/cmd/soroban-cli/src/rpc/transaction.rs b/cmd/soroban-cli/src/rpc/transaction.rs index 520fa3601..f2ed5eeee 100644 --- a/cmd/soroban-cli/src/rpc/transaction.rs +++ b/cmd/soroban-cli/src/rpc/transaction.rs @@ -76,8 +76,8 @@ pub fn assemble( // transaction. If unable to sign, return an error. pub fn sign_soroban_authorizations( raw: &Transaction, - source_key: &ed25519_dalek::Keypair, - signers: &[ed25519_dalek::Keypair], + source_key: &ed25519_dalek::SigningKey, + signers: &[ed25519_dalek::SigningKey], signature_expiration_ledger: u32, network_passphrase: &str, ) -> Result<(Transaction, Vec), Error> { @@ -95,7 +95,8 @@ pub fn sign_soroban_authorizations( let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); - let source_address = source_key.public.as_bytes(); + let verification_key = source_key.verifying_key(); + let source_address = verification_key.as_bytes(); let signed_auths = body .auth @@ -125,7 +126,10 @@ pub fn sign_soroban_authorizations( }); } }; - let signer = if let Some(s) = signers.iter().find(|s| needle == s.public.as_bytes()) { + let signer = if let Some(s) = signers + .iter() + .find(|s| needle == s.verifying_key().as_bytes()) + { s } else if needle == source_address { // This is the source address, so we can sign it @@ -156,7 +160,7 @@ pub fn sign_soroban_authorizations( pub fn sign_soroban_authorization_entry( raw: &SorobanAuthorizationEntry, - signer: &ed25519_dalek::Keypair, + signer: &ed25519_dalek::SigningKey, signature_expiration_ledger: u32, network_id: &Hash, ) -> Result { @@ -187,7 +191,7 @@ pub fn sign_soroban_authorization_entry( ScVal::Symbol(ScSymbol("public_key".try_into()?)), ScVal::Bytes( signer - .public + .verifying_key() .to_bytes() .to_vec() .try_into() diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs index 90a051fa4..0cbf9dfbc 100644 --- a/cmd/soroban-cli/src/utils.rs +++ b/cmd/soroban-cli/src/utils.rs @@ -164,7 +164,7 @@ pub fn transaction_hash(tx: &Transaction, network_passphrase: &str) -> Result<[u /// /// Might return an error pub fn sign_transaction( - key: &ed25519_dalek::Keypair, + key: &ed25519_dalek::SigningKey, tx: &Transaction, network_passphrase: &str, ) -> Result { @@ -172,7 +172,7 @@ pub fn sign_transaction( let tx_signature = key.sign(&tx_hash); let decorated_signature = DecoratedSignature { - hint: SignatureHint(key.public.to_bytes()[28..].try_into()?), + hint: SignatureHint(key.verifying_key().to_bytes()[28..].try_into()?), signature: Signature(tx_signature.to_bytes().try_into()?), }; @@ -336,20 +336,17 @@ pub fn find_config_dir(mut pwd: std::path::PathBuf) -> std::io::Result Result { - let secret = ed25519_dalek::SecretKey::from_bytes(&key.0)?; - let public = (&secret).into(); - Ok(ed25519_dalek::Keypair { secret, public }) +pub(crate) fn into_signing_key(key: &PrivateKey) -> ed25519_dalek::SigningKey { + let secret: ed25519_dalek::SecretKey = key.0; + ed25519_dalek::SigningKey::from_bytes(&secret) } /// Used in tests #[allow(unused)] pub(crate) fn parse_secret_key( s: &str, -) -> Result { - into_key_pair(&PrivateKey::from_string(s).unwrap()) +) -> Result { + Ok(into_signing_key(&PrivateKey::from_string(s)?)) } pub fn is_hex_string(s: &str) -> bool {