bsv/script/templates/
push_drop.rs1use crate::primitives::ecdsa::ecdsa_sign;
9use crate::primitives::hash::sha256;
10use crate::primitives::private_key::PrivateKey;
11use crate::primitives::transaction_signature::{SIGHASH_ALL, SIGHASH_FORKID};
12use crate::script::error::ScriptError;
13use crate::script::locking_script::LockingScript;
14use crate::script::op::Op;
15use crate::script::script::Script;
16use crate::script::script_chunk::ScriptChunk;
17use crate::script::templates::{ScriptTemplateLock, ScriptTemplateUnlock};
18use crate::script::unlocking_script::UnlockingScript;
19
20#[derive(Clone, Debug)]
26pub struct PushDrop {
27 pub fields: Vec<Vec<u8>>,
29 pub private_key: Option<PrivateKey>,
31 pub sighash_type: u32,
33}
34
35impl PushDrop {
36 pub fn new(fields: Vec<Vec<u8>>, key: PrivateKey) -> Self {
41 PushDrop {
42 fields,
43 private_key: Some(key),
44 sighash_type: SIGHASH_ALL | SIGHASH_FORKID,
45 }
46 }
47
48 pub fn lock_only(fields: Vec<Vec<u8>>) -> Self {
53 PushDrop {
54 fields,
55 private_key: None,
56 sighash_type: SIGHASH_ALL | SIGHASH_FORKID,
57 }
58 }
59
60 pub fn unlock(&self, preimage: &[u8]) -> Result<UnlockingScript, ScriptError> {
64 let key = self.private_key.as_ref().ok_or_else(|| {
65 ScriptError::InvalidScript("PushDrop: no private key for unlock".into())
66 })?;
67
68 let msg_hash = sha256(preimage);
69 let sig = ecdsa_sign(&msg_hash, key.bn(), true)
70 .map_err(|e| ScriptError::InvalidSignature(format!("ECDSA sign failed: {}", e)))?;
71
72 let mut sig_bytes = sig.to_der();
73 sig_bytes.push(self.sighash_type as u8);
74
75 let chunks = vec![ScriptChunk::new_raw(sig_bytes.len() as u8, Some(sig_bytes))];
76
77 Ok(UnlockingScript::from_script(Script::from_chunks(chunks)))
78 }
79
80 pub fn estimate_unlock_length(&self) -> usize {
85 74
86 }
87
88 fn make_data_push(data: &[u8]) -> ScriptChunk {
90 let len = data.len();
91 if len < 0x4c {
92 ScriptChunk::new_raw(len as u8, Some(data.to_vec()))
94 } else if len < 256 {
95 ScriptChunk::new_raw(Op::OpPushData1.to_byte(), Some(data.to_vec()))
96 } else if len < 65536 {
97 ScriptChunk::new_raw(Op::OpPushData2.to_byte(), Some(data.to_vec()))
98 } else {
99 ScriptChunk::new_raw(Op::OpPushData4.to_byte(), Some(data.to_vec()))
100 }
101 }
102}
103
104impl ScriptTemplateLock for PushDrop {
105 fn lock(&self) -> Result<LockingScript, ScriptError> {
113 let key = self.private_key.as_ref().ok_or_else(|| {
114 ScriptError::InvalidScript(
115 "PushDrop: need private key to derive pubkey for lock".into(),
116 )
117 })?;
118
119 if self.fields.is_empty() {
120 return Err(ScriptError::InvalidScript(
121 "PushDrop: at least one data field required".into(),
122 ));
123 }
124
125 let mut chunks = Vec::new();
126
127 for field in &self.fields {
129 chunks.push(Self::make_data_push(field));
130 }
131
132 let num_fields = self.fields.len();
135 let num_2drops = num_fields / 2;
136 let num_drops = num_fields % 2;
137
138 for _ in 0..num_2drops {
139 chunks.push(ScriptChunk::new_opcode(Op::Op2Drop));
140 }
141 for _ in 0..num_drops {
142 chunks.push(ScriptChunk::new_opcode(Op::OpDrop));
143 }
144
145 let pubkey = key.to_public_key();
147 let pubkey_bytes = pubkey.to_der();
148 chunks.push(ScriptChunk::new_raw(
149 pubkey_bytes.len() as u8,
150 Some(pubkey_bytes),
151 ));
152 chunks.push(ScriptChunk::new_opcode(Op::OpCheckSig));
153
154 Ok(LockingScript::from_script(Script::from_chunks(chunks)))
155 }
156}
157
158impl ScriptTemplateUnlock for PushDrop {
159 fn sign(&self, preimage: &[u8]) -> Result<UnlockingScript, ScriptError> {
160 self.unlock(preimage)
161 }
162
163 fn estimate_length(&self) -> Result<usize, ScriptError> {
164 Ok(self.estimate_unlock_length())
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
177 fn test_pushdrop_lock_one_field() {
178 let key = PrivateKey::from_hex("1").unwrap();
179 let data = vec![0xca, 0xfe, 0xba, 0xbe];
180 let pd = PushDrop::new(vec![data.clone()], key);
181
182 let lock_script = pd.lock().unwrap();
183 let chunks = lock_script.chunks();
184
185 assert_eq!(chunks.len(), 4, "1-field PushDrop should have 4 chunks");
187
188 assert_eq!(chunks[0].data.as_ref().unwrap(), &data);
190 assert_eq!(chunks[1].op, Op::OpDrop);
192 assert_eq!(chunks[2].data.as_ref().unwrap().len(), 33);
194 assert_eq!(chunks[3].op, Op::OpCheckSig);
196 }
197
198 #[test]
203 fn test_pushdrop_lock_multiple_fields() {
204 let key = PrivateKey::from_hex("1").unwrap();
205 let fields = vec![vec![0x01, 0x02], vec![0x03, 0x04], vec![0x05, 0x06]];
206 let pd = PushDrop::new(fields.clone(), key);
207
208 let lock_script = pd.lock().unwrap();
209 let chunks = lock_script.chunks();
210
211 assert_eq!(chunks.len(), 7, "3-field PushDrop should have 7 chunks");
213
214 assert_eq!(chunks[0].data.as_ref().unwrap(), &fields[0]);
216 assert_eq!(chunks[1].data.as_ref().unwrap(), &fields[1]);
217 assert_eq!(chunks[2].data.as_ref().unwrap(), &fields[2]);
218
219 assert_eq!(chunks[3].op, Op::Op2Drop);
221 assert_eq!(chunks[4].op, Op::OpDrop);
222
223 assert_eq!(chunks[6].op, Op::OpCheckSig);
225 }
226
227 #[test]
232 fn test_pushdrop_lock_even_fields() {
233 let key = PrivateKey::from_hex("1").unwrap();
234 let fields = vec![vec![0x01], vec![0x02]];
235 let pd = PushDrop::new(fields, key);
236
237 let lock_script = pd.lock().unwrap();
238 let chunks = lock_script.chunks();
239
240 assert_eq!(chunks.len(), 5);
242 assert_eq!(chunks[2].op, Op::Op2Drop);
243 }
244
245 #[test]
250 fn test_pushdrop_unlock_produces_signature() {
251 let key = PrivateKey::from_hex("1").unwrap();
252 let pd = PushDrop::new(vec![vec![0xaa]], key);
253
254 let unlock_script = pd.unlock(b"test preimage").unwrap();
255 assert_eq!(
256 unlock_script.chunks().len(),
257 1,
258 "PushDrop unlock should be 1 chunk (just sig)"
259 );
260
261 let sig_data = unlock_script.chunks()[0].data.as_ref().unwrap();
262 assert!(sig_data.len() >= 70 && sig_data.len() <= 74);
264 assert_eq!(
266 *sig_data.last().unwrap(),
267 (SIGHASH_ALL | SIGHASH_FORKID) as u8
268 );
269 }
270
271 #[test]
276 fn test_pushdrop_estimate_length() {
277 let key = PrivateKey::from_hex("1").unwrap();
278 let pd = PushDrop::new(vec![vec![0x01]], key);
279 assert_eq!(pd.estimate_unlock_length(), 74);
280 }
281
282 #[test]
287 fn test_pushdrop_lock_no_key() {
288 let pd = PushDrop::lock_only(vec![vec![0x01]]);
289 assert!(pd.lock().is_err());
290 }
291
292 #[test]
293 fn test_pushdrop_lock_no_fields() {
294 let key = PrivateKey::from_hex("1").unwrap();
295 let pd = PushDrop::new(vec![], key);
296 assert!(pd.lock().is_err());
297 }
298
299 #[test]
300 fn test_pushdrop_unlock_no_key() {
301 let pd = PushDrop::lock_only(vec![vec![0x01]]);
302 assert!(pd.unlock(b"test").is_err());
303 }
304
305 #[test]
310 fn test_pushdrop_trait_sign() {
311 let key = PrivateKey::from_hex("ff").unwrap();
312 let pd = PushDrop::new(vec![vec![0x01, 0x02, 0x03]], key);
313 let unlock_script = pd.sign(b"sighash data").unwrap();
314 assert_eq!(unlock_script.chunks().len(), 1);
315 }
316
317 #[test]
322 fn test_pushdrop_lock_binary_roundtrip() {
323 let key = PrivateKey::from_hex("1").unwrap();
324 let pd = PushDrop::new(vec![vec![0xde, 0xad]], key);
325
326 let lock_script = pd.lock().unwrap();
327 let binary = lock_script.to_binary();
328
329 let reparsed = Script::from_binary(&binary);
331 assert_eq!(
332 reparsed.to_binary(),
333 binary,
334 "binary roundtrip should match"
335 );
336 }
337}