kaspa_txscript/
script_class.rs1use crate::{opcodes, MAX_SCRIPT_PUBLIC_KEY_VERSION};
2use borsh::{BorshDeserialize, BorshSerialize};
3use kaspa_addresses::Version;
4use kaspa_consensus_core::tx::{ScriptPublicKey, ScriptPublicKeyVersion};
5use serde::{Deserialize, Serialize};
6use std::{
7 fmt::{Display, Formatter},
8 str::FromStr,
9};
10use thiserror::Error;
11
12#[derive(Error, PartialEq, Eq, Debug, Clone)]
13pub enum Error {
14 #[error("Invalid script class {0}")]
15 InvalidScriptClass(String),
16}
17
18#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
20#[borsh(use_discriminant = true)]
21#[repr(u8)]
22pub enum ScriptClass {
23 NonStandard = 0,
25 PubKey,
27 PubKeyECDSA,
29 ScriptHash,
31}
32
33const NON_STANDARD: &str = "nonstandard";
34const PUB_KEY: &str = "pubkey";
35const PUB_KEY_ECDSA: &str = "pubkeyecdsa";
36const SCRIPT_HASH: &str = "scripthash";
37
38impl ScriptClass {
39 pub fn from_script(script_public_key: &ScriptPublicKey) -> Self {
40 let script_public_key_ = script_public_key.script();
41 if script_public_key.version() == MAX_SCRIPT_PUBLIC_KEY_VERSION {
42 if Self::is_pay_to_pubkey(script_public_key_) {
43 ScriptClass::PubKey
44 } else if Self::is_pay_to_pubkey_ecdsa(script_public_key_) {
45 Self::PubKeyECDSA
46 } else if Self::is_pay_to_script_hash(script_public_key_) {
47 Self::ScriptHash
48 } else {
49 ScriptClass::NonStandard
50 }
51 } else {
52 ScriptClass::NonStandard
53 }
54 }
55
56 #[inline(always)]
59 pub fn is_pay_to_pubkey(script_public_key: &[u8]) -> bool {
60 (script_public_key.len() == 34) && (script_public_key[0] == opcodes::codes::OpData32) &&
62 (script_public_key[33] == opcodes::codes::OpCheckSig)
63 }
64
65 #[inline(always)]
68 pub fn is_pay_to_pubkey_ecdsa(script_public_key: &[u8]) -> bool {
69 (script_public_key.len() == 35) && (script_public_key[0] == opcodes::codes::OpData33) &&
71 (script_public_key[34] == opcodes::codes::OpCheckSigECDSA)
72 }
73
74 #[inline(always)]
77 pub fn is_pay_to_script_hash(script_public_key: &[u8]) -> bool {
78 (script_public_key.len() == 35) && (script_public_key[0] == opcodes::codes::OpBlake2b) &&
80 (script_public_key[1] == opcodes::codes::OpData32) &&
81 (script_public_key[34] == opcodes::codes::OpEqual)
82 }
83
84 fn as_str(&self) -> &'static str {
85 match self {
86 ScriptClass::NonStandard => NON_STANDARD,
87 ScriptClass::PubKey => PUB_KEY,
88 ScriptClass::PubKeyECDSA => PUB_KEY_ECDSA,
89 ScriptClass::ScriptHash => SCRIPT_HASH,
90 }
91 }
92
93 pub fn version(&self) -> ScriptPublicKeyVersion {
94 match self {
95 ScriptClass::NonStandard => 0,
96 ScriptClass::PubKey => MAX_SCRIPT_PUBLIC_KEY_VERSION,
97 ScriptClass::PubKeyECDSA => MAX_SCRIPT_PUBLIC_KEY_VERSION,
98 ScriptClass::ScriptHash => MAX_SCRIPT_PUBLIC_KEY_VERSION,
99 }
100 }
101}
102
103impl Display for ScriptClass {
104 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
105 f.write_str(self.as_str())
106 }
107}
108
109impl FromStr for ScriptClass {
110 type Err = Error;
111
112 fn from_str(script_class: &str) -> Result<Self, Self::Err> {
113 match script_class {
114 NON_STANDARD => Ok(ScriptClass::NonStandard),
115 PUB_KEY => Ok(ScriptClass::PubKey),
116 PUB_KEY_ECDSA => Ok(ScriptClass::PubKeyECDSA),
117 SCRIPT_HASH => Ok(ScriptClass::ScriptHash),
118 _ => Err(Error::InvalidScriptClass(script_class.to_string())),
119 }
120 }
121}
122
123impl TryFrom<&str> for ScriptClass {
124 type Error = Error;
125
126 fn try_from(script_class: &str) -> Result<Self, Self::Error> {
127 script_class.parse()
128 }
129}
130
131impl From<Version> for ScriptClass {
132 fn from(value: Version) -> Self {
133 match value {
134 Version::PubKey => ScriptClass::PubKey,
135 Version::PubKeyECDSA => ScriptClass::PubKeyECDSA,
136 Version::ScriptHash => ScriptClass::ScriptHash,
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use kaspa_consensus_core::tx::ScriptVec;
144
145 use super::*;
146
147 #[test]
148 fn test_script_class_from_script() {
149 struct Test {
150 name: &'static str,
151 script: Vec<u8>,
152 version: ScriptPublicKeyVersion,
153 class: ScriptClass,
154 }
155
156 let tests = vec![
158 Test {
159 name: "valid pubkey script",
160 script: hex::decode("204a23f5eef4b2dead811c7efb4f1afbd8df845e804b6c36a4001fc096e13f8151ac").unwrap(),
161 version: 0,
162 class: ScriptClass::PubKey,
163 },
164 Test {
165 name: "valid pubkey ecdsa script",
166 script: hex::decode("21fd4a23f5eef4b2dead811c7efb4f1afbd8df845e804b6c36a4001fc096e13f8151ab").unwrap(),
167 version: 0,
168 class: ScriptClass::PubKeyECDSA,
169 },
170 Test {
171 name: "valid scripthash script",
172 script: hex::decode("aa204a23f5eef4b2dead811c7efb4f1afbd8df845e804b6c36a4001fc096e13f815187").unwrap(),
173 version: 0,
174 class: ScriptClass::ScriptHash,
175 },
176 Test {
177 name: "non standard script (unexpected version)",
178 script: hex::decode("204a23f5eef4b2dead811c7efb4f1afbd8df845e804b6c36a4001fc096e13f8151ac").unwrap(),
179 version: MAX_SCRIPT_PUBLIC_KEY_VERSION + 1,
180 class: ScriptClass::NonStandard,
181 },
182 Test {
183 name: "non standard script (unexpected key len)",
184 script: hex::decode("1f4a23f5eef4b2dead811c7efb4f1afbd8df845e804b6c36a4001fc096e13f81ac").unwrap(),
185 version: 0,
186 class: ScriptClass::NonStandard,
187 },
188 Test {
189 name: "non standard script (unexpected final check sig op)",
190 script: hex::decode("204a23f5eef4b2dead811c7efb4f1afbd8df845e804b6c36a4001fc096e13f8151ad").unwrap(),
191 version: 0,
192 class: ScriptClass::NonStandard,
193 },
194 ];
195 for test in tests {
198 let script_public_key = ScriptPublicKey::new(test.version, ScriptVec::from_iter(test.script.iter().copied()));
199 assert_eq!(test.class, ScriptClass::from_script(&script_public_key), "{} wrong script class", test.name);
200 }
201 }
202}