1use alloc::borrow::Cow;
2use borsh::{BorshDeserialize, BorshSerialize};
3use core::fmt::Formatter;
4use js_sys::Object;
5use kaspa_utils::{
6 hex::{FromHex, ToHex},
7 serde_bytes::FromHexVisitor,
8};
9use serde::{
10 de::{Error, Visitor},
11 Deserialize, Deserializer, Serialize, Serializer,
12};
13use smallvec::SmallVec;
14use std::{
15 collections::HashSet,
16 str::{self, FromStr},
17};
18use wasm_bindgen::prelude::*;
19use workflow_wasm::prelude::*;
20
21pub const SCRIPT_VECTOR_SIZE: usize = 36;
23
24pub type ScriptVec = SmallVec<[u8; SCRIPT_VECTOR_SIZE]>;
26
27pub type ScriptPublicKeyVersion = u16;
29
30pub use smallvec::smallvec as scriptvec;
32use wasm_bindgen::prelude::wasm_bindgen;
33
34pub type ScriptPublicKeys = HashSet<ScriptPublicKey>;
36
37#[wasm_bindgen(typescript_custom_section)]
38const TS_SCRIPT_PUBLIC_KEY: &'static str = r#"
39/**
40 * Interface defines the structure of a Script Public Key.
41 *
42 * @category Consensus
43 */
44export interface IScriptPublicKey {
45 version : number;
46 script: HexString;
47}
48"#;
49
50#[derive(Default, PartialEq, Eq, Clone, Hash, CastFromJs)]
53#[wasm_bindgen(inspectable)]
54pub struct ScriptPublicKey {
55 pub version: ScriptPublicKeyVersion,
56 pub(super) script: ScriptVec, }
58
59impl std::fmt::Debug for ScriptPublicKey {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.debug_struct("ScriptPublicKey").field("version", &self.version).field("script", &self.script.to_hex()).finish()
62 }
63}
64
65impl FromHex for ScriptPublicKey {
66 type Error = faster_hex::Error;
67
68 fn from_hex(hex_str: &str) -> Result<Self, Self::Error> {
69 ScriptPublicKey::from_str(hex_str)
70 }
71}
72
73#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Hash)]
74#[serde(rename_all = "camelCase")]
75#[serde(rename = "ScriptPublicKey")]
76struct ScriptPublicKeyInternal<'a> {
77 version: ScriptPublicKeyVersion,
78 script: &'a [u8],
79}
80
81impl Serialize for ScriptPublicKey {
82 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
83 where
84 S: Serializer,
85 {
86 if serializer.is_human_readable() {
87 let mut hex = vec![0u8; self.script.len() * 2 + 4];
88 faster_hex::hex_encode(&self.version.to_be_bytes(), &mut hex).map_err(serde::ser::Error::custom)?;
89 faster_hex::hex_encode(&self.script, &mut hex[4..]).map_err(serde::ser::Error::custom)?;
90 serializer.serialize_str(unsafe { str::from_utf8_unchecked(&hex) })
91 } else {
92 ScriptPublicKeyInternal { version: self.version, script: &self.script }.serialize(serializer)
93 }
94 }
95}
96
97impl<'de: 'a, 'a> Deserialize<'de> for ScriptPublicKey {
98 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
99 where
100 D: Deserializer<'de>,
101 {
102 #[derive(Default)]
103 pub struct ScriptPublicKeyVisitor<'de> {
104 from_hex_visitor: FromHexVisitor<'de, ScriptPublicKey>,
105 marker: std::marker::PhantomData<ScriptPublicKey>,
106 lifetime: std::marker::PhantomData<&'de ()>,
107 }
108 impl<'de> Visitor<'de> for ScriptPublicKeyVisitor<'de> {
109 type Value = ScriptPublicKey;
110
111 fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result {
112 #[cfg(target_arch = "wasm32")]
113 {
114 write!(formatter, "string-type: string, str; bytes-type: slice of bytes, vec of bytes; map; number-type - pointer")
115 }
116 #[cfg(not(target_arch = "wasm32"))]
117 {
118 write!(formatter, "string-type: string, str; bytes-type: slice of bytes, vec of bytes; map")
119 }
120 }
121
122 #[cfg(target_arch = "wasm32")]
131 fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
132 where
133 E: serde::de::Error,
134 {
135 self.visit_u32(v as u32)
136 }
137 #[cfg(target_arch = "wasm32")]
138 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
139 where
140 E: serde::de::Error,
141 {
142 self.visit_u32(v as u32)
143 }
144
145 #[cfg(target_arch = "wasm32")]
146 fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
147 where
148 E: serde::de::Error,
149 {
150 self.visit_u32(v as u32)
151 }
152 #[cfg(target_arch = "wasm32")]
153 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
154 where
155 E: serde::de::Error,
156 {
157 self.visit_u32(v as u32)
158 }
159 #[cfg(target_arch = "wasm32")]
160 fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
161 where
162 E: serde::de::Error,
163 {
164 use wasm_bindgen::convert::RefFromWasmAbi;
165 let instance_ref = unsafe { Self::Value::ref_from_abi(v) }; Ok(instance_ref.clone())
167 }
168 #[cfg(target_arch = "wasm32")]
169 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
170 where
171 E: serde::de::Error,
172 {
173 self.visit_u32(v as u32)
174 }
175
176 #[inline]
177 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
178 where
179 E: Error,
180 {
181 self.from_hex_visitor.visit_str(v)
182 }
183
184 #[inline]
185 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
186 where
187 E: Error,
188 {
189 self.from_hex_visitor.visit_borrowed_str(v)
190 }
191
192 #[inline]
193 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
194 where
195 E: Error,
196 {
197 self.from_hex_visitor.visit_string(v)
198 }
199
200 #[inline]
201 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
202 where
203 E: Error,
204 {
205 self.from_hex_visitor.visit_bytes(v)
206 }
207
208 #[inline]
209 fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
210 where
211 E: Error,
212 {
213 self.from_hex_visitor.visit_borrowed_bytes(v)
214 }
215
216 #[inline]
217 fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
218 where
219 E: Error,
220 {
221 self.from_hex_visitor.visit_byte_buf(v)
222 }
223
224 #[inline]
225 fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
226 where
227 A: serde::de::MapAccess<'de>,
228 {
229 #[derive(Deserialize, Copy, Clone)]
230 #[serde(field_identifier, rename_all = "lowercase")]
231 enum Field {
232 Version,
233 Script,
234 }
235
236 #[derive(Debug, Clone, Deserialize)]
237 #[serde(untagged)]
238 pub enum Value<'a> {
239 U16(u16),
240 #[serde(borrow)]
241 String(Cow<'a, String>),
242 }
243 impl From<Value<'_>> for u16 {
244 fn from(value: Value<'_>) -> Self {
245 let Value::U16(v) = value else { panic!("unexpected conversion: {value:?}") };
246 v
247 }
248 }
249
250 impl TryFrom<Value<'_>> for Vec<u8> {
251 type Error = faster_hex::Error;
252
253 fn try_from(value: Value) -> Result<Self, Self::Error> {
254 match value {
255 Value::U16(_) => {
256 panic!("unexpected conversion: {value:?}")
257 }
258 Value::String(script) => {
259 let mut script_bytes = vec![0u8; script.len() / 2];
260 faster_hex::hex_decode(script.as_bytes(), script_bytes.as_mut_slice())?;
261
262 Ok(script_bytes)
263 }
264 }
265 }
266 }
267
268 let mut version: Option<u16> = None;
269 let mut script: Option<Vec<u8>> = None;
270
271 while let Some((key, value)) = access.next_entry::<Field, Value>()? {
272 match key {
273 Field::Version => {
274 version = Some(value.into());
275 }
276 Field::Script => script = Some(value.try_into().map_err(Error::custom)?),
277 }
278 if version.is_some() && script.is_some() {
279 break;
280 }
281 }
282 let (version, script) = match (version, script) {
283 (Some(version), Some(script)) => Ok((version, script)),
284 (None, _) => Err(serde::de::Error::missing_field("version")),
285 (_, None) => Err(serde::de::Error::missing_field("script")),
286 }?;
287
288 Ok(ScriptPublicKey::from_vec(version, script))
289 }
290 }
291
292 if deserializer.is_human_readable() {
293 deserializer.deserialize_any(ScriptPublicKeyVisitor::default())
294 } else {
295 ScriptPublicKeyInternal::deserialize(deserializer)
296 .map(|ScriptPublicKeyInternal { script, version }| Self { version, script: SmallVec::from_slice(script) })
297 }
298 }
299}
300
301impl FromStr for ScriptPublicKey {
302 type Err = faster_hex::Error;
303 fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
304 let hex_len = hex_str.len();
305 if hex_len < 4 {
306 return Err(faster_hex::Error::InvalidLength(hex_len));
307 }
308 let mut bytes = vec![0u8; hex_len / 2];
309 faster_hex::hex_decode(hex_str.as_bytes(), bytes.as_mut_slice())?;
310 let version = u16::from_be_bytes(bytes[0..2].try_into().unwrap());
311 Ok(Self { version, script: SmallVec::from_slice(&bytes[2..]) })
312 }
313}
314
315impl ScriptPublicKey {
316 pub fn new(version: ScriptPublicKeyVersion, script: ScriptVec) -> Self {
317 Self { version, script }
318 }
319
320 pub fn from_vec(version: ScriptPublicKeyVersion, script: Vec<u8>) -> Self {
321 Self { version, script: ScriptVec::from_vec(script) }
322 }
323
324 pub fn version(&self) -> ScriptPublicKeyVersion {
325 self.version
326 }
327
328 pub fn script(&self) -> &[u8] {
329 &self.script
330 }
331}
332
333#[wasm_bindgen]
334extern "C" {
335 #[wasm_bindgen(typescript_type = "ScriptPublicKey | HexString")]
336 pub type ScriptPublicKeyT;
337}
338
339#[wasm_bindgen]
340impl ScriptPublicKey {
341 #[wasm_bindgen(constructor)]
342 pub fn constructor(version: u16, script: JsValue) -> Result<ScriptPublicKey, JsError> {
343 let script = script.try_as_vec_u8()?;
344 Ok(ScriptPublicKey::new(version, script.into()))
345 }
346
347 #[wasm_bindgen(getter = script)]
348 pub fn script_as_hex(&self) -> String {
349 self.script.to_hex()
350 }
351}
352
353impl BorshSerialize for ScriptPublicKey {
359 fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
360 borsh::BorshSerialize::serialize(&self.version, writer)?;
361 borsh::BorshSerialize::serialize(&self.script.as_slice(), writer)?;
363 Ok(())
364 }
365}
366
367impl BorshDeserialize for ScriptPublicKey {
368 fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
369 Ok(Self::from_vec(borsh::BorshDeserialize::deserialize_reader(reader)?, borsh::BorshDeserialize::deserialize_reader(reader)?))
371 }
372}
373
374type CastError = workflow_wasm::error::Error;
375impl TryCastFromJs for ScriptPublicKey {
376 type Error = workflow_wasm::error::Error;
377 fn try_cast_from<'a, R>(value: &'a R) -> Result<Cast<Self>, Self::Error>
378 where
379 R: AsRef<JsValue> + 'a,
380 {
381 Self::resolve(value, || {
382 if let Some(hex_str) = value.as_ref().as_string() {
383 Ok(Self::from_str(&hex_str).map_err(CastError::custom)?)
384 } else if let Some(object) = Object::try_from(value.as_ref()) {
385 let version = object.try_get_value("version")?.ok_or(CastError::custom(
386 "ScriptPublicKey must be a hex string or an object with 'version' and 'script' properties",
387 ))?;
388
389 let version = if let Ok(version) = version.try_as_u16() {
390 version
391 } else {
392 return Err(CastError::custom("Invalid version value '{version:?}'"));
393 };
394
395 let script = object.get_vec_u8("script")?;
396
397 Ok(ScriptPublicKey::from_vec(version, script))
398 } else {
399 Err(CastError::custom(format!("Unable to convert ScriptPublicKey from: {:?}", value.as_ref())))
400 }
401 })
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use js_sys::Object;
409 use wasm_bindgen::__rt::IntoJsResult;
410
411 #[test]
412 fn test_spk_serde_json() {
413 let vec = (0..SCRIPT_VECTOR_SIZE as u8).collect::<Vec<_>>();
414 let spk = ScriptPublicKey::from_vec(0xc0de, vec.clone()); let hex: String = serde_json::to_string(&spk).unwrap();
416 assert_eq!("\"c0de000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223\"", hex);
417 let spk = serde_json::from_str::<ScriptPublicKey>(&hex).unwrap();
418 assert_eq!(spk.version, 0xc0de);
419 assert_eq!(spk.script.as_slice(), vec.as_slice());
420 let result = "00".parse::<ScriptPublicKey>();
421 assert!(matches!(result, Err(faster_hex::Error::InvalidLength(2))));
422 let result = "0000".parse::<ScriptPublicKey>();
423 let _empty = ScriptPublicKey { version: 0, script: ScriptVec::new() };
424 assert!(matches!(result, Ok(_empty)));
425 }
426
427 #[test]
428 fn test_spk_borsh() {
429 let spk = ScriptPublicKey::from_vec(12, vec![32; 20]);
431 let bin = borsh::to_vec(&spk).unwrap();
432 let spk2: ScriptPublicKey = BorshDeserialize::try_from_slice(&bin).unwrap();
433 assert_eq!(spk, spk2);
434
435 let spk = ScriptPublicKey::from_vec(55455, vec![11; 200]);
436 let bin = borsh::to_vec(&spk).unwrap();
437 let spk2: ScriptPublicKey = BorshDeserialize::try_from_slice(&bin).unwrap();
438 assert_eq!(spk, spk2);
439 }
440
441 use wasm_bindgen::convert::IntoWasmAbi;
442 use wasm_bindgen_test::wasm_bindgen_test;
443 use workflow_wasm::serde::{from_value, to_value};
444
445 #[wasm_bindgen_test]
446 pub fn test_wasm_serde_constructor() {
447 let version = 0xc0de;
448 let vec: Vec<u8> = (0..SCRIPT_VECTOR_SIZE as u8).collect();
449 let spk = ScriptPublicKey::from_vec(version, vec.clone());
450 let str = "c0de000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223";
451 let js = to_value(&spk).unwrap();
452 assert_eq!(js.as_string().unwrap(), str);
453 let script_hex = spk.script_as_hex();
454 let script_hex_js: JsValue = JsValue::from_str(&script_hex);
455
456 let spk_js = to_value(&ScriptPublicKey::constructor(version, script_hex_js).map_err(|_| ()).unwrap()).unwrap();
457 assert_eq!(JsValue::from_str(str), spk_js.clone());
458 assert_eq!(spk, from_value(spk_js.clone()).map_err(|_| ()).unwrap());
459 assert_eq!(JsValue::from_str("string"), spk_js.js_typeof());
460 }
461
462 #[wasm_bindgen_test]
463 pub fn test_wasm_serde_js_spk_object() {
464 let version = 0xc0de;
465 let vec: Vec<u8> = (0..SCRIPT_VECTOR_SIZE as u8).collect();
466 let spk = ScriptPublicKey::from_vec(version, vec.clone());
467
468 let script_hex = spk.script_as_hex();
469
470 let version_js: JsValue = version.into();
471 let script_hex_js: JsValue = JsValue::from_str(&script_hex);
472
473 let obj_hex = Object::new();
474 obj_hex.set("version", &version_js).unwrap();
475 obj_hex.set("script", &script_hex_js).unwrap();
476
477 let actual: ScriptPublicKey = from_value(obj_hex.into_js_result().unwrap()).unwrap();
478 assert_eq!(spk, actual);
479 }
480
481 #[wasm_bindgen_test]
482 pub fn test_wasm_serde_spk_object() {
483 let version = 0xc0de;
484 let vec: Vec<u8> = (0..SCRIPT_VECTOR_SIZE as u8).collect();
485 let spk = ScriptPublicKey::from_vec(version, vec.clone());
486
487 let script_hex = spk.script_as_hex();
488 let script_hex_js: JsValue = JsValue::from_str(&script_hex);
489
490 let expected = ScriptPublicKey::constructor(version, script_hex_js).map_err(|_| ()).unwrap();
491 let wasm_js_value: JsValue = expected.clone().into_abi().into();
492
493 let actual = from_value(wasm_js_value).unwrap();
494 assert_eq!(expected, actual);
495 }
496}