use sha2::{Digest, Sha256};
const BASE64_URL_ALPHABET: &[u8; 64] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
pub fn tn_public_address_encode(out: &mut [u8], input: &[u8; 32]) {
let mut checksum: u64 = 0;
out[0] = b't';
out[1] = b'a';
let mut encoded_len = 2;
let mut accumulator: u32 = 0;
let mut bits_collected = 0;
let mut data_len = 30;
let mut data_idx = 0;
while data_len > 0 {
checksum += input[data_idx] as u64;
accumulator = (accumulator << 8) | input[data_idx] as u32;
bits_collected += 8;
data_idx += 1;
data_len -= 1;
while bits_collected >= 6 {
out[encoded_len] =
BASE64_URL_ALPHABET[((accumulator >> (bits_collected - 6)) & 0x3F) as usize];
encoded_len += 1;
bits_collected -= 6;
}
}
checksum += input[data_idx] as u64;
accumulator = (accumulator << 8) | input[data_idx] as u32;
bits_collected += 8;
data_idx += 1;
checksum += input[data_idx] as u64;
accumulator = (accumulator << 8) | input[data_idx] as u32;
bits_collected += 8;
accumulator = (accumulator << 8) | ((checksum & 0xFF) as u32);
bits_collected += 8;
while bits_collected >= 6 {
out[encoded_len] =
BASE64_URL_ALPHABET[((accumulator >> (bits_collected - 6)) & 0x3F) as usize];
encoded_len += 1;
bits_collected -= 6;
}
}
pub fn tn_public_address_decode(out: &mut [u8; 32], input: &[u8]) -> Result<(), i32> {
if input.len() != 46 {
return Err(-1);
}
if input[0] != b't' || input[1] != b'a' {
return Err(-2);
}
let mut in_sz = 40;
let mut in_idx = 2;
let mut checksum: u64 = 0;
let mut out_idx = 0;
let mut invlut = [0xFFu8; 256];
for (i, &b) in BASE64_URL_ALPHABET.iter().enumerate() {
invlut[b as usize] = i as u8;
}
while in_sz >= 4 {
let a = invlut[input[in_idx + 0] as usize] as i32;
let b = invlut[input[in_idx + 1] as usize] as i32;
let c = invlut[input[in_idx + 2] as usize] as i32;
let d = invlut[input[in_idx + 3] as usize] as i32;
if a < 0 || b < 0 || c < 0 || d < 0 {
return Err(-3);
}
let triple = ((a as u32) << 18) | ((b as u32) << 12) | ((c as u32) << 6) | (d as u32);
let temp1 = ((triple >> 16) & 0xFF) as u8;
checksum += temp1 as u64;
out[out_idx] = temp1;
out_idx += 1;
let temp2 = ((triple >> 8) & 0xFF) as u8;
checksum += temp2 as u64;
out[out_idx] = temp2;
out_idx += 1;
let temp3 = (triple & 0xFF) as u8;
checksum += temp3 as u64;
out[out_idx] = temp3;
out_idx += 1;
in_idx += 4;
in_sz -= 4;
}
let a = invlut[input[in_idx + 0] as usize] as i32;
let b = invlut[input[in_idx + 1] as usize] as i32;
let c = invlut[input[in_idx + 2] as usize] as i32;
let d = invlut[input[in_idx + 3] as usize] as i32;
if a < 0 || b < 0 || c < 0 || d < 0 {
return Err(-4);
}
let triple = ((a as u32) << 18) | ((b as u32) << 12) | ((c as u32) << 6) | (d as u32);
let temp1 = ((triple >> 16) & 0xFF) as u8;
checksum += temp1 as u64;
out[out_idx] = temp1;
out_idx += 1;
let temp2 = ((triple >> 8) & 0xFF) as u8;
checksum += temp2 as u64;
out[out_idx] = temp2;
let incoming_checksum = (triple & 0xFF) as u8;
checksum = checksum & 0xFF;
if checksum as u8 != incoming_checksum {
return Err(-5);
}
Ok(())
}
pub fn tn_pubkey_to_address_string(pubkey: &[u8; 32]) -> String {
let mut out = [0u8; 46];
tn_public_address_encode(&mut out, pubkey);
String::from_utf8_lossy(&out).to_string()
}
pub fn create_program_defined_account_address(
owner: &[u8; 32],
is_ephemeral: bool,
seed: &[u8; 32],
) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(owner);
hasher.update(&[if is_ephemeral { 1u8 } else { 0u8 }]);
hasher.update(seed);
let result = hasher.finalize();
let mut pubkey = [0u8; 32];
pubkey.copy_from_slice(&result);
pubkey
}
pub fn create_program_defined_account_address_string(
owner: &[u8; 32],
is_ephemeral: bool,
seed: &[u8; 32],
) -> String {
let pubkey = create_program_defined_account_address(owner, is_ephemeral, seed);
tn_pubkey_to_address_string(&pubkey)
}
pub fn pack_seed(arg0: u64, arg1: u64, arg2: u64, arg3: u64) -> [u8; 32] {
let mut seed = [0u8; 32];
seed[0..8].copy_from_slice(&arg0.to_le_bytes());
seed[8..16].copy_from_slice(&arg1.to_le_bytes());
seed[16..24].copy_from_slice(&arg2.to_le_bytes());
seed[24..32].copy_from_slice(&arg3.to_le_bytes());
seed
}
#[cfg(test)]
mod tests {
use super::*;
use rand::Rng;
#[test]
fn test_public_address_encoder() {
let mut rng = rand::rng();
let mut pub_key = [0u8; 32];
let mut decoded_pub_key = [0u8; 32];
let mut encoded_pub = [0u8; 64]; for _ in 0..10_000 {
rng.fill_bytes(&mut pub_key);
encoded_pub.fill(0);
tn_public_address_encode(&mut encoded_pub, &pub_key);
let encoded_slice = &encoded_pub[..46];
let res = tn_public_address_decode(&mut decoded_pub_key, encoded_slice);
assert!(res.is_ok(), "decode failed: {:?}", res);
assert_eq!(pub_key, decoded_pub_key, "roundtrip mismatch");
}
let mut corrupted = [0u8; 64];
rng.fill_bytes(&mut pub_key);
tn_public_address_encode(&mut corrupted, &pub_key);
let encoded_slice = &mut corrupted[..46];
encoded_slice[45] = if encoded_slice[45] == b'A' {
b'B'
} else {
b'A'
};
let res = tn_public_address_decode(&mut decoded_pub_key, encoded_slice);
assert_eq!(res, Err(-5), "corrupt checksum should fail with -5");
}
#[test]
fn test_create_program_defined_account_address() {
let owner = [1u8; 32];
let seed = pack_seed(
0x1234567890abcdef,
0xfedcba0987654321,
0x1111111111111111,
0x2222222222222222,
);
let addr1 = create_program_defined_account_address(&owner, false, &seed);
let addr2 = create_program_defined_account_address(&owner, false, &seed);
assert_eq!(addr1, addr2, "Same inputs should produce same address");
let addr3 = create_program_defined_account_address(&owner, true, &seed);
assert_ne!(addr1, addr3, "Ephemeral flag should change the address");
let different_seed = pack_seed(
0xaaaaaaaaaaaaaaaa,
0xbbbbbbbbbbbbbbbb,
0xcccccccccccccccc,
0xdddddddddddddddd,
);
let addr4 = create_program_defined_account_address(&owner, false, &different_seed);
assert_ne!(
addr1, addr4,
"Different seed should produce different address"
);
let different_owner = [2u8; 32];
let addr5 = create_program_defined_account_address(&different_owner, false, &seed);
assert_ne!(
addr1, addr5,
"Different owner should produce different address"
);
let addr_string = create_program_defined_account_address_string(&owner, false, &seed);
assert!(
addr_string.starts_with("ta"),
"Address string should start with 'ta'"
);
assert_eq!(
addr_string.len(),
46,
"Address string should be 46 characters"
);
}
#[test]
fn test_ephemeral_account_addresses() {
let system_program = [0u8; 32];
let seed = pack_seed(
0x1111111111111111,
0x2222222222222222,
0x3333333333333333,
0x4444444444444444,
);
let ephemeral_addr = create_program_defined_account_address(&system_program, true, &seed);
let persistent_addr = create_program_defined_account_address(&system_program, false, &seed);
assert_ne!(
ephemeral_addr, persistent_addr,
"Ephemeral and persistent addresses should differ"
);
let ephemeral_addr2 = create_program_defined_account_address(&system_program, true, &seed);
assert_eq!(
ephemeral_addr, ephemeral_addr2,
"Ephemeral address should be deterministic"
);
}
#[test]
fn test_pack_seed() {
let seed = pack_seed(
0x1234567890abcdef,
0xfedcba0987654321,
0xaaaaaaaaaaaaaaaa,
0xbbbbbbbbbbbbbbbb,
);
assert_eq!(seed.len(), 32, "Packed seed should be exactly 32 bytes");
assert_eq!(&seed[0..8], &0x1234567890abcdefu64.to_le_bytes());
assert_eq!(&seed[8..16], &0xfedcba0987654321u64.to_le_bytes());
assert_eq!(&seed[16..24], &0xaaaaaaaaaaaaaaaau64.to_le_bytes());
assert_eq!(&seed[24..32], &0xbbbbbbbbbbbbbbbbu64.to_le_bytes());
let seed2 = pack_seed(1, 2, 3, 4);
assert_ne!(
seed, seed2,
"Different inputs should produce different seeds"
);
let seed3 = pack_seed(
0x1234567890abcdef,
0xfedcba0987654321,
0xaaaaaaaaaaaaaaaa,
0xbbbbbbbbbbbbbbbb,
);
assert_eq!(seed, seed3, "Same inputs should produce same seeds");
}
}