1use crate::error::{MultisigError, Result};
2use crate::types::{
3 encode_varint, PrivateKey, PublicKey, Transaction, TransactionInput, TransactionOutput, Utxo,
4};
5use k256::ecdsa::{
6 signature::hazmat::{PrehashSigner, PrehashVerifier},
7 Signature as EcdsaSignature, SigningKey, VerifyingKey,
8};
9use k256::elliptic_curve::sec1::ToEncodedPoint;
10use ripemd::Ripemd160;
11use serde::{Deserialize, Serialize};
12use sha2::{Digest, Sha256};
13
14const SIGHASH_ALL_FORKID: u8 = 0x41;
15const OP_CHECKMULTISIG: u8 = 0xae;
16
17#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
18pub struct ArbitratedPoolRoles {
19 pub buyer: PublicKey,
20 pub seller: PublicKey,
21 pub arbiter: PublicKey,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
25pub struct ArbitratedPoolStateInput {
26 pub protocol: String,
27 pub version: u32,
28 pub previous_state: Transaction,
29 pub previous_source_output: TransactionOutput,
30 pub sequence: u32,
31 pub lock_time: Option<u32>,
32 pub buyer_amount: Option<u64>,
33 pub seller_amount: u64,
34 pub arbiter_amount: u64,
35 pub pool_amount: u64,
36 pub roles: ArbitratedPoolRoles,
37 pub fee_rate: u64,
38 pub payment_proof: Option<Vec<u8>>,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
42pub struct FundingTxResult {
43 pub tx: Transaction,
44 pub pool_amount: u64,
45 pub pool_output_index: u32,
46 pub fee: u64,
47}
48
49fn hash256(bytes: &[u8]) -> [u8; 32] {
50 let first = Sha256::digest(bytes);
51 let second = Sha256::digest(first);
52 second.into()
53}
54
55fn hash160(bytes: &[u8]) -> [u8; 20] {
56 let sha = Sha256::digest(bytes);
57 Ripemd160::digest(sha).into()
58}
59
60fn push_data(data: &[u8]) -> Vec<u8> {
61 let mut result = Vec::new();
62 if data.len() < 76 {
63 result.push(data.len() as u8);
64 } else if data.len() <= 0xff {
65 result.extend([0x4c, data.len() as u8]);
66 } else if data.len() <= 0xffff {
67 result.push(0x4d);
68 result.extend_from_slice(&(data.len() as u16).to_le_bytes());
69 } else {
70 result.push(0x4e);
71 result.extend_from_slice(&(data.len() as u32).to_le_bytes());
72 }
73 result.extend(data);
74 result
75}
76
77fn p2pkh(key: &PublicKey) -> Result<Vec<u8>> {
78 validate_public_key(key)?;
79 let hash = hash160(&key.key);
80 let mut script = vec![0x76, 0xa9, 0x14];
81 script.extend(hash);
82 script.extend([0x88, 0xac]);
83 Ok(script)
84}
85
86fn validate_public_key(key: &PublicKey) -> Result<()> {
87 if key.key.len() != 33
88 || (key.key[0] != 0x02 && key.key[0] != 0x03)
89 || VerifyingKey::from_sec1_bytes(&key.key).is_err()
90 {
91 return Err(MultisigError::InvalidPublicKeys);
92 }
93 Ok(())
94}
95
96fn validate_roles(roles: &ArbitratedPoolRoles) -> Result<()> {
97 validate_public_key(&roles.buyer)?;
98 validate_public_key(&roles.seller)?;
99 validate_public_key(&roles.arbiter)?;
100 if roles.buyer == roles.seller || roles.buyer == roles.arbiter || roles.seller == roles.arbiter
101 {
102 return Err(MultisigError::TransactionError(
103 "Buyer, seller and arbiter public keys must be different".to_string(),
104 ));
105 }
106 Ok(())
107}
108
109pub fn build_arbitrated_pool_lock(roles: &ArbitratedPoolRoles) -> Result<Vec<u8>> {
110 validate_roles(roles)?;
111 let mut script = vec![0x52];
112 for key in [&roles.buyer, &roles.seller, &roles.arbiter] {
113 script.extend(push_data(&key.key));
114 }
115 script.extend([0x53, OP_CHECKMULTISIG]);
116 Ok(script)
117}
118
119fn output_scripts(roles: &ArbitratedPoolRoles) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
120 Ok((
121 p2pkh(&roles.buyer)?,
122 p2pkh(&roles.seller)?,
123 p2pkh(&roles.arbiter)?,
124 ))
125}
126
127fn valid_payment_proof(script: &[u8]) -> bool {
128 if script.len() < 3 || script[0] != 0 || script[1] != 0x6a {
129 return false;
130 }
131 let (length, offset) = match script[2] {
132 1..=75 => (script[2] as usize, 3usize),
133 0x4c if script.len() >= 4 => (script[3] as usize, 4usize),
134 0x4d if script.len() >= 5 => (u16::from_le_bytes([script[3], script[4]]) as usize, 5usize),
135 0x4e if script.len() >= 7 => (
136 u32::from_le_bytes([script[3], script[4], script[5], script[6]]) as usize,
137 7usize,
138 ),
139 _ => return false,
140 };
141 length > 0 && offset.checked_add(length) == Some(script.len())
142}
143
144fn validate_state_outputs(tx: &Transaction, roles: &ArbitratedPoolRoles) -> Result<()> {
145 let (buyer, seller, arbiter) = output_scripts(roles)?;
146 if tx.outputs.len() != 3 && tx.outputs.len() != 4 {
147 return Err(MultisigError::TransactionError(
148 "Arbitrated pool state must have exactly three or four outputs".to_string(),
149 ));
150 }
151 for (index, expected) in [buyer, seller, arbiter].iter().enumerate() {
152 if tx.outputs[index].locking_script != *expected {
153 return Err(MultisigError::TransactionError(format!(
154 "Arbitrated pool output {index} does not match its role"
155 )));
156 }
157 }
158 if tx.outputs.len() == 4
159 && (tx.outputs[3].satoshis != 0 || !valid_payment_proof(&tx.outputs[3].locking_script))
160 {
161 return Err(MultisigError::TransactionError(
162 "Invalid payment proof output".to_string(),
163 ));
164 }
165 Ok(())
166}
167
168fn hash_prevouts(tx: &Transaction) -> Result<[u8; 32]> {
169 let mut prevouts = Vec::new();
170 for input in &tx.inputs {
171 let mut txid = hex::decode(&input.source_txid)
172 .map_err(|_| MultisigError::TransactionError("Invalid source txid".to_string()))?;
173 if txid.len() != 32 {
174 return Err(MultisigError::TransactionError(
175 "Invalid source txid length".to_string(),
176 ));
177 }
178 txid.reverse();
179 prevouts.extend(txid);
180 prevouts.extend_from_slice(&input.source_output_index.to_le_bytes());
181 }
182 Ok(hash256(&prevouts))
183}
184
185fn hash_sequences(tx: &Transaction) -> [u8; 32] {
186 let mut sequences = Vec::new();
187 for input in &tx.inputs {
188 sequences.extend_from_slice(&input.sequence.to_le_bytes());
189 }
190 hash256(&sequences)
191}
192
193fn hash_outputs(tx: &Transaction) -> [u8; 32] {
194 let mut outputs = Vec::new();
195 for output in &tx.outputs {
196 outputs.extend_from_slice(&output.satoshis.to_le_bytes());
197 outputs.extend(encode_varint(output.locking_script.len() as u64));
198 outputs.extend(&output.locking_script);
199 }
200 hash256(&outputs)
201}
202
203fn signature_hash(
204 tx: &Transaction,
205 input_index: usize,
206 source: &TransactionOutput,
207) -> Result<[u8; 32]> {
208 if input_index >= tx.inputs.len() {
209 return Err(MultisigError::TransactionError(
210 "Input index out of bounds".to_string(),
211 ));
212 }
213 let input = &tx.inputs[input_index];
214 let mut outpoint_txid = hex::decode(&input.source_txid)
215 .map_err(|_| MultisigError::TransactionError("Invalid source txid".to_string()))?;
216 if outpoint_txid.len() != 32 {
217 return Err(MultisigError::TransactionError(
218 "Invalid source txid length".to_string(),
219 ));
220 }
221 outpoint_txid.reverse();
222 let mut preimage = Vec::new();
223 preimage.extend_from_slice(&tx.version.to_le_bytes());
224 preimage.extend(hash_prevouts(tx)?);
225 preimage.extend(hash_sequences(tx));
226 preimage.extend(outpoint_txid);
227 preimage.extend_from_slice(&input.source_output_index.to_le_bytes());
228 preimage.extend(encode_varint(source.locking_script.len() as u64));
229 preimage.extend(&source.locking_script);
230 preimage.extend_from_slice(&source.satoshis.to_le_bytes());
231 preimage.extend_from_slice(&input.sequence.to_le_bytes());
232 preimage.extend(hash_outputs(tx));
233 preimage.extend_from_slice(&tx.lock_time.to_le_bytes());
234 preimage.extend_from_slice(&(SIGHASH_ALL_FORKID as u32).to_le_bytes());
235 Ok(hash256(&preimage))
236}
237
238fn private_key_public(key: &PrivateKey) -> Result<PublicKey> {
239 let secret =
240 k256::SecretKey::from_slice(&key.key).map_err(|_| MultisigError::InvalidPrivateKey)?;
241 Ok(PublicKey {
242 key: secret
243 .public_key()
244 .to_encoded_point(true)
245 .as_bytes()
246 .to_vec(),
247 })
248}
249
250fn sign_hash(hash: &[u8; 32], key: &PrivateKey) -> Result<Vec<u8>> {
251 let secret =
252 k256::SecretKey::from_slice(&key.key).map_err(|_| MultisigError::InvalidPrivateKey)?;
253 let signing_key = SigningKey::from(secret);
254 let mut signature: EcdsaSignature = signing_key
255 .sign_prehash(hash)
256 .map_err(|_| MultisigError::SignatureError("Failed to create signature".to_string()))?;
257 if let Some(normalized) = signature.normalize_s() {
258 signature = normalized;
259 }
260 let mut result = signature.to_der().as_bytes().to_vec();
261 result.push(SIGHASH_ALL_FORKID);
262 Ok(result)
263}
264
265fn state_source(
266 state: &Transaction,
267 pool_amount: u64,
268 roles: &ArbitratedPoolRoles,
269) -> Result<TransactionOutput> {
270 let source = state
271 .inputs
272 .first()
273 .and_then(|input| input.source_output.clone())
274 .ok_or_else(|| {
275 MultisigError::TransactionError("Previous state source output is required".to_string())
276 })?;
277 let lock = build_arbitrated_pool_lock(roles)?;
278 if source.satoshis != pool_amount || source.locking_script != lock {
279 return Err(MultisigError::TransactionError(
280 "State source output does not match configured pool".to_string(),
281 ));
282 }
283 Ok(source)
284}
285
286fn validate_unsigned_state(state: &Transaction, roles: &ArbitratedPoolRoles) -> Result<()> {
287 validate_roles(roles)?;
288 if state.inputs.len() != 1 {
289 return Err(MultisigError::TransactionError(
290 "Arbitrated pool state must have exactly one input".to_string(),
291 ));
292 }
293 if !state.inputs[0].unlocking_script.is_empty() {
294 return Err(MultisigError::TransactionError(
295 "State must have an empty unlocking script".to_string(),
296 ));
297 }
298 validate_state_outputs(state, roles)
299}
300
301pub fn build_arbitrated_pool_state(input: ArbitratedPoolStateInput) -> Result<Transaction> {
302 if input.protocol != crate::version::PROTOCOL
303 || input.version != crate::version::PROTOCOL_VERSION
304 {
305 return Err(MultisigError::TransactionError(format!(
306 "Unsupported pool protocol: expected {} v{}",
307 crate::version::PROTOCOL,
308 crate::version::PROTOCOL_VERSION
309 )));
310 }
311 if input.pool_amount == 0 {
312 return Err(MultisigError::TransactionError(
313 "Pool amount must be positive".to_string(),
314 ));
315 }
316 if input
317 .seller_amount
318 .checked_add(input.arbiter_amount)
319 .is_none()
320 {
321 return Err(MultisigError::TransactionError(
322 "Allocated amount overflow".to_string(),
323 ));
324 }
325 let allocated = input.seller_amount + input.arbiter_amount;
326 if allocated > input.pool_amount {
327 return Err(MultisigError::TransactionError(
328 "Allocated amount exceeds pool amount".to_string(),
329 ));
330 }
331 if input.previous_state.inputs.len() != 1 {
332 return Err(MultisigError::TransactionError(
333 "Arbitrated pool state must have exactly one input".to_string(),
334 ));
335 }
336 if input.sequence <= input.previous_state.inputs[0].sequence {
337 return Err(MultisigError::TransactionError(
338 "Payment sequence must increase".to_string(),
339 ));
340 }
341 validate_state_outputs(&input.previous_state, &input.roles)?;
342 let configured_lock = build_arbitrated_pool_lock(&input.roles)?;
343 if input.previous_source_output.satoshis != input.pool_amount
344 || input.previous_source_output.locking_script != configured_lock
345 {
346 return Err(MultisigError::TransactionError(
347 "Previous state source output does not match configured pool".to_string(),
348 ));
349 }
350 let source = input.previous_state.inputs[0]
351 .source_output
352 .clone()
353 .unwrap_or_else(|| input.previous_source_output.clone());
354 if source != input.previous_source_output {
355 return Err(MultisigError::TransactionError(
356 "Previous state source output does not match configured pool".to_string(),
357 ));
358 }
359 let (buyer, seller, arbiter) = output_scripts(&input.roles)?;
360 let mut state = input.previous_state.clone();
361 state.inputs[0].source_output = Some(source);
362 state.outputs[0] = TransactionOutput {
363 satoshis: input.pool_amount - allocated,
364 locking_script: buyer,
365 };
366 state.outputs[1] = TransactionOutput {
367 satoshis: input.seller_amount,
368 locking_script: seller,
369 };
370 state.outputs[2] = TransactionOutput {
371 satoshis: input.arbiter_amount,
372 locking_script: arbiter,
373 };
374 state.inputs[0].sequence = input.sequence;
375 if let Some(lock_time) = input.lock_time {
376 state.lock_time = lock_time;
377 }
378 if let Some(proof) = input.payment_proof.filter(|value| !value.is_empty()) {
379 let mut script = vec![0, 0x6a];
380 script.extend(push_data(&proof));
381 let output = TransactionOutput {
382 satoshis: 0,
383 locking_script: script,
384 };
385 if state.outputs.len() == 4 {
386 state.outputs[3] = output;
387 } else {
388 state.outputs.push(output);
389 }
390 }
391 state.inputs[0].unlocking_script = fake_unlocking_script();
392 let size = state.serialize()?.len() as u64;
393 let fee = if input.fee_rate == 0 {
394 0
395 } else {
396 size.checked_mul(input.fee_rate)
397 .and_then(|value| value.checked_add(999))
398 .ok_or_else(|| {
399 MultisigError::TransactionError("Transaction fee overflow".to_string())
400 })?
401 / 1000
402 };
403 if fee > state.outputs[0].satoshis {
404 return Err(MultisigError::TransactionError(
405 "Buyer balance is insufficient for fee".to_string(),
406 ));
407 }
408 state.outputs[0].satoshis -= fee;
409 if input
410 .buyer_amount
411 .is_some_and(|amount| amount != state.outputs[0].satoshis)
412 {
413 return Err(MultisigError::TransactionError(
414 "Buyer amount does not match canonical fee".to_string(),
415 ));
416 }
417 state.inputs[0].unlocking_script.clear();
418 Ok(state)
419}
420
421pub fn build_arbitrated_pool_opening_state(
422 funding_tx: &Transaction,
423 pool_amount: u64,
424 roles: ArbitratedPoolRoles,
425 lock_time: u32,
426 fee_rate: u64,
427) -> Result<Transaction> {
428 if funding_tx.outputs.is_empty() {
429 return Err(MultisigError::TransactionError(
430 "Funding transaction is required".to_string(),
431 ));
432 }
433 let lock = build_arbitrated_pool_lock(&roles)?;
434 if funding_tx.outputs[0].satoshis != pool_amount || funding_tx.outputs[0].locking_script != lock
435 {
436 return Err(MultisigError::TransactionError(
437 "Funding pool output does not match configured pool".to_string(),
438 ));
439 }
440 let (buyer, seller, arbiter) = output_scripts(&roles)?;
441 let source = TransactionOutput {
442 satoshis: pool_amount,
443 locking_script: lock,
444 };
445 let previous = Transaction {
446 version: 1,
447 inputs: vec![TransactionInput {
448 source_txid: funding_tx.txid()?,
449 source_output_index: 0,
450 unlocking_script: Vec::new(),
451 sequence: 1,
452 source_output: Some(source.clone()),
453 }],
454 outputs: vec![
455 TransactionOutput {
456 satoshis: pool_amount,
457 locking_script: buyer,
458 },
459 TransactionOutput {
460 satoshis: 0,
461 locking_script: seller,
462 },
463 TransactionOutput {
464 satoshis: 0,
465 locking_script: arbiter,
466 },
467 ],
468 lock_time,
469 };
470 build_arbitrated_pool_state(ArbitratedPoolStateInput {
471 protocol: crate::version::PROTOCOL.to_string(),
472 version: crate::version::PROTOCOL_VERSION,
473 previous_state: previous.clone(),
474 previous_source_output: source,
475 sequence: 2,
476 lock_time: Some(lock_time),
477 buyer_amount: None,
478 seller_amount: 0,
479 arbiter_amount: 0,
480 pool_amount,
481 roles,
482 fee_rate,
483 payment_proof: None,
484 })
485}
486
487pub fn build_arbitrated_pool_final_state(input: ArbitratedPoolStateInput) -> Result<Transaction> {
488 build_arbitrated_pool_state(input)
489}
490
491pub fn sign_arbitrated_pool_as_buyer(
492 state: &Transaction,
493 pool_amount: u64,
494 roles: &ArbitratedPoolRoles,
495 key: &PrivateKey,
496) -> Result<Vec<u8>> {
497 sign_role(state, pool_amount, roles, key, &roles.buyer)
498}
499pub fn sign_arbitrated_pool_as_seller(
500 state: &Transaction,
501 pool_amount: u64,
502 roles: &ArbitratedPoolRoles,
503 key: &PrivateKey,
504) -> Result<Vec<u8>> {
505 sign_role(state, pool_amount, roles, key, &roles.seller)
506}
507pub fn sign_arbitrated_pool_as_arbiter(
508 state: &Transaction,
509 pool_amount: u64,
510 roles: &ArbitratedPoolRoles,
511 key: &PrivateKey,
512) -> Result<Vec<u8>> {
513 sign_role(state, pool_amount, roles, key, &roles.arbiter)
514}
515
516fn sign_role(
517 state: &Transaction,
518 pool_amount: u64,
519 roles: &ArbitratedPoolRoles,
520 key: &PrivateKey,
521 expected: &PublicKey,
522) -> Result<Vec<u8>> {
523 validate_unsigned_state(state, roles)?;
524 if private_key_public(key)? != *expected {
525 return Err(MultisigError::TransactionError(
526 "Private key does not match declared role".to_string(),
527 ));
528 }
529 let source = state_source(state, pool_amount, roles)?;
530 sign_hash(&signature_hash(state, 0, &source)?, key)
531}
532
533fn verify_role(
534 state: &Transaction,
535 pool_amount: u64,
536 roles: &ArbitratedPoolRoles,
537 signature: &[u8],
538 expected: &PublicKey,
539) -> Result<bool> {
540 validate_unsigned_state(state, roles)?;
541 if signature.len() < 9 || *signature.last().unwrap() != SIGHASH_ALL_FORKID {
542 return Err(MultisigError::SignatureError(
543 "Invalid signature".to_string(),
544 ));
545 }
546 let source = state_source(state, pool_amount, roles)?;
547 let key = VerifyingKey::from_sec1_bytes(&expected.key)
548 .map_err(|_| MultisigError::InvalidPublicKeys)?;
549 let parsed = EcdsaSignature::from_der(&signature[..signature.len() - 1])
550 .map_err(|_| MultisigError::SignatureError("Invalid DER signature".to_string()))?;
551 key.verify_prehash(&signature_hash(state, 0, &source)?, &parsed)
552 .map_err(|_| MultisigError::SignatureError("Signature verification failed".to_string()))?;
553 Ok(true)
554}
555
556pub fn verify_arbitrated_pool_buyer_signature(
557 state: &Transaction,
558 pool_amount: u64,
559 roles: &ArbitratedPoolRoles,
560 signature: &[u8],
561) -> Result<bool> {
562 verify_role(state, pool_amount, roles, signature, &roles.buyer)
563}
564pub fn verify_arbitrated_pool_seller_signature(
565 state: &Transaction,
566 pool_amount: u64,
567 roles: &ArbitratedPoolRoles,
568 signature: &[u8],
569) -> Result<bool> {
570 verify_role(state, pool_amount, roles, signature, &roles.seller)
571}
572pub fn verify_arbitrated_pool_arbiter_signature(
573 state: &Transaction,
574 pool_amount: u64,
575 roles: &ArbitratedPoolRoles,
576 signature: &[u8],
577) -> Result<bool> {
578 verify_role(state, pool_amount, roles, signature, &roles.arbiter)
579}
580
581fn merge(
582 state: &Transaction,
583 pool_amount: u64,
584 roles: &ArbitratedPoolRoles,
585 first: &[u8],
586 second: &[u8],
587 first_key: &PublicKey,
588 second_key: &PublicKey,
589) -> Result<Transaction> {
590 validate_unsigned_state(state, roles)?;
591 if first == second {
592 return Err(MultisigError::SignatureError(
593 "Duplicate signatures are not permitted".to_string(),
594 ));
595 }
596 verify_role(state, pool_amount, roles, first, first_key)?;
597 verify_role(state, pool_amount, roles, second, second_key)?;
598 let mut result = state.clone();
599 let mut unlocking = vec![0];
600 unlocking.extend(push_data(first));
601 unlocking.extend(push_data(second));
602 result.inputs[0].unlocking_script = unlocking;
603 Ok(result)
604}
605
606pub fn merge_arbitrated_pool_buyer_seller_signatures(
607 state: &Transaction,
608 pool_amount: u64,
609 roles: &ArbitratedPoolRoles,
610 buyer: &[u8],
611 seller: &[u8],
612) -> Result<Transaction> {
613 merge(
614 state,
615 pool_amount,
616 roles,
617 buyer,
618 seller,
619 &roles.buyer,
620 &roles.seller,
621 )
622}
623pub fn merge_arbitrated_pool_buyer_arbiter_signatures(
624 state: &Transaction,
625 pool_amount: u64,
626 roles: &ArbitratedPoolRoles,
627 buyer: &[u8],
628 arbiter: &[u8],
629) -> Result<Transaction> {
630 merge(
631 state,
632 pool_amount,
633 roles,
634 buyer,
635 arbiter,
636 &roles.buyer,
637 &roles.arbiter,
638 )
639}
640pub fn merge_arbitrated_pool_seller_arbiter_signatures(
641 state: &Transaction,
642 pool_amount: u64,
643 roles: &ArbitratedPoolRoles,
644 seller: &[u8],
645 arbiter: &[u8],
646) -> Result<Transaction> {
647 merge(
648 state,
649 pool_amount,
650 roles,
651 seller,
652 arbiter,
653 &roles.seller,
654 &roles.arbiter,
655 )
656}
657
658fn fake_unlocking_script() -> Vec<u8> {
659 let mut script = vec![0];
660 script.extend(push_data(&[0; 73]));
661 script.extend(push_data(&[0; 73]));
662 script
663}
664
665#[cfg(test)]
666#[allow(clippy::items_after_test_module)]
667mod tests {
668 use super::*;
669 use crate::{PROTOCOL, PROTOCOL_VERSION};
670
671 fn setup() -> (
672 PrivateKey,
673 PrivateKey,
674 PrivateKey,
675 ArbitratedPoolRoles,
676 Transaction,
677 ) {
678 let buyer = PrivateKey::new(vec![1; 32]);
679 let seller = PrivateKey::new(vec![2; 32]);
680 let arbiter = PrivateKey::new(vec![3; 32]);
681 let roles = ArbitratedPoolRoles {
682 buyer: private_key_public(&buyer).unwrap(),
683 seller: private_key_public(&seller).unwrap(),
684 arbiter: private_key_public(&arbiter).unwrap(),
685 };
686 let funding = build_arbitrated_pool_funding_tx(
687 &[Utxo {
688 txid: "bb".repeat(32),
689 vout: 0,
690 satoshis: 30000,
691 }],
692 29000,
693 &buyer,
694 &roles,
695 0,
696 )
697 .unwrap();
698 let opening = build_arbitrated_pool_opening_state(
699 &funding.tx,
700 funding.pool_amount,
701 roles.clone(),
702 800000,
703 0,
704 )
705 .unwrap();
706 (buyer, seller, arbiter, roles, opening)
707 }
708
709 fn fixture_paid_state() -> (Transaction, ArbitratedPoolRoles) {
710 let buyer = PrivateKey::new(
711 hex::decode("a682814ac246ca65543197e593aa3b2633b891959c183416f54e2c63a8de1d8c")
712 .unwrap(),
713 );
714 let seller = PrivateKey::new(
715 hex::decode("903b1b2c396f17203fa83444d72bf5c666119d9d681eb715520f99ae6f92322c")
716 .unwrap(),
717 );
718 let arbiter = PrivateKey::new(
719 hex::decode("a2d2ca4c19e3c560792ca751842c29b9da94be09f712a7f9ba7c66e64a354829")
720 .unwrap(),
721 );
722 let roles = ArbitratedPoolRoles {
723 buyer: private_key_public(&buyer).unwrap(),
724 seller: private_key_public(&seller).unwrap(),
725 arbiter: private_key_public(&arbiter).unwrap(),
726 };
727 let mut state = Transaction::from_hex("01000000013d4fa11aefde8f614b8d99c0e4d840c09f55fc8b0a2611befc10e5328cc847d2000000000004000000031b700000000000001976a914a8d0cb37061679d0523314d882d81b989254df7b88acc8000000000000001976a9147e06a09c32ea06e80745cbfae60036968b64238888ac64000000000000001976a914789d07c284ff3f6c41633e2031b375e57434759688ac00350c00").unwrap();
728 state.inputs[0].source_output = Some(TransactionOutput::new(
729 29000,
730 build_arbitrated_pool_lock(&roles).unwrap(),
731 ));
732 (state, roles)
733 }
734
735 #[test]
736 fn v4_raw_hex_roundtrip_and_bip143_component_vector_are_stable() {
737 let (state, roles) = fixture_paid_state();
738 let raw = state.to_hex().unwrap();
739 assert_eq!(Transaction::from_hex(&raw).unwrap().to_hex().unwrap(), raw);
740 let source = state.inputs[0].source_output.as_ref().unwrap();
741 assert_eq!(
742 hex::encode(hash_prevouts(&state).unwrap()),
743 "e46a58d1738a4d5c782d6fa0fb0581de503076f15cda7b7c9c8d79ba75d1d2fb"
744 );
745 assert_eq!(
746 hex::encode(hash_sequences(&state)),
747 "a14e2895f7b9e1e7b37f82b38e345462a36edfa6dbce70939cc1bfc6a74ddd5e"
748 );
749 assert_eq!(
750 hex::encode(hash_outputs(&state)),
751 "761562e89b68d7cfee518e90c4cd2f1fce533cac59312184800322e22f11f51f"
752 );
753 assert_eq!(
754 hex::encode(signature_hash(&state, 0, source).unwrap()),
755 "7a1c6a0c0a9f541b2e1523c1f291d27573371fc553078ee450659fc88f198524"
756 );
757 let signature = sign_arbitrated_pool_as_buyer(
758 &state,
759 29000,
760 &roles,
761 &PrivateKey::new(
762 hex::decode("a682814ac246ca65543197e593aa3b2633b891959c183416f54e2c63a8de1d8c")
763 .unwrap(),
764 ),
765 )
766 .unwrap();
767 assert!(verify_arbitrated_pool_buyer_signature(&state, 29000, &roles, &signature).unwrap());
768 }
769
770 #[test]
771 fn v4_fee_zero_and_nonzero_arbiter_amounts_are_exact() {
772 let (buyer, _seller, _arbiter, roles, opening) = setup();
773 assert_eq!(opening.outputs[0].satoshis, 29000);
774 let source = TransactionOutput::new(29000, build_arbitrated_pool_lock(&roles).unwrap());
775 let paid = build_arbitrated_pool_state(ArbitratedPoolStateInput {
776 protocol: PROTOCOL.to_string(),
777 version: PROTOCOL_VERSION,
778 previous_state: opening,
779 previous_source_output: source.clone(),
780 sequence: 3,
781 lock_time: None,
782 buyer_amount: None,
783 seller_amount: 200,
784 arbiter_amount: 100,
785 pool_amount: 29000,
786 roles: roles.clone(),
787 fee_rate: 0,
788 payment_proof: None,
789 })
790 .unwrap();
791 assert_eq!(
792 paid.outputs
793 .iter()
794 .map(|output| output.satoshis)
795 .collect::<Vec<_>>(),
796 vec![28700, 200, 100]
797 );
798 let with_proof = build_arbitrated_pool_state(ArbitratedPoolStateInput {
799 protocol: PROTOCOL.to_string(),
800 version: PROTOCOL_VERSION,
801 previous_state: paid.clone(),
802 previous_source_output: source.clone(),
803 sequence: 4,
804 lock_time: None,
805 buyer_amount: None,
806 seller_amount: 300,
807 arbiter_amount: 200,
808 pool_amount: 29000,
809 roles: roles.clone(),
810 fee_rate: 0,
811 payment_proof: Some(vec![1, 2, 3]),
812 })
813 .unwrap();
814 let preserved = build_arbitrated_pool_state(ArbitratedPoolStateInput {
815 protocol: PROTOCOL.to_string(),
816 version: PROTOCOL_VERSION,
817 previous_state: with_proof.clone(),
818 previous_source_output: source.clone(),
819 sequence: 5,
820 lock_time: None,
821 buyer_amount: None,
822 seller_amount: 300,
823 arbiter_amount: 200,
824 pool_amount: 29000,
825 roles: roles.clone(),
826 fee_rate: 0,
827 payment_proof: None,
828 })
829 .unwrap();
830 let replaced = build_arbitrated_pool_state(ArbitratedPoolStateInput {
831 protocol: PROTOCOL.to_string(),
832 version: PROTOCOL_VERSION,
833 previous_state: with_proof.clone(),
834 previous_source_output: source.clone(),
835 sequence: 5,
836 lock_time: None,
837 buyer_amount: None,
838 seller_amount: 300,
839 arbiter_amount: 200,
840 pool_amount: 29000,
841 roles: roles.clone(),
842 fee_rate: 0,
843 payment_proof: Some(vec![4, 5, 6]),
844 })
845 .unwrap();
846 assert_eq!(preserved.outputs[3], with_proof.outputs[3]);
847 assert_ne!(replaced.outputs[3], with_proof.outputs[3]);
848 let buyer_signature = sign_arbitrated_pool_as_buyer(&paid, 29000, &roles, &buyer).unwrap();
849 let mut changed = paid.clone();
850 changed.outputs[1].satoshis += 1;
851 assert!(
852 verify_arbitrated_pool_buyer_signature(&changed, 29000, &roles, &buyer_signature)
853 .is_err()
854 );
855 }
856
857 #[test]
858 fn v4_sign_verify_and_merge_reject_noncanonical_boundaries() {
859 let (buyer, seller, _arbiter, roles, state) = setup();
860 let signature = sign_arbitrated_pool_as_buyer(&state, 29000, &roles, &buyer).unwrap();
861 let mut multiple_inputs = state.clone();
862 multiple_inputs
863 .inputs
864 .push(multiple_inputs.inputs[0].clone());
865 assert!(sign_arbitrated_pool_as_buyer(&multiple_inputs, 29000, &roles, &buyer).is_err());
866 let mut signed_input = state.clone();
867 signed_input.inputs[0].unlocking_script = vec![0];
868 assert!(
869 verify_arbitrated_pool_buyer_signature(&signed_input, 29000, &roles, &signature)
870 .is_err()
871 );
872 let mut missing_output = state.clone();
873 missing_output.outputs.truncate(2);
874 assert!(
875 verify_arbitrated_pool_buyer_signature(&missing_output, 29000, &roles, &signature)
876 .is_err()
877 );
878 let mut too_many_outputs = state.clone();
879 too_many_outputs
880 .outputs
881 .push(too_many_outputs.outputs[0].clone());
882 too_many_outputs
883 .outputs
884 .push(too_many_outputs.outputs[0].clone());
885 assert!(verify_arbitrated_pool_buyer_signature(
886 &too_many_outputs,
887 29000,
888 &roles,
889 &signature
890 )
891 .is_err());
892 assert!(merge_arbitrated_pool_buyer_seller_signatures(
893 &state, 29000, &roles, &signature, &signature
894 )
895 .is_err());
896 let source = TransactionOutput::new(29000, build_arbitrated_pool_lock(&roles).unwrap());
897 let mut wrong_arbiter = state.clone();
898 wrong_arbiter.outputs[2].locking_script = p2pkh(&roles.seller).unwrap();
899 assert!(
900 verify_arbitrated_pool_buyer_signature(&wrong_arbiter, 29000, &roles, &signature)
901 .is_err()
902 );
903 let mut invalid_proof = state.clone();
904 invalid_proof
905 .outputs
906 .push(TransactionOutput::new(0, vec![0, 0x6a, 1, 1]));
907 invalid_proof.outputs[3].satoshis = 1;
908 assert!(
909 verify_arbitrated_pool_buyer_signature(&invalid_proof, 29000, &roles, &signature)
910 .is_err()
911 );
912 assert!(build_arbitrated_pool_state(ArbitratedPoolStateInput {
913 protocol: PROTOCOL.to_string(),
914 version: PROTOCOL_VERSION,
915 previous_state: state.clone(),
916 previous_source_output: source.clone(),
917 sequence: 3,
918 lock_time: None,
919 buyer_amount: None,
920 seller_amount: 29000,
921 arbiter_amount: 0,
922 pool_amount: 29000,
923 roles: roles.clone(),
924 fee_rate: 1,
925 payment_proof: None,
926 })
927 .is_err());
928 assert!(build_arbitrated_pool_state(ArbitratedPoolStateInput {
929 protocol: PROTOCOL.to_string(),
930 version: PROTOCOL_VERSION,
931 previous_state: state.clone(),
932 previous_source_output: source.clone(),
933 sequence: 3,
934 lock_time: None,
935 buyer_amount: None,
936 seller_amount: 0,
937 arbiter_amount: 0,
938 pool_amount: 29000,
939 roles: roles.clone(),
940 fee_rate: u64::MAX,
941 payment_proof: None,
942 })
943 .is_err());
944 assert!(build_arbitrated_pool_state(ArbitratedPoolStateInput {
945 protocol: PROTOCOL.to_string(),
946 version: PROTOCOL_VERSION,
947 previous_state: state.clone(),
948 previous_source_output: source.clone(),
949 sequence: 3,
950 lock_time: None,
951 buyer_amount: Some(0),
952 seller_amount: 0,
953 arbiter_amount: 0,
954 pool_amount: 29000,
955 roles: roles.clone(),
956 fee_rate: 0,
957 payment_proof: None,
958 })
959 .is_err());
960 assert!(build_arbitrated_pool_state(ArbitratedPoolStateInput {
961 protocol: PROTOCOL.to_string(),
962 version: PROTOCOL_VERSION,
963 previous_state: state,
964 previous_source_output: source,
965 sequence: 3,
966 lock_time: None,
967 buyer_amount: None,
968 seller_amount: 1,
969 arbiter_amount: u64::MAX,
970 pool_amount: 29000,
971 roles: roles.clone(),
972 fee_rate: 0,
973 payment_proof: None,
974 })
975 .is_err());
976 assert_ne!(private_key_public(&seller).unwrap(), roles.buyer);
977 }
978}
979
980pub fn build_arbitrated_pool_funding_tx(
981 utxos: &[Utxo],
982 pool_amount: u64,
983 buyer_private_key: &PrivateKey,
984 roles: &ArbitratedPoolRoles,
985 fee_rate: u64,
986) -> Result<FundingTxResult> {
987 if utxos.is_empty() || pool_amount == 0 {
988 return Err(MultisigError::TransactionError(
989 "Buyer UTXOs and a positive pool amount are required".to_string(),
990 ));
991 }
992 validate_roles(roles)?;
993 if private_key_public(buyer_private_key)? != roles.buyer {
994 return Err(MultisigError::TransactionError(
995 "Private key does not match buyer public key".to_string(),
996 ));
997 }
998 let source_script = p2pkh(&roles.buyer)?;
999 let pool_script = build_arbitrated_pool_lock(roles)?;
1000 let total = utxos.iter().try_fold(0u64, |sum, utxo| {
1001 sum.checked_add(utxo.satoshis).ok_or_else(|| {
1002 MultisigError::TransactionError("Buyer UTXO total overflows".to_string())
1003 })
1004 })?;
1005 if total < pool_amount {
1006 return Err(MultisigError::TransactionError(
1007 "Buyer balance is insufficient for pool amount".to_string(),
1008 ));
1009 }
1010 let mut tx = Transaction {
1011 version: 1,
1012 inputs: utxos
1013 .iter()
1014 .map(|u| TransactionInput {
1015 source_txid: u.txid.clone(),
1016 source_output_index: u.vout,
1017 unlocking_script: Vec::new(),
1018 sequence: 0xffff_ffff,
1019 source_output: Some(TransactionOutput {
1020 satoshis: u.satoshis,
1021 locking_script: source_script.clone(),
1022 }),
1023 })
1024 .collect(),
1025 outputs: vec![
1026 TransactionOutput {
1027 satoshis: pool_amount,
1028 locking_script: pool_script,
1029 },
1030 TransactionOutput {
1031 satoshis: total - pool_amount,
1032 locking_script: source_script.clone(),
1033 },
1034 ],
1035 lock_time: 0,
1036 };
1037 for index in 0..tx.inputs.len() {
1038 let sig = sign_hash(
1039 &signature_hash(&tx, index, tx.inputs[index].source_output.as_ref().unwrap())?,
1040 buyer_private_key,
1041 )?;
1042 let key = private_key_public(buyer_private_key)?;
1043 tx.inputs[index].unlocking_script = push_data(&sig);
1044 tx.inputs[index]
1045 .unlocking_script
1046 .extend(push_data(&key.key));
1047 }
1048 let size = tx.serialize()?.len() as u64;
1049 let fee = if fee_rate == 0 {
1050 0
1051 } else {
1052 size.checked_mul(fee_rate)
1053 .and_then(|v| v.checked_add(999))
1054 .ok_or_else(|| {
1055 MultisigError::TransactionError("Transaction fee overflow".to_string())
1056 })?
1057 / 1000
1058 };
1059 if total
1060 < pool_amount.checked_add(fee).ok_or_else(|| {
1061 MultisigError::TransactionError(
1062 "Buyer balance is insufficient for pool amount and fee".to_string(),
1063 )
1064 })?
1065 {
1066 return Err(MultisigError::TransactionError(
1067 "Buyer balance is insufficient for pool amount and fee".to_string(),
1068 ));
1069 }
1070 tx.outputs[1].satoshis = total - pool_amount - fee;
1071 for index in 0..tx.inputs.len() {
1072 let sig = sign_hash(
1073 &signature_hash(&tx, index, tx.inputs[index].source_output.as_ref().unwrap())?,
1074 buyer_private_key,
1075 )?;
1076 let key = private_key_public(buyer_private_key)?;
1077 tx.inputs[index].unlocking_script = push_data(&sig);
1078 tx.inputs[index]
1079 .unlocking_script
1080 .extend(push_data(&key.key));
1081 }
1082 Ok(FundingTxResult {
1083 tx,
1084 pool_amount,
1085 pool_output_index: 0,
1086 fee,
1087 })
1088}