From fa977f2110fb3530158c6bd5b6b2097ce31e054d Mon Sep 17 00:00:00 2001 From: qkin Date: Wed, 25 Feb 2026 11:05:18 +0800 Subject: [PATCH] add kaspa_pskt ur type --- include/URRegistryFFI/lib_ur_registry_ffi.h | 6 +- libs/ur-registry-ffi/src/kaspa/kaspa_pskt.rs | 89 ++++++++++++++++++++ libs/ur-registry-ffi/src/kaspa/mod.rs | 1 + libs/ur-registry-ffi/src/lib.rs | 1 + libs/ur-registry/src/kaspa/kaspa_pskt.rs | 72 ++++++++++++++++ libs/ur-registry/src/kaspa/mod.rs | 1 + libs/ur-registry/src/lib.rs | 1 + libs/ur-registry/src/macros_impl.rs | 2 + libs/ur-registry/src/registry_types.rs | 6 ++ 9 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 libs/ur-registry-ffi/src/kaspa/kaspa_pskt.rs create mode 100644 libs/ur-registry-ffi/src/kaspa/mod.rs create mode 100644 libs/ur-registry/src/kaspa/kaspa_pskt.rs create mode 100644 libs/ur-registry/src/kaspa/mod.rs diff --git a/include/URRegistryFFI/lib_ur_registry_ffi.h b/include/URRegistryFFI/lib_ur_registry_ffi.h index 1ffad13..4133979 100644 --- a/include/URRegistryFFI/lib_ur_registry_ffi.h +++ b/include/URRegistryFFI/lib_ur_registry_ffi.h @@ -71,4 +71,8 @@ const char* parse_cardano_signature(struct ExternError*, const char* ur_type, co // Zcash const char* parse_zcash_accounts(struct ExternError*, const char* ur_type, const char* cbor_hex); const char* parse_zcash_pczt(struct ExternError*, const char* ur_type, const char* cbor_hex); -const char* generate_zcash_pczt(struct ExternError*, const char* pczt_hex); \ No newline at end of file +const char* generate_zcash_pczt(struct ExternError*, const char* pczt_hex); + +// Kaspa PSKT +const char* generate_kaspa_pskt(struct ExternError*, const char* pskt_hex); +const char* parse_kaspa_pskt(struct ExternError*, const char* ur_type, const char* cbor_hex); \ No newline at end of file diff --git a/libs/ur-registry-ffi/src/kaspa/kaspa_pskt.rs b/libs/ur-registry-ffi/src/kaspa/kaspa_pskt.rs new file mode 100644 index 0000000..ea54858 --- /dev/null +++ b/libs/ur-registry-ffi/src/kaspa/kaspa_pskt.rs @@ -0,0 +1,89 @@ +use crate::{export, util_internal::string_helper::remove_prefix_0x}; +use anyhow::{format_err, Error}; +use serde_json::json; +use ur_registry::{registry_types::KASPA_PSKT, kaspa::kaspa_pskt::KaspaPskt}; +use core::convert::TryInto; + +export! { + @Java_com_keystone_sdk_KeystoneNativeSDK_generateKaspaPskt + fn generate_kaspa_pskt( + data: &str + ) -> String { + if data.is_empty() { + return json!({"error": "data is required"}).to_string(); + } + + let bytes = match hex::decode(remove_prefix_0x(data)) { + Ok(v) => v, + Err(_) => return json!({"error": "data is invalid hex"}).to_string(), + }; + + let cbor_bytes: Vec = match KaspaPskt::new(bytes).try_into() { + Ok(v) => v, + Err(_) => return json!({"error": "CBOR encode failed"}).to_string(), + }; + + let cbor_hex = hex::encode(cbor_bytes); + json!({ + "type": KASPA_PSKT.get_type(), + "cbor": cbor_hex, + }).to_string() + } + + @Java_com_keystone_sdk_KeystoneNativeSDK_parseKaspaPskt + fn parse_kaspa_pskt(ur_type: &str, cbor_hex: &str) -> String { + if KASPA_PSKT.get_type() != ur_type { + return json!({"error": "type not match"}).to_string(); + } + + let parse = || -> Result { + let cbor = hex::decode(remove_prefix_0x(cbor_hex).to_string())?; + let pskt = KaspaPskt::try_from(cbor).map_err(|_| format_err!("decode failed"))?; + let pskt_hex = hex::encode(pskt.get_pskt()); + Ok(pskt_hex) + }; + + match parse() { + Ok(v) => json!({ "pskt": v }).to_string(), + Err(_) => json!({"error": "PSKT is invalid"}).to_string(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_kaspa_pskt() { + let data = "0102030405"; + let result = generate_kaspa_pskt(data); + let json_result: serde_json::Value = serde_json::from_str(&result).unwrap(); + + assert_eq!(json_result["type"], "kaspa-pskt"); + assert!(json_result["cbor"].as_str().unwrap().starts_with("a1")); + + assert!(generate_kaspa_pskt("").contains("data is required")); + } + + #[test] + fn test_parse_kaspa_pskt() { + let data = "0102030405"; + let gen_res = generate_kaspa_pskt(data); + let json_gen: serde_json::Value = serde_json::from_str(&gen_res).unwrap(); + let cbor_hex = json_gen["cbor"].as_str().unwrap(); + + let parse_res = parse_kaspa_pskt("kaspa-pskt", cbor_hex); + let json_parse: serde_json::Value = serde_json::from_str(&parse_res).unwrap(); + assert_eq!(json_parse["pskt"], data); + } + + #[test] + fn test_kaspa_pskt_error_cases() { + assert!(generate_kaspa_pskt("0102030G").contains("invalid hex")); + + assert!(parse_kaspa_pskt("wrong-type", "a101...").contains("type not match")); + + assert!(parse_kaspa_pskt("kaspa-pskt", "ffff").contains("invalid")); + } +} \ No newline at end of file diff --git a/libs/ur-registry-ffi/src/kaspa/mod.rs b/libs/ur-registry-ffi/src/kaspa/mod.rs new file mode 100644 index 0000000..24e94f7 --- /dev/null +++ b/libs/ur-registry-ffi/src/kaspa/mod.rs @@ -0,0 +1 @@ +pub mod kaspa_pskt; \ No newline at end of file diff --git a/libs/ur-registry-ffi/src/lib.rs b/libs/ur-registry-ffi/src/lib.rs index 3856e30..5cbd8dc 100644 --- a/libs/ur-registry-ffi/src/lib.rs +++ b/libs/ur-registry-ffi/src/lib.rs @@ -18,5 +18,6 @@ mod util_internal; pub mod utils; pub mod zcash; pub mod ergo; +pub mod kaspa; ffi_support::define_string_destructor!(keystone_sdk_destroy_string); diff --git a/libs/ur-registry/src/kaspa/kaspa_pskt.rs b/libs/ur-registry/src/kaspa/kaspa_pskt.rs new file mode 100644 index 0000000..0b83877 --- /dev/null +++ b/libs/ur-registry/src/kaspa/kaspa_pskt.rs @@ -0,0 +1,72 @@ +use alloc::string::ToString; +use minicbor::data::Int; +use crate::{ + cbor::cbor_map, + impl_template_struct, + registry_types::{RegistryType, KASPA_PSKT}, + traits::{MapSize, RegistryItem}, + types::Bytes, +}; + +const PSKT: u8 = 1; + +impl_template_struct!(KaspaPskt { pskt: Bytes }); + +impl MapSize for KaspaPskt { + fn map_size(&self) -> u64 { + 1 + } +} + +impl RegistryItem for KaspaPskt { + fn get_registry_type() -> RegistryType<'static> { + KASPA_PSKT + } +} + +impl minicbor::Encode for KaspaPskt { + fn encode( + &self, + e: &mut minicbor::Encoder, + _ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + e.map(self.map_size())?; + e.int(Int::from(PSKT))?.bytes(&self.pskt)?; + Ok(()) + } +} + +impl<'b, C> minicbor::Decode<'b, C> for KaspaPskt { + fn decode(d: &mut minicbor::Decoder<'b>, _ctx: &mut C) -> Result { + let mut result = KaspaPskt::default(); + cbor_map(d, &mut result, |key, obj, d| { + let key = u8::try_from(key) + .map_err(|e| minicbor::decode::Error::message(e.to_string()))?; + match key { + PSKT => { + obj.pskt = d.bytes()?.to_vec(); + } + _ => {} + } + Ok(()) + })?; + Ok(result) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::vec; + + #[test] + fn test_kaspa_pskt_encode_decode() { + let data = vec![1, 2, 3, 4, 5]; + let pskt = KaspaPskt::new(data.clone()); + + let cbor = minicbor::to_vec(&pskt).expect("Failed to encode"); + let decoded: KaspaPskt = minicbor::decode(&cbor).expect("Failed to decode"); + + assert_eq!(decoded.get_pskt(), data); + } +} \ No newline at end of file diff --git a/libs/ur-registry/src/kaspa/mod.rs b/libs/ur-registry/src/kaspa/mod.rs new file mode 100644 index 0000000..24e94f7 --- /dev/null +++ b/libs/ur-registry/src/kaspa/mod.rs @@ -0,0 +1 @@ +pub mod kaspa_pskt; \ No newline at end of file diff --git a/libs/ur-registry/src/lib.rs b/libs/ur-registry/src/lib.rs index df92c0d..1a7ae4c 100644 --- a/libs/ur-registry/src/lib.rs +++ b/libs/ur-registry/src/lib.rs @@ -24,6 +24,7 @@ pub mod error; pub mod ethereum; pub mod extend; pub mod iota; +pub mod kaspa; pub mod keystone; mod macros; mod macros_impl; diff --git a/libs/ur-registry/src/macros_impl.rs b/libs/ur-registry/src/macros_impl.rs index 16011e0..0470a24 100644 --- a/libs/ur-registry/src/macros_impl.rs +++ b/libs/ur-registry/src/macros_impl.rs @@ -65,6 +65,7 @@ use crate::{ use crate::{impl_cbor_bytes, impl_ur_try_from_cbor_bytes, impl_ur_try_into_cbor_bytes}; use alloc::string::ToString; use alloc::vec::Vec; +use crate::kaspa::kaspa_pskt::KaspaPskt; impl_cbor_bytes!( Bytes, @@ -133,4 +134,5 @@ impl_cbor_bytes!( IotaSignRequest, IotaSignHashRequest, IotaSignature, + KaspaPskt, ); diff --git a/libs/ur-registry/src/registry_types.rs b/libs/ur-registry/src/registry_types.rs index 2a49bac..6558c0a 100644 --- a/libs/ur-registry/src/registry_types.rs +++ b/libs/ur-registry/src/registry_types.rs @@ -38,6 +38,7 @@ pub enum URType { SolSignature(String), TronSignRequest(String), TronSignature(String), + KaspaPskt(String), } impl URType { @@ -84,6 +85,7 @@ impl URType { "iota-sign-request" => Ok(URType::IotaSignRequest(type_str.to_string())), "ergo-sign-request" => Ok(URType::ErgoSignRequest(type_str.to_string())), "sol-signature" => Ok(URType::SolSignature(type_str.to_string())), + "kaspa-pskt" => Ok(URType::KaspaPskt(type_str.to_string())), _ => Err(URError::NotSupportURTypeError(type_str.to_string())), } } @@ -125,6 +127,7 @@ impl URType { URType::IotaSignHashRequest(type_str) => type_str.to_string(), URType::ErgoSignRequest(type_str) => type_str.to_string(), URType::SolSignature(type_str) => type_str.to_string(), + URType::KaspaPskt(type_str) => type_str.to_string(), } } } @@ -243,6 +246,9 @@ pub const IOTA_SIGN_REQUEST: RegistryType = RegistryType("iota-sign-request", So pub const IOTA_SIGNATURE: RegistryType = RegistryType("iota-signature", Some(8502)); pub const IOTA_SIGN_HASH_REQUEST: RegistryType = RegistryType("iota-sign-hash-request", Some(8503)); +// Kaspa +pub const KASPA_PSKT: RegistryType = RegistryType("kaspa-pskt", Some(8601)); + // Zcash pub const ZCASH_ACCOUNTS: RegistryType = RegistryType("zcash-accounts", Some(49201)); pub const ZCASH_FULL_VIEWING_KEY: RegistryType =