mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-03-07 01:21:09 +03:00
* updated util and libwallet with thiserror * update impl crate to thiserror * api crate converted to thiserror * update of controller crate to thiserror * update final bin + tests to thiserror * update unused import * remove failure derive * reset import of grin to master * update cargo lock * update from master * remove grin libs re-export from util crate :( * temp local * point code at bleeding edge grin master branch
204 lines
6.1 KiB
Rust
204 lines
6.1 KiB
Rust
// Copyright 2021 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 data_encoding::BASE32;
|
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
|
use ed25519_dalek::SecretKey as DalekSecretKey;
|
|
use grin_util::from_hex;
|
|
use sha3::{Digest, Sha3_256};
|
|
use std::convert::TryFrom;
|
|
use std::fmt;
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error, Serialize, Deserialize)]
|
|
/// OnionV3 Address Errors
|
|
pub enum OnionV3Error {
|
|
/// Error decoding an address from a string
|
|
#[error("Address Decoding: {0}")]
|
|
AddressDecoding(String),
|
|
/// Error with given private key
|
|
#[error("Invalid Private Key: {0}")]
|
|
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<Self, OnionV3Error> {
|
|
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()))
|
|
}
|
|
|
|
/// return dalek public key
|
|
pub fn to_ed25519(&self) -> Result<DalekPublicKey, OnionV3Error> {
|
|
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
|
|
pub 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()
|
|
}
|
|
|
|
/// return as http url
|
|
pub fn to_http_str(&self) -> String {
|
|
format!("http://{}.onion", self.to_ov3_str())
|
|
}
|
|
}
|
|
|
|
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<Self, Self::Error> {
|
|
// First attempt to decode a pubkey from hex
|
|
if let Ok(b) = from_hex(input) {
|
|
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<OnionV3Address, OnionV3Error> = 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<OnionV3Address, OnionV3Error> = raw_pubkey_str.try_into();
|
|
assert!(ret.is_err());
|
|
|
|
// wrong length ov3 string
|
|
let onion_address_str = "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyidx";
|
|
let ret: Result<OnionV3Address, OnionV3Error> = onion_address_str.try_into();
|
|
assert!(ret.is_err());
|
|
|
|
// not base 32 ov3 string
|
|
let onion_address_str = "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyi-";
|
|
let ret: Result<OnionV3Address, OnionV3Error> = onion_address_str.try_into();
|
|
assert!(ret.is_err());
|
|
|
|
Ok(())
|
|
}
|
|
}
|