1use bincode::de::{BorrowDecoder, Decoder};
2use bincode::enc::Encoder;
3use bincode::error::{DecodeError, EncodeError};
4use bincode::{BorrowDecode, Decode, 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
94impl<C> Decode<C> for CoreScript {
96 fn decode<D: Decoder<Context = C>>(decoder: &mut D) -> Result<Self, DecodeError> {
97 let bytes = Vec::<u8>::decode(decoder)?;
98 Ok(CoreScript(ScriptBuf(bytes)))
100 }
101}
102
103impl<'de, C> BorrowDecode<'de, C> for CoreScript {
104 fn borrow_decode<D: BorrowDecoder<'de, Context = C>>(
105 decoder: &mut D,
106 ) -> Result<Self, DecodeError> {
107 let mut bytes = Vec::new();
109 loop {
110 let buf_len = 1024; let mut buf = vec![0; buf_len];
112
113 match decoder.reader().read(&mut buf) {
114 Ok(()) => {
115 let read_bytes = buf.iter().position(|&x| x == 0).unwrap_or(buf.len());
116 bytes.extend_from_slice(&buf[..read_bytes]);
117 if read_bytes < buf_len {
118 break;
119 }
120 }
121 Err(DecodeError::Io { inner, additional })
122 if inner.kind() == std::io::ErrorKind::UnexpectedEof =>
123 {
124 if additional > 0 {
125 return Err(DecodeError::Io { inner, additional });
126 } else {
127 break;
128 }
129 }
130 Err(e) => return Err(e),
131 }
132 }
133
134 let dash_core_script = DashcoreScript(bytes);
136
137 Ok(CoreScript(dash_core_script))
139 }
140}
141
142impl Serialize for CoreScript {
143 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
144 where
145 S: serde::Serializer,
146 {
147 if serializer.is_human_readable() {
148 serializer.serialize_str(&self.to_string(Encoding::Base64))
149 } else {
150 serializer.serialize_bytes(self.as_bytes())
151 }
152 }
153}
154
155impl<'de> Deserialize<'de> for CoreScript {
156 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
157 where
158 D: serde::Deserializer<'de>,
159 {
160 struct CoreScriptVisitor;
167
168 impl Visitor<'_> for CoreScriptVisitor {
169 type Value = CoreScript;
170
171 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
172 formatter.write_str("a byte array or base64-encoded string")
173 }
174
175 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
176 CoreScript::from_string(v, Encoding::Base64).map_err(|e| {
177 E::custom(format!(
178 "expected to be able to deserialize core script from string: {}",
179 e
180 ))
181 })
182 }
183
184 fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
185 self.visit_str(&v)
186 }
187
188 fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
189 Ok(CoreScript::from_bytes(v.to_vec()))
190 }
191
192 fn visit_byte_buf<E: serde::de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
193 Ok(CoreScript::from_bytes(v))
194 }
195 }
196
197 if deserializer.is_human_readable() {
198 deserializer.deserialize_string(CoreScriptVisitor)
199 } else {
200 deserializer.deserialize_bytes(CoreScriptVisitor)
201 }
202 }
203}
204
205impl std::fmt::Display for CoreScript {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 write!(f, "{}", self.to_string(Encoding::Base64))
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use dashcore::blockdata::opcodes;
215 use platform_value::string_encoding::Encoding;
216
217 mod construction {
218 use super::*;
219
220 #[test]
221 fn from_bytes_creates_script() {
222 let bytes = vec![1, 2, 3, 4, 5];
223 let script = CoreScript::from_bytes(bytes.clone());
224 assert_eq!(script.as_bytes(), &bytes);
225 }
226
227 #[test]
228 fn new_wraps_dashcore_script() {
229 let dashcore_script = DashcoreScript::from(vec![10, 20, 30]);
230 let script = CoreScript::new(dashcore_script.clone());
231 assert_eq!(script.as_bytes(), dashcore_script.as_bytes());
232 }
233
234 #[test]
235 fn default_is_empty() {
236 let script = CoreScript::default();
237 assert!(script.as_bytes().is_empty());
238 }
239
240 #[test]
241 fn from_vec_u8() {
242 let bytes = vec![0xAA, 0xBB, 0xCC];
243 let script: CoreScript = bytes.clone().into();
244 assert_eq!(script.as_bytes(), &bytes);
245 }
246 }
247
248 mod p2pkh {
249 use super::*;
250
251 #[test]
252 fn new_p2pkh_has_correct_structure() {
253 let key_hash = [0u8; 20];
254 let script = CoreScript::new_p2pkh(key_hash);
255 let bytes = script.as_bytes();
256
257 assert_eq!(bytes.len(), 25); assert_eq!(bytes[0], opcodes::all::OP_DUP.to_u8());
260 assert_eq!(bytes[1], opcodes::all::OP_HASH160.to_u8());
261 assert_eq!(bytes[2], opcodes::all::OP_PUSHBYTES_20.to_u8());
262 assert_eq!(&bytes[3..23], &key_hash);
263 assert_eq!(bytes[23], opcodes::all::OP_EQUALVERIFY.to_u8());
264 assert_eq!(bytes[24], opcodes::all::OP_CHECKSIG.to_u8());
265 }
266
267 #[test]
268 fn new_p2pkh_with_nonzero_hash() {
269 let key_hash: [u8; 20] = [
270 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
271 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
272 ];
273 let script = CoreScript::new_p2pkh(key_hash);
274 let bytes = script.as_bytes();
275 assert_eq!(&bytes[3..23], &key_hash);
276 }
277
278 #[test]
279 fn two_different_key_hashes_produce_different_scripts() {
280 let hash_a = [0xAA; 20];
281 let hash_b = [0xBB; 20];
282 let script_a = CoreScript::new_p2pkh(hash_a);
283 let script_b = CoreScript::new_p2pkh(hash_b);
284 assert_ne!(script_a, script_b);
285 }
286 }
287
288 mod p2sh {
289 use super::*;
290
291 #[test]
292 fn new_p2sh_has_correct_structure() {
293 let script_hash = [0u8; 20];
294 let script = CoreScript::new_p2sh(script_hash);
295 let bytes = script.as_bytes();
296
297 assert_eq!(bytes.len(), 23); assert_eq!(bytes[0], opcodes::all::OP_HASH160.to_u8());
300 assert_eq!(bytes[1], opcodes::all::OP_PUSHBYTES_20.to_u8());
301 assert_eq!(&bytes[2..22], &script_hash);
302 assert_eq!(bytes[22], opcodes::all::OP_EQUAL.to_u8());
303 }
304
305 #[test]
306 fn new_p2sh_with_nonzero_hash() {
307 let script_hash: [u8; 20] = [0xFF; 20];
308 let script = CoreScript::new_p2sh(script_hash);
309 let bytes = script.as_bytes();
310 assert_eq!(&bytes[2..22], &script_hash);
311 }
312
313 #[test]
314 fn p2pkh_and_p2sh_differ_for_same_hash() {
315 let hash = [0x42; 20];
316 let p2pkh = CoreScript::new_p2pkh(hash);
317 let p2sh = CoreScript::new_p2sh(hash);
318 assert_ne!(p2pkh, p2sh);
319 assert_eq!(p2pkh.as_bytes().len(), 25);
321 assert_eq!(p2sh.as_bytes().len(), 23);
322 }
323 }
324
325 mod string_encoding_round_trip {
326 use super::*;
327
328 #[test]
329 fn base64_round_trip() {
330 let original = CoreScript::new_p2pkh([0xAB; 20]);
331 let encoded = original.to_string(Encoding::Base64);
332 let decoded =
333 CoreScript::from_string(&encoded, Encoding::Base64).expect("should decode base64");
334 assert_eq!(original, decoded);
335 }
336
337 #[test]
338 fn hex_round_trip() {
339 let original = CoreScript::new_p2sh([0xCD; 20]);
340 let encoded = original.to_string(Encoding::Hex);
341 let decoded =
342 CoreScript::from_string(&encoded, Encoding::Hex).expect("should decode hex");
343 assert_eq!(original, decoded);
344 }
345
346 #[test]
347 fn from_string_invalid_base64_fails() {
348 let result = CoreScript::from_string("not-valid-base64!!!", Encoding::Base64);
349 assert!(result.is_err());
350 }
351
352 #[test]
353 fn display_uses_base64() {
354 let script = CoreScript::new_p2pkh([0x00; 20]);
355 let display_str = format!("{}", script);
356 let encoded = script.to_string(Encoding::Base64);
357 assert_eq!(display_str, encoded);
358 }
359 }
360
361 mod from_bytes_round_trip {
362 use super::*;
363
364 #[test]
365 fn bytes_round_trip() {
366 let original_bytes = vec![1, 2, 3, 4, 5, 6, 7, 8];
367 let script = CoreScript::from_bytes(original_bytes.clone());
368 assert_eq!(script.as_bytes(), &original_bytes);
369 }
370
371 #[test]
372 fn empty_bytes() {
373 let script = CoreScript::from_bytes(vec![]);
374 assert!(script.as_bytes().is_empty());
375 }
376 }
377
378 mod deref {
379 use super::*;
380
381 #[test]
382 fn deref_returns_inner_script() {
383 let bytes = vec![1, 2, 3];
384 let script = CoreScript::from_bytes(bytes.clone());
385 let inner: &DashcoreScript = &script;
387 assert_eq!(inner.as_bytes(), &bytes);
388 }
389 }
390
391 mod equality_and_clone {
392 use super::*;
393
394 #[test]
395 fn equal_scripts_are_equal() {
396 let a = CoreScript::new_p2pkh([0x11; 20]);
397 let b = CoreScript::new_p2pkh([0x11; 20]);
398 assert_eq!(a, b);
399 }
400
401 #[test]
402 fn different_scripts_are_not_equal() {
403 let a = CoreScript::new_p2pkh([0x11; 20]);
404 let b = CoreScript::new_p2pkh([0x22; 20]);
405 assert_ne!(a, b);
406 }
407
408 #[test]
409 fn clone_produces_equal_script() {
410 let original = CoreScript::new_p2sh([0x33; 20]);
411 let cloned = original.clone();
412 assert_eq!(original, cloned);
413 }
414 }
415
416 mod random_scripts {
417 use super::*;
418 use rand::SeedableRng;
419
420 #[test]
421 fn random_p2pkh_produces_valid_script() {
422 let mut rng = StdRng::seed_from_u64(42);
423 let script = CoreScript::random_p2pkh(&mut rng);
424 let bytes = script.as_bytes();
425 assert_eq!(bytes.len(), 25);
426 assert_eq!(bytes[0], opcodes::all::OP_DUP.to_u8());
427 }
428
429 #[test]
430 fn random_p2sh_produces_valid_script() {
431 let mut rng = StdRng::seed_from_u64(42);
432 let script = CoreScript::random_p2sh(&mut rng);
433 let bytes = script.as_bytes();
434 assert_eq!(bytes.len(), 23);
435 assert_eq!(bytes[0], opcodes::all::OP_HASH160.to_u8());
436 }
437
438 #[test]
439 fn two_random_scripts_differ() {
440 let mut rng = StdRng::seed_from_u64(42);
441 let a = CoreScript::random_p2pkh(&mut rng);
442 let b = CoreScript::random_p2pkh(&mut rng);
443 assert_ne!(a, b);
444 }
445 }
446}