Skip to content

Commit

Permalink
Backend: addressed comments
Browse files Browse the repository at this point in the history
Refactoring: addressed comments to the PR.
  • Loading branch information
webwarrior-ws committed Jul 22, 2024
1 parent ddf8b92 commit e798eb1
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 35 deletions.
6 changes: 3 additions & 3 deletions src/GWallet.Backend.Tests/SilentPayments.fs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ type SilentPayments() =
let stream = BitcoinStream(DataEncoders.Encoders.Hex.DecodeData hex)
Some <| WitScript.Load stream
let spInput =
SilentPayments.convertToSilentPaymentInput
SilentPayments.ConvertToSilentPaymentInput
(Script.FromHex input.ScriptPubKey)
(DataEncoders.Encoders.Hex.DecodeData input.ScriptSig)
witness
Expand Down Expand Up @@ -104,9 +104,9 @@ type SilentPayments() =
| [], Some _ ->
Assert.Fail(sprintf "No inputs for shared secret derivation in test case '%s'" testCaseName)
| _, Some expectedOutputString ->
let output = SilentPayments.createOutput privateKeys outpoints recipients.[0]
let output = SilentPayments.CreateOutput privateKeys outpoints recipients.[0]
let outputString = output.GetEncoded() |> DataEncoders.Encoders.Hex.EncodeData
Assert.AreEqual(expectedOutputString, outputString, sprintf "Failure in test case '%s'" testCaseName)
| _, None ->
Assert.Throws(fun () -> SilentPayments.createOutput privateKeys outpoints recipients.[0] |> ignore)
Assert.Throws(fun () -> SilentPayments.CreateOutput privateKeys outpoints recipients.[0] |> ignore)
|> ignore
42 changes: 28 additions & 14 deletions src/GWallet.Backend/UtxoCoin/SilentPayments.fs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ type SilentPaymentAddress =
static member MainNetPrefix = "sp"
static member TestNetPrefix = "tsp"

// https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#address-encoding
static member MinimumEncodedLength = 116u
static member MaximumEncodedLength = 1023u

static member private GetEncoder(chainName: ChainName) =
let hrp =
if chainName = ChainName.Mainnet then
Expand Down Expand Up @@ -81,20 +85,20 @@ type SilentPaymentInput =
| InputJustForSpending

module SilentPayments =
let private secp256k1 = EC.CustomNamedCurves.GetByName("secp256k1")
let private secp256k1 = EC.CustomNamedCurves.GetByName "secp256k1"

let private scalarOrder = BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16)

let private NUMS_H_bytes =
let private NUMS_H_BYTES =
[| 0x50uy; 0x92uy; 0x9buy; 0x74uy; 0xc1uy; 0xa0uy; 0x49uy; 0x54uy; 0xb7uy; 0x8buy; 0x4buy; 0x60uy; 0x35uy; 0xe9uy; 0x7auy; 0x5euy;
0x07uy; 0x8auy; 0x5auy; 0x0fuy; 0x28uy; 0xecuy; 0x96uy; 0xd5uy; 0x47uy; 0xbfuy; 0xeeuy; 0x9auy; 0xceuy; 0x80uy; 0x3auy; 0xc0uy; |]

type BigInteger with
static member FromByteArrayUnsigned (bytes: array<byte>) =
module BigInteger =
let FromByteArrayUnsigned (bytes: array<byte>) =
BigInteger(1, bytes)

// see https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#selecting-inputs
let convertToSilentPaymentInput (scriptPubKey: Script) (scriptSig: array<byte>) (witness: Option<WitScript>): SilentPaymentInput =
let ConvertToSilentPaymentInput (scriptPubKey: Script) (scriptSig: array<byte>) (witness: Option<WitScript>): SilentPaymentInput =
if scriptPubKey.IsScriptType ScriptType.P2PKH then
// skip the first 3 op_codes and grab the 20 byte hash
// from the scriptPubKey
Expand Down Expand Up @@ -144,7 +148,7 @@ module SilentPayments =
let controlBlock = witnessStack.Peek()
// controlBlock is <control byte> <32 byte internal key> and 0 or more <32 byte hash>
let internalKey = controlBlock.[1..32]
internalKey = NUMS_H_bytes
internalKey = NUMS_H_BYTES
else
false
if internalKeyIsH then
Expand All @@ -165,7 +169,7 @@ module SilentPayments =
else
InvalidInput

let taggedHash (tag: string) (data: array<byte>) : array<byte> =
let TaggedHash (tag: string) (data: array<byte>) : array<byte> =
let sha256 = Digests.Sha256Digest()

let tag = Text.ASCIIEncoding.ASCII.GetBytes tag
Expand All @@ -181,12 +185,12 @@ module SilentPayments =
sha256.DoFinal(result, 0) |> ignore
result

let getInputHash (outpoints: List<OutPoint>) (sumInputPubKeys: EC.ECPoint) : array<byte> =
let GetInputHash (outpoints: List<OutPoint>) (sumInputPubKeys: EC.ECPoint) : array<byte> =
let lowestOutpoint = outpoints |> List.map (fun outpoint -> outpoint.ToBytes()) |> List.min
let hashInput = Array.append lowestOutpoint (sumInputPubKeys.GetEncoded true)
taggedHash "BIP0352/Inputs" hashInput
TaggedHash "BIP0352/Inputs" hashInput

let createOutput (privateKeys: List<Key * bool>) (outpoints: List<OutPoint>) (spAddress: SilentPaymentAddress) =
let CreateOutput (privateKeys: List<Key * bool>) (outpoints: List<OutPoint>) (spAddress: SilentPaymentAddress) =
if privateKeys.IsEmpty then
failwith "privateKeys should not be empty"

Expand All @@ -211,7 +215,7 @@ module SilentPayments =
if aSum = BigInteger.Zero then
failwith "Input privkeys sum is zero"

let inputHash = getInputHash outpoints (secp256k1.G.Multiply aSum)
let inputHash = GetInputHash outpoints (secp256k1.G.Multiply aSum)

let tweak = BigInteger.FromByteArrayUnsigned inputHash
let tweakedSumSeckey = aSum.Multiply(tweak).Mod(scalarOrder)
Expand All @@ -220,11 +224,21 @@ module SilentPayments =

let k = 0u
let tK =
taggedHash
TaggedHash
"BIP0352/SharedSecret"
(Array.append (ecdhSharedSecret.GetEncoded true) (BitConverter.GetBytes k))
|> BigInteger.FromByteArrayUnsigned
let Bm = secp256k1.Curve.DecodePoint <| spAddress.SpendPublicKey.ToBytes()
let sharedSecret = Bm.Add(secp256k1.G.Multiply tK)
let bM = secp256k1.Curve.DecodePoint <| spAddress.SpendPublicKey.ToBytes()
let sharedSecret = bM.Add(secp256k1.G.Multiply tK)

sharedSecret.Normalize().AffineXCoord

let GetFinalDestination (privateKey: Key) (outpoints: List<OutPoint>) (destination: string) (network: Network) : string =
let privateKeys =
outpoints
|> List.map (fun _ -> (privateKey, false))

let output = CreateOutput privateKeys outpoints (SilentPaymentAddress.Decode destination)
let taprootAddress = TaprootAddress(TaprootPubKey(output.GetEncoded()), network)

taprootAddress.ToString()
30 changes: 12 additions & 18 deletions src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ module Account =

let private CalculateSilentPaymentDestination (account: IUtxoAccount)
(transactionInputs: List<TransactionInputOutpointInfo>)
(destination: string)
(reusableAddress: string)
(privateKey: Key) =
// Since we can only receive P2WPKH-P2SH or P2WPKH transactions, and
// non-compressed keys are not accepted as default policy
Expand All @@ -302,15 +302,8 @@ module Account =
let outpoints =
transactionInputs
|> List.map (fun input -> (ConvertToICoin account input).Outpoint)

let privateKeys =
outpoints
|> List.map (fun _ -> (privateKey, false))

let output = SilentPayments.createOutput privateKeys outpoints (SilentPaymentAddress.Decode destination)
let taprootAddress = TaprootAddress(TaprootPubKey(output.GetEncoded()), GetNetwork (account:>IAccount).Currency)

taprootAddress.ToString()

SilentPayments.GetFinalDestination privateKey outpoints reusableAddress (GetNetwork (account:>IAccount).Currency)

let private CheckSilentPaymentTransactionInputValidity (finalTransaction: Transaction)
(txMetadataInputs: List<TransactionInputOutpointInfo>) =
Expand All @@ -324,11 +317,11 @@ module Account =
| None -> failwith (SPrintF1 "Could not find output with index=%d in txMetadataInputs" input.PrevOut.N)
let scriptPubKey =
Script(NBitcoin.DataEncoders.Encoders.Hex.DecodeData inputOutpointInfo.DestinationInHex)
match SilentPayments.convertToSilentPaymentInput scriptPubKey (input.ScriptSig.ToBytes()) (Some input.WitScript) with
match SilentPayments.ConvertToSilentPaymentInput scriptPubKey (input.ScriptSig.ToBytes()) (Some input.WitScript) with
| InputForSharedSecretDerivation _ -> ()
| _ ->
let errorMessage = SPrintF1 "One of the inputs is not valid for shared secret derivation: %A" inputOutpointInfo
failwith ("Error creating silent payment transaction:\n" + errorMessage)
failwith (SPrintF1 "Error creating silent payment transaction: %s" errorMessage)

let internal EstimateTransferFee
(account: IUtxoAccount)
Expand Down Expand Up @@ -427,7 +420,7 @@ module Account =
raise <| Exception(SPrintF1 "Could not create fee rate from %s btc per KB"
(btcPerKiloByteForFastTrans.ToString()), ex)

let destination =
let finalDestination =
if SilentPaymentAddress.IsSilentPaymentAddress destination then
// calculate bogus silent payment destinantion using throwaway private key,
// as it will only be used for fee estimation
Expand All @@ -438,7 +431,7 @@ module Account =

let transactionBuilder = CreateTransactionAndCoinsToBeSigned account
initiallyUsedInputs
destination
finalDestination
amount

try
Expand Down Expand Up @@ -486,13 +479,13 @@ module Account =

let isSilentPayment = SilentPaymentAddress.IsSilentPaymentAddress destination

let destination =
let finalDestination =
if isSilentPayment then
CalculateSilentPaymentDestination account txMetadata.Inputs destination privateKey
else
destination

let finalTransactionBuilder = CreateTransactionAndCoinsToBeSigned account txMetadata.Inputs destination amount
let finalTransactionBuilder = CreateTransactionAndCoinsToBeSigned account txMetadata.Inputs finalDestination amount

finalTransactionBuilder.AddKeys privateKey |> ignore
finalTransactionBuilder.SendFees (Money.Satoshis btcMinerFee.EstimatedFeeInSatoshis)
Expand Down Expand Up @@ -725,8 +718,9 @@ module Account =
// (FIXME: this is only valid for the first version of segwit, fix it!)
Fixed [ 42u; 62u ]
| Currency.BTC, _ when SilentPaymentAddress.IsSilentPaymentAddress address ->
// https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#address-encoding
Variable { Minimum = 116u; Maximum = 1023u }
Variable
{ Minimum = SilentPaymentAddress.MinimumEncodedLength
Maximum = SilentPaymentAddress.MaximumEncodedLength }
| Currency.LTC, _ when address.StartsWith LITECOIN_ADDRESS_BECH32_PREFIX ->
// taken from https://coin.space/all-about-address-types/, e.g. ltc1q3qkpj5s4ru3cx9t7dt27pdfmz5aqy3wplamkns
// FIXME: hopefully someone replies/documents https://bitcoin.stackexchange.com/questions/110975/how-long-can-bech32-addresses-be-in-the-litecoin-mainnet
Expand Down

0 comments on commit e798eb1

Please sign in to comment.