1use bincode::de::BorrowDecoder;
2use bincode::enc::Encoder;
3use bincode::error::{DecodeError, EncodeError};
4use bincode::{BorrowDecode, Encode};
5use dashcore::blockdata::opcodes;
6use std::fmt;
7use std::ops::Deref;
8
9use dashcore::{ScriptBuf as DashcoreScript, ScriptBuf};
10use platform_value::string_encoding::{self, Encoding};
11use rand::rngs::StdRng;
12use rand::Rng;
13
14use serde::de::Visitor;
15use serde::{Deserialize, Serialize};
16
17use crate::ProtocolError;
18use bincode::de::read::Reader;
19
20#[derive(Clone, Debug, Eq, PartialEq, Default)]
21pub struct CoreScript(DashcoreScript);
22
23impl CoreScript {
24 pub fn new(script: DashcoreScript) -> Self {
25 CoreScript(script)
26 }
27
28 pub fn to_string(&self, encoding: Encoding) -> String {
29 string_encoding::encode(&self.0.to_bytes(), encoding)
30 }
31
32 pub fn from_string(encoded_value: &str, encoding: Encoding) -> Result<Self, ProtocolError> {
33 let vec = string_encoding::decode(encoded_value, encoding)?;
34
35 Ok(Self(vec.into()))
36 }
37
38 pub fn from_bytes(bytes: Vec<u8>) -> Self {
39 Self(bytes.into())
40 }
41
42 pub fn new_p2pkh(key_hash: [u8; 20]) -> Self {
43 let mut bytes: Vec<u8> = vec![
44 opcodes::all::OP_DUP.to_u8(),
45 opcodes::all::OP_HASH160.to_u8(),
46 opcodes::all::OP_PUSHBYTES_20.to_u8(),
47 ];
48 bytes.extend_from_slice(&key_hash);
49 bytes.push(opcodes::all::OP_EQUALVERIFY.to_u8());
50 bytes.push(opcodes::all::OP_CHECKSIG.to_u8());
51 Self::from_bytes(bytes)
52 }
53
54 pub fn new_p2sh(script_hash: [u8; 20]) -> Self {
55 let mut bytes = vec![
56 opcodes::all::OP_HASH160.to_u8(),
57 opcodes::all::OP_PUSHBYTES_20.to_u8(),
58 ];
59 bytes.extend_from_slice(&script_hash);
60 bytes.push(opcodes::all::OP_EQUAL.to_u8());
61 Self::from_bytes(bytes)
62 }
63
64 pub fn random_p2sh(rng: &mut StdRng) -> Self {
65 Self::new_p2sh(rng.gen())
66 }
67
68 pub fn random_p2pkh(rng: &mut StdRng) -> Self {
69 Self::new_p2pkh(rng.gen())
70 }
71}
72
73impl From<Vec<u8>> for CoreScript {
74 fn from(value: Vec<u8>) -> Self {
75 CoreScript::from_bytes(value)
76 }
77}
78
79impl Deref for CoreScript {
80 type Target = DashcoreScript;
81
82 fn deref(&self) -> &Self::Target {
83 &self.0
84 }
85}
86
87impl Encode for CoreScript {
89 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
90 self.0.as_bytes().encode(encoder)
91 }
92}
93
94macro_rules! impl_core_script_decode {
97 ($decode:ident, $decoder:ident, $method:ident, $untrusted:expr) => {
98 impl<C> bincode::$decode<C> for CoreScript {
99 fn $method<D: bincode::de::$decoder<Context = C>>(
100 decoder: &mut D,
101 ) -> Result<Self, DecodeError> {
102 let bytes = Vec::<u8>::$method(decoder)?;
103 Ok(CoreScript(ScriptBuf(bytes)))
105 }
106 }
107 };
108}
109impl_core_script_decode!(Decode, Decoder, decode, false);
110impl_core_script_decode!(DecodeUntrusted, UntrustedDecoder, decode_untrusted, true);
111bincode::impl_borrow_decode_untrusted!(CoreScript);
112
113impl<'de, C> BorrowDecode<'de, C> for CoreScript {
114 fn borrow_decode<D: BorrowDecoder<'de, Context = C>>(
115 decoder: &mut D,
116 ) -> Result<Self, DecodeError> {
117 let mut bytes = Vec::new();
119 loop {
120 let buf_len = 1024; let mut buf = vec![0; buf_len];
122
123 match decoder.reader().read(&mut buf) {
124 Ok(()) => {
125 let read_bytes = buf.iter().position(|&x| x == 0).unwrap_or(buf.len());
126 bytes.extend_from_slice(&buf[..read_bytes]);
127 if read_bytes < buf_len {
128 break;
129 }
130 }
131 Err(DecodeError::Io { inner, additional })
132 if inner.kind() == std::io::ErrorKind::UnexpectedEof =>
133 {
134 if additional > 0 {
135 return Err(DecodeError::Io { inner, additional });
136 } else {
137 break;
138 }
139 }
140 Err(e) => return Err(e),
141 }
142 }
143
144 let dash_core_script = DashcoreScript(bytes);
146
147 Ok(CoreScript(dash_core_script))
149 }
150}
151
152impl Serialize for CoreScript {
153 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
154 where
155 S: serde::Serializer,
156 {
157 if serializer.is_human_readable() {
158 serializer.serialize_str(&self.to_string(Encoding::Base64))
159 } else {
160 serializer.serialize_bytes(self.as_bytes())
161 }
162 }
163}
164
165impl<'de> Deserialize<'de> for CoreScript {
166 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
167 where
168 D: serde::Deserializer<'de>,
169 {
170 struct CoreScriptVisitor;
177
178 impl Visitor<'_> for CoreScriptVisitor {
179 type Value = CoreScript;
180
181 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
182 formatter.write_str("a byte array or base64-encoded string")
183 }
184
185 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
186 CoreScript::from_string(v, Encoding::Base64).map_err(|e| {
187 E::custom(format!(
188 "expected to be able to deserialize core script from string: {}",
189 e
190 ))
191 })
192 }
193
194 fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
195 self.visit_str(&v)
196 }
197
198 fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
199 Ok(CoreScript::from_bytes(v.to_vec()))
200 }
201
202 fn visit_byte_buf<E: serde::de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
203 Ok(CoreScript::from_bytes(v))
204 }
205 }
206
207 if deserializer.is_human_readable() {
208 deserializer.deserialize_string(CoreScriptVisitor)
209 } else {
210 deserializer.deserialize_bytes(CoreScriptVisitor)
211 }
212 }
213}
214
215impl std::fmt::Display for CoreScript {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 write!(f, "{}", self.to_string(Encoding::Base64))
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use dashcore::blockdata::opcodes;
225 use platform_value::string_encoding::Encoding;
226
227 mod construction {
228 use super::*;
229
230 #[test]
231 fn from_bytes_creates_script() {
232 let bytes = vec![1, 2, 3, 4, 5];
233 let script = CoreScript::from_bytes(bytes.clone());
234 assert_eq!(script.as_bytes(), &bytes);
235 }
236
237 #[test]
238 fn new_wraps_dashcore_script() {
239 let dashcore_script = DashcoreScript::from(vec![10, 20, 30]);
240 let script = CoreScript::new(dashcore_script.clone());
241 assert_eq!(script.as_bytes(), dashcore_script.as_bytes());
242 }
243
244 #[test]
245 fn default_is_empty() {
246 let script = CoreScript::default();
247 assert!(script.as_bytes().is_empty());
248 }
249
250 #[test]
251 fn from_vec_u8() {
252 let bytes = vec![0xAA, 0xBB, 0xCC];
253 let script: CoreScript = bytes.clone().into();
254 assert_eq!(script.as_bytes(), &bytes);
255 }
256 }
257
258 mod p2pkh {
259 use super::*;
260
261 #[test]
262 fn new_p2pkh_has_correct_structure() {
263 let key_hash = [0u8; 20];
264 let script = CoreScript::new_p2pkh(key_hash);
265 let bytes = script.as_bytes();
266
267 assert_eq!(bytes.len(), 25); assert_eq!(bytes[0], opcodes::all::OP_DUP.to_u8());
270 assert_eq!(bytes[1], opcodes::all::OP_HASH160.to_u8());
271 assert_eq!(bytes[2], opcodes::all::OP_PUSHBYTES_20.to_u8());
272 assert_eq!(&bytes[3..23], &key_hash);
273 assert_eq!(bytes[23], opcodes::all::OP_EQUALVERIFY.to_u8());
274 assert_eq!(bytes[24], opcodes::all::OP_CHECKSIG.to_u8());
275 }
276
277 #[test]
278 fn new_p2pkh_with_nonzero_hash() {
279 let key_hash: [u8; 20] = [
280 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
281 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
282 ];
283 let script = CoreScript::new_p2pkh(key_hash);
284 let bytes = script.as_bytes();
285 assert_eq!(&bytes[3..23], &key_hash);
286 }
287
288 #[test]
289 fn two_different_key_hashes_produce_different_scripts() {
290 let hash_a = [0xAA; 20];
291 let hash_b = [0xBB; 20];
292 let script_a = CoreScript::new_p2pkh(hash_a);
293 let script_b = CoreScript::new_p2pkh(hash_b);
294 assert_ne!(script_a, script_b);
295 }
296 }
297
298 mod p2sh {
299 use super::*;
300
301 #[test]
302 fn new_p2sh_has_correct_structure() {
303 let script_hash = [0u8; 20];
304 let script = CoreScript::new_p2sh(script_hash);
305 let bytes = script.as_bytes();
306
307 assert_eq!(bytes.len(), 23); assert_eq!(bytes[0], opcodes::all::OP_HASH160.to_u8());
310 assert_eq!(bytes[1], opcodes::all::OP_PUSHBYTES_20.to_u8());
311 assert_eq!(&bytes[2..22], &script_hash);
312 assert_eq!(bytes[22], opcodes::all::OP_EQUAL.to_u8());
313 }
314
315 #[test]
316 fn new_p2sh_with_nonzero_hash() {
317 let script_hash: [u8; 20] = [0xFF; 20];
318 let script = CoreScript::new_p2sh(script_hash);
319 let bytes = script.as_bytes();
320 assert_eq!(&bytes[2..22], &script_hash);
321 }
322
323 #[test]
324 fn p2pkh_and_p2sh_differ_for_same_hash() {
325 let hash = [0x42; 20];
326 let p2pkh = CoreScript::new_p2pkh(hash);
327 let p2sh = CoreScript::new_p2sh(hash);
328 assert_ne!(p2pkh, p2sh);
329 assert_eq!(p2pkh.as_bytes().len(), 25);
331 assert_eq!(p2sh.as_bytes().len(), 23);
332 }
333 }
334
335 mod string_encoding_round_trip {
336 use super::*;
337
338 #[test]
339 fn base64_round_trip() {
340 let original = CoreScript::new_p2pkh([0xAB; 20]);
341 let encoded = original.to_string(Encoding::Base64);
342 let decoded =
343 CoreScript::from_string(&encoded, Encoding::Base64).expect("should decode base64");
344 assert_eq!(original, decoded);
345 }
346
347 #[test]
348 fn hex_round_trip() {
349 let original = CoreScript::new_p2sh([0xCD; 20]);
350 let encoded = original.to_string(Encoding::Hex);
351 let decoded =
352 CoreScript::from_string(&encoded, Encoding::Hex).expect("should decode hex");
353 assert_eq!(original, decoded);
354 }
355
356 #[test]
357 fn from_string_invalid_base64_fails() {
358 let result = CoreScript::from_string("not-valid-base64!!!", Encoding::Base64);
359 assert!(result.is_err());
360 }
361
362 #[test]
363 fn display_uses_base64() {
364 let script = CoreScript::new_p2pkh([0x00; 20]);
365 let display_str = format!("{}", script);
366 let encoded = script.to_string(Encoding::Base64);
367 assert_eq!(display_str, encoded);
368 }
369 }
370
371 mod from_bytes_round_trip {
372 use super::*;
373
374 #[test]
375 fn bytes_round_trip() {
376 let original_bytes = vec![1, 2, 3, 4, 5, 6, 7, 8];
377 let script = CoreScript::from_bytes(original_bytes.clone());
378 assert_eq!(script.as_bytes(), &original_bytes);
379 }
380
381 #[test]
382 fn empty_bytes() {
383 let script = CoreScript::from_bytes(vec![]);
384 assert!(script.as_bytes().is_empty());
385 }
386 }
387
388 mod deref {
389 use super::*;
390
391 #[test]
392 fn deref_returns_inner_script() {
393 let bytes = vec![1, 2, 3];
394 let script = CoreScript::from_bytes(bytes.clone());
395 let inner: &DashcoreScript = &script;
397 assert_eq!(inner.as_bytes(), &bytes);
398 }
399 }
400
401 mod equality_and_clone {
402 use super::*;
403
404 #[test]
405 fn equal_scripts_are_equal() {
406 let a = CoreScript::new_p2pkh([0x11; 20]);
407 let b = CoreScript::new_p2pkh([0x11; 20]);
408 assert_eq!(a, b);
409 }
410
411 #[test]
412 fn different_scripts_are_not_equal() {
413 let a = CoreScript::new_p2pkh([0x11; 20]);
414 let b = CoreScript::new_p2pkh([0x22; 20]);
415 assert_ne!(a, b);
416 }
417
418 #[test]
419 fn clone_produces_equal_script() {
420 let original = CoreScript::new_p2sh([0x33; 20]);
421 let cloned = original.clone();
422 assert_eq!(original, cloned);
423 }
424 }
425
426 mod random_scripts {
427 use super::*;
428 use rand::SeedableRng;
429
430 #[test]
431 fn random_p2pkh_produces_valid_script() {
432 let mut rng = StdRng::seed_from_u64(42);
433 let script = CoreScript::random_p2pkh(&mut rng);
434 let bytes = script.as_bytes();
435 assert_eq!(bytes.len(), 25);
436 assert_eq!(bytes[0], opcodes::all::OP_DUP.to_u8());
437 }
438
439 #[test]
440 fn random_p2sh_produces_valid_script() {
441 let mut rng = StdRng::seed_from_u64(42);
442 let script = CoreScript::random_p2sh(&mut rng);
443 let bytes = script.as_bytes();
444 assert_eq!(bytes.len(), 23);
445 assert_eq!(bytes[0], opcodes::all::OP_HASH160.to_u8());
446 }
447
448 #[test]
449 fn two_random_scripts_differ() {
450 let mut rng = StdRng::seed_from_u64(42);
451 let a = CoreScript::random_p2pkh(&mut rng);
452 let b = CoreScript::random_p2pkh(&mut rng);
453 assert_ne!(a, b);
454 }
455 }
456}