Skip to content

Commit

Permalink
Backend: implement Silent Payments sending
Browse files Browse the repository at this point in the history
Implemented sending of Silent Payments (BIP-352).

Created tests for Silent Payments based on test data for
BIP-352.

Updated NBitcoin to version 7.0.13 because older NBitcoin
versions can't decode bech32 strings of length > 90, which is
needed for Silent Payment address decoding. Use property
UnknownParameters of BitcoinUrlBuilder as UnknowParameters
is deprecated. Had to change some code since return type of
new property is different.
  • Loading branch information
webwarrior-ws committed Jul 15, 2024
1 parent 3af561c commit 14f35fe
Show file tree
Hide file tree
Showing 18 changed files with 3,196 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
<HintPath>..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.0.2\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
</Reference>
<Reference Include="NBitcoin">
<HintPath>..\..\packages\NBitcoin.6.0.17\lib\net461\NBitcoin.dll</HintPath>
<HintPath>..\..\packages\NBitcoin.7.0.13\lib\netstandard2.0\NBitcoin.dll</HintPath>
</Reference>
<Reference Include="System.Runtime">
<HintPath>..\..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll</HintPath>
Expand Down
3 changes: 3 additions & 0 deletions src/GWallet.Backend.Tests/GWallet.Backend.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
<Compile Include="Deserialization.fs" />
<Compile Include="ExceptionMarshalling.fs" />
<Compile Include="MetaMarshalling.fs" />
<Compile Include="SilentPayments.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
Expand All @@ -38,6 +40,7 @@
<ProjectReference Include="..\GWallet.Backend\GWallet.Backend.fsproj" />
</ItemGroup>
<ItemGroup>
<Content Include="data\send_and_receive_test_vectors.json" />
<EmbeddedResource Include="data\basicException.json" />
<EmbeddedResource Include="data\customFSharpException.json" />
<EmbeddedResource Include="data\realException.json" />
Expand Down
112 changes: 112 additions & 0 deletions src/GWallet.Backend.Tests/SilentPayments.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
namespace GWallet.Backend.Tests

open System.IO
open System.Reflection
open System.Text.Json

open NUnit.Framework
open NBitcoin

open GWallet.Backend.UtxoCoin


type private TestInput =
{
TxId: string
Vout: int
ScriptSig: string
TxInWitness: string
ScriptPubKey: string
PrivateKey: string
}
static member FromJsonElement(jsonElement: JsonElement) =
{
TxId = jsonElement.GetProperty("txid").GetString()
Vout = jsonElement.GetProperty("vout").GetInt32()
ScriptSig = jsonElement.GetProperty("scriptSig").GetString()
TxInWitness = jsonElement.GetProperty("txinwitness").GetString()
ScriptPubKey = jsonElement.GetProperty("prevout").GetProperty("scriptPubKey").GetProperty("hex").GetString()
PrivateKey = jsonElement.GetProperty("private_key").GetString()
}

[<TestFixture>]
type SilentPayments() =

let executingAssembly = Assembly.GetExecutingAssembly()
let binPath = executingAssembly.Location |> FileInfo
let projectDirPath = Path.Combine(binPath.Directory.FullName, "..", "..", "..")
let dataDir = Path.Combine(projectDirPath, "data") |> DirectoryInfo

[<Test>]
member __.``Test creating outputs using test vectors from BIP-352``() =
// https://github.com/bitcoin/bips/blob/master/bip-0352/send_and_receive_test_vectors.json

let testVectorsFileName = "send_and_receive_test_vectors.json"
let testVectorsJson = JsonDocument.Parse(File.ReadAllText(Path.Combine(dataDir.FullName, testVectorsFileName)))

for testCase in testVectorsJson.RootElement.EnumerateArray() do
let testCaseName = testCase.GetProperty("comment").GetString()
let sending = testCase.GetProperty("sending").[0]
let expectedOutputs =
sending.GetProperty("expected").GetProperty("outputs").EnumerateArray()
|> Seq.map (fun each -> each.EnumerateArray() |> Seq.toArray)
|> Seq.toArray
let given = sending.GetProperty "given"
let inputs = given.GetProperty("vin").EnumerateArray() |> Seq.map TestInput.FromJsonElement |> Seq.toList
let recipients =
given.GetProperty("recipients").EnumerateArray()
|> Seq.map (fun each -> each.GetString() |> SilentPaymentAddress.Decode)
|> Seq.toList

if expectedOutputs.Length > 1 || (expectedOutputs.Length = 1 && expectedOutputs.[0].Length > 1) || recipients.Length > 1 then
printfn "Skipping BIP-352 test case '%s'" testCaseName
else
printfn "Running BIP-352 test case '%s'" testCaseName

let expectedOutput = expectedOutputs.[0] |> Array.tryHead |> Option.map (fun elem -> elem.GetString())

let spInputs =
inputs
|> List.map (
fun input ->
let witness =
match input.TxInWitness with
| "" -> None
| hex ->
let stream = BitcoinStream(DataEncoders.Encoders.Hex.DecodeData hex)
Some <| WitScript.Load stream
let spInput =
SilentPayments.convertToSilentPaymentInput
(Script.FromHex input.ScriptPubKey)
(DataEncoders.Encoders.Hex.DecodeData input.ScriptSig)
witness
input, spInput)

let maybePrivateKeys, outpoints =
spInputs
|> List.choose
(fun (input, spInput) ->
let privKey = new Key(DataEncoders.Encoders.Hex.DecodeData input.PrivateKey)
let outPoint = OutPoint(uint256.Parse input.TxId, input.Vout)
match spInput with
| InputForSharedSecretDerivation(_pubKey) ->
let isTapRoot = (Script.FromHex input.ScriptPubKey).IsScriptType ScriptType.Taproot
Some (Some(privKey, isTapRoot), outPoint)
| InputJustForSpending ->
Some(None, outPoint)
| _ -> None)
|> List.unzip

let privateKeys = maybePrivateKeys |> List.choose id

match privateKeys, expectedOutput with
| [], None -> ()
| [], 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 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)
|> ignore
Loading

0 comments on commit 14f35fe

Please sign in to comment.