use crate::error::BittensorError;
use crate::types::Hotkey;
use crate::AccountId;
use std::str::FromStr;
use sp_core::{sr25519, Pair};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NormalizedWeight {
pub uid: u16,
pub weight: u16,
}
pub fn normalize_weights(weights: &[(u16, u16)]) -> Vec<NormalizedWeight> {
if weights.is_empty() {
return vec![];
}
let total: u64 = weights.iter().map(|(_, w)| *w as u64).sum();
if total == 0 {
return weights
.iter()
.map(|(uid, _)| NormalizedWeight {
uid: *uid,
weight: 0,
})
.collect();
}
let target = u16::MAX as u64;
weights
.iter()
.map(|(uid, weight)| {
let normalized = ((*weight as u64 * target) / total) as u16;
NormalizedWeight {
uid: *uid,
weight: normalized,
}
})
.collect()
}
pub fn set_weights_payload(
netuid: u16,
weights: Vec<NormalizedWeight>,
version_key: u64,
) -> impl subxt::tx::Payload {
use crate::api::api;
let (dests, values): (Vec<u16>, Vec<u16>) =
weights.into_iter().map(|w| (w.uid, w.weight)).unzip();
api::tx()
.subtensor_module()
.set_weights(netuid, dests, values, version_key)
}
pub fn verify_bittensor_signature(
hotkey: &Hotkey,
signature_hex: &str,
data: &[u8],
) -> Result<(), BittensorError> {
if signature_hex.is_empty() {
return Err(BittensorError::AuthError {
message: "Empty signature".to_string(),
});
}
if data.is_empty() {
return Err(BittensorError::AuthError {
message: "Empty data".to_string(),
});
}
let signature_bytes = hex::decode(signature_hex).map_err(|e| BittensorError::AuthError {
message: format!("Invalid hex signature format: {e}"),
})?;
let account_id =
AccountId::from_str(hotkey.as_str()).map_err(|_| BittensorError::InvalidHotkey {
hotkey: hotkey.as_str().to_string(),
})?;
if signature_bytes.len() != 64 {
return Err(BittensorError::AuthError {
message: format!(
"Invalid signature length: expected 64 bytes, got {}",
signature_bytes.len()
),
});
}
let mut signature_array = [0u8; 64];
signature_array.copy_from_slice(&signature_bytes);
let signature = sr25519::Signature::from_raw(signature_array);
use sp_runtime::traits::Verify;
let public_key = sr25519::Public::from_raw(account_id.0);
let is_valid = signature.verify(data, &public_key);
if is_valid {
Ok(())
} else {
Err(BittensorError::AuthError {
message: "Signature verification failed".to_string(),
})
}
}
pub type BittensorSignature = sr25519::Signature;
pub fn sign_with_keypair(keypair: &sr25519::Pair, message: &[u8]) -> BittensorSignature {
keypair.sign(message)
}
pub fn sign_message_hex(keypair: &sr25519::Pair, message: &[u8]) -> String {
let signature = sign_with_keypair(keypair, message);
hex::encode(signature.0)
}
pub fn create_signature<T>(signer: &T, data: &[u8]) -> String
where
T: subxt::tx::Signer<subxt::PolkadotConfig>,
{
let signature = signer.sign(data);
match signature {
subxt::utils::MultiSignature::Sr25519(sig) => hex::encode(sig),
_ => hex::encode([0u8; 64]),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_weights_empty() {
let result = normalize_weights(&[]);
assert!(result.is_empty());
}
#[test]
fn test_normalize_weights_zero_weights() {
let weights = vec![(0, 0), (1, 0)];
let result = normalize_weights(&weights);
assert_eq!(result.len(), 2);
assert_eq!(result[0].weight, 0);
assert_eq!(result[1].weight, 0);
}
#[test]
fn test_normalize_weights_equal() {
let weights = vec![(0, 100), (1, 100)];
let result = normalize_weights(&weights);
assert_eq!(result.len(), 2);
assert!(result[0].weight > 30000);
assert!(result[1].weight > 30000);
}
#[test]
fn test_normalize_weights_unequal() {
let weights = vec![(0, 75), (1, 25)];
let result = normalize_weights(&weights);
assert_eq!(result.len(), 2);
assert!(result[0].weight > result[1].weight * 2);
}
#[test]
fn test_signature_verification_empty_signature() {
let hotkey =
Hotkey::new("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".to_string()).unwrap();
let result = verify_bittensor_signature(&hotkey, "", b"data");
assert!(result.is_err());
}
#[test]
fn test_signature_verification_empty_data() {
let hotkey =
Hotkey::new("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".to_string()).unwrap();
let result = verify_bittensor_signature(&hotkey, &"ab".repeat(64), b"");
assert!(result.is_err());
}
#[test]
fn test_signature_verification_invalid_hex() {
let hotkey =
Hotkey::new("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".to_string()).unwrap();
let result = verify_bittensor_signature(&hotkey, "not_hex!", b"data");
assert!(result.is_err());
}
#[test]
fn test_signature_verification_wrong_length() {
let hotkey =
Hotkey::new("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".to_string()).unwrap();
let result = verify_bittensor_signature(&hotkey, "abcd", b"data");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("length"));
}
#[test]
fn test_sign_and_verify() {
use sp_core::Pair;
let (pair, _) = sr25519::Pair::generate();
let message = b"test message";
let signature = sign_with_keypair(&pair, message);
use sp_runtime::traits::Verify;
let public = pair.public();
assert!(signature.verify(message.as_slice(), &public));
}
#[test]
fn test_sign_message_hex() {
use sp_core::Pair;
let (pair, _) = sr25519::Pair::generate();
let message = b"test message";
let hex_sig = sign_message_hex(&pair, message);
assert_eq!(hex_sig.len(), 128);
assert!(hex::decode(&hex_sig).is_ok());
}
}