Skip to main content

keymaster_multisig/
arbitrated_pool.rs

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}