1use 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 pub fn decode(script: &LockingScript) -> Result<PushDrop, ScriptError> {
95 let chunks = script.chunks();
96 if chunks.len() < 3 {
97 return Err(ScriptError::InvalidScript(
98 "PushDrop::decode: script too short".into(),
99 ));
100 }
101
102 let last = &chunks[chunks.len() - 1];
104 if last.op != Op::OpCheckSig {
105 return Err(ScriptError::InvalidScript(
106 "PushDrop::decode: last opcode must be OP_CHECKSIG".into(),
107 ));
108 }
109
110 let pubkey_chunk = &chunks[chunks.len() - 2];
112 if pubkey_chunk.data.is_none() {
113 return Err(ScriptError::InvalidScript(
114 "PushDrop::decode: expected pubkey data push before OP_CHECKSIG".into(),
115 ));
116 }
117
118 let mut drop_field_count = 0usize;
120 let mut pos = chunks.len() - 3; loop {
122 let chunk = &chunks[pos];
123 if chunk.op == Op::Op2Drop {
124 drop_field_count += 2;
125 } else if chunk.op == Op::OpDrop {
126 drop_field_count += 1;
127 } else {
128 break;
129 }
130 if pos == 0 {
131 break;
132 }
133 pos -= 1;
134 }
135
136 if drop_field_count == 0 {
137 return Err(ScriptError::InvalidScript(
138 "PushDrop::decode: no OP_DROP/OP_2DROP found".into(),
139 ));
140 }
141
142 if drop_field_count > pos + 1 {
144 return Err(ScriptError::InvalidScript(
145 "PushDrop::decode: not enough data pushes for drop count".into(),
146 ));
147 }
148
149 let data_end = pos + 1; if data_end != drop_field_count {
155 return Err(ScriptError::InvalidScript(format!(
156 "PushDrop::decode: field count mismatch: {} data chunks but {} drops",
157 data_end, drop_field_count
158 )));
159 }
160
161 let mut fields = Vec::with_capacity(drop_field_count);
162 for chunk in &chunks[0..drop_field_count] {
163 let data = chunk.data.as_ref().ok_or_else(|| {
164 ScriptError::InvalidScript("PushDrop::decode: expected data push for field".into())
165 })?;
166 fields.push(data.clone());
167 }
168
169 Ok(PushDrop {
170 fields,
171 private_key: None,
172 sighash_type: SIGHASH_ALL | SIGHASH_FORKID,
173 })
174 }
175
176 fn make_data_push(data: &[u8]) -> ScriptChunk {
178 let len = data.len();
179 if len < 0x4c {
180 ScriptChunk::new_raw(len as u8, Some(data.to_vec()))
182 } else if len < 256 {
183 ScriptChunk::new_raw(Op::OpPushData1.to_byte(), Some(data.to_vec()))
184 } else if len < 65536 {
185 ScriptChunk::new_raw(Op::OpPushData2.to_byte(), Some(data.to_vec()))
186 } else {
187 ScriptChunk::new_raw(Op::OpPushData4.to_byte(), Some(data.to_vec()))
188 }
189 }
190}
191
192impl ScriptTemplateLock for PushDrop {
193 fn lock(&self) -> Result<LockingScript, ScriptError> {
201 let key = self.private_key.as_ref().ok_or_else(|| {
202 ScriptError::InvalidScript(
203 "PushDrop: need private key to derive pubkey for lock".into(),
204 )
205 })?;
206
207 if self.fields.is_empty() {
208 return Err(ScriptError::InvalidScript(
209 "PushDrop: at least one data field required".into(),
210 ));
211 }
212
213 let mut chunks = Vec::new();
214
215 for field in &self.fields {
217 chunks.push(Self::make_data_push(field));
218 }
219
220 let num_fields = self.fields.len();
223 let num_2drops = num_fields / 2;
224 let num_drops = num_fields % 2;
225
226 for _ in 0..num_2drops {
227 chunks.push(ScriptChunk::new_opcode(Op::Op2Drop));
228 }
229 for _ in 0..num_drops {
230 chunks.push(ScriptChunk::new_opcode(Op::OpDrop));
231 }
232
233 let pubkey = key.to_public_key();
235 let pubkey_bytes = pubkey.to_der();
236 chunks.push(ScriptChunk::new_raw(
237 pubkey_bytes.len() as u8,
238 Some(pubkey_bytes),
239 ));
240 chunks.push(ScriptChunk::new_opcode(Op::OpCheckSig));
241
242 Ok(LockingScript::from_script(Script::from_chunks(chunks)))
243 }
244}
245
246impl ScriptTemplateUnlock for PushDrop {
247 fn sign(&self, preimage: &[u8]) -> Result<UnlockingScript, ScriptError> {
248 self.unlock(preimage)
249 }
250
251 fn estimate_length(&self) -> Result<usize, ScriptError> {
252 Ok(self.estimate_unlock_length())
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
265 fn test_pushdrop_decode_roundtrip_one_field() {
266 let key = PrivateKey::from_hex("1").unwrap();
267 let fields = vec![vec![0xca, 0xfe, 0xba, 0xbe]];
268 let pd = PushDrop::new(fields.clone(), key);
269 let lock_script = pd.lock().unwrap();
270
271 let decoded = PushDrop::decode(&lock_script).unwrap();
272 assert_eq!(decoded.fields, fields, "decode should recover 1 field");
273 }
274
275 #[test]
276 fn test_pushdrop_decode_roundtrip_two_fields() {
277 let key = PrivateKey::from_hex("1").unwrap();
278 let fields = vec![vec![0x01, 0x02], vec![0x03, 0x04]];
279 let pd = PushDrop::new(fields.clone(), key);
280 let lock_script = pd.lock().unwrap();
281
282 let decoded = PushDrop::decode(&lock_script).unwrap();
283 assert_eq!(
284 decoded.fields, fields,
285 "decode should recover 2 fields (OP_2DROP)"
286 );
287 }
288
289 #[test]
290 fn test_pushdrop_decode_roundtrip_three_fields() {
291 let key = PrivateKey::from_hex("1").unwrap();
292 let fields = vec![vec![0x01], vec![0x02], vec![0x03]];
293 let pd = PushDrop::new(fields.clone(), key);
294 let lock_script = pd.lock().unwrap();
295
296 let decoded = PushDrop::decode(&lock_script).unwrap();
297 assert_eq!(
298 decoded.fields, fields,
299 "decode should recover 3 fields (OP_2DROP + OP_DROP)"
300 );
301 }
302
303 #[test]
304 fn test_pushdrop_decode_non_pushdrop_script_errors() {
305 let script = LockingScript::from_binary(&[0x76, 0xa9, 0x14]);
307 assert!(PushDrop::decode(&script).is_err());
308 }
309
310 #[test]
315 fn test_pushdrop_lock_one_field() {
316 let key = PrivateKey::from_hex("1").unwrap();
317 let data = vec![0xca, 0xfe, 0xba, 0xbe];
318 let pd = PushDrop::new(vec![data.clone()], key);
319
320 let lock_script = pd.lock().unwrap();
321 let chunks = lock_script.chunks();
322
323 assert_eq!(chunks.len(), 4, "1-field PushDrop should have 4 chunks");
325
326 assert_eq!(chunks[0].data.as_ref().unwrap(), &data);
328 assert_eq!(chunks[1].op, Op::OpDrop);
330 assert_eq!(chunks[2].data.as_ref().unwrap().len(), 33);
332 assert_eq!(chunks[3].op, Op::OpCheckSig);
334 }
335
336 #[test]
341 fn test_pushdrop_lock_multiple_fields() {
342 let key = PrivateKey::from_hex("1").unwrap();
343 let fields = vec![vec![0x01, 0x02], vec![0x03, 0x04], vec![0x05, 0x06]];
344 let pd = PushDrop::new(fields.clone(), key);
345
346 let lock_script = pd.lock().unwrap();
347 let chunks = lock_script.chunks();
348
349 assert_eq!(chunks.len(), 7, "3-field PushDrop should have 7 chunks");
351
352 assert_eq!(chunks[0].data.as_ref().unwrap(), &fields[0]);
354 assert_eq!(chunks[1].data.as_ref().unwrap(), &fields[1]);
355 assert_eq!(chunks[2].data.as_ref().unwrap(), &fields[2]);
356
357 assert_eq!(chunks[3].op, Op::Op2Drop);
359 assert_eq!(chunks[4].op, Op::OpDrop);
360
361 assert_eq!(chunks[6].op, Op::OpCheckSig);
363 }
364
365 #[test]
370 fn test_pushdrop_lock_even_fields() {
371 let key = PrivateKey::from_hex("1").unwrap();
372 let fields = vec![vec![0x01], vec![0x02]];
373 let pd = PushDrop::new(fields, key);
374
375 let lock_script = pd.lock().unwrap();
376 let chunks = lock_script.chunks();
377
378 assert_eq!(chunks.len(), 5);
380 assert_eq!(chunks[2].op, Op::Op2Drop);
381 }
382
383 #[test]
388 fn test_pushdrop_unlock_produces_signature() {
389 let key = PrivateKey::from_hex("1").unwrap();
390 let pd = PushDrop::new(vec![vec![0xaa]], key);
391
392 let unlock_script = pd.unlock(b"test preimage").unwrap();
393 assert_eq!(
394 unlock_script.chunks().len(),
395 1,
396 "PushDrop unlock should be 1 chunk (just sig)"
397 );
398
399 let sig_data = unlock_script.chunks()[0].data.as_ref().unwrap();
400 assert!(sig_data.len() >= 70 && sig_data.len() <= 74);
402 assert_eq!(
404 *sig_data.last().unwrap(),
405 (SIGHASH_ALL | SIGHASH_FORKID) as u8
406 );
407 }
408
409 #[test]
414 fn test_pushdrop_estimate_length() {
415 let key = PrivateKey::from_hex("1").unwrap();
416 let pd = PushDrop::new(vec![vec![0x01]], key);
417 assert_eq!(pd.estimate_unlock_length(), 74);
418 }
419
420 #[test]
425 fn test_pushdrop_lock_no_key() {
426 let pd = PushDrop::lock_only(vec![vec![0x01]]);
427 assert!(pd.lock().is_err());
428 }
429
430 #[test]
431 fn test_pushdrop_lock_no_fields() {
432 let key = PrivateKey::from_hex("1").unwrap();
433 let pd = PushDrop::new(vec![], key);
434 assert!(pd.lock().is_err());
435 }
436
437 #[test]
438 fn test_pushdrop_unlock_no_key() {
439 let pd = PushDrop::lock_only(vec![vec![0x01]]);
440 assert!(pd.unlock(b"test").is_err());
441 }
442
443 #[test]
448 fn test_pushdrop_trait_sign() {
449 let key = PrivateKey::from_hex("ff").unwrap();
450 let pd = PushDrop::new(vec![vec![0x01, 0x02, 0x03]], key);
451 let unlock_script = pd.sign(b"sighash data").unwrap();
452 assert_eq!(unlock_script.chunks().len(), 1);
453 }
454
455 #[test]
460 fn test_pushdrop_lock_binary_roundtrip() {
461 let key = PrivateKey::from_hex("1").unwrap();
462 let pd = PushDrop::new(vec![vec![0xde, 0xad]], key);
463
464 let lock_script = pd.lock().unwrap();
465 let binary = lock_script.to_binary();
466
467 let reparsed = Script::from_binary(&binary);
469 assert_eq!(
470 reparsed.to_binary(),
471 binary,
472 "binary roundtrip should match"
473 );
474 }
475}