bsv_rs/script/templates/
p2pk.rs1use crate::error::Error;
40use crate::primitives::bsv::TransactionSignature;
41use crate::primitives::ec::PrivateKey;
42use crate::script::op::*;
43use crate::script::template::{
44 compute_sighash_scope, ScriptTemplate, ScriptTemplateUnlock, SignOutputs, SigningContext,
45};
46use crate::script::{LockingScript, Script, ScriptChunk, UnlockingScript};
47use crate::Result;
48
49#[derive(Debug, Clone, Copy, Default)]
54pub struct P2PK;
55
56impl P2PK {
57 pub fn new() -> Self {
59 Self
60 }
61
62 pub fn unlock(
67 private_key: &PrivateKey,
68 sign_outputs: SignOutputs,
69 anyone_can_pay: bool,
70 ) -> ScriptTemplateUnlock {
71 let key = private_key.clone();
72 let scope = compute_sighash_scope(sign_outputs, anyone_can_pay);
73
74 ScriptTemplateUnlock::new(
75 move |context: &SigningContext| {
76 let sighash = context.compute_sighash(scope)?;
77 let signature = key.sign(&sighash)?;
78 let tx_sig = TransactionSignature::new(signature, scope);
79 let sig_bytes = tx_sig.to_checksig_format();
80
81 let mut script = Script::new();
82 script.write_bin(&sig_bytes);
83
84 Ok(UnlockingScript::from_script(script))
85 },
86 || {
87 74
89 },
90 )
91 }
92
93 pub fn sign_with_sighash(
95 private_key: &PrivateKey,
96 sighash: &[u8; 32],
97 sign_outputs: SignOutputs,
98 anyone_can_pay: bool,
99 ) -> Result<UnlockingScript> {
100 let scope = compute_sighash_scope(sign_outputs, anyone_can_pay);
101 let signature = private_key.sign(sighash)?;
102 let tx_sig = TransactionSignature::new(signature, scope);
103 let sig_bytes = tx_sig.to_checksig_format();
104
105 let mut script = Script::new();
106 script.write_bin(&sig_bytes);
107
108 Ok(UnlockingScript::from_script(script))
109 }
110}
111
112impl ScriptTemplate for P2PK {
113 fn lock(&self, params: &[u8]) -> Result<LockingScript> {
116 if params.len() != 33 && params.len() != 65 {
117 return Err(Error::InvalidDataLength {
118 expected: 33,
119 actual: params.len(),
120 });
121 }
122
123 match params[0] {
125 0x02 | 0x03 if params.len() == 33 => {}
126 0x04 | 0x06 | 0x07 if params.len() == 65 => {}
127 _ => {
128 return Err(Error::InvalidPublicKey(
129 "Invalid public key prefix".to_string(),
130 ));
131 }
132 }
133
134 let chunks = vec![
135 ScriptChunk::new(params.len() as u8, Some(params.to_vec())),
136 ScriptChunk::new_opcode(OP_CHECKSIG),
137 ];
138
139 Ok(LockingScript::from_chunks(chunks))
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_p2pk_lock_compressed() {
149 let private_key = PrivateKey::from_hex(
150 "0000000000000000000000000000000000000000000000000000000000000001",
151 )
152 .unwrap();
153 let pubkey = private_key.public_key().to_compressed();
154
155 let template = P2PK::new();
156 let locking = template.lock(&pubkey).unwrap();
157
158 let chunks = locking.chunks();
160 assert_eq!(chunks.len(), 2);
161 assert_eq!(chunks[0].data.as_ref().unwrap().len(), 33);
162 assert_eq!(chunks[1].op, OP_CHECKSIG);
163
164 assert!(locking.as_script().is_p2pk());
166 }
167
168 #[test]
169 fn test_p2pk_lock_invalid_length() {
170 let template = P2PK::new();
171
172 assert!(template.lock(&[0x02; 20]).is_err());
173 assert!(template.lock(&[0x02; 32]).is_err());
174 assert!(template.lock(&[0x02; 34]).is_err());
175 }
176
177 #[test]
178 fn test_p2pk_lock_invalid_prefix() {
179 let template = P2PK::new();
180
181 let mut bad_key = [0u8; 33];
183 bad_key[0] = 0x05;
184 assert!(template.lock(&bad_key).is_err());
185 }
186
187 #[test]
188 fn test_p2pk_unlock_signature_only() {
189 let private_key = PrivateKey::random();
190 let sighash = [1u8; 32];
191
192 let unlocking =
193 P2PK::sign_with_sighash(&private_key, &sighash, SignOutputs::All, false).unwrap();
194
195 let chunks = unlocking.chunks();
197 assert_eq!(chunks.len(), 1);
198
199 let sig_data = chunks[0].data.as_ref().unwrap();
200 assert!(sig_data.len() >= 70 && sig_data.len() <= 73);
201 assert_eq!(*sig_data.last().unwrap(), 0x41u8); }
203
204 #[test]
205 fn test_p2pk_estimate_length() {
206 let private_key = PrivateKey::random();
207 let unlock = P2PK::unlock(&private_key, SignOutputs::All, false);
208 assert_eq!(unlock.estimate_length(), 74);
209 }
210
211 #[test]
212 fn test_p2pk_asm() {
213 let private_key = PrivateKey::random();
214 let pubkey = private_key.public_key().to_compressed();
215
216 let template = P2PK::new();
217 let locking = template.lock(&pubkey).unwrap();
218
219 let asm = locking.to_asm();
220 assert!(asm.contains("OP_CHECKSIG"));
221 assert!(!asm.contains("OP_DUP")); assert!(!asm.contains("OP_HASH160")); }
224}