From a2560179aec3bc5aed63561871f2e947442a63b7 Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Fri, 24 Jan 2020 13:02:09 +0000 Subject: [PATCH] Onion V3 address consistency (#309) * initial addition of ov3 * move libwallet address functions into OnionV3Address type * incorporate OnionV3 type where possible * factor out manual dalek pubkey manipulations * corrections due to test failures * change json-rpc example to use ov3 for payment proof recipient address --- Cargo.lock | 5 +- api/src/owner.rs | 7 +- api/src/owner_rpc.rs | 15 +-- api/src/owner_rpc_s.rs | 4 +- controller/src/command.rs | 41 ++---- controller/src/controller.rs | 3 +- controller/src/display.rs | 23 ++-- controller/tests/payment_proofs.rs | 8 +- impls/src/error.rs | 11 ++ impls/src/tor/config.rs | 62 ++------- libwallet/Cargo.toml | 2 - libwallet/src/address.rs | 124 +---------------- libwallet/src/api_impl/owner.rs | 20 +-- libwallet/src/api_impl/types.rs | 12 +- libwallet/src/error.rs | 11 ++ libwallet/src/internal/selection.rs | 5 +- libwallet/src/internal/tx.rs | 11 +- libwallet/src/lib.rs | 2 + libwallet/src/slate_versions/ser.rs | 61 +++++++++ src/cmd/wallet_args.rs | 23 +++- util/Cargo.toml | 3 + util/src/lib.rs | 7 + util/src/ov3.rs | 197 ++++++++++++++++++++++++++++ 23 files changed, 395 insertions(+), 262 deletions(-) create mode 100644 util/src/ov3.rs diff --git a/Cargo.lock b/Cargo.lock index 68d53bc4..64a2355e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1053,7 +1053,6 @@ dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", - "data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1065,7 +1064,6 @@ dependencies = [ "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1075,7 +1073,9 @@ dependencies = [ name = "grin_wallet_util" version = "3.1.0-beta.1" dependencies = [ + "data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)", "grin_api 3.1.0-beta.1 (git+https://github.com/mimblewimble/grin)", "grin_chain 3.1.0-beta.1 (git+https://github.com/mimblewimble/grin)", "grin_core 3.1.0-beta.1 (git+https://github.com/mimblewimble/grin)", @@ -1086,6 +1086,7 @@ dependencies = [ "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", + "sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/api/src/owner.rs b/api/src/owner.rs index e3daf50b..f68fadd9 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -26,13 +26,15 @@ use crate::keychain::{Identifier, Keychain}; use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage}; use crate::libwallet::api_impl::{owner, owner_updater}; use crate::libwallet::{ - address, AcctPathMapping, Error, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, + AcctPathMapping, Error, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult, OutputCommitMapping, PaymentProof, Slate, TxLogEntry, WalletInfo, WalletInst, WalletLCProvider, }; use crate::util::logger::LoggingConfig; use crate::util::secp::key::SecretKey; use crate::util::{from_hex, static_secp_instance, Mutex, ZeroingString}; +use grin_wallet_util::OnionV3Address; +use std::convert::TryFrom; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{channel, Sender}; use std::sync::Arc; @@ -2010,7 +2012,8 @@ where /// ``` pub fn proof_address_from_onion_v3(&self, address_v3: &str) -> Result { - address::pubkey_from_onion_v3(address_v3) + let addr = OnionV3Address::try_from(address_v3)?; + Ok(addr.to_ed25519()?) } /// Returns a single, exportable [PaymentProof](../grin_wallet_libwallet/api_impl/types/struct.PaymentProof.html) diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index bec45ac9..9cc3b6bc 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -23,9 +23,11 @@ use crate::libwallet::{ OutputCommitMapping, Slate, SlateVersion, TxLogEntry, VersionedSlate, WalletInfo, WalletLCProvider, }; -use crate::util::{from_hex, Mutex}; +use crate::util::Mutex; use crate::{Owner, OwnerRpcS}; use easy_jsonrpc_mw; +use grin_wallet_util::OnionV3Address; +use std::convert::TryFrom; use std::sync::Arc; /// Public definition used to generate Owner jsonrpc api. @@ -1380,8 +1382,6 @@ pub fn run_doctest_owner( use grin_wallet_libwallet::{api_impl, WalletInst}; use grin_wallet_util::grin_keychain::ExtKeychain; - use ed25519_dalek::PublicKey as DalekPublicKey; - use crate::core::global; use crate::core::global::ChainTypes; use grin_wallet_util::grin_util as util; @@ -1510,13 +1510,8 @@ pub fn run_doctest_owner( let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); let proof_address = match payment_proof { true => { - let bytes = from_hex( - "783f6528669742a990e0faf0a5fca5d5b3330e37bbb9cd5c628696d03ce4e810".to_string(), - ) - .unwrap(); - let mut b = [0u8; 32]; - b.copy_from_slice(&bytes[0..32]); - Some(DalekPublicKey::from_bytes(&b).unwrap()) + let address = "783f6528669742a990e0faf0a5fca5d5b3330e37bbb9cd5c628696d03ce4e810"; + Some(OnionV3Address::try_from(address).unwrap()) } false => None, }; diff --git a/api/src/owner_rpc_s.rs b/api/src/owner_rpc_s.rs index 2bbd20d9..e1e7c264 100644 --- a/api/src/owner_rpc_s.rs +++ b/api/src/owner_rpc_s.rs @@ -376,7 +376,7 @@ pub trait OwnerRpcS { "selection_strategy_is_use_all": true, "message": "my message", "target_slate_version": null, - "payment_proof_recipient_address": "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb", + "payment_proof_recipient_address": "pa7wkkdgs5bkteha7lykl7ff2wztgdrxxo442xdcq2lnaphe5aidd4id", "ttl_blocks": null, "send_args": null } @@ -399,7 +399,7 @@ pub trait OwnerRpcS { "ttl_cutoff_height": null, "num_participants": 2, "payment_proof": { - "receiver_address": "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb", + "receiver_address": "783f6528669742a990e0faf0a5fca5d5b3330e37bbb9cd5c628696d03ce4e810", "receiver_signature": null, "sender_address": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3" }, diff --git a/controller/src/command.rs b/controller/src/command.rs index fc4acc34..0b9dc206 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -22,12 +22,12 @@ use crate::impls::{create_sender, KeybaseAllChannels, SlateGetter as _, SlateRec use crate::impls::{PathToSlate, SlatePutter}; use crate::keychain; use crate::libwallet::{ - self, address, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, WalletInst, - WalletLCProvider, + self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, WalletInst, WalletLCProvider, }; use crate::util::secp::key::SecretKey; -use crate::util::{to_hex, Mutex, ZeroingString}; +use crate::util::{Mutex, ZeroingString}; use crate::{controller, display}; +use grin_wallet_util::OnionV3Address; use serde_json as json; use std::fs::File; use std::io::{Read, Write}; @@ -253,7 +253,7 @@ pub struct SendArgs { pub fluff: bool, pub max_outputs: usize, pub target_slate_version: Option, - pub payment_proof_address: Option, + pub payment_proof_address: Option, pub ttl_blocks: Option, } @@ -290,10 +290,6 @@ where .collect(); display::estimate(args.amount, strategies, dark_scheme); } else { - let payment_proof_recipient_address = match args.payment_proof_address { - Some(ref p) => Some(address::ed25519_parse_pubkey(p)?), - None => None, - }; let init_args = InitTxArgs { src_acct_name: None, amount: args.amount, @@ -303,7 +299,7 @@ where selection_strategy_is_use_all: args.selection_strategy == "all", message: args.message.clone(), target_slate_version: args.target_slate_version, - payment_proof_recipient_address, + payment_proof_recipient_address: args.payment_proof_address.clone(), ttl_blocks: args.ttl_blocks, send_args: None, ..Default::default() @@ -897,26 +893,13 @@ where controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| { // Just address at derivation index 0 for now let pub_key = api.get_public_proof_address(m, 0)?; - let result = address::onion_v3_from_pubkey(&pub_key); - match result { - Ok(a) => { - println!(); - println!("Public Proof Address for account - {}", g_args.account); - println!("-------------------------------------"); - println!("{}", to_hex(pub_key.as_bytes().to_vec())); - println!(); - println!("TOR Onion V3 Address for account - {}", g_args.account); - println!("-------------------------------------"); - println!("{}", a); - println!(); - Ok(()) - } - Err(e) => { - error!("Address retrieval failed: {}", e); - error!("Backtrace: {}", e.backtrace().unwrap()); - Err(e) - } - } + let addr = OnionV3Address::from_bytes(pub_key.to_bytes()); + println!(); + println!("Address for account - {}", g_args.account); + println!("-------------------------------------"); + println!("{}", addr); + println!(); + Ok(()) })?; Ok(()) } diff --git a/controller/src/controller.rs b/controller/src/controller.rs index 3f0e6dd1..5cb9b480 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -26,6 +26,7 @@ use crate::util::{from_hex, static_secp_instance, to_base64, Mutex}; use failure::ResultExt; use futures::future::{err, ok}; use futures::{Future, Stream}; +use grin_wallet_util::OnionV3Address; use hyper::header::HeaderValue; use hyper::{Body, Request, Response, StatusCode}; use serde::{Deserialize, Serialize}; @@ -98,7 +99,7 @@ where let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?); let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0) .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?; - let onion_address = tor_config::onion_address_from_seckey(&sec_key) + let onion_address = OnionV3Address::from_private(&sec_key.0) .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?; warn!( "Starting TOR Hidden Service for API listener at address {}, binding to {}", diff --git a/controller/src/display.rs b/controller/src/display.rs index 24ee7c0b..35f71a1b 100644 --- a/controller/src/display.rs +++ b/controller/src/display.rs @@ -15,9 +15,10 @@ use crate::core::core::{self, amount_to_hr_string}; use crate::core::global; use crate::libwallet::{ - address, AcctPathMapping, Error, OutputCommitMapping, OutputStatus, TxLogEntry, WalletInfo, + AcctPathMapping, Error, OutputCommitMapping, OutputStatus, TxLogEntry, WalletInfo, }; use crate::util; +use grin_wallet_util::OnionV3Address; use prettytable; use std::io::prelude::Write; use term; @@ -537,8 +538,6 @@ pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> { t.fg(term::color::WHITE).unwrap(); writeln!(t).unwrap(); - let receiver_address = util::to_hex(pp.receiver_address.to_bytes().to_vec()); - let receiver_onion_address = address::onion_v3_from_pubkey(&pp.receiver_address)?; let receiver_signature = match pp.receiver_signature { Some(s) => util::to_hex(s.to_bytes().to_vec()), None => "None".to_owned(), @@ -556,8 +555,6 @@ pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> { ) }; - let sender_address = util::to_hex(pp.sender_address.to_bytes().to_vec()); - let sender_onion_address = address::onion_v3_from_pubkey(&pp.sender_address)?; let sender_signature = match pp.sender_signature { Some(s) => util::to_hex(s.to_bytes().to_vec()), None => "None".to_owned(), @@ -567,14 +564,22 @@ pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> { None => "None".to_owned(), }; - writeln!(t, "Receiver Address: {}", receiver_address).unwrap(); - writeln!(t, "Receiver Address (Onion V3): {}", receiver_onion_address).unwrap(); + writeln!( + t, + "Receiver Address: {}", + OnionV3Address::from_bytes(pp.receiver_address.to_bytes()) + ) + .unwrap(); writeln!(t, "Receiver Signature: {}", receiver_signature).unwrap(); writeln!(t, "Amount: {}", amount).unwrap(); writeln!(t, "Kernel Excess: {}", kernel_excess).unwrap(); - writeln!(t, "Sender Address: {}", sender_address).unwrap(); + writeln!( + t, + "Sender Address: {}", + OnionV3Address::from_bytes(pp.sender_address.to_bytes()) + ) + .unwrap(); writeln!(t, "Sender Signature: {}", sender_signature).unwrap(); - writeln!(t, "Sender Address (Onion V3): {}", sender_onion_address).unwrap(); t.reset().unwrap(); diff --git a/controller/tests/payment_proofs.rs b/controller/tests/payment_proofs.rs index de9b9a25..85dfba51 100644 --- a/controller/tests/payment_proofs.rs +++ b/controller/tests/payment_proofs.rs @@ -24,6 +24,8 @@ use libwallet::{InitTxArgs, Slate}; use std::thread; use std::time::Duration; +use grin_wallet_util::OnionV3Address; + #[macro_use] mod common; use common::{clean_output_dir, create_wallet_proxy, setup}; @@ -80,7 +82,7 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err Ok(()) })?; - let address = address.unwrap(); + let address = OnionV3Address::from_bytes(address.as_ref().unwrap().to_bytes()); println!("Public address is: {:?}", address); let amount = 60_000_000_000; let mut slate = Slate::blank(1); @@ -93,14 +95,14 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err max_outputs: 500, num_change_outputs: 1, selection_strategy_is_use_all: true, - payment_proof_recipient_address: Some(address), + payment_proof_recipient_address: Some(address.clone()), ..Default::default() }; let slate_i = sender_api.init_send_tx(m, args)?; assert_eq!( slate_i.payment_proof.as_ref().unwrap().receiver_address, - address + address.to_ed25519()?, ); println!( "Sender addr: {:?}", diff --git a/impls/src/error.rs b/impls/src/error.rs index abead3f1..44b85143 100644 --- a/impls/src/error.rs +++ b/impls/src/error.rs @@ -18,6 +18,7 @@ use crate::keychain; use crate::libwallet; use crate::util::secp; use failure::{Backtrace, Context, Fail}; +use grin_wallet_util::OnionV3AddressError; use std::env; use std::fmt::{self, Display}; @@ -42,6 +43,10 @@ pub enum ErrorKind { #[fail(display = "Keychain error")] Keychain(keychain::Error), + /// Onion V3 Address Error + #[fail(display = "Onion V3 Address Error")] + OnionV3Address(OnionV3AddressError), + /// Error when formatting json #[fail(display = "IO error")] IO, @@ -187,3 +192,9 @@ impl From for Error { } } } + +impl From for Error { + fn from(error: OnionV3AddressError) -> Error { + Error::from(ErrorKind::OnionV3Address(error)) + } +} diff --git a/impls/src/tor/config.rs b/impls/src/tor/config.rs index 2c0c399e..7d21bb8f 100644 --- a/impls/src/tor/config.rs +++ b/impls/src/tor/config.rs @@ -15,12 +15,13 @@ //! Tor Configuration + Onion (Hidden) Service operations use crate::util::secp::key::SecretKey; use crate::{Error, ErrorKind}; -use grin_wallet_libwallet::address; +use grin_wallet_util::OnionV3Address; use ed25519_dalek::ExpandedSecretKey; use ed25519_dalek::PublicKey as DalekPublicKey; use ed25519_dalek::SecretKey as DalekSecretKey; +use std::convert::TryFrom; use std::fs::{self, File}; use std::io::Write; use std::path::{Path, MAIN_SEPARATOR}; @@ -92,12 +93,6 @@ impl TorRcConfig { } } -/// helper to get address -pub fn onion_address_from_seckey(sec_key: &SecretKey) -> Result { - let (_, d_pub_key) = address::ed25519_keypair(sec_key)?; - Ok(address::onion_v3_from_pubkey(&d_pub_key)?) -} - pub fn create_onion_service_sec_key_file( os_directory: &str, sec_key: &DalekSecretKey, @@ -143,9 +138,10 @@ pub fn create_onion_auth_clients_dir(os_directory: &str) -> Result<(), Error> { pub fn output_onion_service_config( tor_config_directory: &str, sec_key: &SecretKey, -) -> Result { - let (_, d_pub_key) = address::ed25519_keypair(&sec_key)?; - let address = address::onion_v3_from_pubkey(&d_pub_key)?; +) -> Result { + let d_sec_key = DalekSecretKey::from_bytes(&sec_key.0) + .context(ErrorKind::ED25519Key("Unable to parse private key".into()))?; + let address = OnionV3Address::from_private(&sec_key.0)?; let hs_dir_file_path = format!( "{}{}{}{}{}", tor_config_directory, MAIN_SEPARATOR, HIDDEN_SERVICES_DIR, MAIN_SEPARATOR, address @@ -159,10 +155,9 @@ pub fn output_onion_service_config( // create directory if it doesn't exist fs::create_dir_all(&hs_dir_file_path).context(ErrorKind::IO)?; - let (d_sec_key, d_pub_key) = address::ed25519_keypair(&sec_key)?; create_onion_service_sec_key_file(&hs_dir_file_path, &d_sec_key)?; - create_onion_service_pub_key_file(&hs_dir_file_path, &d_pub_key)?; - create_onion_service_hostname_file(&hs_dir_file_path, &address)?; + create_onion_service_pub_key_file(&hs_dir_file_path, &address.to_ed25519()?)?; + create_onion_service_hostname_file(&hs_dir_file_path, &address.to_string())?; create_onion_auth_clients_dir(&hs_dir_file_path)?; set_permissions(&hs_dir_file_path)?; @@ -211,7 +206,7 @@ pub fn output_tor_listener_config( for k in listener_keys { let service_dir = output_onion_service_config(tor_config_directory, &k)?; - service_dirs.push(service_dir); + service_dirs.push(service_dir.to_string()); } // hidden service listener doesn't need a socks port @@ -239,10 +234,10 @@ pub fn output_tor_sender_config( } pub fn is_tor_address(input: &str) -> Result<(), Error> { - match address::pubkey_from_onion_v3(input) { + match OnionV3Address::try_from(input) { Ok(_) => Ok(()), Err(e) => { - let msg = format!("{}", e); + let msg = format!("{:?}", e); Err(ErrorKind::NotOnion(msg).to_owned())? } } @@ -265,7 +260,6 @@ mod tests { use super::*; use rand::rngs::mock::StepRng; - use rand::thread_rng; use crate::util::{self, secp, static_secp_instance}; @@ -278,40 +272,6 @@ mod tests { clean_output_dir(test_dir); } - #[test] - fn gen_ed25519_pub_key() -> Result<(), Error> { - let secp_inst = static_secp_instance(); - let secp = secp_inst.lock(); - let mut test_rng = StepRng::new(1234567890u64, 1); - let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng); - println!("{:?}", sec_key); - let (_, d_pub_key) = address::ed25519_keypair(&sec_key)?; - println!("{:?}", d_pub_key); - // some randoms - for _ in 0..1000 { - let sec_key = secp::key::SecretKey::new(&secp, &mut thread_rng()); - let (_, _) = address::ed25519_keypair(&sec_key)?; - } - Ok(()) - } - - #[test] - fn gen_onion_address() -> Result<(), Error> { - let secp_inst = static_secp_instance(); - let secp = secp_inst.lock(); - let mut test_rng = StepRng::new(1234567890u64, 1); - let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng); - println!("{:?}", sec_key); - let (_, d_pub_key) = address::ed25519_keypair(&sec_key)?; - let address = address::onion_v3_from_pubkey(&d_pub_key)?; - assert_eq!( - "kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid", - address - ); - println!("{}", address); - Ok(()) - } - #[test] fn test_service_config() -> Result<(), Error> { let test_dir = "target/test_output/onion_service"; diff --git a/libwallet/Cargo.toml b/libwallet/Cargo.toml index baeec353..3428982d 100644 --- a/libwallet/Cargo.toml +++ b/libwallet/Cargo.toml @@ -25,9 +25,7 @@ lazy_static = "1" strum = "0.15" strum_macros = "0.15" ed25519-dalek = "1.0.0-pre.1" -sha3 = "0.8" byteorder = "1" -data-encoding = "2" grin_wallet_util = { path = "../util", version = "3.1.0-beta.1" } grin_wallet_config = { path = "../config", version = "3.1.0-beta.1" } diff --git a/libwallet/src/address.rs b/libwallet/src/address.rs index 2fabd3c7..66e6c4a0 100644 --- a/libwallet/src/address.rs +++ b/libwallet/src/address.rs @@ -15,17 +15,10 @@ //! Functions defining wallet 'addresses', i.e. ed2559 keys based on //! a derivation path -use crate::grin_util::from_hex; use crate::grin_util::secp::key::SecretKey; -use crate::{Error, ErrorKind}; +use crate::Error; use grin_wallet_util::grin_keychain::{ChildNumber, Identifier, Keychain, SwitchCommitmentType}; -use data_encoding::BASE32; -use ed25519_dalek::PublicKey as DalekPublicKey; -use ed25519_dalek::SecretKey as DalekSecretKey; -use failure::ResultExt; -use sha3::{Digest, Sha3_256}; - use crate::blake2::blake2b::blake2b; /// Derive a secret key given a derivation path and index @@ -54,118 +47,3 @@ where &hashed.as_bytes()[..], )?) } - -/// Output ed25519 keypair given an rust_secp256k1 SecretKey -pub fn ed25519_keypair(sec_key: &SecretKey) -> Result<(DalekSecretKey, DalekPublicKey), Error> { - let d_skey = match DalekSecretKey::from_bytes(&sec_key.0) { - Ok(k) => k, - Err(e) => { - return Err(ErrorKind::ED25519Key(format!("{}", e)).to_owned())?; - } - }; - let d_pub_key: DalekPublicKey = (&d_skey).into(); - Ok((d_skey, d_pub_key)) -} - -/// Output ed25519 pubkey represented by string -pub fn ed25519_parse_pubkey(pub_key: &str) -> Result { - let bytes = from_hex(pub_key.to_owned()) - .context(ErrorKind::AddressDecoding("Can't parse pubkey".to_owned()))?; - match DalekPublicKey::from_bytes(&bytes) { - Ok(k) => Ok(k), - Err(_) => { - return Err( - ErrorKind::AddressDecoding("Not a valid public key".to_owned()).to_owned(), - )?; - } - } -} - -/// Return the ed25519 public key represented in an onion address -pub fn pubkey_from_onion_v3(onion_address: &str) -> Result { - let mut input = onion_address.to_uppercase(); - if input.starts_with("HTTP://") || input.starts_with("HTTPS://") { - input = input.replace("HTTP://", ""); - input = input.replace("HTTPS://", ""); - } - if input.ends_with(".ONION") { - input = input.replace(".ONION", ""); - } - let orig_address_raw = input.clone(); - // for now, just check input is the right length and try and decode from base32 - if input.len() != 56 { - return Err( - ErrorKind::AddressDecoding("Input address is wrong length".to_owned()).to_owned(), - )?; - } - let mut address = BASE32 - .decode(input.as_bytes()) - .context(ErrorKind::AddressDecoding( - "Input address is not base 32".to_owned(), - ))? - .to_vec(); - - address.split_off(32); - let key = match DalekPublicKey::from_bytes(&address) { - Ok(k) => k, - Err(_) => { - return Err(ErrorKind::AddressDecoding( - "Provided onion V3 address is invalid (parsing key)".to_owned(), - ) - .to_owned())?; - } - }; - let test_v3 = match onion_v3_from_pubkey(&key) { - Ok(k) => k, - Err(_) => { - return Err(ErrorKind::AddressDecoding( - "Provided onion V3 address is invalid (converting from pubkey)".to_owned(), - ) - .to_owned())?; - } - }; - - if test_v3.to_uppercase() != orig_address_raw.to_uppercase() { - return Err(ErrorKind::AddressDecoding( - "Provided onion V3 address is invalid (no match)".to_owned(), - ) - .to_owned())?; - } - Ok(key) -} - -/// Generate an onion address from an ed25519_dalek public key -pub fn onion_v3_from_pubkey(pub_key: &DalekPublicKey) -> Result { - // calculate checksum - let mut hasher = Sha3_256::new(); - hasher.input(b".onion checksum"); - hasher.input(pub_key.as_bytes()); - hasher.input([0x03u8]); - let checksum = hasher.result(); - - let mut address_bytes = pub_key.as_bytes().to_vec(); - address_bytes.push(checksum[0]); - address_bytes.push(checksum[1]); - address_bytes.push(0x03u8); - - let ret = BASE32.encode(&address_bytes); - Ok(ret.to_lowercase()) -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn onion_v3_conversion() { - let onion_address = "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid"; - - let key = pubkey_from_onion_v3(onion_address).unwrap(); - println!("Key: {:?}", &key); - - let out_address = onion_v3_from_pubkey(&key).unwrap(); - println!("Address: {:?}", &out_address); - - assert_eq!(onion_address, out_address); - } -} diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 1236f294..091fa1c7 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -22,6 +22,7 @@ use crate::grin_core::ser; use crate::grin_util; use crate::grin_util::secp::key::SecretKey; use crate::grin_util::Mutex; +use crate::util::OnionV3Address; use crate::api_impl::owner_updater::StatusMessage; use crate::grin_keychain::{Identifier, Keychain}; @@ -92,7 +93,8 @@ where let parent_key_id = w.parent_key_id(); let k = w.keychain(keychain_mask)?; let sec_addr_key = address::address_from_derivation_path(&k, &parent_key_id, index)?; - Ok(address::ed25519_keypair(&sec_addr_key)?.1) + let addr = OnionV3Address::from_private(&sec_addr_key.0)?; + Ok(addr.to_ed25519()?) } /// retrieve outputs @@ -284,9 +286,9 @@ where Ok(PaymentProof { amount: amount, excess: excess, - recipient_address: address::onion_v3_from_pubkey(&proof.receiver_address)?, + recipient_address: OnionV3Address::from_bytes(proof.receiver_address.to_bytes()), recipient_sig: r_sig, - sender_address: address::onion_v3_from_pubkey(&proof.sender_address)?, + sender_address: OnionV3Address::from_bytes(proof.sender_address.to_bytes()), sender_sig: s_sig, }) } @@ -366,11 +368,11 @@ where let k = w.keychain(keychain_mask)?; let sec_addr_key = address::address_from_derivation_path(&k, &parent_key_id, deriv_path)?; - let sender_address = address::ed25519_keypair(&sec_addr_key)?.1; + let sender_address = OnionV3Address::from_private(&sec_addr_key.0)?; slate.payment_proof = Some(PaymentInfo { - sender_address, - receiver_address: a, + sender_address: sender_address.to_ed25519()?, + receiver_address: a.to_ed25519()?, receiver_signature: None, }); @@ -889,7 +891,7 @@ where C: NodeClient + 'a, K: Keychain + 'a, { - let sender_pubkey = address::pubkey_from_onion_v3(&proof.sender_address)?; + let sender_pubkey = proof.sender_address.to_ed25519()?; let msg = tx::payment_proof_message(proof.amount, &proof.excess, sender_pubkey)?; let (mut client, parent_key_id, keychain) = { @@ -921,14 +923,14 @@ where }; // Check Sigs - let recipient_pubkey = address::pubkey_from_onion_v3(&proof.recipient_address)?; + let recipient_pubkey = proof.recipient_address.to_ed25519()?; if let Err(_) = recipient_pubkey.verify(&msg, &proof.recipient_sig) { return Err(ErrorKind::PaymentProof( "Invalid recipient signature".to_owned(), ))?; }; - let sender_pubkey = address::pubkey_from_onion_v3(&proof.sender_address)?; + let sender_pubkey = proof.sender_address.to_ed25519()?; if let Err(_) = sender_pubkey.verify(&msg, &proof.sender_sig) { return Err(ErrorKind::PaymentProof( "Invalid sender signature".to_owned(), diff --git a/libwallet/src/api_impl/types.rs b/libwallet/src/api_impl/types.rs index b6e344b9..3c4f318d 100644 --- a/libwallet/src/api_impl/types.rs +++ b/libwallet/src/api_impl/types.rs @@ -20,8 +20,8 @@ use crate::grin_util::secp::pedersen; use crate::slate_versions::ser as dalek_ser; use crate::slate_versions::SlateVersion; use crate::types::OutputData; +use grin_wallet_util::OnionV3Address; -use ed25519_dalek::PublicKey as DalekPublicKey; use ed25519_dalek::Signature as DalekSignature; /// Send TX API Args @@ -94,8 +94,8 @@ pub struct InitTxArgs { #[serde(with = "secp_ser::opt_string_or_u64")] pub ttl_blocks: Option, /// If set, require a payment proof for the particular recipient - #[serde(with = "dalek_ser::option_dalek_pubkey_serde")] - pub payment_proof_recipient_address: Option, + #[serde(with = "dalek_ser::option_ov3_serde")] + pub payment_proof_recipient_address: Option, /// If true, just return an estimate of the resulting slate, containing fees and amounts /// locked without actually locking outputs or creating the transaction. Note if this is set to /// 'true', the amount field in the slate will contain the total amount locked, not the provided @@ -237,12 +237,14 @@ pub struct PaymentProof { )] pub excess: pedersen::Commitment, /// Recipient Wallet Address (Onion V3) - pub recipient_address: String, + #[serde(with = "dalek_ser::ov3_serde")] + pub recipient_address: OnionV3Address, /// Recipient Signature #[serde(with = "dalek_ser::dalek_sig_serde")] pub recipient_sig: DalekSignature, /// Sender Wallet Address (Onion V3) - pub sender_address: String, + #[serde(with = "dalek_ser::ov3_serde")] + pub sender_address: OnionV3Address, /// Sender Signature #[serde(with = "dalek_ser::dalek_sig_serde")] pub sender_sig: DalekSignature, diff --git a/libwallet/src/error.rs b/libwallet/src/error.rs index 02fc1237..f024f3f4 100644 --- a/libwallet/src/error.rs +++ b/libwallet/src/error.rs @@ -19,6 +19,7 @@ use crate::grin_core::libtx; use crate::grin_keychain; use crate::grin_store; use crate::grin_util::secp; +use crate::util; use failure::{Backtrace, Context, Fail}; use std::env; use std::fmt::{self, Display}; @@ -73,6 +74,10 @@ pub enum ErrorKind { #[fail(display = "Secp error")] Secp(secp::Error), + /// Onion V3 Address Error + #[fail(display = "Onion V3 Address Error")] + OnionV3Address(util::OnionV3AddressError), + /// Callback implementation error conversion #[fail(display = "Trait Implementation error")] CallbackImpl(&'static str), @@ -373,3 +378,9 @@ impl From for Error { Error::from(ErrorKind::Backend(format!("{}", error))) } } + +impl From for Error { + fn from(error: util::OnionV3AddressError) -> Error { + Error::from(ErrorKind::OnionV3Address(error)) + } +} diff --git a/libwallet/src/internal/selection.rs b/libwallet/src/internal/selection.rs index ce5bc35a..32c2b666 100644 --- a/libwallet/src/internal/selection.rs +++ b/libwallet/src/internal/selection.rs @@ -27,6 +27,7 @@ use crate::grin_util::secp::key::SecretKey; use crate::internal::keys; use crate::slate::Slate; use crate::types::*; +use crate::util::OnionV3Address; use std::collections::HashMap; /// Initialize a transaction on the sender side, returns a corresponding @@ -177,11 +178,11 @@ where &parent_key_id, sender_address_path, )?; - let sender_address = address::ed25519_keypair(&sender_key)?.1; + let sender_address = OnionV3Address::from_private(&sender_key.0)?; t.payment_proof = Some(StoredProofInfo { receiver_address: p.receiver_address.clone(), receiver_signature: p.receiver_signature.clone(), - sender_address, + sender_address: sender_address.to_ed25519()?, sender_address_path, sender_signature: None, }); diff --git a/libwallet/src/internal/tx.rs b/libwallet/src/internal/tx.rs index c54fd66b..f23fedd1 100644 --- a/libwallet/src/internal/tx.rs +++ b/libwallet/src/internal/tx.rs @@ -27,6 +27,7 @@ use crate::grin_util::Mutex; use crate::internal::{selection, updater}; use crate::slate::Slate; use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType, WalletBackend}; +use crate::util::OnionV3Address; use crate::{address, Error, ErrorKind}; use ed25519_dalek::Keypair as DalekKeypair; use ed25519_dalek::PublicKey as DalekPublicKey; @@ -363,14 +364,14 @@ where let excess = slate.calc_excess(&keychain)?; let sender_key = address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?; - let sender_address = address::ed25519_keypair(&sender_key)?.1; + let sender_address = OnionV3Address::from_private(&sender_key.0)?; let sig = create_payment_proof_signature(slate.amount, &excess, p.sender_address, sender_key)?; tx.payment_proof = Some(StoredProofInfo { receiver_address: p.receiver_address, receiver_signature: p.receiver_signature, sender_address_path: derivation_index, - sender_address, + sender_address: sender_address.to_ed25519()?, sender_signature: Some(sig), }) } @@ -509,8 +510,8 @@ where }; let orig_sender_sk = address::address_from_derivation_path(&keychain, parent_key_id, index)?; - let orig_sender_address = address::ed25519_keypair(&orig_sender_sk)?.1; - if p.sender_address != orig_sender_address { + let orig_sender_address = OnionV3Address::from_private(&orig_sender_sk.0)?; + if p.sender_address != orig_sender_address.to_ed25519()? { return Err(ErrorKind::PaymentProof( "Sender address on slate does not match original sender address".to_owned(), ))?; @@ -524,7 +525,7 @@ where let msg = payment_proof_message( slate.amount, &slate.calc_excess(&keychain)?, - orig_sender_address, + orig_sender_address.to_ed25519()?, )?; let sig = match p.receiver_signature { Some(s) => s, diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index f6f09c73..2b6666da 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -28,6 +28,8 @@ use grin_wallet_util::grin_keychain; use grin_wallet_util::grin_store; use grin_wallet_util::grin_util; +use grin_wallet_util as util; + use blake2_rfc as blake2; use failure; diff --git a/libwallet/src/slate_versions/ser.rs b/libwallet/src/slate_versions/ser.rs index 0cb9be23..cb63a8cc 100644 --- a/libwallet/src/slate_versions/ser.rs +++ b/libwallet/src/slate_versions/ser.rs @@ -13,6 +13,67 @@ // limitations under the License. //! Sane serialization & deserialization of cryptographic structs into hex +/// Serializes an OnionV3Address to and from hex +pub mod option_ov3_serde { + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serializer}; + use std::convert::TryFrom; + + use crate::util::{OnionV3Address, OnionV3AddressError}; + + /// + pub fn serialize(addr: &Option, serializer: S) -> Result + where + S: Serializer, + { + match addr { + Some(a) => serializer.serialize_str(&a.to_string()), + None => serializer.serialize_none(), + } + } + + /// + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer).and_then(|res| match res { + Some(s) => OnionV3Address::try_from(s.as_str()) + .map_err(|err: OnionV3AddressError| Error::custom(format!("{:?}", err))) + .and_then(|a| Ok(Some(a))), + None => Ok(None), + }) + } +} + +/// Serializes an OnionV3Address to and from hex +pub mod ov3_serde { + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serializer}; + use std::convert::TryFrom; + + use crate::util::{OnionV3Address, OnionV3AddressError}; + + /// + pub fn serialize(addr: &OnionV3Address, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&addr.to_string()) + } + + /// + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer).and_then(|s| { + OnionV3Address::try_from(s.as_str()) + .map_err(|err: OnionV3AddressError| Error::custom(format!("{:?}", err))) + .and_then(|a| Ok(a)) + }) + } +} /// Serializes an ed25519 PublicKey to and from hex pub mod dalek_pubkey_serde { diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index 9e9b213d..7cf948fb 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -15,7 +15,7 @@ use crate::api::TLSConfig; use crate::config::GRIN_WALLET_DIR; use crate::util::file::get_first_line; -use crate::util::{to_hex, Mutex, ZeroingString}; +use crate::util::{Mutex, ZeroingString}; /// Argument parsing and error handling for wallet commands use clap::ArgMatches; use failure::Fail; @@ -26,16 +26,16 @@ use grin_wallet_impls::tor::config::is_tor_address; use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl}; use grin_wallet_impls::{PathToSlate, SlateGetter as _}; use grin_wallet_libwallet::Slate; -use grin_wallet_libwallet::{ - address, IssueInvoiceTxArgs, NodeClient, WalletInst, WalletLCProvider, -}; +use grin_wallet_libwallet::{IssueInvoiceTxArgs, NodeClient, WalletInst, WalletLCProvider}; use grin_wallet_util::grin_core as core; use grin_wallet_util::grin_core::core::amount_to_hr_string; use grin_wallet_util::grin_core::global; use grin_wallet_util::grin_keychain as keychain; +use grin_wallet_util::OnionV3Address; use linefeed::terminal::Signal; use linefeed::{Interface, ReadResult}; use rpassword; +use std::convert::TryFrom; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -475,9 +475,18 @@ pub fn parse_send_args(args: &ArgMatches) -> Result { // if the destination address is a TOR address, we don't need the address // separately - match address::pubkey_from_onion_v3(&dest) { - Ok(k) => Some(to_hex(k.to_bytes().to_vec())), - Err(_) => Some(parse_required(args, "proof_address")?.to_owned()), + match OnionV3Address::try_from(dest) { + Ok(a) => Some(a), + Err(_) => { + let addr = parse_required(args, "proof_address")?; + match OnionV3Address::try_from(addr) { + Ok(a) => Some(a), + Err(e) => { + let msg = format!("Invalid proof address: {:?}", e); + return Err(ParseError::ArgumentError(msg)); + } + } + } } } false => None, diff --git a/util/Cargo.toml b/util/Cargo.toml index e8b24405..b460545c 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -15,6 +15,9 @@ serde = "1" serde_derive = "1" toml = "0.4" dirs = "1.0.3" +ed25519-dalek = "1.0.0-pre.1" +data-encoding = "2" +sha3 = "0.8" # For Release # grin_core = "2.0.0" diff --git a/util/src/lib.rs b/util/src/lib.rs index bca19f21..5995e738 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -20,6 +20,13 @@ #![deny(unused_mut)] #![warn(missing_docs)] +#[macro_use] +extern crate serde_derive; + +mod ov3; +pub use ov3::OnionV3Address; +pub use ov3::OnionV3Error as OnionV3AddressError; + pub use grin_api; pub use grin_chain; pub use grin_core; diff --git a/util/src/ov3.rs b/util/src/ov3.rs new file mode 100644 index 00000000..ee108bcd --- /dev/null +++ b/util/src/ov3.rs @@ -0,0 +1,197 @@ +// Copyright 2020 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::grin_util::from_hex; +use data_encoding::BASE32; +use ed25519_dalek::PublicKey as DalekPublicKey; +use ed25519_dalek::SecretKey as DalekSecretKey; +use sha3::{Digest, Sha3_256}; +use std::convert::TryFrom; +use std::fmt; + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +/// OnionV3 Address Errors +pub enum OnionV3Error { + /// Error decoding an address from a string + AddressDecoding(String), + /// Error with given private key + InvalidPrivateKey(String), +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +/// Struct to hold an onion V3 address, represented internally as a raw +/// ed25519 public key +pub struct OnionV3Address([u8; 32]); + +impl OnionV3Address { + /// from bytes + pub fn from_bytes(bytes: [u8; 32]) -> Self { + OnionV3Address(bytes) + } + + /// as bytes + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + /// populate from a private key + pub fn from_private(key: &[u8; 32]) -> Result { + let d_skey = match DalekSecretKey::from_bytes(key) { + Ok(k) => k, + Err(e) => { + return Err(OnionV3Error::InvalidPrivateKey(format!( + "Unable to create public key: {}", + e + ))); + } + }; + let d_pub_key: DalekPublicKey = (&d_skey).into(); + Ok(OnionV3Address(d_pub_key.as_bytes().clone())) + } + + /// return dalek public key + pub fn to_ed25519(&self) -> Result { + let d_skey = match DalekPublicKey::from_bytes(&self.0) { + Ok(k) => k, + Err(e) => { + return Err(OnionV3Error::InvalidPrivateKey(format!( + "Unable to create dalek public key: {}", + e + ))); + } + }; + Ok(d_skey) + } + + /// Return as onion v3 address string + fn to_ov3_str(&self) -> String { + // calculate checksum + let mut hasher = Sha3_256::new(); + hasher.input(b".onion checksum"); + hasher.input(self.0); + hasher.input([0x03u8]); + let checksum = hasher.result(); + + let mut address_bytes = self.0.to_vec(); + address_bytes.push(checksum[0]); + address_bytes.push(checksum[1]); + address_bytes.push(0x03u8); + + let ret = BASE32.encode(&address_bytes); + ret.to_lowercase() + } +} + +impl fmt::Display for OnionV3Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_ov3_str()) + } +} + +impl TryFrom<&str> for OnionV3Address { + type Error = OnionV3Error; + + fn try_from(input: &str) -> Result { + // First attempt to decode a pubkey from hex + if let Ok(b) = from_hex(input.to_owned()) { + if b.len() == 32 { + let mut retval = OnionV3Address([0; 32]); + retval.0.copy_from_slice(&b[0..32]); + return Ok(retval); + } else { + return Err(OnionV3Error::AddressDecoding( + "(Interpreted as Hex String) Public key is wrong length".to_owned(), + )); + } + }; + + // Otherwise try to parse as onion V3 address + let mut input = input.to_uppercase(); + if input.starts_with("HTTP://") || input.starts_with("HTTPS://") { + input = input.replace("HTTP://", ""); + input = input.replace("HTTPS://", ""); + } + if input.ends_with(".ONION") { + input = input.replace(".ONION", ""); + } + let orig_address_raw = input.clone(); + // for now, just check input is the right length and try and decode from base32 + if input.len() != 56 { + return Err(OnionV3Error::AddressDecoding( + "(Interpreted as Base32 String) Input address is wrong length".to_owned(), + )); + } + let address = match BASE32.decode(input.as_bytes()) { + Ok(a) => a, + Err(_) => { + return Err(OnionV3Error::AddressDecoding( + "(Interpreted as Base32 String) Input address is not base 32".to_owned(), + )); + } + }; + + let mut retval = OnionV3Address([0; 32]); + retval.0.copy_from_slice(&address[0..32]); + + let test_v3 = retval.to_ov3_str(); + if test_v3.to_uppercase() != orig_address_raw.to_uppercase() { + return Err(OnionV3Error::AddressDecoding( + "(Interpreted as Base32 String) Provided onion V3 address is invalid (no match)" + .to_owned(), + )); + } + + Ok(retval) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::convert::TryInto; + + #[test] + fn onion_v3() -> Result<(), OnionV3Error> { + let onion_address_str = "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid"; + let onion_address: OnionV3Address = onion_address_str.try_into()?; + + println!("Onion address: {:?}", onion_address); + let raw_pubkey_str = "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb"; + let onion_address_2: OnionV3Address = raw_pubkey_str.try_into()?; + + assert_eq!(onion_address, onion_address_2); + + // invalid hex string, should be interpreted as base32 and fail + let raw_pubkey_str = "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bx"; + let ret: Result = raw_pubkey_str.try_into(); + assert!(ret.is_err()); + + // wrong length hex string, should be interpreted as base32 and fail + let raw_pubkey_str = "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bbff"; + let ret: Result = raw_pubkey_str.try_into(); + assert!(ret.is_err()); + + // wrong length ov3 string + let onion_address_str = "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyidx"; + let ret: Result = onion_address_str.try_into(); + assert!(ret.is_err()); + + // not base 32 ov3 string + let onion_address_str = "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyi-"; + let ret: Result = onion_address_str.try_into(); + assert!(ret.is_err()); + + Ok(()) + } +}