Skip to content

Commit dc90149

Browse files
authored
Feat(tests): preimage derivation tests (#151)
* chore(tests): Move SimpleSeedWallet and InMemoryWalletStorage.cs to Unit Tests project. Reference tests projects in e2e project * feat(tests): add more flexible overload for SimpleSeedWallet creation * feat(test): modify SimpleSeedWallet.cs so it allows signing without IClientTransport * fix: deterministic signing * feat: Preimage tests and test vectors * fix(tests): test vector semantics
1 parent a2f67a6 commit dc90149

21 files changed

Lines changed: 511 additions & 94 deletions

NArk.Abstractions/Wallets/IRemoteSignerTransport.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,20 @@ Task<MusigPartialSignature> SignMusigAsync(
7979
/// signature.
8080
/// </summary>
8181
/// <remarks>
82-
/// Implementations <b>SHOULD</b> sign with <c>aux_rand</c> set to null (or all-zero) so the
82+
/// Implementations <b>MUST</b> sign with <c>aux_rand</c> set to 32 zero bytes so the
8383
/// signature is deterministic per <c>(key, hash)</c>. The SDK relies on this for the
8484
/// swap-preimage recovery scheme in <c>SwapsManagementService.DerivePreimageAsync</c>:
8585
/// same descriptor + same wallet must produce the same signature, otherwise a restored
8686
/// wallet that rediscovers an outstanding swap will re-derive a different preimage and
87-
/// fail to claim the VHTLC. Implementations that randomise <c>aux_rand</c> (e.g. for
88-
/// side-channel resistance on a hardware signer) break that contract, and remote-signed
89-
/// wallets on such transports will not recover outstanding swap preimages from seed.
87+
/// fail to claim the VHTLC.
88+
/// <para>
89+
/// In NBitcoin.Secp256k1, use <c>ECPrivKey.SignBIP340(msg, new byte[32])</c> — the
90+
/// no-auxData overload <c>SignBIP340(msg)</c> draws from the system RNG on every call
91+
/// and is therefore non-deterministic.
92+
/// </para>
93+
/// Implementations that randomise <c>aux_rand</c> (e.g. for side-channel resistance on a
94+
/// hardware signer) break that contract, and remote-signed wallets on such transports will
95+
/// not recover outstanding swap preimages from seed.
9096
/// </remarks>
9197
/// <param name="walletId">The wallet whose key signs.</param>
9298
/// <param name="descriptor">The descriptor identifying the signing key.</param>

NArk.Core/Wallet/SigningSources/Bip39SigningSource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public Task<ECPubKey> GetPubKeyAsync(OutputDescriptor descriptor, CancellationTo
5353
public Task<(ECXOnlyPubKey, SecpSchnorrSignature)> SignAsync(OutputDescriptor descriptor, uint256 hash, CancellationToken cancellationToken = default)
5454
{
5555
var privKey = DerivePrivateKey(descriptor);
56-
return Task.FromResult((privKey.CreateXOnlyPubKey(), privKey.SignBIP340(hash.ToBytes())));
56+
return Task.FromResult((privKey.CreateXOnlyPubKey(), privKey.SignBIP340(hash.ToBytes(), new byte[32])));
5757
}
5858

5959
public Task<MusigPubNonce> GenerateNoncesAsync(OutputDescriptor descriptor, MusigContext context,

NArk.Core/Wallet/SigningSources/NsecSigningSource.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,7 @@ public Task<ECPubKey> GetPubKeyAsync(OutputDescriptor descriptor, CancellationTo
6969
public Task<(ECXOnlyPubKey, SecpSchnorrSignature)> SignAsync(OutputDescriptor descriptor, uint256 hash, CancellationToken cancellationToken = default)
7070
{
7171
EnsureMatches(descriptor);
72-
if (!_privateKey.TrySignBIP340(hash.ToBytes(), null, out var sig))
73-
throw new InvalidOperationException("Failed to sign data");
72+
var sig = _privateKey.SignBIP340(hash.ToBytes(), new byte[32]);
7473
return Task.FromResult((_xOnlyPubKey, sig));
7574
}
7675

NArk.Swaps/Services/SwapsManagementService.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ public ISwapProvider ResolveProvider(SwapRoute route, string? preferredProviderI
117117
// the preimage and claim the VHTLC. Watch-only and remote-signer-less wallets fall
118118
// through to a random preimage.
119119
//
120-
// Local signing sources pass aux_rand=null to BIP-340, so signatures are deterministic.
120+
// Local signing sources MUST pass aux_rand=zeroes (32 zero bytes) to BIP-340 to produce
121+
// deterministic signatures. ECPrivKey.SignBIP340(msg) without an explicit auxData draws
122+
// from the system RNG on each call — pass SignBIP340(msg, new byte[32]) instead.
121123
// Remote-signer transports MUST honour the same convention or the preimage will rotate
122124
// per call and recovery will silently fail — see IRemoteSignerTransport.SignAsync.
123125
// Tag is protocol+provider scoped (Arkade brand, Boltz provider), not SDK-scoped, so any
@@ -144,7 +146,7 @@ internal static byte[] BuildPreimageMessage(OutputDescriptor descriptor, uint in
144146
return message;
145147
}
146148

147-
private async Task<byte[]> DerivePreimageAsync(
149+
internal async Task<byte[]> DerivePreimageAsync(
148150
string walletId, OutputDescriptor descriptor, uint index, CancellationToken cancellationToken)
149151
{
150152
var signer = await _walletProvider.GetSignerAsync(walletId, cancellationToken);

NArk.Tests.End2End/AssetTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using NArk.Tests.End2End.Common;
2121
using NArk.Tests.End2End.TestPersistance;
2222
using NArk.Abstractions.VTXOs;
23+
using NArk.Tests.Common;
2324
using NBitcoin;
2425

2526
namespace NArk.Tests.End2End.Core;

NArk.Tests.End2End/BoardingTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using NArk.Core.Transformers;
1414
using NArk.Abstractions.Safety;
1515
using NArk.Safety.AsyncKeyedLock;
16+
using NArk.Tests.Common;
1617
using NArk.Tests.End2End.Common;
1718
using NArk.Tests.End2End.TestPersistance;
1819
using NArk.Transport.GrpcClient;

NArk.Tests.End2End/BuilderStyleTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using NArk.Safety.AsyncKeyedLock;
99
using NArk.Core.Services;
1010
using NArk.Storage.EfCore.Hosting;
11+
using NArk.Tests.Common;
1112
using NArk.Tests.End2End.TestPersistance;
1213
using NBitcoin;
1314

NArk.Tests.End2End/Common/AssetTestHelpers.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using NArk.Core.Services;
99
using NArk.Core.Transformers;
1010
using NArk.Core.Transport;
11+
using NArk.Tests.Common;
1112
using NArk.Tests.End2End.TestPersistance;
1213

1314
namespace NArk.Tests.End2End.Common;

NArk.Tests.End2End/Common/FundedWalletHelper.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using NArk.Tests.End2End.Core;
1111
using NArk.Tests.End2End.TestPersistance;
1212
using NArk.Core.Transport;
13+
using NArk.Tests.Common;
1314
using NArk.Transport.GrpcClient;
1415

1516
namespace NArk.Tests.End2End.Common;

NArk.Tests.End2End/DelegationTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using NArk.Core.Models.Options;
1515
using NArk.Core.Services;
1616
using NArk.Core.Transformers;
17+
using NArk.Tests.Common;
1718
using NArk.Tests.End2End.Common;
1819
using NArk.Tests.End2End.Core;
1920
using NArk.Tests.End2End.TestPersistance;

0 commit comments

Comments
 (0)