use krusty_kms_common::Result;
use starknet_types_core::curve::ProjectivePoint;
use starknet_types_core::felt::Felt;
#[derive(Debug, Clone)]
pub struct CipherBalance {
pub l: ProjectivePoint,
pub r: ProjectivePoint,
}
#[derive(Debug, Clone)]
pub struct AccountState {
pub balance: CipherBalance,
pub pending: CipherBalance,
pub nonce: Felt,
}
#[derive(Debug, Clone)]
pub struct DecryptedAccountState {
pub balance: u128,
pub pending: u128,
pub nonce: Felt,
}
pub fn decrypt_cipher_balance(private_key: &Felt, cipher: &CipherBalance) -> Result<u128> {
let r_x = multiply_point(&cipher.r, private_key)?;
let g_m = subtract_points(&cipher.l, &r_x)?;
let balance = discrete_log_brute_force(&g_m)?;
Ok(balance)
}
fn multiply_point(point: &ProjectivePoint, scalar: &Felt) -> Result<ProjectivePoint> {
let scalar_bytes = scalar.to_bytes_be();
let mut result: Option<ProjectivePoint> = None;
let mut temp = point.clone();
for byte in scalar_bytes.iter().rev() {
for i in 0..8 {
if (byte >> i) & 1 == 1 {
result = Some(match result {
Some(r) => &r + &temp,
None => temp.clone(),
});
}
temp = &temp + &temp;
}
}
Ok(result.unwrap_or(point.clone()))
}
fn subtract_points(a: &ProjectivePoint, b: &ProjectivePoint) -> Result<ProjectivePoint> {
let b_affine = b.to_affine().map_err(|_| {
krusty_kms_common::KmsError::CryptoError("Invalid point (identity)".to_string())
})?;
let neg_y = Felt::ZERO - b_affine.y();
let neg_b = ProjectivePoint::from_affine(b_affine.x(), neg_y).map_err(|_| {
krusty_kms_common::KmsError::CryptoError("Invalid negated point".to_string())
})?;
Ok(a + &neg_b)
}
fn discrete_log_brute_force(g_m: &ProjectivePoint) -> Result<u128> {
let generator = krusty_kms_crypto::StarkCurve::generator();
if g_m.to_affine().is_err() {
return Ok(0);
}
const MAX_SEARCH: u128 = 1_000_000_000_000; let mut current = generator.clone();
for i in 1..=MAX_SEARCH {
if points_equal(¤t, g_m) {
return Ok(i);
}
current = ¤t + &generator;
if i > 1_000_000 && i % 1_000_000 == 0 {
}
}
Err(krusty_kms_common::KmsError::CryptoError(
"Failed to recover balance (discrete log not found within search limit)".to_string(),
))
}
fn points_equal(a: &ProjectivePoint, b: &ProjectivePoint) -> bool {
match (a.to_affine(), b.to_affine()) {
(Ok(a_aff), Ok(b_aff)) => a_aff.x() == b_aff.x() && a_aff.y() == b_aff.y(),
(Err(_), Err(_)) => true, _ => false,
}
}
pub fn erc20_to_tongo(erc20_amount: u128, rate: u128) -> u128 {
erc20_amount.div_ceil(rate)
}
pub fn tongo_to_erc20(tongo_amount: u128, rate: u128) -> u128 {
tongo_amount * rate
}
#[derive(Debug, Clone)]
pub struct AEBalance {
pub ciphertext: [u8; 64],
pub nonce: [u8; 24],
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decrypt_zero_balance() {
let private_key = Felt::from(12345u64);
let g_x =
Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
.unwrap();
let g_y =
Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
.unwrap();
let generator = ProjectivePoint::from_affine(g_x, g_y).unwrap();
let public_key = multiply_point(&generator, &private_key).unwrap();
let r = Felt::from(999u64);
let r_point = multiply_point(&generator, &r).unwrap();
let y_r = multiply_point(&public_key, &r).unwrap();
let cipher = CipherBalance { l: y_r, r: r_point };
let decrypted = decrypt_cipher_balance(&private_key, &cipher).unwrap();
assert_eq!(decrypted, 0);
}
#[test]
fn test_point_subtraction() {
let g_x =
Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
.unwrap();
let g_y =
Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
.unwrap();
let g = ProjectivePoint::from_affine(g_x, g_y).unwrap();
let result = subtract_points(&g, &g).unwrap();
assert!(result.to_affine().is_err());
}
#[test]
fn test_points_equal_same_point() {
let g_x =
Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
.unwrap();
let g_y =
Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
.unwrap();
let g = ProjectivePoint::from_affine(g_x, g_y).unwrap();
assert!(points_equal(&g, &g));
}
#[test]
fn test_points_equal_different_points() {
let g_x =
Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
.unwrap();
let g_y =
Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
.unwrap();
let g = ProjectivePoint::from_affine(g_x, g_y).unwrap();
let g2 = &g + &g;
assert!(!points_equal(&g, &g2));
}
#[test]
fn test_points_equal_both_identity() {
let g_x =
Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
.unwrap();
let g_y =
Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
.unwrap();
let g = ProjectivePoint::from_affine(g_x, g_y).unwrap();
let id1 = subtract_points(&g, &g).unwrap();
let id2 = subtract_points(&g, &g).unwrap();
assert!(points_equal(&id1, &id2));
}
#[test]
fn test_points_equal_one_identity() {
let g_x =
Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
.unwrap();
let g_y =
Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
.unwrap();
let g = ProjectivePoint::from_affine(g_x, g_y).unwrap();
let identity = subtract_points(&g, &g).unwrap();
assert!(!points_equal(&g, &identity));
assert!(!points_equal(&identity, &g));
}
#[test]
fn test_multiply_point_by_one() {
let g_x =
Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
.unwrap();
let g_y =
Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
.unwrap();
let g = ProjectivePoint::from_affine(g_x, g_y).unwrap();
let result = multiply_point(&g, &Felt::ONE).unwrap();
assert!(points_equal(&result, &g));
}
#[test]
fn test_multiply_point_by_two() {
let g_x =
Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
.unwrap();
let g_y =
Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
.unwrap();
let g = ProjectivePoint::from_affine(g_x, g_y).unwrap();
let result = multiply_point(&g, &Felt::TWO).unwrap();
let expected = &g + &g;
assert!(points_equal(&result, &expected));
}
#[test]
fn test_discrete_log_small_value() {
let g_x =
Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
.unwrap();
let g_y =
Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
.unwrap();
let g = ProjectivePoint::from_affine(g_x, g_y).unwrap();
let result = discrete_log_brute_force(&g).unwrap();
assert_eq!(result, 1);
}
#[test]
fn test_discrete_log_value_5() {
let g_x =
Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
.unwrap();
let g_y =
Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
.unwrap();
let g = ProjectivePoint::from_affine(g_x, g_y).unwrap();
let five_g = multiply_point(&g, &Felt::from(5u64)).unwrap();
let result = discrete_log_brute_force(&five_g).unwrap();
assert_eq!(result, 5);
}
#[test]
fn test_decrypt_small_balance() {
let private_key = Felt::from(12345u64);
let g_x =
Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
.unwrap();
let g_y =
Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
.unwrap();
let generator = ProjectivePoint::from_affine(g_x, g_y).unwrap();
let public_key = multiply_point(&generator, &private_key).unwrap();
let r = Felt::from(999u64);
let r_point = multiply_point(&generator, &r).unwrap(); let y_r = multiply_point(&public_key, &r).unwrap(); let g_m = multiply_point(&generator, &Felt::from(5u64)).unwrap(); let l = &g_m + &y_r;
let cipher = CipherBalance { l, r: r_point };
let decrypted = decrypt_cipher_balance(&private_key, &cipher).unwrap();
assert_eq!(decrypted, 5);
}
#[test]
fn test_erc20_to_tongo_exact() {
assert_eq!(erc20_to_tongo(1000, 10), 100);
}
#[test]
fn test_erc20_to_tongo_ceiling() {
assert_eq!(erc20_to_tongo(1001, 10), 101);
assert_eq!(erc20_to_tongo(1009, 10), 101);
}
#[test]
fn test_erc20_to_tongo_rate_one() {
assert_eq!(erc20_to_tongo(42, 1), 42);
}
#[test]
fn test_erc20_to_tongo_rate_greater_than_amount() {
assert_eq!(erc20_to_tongo(5, 100), 1);
}
#[test]
fn test_erc20_to_tongo_zero_amount() {
assert_eq!(erc20_to_tongo(0, 10), 0);
}
#[test]
fn test_tongo_to_erc20_basic() {
assert_eq!(tongo_to_erc20(100, 10), 1000);
}
#[test]
fn test_tongo_to_erc20_rate_one() {
assert_eq!(tongo_to_erc20(42, 1), 42);
}
#[test]
fn test_tongo_to_erc20_zero_amount() {
assert_eq!(tongo_to_erc20(0, 10), 0);
}
#[test]
fn test_roundtrip_conversion() {
let rate = 1000u128;
let original_tongo = 50u128;
let erc20 = tongo_to_erc20(original_tongo, rate);
let back = erc20_to_tongo(erc20, rate);
assert_eq!(back, original_tongo);
}
}