v0.3.10-pre.004

This commit is contained in:
2026-09-06 17:15:51 +02:00
parent 8309d829cc
commit e19a202565
20 changed files with 949 additions and 56 deletions

View File

@@ -1,5 +1,7 @@
// file: crates/ksp-raw-transaction-lib/src/signature.rs
// version: 1
// version: 2
use base64::Engine; // rust-rules: trait-import
/// Maximum UTF-8 byte length admitted for one textual Base58 Solana transaction signature.
pub const MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES: usize = 88;
@@ -50,6 +52,80 @@ pub fn parse_raw_transaction_signature(value: &str) -> ksp_core_lib::Result<ksp_
return std::result::Result::Ok(ksp_store_api::RawTransactionSignature::new(decoded));
}
/// Extracts the first canonical 64-byte Solana signature from one complete Base64-encoded transaction wire.
///
/// The compact signature-count prefix must use its canonical short-vector representation, contain at least one signature,
/// and the decoded transaction must retain message bytes after the declared signature array.
pub fn extract_raw_transaction_signature_from_binary_base64(value: &str) -> ksp_core_lib::Result<ksp_store_api::RawTransactionSignature> {
if value.is_empty() || value.len() > ksp_store_api::MAX_RAW_PAYLOAD_BYTES {
return std::result::Result::Err(crate::signature_error());
}
let decoded = base64::engine::general_purpose::STANDARD.decode(value);
let decoded = match decoded {
std::result::Result::Ok(decoded) => decoded,
std::result::Result::Err(_) => return std::result::Result::Err(crate::signature_error()),
};
if base64::engine::general_purpose::STANDARD.encode(decoded.as_slice()) != value {
return std::result::Result::Err(crate::signature_error());
}
let prefix = decode_signature_count(decoded.as_slice());
let (signature_count, prefix_len) = match prefix {
std::result::Result::Ok(prefix) => prefix,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if signature_count == 0 {
return std::result::Result::Err(crate::signature_error());
}
let signatures_len = match signature_count.checked_mul(64) {
std::option::Option::Some(length) => length,
std::option::Option::None => return std::result::Result::Err(crate::signature_error()),
};
let message_offset = match prefix_len.checked_add(signatures_len) {
std::option::Option::Some(offset) => offset,
std::option::Option::None => return std::result::Result::Err(crate::signature_error()),
};
if message_offset >= decoded.len() {
return std::result::Result::Err(crate::signature_error());
}
let first_end = match prefix_len.checked_add(64) {
std::option::Option::Some(end) => end,
std::option::Option::None => return std::result::Result::Err(crate::signature_error()),
};
let first = match decoded.get(prefix_len..first_end) {
std::option::Option::Some(first) => first,
std::option::Option::None => return std::result::Result::Err(crate::signature_error()),
};
let mut signature = [0_u8; 64];
signature.copy_from_slice(first);
return std::result::Result::Ok(ksp_store_api::RawTransactionSignature::new(signature));
}
fn decode_signature_count(bytes: &[u8]) -> ksp_core_lib::Result<(usize, usize)> {
let mut value = 0_usize;
for index in 0..3_usize {
let byte = match bytes.get(index) {
std::option::Option::Some(byte) => *byte,
std::option::Option::None => return std::result::Result::Err(crate::signature_error()),
};
let payload = usize::from(byte & 0x7f);
let shift = index * 7;
value |= payload << shift;
if byte & 0x80 == 0 {
if index != 0 {
let minimum = 1_usize << shift;
if value < minimum {
return std::result::Result::Err(crate::signature_error());
}
}
if value > usize::from(u16::MAX) {
return std::result::Result::Err(crate::signature_error());
}
return std::result::Result::Ok((value, index + 1));
}
}
return std::result::Result::Err(crate::signature_error());
}
fn base58_digit(byte: u8) -> std::option::Option<u8> {
return match byte {
b'1'..=b'9' => std::option::Option::Some(byte - b'1'),