Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
(builtins.match ".*nginx.conf.template$" path != null)
|| (builtins.match ".*\\.mmdb$" path != null)
|| (builtins.match ".*\\.html$" path != null)
|| (builtins.match ".*/payjoin-test-utils/fixtures/.*" path != null)
|| (craneLibVersions.msrv.filterCargoSources path type);
name = "source";
};
Expand Down
2 changes: 1 addition & 1 deletion payjoin-ffi/csharp/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ public void TestFfiValidation()
using var receiver = receiveTransition.Save(recvPersister);
using var pjUri = receiver.PjUri();

var psbt = PayjoinMethods.OriginalPsbt();
var psbt = TestVectors.OriginalPsbt;

var feeRateOutOfRange = Assert.Throws<SenderInputException.FfiValidation>(() =>
{
Expand Down
6 changes: 6 additions & 0 deletions payjoin-ffi/csharp/Payjoin.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
<Compile Remove="src/**/*.cs" />
</ItemGroup>

<ItemGroup>
<None Include="../../payjoin-test-utils/fixtures/*"
Link="fixtures/%(Filename)%(Extension)"
CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<!-- Include native library - copied to output directory for testing -->
<Target Name="CopyNativeLibraries" AfterTargets="Build">
<Copy SourceFiles="lib/libpayjoin_ffi.so" DestinationFolder="$(TargetDir)" Condition="Exists('lib/libpayjoin_ffi.so')" />
Expand Down
12 changes: 12 additions & 0 deletions payjoin-ffi/csharp/TestVectors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Payjoin.Tests;

internal static class TestVectors
{
private static string Read(string name) =>
File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "fixtures", name));

internal static readonly string OriginalPsbt = Read("original-psbt.base64");
private static readonly string OhttpKeysHex = Read("ohttp-keys.hex").Trim();

internal static byte[] OhttpKeys => Convert.FromHexString(OhttpKeysHex);
}
74 changes: 19 additions & 55 deletions payjoin-ffi/csharp/UnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,12 @@ public void ValidUrisWithDifferentAddressesAndEndpoints(string address, string p

public class PersistenceTests
{
private static readonly byte[] OhttpKeysData = new byte[]
{
0x01, 0x00, 0x16, 0x04, 0xba, 0x48, 0xc4, 0x9c, 0x3d, 0x4a,
0x92, 0xa3, 0xad, 0x00, 0xec, 0xc6, 0x3a, 0x02, 0x4d, 0xa1,
0x0c, 0xed, 0x02, 0x18, 0x0c, 0x73, 0xec, 0x12, 0xd8, 0xa7,
0xad, 0x2c, 0xc9, 0x1b, 0xb4, 0x83, 0x82, 0x4f, 0xe2, 0xbe,
0xe8, 0xd2, 0x8b, 0xfe, 0x2e, 0xb2, 0xfc, 0x64, 0x53, 0xbc,
0x4d, 0x31, 0xcd, 0x85, 0x1e, 0x8a, 0x65, 0x40, 0xe8, 0x6c,
0x53, 0x82, 0xaf, 0x58, 0x8d, 0x37, 0x09, 0x57, 0x00, 0x04,
0x00, 0x01, 0x00, 0x03,
};

[Fact]
public void ReceiverPersistence()
{
var persister = new InMemoryReceiverPersister();
var address = "tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4";
var ohttpKeys = OhttpKeys.Decode(OhttpKeysData);
var ohttpKeys = OhttpKeys.Decode(TestVectors.OhttpKeys);

var builder = new ReceiverBuilder(address, "https://example.com", ohttpKeys);
var transition = builder.Build();
Expand All @@ -78,15 +66,15 @@ public void SenderPersistence()
{
var receiverPersister = new InMemoryReceiverPersister();
var address = "2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK";
var ohttpKeys = OhttpKeys.Decode(OhttpKeysData);
var ohttpKeys = OhttpKeys.Decode(TestVectors.OhttpKeys);

var receiver = new ReceiverBuilder(address, "https://example.com", ohttpKeys)
.Build()
.Save(receiverPersister);
var uri = receiver.PjUri();

var senderPersister = new InMemorySenderPersister();
var psbt = PayjoinMethods.OriginalPsbt();
var psbt = TestVectors.OriginalPsbt;

var withReplyKey = new SenderBuilder(psbt, uri)
.BuildRecommended(1000)
Expand All @@ -103,7 +91,7 @@ public async Task ReceiverPersistenceAsync()
{
var persister = new InMemoryReceiverPersisterAsync();
var address = "tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4";
var ohttpKeys = OhttpKeys.Decode(OhttpKeysData);
var ohttpKeys = OhttpKeys.Decode(TestVectors.OhttpKeys);

var builder = new ReceiverBuilder(address, "https://example.com", ohttpKeys);
var transition = builder.Build();
Expand All @@ -120,15 +108,15 @@ public async Task SenderPersistenceAsync()
{
var receiverPersister = new InMemoryReceiverPersisterAsync();
var address = "2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK";
var ohttpKeys = OhttpKeys.Decode(OhttpKeysData);
var ohttpKeys = OhttpKeys.Decode(TestVectors.OhttpKeys);

var receiver = await new ReceiverBuilder(address, "https://example.com", ohttpKeys)
.Build()
.SaveAsync(receiverPersister);
var uri = receiver.PjUri();

var senderPersister = new InMemorySenderPersisterAsync();
var psbt = PayjoinMethods.OriginalPsbt();
var psbt = TestVectors.OriginalPsbt;

var withReplyKey = await new SenderBuilder(psbt, uri)
.BuildRecommended(1000)
Expand All @@ -143,24 +131,12 @@ public async Task SenderPersistenceAsync()

public class CancelTests
{
private static readonly byte[] OhttpKeysData = new byte[]
{
0x01, 0x00, 0x16, 0x04, 0xba, 0x48, 0xc4, 0x9c, 0x3d, 0x4a,
0x92, 0xa3, 0xad, 0x00, 0xec, 0xc6, 0x3a, 0x02, 0x4d, 0xa1,
0x0c, 0xed, 0x02, 0x18, 0x0c, 0x73, 0xec, 0x12, 0xd8, 0xa7,
0xad, 0x2c, 0xc9, 0x1b, 0xb4, 0x83, 0x82, 0x4f, 0xe2, 0xbe,
0xe8, 0xd2, 0x8b, 0xfe, 0x2e, 0xb2, 0xfc, 0x64, 0x53, 0xbc,
0x4d, 0x31, 0xcd, 0x85, 0x1e, 0x8a, 0x65, 0x40, 0xe8, 0x6c,
0x53, 0x82, 0xaf, 0x58, 0x8d, 0x37, 0x09, 0x57, 0x00, 0x04,
0x00, 0x01, 0x00, 0x03,
};

[Fact]
public void ReceiverCancelFromInitialized()
{
var persister = new InMemoryReceiverPersister();
var address = "tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4";
var ohttpKeys = OhttpKeys.Decode(OhttpKeysData);
var ohttpKeys = OhttpKeys.Decode(TestVectors.OhttpKeys);

var initialized = new ReceiverBuilder(address, "https://example.com", ohttpKeys)
.Build()
Expand All @@ -179,7 +155,7 @@ public async Task ReceiverCancelFromInitializedAsync()
{
var persister = new InMemoryReceiverPersisterAsync();
var address = "tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4";
var ohttpKeys = OhttpKeys.Decode(OhttpKeysData);
var ohttpKeys = OhttpKeys.Decode(TestVectors.OhttpKeys);

var initialized = await new ReceiverBuilder(address, "https://example.com", ohttpKeys)
.Build()
Expand All @@ -198,15 +174,15 @@ public void SenderCancelFromWithReplyKey()
{
var receiverPersister = new InMemoryReceiverPersister();
var address = "2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK";
var ohttpKeys = OhttpKeys.Decode(OhttpKeysData);
var ohttpKeys = OhttpKeys.Decode(TestVectors.OhttpKeys);

var receiver = new ReceiverBuilder(address, "https://example.com", ohttpKeys)
.Build()
.Save(receiverPersister);
var uri = receiver.PjUri();

var senderPersister = new InMemorySenderPersister();
var psbt = PayjoinMethods.OriginalPsbt();
var psbt = TestVectors.OriginalPsbt;
var withReplyKey = new SenderBuilder(psbt, uri)
.BuildRecommended(1000)
.Save(senderPersister);
Expand All @@ -228,15 +204,15 @@ public async Task SenderCancelFromWithReplyKeyAsync()
{
var receiverPersister = new InMemoryReceiverPersisterAsync();
var address = "2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK";
var ohttpKeys = OhttpKeys.Decode(OhttpKeysData);
var ohttpKeys = OhttpKeys.Decode(TestVectors.OhttpKeys);

var receiver = await new ReceiverBuilder(address, "https://example.com", ohttpKeys)
.Build()
.SaveAsync(receiverPersister);
var uri = receiver.PjUri();

var senderPersister = new InMemorySenderPersisterAsync();
var psbt = PayjoinMethods.OriginalPsbt();
var psbt = TestVectors.OriginalPsbt;
var withReplyKey = await new SenderBuilder(psbt, uri)
.BuildRecommended(1000)
.SaveAsync(senderPersister);
Expand All @@ -256,21 +232,9 @@ public async Task SenderCancelFromWithReplyKeyAsync()

public class ValidationTests
{
private static readonly byte[] OhttpKeysData = new byte[]
{
0x01, 0x00, 0x16, 0x04, 0xba, 0x48, 0xc4, 0x9c, 0x3d, 0x4a,
0x92, 0xa3, 0xad, 0x00, 0xec, 0xc6, 0x3a, 0x02, 0x4d, 0xa1,
0x0c, 0xed, 0x02, 0x18, 0x0c, 0x73, 0xec, 0x12, 0xd8, 0xa7,
0xad, 0x2c, 0xc9, 0x1b, 0xb4, 0x83, 0x82, 0x4f, 0xe2, 0xbe,
0xe8, 0xd2, 0x8b, 0xfe, 0x2e, 0xb2, 0xfc, 0x64, 0x53, 0xbc,
0x4d, 0x31, 0xcd, 0x85, 0x1e, 0x8a, 0x65, 0x40, 0xe8, 0x6c,
0x53, 0x82, 0xaf, 0x58, 0x8d, 0x37, 0x09, 0x57, 0x00, 0x04,
0x00, 0x01, 0x00, 0x03,
};

private static PjUri CreateV2PjUri()
{
var ohttpKeys = OhttpKeys.Decode(OhttpKeysData);
var ohttpKeys = OhttpKeys.Decode(TestVectors.OhttpKeys);
var persister = new InMemoryReceiverPersister();
using var builder = new ReceiverBuilder("2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK", "https://example.com", ohttpKeys);
using var transition = builder.Build();
Expand All @@ -281,7 +245,7 @@ private static PjUri CreateV2PjUri()
[Fact]
public void ReceiverBuilderRejectsBadAddress()
{
var ohttpKeys = OhttpKeys.Decode(OhttpKeysData);
var ohttpKeys = OhttpKeys.Decode(TestVectors.OhttpKeys);

Assert.Throws<ReceiverBuilderException.InvalidAddress>(() =>
{
Expand Down Expand Up @@ -322,7 +286,7 @@ public void SenderBuilderRejectsBadPsbt()
[Fact]
public void ReceiverBuilderRejectsAmountOverflow()
{
var ohttpKeys = OhttpKeys.Decode(OhttpKeysData);
var ohttpKeys = OhttpKeys.Decode(TestVectors.OhttpKeys);
using var builder = new ReceiverBuilder(
"tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4",
"https://example.com",
Expand All @@ -337,7 +301,7 @@ public void ReceiverBuilderRejectsAmountOverflow()
[Fact]
public void ReceiverBuilderRejectsExpirationOverflow()
{
var ohttpKeys = OhttpKeys.Decode(OhttpKeysData);
var ohttpKeys = OhttpKeys.Decode(TestVectors.OhttpKeys);
using var builder = new ReceiverBuilder(
"tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4",
"https://example.com",
Expand All @@ -353,7 +317,7 @@ public void ReceiverBuilderRejectsExpirationOverflow()
public void SenderBuilderWithAdditionalFeeRejectsFeeContributionOverflow()
{
using var uri = CreateV2PjUri();
var psbt = PayjoinMethods.OriginalPsbt();
var psbt = TestVectors.OriginalPsbt;
using var builder = new SenderBuilder(psbt, uri);

var ex = Assert.Throws<SenderInputException.FfiValidation>(() =>
Expand All @@ -368,7 +332,7 @@ public void SenderBuilderWithAdditionalFeeRejectsFeeContributionOverflow()
public void SenderBuilderWithAdditionalFeeRejectsFeeRateOverflow()
{
using var uri = CreateV2PjUri();
var psbt = PayjoinMethods.OriginalPsbt();
var psbt = TestVectors.OriginalPsbt;
using var builder = new SenderBuilder(psbt, uri);

var ex = Assert.Throws<SenderInputException.FfiValidation>(() =>
Expand All @@ -383,7 +347,7 @@ public void SenderBuilderWithAdditionalFeeRejectsFeeRateOverflow()
public void SenderBuilderNonIncentivizingRejectsFeeRateOverflow()
{
using var uri = CreateV2PjUri();
var psbt = PayjoinMethods.OriginalPsbt();
var psbt = TestVectors.OriginalPsbt;
using var builder = new SenderBuilder(psbt, uri);

var ex = Assert.Throws<SenderInputException.FfiValidation>(() =>
Expand Down
6 changes: 4 additions & 2 deletions payjoin-ffi/dart/contrib/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ use_lockfile Cargo-recent.lock

cd "$REPO_ROOT/payjoin-ffi/dart"

echo "==> Cleaning nested Cargo.lock..."
rm -f native/Cargo.lock
echo "==> Seeding native wrapper dependencies from the maintained lockfile..."
# The wrapper is a separate workspace. Preserve the tested dependency versions
# when Cargo adds its wrapper package during the native build hook.
cp "$REPO_ROOT/Cargo.lock" native/Cargo.lock

echo "==> Generating FFI bindings..."
bash ./scripts/generate_bindings.sh
Expand Down
18 changes: 3 additions & 15 deletions payjoin-ffi/dart/test/fetch_ohttp_keys_http_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import 'package:test/test.dart';
import 'package:payjoin/http.dart' as payjoin_http;
import 'package:payjoin/payjoin.dart' as payjoin;

import 'fixtures.dart' as fixtures;

const _localhostCertPem =
'-----BEGIN CERTIFICATE-----\n'
'MIIBmDCCAT+gAwIBAgIUZmuZcOJ7AKPKxmXUdl8mALQqLjkwCgYIKoZIzj0EAwIw\n'
Expand All @@ -28,21 +30,7 @@ const _localhostKeyPem =
'1f17kOSt4gPp9WH21EiioVJREvUvyraYsjEuXhbkvgdEm4+QpUKJQj3+\n'
'-----END PRIVATE KEY-----';

final _ohttpKeysBytes = Uint8List.fromList(
_hexToBytes(
'01001604ba48c49c3d4a92a3ad00ecc63a024da10ced02180c73ec12d8a7ad'
'2cc91bb483824fe2bee8d28bfe2eb2fc6453bc4d31cd851e8a6540e86c5382'
'af588d370957000400010003',
),
);

List<int> _hexToBytes(String hex) {
final bytes = <int>[];
for (var i = 0; i < hex.length; i += 2) {
bytes.add(int.parse(hex.substring(i, i + 2), radix: 16));
}
return bytes;
}
final _ohttpKeysBytes = fixtures.ohttpKeys;

Uint8List _localhostCertDer() => base64.decode(
_localhostCertPem.replaceAll(RegExp(r'-----[^-]+-----|\s'), ''),
Expand Down
19 changes: 19 additions & 0 deletions payjoin-ffi/dart/test/fixtures.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';

import 'package:convert/convert.dart';

// Resolve from the package source so fixture loading is independent of the
// test runner's working directory. These tests run from the repository checkout.
final _fixtures = Isolate.resolvePackageUriSync(
Uri.parse('package:payjoin/payjoin.dart'),
)!.resolve('../../../payjoin-test-utils/fixtures/');

final originalPsbt = File.fromUri(_fixtures.resolve('original-psbt.base64'))
.readAsStringSync();
final _ohttpKeysHex = File.fromUri(_fixtures.resolve('ohttp-keys.hex'))
.readAsStringSync()
.trim();

Uint8List get ohttpKeys => Uint8List.fromList(hex.decode(_ohttpKeysHex));
3 changes: 2 additions & 1 deletion payjoin-ffi/dart/test/test_payjoin_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "package:payjoin/http.dart" as payjoin_http;
import "package:payjoin/payjoin.dart" as payjoin;
import "package:payjoin/test_utils.dart" as test_utils;

import "fixtures.dart" as fixtures;
import "utils.dart";

late test_utils.BitcoindEnv env;
Expand Down Expand Up @@ -477,7 +478,7 @@ void main() {
ohttpKeys: ohttpKeys,
).build().save(persister: recvPersister).pjUri();

final psbt = test_utils.originalPsbt();
final psbt = fixtures.originalPsbt;
// Large enough to overflow fee * weight but still parsable as Dart int.
const overflowFeeRate = 5000000000000; // sat/kwu
expect(
Expand Down
Loading
Loading