removed long addresses

This commit is contained in:
viraladmin 2026-06-04 10:18:55 -06:00
parent 6cd01d1345
commit 0aea32a2d5
41 changed files with 303 additions and 405 deletions

View File

@ -63,7 +63,7 @@ async fn main() {
wallet_address.clone(),
rpc_command,
handshake::HandshakeWallet::WalletParts {
long_address: wallet.saved.long_address.clone(),
public_key: wallet.saved.public_key.clone(),
private_key: wallet.saved.private_key.clone(),
},
hashmap_key,

View File

@ -144,7 +144,7 @@ async fn main() {
address.clone(),
rpc_command,
handshake::HandshakeWallet::WalletParts {
long_address: wallet.saved.long_address.clone(),
public_key: wallet.saved.public_key.clone(),
private_key: wallet.saved.private_key.clone(),
},
generate_uid(),

View File

@ -33,14 +33,12 @@ fn extract_address(contents: &str) -> Result<String, String> {
}
if trimmed.starts_with('{') {
// Prefer short_address for balance lookups, but accept long_address too.
let value: Value =
from_str(trimmed).map_err(|e| format!("Failed to parse wallet JSON: {e}"))?;
let address = value
.get("short_address")
.and_then(|v| v.as_str())
.or_else(|| value.get("vanity_address").and_then(|v| v.as_str()))
.or_else(|| value.get("long_address").and_then(|v| v.as_str()))
.ok_or_else(|| "Wallet JSON does not contain a usable address field".to_string())?;
return Ok(address.trim().to_string());
}
@ -149,7 +147,7 @@ async fn main() {
json.clone(),
rpc_command,
handshake::HandshakeWallet::WalletParts {
long_address: wallet.saved.long_address.clone(),
public_key: wallet.saved.public_key.clone(),
private_key: wallet.saved.private_key.clone(),
},
hashmap_key,

View File

@ -36,7 +36,7 @@ fn display_vanity_address(short_address: &str) -> Option<String> {
async fn lookup_registered_vanity_address(
short_address: &str,
long_address: &str,
public_key: &str,
private_key: &str,
) -> Option<String> {
let hashmap_key = generate_uid();
@ -53,7 +53,7 @@ async fn lookup_registered_vanity_address(
short_address.to_string(),
rpc_command,
handshake::HandshakeWallet::WalletParts {
long_address: long_address.to_string(),
public_key: public_key.to_string(),
private_key: private_key.to_string(),
},
hashmap_key,
@ -209,17 +209,14 @@ async fn main() {
match Wallet::regenerate_public_key(&private_key) {
Ok(public_key_bytes) => {
let public_key = blockchain::encode(public_key_bytes.clone());
let long_address = Wallet::generate_address(&public_key);
let long_address_bytes = Wallet::long_address_to_bytes(long_address.clone());
let short_address_bytes =
Wallet::long_address_bytes_to_short_address_bytes(&long_address_bytes)
Wallet::public_key_bytes_to_short_address_bytes(&public_key_bytes)
.expect("Failed to derive short address bytes");
let short_address = Wallet::bytes_to_short_address(&short_address_bytes)
.expect("Failed to encode short address");
let vanity_address =
lookup_registered_vanity_address(&short_address, &long_address, &private_key).await;
lookup_registered_vanity_address(&short_address, &public_key, &private_key).await;
let saved_wallet = SavedWallet {
long_address,
short_address,
vanity_address,
public_key,

View File

@ -34,7 +34,7 @@ fn display_vanity_address(short_address: &str) -> Option<String> {
async fn lookup_registered_vanity_address(
short_address: &str,
long_address: &str,
public_key: &str,
private_key: &str,
) -> Option<String> {
let hashmap_key = generate_uid();
@ -51,7 +51,7 @@ async fn lookup_registered_vanity_address(
short_address.to_string(),
rpc_command,
handshake::HandshakeWallet::WalletParts {
long_address: long_address.to_string(),
public_key: public_key.to_string(),
private_key: private_key.to_string(),
},
hashmap_key,
@ -246,17 +246,14 @@ async fn main() {
match Wallet::regenerate_public_key(&private_key) {
Ok(public_key_bytes) => {
let public_key = blockchain::encode(public_key_bytes.clone());
let long_address = Wallet::generate_address(&public_key);
let long_address_bytes = Wallet::long_address_to_bytes(long_address.clone());
let short_address_bytes =
Wallet::long_address_bytes_to_short_address_bytes(&long_address_bytes)
Wallet::public_key_bytes_to_short_address_bytes(&public_key_bytes)
.expect("Failed to derive short address bytes");
let short_address = Wallet::bytes_to_short_address(&short_address_bytes)
.expect("Failed to encode short address");
let vanity_address =
lookup_registered_vanity_address(&short_address, &long_address, &private_key).await;
lookup_registered_vanity_address(&short_address, &public_key, &private_key).await;
let saved_wallet = SavedWallet {
long_address,
short_address,
vanity_address,
public_key,

View File

@ -35,9 +35,8 @@ async fn main() {
}
};
// The peer receives both addresses, but the signature proves they belong together.
// The peer receives the short address and raw public key; the signature proves they belong together.
let short_address = wallet.saved.short_address.clone();
let long_address = wallet.saved.long_address.clone();
let short_address_bytes = match Wallet::short_address_to_bytes(&short_address) {
Some(bytes) => bytes,
None => {
@ -45,20 +44,28 @@ async fn main() {
return;
}
};
let long_address_bytes = Wallet::long_address_to_bytes(long_address.clone());
let public_key_bytes = match Wallet::normalize_saved_public_key_bytes(&wallet.saved.public_key)
{
Some(bytes) => bytes,
None => {
eprintln!("Failed to decode public key from the wallet.");
return;
}
};
// The signed payload mirrors the binary request body: command byte, short address, long address.
// The signed payload mirrors the binary request body: command byte, short address, public key.
let mut signed_payload =
Vec::with_capacity(1 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + Wallet::ADDRESS_BYTES_LENGTH);
Vec::with_capacity(1 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + Wallet::PUBLIC_KEY_LENGTH);
signed_payload.push(RPC_REGISTER_WALLET);
signed_payload.extend_from_slice(&short_address_bytes);
signed_payload.extend_from_slice(&long_address_bytes);
signed_payload.extend_from_slice(&public_key_bytes);
let payload_hash = skein_256_hash_bytes(&signed_payload);
let signature = Wallet::sign_transaction(&payload_hash, &wallet.saved.private_key).await;
// sending_request encodes command 38 from this pipe-delimited payload.
let json = format!("{short_address}|{long_address}|{signature}");
let public_key_hex = blockchain::encode(&public_key_bytes);
let json = format!("{short_address}|{public_key_hex}|{signature}");
let rpc_command = 38;
let connections = get_connections().await;

View File

@ -30,13 +30,12 @@ fn extract_address(contents: &str) -> Result<String, String> {
}
if trimmed.starts_with('{') {
// Prefer the short address when present, but accept long_address for wallet validation too.
// Wallet JSON address validation uses the canonical short address.
let value: Value =
from_str(trimmed).map_err(|e| format!("Failed to parse wallet JSON: {e}"))?;
let address = value
.get("short_address")
.and_then(|v| v.as_str())
.or_else(|| value.get("long_address").and_then(|v| v.as_str()))
.ok_or_else(|| "Wallet JSON does not contain a usable address field".to_string())?;
return Ok(address.trim().to_string());
}
@ -100,9 +99,7 @@ async fn main() {
}
};
// Verify either a long wallet address or a current-network short address.
let message_hash = Wallet::wallet_validation(address.trim()).await
|| Wallet::short_address_validation(address.trim());
let message_hash = Wallet::short_address_validation(address.trim());
println!("{message_hash}");
}

View File

@ -1,8 +1,11 @@
use blockchain::common::cli_prompts::prompt_visible;
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
use blockchain::common::skein::skein_256_hash_data;
use blockchain::env;
use blockchain::from_str;
use blockchain::read_to_string;
use blockchain::records::wallet_registry::resolve_pubkey_from_short_address;
use blockchain::sled;
use blockchain::tilde;
use blockchain::wallets::structures::Wallet;
use blockchain::Value;
@ -41,13 +44,12 @@ fn extract_address(contents: &str) -> Result<String, String> {
}
if trimmed.starts_with('{') {
// Signature verification prefers the long address but can also verify with a short address.
// Signature verification uses the canonical short address.
let value: Value =
from_str(trimmed).map_err(|e| format!("Failed to parse wallet JSON: {e}"))?;
let address = value
.get("long_address")
.get("short_address")
.and_then(|v| v.as_str())
.or_else(|| value.get("short_address").and_then(|v| v.as_str()))
.ok_or_else(|| "Wallet JSON does not contain a usable address field".to_string())?;
return Ok(address.trim().to_string());
}
@ -140,7 +142,31 @@ async fn main() {
// Hash the message exactly as sign_message does before verifying the wallet signature.
let message_hash = skein_256_hash_data(message.as_str());
let signature = Wallet::verify_transaction(&message_hash, &signature, address.trim()).await;
let (
_network_name,
_padded_base_coin,
_suffix,
_torrent_path,
_wallet_path,
_block_path,
db_path,
_balance_path,
_log_path,
) = block_extension_and_paths();
let db = match sled::open(&db_path) {
Ok(db) => db,
Err(err) => {
eprintln!("Failed to open wallet registry database: {err}");
return;
}
};
let signature = match resolve_pubkey_from_short_address(&db, address.trim()) {
Ok(Some(pubkey)) => {
Wallet::verify_transaction_with_public_key_bytes(&message_hash, &signature, &pubkey)
.await
}
_ => false,
};
if signature {
println!("valid signature");

View File

@ -213,7 +213,7 @@ impl Connection {
port,
stream,
client_type,
wallet_address,
wallet_public_key,
command_map,
} = params;
@ -250,8 +250,7 @@ impl Connection {
return false;
}
let address = Wallet::long_address_to_bytes(wallet_address);
if address.len() != Wallet::ADDRESS_BYTES_LENGTH {
if wallet_public_key.len() != Wallet::PUBLIC_KEY_LENGTH {
return false;
}
let connection_info = ConnectionInfo::new(
@ -260,12 +259,17 @@ impl Connection {
port,
stream.clone(),
client_type.as_bytes(),
address.clone(),
wallet_public_key.clone(),
);
self.connection_map.insert(connection_key, connection_info);
if client_type == ClientType::Miner {
spawn_monitor_update(ip.clone(), MONITOR_ACTION_ADD, address.clone(), port);
spawn_monitor_update(
ip.clone(),
MONITOR_ACTION_ADD,
wallet_public_key.clone(),
port,
);
Connection::client_checkup(stream, connection_type, ip, port, command_map);
}
true
@ -290,7 +294,7 @@ impl Connection {
spawn_monitor_update(
ip.clone(),
MONITOR_ACTION_REMOVE,
connection_info.wallet_address.clone(),
connection_info.wallet_public_key.clone(),
port,
);
}
@ -509,7 +513,7 @@ impl Connection {
&& connection_key.port == port
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
{
Some(info.wallet_address.clone())
Some(info.wallet_public_key.clone())
} else {
None
}
@ -615,7 +619,7 @@ impl Connection {
}
}
fn spawn_monitor_update(ip: String, action: u8, peer_wallet_bytes: Vec<u8>, port: u16) {
fn spawn_monitor_update(ip: String, action: u8, peer_public_key: Vec<u8>, port: u16) {
tokio::spawn(async move {
let context = {
let guard = RECONNECT_CONTEXT.lock().await;
@ -624,8 +628,8 @@ fn spawn_monitor_update(ip: String, action: u8, peer_wallet_bytes: Vec<u8>, port
let Some(context) = context else {
return;
};
let peer_long = Wallet::bytes_to_long_address(peer_wallet_bytes);
let Some(monitored_address) = Wallet::normalize_to_short_address(&peer_long) else {
let Some(monitored_address) = Wallet::public_key_bytes_to_short_address(&peer_public_key)
else {
return;
};
let monitoring_address = context.wallet.saved.short_address.clone();

View File

@ -22,7 +22,10 @@ impl NodeInfo {
return;
}
};
let modified_by_bytes = Wallet::long_address_to_bytes(edit.modified_by.clone());
let modified_by_bytes = match Wallet::short_address_to_bytes(&edit.modified_by) {
Some(bytes) => bytes,
None => vec![0u8; Wallet::SHORT_ADDRESS_BYTES_LENGTH],
};
let modified_timestamp_bytes = edit.modified_timestamp.to_le_bytes();
let modified_signature_bytes = decode(&edit.modified_signature).unwrap();
let connections_lock = CONNECTIONS.read().await;
@ -90,7 +93,7 @@ impl NodeInfo {
// current timestamp so they can be propagated as fresh node events.
if edit.ip == remote_ip {
edit.modified_timestamp = current_timestamp;
edit.modified_by = wallet.saved.long_address.clone();
edit.modified_by = wallet.saved.short_address.clone();
edit.modified_signature =
Self::added_signature(&edit.address, &edit.ip, current_timestamp, &wallet).await;
}
@ -107,7 +110,18 @@ impl NodeInfo {
// Every add/delete edit is signed, so the network map accepts
// only node changes backed by a valid wallet signature.
if !Wallet::verify_transaction(&hashed_data, &edit.modified_signature, &edit.modified_by)
let Ok(Some(pubkey)) = crate::records::wallet_registry::resolve_pubkey_from_short_address(
&db,
&edit.modified_by,
) else {
return RpcResponse::Binary(b"Error: Could not validate signature".to_vec());
};
if !Wallet::verify_transaction_with_public_key_bytes(
&hashed_data,
&edit.modified_signature,
&pubkey,
)
.await
{
return RpcResponse::Binary(b"Error: Could not validate signature".to_vec());

View File

@ -129,7 +129,6 @@ impl NodeInfo {
else {
return false;
};
let signer = Wallet::bytes_to_long_address(pubkey);
let data = format!(
"{}{}{}{}{}",
edit.action,
@ -139,7 +138,12 @@ impl NodeInfo {
edit.modified_timestamp
);
let hashed_data = skein_256_hash_data(&data);
Wallet::verify_transaction(&hashed_data, &edit.modified_signature, &signer).await
Wallet::verify_transaction_with_public_key_bytes(
&hashed_data,
&edit.modified_signature,
&pubkey,
)
.await
}
pub async fn add_monitor(params: MonitorAddressParams) -> RpcResponse {

View File

@ -67,7 +67,8 @@ impl NodeInfo {
};
let ip_bytes = ip_to_binary(&node_info.ip);
let blocks_mined = node_info.blocks_mined;
let added_by_bytes = Wallet::long_address_to_bytes(node_info.added_by.to_string());
let added_by_bytes =
Wallet::short_address_to_bytes(&node_info.added_by).unwrap_or_default();
let added_timestamp_bytes = node_info.added_timestamp.to_le_bytes();
let deleted_timestamp_bytes = node_info.deleted_timestamp.to_le_bytes();
@ -82,7 +83,11 @@ impl NodeInfo {
data.extend_from_slice(&address_bytes);
data.extend_from_slice(&ip_bytes);
data.push(blocks_mined);
if added_by_bytes.len() == Wallet::SHORT_ADDRESS_BYTES_LENGTH {
data.extend_from_slice(&added_by_bytes);
} else {
data.extend_from_slice(&[0u8; Wallet::SHORT_ADDRESS_BYTES_LENGTH]);
}
data.extend_from_slice(&added_timestamp_bytes);
data.extend_from_slice(&added_signature_bytes);
data.extend_from_slice(&deleted_timestamp_bytes);
@ -107,7 +112,7 @@ impl NodeInfo {
) -> String {
// Node edits are signed over address, IP, signer, and timestamp
// so peers can independently verify the advertised change.
let added_by = wallet.saved.long_address.clone();
let added_by = wallet.saved.short_address.clone();
let private_key = &wallet.saved.private_key;
let data = format!("{address}{ip}{added_by}{current_timestamp}");

View File

@ -14,7 +14,8 @@ pub const NODE_ADDRESS_OFFSET: usize = 0;
pub const NODE_IP_OFFSET: usize = NODE_ADDRESS_OFFSET + Wallet::SHORT_ADDRESS_BYTES_LENGTH;
pub const NODE_BLOCKS_MINED_OFFSET: usize = NODE_IP_OFFSET + NODE_IP_BYTES;
pub const NODE_ADDED_BY_OFFSET: usize = NODE_BLOCKS_MINED_OFFSET + NODE_BLOCKS_MINED_BYTES;
pub const NODE_ADDED_TIMESTAMP_OFFSET: usize = NODE_ADDED_BY_OFFSET + Wallet::ADDRESS_BYTES_LENGTH;
pub const NODE_ADDED_TIMESTAMP_OFFSET: usize =
NODE_ADDED_BY_OFFSET + Wallet::SHORT_ADDRESS_BYTES_LENGTH;
pub const NODE_ADDED_SIGNATURE_OFFSET: usize = NODE_ADDED_TIMESTAMP_OFFSET + NODE_TIMESTAMP_BYTES;
pub const NODE_DELETED_TIMESTAMP_OFFSET: usize =
NODE_ADDED_SIGNATURE_OFFSET + Wallet::SIGNATURE_LENGTH;

View File

@ -15,7 +15,7 @@ pub struct ConnectionInfo {
pub port: u16,
pub stream: Arc<Mutex<TcpStream>>,
pub client_type: Vec<u8>,
pub wallet_address: Vec<u8>,
pub wallet_public_key: Vec<u8>,
}
// ConnectionKey is the stable lookup key used inside the in-memory connection map.
@ -34,7 +34,7 @@ pub struct StoreConnectionParams {
pub port: u16,
pub stream: Arc<Mutex<TcpStream>>,
pub client_type: ClientType,
pub wallet_address: String,
pub wallet_public_key: Vec<u8>,
pub command_map: Arc<Mutex<Command>>,
}
@ -199,7 +199,7 @@ impl ConnectionInfo {
port: u16,
stream: Arc<Mutex<TcpStream>>,
client_type: Vec<u8>,
wallet_address: Vec<u8>,
wallet_public_key: Vec<u8>,
) -> Self {
ConnectionInfo {
connection_type,
@ -207,7 +207,7 @@ impl ConnectionInfo {
port,
stream,
client_type,
wallet_address,
wallet_public_key,
}
}
}

View File

@ -22,7 +22,8 @@ pub async fn prepare_handshake_message(wallet: &Wallet, message: &str) -> io::Re
}
};
let address_bin = Wallet::long_address_to_bytes(wallet.saved.long_address.clone());
let public_key_bin = Wallet::normalize_saved_public_key_bytes(&wallet.saved.public_key)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid wallet public key"))?;
let message_bin =
decode(message).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
let signed_bin =
@ -34,7 +35,7 @@ pub async fn prepare_handshake_message(wallet: &Wallet, message: &str) -> io::Re
let mut data = Vec::with_capacity(HANDSHAKE_REQUEST_BYTES);
data.extend_from_slice(&message_bin);
data.extend_from_slice(&signed_bin);
data.extend_from_slice(&address_bin);
data.extend_from_slice(&public_key_bin);
data.extend_from_slice(&time_bin);
data.extend_from_slice(&ip);

View File

@ -236,25 +236,21 @@ pub async fn process_handshake_response(
let returned_message_bin = &response[..HANDSHAKE_MESSAGE_BYTES];
let returned_signed_bin = &response[HANDSHAKE_SIGNATURE_OFFSET..HANDSHAKE_ADDRESS_OFFSET];
let returned_address_bin = &response[HANDSHAKE_ADDRESS_OFFSET..HANDSHAKE_RESPONSE_BYTES];
let returned_public_key_bin = &response[HANDSHAKE_ADDRESS_OFFSET..HANDSHAKE_RESPONSE_BYTES];
let returned_message = encode(returned_message_bin);
let returned_signed_message = encode(returned_signed_bin);
if Wallet::map_byte_to_wallet(returned_address_bin[0]).is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Invalid handshake wallet network byte",
));
}
let returned_address = Wallet::bytes_to_long_address(returned_address_bin.to_vec());
if !Wallet::wallet_validation(&returned_address).await {
if returned_public_key_bin.len() != Wallet::PUBLIC_KEY_LENGTH {
return Ok(());
}
let hash = skein_256_hash_data(&returned_message);
let valid_response_signature =
Wallet::verify_transaction(&hash, &returned_signed_message, &returned_address).await;
let valid_response_signature = Wallet::verify_transaction_with_public_key_bytes(
&hash,
&returned_signed_message,
returned_public_key_bin,
)
.await;
if returned_message == "ecaf" && valid_response_signature {
return Err(io::Error::other(
@ -290,7 +286,7 @@ pub async fn process_handshake_response(
port,
stream: Arc::clone(&stream),
client_type: ClientType::Miner,
wallet_address: returned_address.clone(),
wallet_public_key: returned_public_key_bin.to_vec(),
command_map: params.map.clone(),
}) {
return Err(io::Error::other(

View File

@ -19,16 +19,14 @@ pub async fn register_connected_wallet(
) -> Result<(), String> {
let short_address_bytes = Wallet::short_address_to_bytes(&wallet.saved.short_address)
.ok_or_else(|| "Failed to derive short address bytes from wallet".to_string())?;
let long_address_bytes = Wallet::long_address_to_bytes(wallet.saved.long_address.clone());
if long_address_bytes.len() != Wallet::ADDRESS_BYTES_LENGTH {
return Err("Startup wallet long address was invalid".to_string());
}
let public_key_bytes = Wallet::normalize_saved_public_key_bytes(&wallet.saved.public_key)
.ok_or_else(|| "Startup wallet public key was invalid".to_string())?;
let mut signed_payload =
Vec::with_capacity(1 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + Wallet::ADDRESS_BYTES_LENGTH);
Vec::with_capacity(1 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + Wallet::PUBLIC_KEY_LENGTH);
signed_payload.push(RPC_REGISTER_WALLET);
signed_payload.extend_from_slice(&short_address_bytes);
signed_payload.extend_from_slice(&long_address_bytes);
signed_payload.extend_from_slice(&public_key_bytes);
let payload_hash = skein_256_hash_bytes(&signed_payload);
let signature = Wallet::sign_transaction(&payload_hash, &wallet.saved.private_key).await;
@ -42,13 +40,13 @@ pub async fn register_connected_wallet(
let mut message = Vec::with_capacity(
1 + 3
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
+ Wallet::ADDRESS_BYTES_LENGTH
+ Wallet::PUBLIC_KEY_LENGTH
+ Wallet::SIGNATURE_LENGTH,
);
message.push(RPC_REGISTER_WALLET);
message.extend_from_slice(&hashmap_key);
message.extend_from_slice(&short_address_bytes);
message.extend_from_slice(&long_address_bytes);
message.extend_from_slice(&public_key_bytes);
message.extend_from_slice(&signature_bytes);
RpcResponse::send_raw(&stream, Some(&connections_key), &message).await;

View File

@ -32,14 +32,14 @@ pub async fn add_network_node(
read_bytes_from_stream::read_ip_from_stream(connections_key, stream_locked.clone()).await?;
let added_by_bytes = read_bytes_from_stream::read_usize_from_stream(
connections_key,
Wallet::ADDRESS_BYTES_LENGTH,
Wallet::SHORT_ADDRESS_BYTES_LENGTH,
stream_locked.clone(),
)
.await?;
let added_by = if added_by_bytes.iter().all(|&byte| byte == 0) {
String::new()
} else {
Wallet::bytes_to_long_address(added_by_bytes)
Wallet::bytes_to_short_address(&added_by_bytes).unwrap_or_default()
};
let added_timestamp =
read_bytes_from_stream::read_u64_from_stream(connections_key, stream_locked.clone())

View File

@ -11,7 +11,9 @@ pub async fn block_peer(
) -> RpcResponse {
// Peer blocking is restricted to the local node owner, proven by a
// signature from the locally loaded wallet over the target IP string.
if Wallet::verify_transaction(&ip, &signature, &wallet.saved.long_address).await {
let public_key =
Wallet::normalize_saved_public_key_bytes(&wallet.saved.public_key).unwrap_or_default();
if Wallet::verify_transaction_with_public_key_bytes(&ip, &signature, &public_key).await {
let tree = db.open_tree("blocked_peers").unwrap();
let key = ip.clone();
let value = b"blocked";

View File

@ -30,9 +30,13 @@ pub async fn delete_network_node(
.ok_or_else(|| "error: Invalid short address bytes".to_string())?;
let ip =
read_bytes_from_stream::read_ip_from_stream(connections_key, stream_locked.clone()).await?;
let deleted_by =
read_bytes_from_stream::read_wallet_from_stream(connections_key, stream_locked.clone())
let deleted_by_bytes = read_bytes_from_stream::read_short_address_from_stream(
connections_key,
stream_locked.clone(),
)
.await?;
let deleted_by = Wallet::bytes_to_short_address(&deleted_by_bytes)
.ok_or_else(|| "error: Invalid short address bytes".to_string())?;
let deleted_timestamp =
read_bytes_from_stream::read_u64_from_stream(connections_key, stream_locked.clone())
.await?;

View File

@ -11,7 +11,9 @@ pub async fn unblock_peer(
) -> RpcResponse {
// Peer unblocking is restricted to the local node owner, proven by a
// signature from the locally loaded wallet over the target IP string.
if Wallet::verify_transaction(&ip, &signature, &wallet.saved.long_address).await {
let public_key =
Wallet::normalize_saved_public_key_bytes(&wallet.saved.public_key).unwrap_or_default();
if Wallet::verify_transaction_with_public_key_bytes(&ip, &signature, &public_key).await {
let tree = db.open_tree("blocked_peers").unwrap();
let key = ip.clone();
let _ = tree.remove(key);

View File

@ -1,10 +1,16 @@
use crate::records::wallet_registry::resolve_pubkey_from_short_address;
use crate::rpc::responses::RpcResponse;
use crate::sled::Db;
use crate::wallets::structures::Wallet;
pub async fn validate(message: String, signature: String, address: String) -> RpcResponse {
pub async fn validate(message: String, signature: String, address: String, db: &Db) -> RpcResponse {
// Signed-message verification returns a simple text status for
// lightweight external clients.
if Wallet::verify_transaction(&message, &signature, &address).await {
let Ok(Some(pubkey)) = resolve_pubkey_from_short_address(db, &address) else {
return RpcResponse::Binary(b"invalid".to_vec());
};
if Wallet::verify_transaction_with_public_key_bytes(&message, &signature, &pubkey).await {
let msg = "valid".to_string().as_bytes().to_vec();
RpcResponse::Binary(msg)
} else {

View File

@ -15,7 +15,7 @@ use crate::Mutex;
async fn broadcast_wallet_registration(
short_address: &[u8],
long_address_bytes: &[u8],
public_key_bytes: &[u8],
signature: &str,
map: Arc<Mutex<Command>>,
remote_ip: &str,
@ -54,14 +54,14 @@ async fn broadcast_wallet_registration(
let mut message = Vec::with_capacity(
1 + 3
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
+ Wallet::ADDRESS_BYTES_LENGTH
+ Wallet::PUBLIC_KEY_LENGTH
+ Wallet::SIGNATURE_LENGTH,
);
// Wire layout: command, UID, short address, long address, signature.
// Wire layout: command, UID, short address, public key, signature.
message.push(RPC_REGISTER_WALLET);
message.extend_from_slice(&hashmap_key);
message.extend_from_slice(short_address);
message.extend_from_slice(long_address_bytes);
message.extend_from_slice(public_key_bytes);
message.extend_from_slice(&signature_bytes);
RpcResponse::send_raw(&unlocked_stream, Some(connections_key), &message).await;
@ -79,7 +79,7 @@ async fn broadcast_wallet_registration(
pub async fn register(
short_address_bytes: Vec<u8>,
long_address: String,
public_key_bytes: Vec<u8>,
signature: String,
db: &Db,
map: Arc<Mutex<Command>>,
@ -97,17 +97,12 @@ pub async fn register(
return RpcResponse::Binary(b"0".to_vec());
}
if !Wallet::wallet_validation(&long_address).await {
return RpcResponse::Binary(b"0".to_vec());
}
let long_address_bytes = Wallet::long_address_to_bytes(long_address.clone());
if long_address_bytes.len() != Wallet::ADDRESS_BYTES_LENGTH {
if public_key_bytes.len() != Wallet::PUBLIC_KEY_LENGTH {
return RpcResponse::Binary(b"0".to_vec());
}
let expected_short_address =
match Wallet::long_address_bytes_to_short_address_bytes(&long_address_bytes) {
match Wallet::public_key_bytes_to_short_address_bytes(&public_key_bytes) {
Some(address) => address,
None => return RpcResponse::Binary(b"0".to_vec()),
};
@ -119,27 +114,29 @@ pub async fn register(
}
let mut signed_payload =
Vec::with_capacity(1 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + Wallet::ADDRESS_BYTES_LENGTH);
Vec::with_capacity(1 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + Wallet::PUBLIC_KEY_LENGTH);
signed_payload.push(RPC_REGISTER_WALLET);
signed_payload.extend_from_slice(&short_address_bytes);
signed_payload.extend_from_slice(&long_address_bytes);
signed_payload.extend_from_slice(&public_key_bytes);
// Registrations are signed over the exact command payload so peers
// can verify rebroadcasted registrations without trusting the sender.
let payload_hash = skein_256_hash_bytes(&signed_payload);
if !Wallet::verify_transaction(&payload_hash, &signature, &long_address).await {
if !Wallet::verify_transaction_with_public_key_bytes(
&payload_hash,
&signature,
&public_key_bytes,
)
.await
{
return RpcResponse::Binary(b"0".to_vec());
}
let public_key_bytes = &long_address_bytes[1..];
// The registry stores short address -> public key; the long-address
// network byte is not part of the Falcon public key.
match register_short_address(db, &short_address_bytes, public_key_bytes) {
match register_short_address(db, &short_address_bytes, &public_key_bytes) {
Ok(WalletRegistrationResult::Inserted) => {
broadcast_wallet_registration(
&short_address_bytes,
&long_address_bytes,
&public_key_bytes,
&signature,
map,
&remote_ip,

View File

@ -6,8 +6,8 @@ pub const HANDSHAKE_IP_BYTES: usize = 18;
pub const HANDSHAKE_SIGNATURE_OFFSET: usize = HANDSHAKE_MESSAGE_BYTES;
pub const HANDSHAKE_ADDRESS_OFFSET: usize = HANDSHAKE_SIGNATURE_OFFSET + Wallet::SIGNATURE_LENGTH;
pub const HANDSHAKE_TIME_OFFSET: usize = HANDSHAKE_ADDRESS_OFFSET + Wallet::ADDRESS_BYTES_LENGTH;
pub const HANDSHAKE_TIME_OFFSET: usize = HANDSHAKE_ADDRESS_OFFSET + Wallet::PUBLIC_KEY_LENGTH;
pub const HANDSHAKE_IP_OFFSET: usize = HANDSHAKE_TIME_OFFSET + HANDSHAKE_TIME_BYTES;
pub const HANDSHAKE_REQUEST_BYTES: usize = HANDSHAKE_IP_OFFSET + HANDSHAKE_IP_BYTES;
pub const HANDSHAKE_RESPONSE_BYTES: usize = HANDSHAKE_ADDRESS_OFFSET + Wallet::ADDRESS_BYTES_LENGTH;
pub const HANDSHAKE_RESPONSE_BYTES: usize = HANDSHAKE_ADDRESS_OFFSET + Wallet::PUBLIC_KEY_LENGTH;

View File

@ -151,18 +151,15 @@ pub async fn read_usize_from_stream(
Ok(buffer)
}
pub async fn read_wallet_from_stream(
pub async fn read_public_key_from_stream(
key: &str,
stream_locked: Arc<Mutex<TcpStream>>,
) -> Result<String, String> {
// Wallet addresses use a fixed binary length on the wire and are
// decoded into their CLTC string form here.
) -> Result<Vec<u8>, String> {
let mut stream = stream_locked.lock().await;
let mut buffer = vec![0u8; Wallet::ADDRESS_BYTES_LENGTH];
let mut buffer = vec![0u8; Wallet::PUBLIC_KEY_LENGTH];
read_exact_from_stream(key, &mut stream, &mut buffer).await?;
drop(stream);
let address = Wallet::bytes_to_long_address(buffer.to_vec());
Ok(address)
Ok(buffer)
}
pub async fn read_short_address_from_stream(

View File

@ -28,7 +28,7 @@ pub async fn write_to_memory(
received_ip_port: &str,
stream: Arc<Mutex<TcpStream>>,
client_type: &str,
wallet: &str,
wallet_public_key: Vec<u8>,
command_map: Arc<Mutex<Command>>,
) -> String {
// Reject unknown connection labels before the connection manager is
@ -52,7 +52,7 @@ pub async fn write_to_memory(
port,
stream: stream.clone(),
client_type,
wallet_address: wallet.to_string(),
wallet_public_key,
command_map,
});

View File

@ -90,7 +90,7 @@ pub async fn handle_handshake(
let Ok((
received_message,
received_signed_message,
received_address,
received_public_key,
hash,
received_ip,
peer_time,
@ -116,7 +116,7 @@ pub async fn handle_handshake(
incoming_connections,
hash: &hash,
received_signed_message: &received_signed_message,
received_address: &received_address,
received_address: &received_public_key,
received_ip: &received_ip,
})
.await
@ -177,12 +177,20 @@ pub async fn handle_handshake(
}
}
let received_public_key_bytes = match crate::decode(&received_public_key) {
Ok(bytes) if bytes.len() == Wallet::PUBLIC_KEY_LENGTH => bytes,
_ => {
drop_failed_handshake(&stream).await;
return;
}
};
// write to memory
let connections_key = write_to_memory(
&received_ip,
stream.clone(),
connection_type,
&received_address,
received_public_key_bytes,
map.clone(),
)
.await;
@ -202,7 +210,7 @@ pub async fn handle_handshake(
connection_type: connection_type.to_string(),
wallet: wallet.clone(),
map,
returned_address: received_address.clone(),
returned_address: received_public_key.clone(),
};
if combine_and_send_data(params).await.is_ok() && is_miner {
complete_incoming_miner_setup(

View File

@ -48,14 +48,7 @@ pub async fn parse_received_data(
let received_message = encode(&buffer[..HANDSHAKE_MESSAGE_BYTES]);
let received_signed_message =
encode(&buffer[HANDSHAKE_SIGNATURE_OFFSET..HANDSHAKE_ADDRESS_OFFSET]);
let received_address_bytes = &buffer[HANDSHAKE_ADDRESS_OFFSET..HANDSHAKE_TIME_OFFSET];
// Long wallet addresses carry the network byte at the front, so
// reject the handshake before rebuilding an address from bad bytes.
if Wallet::map_byte_to_wallet(received_address_bytes[0]).is_empty() {
return Err("error: Invalid handshake wallet network byte".to_string());
}
let received_address = Wallet::bytes_to_long_address(received_address_bytes.to_vec());
let received_public_key = encode(&buffer[HANDSHAKE_ADDRESS_OFFSET..HANDSHAKE_TIME_OFFSET]);
// The timestamp and announced ip:port are kept as raw protocol bytes
// until this point so the offsets remain explicit.
@ -70,7 +63,7 @@ pub async fn parse_received_data(
Ok((
received_message,
received_signed_message,
received_address,
received_public_key,
hash,
received_ip,
peer_time,
@ -81,8 +74,7 @@ pub async fn generate_and_sign_message(
connection_type: &str,
wallet: &Wallet,
) -> Result<(String, String, String), String> {
// get the wallet info so we can sign our return message
let address = wallet.saved.long_address.clone();
let public_key = wallet.saved.public_key.clone();
// if miner face is the return message, used because its hex so compressed better
// otherwise face spelledbackwards is used
@ -93,7 +85,7 @@ pub async fn generate_and_sign_message(
let hashed = skein_256_hash_data(message);
let signed_message = Wallet::sign_transaction(&hashed, &wallet.saved.private_key).await;
Ok((
address.to_string(),
public_key.to_string(),
message.to_string(),
signed_message.to_string(),
))
@ -104,7 +96,7 @@ pub async fn generate_and_sign_message(
let hashed = skein_256_hash_data(message);
let signed_message = Wallet::sign_transaction(&hashed, &wallet.saved.private_key).await;
Ok((
address.to_string(),
public_key.to_string(),
message.to_string(),
signed_message.to_string(),
))
@ -113,22 +105,23 @@ pub async fn generate_and_sign_message(
pub async fn return_handshake(
stream: Arc<Mutex<TcpStream>>,
address: &str,
public_key: &str,
message: &str,
signed_message: &str,
connections_key: &str,
) -> Result<(), String> {
// convert to binary/bytes
let address_bin = Wallet::long_address_to_bytes(address.to_string());
let public_key_bin = Wallet::normalize_saved_public_key_bytes(public_key)
.ok_or_else(|| "error: Invalid public key".to_string())?;
let message_bin = decode(message).expect("Failed to decode message");
let signed_bin = decode(signed_message).expect("Failed to decode signed message");
let mut data = Vec::with_capacity(HANDSHAKE_RESPONSE_BYTES);
// Response frames mirror the request layout: challenge, signature,
// then the wallet address that signed the challenge.
// then the public key that signed the challenge.
data.extend_from_slice(&message_bin);
data.extend_from_slice(&signed_bin);
data.extend(&address_bin);
data.extend(&public_key_bin);
// get stream lock
let mut stream_guard = stream.lock().await;
@ -164,13 +157,13 @@ pub async fn combine_and_send_data(params: CombineAndSendDataParams) -> Result<(
error!("Failed: {err}");
return Err(err);
}
let (address, message, signed_message) = result.unwrap();
let (public_key, message, signed_message) = result.unwrap();
// The handshake response must complete before the command loop is
// spawned, otherwise the peer may start reading command data early.
if let Err(err) = return_handshake(
stream.clone(),
&address,
&public_key,
&message,
&signed_message,
&connections_key,

View File

@ -108,7 +108,7 @@ pub async fn verify_handshake(
_map: Arc<Mutex<Command>>,
hash: &str,
received_signed_message: &str,
received_address: &str,
received_public_key: &str,
) -> bool {
// Signature verification proves the peer controls the wallet address
// embedded in the handshake request.
@ -116,7 +116,13 @@ pub async fn verify_handshake(
let padded_bytes = [hashmap_key[0], hashmap_key[1], hashmap_key[2], 0];
let uid = u32::from_le_bytes(padded_bytes);
if !Wallet::verify_transaction(hash, received_signed_message, received_address).await {
if !Wallet::verify_transaction_with_public_key(
hash,
received_signed_message,
received_public_key,
)
.await
{
let response_bytes = RpcResponse::Binary({
"error: Handshake failed: Invalid handshake"
.to_string()

View File

@ -483,7 +483,7 @@ pub async fn start_loop(
)
.await?;
let message = binary_to_string(message_bytes);
let address = read_bytes_from_stream::read_wallet_from_stream(
let address = read_bytes_from_stream::read_short_address_string_from_stream(
&connections_key,
stream_locked.clone(),
)
@ -495,7 +495,7 @@ pub async fn start_loop(
.await?;
let result =
commands::validate_message::validate(message, address, signature).await;
commands::validate_message::validate(message, signature, address, &db).await;
result
.send(&stream_locked, Some(&connections_key), uid)
.await;
@ -724,7 +724,7 @@ pub async fn start_loop(
stream_locked.clone(),
)
.await?;
let long_address = read_bytes_from_stream::read_wallet_from_stream(
let public_key = read_bytes_from_stream::read_public_key_from_stream(
&connections_key,
stream_locked.clone(),
)
@ -737,7 +737,7 @@ pub async fn start_loop(
let result = commands::wallet_register::register(
short_address,
long_address,
public_key,
signature,
&db,
map.clone(),

View File

@ -25,7 +25,7 @@ pub enum HandshakeWallet {
},
// Wallet recovery tools already have the address and private key before the wallet is saved.
WalletParts {
long_address: String,
public_key: String,
private_key: String,
},
}
@ -38,7 +38,7 @@ pub async fn connect_and_handshake(
hashmap_key: Byte3,
) -> Result<Vec<u8>, io::Error> {
// Resolve the wallet material before opening the stream so the handshake can sign its challenge.
let (long_address, private_key) = match wallet_source {
let (public_key, private_key) = match wallet_source {
HandshakeWallet::WalletKey {
encryption_key,
wallet_path,
@ -46,12 +46,12 @@ pub async fn connect_and_handshake(
let wallet = Wallet::try_obtain_wallet(encryption_key, Some(&wallet_path))
.await
.map_err(io::Error::other)?;
(wallet.saved.long_address, wallet.saved.private_key)
(wallet.saved.public_key, wallet.saved.private_key)
}
HandshakeWallet::WalletParts {
long_address,
public_key,
private_key,
} => (long_address, private_key),
} => (public_key, private_key),
};
let stream = TcpStream::connect(addr).await?;
@ -61,7 +61,7 @@ pub async fn connect_and_handshake(
stream,
json,
rpc_command,
long_address,
public_key,
private_key,
hashmap_key,
)
@ -81,7 +81,7 @@ async fn perform_handshake(
mut stream: TcpStream,
json: String,
rpc_command: usize,
address: String,
public_key: String,
private_key: String,
hashmap_key: Byte3,
) -> Result<Vec<u8>, io::Error> {
@ -93,7 +93,8 @@ async fn perform_handshake(
let signed_message = Wallet::sign_transaction(&hash, &private_key).await;
// The peer expects the handshake as fixed-width binary fields in this order.
let address_bin = Wallet::long_address_to_bytes(address);
let public_key_bin = Wallet::normalize_saved_public_key_bytes(&public_key)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid wallet public key"))?;
let message_bin =
decode(message).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
let signed_bin =
@ -105,7 +106,7 @@ async fn perform_handshake(
// Build the complete client handshake packet.
data.extend_from_slice(&message_bin);
data.extend_from_slice(&signed_bin);
data.extend_from_slice(&address_bin);
data.extend_from_slice(&public_key_bin);
data.extend_from_slice(&time_bin);
data.extend_from_slice(&ip);
@ -172,23 +173,26 @@ async fn perform_handshake(
let response = &received;
let returned_message_bin = &response[..HANDSHAKE_MESSAGE_BYTES];
let returned_signed_bin = &response[HANDSHAKE_SIGNATURE_OFFSET..HANDSHAKE_ADDRESS_OFFSET];
let returned_address_bin = &response[HANDSHAKE_ADDRESS_OFFSET..HANDSHAKE_RESPONSE_BYTES];
let returned_public_key_bin = &response[HANDSHAKE_ADDRESS_OFFSET..HANDSHAKE_RESPONSE_BYTES];
// Convert the returned binary fields into the formats used by wallet signature verification.
let returned_message = encode(returned_message_bin);
let returned_signed_message = encode(returned_signed_bin);
let complete_returned_address = Wallet::bytes_to_long_address(returned_address_bin.to_vec());
if complete_returned_address.is_empty() {
if returned_public_key_bin.len() != Wallet::PUBLIC_KEY_LENGTH {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Handshake returned an invalid wallet address",
"Handshake returned an invalid public key",
));
}
// The server challenge must be the expected marker and its signature must match its address.
let hash = skein_256_hash_data(&returned_message);
if returned_message == "ecaf" {
if Wallet::verify_transaction(&hash, &returned_signed_message, &complete_returned_address)
if Wallet::verify_transaction_with_public_key_bytes(
&hash,
&returned_signed_message,
returned_public_key_bin,
)
.await
{
// At this point the handshake is complete, so the stream can carry the RPC request.

View File

@ -404,30 +404,35 @@ async fn build_request_bytes(
bin_msg.extend_from_slice(&hashmap_key);
bin_msg.extend_from_slice(&address_bytes);
}
// Register a wallet using "short_address|long_address|signature".
// Register a wallet using "short_address|public_key|signature".
38 => {
let command_number: u8 = RPC_REGISTER_WALLET;
let (short_address, rest) = command_input.split_once('|').ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"Wallet registration input must be short|long|signature",
"Wallet registration input must be short|public_key|signature",
)
})?;
let (long_address, signature) = rest.split_once('|').ok_or_else(|| {
let (public_key, signature) = rest.split_once('|').ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"Wallet registration input must be short|long|signature",
"Wallet registration input must be short|public_key|signature",
)
})?;
let short_address_bytes =
Wallet::short_address_to_bytes(short_address).ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "Invalid short wallet address")
})?;
let long_address_bytes = Wallet::long_address_to_bytes(long_address.to_string());
if long_address_bytes.len() != Wallet::ADDRESS_BYTES_LENGTH {
let public_key_bytes = decode(public_key).map_err(|err| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("Invalid wallet registration public key: {err}"),
)
})?;
if public_key_bytes.len() != Wallet::PUBLIC_KEY_LENGTH {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid long wallet address",
"Invalid wallet registration public key",
));
}
let signature_bytes = decode(signature).map_err(|err| {
@ -440,7 +445,7 @@ async fn build_request_bytes(
bin_msg.extend_from_slice(&command_number.to_le_bytes());
bin_msg.extend_from_slice(&hashmap_key);
bin_msg.extend_from_slice(&short_address_bytes);
bin_msg.extend_from_slice(&long_address_bytes);
bin_msg.extend_from_slice(&public_key_bytes);
bin_msg.extend_from_slice(&signature_bytes);
}
// Lookup remote balance by long, short, or vanity address.

View File

@ -45,7 +45,7 @@ async fn lookup_vanity_owner(vanity_address: &str, wallet: &Wallet) -> Result<St
vanity_address.to_string(),
RPC_VANITY_OWNER_LOOKUP as usize,
HandshakeWallet::WalletParts {
long_address: wallet.saved.long_address.clone(),
public_key: wallet.saved.public_key.clone(),
private_key: wallet.saved.private_key.clone(),
},
generate_uid(),

View File

@ -29,8 +29,8 @@ async fn record_local_monitor_for_peer(
else {
return;
};
let peer_long = Wallet::bytes_to_long_address(peer_wallet_bytes);
let Some(monitored_address) = Wallet::normalize_to_short_address(&peer_long) else {
let Some(monitored_address) = Wallet::public_key_bytes_to_short_address(&peer_wallet_bytes)
else {
return;
};
let monitoring_address = wallet.saved.short_address.clone();
@ -90,7 +90,7 @@ pub async fn announce_self_to_network(
Some(bytes) => bytes,
None => return,
};
let modified_by_bytes = vec![0u8; Wallet::ADDRESS_BYTES_LENGTH];
let modified_by_bytes = vec![0u8; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
let time = Utc::now().timestamp_millis() as u64;
let modified_timestamp_bytes = time.to_le_bytes();
// Self-announcement is intentionally unsigned. The receiving node
@ -101,7 +101,7 @@ pub async fn announce_self_to_network(
1 + 3
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
+ 16
+ Wallet::ADDRESS_BYTES_LENGTH
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
+ 8
+ Wallet::SIGNATURE_LENGTH,
);
@ -180,7 +180,7 @@ pub async fn get_network_mapping(
let added_by = if added_by_bytes.iter().all(|&byte| byte == 0) {
String::new()
} else {
Wallet::bytes_to_long_address(added_by_bytes.to_vec())
Wallet::bytes_to_short_address(added_by_bytes).unwrap_or_default()
};
let added_timestamp = u64::from_le_bytes(
chunk[NODE_ADDED_TIMESTAMP_OFFSET..NODE_ADDED_SIGNATURE_OFFSET]

View File

@ -3,7 +3,7 @@ use crate::FnDsaKeyPair;
use crate::{decode, encode};
impl Wallet {
pub fn generate_keypair(network_byte: u8) -> (Vec<u8>, String) {
pub fn generate_keypair(_network_byte: u8) -> (Vec<u8>, String) {
// Generate a new FN-DSA key pair using the wallet security parameter.
let keypair =
FnDsaKeyPair::generate(Self::FN_DSA_LOGN).expect("Failed to generate FN-DSA key pair");
@ -11,19 +11,12 @@ impl Wallet {
// Keep the private key as raw bytes until it is hex-encoded for storage.
let secret_key_bytes = keypair.private_key().to_vec();
// Keep the public key bytes separate so the network byte can be prepended.
let public_key_bytes = keypair.public_key().to_vec();
// Store long-address bytes as network byte plus the raw public key.
let mut encoded_public_key = Vec::with_capacity(Self::ADDRESS_BYTES_LENGTH);
encoded_public_key.push(network_byte);
encoded_public_key.extend_from_slice(&public_key_bytes);
// Private keys are persisted as hex before wallet-image encryption.
let private_key_hex = encode(secret_key_bytes);
// Return the network-tagged public key bytes and hex private key.
(encoded_public_key, private_key_hex)
(public_key_bytes, private_key_hex)
}
pub fn regenerate_public_key(private_key_hex: &str) -> Result<Vec<u8>, String> {
@ -49,10 +42,7 @@ impl Wallet {
let keypair = FnDsaKeyPair::from_private_key(&private_key_bytes)
.map_err(|e| format!("Failed to decode the provided FN-DSA private key: {e}"))?;
// Return the public key in the same network-prefixed byte layout used by long addresses.
let mut encoded_public_key = Vec::with_capacity(Self::ADDRESS_BYTES_LENGTH);
encoded_public_key.push(network_byte);
encoded_public_key.extend_from_slice(keypair.public_key());
Ok(encoded_public_key)
let _ = network_byte;
Ok(keypair.public_key().to_vec())
}
}

View File

@ -1,53 +1,16 @@
use crate::encode;
use crate::wallets::structures::SavedWallet;
use crate::wallets::structures::Wallet;
use crate::Path;
use crate::{create_img, encrypts};
use crate::{decode, encode};
impl Wallet {
// Derive the long wallet address from network-prefixed public key bytes.
pub fn generate_address(public_key: &str) -> String {
// Decode the hex public key into the long-address byte layout.
let public_key_bytes = decode(public_key).expect("Failed to decode public key hex");
// Long-address bytes must contain the network byte plus the public key.
if public_key_bytes.len() != Self::ADDRESS_BYTES_LENGTH {
panic!(
"Invalid FN-DSA public key byte count. Expected {} bytes, got {} bytes.",
Self::ADDRESS_BYTES_LENGTH,
public_key_bytes.len()
);
}
// The first byte selects the wallet prefix and XOR mask.
let network_byte = public_key_bytes[0];
// Convert the network byte back into the visible CLC/CLTC prefix.
let prefix_str = Self::map_byte_to_wallet(network_byte);
// XOR the public key body so the visible long address is network-bound.
let xored_public_key: Vec<u8> = public_key_bytes[1..]
.iter()
.map(|&byte| byte ^ network_byte)
.collect();
// Return the text long address as prefix plus encoded key body.
format!("{}{}", prefix_str, encode(xored_public_key))
}
pub fn create_wallet(&self, network_byte: u8) -> Wallet {
// Generate a fresh FN-DSA key pair for the selected network.
let (public_key, private_key) = Self::generate_keypair(network_byte);
// Build the visible long address from the network-prefixed public key.
let long_address = Self::generate_address(&encode(&public_key));
// Decode the long address so the canonical short address can be derived.
let long_address_bytes = Self::long_address_to_bytes(long_address.clone());
// Hash the public key body into the fixed 22-byte short-address format.
let short_address_bytes =
Self::long_address_bytes_to_short_address_bytes(&long_address_bytes)
let short_address_bytes = Self::public_key_bytes_to_short_address_bytes(&public_key)
.expect("Failed to derive short address from generated wallet");
// Convert the short-address bytes into the saved text form.
@ -82,7 +45,6 @@ impl Wallet {
// Build the wallet data saved to disk.
let saved = SavedWallet {
long_address,
short_address,
vanity_address: None,
public_key: encode(public_key),
@ -102,7 +64,6 @@ impl Wallet {
) -> Result<Wallet, String> {
// Start with an empty saved wallet so create_wallet has an encryption key holder.
let new_wallet = SavedWallet {
long_address: "".to_string(),
short_address: "".to_string(),
vanity_address: None,
public_key: "".to_string(),

View File

@ -14,15 +14,6 @@ impl Wallet {
return Self::bytes_to_vanity_address(&vanity_address_bytes);
}
// Treat anything else as a possible long wallet address.
let long_address_bytes = Self::long_address_to_bytes(address.to_string());
if long_address_bytes.len() != Self::ADDRESS_BYTES_LENGTH {
return None;
}
// Derive the canonical short address from valid long-address bytes.
let derived_short_address =
Self::long_address_bytes_to_short_address_bytes(&long_address_bytes)?;
Self::bytes_to_short_address(&derived_short_address)
None
}
}

View File

@ -2,7 +2,6 @@ use crate::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SavedWallet {
pub long_address: String,
pub short_address: String,
pub vanity_address: Option<String>,
pub public_key: String,
@ -26,10 +25,7 @@ impl Wallet {
pub const SHORT_ADDRESS_SEPARATOR: u8 = b'.';
pub fn display_wallet(&self) -> String {
let mut output = format!(
"Long Address: {}\nShort Address: {}",
self.saved.long_address, self.saved.short_address
);
let mut output = format!("Short Address: {}", self.saved.short_address);
if let Some(vanity_address) = &self.saved.vanity_address {
output.push_str(&format!("\nVanity Address: {vanity_address}"));
}

View File

@ -4,17 +4,6 @@ use crate::wallets::structures::Wallet;
use fn_dsa::{VerifyingKey, VerifyingKeyStandard, DOMAIN_NONE, HASH_ID_RAW};
impl Wallet {
pub async fn vrf_verify(number: u128, hash: &str, address: &str, signature: &str) -> bool {
// Derive the VRF number from the submitted signature.
let calculated_number = UnminedBlock::generate_random_number(signature).await;
// Accept the VRF only when the signature verifies and produces the expected number.
if Self::verify_transaction(hash, signature, address).await && calculated_number == number {
return true;
}
false
}
pub async fn vrf_verify_with_public_key(
number: u128,
hash: &str,
@ -33,30 +22,6 @@ impl Wallet {
false
}
pub async fn verify_transaction(message: &str, signature_hex: &str, address: &str) -> bool {
// Decode and length-check the submitted signature.
let sig_bytes = match decode(signature_hex) {
Ok(b) if b.len() == Self::SIGNATURE_LENGTH => b,
_ => return false,
};
// Decode the long wallet address into network-prefixed public key bytes.
let wallet_bytes = Self::long_address_to_bytes(address.to_string());
if wallet_bytes.len() != Self::ADDRESS_BYTES_LENGTH {
return false;
}
// Rebuild the FN-DSA verifying key from the public key body.
let verifying_key = match VerifyingKeyStandard::decode(&wallet_bytes[1..]) {
Some(key) => key,
None => return false,
};
// Verify decoded hash bytes when the message is hex, otherwise verify raw message bytes.
let message_bytes = decode(message).unwrap_or_else(|_| message.as_bytes().to_vec());
verifying_key.verify(&sig_bytes, &DOMAIN_NONE, &HASH_ID_RAW, &message_bytes)
}
pub async fn verify_transaction_with_public_key(
message: &str,
signature_hex: &str,
@ -84,4 +49,27 @@ impl Wallet {
let message_bytes = decode(message).unwrap_or_else(|_| message.as_bytes().to_vec());
verifying_key.verify(&sig_bytes, &DOMAIN_NONE, &HASH_ID_RAW, &message_bytes)
}
pub async fn verify_transaction_with_public_key_bytes(
message: &str,
signature_hex: &str,
public_key_bytes: &[u8],
) -> bool {
let sig_bytes = match decode(signature_hex) {
Ok(b) if b.len() == Self::SIGNATURE_LENGTH => b,
_ => return false,
};
if public_key_bytes.len() != Self::PUBLIC_KEY_LENGTH {
return false;
}
let verifying_key = match VerifyingKeyStandard::decode(public_key_bytes) {
Some(key) => key,
None => return false,
};
let message_bytes = decode(message).unwrap_or_else(|_| message.as_bytes().to_vec());
verifying_key.verify(&sig_bytes, &DOMAIN_NONE, &HASH_ID_RAW, &message_bytes)
}
}

View File

@ -1,7 +1,7 @@
use crate::wallets::structures::Wallet;
impl Wallet {
fn has_valid_public_key_bytes(public_key_bytes: &[u8]) -> bool {
pub fn has_valid_public_key_bytes(public_key_bytes: &[u8]) -> bool {
// Public keys must match the FN-DSA public key byte length exactly.
if public_key_bytes.len() != Self::PUBLIC_KEY_LENGTH {
return false;
@ -11,41 +11,6 @@ impl Wallet {
public_key_bytes[0] == Self::FN_DSA_LOGN as u8
}
// Validate a long wallet address for the active network and FN-DSA key layout.
pub async fn wallet_validation(wallet: &str) -> bool {
// Split the visible network prefix from the encoded key body.
let (network_prefix, encoded_key) = if let Some(rest) = wallet.strip_prefix("CLTC") {
("CLTC", rest)
} else if let Some(rest) = wallet.strip_prefix("CLC") {
("CLC", rest)
} else {
return false;
};
// Compare the wallet prefix against the currently compiled network.
let expected_byte = Self::current_network_byte();
if Wallet::map_wallet_to_byte(network_prefix) != expected_byte {
return false;
}
// Long wallet address bodies must be the exact encoded public key length.
if encoded_key.len() != Self::ADDRESS_HEX_LENGTH {
return false;
}
// The public key body is stored as hex text.
if !encoded_key.chars().all(|ch| ch.is_ascii_hexdigit()) {
return false;
}
// Decode the address and verify the network byte and public key payload.
let wallet_bytes = Self::long_address_to_bytes(wallet.to_string());
wallet_bytes.len() == Self::ADDRESS_BYTES_LENGTH
&& wallet_bytes[0] == expected_byte
&& Self::has_valid_public_key_bytes(&wallet_bytes[1..])
}
pub fn short_address_validation(short_address: &str) -> bool {
// Accept either canonical short-address bytes or vanity-address bytes.
let short_address_bytes = match Self::short_address_to_bytes(short_address)

View File

@ -1,79 +1,8 @@
use crate::common::skein::{ripemd160_hash_bytes, skein_256_hash_bytes};
use crate::log::error;
use crate::wallets::structures::Wallet;
use crate::{decode, encode};
impl Wallet {
pub fn long_address_to_bytes(wallet: String) -> Vec<u8> {
// Return an empty byte vector when the wallet cannot be decoded.
let mut wallet_bytes = Vec::new();
// Split the visible long-address prefix from the encoded public key body.
let (prefix, encoded_key) = if let Some(rest) = wallet.strip_prefix("CLTC") {
("CLTC", rest)
} else if let Some(rest) = wallet.strip_prefix("CLC") {
("CLC", rest)
} else {
return wallet_bytes;
};
// Long-address bodies must be exactly one public key encoded as hex.
if encoded_key.len() != Self::ADDRESS_HEX_LENGTH {
return wallet_bytes;
}
// Convert the visible network prefix into the stored network byte.
let prefix_byte = Self::map_wallet_to_byte(prefix);
if prefix_byte == 0 {
return wallet_bytes;
}
// Decode the XORed public key body from hex.
let xored_key_bytes = match decode(encoded_key) {
Ok(bytes) if bytes.len() == Self::PUBLIC_KEY_LENGTH => bytes,
_ => return wallet_bytes,
};
// Store the network byte first, followed by the unmasked public key bytes.
wallet_bytes.push(prefix_byte);
wallet_bytes.extend(xored_key_bytes.into_iter().map(|byte| byte ^ prefix_byte));
wallet_bytes
}
pub fn bytes_to_long_address(bytes: Vec<u8>) -> String {
// Long-address bytes must contain the network byte and public key body.
if bytes.len() != Self::ADDRESS_BYTES_LENGTH {
error!(
"Invalid byte length for wallet. Expected {} bytes, got {}",
Self::ADDRESS_BYTES_LENGTH,
bytes.len()
);
return String::new();
}
// Read the network byte and public key body from the fixed layout.
let prefix_byte = bytes[0];
let public_key_bytes = &bytes[1..Self::ADDRESS_BYTES_LENGTH];
// Convert the network byte into the visible long-address prefix.
let prefix = Self::map_byte_to_wallet(prefix_byte);
if prefix.is_empty() {
error!("Invalid wallet network byte: {prefix_byte}");
return String::new();
}
// XOR the public key body for the visible long-address encoding.
let xored_public_key: Vec<u8> = public_key_bytes
.iter()
.map(|byte| byte ^ prefix_byte)
.collect();
// Encode the masked public key body as hex and prepend the network prefix.
let address = encode(xored_public_key);
let wallet = format!("{prefix}{address}");
wallet
}
pub fn bytes_to_short_address(short_address_bytes: &[u8]) -> Option<String> {
// Short-address bytes must contain a 20-byte payload, separator, and network byte.
if short_address_bytes.len() != Self::SHORT_ADDRESS_BYTES_LENGTH {
@ -185,20 +114,13 @@ impl Wallet {
Some(format!("{payload}.{network_suffix}"))
}
pub fn long_address_bytes_to_short_address_bytes(long_address_bytes: &[u8]) -> Option<Vec<u8>> {
// Long-address bytes must contain the network byte and public key body.
if long_address_bytes.len() != Self::ADDRESS_BYTES_LENGTH {
pub fn public_key_bytes_to_short_address_bytes(public_key_bytes: &[u8]) -> Option<Vec<u8>> {
if public_key_bytes.len() != Self::PUBLIC_KEY_LENGTH {
return None;
}
// Keep the source network byte as the short-address network byte.
let network_byte = long_address_bytes[0];
if Self::map_byte_to_wallet(network_byte).is_empty() {
return None;
}
// Hash the public key body with Skein before RIPEMD-160 shortening.
let skein_hash_hex = skein_256_hash_bytes(&long_address_bytes[1..]);
let network_byte = Self::current_network_byte();
let skein_hash_hex = skein_256_hash_bytes(public_key_bytes);
let skein_hash_bytes = decode(&skein_hash_hex).ok()?;
// RIPEMD-160 produces the 20-byte short-address payload.
@ -217,6 +139,22 @@ impl Wallet {
Some(short_address)
}
pub fn public_key_bytes_to_short_address(public_key_bytes: &[u8]) -> Option<String> {
let short_address_bytes = Self::public_key_bytes_to_short_address_bytes(public_key_bytes)?;
Self::bytes_to_short_address(&short_address_bytes)
}
pub fn normalize_saved_public_key_bytes(public_key_hex: &str) -> Option<Vec<u8>> {
let bytes = decode(public_key_hex).ok()?;
if bytes.len() == Self::PUBLIC_KEY_LENGTH {
return Some(bytes);
}
if bytes.len() == Self::ADDRESS_BYTES_LENGTH && bytes[0] == Self::current_network_byte() {
return Some(bytes[1..].to_vec());
}
None
}
pub fn sanitize_vanity_address_bytes(payload: &str) -> Option<Vec<u8>> {
// Vanity payloads are stored as exactly 20 ASCII bytes.
if payload.len() != Self::SHORT_ADDRESS_HASH_BYTES_LENGTH {