2019-05-08 16:39:11 +02:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Threading.Tasks ;
using BTCPayServer.Payments ;
using NBitcoin ;
using NBXplorer.DerivationStrategy ;
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
namespace BTCPayServer
{
public class DerivationSchemeSettings : ISupportedPaymentMethod
{
public static DerivationSchemeSettings Parse ( string derivationStrategy , BTCPayNetwork network )
{
if ( network = = null )
throw new ArgumentNullException ( nameof ( network ) ) ;
if ( derivationStrategy = = null )
throw new ArgumentNullException ( nameof ( derivationStrategy ) ) ;
2019-12-24 08:20:44 +01:00
var result = network . NBXplorerNetwork . DerivationStrategyFactory . Parse ( derivationStrategy ) ;
2019-05-08 16:39:11 +02:00
return new DerivationSchemeSettings ( result , network ) { AccountOriginal = derivationStrategy . Trim ( ) } ;
}
2019-05-08 17:40:30 +02:00
2019-05-09 09:11:09 +02:00
public static bool TryParseFromJson ( string config , BTCPayNetwork network , out DerivationSchemeSettings strategy )
{
if ( network = = null )
throw new ArgumentNullException ( nameof ( network ) ) ;
if ( config = = null )
throw new ArgumentNullException ( nameof ( config ) ) ;
strategy = null ;
try
{
strategy = network . NBXplorerNetwork . Serializer . ToObject < DerivationSchemeSettings > ( config ) ;
strategy . Network = network ;
}
catch { }
return strategy ! = null ;
}
2019-05-08 17:40:30 +02:00
public static bool TryParseFromColdcard ( string coldcardExport , BTCPayNetwork network , out DerivationSchemeSettings settings )
{
settings = null ;
if ( coldcardExport = = null )
throw new ArgumentNullException ( nameof ( coldcardExport ) ) ;
if ( network = = null )
throw new ArgumentNullException ( nameof ( network ) ) ;
var result = new DerivationSchemeSettings ( ) ;
2019-05-08 17:54:53 +02:00
result . Source = "Coldcard" ;
2019-05-09 09:05:18 +02:00
var derivationSchemeParser = new DerivationSchemeParser ( network ) ;
2019-05-08 17:40:30 +02:00
JObject jobj = null ;
try
{
jobj = JObject . Parse ( coldcardExport ) ;
jobj = ( JObject ) jobj [ "keystore" ] ;
}
catch
{
return false ;
}
if ( jobj . ContainsKey ( "xpub" ) )
{
try
{
result . AccountOriginal = jobj [ "xpub" ] . Value < string > ( ) . Trim ( ) ;
result . AccountDerivation = derivationSchemeParser . ParseElectrum ( result . AccountOriginal ) ;
2019-05-12 17:13:55 +02:00
result . AccountKeySettings = new AccountKeySettings [ 1 ] ;
result . AccountKeySettings [ 0 ] = new AccountKeySettings ( ) ;
result . AccountKeySettings [ 0 ] . AccountKey = result . AccountDerivation . GetExtPubKeys ( ) . Single ( ) . GetWif ( network . NBitcoinNetwork ) ;
2019-05-08 17:54:53 +02:00
if ( result . AccountDerivation is DirectDerivationStrategy direct & & ! direct . Segwit )
result . AccountOriginal = null ; // Saving this would be confusing for user, as xpub of electrum is legacy derivation, but for btcpay, it is segwit derivation
2019-05-08 17:40:30 +02:00
}
catch
{
return false ;
}
}
else
{
return false ;
}
if ( jobj . ContainsKey ( "label" ) )
{
try
{
result . Label = jobj [ "label" ] . Value < string > ( ) ;
}
catch { return false ; }
}
if ( jobj . ContainsKey ( "ckcc_xfp" ) )
{
try
{
2019-05-12 17:13:55 +02:00
result . AccountKeySettings [ 0 ] . RootFingerprint = new HDFingerprint ( jobj [ "ckcc_xfp" ] . Value < uint > ( ) ) ;
2019-05-08 17:40:30 +02:00
}
catch { return false ; }
}
if ( jobj . ContainsKey ( "derivation" ) )
{
try
{
2019-05-12 17:13:55 +02:00
result . AccountKeySettings [ 0 ] . AccountKeyPath = new KeyPath ( jobj [ "derivation" ] . Value < string > ( ) ) ;
2019-05-08 17:40:30 +02:00
}
catch { return false ; }
}
settings = result ;
settings . Network = network ;
return true ;
}
2019-05-08 16:39:11 +02:00
public DerivationSchemeSettings ( )
{
}
2019-05-12 17:30:28 +02:00
2019-05-08 16:39:11 +02:00
public DerivationSchemeSettings ( DerivationStrategyBase derivationStrategy , BTCPayNetwork network )
{
if ( network = = null )
throw new ArgumentNullException ( nameof ( network ) ) ;
if ( derivationStrategy = = null )
throw new ArgumentNullException ( nameof ( derivationStrategy ) ) ;
AccountDerivation = derivationStrategy ;
Network = network ;
2019-05-12 17:13:55 +02:00
AccountKeySettings = derivationStrategy . GetExtPubKeys ( ) . Select ( c = > new AccountKeySettings ( )
{
AccountKey = c . GetWif ( network . NBitcoinNetwork )
} ) . ToArray ( ) ;
2019-05-08 16:39:11 +02:00
}
2019-05-12 17:30:28 +02:00
BitcoinExtPubKey _SigningKey ;
public BitcoinExtPubKey SigningKey
{
get
{
return _SigningKey ? ? AccountKeySettings ? . Select ( k = > k . AccountKey ) . FirstOrDefault ( ) ;
}
set
{
_SigningKey = value ;
}
}
2019-05-08 16:39:11 +02:00
[JsonIgnore]
public BTCPayNetwork Network { get ; set ; }
2019-05-08 17:54:53 +02:00
public string Source { get ; set ; }
2019-05-12 17:13:55 +02:00
[Obsolete("Use GetAccountKeySettings().AccountKeyPath instead")]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
2019-05-08 16:39:11 +02:00
public KeyPath AccountKeyPath { get ; set ; }
2019-05-10 12:30:10 +02:00
2019-05-08 16:39:11 +02:00
public DerivationStrategyBase AccountDerivation { get ; set ; }
public string AccountOriginal { get ; set ; }
2019-05-12 17:13:55 +02:00
[Obsolete("Use GetAccountKeySettings().RootFingerprint instead")]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
2019-05-08 16:39:11 +02:00
public HDFingerprint ? RootFingerprint { get ; set ; }
2019-05-12 17:13:55 +02:00
[Obsolete("Use GetAccountKeySettings().AccountKey instead")]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
2019-05-10 12:30:10 +02:00
public BitcoinExtPubKey ExplicitAccountKey { get ; set ; }
[JsonIgnore]
2019-05-12 17:13:55 +02:00
[Obsolete("Use GetAccountKeySettings().AccountKey instead")]
2019-05-10 12:30:10 +02:00
public BitcoinExtPubKey AccountKey
{
get
{
return ExplicitAccountKey ? ? new BitcoinExtPubKey ( AccountDerivation . GetExtPubKeys ( ) . First ( ) , Network . NBitcoinNetwork ) ;
}
}
2019-05-12 17:30:28 +02:00
public AccountKeySettings GetSigningAccountKeySettings ( )
{
return AccountKeySettings . Single ( a = > a . AccountKey = = SigningKey ) ;
}
2019-05-12 17:13:55 +02:00
AccountKeySettings [ ] _AccountKeySettings ;
public AccountKeySettings [ ] AccountKeySettings
{
get
{
// Legacy
if ( _AccountKeySettings = = null )
{
if ( this . Network = = null )
return null ;
_AccountKeySettings = AccountDerivation . GetExtPubKeys ( ) . Select ( e = > new AccountKeySettings ( )
{
AccountKey = e . GetWif ( this . Network . NBitcoinNetwork ) ,
} ) . ToArray ( ) ;
#pragma warning disable CS0618 // Type or member is obsolete
_AccountKeySettings [ 0 ] . AccountKeyPath = AccountKeyPath ;
_AccountKeySettings [ 0 ] . RootFingerprint = RootFingerprint ;
ExplicitAccountKey = null ;
AccountKeyPath = null ;
RootFingerprint = null ;
#pragma warning restore CS0618 // Type or member is obsolete
}
return _AccountKeySettings ;
}
set
{
_AccountKeySettings = value ;
}
}
2019-05-12 04:07:41 +02:00
public IEnumerable < NBXplorer . Models . PSBTRebaseKeyRules > GetPSBTRebaseKeyRules ( )
{
2019-05-12 17:30:28 +02:00
foreach ( var accountKey in AccountKeySettings )
2019-05-12 04:07:41 +02:00
{
2019-11-16 09:22:51 +01:00
if ( accountKey . GetRootedKeyPath ( ) is RootedKeyPath rootedKeyPath )
2019-05-12 04:07:41 +02:00
{
2019-05-12 17:13:55 +02:00
yield return new NBXplorer . Models . PSBTRebaseKeyRules ( )
{
AccountKey = accountKey . AccountKey ,
2019-11-16 09:22:51 +01:00
AccountKeyPath = rootedKeyPath
2019-05-12 17:13:55 +02:00
} ;
}
2019-05-12 04:07:41 +02:00
}
}
2019-05-08 16:39:11 +02:00
public string Label { get ; set ; }
2019-05-08 18:06:03 +02:00
[JsonIgnore]
2019-05-08 16:39:11 +02:00
public PaymentMethodId PaymentId = > new PaymentMethodId ( Network . CryptoCode , PaymentTypes . BTCLike ) ;
public override string ToString ( )
{
return AccountDerivation . ToString ( ) ;
}
public string ToPrettyString ( )
{
2019-05-08 18:07:05 +02:00
return ! string . IsNullOrEmpty ( Label ) ? Label :
! String . IsNullOrEmpty ( AccountOriginal ) ? AccountOriginal :
2019-05-08 16:39:11 +02:00
ToString ( ) ;
}
2019-05-09 09:11:09 +02:00
public string ToJson ( )
{
return Network . NBXplorerNetwork . Serializer . ToString ( this ) ;
}
2019-05-12 04:07:41 +02:00
public void RebaseKeyPaths ( PSBT psbt )
{
foreach ( var rebase in GetPSBTRebaseKeyRules ( ) )
{
2019-11-16 09:22:51 +01:00
psbt . RebaseKeyPaths ( rebase . AccountKey , rebase . AccountKeyPath ) ;
2019-05-12 04:07:41 +02:00
}
}
2019-05-08 16:39:11 +02:00
}
2019-05-12 17:13:55 +02:00
public class AccountKeySettings
{
public HDFingerprint ? RootFingerprint { get ; set ; }
public KeyPath AccountKeyPath { get ; set ; }
2019-05-14 09:06:43 +02:00
public RootedKeyPath GetRootedKeyPath ( )
{
if ( RootFingerprint is HDFingerprint fp & & AccountKeyPath ! = null )
return new RootedKeyPath ( fp , AccountKeyPath ) ;
return null ;
}
2019-05-12 17:13:55 +02:00
public BitcoinExtPubKey AccountKey { get ; set ; }
public bool IsFullySetup ( )
{
return AccountKeyPath ! = null & & RootFingerprint is HDFingerprint ;
}
}
2019-05-08 16:39:11 +02:00
}