@@ -14,7 +14,7 @@ use bdk_esplora::EsploraAsyncExt;
1414use bitcoin:: bip32:: { ChildNumber , DerivationPath , ExtendedPrivKey } ;
1515use bitcoin:: consensus:: serialize;
1616use bitcoin:: psbt:: { Input , PartiallySignedTransaction } ;
17- use bitcoin:: { Address , Network , OutPoint , ScriptBuf , Transaction , Txid } ;
17+ use bitcoin:: { Address , Network , OutPoint , Script , ScriptBuf , Transaction , Txid } ;
1818use esplora_client:: AsyncClient ;
1919use hex_conservative:: DisplayHex ;
2020use lightning:: events:: bump_transaction:: { Utxo , WalletSource } ;
@@ -355,10 +355,99 @@ impl<S: MutinyStorage> OnChainWallet<S> {
355355 Ok ( ( ) )
356356 }
357357
358+ fn is_mine ( & self , script : & Script ) -> Result < bool , MutinyError > {
359+ Ok ( self . wallet . try_read ( ) ?. is_mine ( script) )
360+ }
361+
358362 pub fn list_utxos ( & self ) -> Result < Vec < LocalOutput > , MutinyError > {
359363 Ok ( self . wallet . try_read ( ) ?. list_unspent ( ) . collect ( ) )
360364 }
361365
366+ pub fn process_payjoin_proposal (
367+ & self ,
368+ proposal : payjoin:: receive:: v2:: UncheckedProposal ,
369+ ) -> Result < payjoin:: receive:: v2:: PayjoinProposal , payjoin:: Error > {
370+ use payjoin:: Error ;
371+
372+ // Receive Check 1 bypass: We're not an automated payment processor.
373+ let proposal = proposal. assume_interactive_receiver ( ) ;
374+ log:: trace!( "check1" ) ;
375+
376+ // Receive Check 2: receiver can't sign for proposal inputs
377+ let proposal = proposal. check_inputs_not_owned ( |input| {
378+ self . is_mine ( input) . map_err ( |e| Error :: Server ( e. into ( ) ) )
379+ } ) ?;
380+ log:: trace!( "check2" ) ;
381+
382+ // Receive Check 3: receiver can't sign for proposal inputs
383+ let proposal = proposal. check_no_mixed_input_scripts ( ) ?;
384+ log:: trace!( "check3" ) ;
385+
386+ // Receive Check 4: have we seen this input before?
387+ let payjoin = proposal. check_no_inputs_seen_before ( |_input| {
388+ // This check ensures an automated sender does not get phished. It is not necessary for interactive payjoin **where the sender cannot generate bip21s from us**
389+ // assume false since Mutiny is not an automatic payment processor
390+ Ok ( false )
391+ } ) ?;
392+ log:: trace!( "check4" ) ;
393+
394+ let mut provisional_payjoin = payjoin. identify_receiver_outputs ( |output| {
395+ self . is_mine ( output) . map_err ( |e| Error :: Server ( e. into ( ) ) )
396+ } ) ?;
397+ self . try_contributing_inputs ( & mut provisional_payjoin)
398+ . map_err ( |e| Error :: Server ( e. into ( ) ) ) ?;
399+
400+ // Outputs may be substituted for e.g. batching at this stage
401+ // We're not doing this yet.
402+
403+ let payjoin_proposal = provisional_payjoin. finalize_proposal (
404+ |psbt| {
405+ let mut psbt = psbt. clone ( ) ;
406+ let wallet = self
407+ . wallet
408+ . try_read ( )
409+ . map_err ( |_| Error :: Server ( MutinyError :: WalletSigningFailed . into ( ) ) ) ?;
410+ wallet
411+ . sign ( & mut psbt, SignOptions :: default ( ) )
412+ . map_err ( |_| Error :: Server ( MutinyError :: WalletSigningFailed . into ( ) ) ) ?;
413+ Ok ( psbt)
414+ } ,
415+ // TODO: check Mutiny's minfeerate is present here
416+ Some ( payjoin:: bitcoin:: FeeRate :: MIN ) ,
417+ ) ?;
418+ let payjoin_proposal_psbt = payjoin_proposal. psbt ( ) ;
419+ log:: debug!(
420+ "Receiver's Payjoin proposal PSBT Rsponse: {:#?}" ,
421+ payjoin_proposal_psbt
422+ ) ;
423+ Ok ( payjoin_proposal)
424+ }
425+
426+ fn try_contributing_inputs (
427+ & self ,
428+ payjoin : & mut payjoin:: receive:: v2:: ProvisionalProposal ,
429+ ) -> Result < ( ) , MutinyError > {
430+ use payjoin:: bitcoin:: Amount ;
431+
432+ let available_inputs = self . list_utxos ( ) ?;
433+ let candidate_inputs: std:: collections:: HashMap < Amount , OutPoint > = available_inputs
434+ . iter ( )
435+ . map ( |i| ( Amount :: from_sat ( i. txout . value ) , i. outpoint ) )
436+ . collect ( ) ;
437+
438+ let selected_outpoint = payjoin
439+ . try_preserving_privacy ( candidate_inputs)
440+ . map_err ( |_| anyhow ! ( "no privacy-preserving selection available" ) ) ?;
441+ let selected_utxo = available_inputs
442+ . iter ( )
443+ . find ( |i| i. outpoint == selected_outpoint)
444+ . ok_or ( anyhow ! ( "This shouldn't happen. Failed to retrieve the privacy preserving utxo from those we provided to the seclector." ) ) ?;
445+ log:: debug!( "selected utxo: {:#?}" , selected_utxo) ;
446+
447+ payjoin. contribute_witness_input ( selected_utxo. txout . clone ( ) , selected_outpoint) ;
448+ Ok ( ( ) )
449+ }
450+
362451 pub fn list_transactions (
363452 & self ,
364453 include_raw : bool ,
0 commit comments