Skip to content

Commit fdef5ce

Browse files
committed
Backend/UtxoCoin: support nativeSegwit+coldStorage
This fixes the following crash when trying to broadcast a signed transaction from your cold-storage device: ``` unknown origin account at GWallet.Backend.UtxoCoin.Account.GetSignedTransactionDetails[T](String rawTransaction, Currency currency) in /Users/knocte/Documents/Code/geewalletMASTERclean/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs:line 728 at GWallet.Backend.Account.GetSignedTransactionDetails[T](SignedTransaction`1 signedTransaction) in /Users/knocte/Documents/Code/geewalletMASTERclean/src/GWallet.Backend/Account.fs:line 793 at Program.BroadcastPayment() in /Users/knocte/Documents/Code/geewalletMASTERclean/src/GWallet.Frontend.Console/Program.fs:line 82 at Program.PerformOperation(UInt32 numActiveAccounts, UInt32 numHotAccounts) in /Users/knocte/Documents/Code/geewalletMASTERclean/src/GWallet.Frontend.Console/Program.fs:line 398 at Program.ProgramMainLoop[a]() in /Users/knocte/Documents/Code/geewalletMASTERclean/src/GWallet.Frontend.Console/Program.fs:line 481 at Program.NormalStartWithNoParameters() in /Users/knocte/Documents/Code/geewalletMASTERclean/src/GWallet.Frontend.Console/Program.fs:line 493 ``` This change also reduces a bit of primitive obsession (i.e. string vs BitcoinAddress) to make the code less "stringly-typed" lol. This should have been done in [1] but was an oversight on my part when reviewing. [1] #211
1 parent 279ff02 commit fdef5ce

File tree

1 file changed

+40
-29
lines changed

1 file changed

+40
-29
lines changed

src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -72,27 +72,28 @@ module Account =
7272
// TODO: measure how long does it take to get the script hash and if it's too long, cache it at app startup?
7373
BitcoinAddress.Create(publicAddress, GetNetwork currency) |> GetElectrumScriptHashFromAddress
7474

75-
let internal GetSegwitP2shPublicAddressFromPublicKey currency (publicKey: PubKey) =
75+
let internal GetNestedSegwitPublicAddressFromPublicKey currency (publicKey: PubKey): BitcoinAddress =
7676
publicKey
7777
.GetScriptPubKey(ScriptPubKeyType.SegwitP2SH)
7878
.GetDestinationAddress(GetNetwork currency)
79-
.ToString()
8079

81-
let internal GetNativeSegwitPublicAddressFromPublicKey currency (publicKey: PubKey) =
80+
let internal GetNativeSegwitPublicAddressFromPublicKey currency (publicKey: PubKey): BitcoinAddress =
8281
publicKey
8382
.GetScriptPubKey(ScriptPubKeyType.Segwit)
8483
.GetDestinationAddress(GetNetwork currency)
85-
.ToString()
8684

87-
let internal GetPublicAddressFromPublicKey =
85+
let private GetUtxoPublicAddressFromPublicKey =
8886
if Config.UseNativeSegwit then
8987
GetNativeSegwitPublicAddressFromPublicKey
9088
else
91-
GetSegwitP2shPublicAddressFromPublicKey
89+
GetNestedSegwitPublicAddressFromPublicKey
90+
91+
let internal GetPublicAddressFromPublicKey currency pubKey =
92+
(GetUtxoPublicAddressFromPublicKey currency pubKey).ToString()
9293

9394
let internal GetPublicAddressFromNormalAccountFile (currency: Currency) (accountFile: FileRepresentation): string =
9495
let pubKey = PubKey(accountFile.Name)
95-
GetPublicAddressFromPublicKey currency pubKey
96+
(GetUtxoPublicAddressFromPublicKey currency pubKey).ToString()
9697

9798
let internal GetPublicKeyFromNormalAccountFile (accountFile: FileRepresentation): PubKey =
9899
PubKey accountFile.Name
@@ -102,7 +103,7 @@ module Account =
102103

103104
let internal GetPublicAddressFromUnencryptedPrivateKey (currency: Currency) (privateKey: string) =
104105
let privateKey = Key.Parse(privateKey, GetNetwork currency)
105-
GetPublicAddressFromPublicKey currency privateKey.PubKey
106+
(GetUtxoPublicAddressFromPublicKey currency privateKey.PubKey).ToString()
106107

107108
let internal GetAccountFromFile (accountFile: FileRepresentation) (currency: Currency) kind: IAccount =
108109
if not (currency.IsUtxo()) then
@@ -152,9 +153,9 @@ module Account =
152153
: Async<BlockchainScriptHashGetBalanceInnerResult> =
153154
let scriptHashesHex =
154155
[
155-
GetNativeSegwitPublicAddressFromPublicKey account.Currency account.PublicKey
156+
(GetNativeSegwitPublicAddressFromPublicKey account.Currency account.PublicKey).ToString()
156157
|> GetElectrumScriptHashFromPublicAddress account.Currency
157-
GetSegwitP2shPublicAddressFromPublicKey account.Currency account.PublicKey
158+
(GetNestedSegwitPublicAddressFromPublicKey account.Currency account.PublicKey).ToString()
158159
|> GetElectrumScriptHashFromPublicAddress account.Currency
159160
]
160161

@@ -200,9 +201,9 @@ module Account =
200201
let sourceAddress = scriptPubKey.GetDestinationAddress(GetNetwork account.Currency).ToString()
201202
let coin =
202203
Coin(txHash, uint32 inputOutpointInfo.OutputIndex, Money(inputOutpointInfo.ValueInSatoshis), scriptPubKey)
203-
if sourceAddress = GetSegwitP2shPublicAddressFromPublicKey account.Currency account.PublicKey then
204+
if sourceAddress = (GetNestedSegwitPublicAddressFromPublicKey account.Currency account.PublicKey).ToString() then
204205
coin.ToScriptCoin(account.PublicKey.WitHash.ScriptPubKey) :> ICoin
205-
elif sourceAddress = GetNativeSegwitPublicAddressFromPublicKey account.Currency account.PublicKey then
206+
elif sourceAddress = (GetNativeSegwitPublicAddressFromPublicKey account.Currency account.PublicKey).ToString() then
206207
coin :> ICoin
207208
else
208209
//We filter utxos based on scriptPubKey when retrieving from electrum
@@ -325,9 +326,9 @@ module Account =
325326

326327
let currency = account.Currency
327328

328-
let getUtxos (publicAddress: string) =
329+
let getUtxos (publicAddress: BitcoinAddress) =
329330
async {
330-
let job = GetElectrumScriptHashFromPublicAddress currency publicAddress
331+
let job = GetElectrumScriptHashFromPublicAddress currency (publicAddress.ToString())
331332
|> ElectrumClient.GetUnspentTransactionOutputs
332333

333334
return! Server.Query currency (QuerySettings.Default ServerSelectionMode.Fast) job None
@@ -340,7 +341,7 @@ module Account =
340341
|> getUtxos
341342

342343
let! legacySegwitUtxos =
343-
GetSegwitP2shPublicAddressFromPublicKey currency account.PublicKey
344+
GetNestedSegwitPublicAddressFromPublicKey currency account.PublicKey
344345
|> getUtxos
345346

346347
return Seq.concat [ nativeSegwitUtxos; legacySegwitUtxos ]
@@ -707,11 +708,23 @@ module Account =
707708
failwith "transaction has multiple different inputs"
708709
origin
709710

710-
let matchOriginToAccount(account: ReadOnlyUtxoAccount): bool =
711-
let accountAddress = (account :> IAccount).PublicAddress
712-
let bitcoinAddress = BitcoinAddress.Create(accountAddress, network)
713-
let destination = bitcoinAddress.ScriptPubKey.GetDestination()
714-
(destination :> IDestination) = origin
711+
let anyAccountAddressesMatch (originOrDestination: IDestination) (account: IUtxoAccount) (throwIfNot: bool): bool =
712+
let nativeSegWitAddress =
713+
GetNativeSegwitPublicAddressFromPublicKey account.Currency account.PublicKey
714+
:> IDestination
715+
let nestedSegWitAddress =
716+
GetNestedSegwitPublicAddressFromPublicKey account.Currency account.PublicKey
717+
:> IDestination
718+
let network = GetNetwork account.Currency
719+
let originOrDestinationAddress = originOrDestination.ScriptPubKey.GetDestinationAddress network :> IDestination
720+
let anyMatch =
721+
nativeSegWitAddress = originOrDestinationAddress || nestedSegWitAddress = originOrDestinationAddress
722+
if not anyMatch then
723+
if throwIfNot then
724+
failwith
725+
<| SPrintF3 "ReadOnly account's addresses (%s and %s) don't match with the source adress of the signed transaction (%s)"
726+
(nativeSegWitAddress.ToString()) (nestedSegWitAddress.ToString()) (originOrDestinationAddress.ToString())
727+
anyMatch
715728

716729
let account =
717730
let accountOpt =
@@ -721,22 +734,20 @@ module Account =
721734
GetAccountFromFile accountFile currency AccountKind.ReadOnly
722735
:?> ReadOnlyUtxoAccount
723736
)
724-
|> Seq.filter matchOriginToAccount
725737
|> Seq.tryExactlyOne
726738
match accountOpt with
727-
| Some account -> account
728-
| None -> failwith "unknown origin account"
729-
let originAddress =
730-
let accountAddress = (account :> IAccount).PublicAddress
731-
let bitcoinAddress = BitcoinAddress.Create(accountAddress, network)
732-
bitcoinAddress
739+
| None -> failwith "Cannot broadcast transactions from a wallet instance that doesn't have read-only accounts"
740+
| Some account ->
741+
let utxoAccount = account :> IUtxoAccount
742+
anyAccountAddressesMatch origin utxoAccount true |> ignore<bool>
743+
account
733744

734745
let destinationAddress, value =
735746
let filterChangeTxOuts(txOut: TxOut): Option<BitcoinAddress * Money> =
736747
let scriptPubKey = txOut.ScriptPubKey
737748
let destinationAddress = scriptPubKey.GetDestinationAddress network
738749
let destination = destinationAddress.ScriptPubKey.GetDestination()
739-
if (destination :> IDestination) = origin then
750+
if anyAccountAddressesMatch destination account false then
740751
None
741752
else
742753
Some (destinationAddress, txOut.Value)
@@ -748,7 +759,7 @@ module Account =
748759
failwith "expected a single destination address"
749760

750761
{
751-
OriginAddress = originAddress.ToString()
762+
OriginAddress = (account :> IAccount).PublicAddress
752763
DestinationAddress = destinationAddress.ToString()
753764
Amount = value.ToDecimal MoneyUnit.BTC
754765
Currency = currency

0 commit comments

Comments
 (0)