@@ -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,100 @@ 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 =
395+ payjoin. identify_receiver_outputs ( |output : & payjoin:: bitcoin:: Script | {
396+ self . is_mine ( output) . map_err ( |e| Error :: Server ( e. into ( ) ) )
397+ } ) ?;
398+ self . try_contributing_inputs ( & mut provisional_payjoin)
399+ . map_err ( |e| Error :: Server ( e. into ( ) ) ) ?;
400+
401+ // Outputs may be substituted for e.g. batching at this stage
402+ // We're not doing this yet.
403+
404+ let payjoin_proposal = provisional_payjoin. finalize_proposal (
405+ |psbt| {
406+ let mut psbt = psbt. clone ( ) ;
407+ let wallet = self
408+ . wallet
409+ . try_read ( )
410+ . map_err ( |_| Error :: Server ( MutinyError :: WalletSigningFailed . into ( ) ) ) ?;
411+ wallet
412+ . sign ( & mut psbt, SignOptions :: default ( ) )
413+ . map_err ( |_| Error :: Server ( MutinyError :: WalletSigningFailed . into ( ) ) ) ?;
414+ Ok ( psbt)
415+ } ,
416+ // TODO: check Mutiny's minfeerate is present here
417+ Some ( payjoin:: bitcoin:: FeeRate :: MIN ) ,
418+ ) ?;
419+ let payjoin_proposal_psbt = payjoin_proposal. psbt ( ) ;
420+ log:: debug!(
421+ "Receiver's Payjoin proposal PSBT Rsponse: {:#?}" ,
422+ payjoin_proposal_psbt
423+ ) ;
424+ Ok ( payjoin_proposal)
425+ }
426+
427+ fn try_contributing_inputs (
428+ & self ,
429+ payjoin : & mut payjoin:: receive:: v2:: ProvisionalProposal ,
430+ ) -> Result < ( ) , MutinyError > {
431+ use payjoin:: bitcoin:: Amount ;
432+
433+ let available_inputs = self . list_utxos ( ) ?;
434+ let candidate_inputs: std:: collections:: HashMap < Amount , OutPoint > = available_inputs
435+ . iter ( )
436+ . map ( |i| ( Amount :: from_sat ( i. txout . value ) , i. outpoint ) )
437+ . collect ( ) ;
438+
439+ let selected_outpoint = payjoin
440+ . try_preserving_privacy ( candidate_inputs)
441+ . map_err ( |_| anyhow ! ( "no privacy-preserving selection available" ) ) ?;
442+ let selected_utxo = available_inputs
443+ . iter ( )
444+ . find ( |i| i. outpoint == selected_outpoint)
445+ . ok_or ( anyhow ! ( "This shouldn't happen. Failed to retrieve the privacy preserving utxo from those we provided to the seclector." ) ) ?;
446+ log:: debug!( "selected utxo: {:#?}" , selected_utxo) ;
447+
448+ payjoin. contribute_witness_input ( selected_utxo. txout . clone ( ) , selected_outpoint) ;
449+ Ok ( ( ) )
450+ }
451+
362452 pub fn list_transactions (
363453 & self ,
364454 include_raw : bool ,
0 commit comments