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 if deserializer.is_human_readable() {
161 let data: String = Deserialize::deserialize(deserializer)?;
162
163 Self::from_string(&data, Encoding::Base64).map_err(|e| {
164 serde::de::Error::custom(format!(
165 "expected to be able to deserialize core script from string: {}",
166 e
167 ))
168 })
169 } else {
170 struct BytesVisitor;
171
172 impl Visitor<'_> for BytesVisitor {
173 type Value = CoreScript;
174
175 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
176 formatter.write_str("a byte array")
177 }
178
179 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
180 where
181 E: serde::de::Error,
182 {
183 Ok(CoreScript::from_bytes(v.to_vec()))
184 }
185 }
186
187 deserializer.deserialize_bytes(BytesVisitor)
188 }
189 }
190}
191
192impl std::fmt::Display for CoreScript {
193 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 write!(f, "{}", self.to_string(Encoding::Base64))
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use dashcore::blockdata::opcodes;
202 use platform_value::string_encoding::Encoding;
203
204 mod construction {
205 use super::*;
206
207 #[test]
208 fn from_bytes_creates_script() {
209 let bytes = vec![1, 2, 3, 4, 5];
210 let script = CoreScript::from_bytes(bytes.clone());
211 assert_eq!(script.as_bytes(), &bytes);
212 }
213
214 #[test]
215 fn new_wraps_dashcore_script() {
216 let dashcore_script = DashcoreScript::from(vec![10, 20, 30]);
217 let script = CoreScript::new(dashcore_script.clone());
218 assert_eq!(script.as_bytes(), dashcore_script.as_bytes());
219 }
220
221 #[test]
222 fn default_is_empty() {
223 let script = CoreScript::default();
224 assert!(script.as_bytes().is_empty());
225 }
226
227 #[test]
228 fn from_vec_u8() {
229 let bytes = vec![0xAA, 0xBB, 0xCC];
230 let script: CoreScript = bytes.clone().into();
231 assert_eq!(script.as_bytes(), &bytes);
232 }
233 }
234
235 mod p2pkh {
236 use super::*;
237
238 #[test]
239 fn new_p2pkh_has_correct_structure() {
240 let key_hash = [0u8; 20];
241 let script = CoreScript::new_p2pkh(key_hash);
242 let bytes = script.as_bytes();
243
244 assert_eq!(bytes.len(), 25); assert_eq!(bytes[0], opcodes::all::OP_DUP.to_u8());
247 assert_eq!(bytes[1], opcodes::all::OP_HASH160.to_u8());
248 assert_eq!(bytes[2], opcodes::all::OP_PUSHBYTES_20.to_u8());
249 assert_eq!(&bytes[3..23], &key_hash);
250 assert_eq!(bytes[23], opcodes::all::OP_EQUALVERIFY.to_u8());
251 assert_eq!(bytes[24], opcodes::all::OP_CHECKSIG.to_u8());
252 }
253
254 #[test]
255 fn new_p2pkh_with_nonzero_hash() {
256 let key_hash: [u8; 20] = [
257 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
258 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
259 ];
260 let script = CoreScript::new_p2pkh(key_hash);
261 let bytes = script.as_bytes();
262 assert_eq!(&bytes[3..23], &key_hash);
263 }
264
265 #[test]
266 fn two_different_key_hashes_produce_different_scripts() {
267 let hash_a = [0xAA; 20];
268 let hash_b = [0xBB; 20];
269 let script_a = CoreScript::new_p2pkh(hash_a);
270 let script_b = CoreScript::new_p2pkh(hash_b);
271 assert_ne!(script_a, script_b);
272 }
273 }
274
275 mod p2sh {
276 use super::*;
277
278 #[test]
279 fn new_p2sh_has_correct_structure() {
280 let script_hash = [0u8; 20];
281 let script = CoreScript::new_p2sh(script_hash);
282 let bytes = script.as_bytes();
283
284 assert_eq!(bytes.len(), 23); assert_eq!(bytes[0], opcodes::all::OP_HASH160.to_u8());
287 assert_eq!(bytes[1], opcodes::all::OP_PUSHBYTES_20.to_u8());
288 assert_eq!(&bytes[2..22], &script_hash);
289 assert_eq!(bytes[22], opcodes::all::OP_EQUAL.to_u8());
290 }
291
292 #[test]
293 fn new_p2sh_with_nonzero_hash() {
294 let script_hash: [u8; 20] = [0xFF; 20];
295 let script = CoreScript::new_p2sh(script_hash);
296 let bytes = script.as_bytes();
297 assert_eq!(&bytes[2..22], &script_hash);
298 }
299
300 #[test]
301 fn p2pkh_and_p2sh_differ_for_same_hash() {
302 let hash = [0x42; 20];
303 let p2pkh = CoreScript::new_p2pkh(hash);
304 let p2sh = CoreScript::new_p2sh(hash);
305 assert_ne!(p2pkh, p2sh);
306 assert_eq!(p2pkh.as_bytes().len(), 25);
308 assert_eq!(p2sh.as_bytes().len(), 23);
309 }
310 }
311
312 mod string_encoding_round_trip {
313 use super::*;
314
315 #[test]
316 fn base64_round_trip() {
317 let original = CoreScript::new_p2pkh([0xAB; 20]);
318 let encoded = original.to_string(Encoding::Base64);
319 let decoded =
320 CoreScript::from_string(&encoded, Encoding::Base64).expect("should decode base64");
321 assert_eq!(original, decoded);
322 }
323
324 #[test]
325 fn hex_round_trip() {
326 let original = CoreScript::new_p2sh([0xCD; 20]);
327 let encoded = original.to_string(Encoding::Hex);
328 let decoded =
329 CoreScript::from_string(&encoded, Encoding::Hex).expect("should decode hex");
330 assert_eq!(original, decoded);
331 }
332
333 #[test]
334 fn from_string_invalid_base64_fails() {
335 let result = CoreScript::from_string("not-valid-base64!!!", Encoding::Base64);
336 assert!(result.is_err());
337 }
338
339 #[test]
340 fn display_uses_base64() {
341 let script = CoreScript::new_p2pkh([0x00; 20]);
342 let display_str = format!("{}", script);
343 let encoded = script.to_string(Encoding::Base64);
344 assert_eq!(display_str, encoded);
345 }
346 }
347
348 mod from_bytes_round_trip {
349 use super::*;
350
351 #[test]
352 fn bytes_round_trip() {
353 let original_bytes = vec![1, 2, 3, 4, 5, 6, 7, 8];
354 let script = CoreScript::from_bytes(original_bytes.clone());
355 assert_eq!(script.as_bytes(), &original_bytes);
356 }
357
358 #[test]
359 fn empty_bytes() {
360 let script = CoreScript::from_bytes(vec![]);
361 assert!(script.as_bytes().is_empty());
362 }
363 }
364
365 mod deref {
366 use super::*;
367
368 #[test]
369 fn deref_returns_inner_script() {
370 let bytes = vec![1, 2, 3];
371 let script = CoreScript::from_bytes(bytes.clone());
372 let inner: &DashcoreScript = &script;
374 assert_eq!(inner.as_bytes(), &bytes);
375 }
376 }
377
378 mod equality_and_clone {
379 use super::*;
380
381 #[test]
382 fn equal_scripts_are_equal() {
383 let a = CoreScript::new_p2pkh([0x11; 20]);
384 let b = CoreScript::new_p2pkh([0x11; 20]);
385 assert_eq!(a, b);
386 }
387
388 #[test]
389 fn different_scripts_are_not_equal() {
390 let a = CoreScript::new_p2pkh([0x11; 20]);
391 let b = CoreScript::new_p2pkh([0x22; 20]);
392 assert_ne!(a, b);
393 }
394
395 #[test]
396 fn clone_produces_equal_script() {
397 let original = CoreScript::new_p2sh([0x33; 20]);
398 let cloned = original.clone();
399 assert_eq!(original, cloned);
400 }
401 }
402
403 mod random_scripts {
404 use super::*;
405 use rand::SeedableRng;
406
407 #[test]
408 fn random_p2pkh_produces_valid_script() {
409 let mut rng = StdRng::seed_from_u64(42);
410 let script = CoreScript::random_p2pkh(&mut rng);
411 let bytes = script.as_bytes();
412 assert_eq!(bytes.len(), 25);
413 assert_eq!(bytes[0], opcodes::all::OP_DUP.to_u8());
414 }
415
416 #[test]
417 fn random_p2sh_produces_valid_script() {
418 let mut rng = StdRng::seed_from_u64(42);
419 let script = CoreScript::random_p2sh(&mut rng);
420 let bytes = script.as_bytes();
421 assert_eq!(bytes.len(), 23);
422 assert_eq!(bytes[0], opcodes::all::OP_HASH160.to_u8());
423 }
424
425 #[test]
426 fn two_random_scripts_differ() {
427 let mut rng = StdRng::seed_from_u64(42);
428 let a = CoreScript::random_p2pkh(&mut rng);
429 let b = CoreScript::random_p2pkh(&mut rng);
430 assert_ne!(a, b);
431 }
432 }
433}