1use crate::primitives::ecdsa::ecdsa_sign;
12use crate::primitives::hash::sha256;
13use crate::primitives::private_key::PrivateKey;
14use crate::primitives::transaction_signature::{SIGHASH_ALL, SIGHASH_FORKID};
15use crate::script::error::ScriptError;
16use crate::script::locking_script::LockingScript;
17use crate::script::op::Op;
18use crate::script::script::Script;
19use crate::script::script_chunk::ScriptChunk;
20use crate::script::templates::{ScriptTemplateLock, ScriptTemplateUnlock};
21use crate::script::unlocking_script::UnlockingScript;
22
23#[derive(Clone, Copy, Debug, Default, PartialEq)]
25pub enum LockPosition {
26 #[default]
28 Before,
29 After,
31}
32
33#[derive(Clone, Debug)]
39pub struct PushDrop {
40 pub fields: Vec<Vec<u8>>,
42 pub private_key: Option<PrivateKey>,
44 pub sighash_type: u32,
46 pub lock_position: LockPosition,
48}
49
50impl PushDrop {
51 pub fn new(fields: Vec<Vec<u8>>, key: PrivateKey) -> Self {
53 PushDrop {
54 fields,
55 private_key: Some(key),
56 sighash_type: SIGHASH_ALL | SIGHASH_FORKID,
57 lock_position: LockPosition::default(),
58 }
59 }
60
61 pub fn lock_only(fields: Vec<Vec<u8>>) -> Self {
63 PushDrop {
64 fields,
65 private_key: None,
66 sighash_type: SIGHASH_ALL | SIGHASH_FORKID,
67 lock_position: LockPosition::default(),
68 }
69 }
70
71 pub fn with_lock_position(mut self, position: LockPosition) -> Self {
73 self.lock_position = position;
74 self
75 }
76
77 pub fn unlock(&self, preimage: &[u8]) -> Result<UnlockingScript, ScriptError> {
79 let key = self.private_key.as_ref().ok_or_else(|| {
80 ScriptError::InvalidScript("PushDrop: no private key for unlock".into())
81 })?;
82
83 let msg_hash = sha256(preimage);
84 let sig = ecdsa_sign(&msg_hash, key.bn(), true)
85 .map_err(|e| ScriptError::InvalidSignature(format!("ECDSA sign failed: {}", e)))?;
86
87 let mut sig_bytes = sig.to_der();
88 sig_bytes.push(self.sighash_type as u8);
89
90 let chunks = vec![ScriptChunk::new_raw(sig_bytes.len() as u8, Some(sig_bytes))];
91
92 Ok(UnlockingScript::from_script(Script::from_chunks(chunks)))
93 }
94
95 pub fn estimate_unlock_length(&self) -> usize {
97 74
98 }
99
100 pub fn decode(script: &LockingScript) -> Result<PushDrop, ScriptError> {
108 Self::decode_with_position(script, LockPosition::Before)
109 }
110
111 pub fn decode_with_position(
113 script: &LockingScript,
114 position: LockPosition,
115 ) -> Result<PushDrop, ScriptError> {
116 let chunks = script.chunks();
117 if chunks.len() < 3 {
118 return Err(ScriptError::InvalidScript(
119 "PushDrop::decode: script too short".into(),
120 ));
121 }
122
123 let fields = match position {
124 LockPosition::Before => Self::decode_before(chunks)?,
125 LockPosition::After => Self::decode_after(chunks)?,
126 };
127
128 Ok(PushDrop {
129 fields,
130 private_key: None,
131 sighash_type: SIGHASH_ALL | SIGHASH_FORKID,
132 lock_position: position,
133 })
134 }
135
136 fn decode_before(chunks: &[ScriptChunk]) -> Result<Vec<Vec<u8>>, ScriptError> {
142 if chunks.len() < 2 || chunks[0].data.is_none() || chunks[1].op != Op::OpCheckSig {
143 return Err(ScriptError::InvalidScript(
144 "PushDrop::decode(before): expected <pubkey> OP_CHECKSIG at start".into(),
145 ));
146 }
147
148 let mut fields = Vec::new();
149 for i in 2..chunks.len() {
150 let next_is_drop = chunks
152 .get(i + 1)
153 .is_some_and(|next| next.op == Op::OpDrop || next.op == Op::Op2Drop);
154
155 if chunks[i].op == Op::OpDrop || chunks[i].op == Op::Op2Drop {
157 break;
158 }
159
160 if let Some(ref data) = chunks[i].data {
161 fields.push(data.clone());
162 }
163
164 if next_is_drop {
165 break;
166 }
167 }
168
169 Ok(fields)
170 }
171
172 fn decode_after(chunks: &[ScriptChunk]) -> Result<Vec<Vec<u8>>, ScriptError> {
174 let last = &chunks[chunks.len() - 1];
175 if last.op != Op::OpCheckSig {
176 return Err(ScriptError::InvalidScript(
177 "PushDrop::decode(after): last opcode must be OP_CHECKSIG".into(),
178 ));
179 }
180
181 if chunks[chunks.len() - 2].data.is_none() {
182 return Err(ScriptError::InvalidScript(
183 "PushDrop::decode(after): expected pubkey before OP_CHECKSIG".into(),
184 ));
185 }
186
187 let mut drop_field_count = 0usize;
189 let mut pos = chunks.len() - 3;
190 loop {
191 let chunk = &chunks[pos];
192 if chunk.op == Op::Op2Drop {
193 drop_field_count += 2;
194 } else if chunk.op == Op::OpDrop {
195 drop_field_count += 1;
196 } else {
197 break;
198 }
199 if pos == 0 {
200 break;
201 }
202 pos -= 1;
203 }
204
205 if drop_field_count == 0 {
206 return Err(ScriptError::InvalidScript(
207 "PushDrop::decode(after): no OP_DROP/OP_2DROP found".into(),
208 ));
209 }
210
211 let mut fields = Vec::with_capacity(drop_field_count);
212 for chunk in &chunks[0..drop_field_count] {
213 let data = chunk.data.as_ref().ok_or_else(|| {
214 ScriptError::InvalidScript(
215 "PushDrop::decode(after): expected data push for field".into(),
216 )
217 })?;
218 fields.push(data.clone());
219 }
220
221 Ok(fields)
222 }
223
224 fn make_data_push(data: &[u8]) -> ScriptChunk {
226 let len = data.len();
227 if len < 0x4c {
228 ScriptChunk::new_raw(len as u8, Some(data.to_vec()))
229 } else if len < 256 {
230 ScriptChunk::new_raw(Op::OpPushData1.to_byte(), Some(data.to_vec()))
231 } else if len < 65536 {
232 ScriptChunk::new_raw(Op::OpPushData2.to_byte(), Some(data.to_vec()))
233 } else {
234 ScriptChunk::new_raw(Op::OpPushData4.to_byte(), Some(data.to_vec()))
235 }
236 }
237}
238
239impl ScriptTemplateLock for PushDrop {
240 fn lock(&self) -> Result<LockingScript, ScriptError> {
242 let key = self.private_key.as_ref().ok_or_else(|| {
243 ScriptError::InvalidScript(
244 "PushDrop: need private key to derive pubkey for lock".into(),
245 )
246 })?;
247
248 if self.fields.is_empty() {
249 return Err(ScriptError::InvalidScript(
250 "PushDrop: at least one data field required".into(),
251 ));
252 }
253
254 let pubkey = key.to_public_key();
255 let pubkey_bytes = pubkey.to_der();
256
257 let mut lock_chunks = vec![
258 ScriptChunk::new_raw(pubkey_bytes.len() as u8, Some(pubkey_bytes)),
259 ScriptChunk::new_opcode(Op::OpCheckSig),
260 ];
261
262 let mut field_chunks = Vec::new();
263 for field in &self.fields {
264 field_chunks.push(Self::make_data_push(field));
265 }
266
267 let num_fields = self.fields.len();
268 for _ in 0..num_fields / 2 {
269 field_chunks.push(ScriptChunk::new_opcode(Op::Op2Drop));
270 }
271 for _ in 0..num_fields % 2 {
272 field_chunks.push(ScriptChunk::new_opcode(Op::OpDrop));
273 }
274
275 let chunks = match self.lock_position {
276 LockPosition::Before => {
277 lock_chunks.extend(field_chunks);
278 lock_chunks
279 }
280 LockPosition::After => {
281 field_chunks.extend(lock_chunks);
282 field_chunks
283 }
284 };
285
286 Ok(LockingScript::from_script(Script::from_chunks(chunks)))
287 }
288}
289
290impl ScriptTemplateUnlock for PushDrop {
291 fn sign(&self, preimage: &[u8]) -> Result<UnlockingScript, ScriptError> {
292 self.unlock(preimage)
293 }
294
295 fn estimate_length(&self) -> Result<usize, ScriptError> {
296 Ok(self.estimate_unlock_length())
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
309 fn test_before_lock_one_field() {
310 let key = PrivateKey::from_hex("1").unwrap();
311 let data = vec![0xca, 0xfe, 0xba, 0xbe];
312 let pd = PushDrop::new(vec![data.clone()], key);
313 let lock_script = pd.lock().unwrap();
314 let chunks = lock_script.chunks();
315
316 assert_eq!(chunks.len(), 4);
318 assert_eq!(chunks[0].data.as_ref().unwrap().len(), 33); assert_eq!(chunks[1].op, Op::OpCheckSig);
320 assert_eq!(chunks[2].data.as_ref().unwrap(), &data);
321 assert_eq!(chunks[3].op, Op::OpDrop);
322 }
323
324 #[test]
325 fn test_before_lock_three_fields() {
326 let key = PrivateKey::from_hex("1").unwrap();
327 let fields = vec![vec![0x01], vec![0x02], vec![0x03]];
328 let pd = PushDrop::new(fields.clone(), key);
329 let lock_script = pd.lock().unwrap();
330 let chunks = lock_script.chunks();
331
332 assert_eq!(chunks.len(), 7);
334 assert_eq!(chunks[0].data.as_ref().unwrap().len(), 33);
335 assert_eq!(chunks[1].op, Op::OpCheckSig);
336 assert_eq!(chunks[2].data.as_ref().unwrap(), &fields[0]);
337 assert_eq!(chunks[3].data.as_ref().unwrap(), &fields[1]);
338 assert_eq!(chunks[4].data.as_ref().unwrap(), &fields[2]);
339 assert_eq!(chunks[5].op, Op::Op2Drop);
340 assert_eq!(chunks[6].op, Op::OpDrop);
341 }
342
343 #[test]
344 fn test_before_decode_roundtrip() {
345 let key = PrivateKey::from_hex("1").unwrap();
346 let fields = vec![
347 b"SLAP".to_vec(),
348 vec![0x02, 0x79],
349 b"https://example.com".to_vec(),
350 b"ls_ship".to_vec(),
351 ];
352 let pd = PushDrop::new(fields.clone(), key);
353 let lock_script = pd.lock().unwrap();
354
355 let decoded = PushDrop::decode(&lock_script).unwrap();
356 assert_eq!(decoded.fields, fields);
357 assert_eq!(decoded.lock_position, LockPosition::Before);
358 }
359
360 #[test]
365 fn test_after_lock_one_field() {
366 let key = PrivateKey::from_hex("1").unwrap();
367 let data = vec![0xca, 0xfe];
368 let pd = PushDrop::new(vec![data.clone()], key).with_lock_position(LockPosition::After);
369 let lock_script = pd.lock().unwrap();
370 let chunks = lock_script.chunks();
371
372 assert_eq!(chunks.len(), 4);
374 assert_eq!(chunks[0].data.as_ref().unwrap(), &data);
375 assert_eq!(chunks[1].op, Op::OpDrop);
376 assert_eq!(chunks[2].data.as_ref().unwrap().len(), 33);
377 assert_eq!(chunks[3].op, Op::OpCheckSig);
378 }
379
380 #[test]
381 fn test_after_decode_roundtrip() {
382 let key = PrivateKey::from_hex("1").unwrap();
383 let fields = vec![vec![0x01, 0x02], vec![0x03, 0x04]];
384 let pd = PushDrop::new(fields.clone(), key).with_lock_position(LockPosition::After);
385 let lock_script = pd.lock().unwrap();
386
387 let decoded = PushDrop::decode_with_position(&lock_script, LockPosition::After).unwrap();
388 assert_eq!(decoded.fields, fields);
389 }
390
391 #[test]
396 fn test_unlock_produces_signature() {
397 let key = PrivateKey::from_hex("1").unwrap();
398 let pd = PushDrop::new(vec![vec![0xaa]], key);
399 let unlock_script = pd.unlock(b"test preimage").unwrap();
400 assert_eq!(unlock_script.chunks().len(), 1);
401 let sig_data = unlock_script.chunks()[0].data.as_ref().unwrap();
402 assert!(sig_data.len() >= 70 && sig_data.len() <= 74);
403 }
404
405 #[test]
406 fn test_lock_no_key_errors() {
407 let pd = PushDrop::lock_only(vec![vec![0x01]]);
408 assert!(pd.lock().is_err());
409 }
410
411 #[test]
412 fn test_lock_no_fields_errors() {
413 let key = PrivateKey::from_hex("1").unwrap();
414 let pd = PushDrop::new(vec![], key);
415 assert!(pd.lock().is_err());
416 }
417
418 #[test]
419 fn test_decode_non_pushdrop_errors() {
420 let script = LockingScript::from_binary(&[0x76, 0xa9, 0x14]);
421 assert!(PushDrop::decode(&script).is_err());
422 }
423}