Skip to content

Commit 14f35fe

Browse files
committed
Backend: implement Silent Payments sending
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.
1 parent 3af561c commit 14f35fe

18 files changed

+3196
-18
lines changed

src/GWallet.Backend.Tests/GWallet.Backend.Tests-legacy.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@
124124
<HintPath>..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.0.2\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
125125
</Reference>
126126
<Reference Include="NBitcoin">
127-
<HintPath>..\..\packages\NBitcoin.6.0.17\lib\net461\NBitcoin.dll</HintPath>
127+
<HintPath>..\..\packages\NBitcoin.7.0.13\lib\netstandard2.0\NBitcoin.dll</HintPath>
128128
</Reference>
129129
<Reference Include="System.Runtime">
130130
<HintPath>..\..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll</HintPath>

src/GWallet.Backend.Tests/GWallet.Backend.Tests.fsproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
<Compile Include="Deserialization.fs" />
2626
<Compile Include="ExceptionMarshalling.fs" />
2727
<Compile Include="MetaMarshalling.fs" />
28+
<Compile Include="SilentPayments.fs" />
2829
<Compile Include="Program.fs" />
2930
</ItemGroup>
31+
<ItemGroup />
3032
<ItemGroup>
3133
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
3234
<PackageReference Include="NUnit" Version="3.13.3" />
@@ -38,6 +40,7 @@
3840
<ProjectReference Include="..\GWallet.Backend\GWallet.Backend.fsproj" />
3941
</ItemGroup>
4042
<ItemGroup>
43+
<Content Include="data\send_and_receive_test_vectors.json" />
4144
<EmbeddedResource Include="data\basicException.json" />
4245
<EmbeddedResource Include="data\customFSharpException.json" />
4346
<EmbeddedResource Include="data\realException.json" />
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
namespace GWallet.Backend.Tests
2+
3+
open System.IO
4+
open System.Reflection
5+
open System.Text.Json
6+
7+
open NUnit.Framework
8+
open NBitcoin
9+
10+
open GWallet.Backend.UtxoCoin
11+
12+
13+
type private TestInput =
14+
{
15+
TxId: string
16+
Vout: int
17+
ScriptSig: string
18+
TxInWitness: string
19+
ScriptPubKey: string
20+
PrivateKey: string
21+
}
22+
static member FromJsonElement(jsonElement: JsonElement) =
23+
{
24+
TxId = jsonElement.GetProperty("txid").GetString()
25+
Vout = jsonElement.GetProperty("vout").GetInt32()
26+
ScriptSig = jsonElement.GetProperty("scriptSig").GetString()
27+
TxInWitness = jsonElement.GetProperty("txinwitness").GetString()
28+
ScriptPubKey = jsonElement.GetProperty("prevout").GetProperty("scriptPubKey").GetProperty("hex").GetString()
29+
PrivateKey = jsonElement.GetProperty("private_key").GetString()
30+
}
31+
32+
[<TestFixture>]
33+
type SilentPayments() =
34+
35+
let executingAssembly = Assembly.GetExecutingAssembly()
36+
let binPath = executingAssembly.Location |> FileInfo
37+
let projectDirPath = Path.Combine(binPath.Directory.FullName, "..", "..", "..")
38+
let dataDir = Path.Combine(projectDirPath, "data") |> DirectoryInfo
39+
40+
[<Test>]
41+
member __.``Test creating outputs using test vectors from BIP-352``() =
42+
// https://github.com/bitcoin/bips/blob/master/bip-0352/send_and_receive_test_vectors.json
43+
44+
let testVectorsFileName = "send_and_receive_test_vectors.json"
45+
let testVectorsJson = JsonDocument.Parse(File.ReadAllText(Path.Combine(dataDir.FullName, testVectorsFileName)))
46+
47+
for testCase in testVectorsJson.RootElement.EnumerateArray() do
48+
let testCaseName = testCase.GetProperty("comment").GetString()
49+
let sending = testCase.GetProperty("sending").[0]
50+
let expectedOutputs =
51+
sending.GetProperty("expected").GetProperty("outputs").EnumerateArray()
52+
|> Seq.map (fun each -> each.EnumerateArray() |> Seq.toArray)
53+
|> Seq.toArray
54+
let given = sending.GetProperty "given"
55+
let inputs = given.GetProperty("vin").EnumerateArray() |> Seq.map TestInput.FromJsonElement |> Seq.toList
56+
let recipients =
57+
given.GetProperty("recipients").EnumerateArray()
58+
|> Seq.map (fun each -> each.GetString() |> SilentPaymentAddress.Decode)
59+
|> Seq.toList
60+
61+
if expectedOutputs.Length > 1 || (expectedOutputs.Length = 1 && expectedOutputs.[0].Length > 1) || recipients.Length > 1 then
62+
printfn "Skipping BIP-352 test case '%s'" testCaseName
63+
else
64+
printfn "Running BIP-352 test case '%s'" testCaseName
65+
66+
let expectedOutput = expectedOutputs.[0] |> Array.tryHead |> Option.map (fun elem -> elem.GetString())
67+
68+
let spInputs =
69+
inputs
70+
|> List.map (
71+
fun input ->
72+
let witness =
73+
match input.TxInWitness with
74+
| "" -> None
75+
| hex ->
76+
let stream = BitcoinStream(DataEncoders.Encoders.Hex.DecodeData hex)
77+
Some <| WitScript.Load stream
78+
let spInput =
79+
SilentPayments.convertToSilentPaymentInput
80+
(Script.FromHex input.ScriptPubKey)
81+
(DataEncoders.Encoders.Hex.DecodeData input.ScriptSig)
82+
witness
83+
input, spInput)
84+
85+
let maybePrivateKeys, outpoints =
86+
spInputs
87+
|> List.choose
88+
(fun (input, spInput) ->
89+
let privKey = new Key(DataEncoders.Encoders.Hex.DecodeData input.PrivateKey)
90+
let outPoint = OutPoint(uint256.Parse input.TxId, input.Vout)
91+
match spInput with
92+
| InputForSharedSecretDerivation(_pubKey) ->
93+
let isTapRoot = (Script.FromHex input.ScriptPubKey).IsScriptType ScriptType.Taproot
94+
Some (Some(privKey, isTapRoot), outPoint)
95+
| InputJustForSpending ->
96+
Some(None, outPoint)
97+
| _ -> None)
98+
|> List.unzip
99+
100+
let privateKeys = maybePrivateKeys |> List.choose id
101+
102+
match privateKeys, expectedOutput with
103+
| [], None -> ()
104+
| [], Some _ ->
105+
Assert.Fail(sprintf "No inputs for shared secret derivation in test case '%s'" testCaseName)
106+
| _, Some expectedOutputString ->
107+
let output = SilentPayments.createOutput privateKeys outpoints recipients.[0]
108+
let outputString = output.GetEncoded() |> DataEncoders.Encoders.Hex.EncodeData
109+
Assert.AreEqual(expectedOutputString, outputString, sprintf "Failure in test case '%s'" testCaseName)
110+
| _, None ->
111+
Assert.Throws(fun () -> SilentPayments.createOutput privateKeys outpoints recipients.[0] |> ignore)
112+
|> ignore

0 commit comments

Comments
 (0)