Implement SignTx
MODIFY
WalletWasabi.Tests/HwiTests/DeviceConnectedTests/Katas.cs
WalletWasabi.Tests/HwiTests/DeviceConnectedTests/Katas.cs
@@ -38,17 +38,25 @@ namespace WalletWasabi.Tests.HwiTests.DeviceConnectedTests
// displayaddress request: refuse
// displayaddress request: confirm
// displayaddress request: confirm
+ // signtx request: refuse
+ // signtx request: confirm
//
// --- USER INTERACTIONS ---
- var client = new HwiClient(Network.Main);
+ var network = Network.Main;
+ var client = new HwiClient(network);
using (var cts = new CancellationTokenSource(ReasonableRequestTimeout))
{
var enumerate = await client.EnumerateAsync(cts.Token);
+ Assert.Single(enumerate);
HwiEnumerateEntry entry = enumerate.Single();
+ Assert.NotNull(entry.Path);
+ Assert.True(entry.Type.HasValue);
+ Assert.True(entry.Fingerprint.HasValue);
string devicePath = entry.Path;
HardwareWalletVendors deviceType = entry.Type.Value;
+ HDFingerprint fingerprint = entry.Fingerprint.Value;
await Assert.ThrowsAsync<HwiException>(async () => await client.SetupAsync(deviceType, devicePath, cts.Token));
@@ -80,16 +88,58 @@ namespace WalletWasabi.Tests.HwiTests.DeviceConnectedTests
Assert.NotNull(address1);
Assert.NotNull(address2);
Assert.NotEqual(address1, address2);
- var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, client.Network);
- var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, client.Network);
+ var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
+ var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
Assert.Equal(expectedAddress1, address1);
Assert.Equal(expectedAddress2, address2);
- // ToDo: signmessage
- // ToDo: signtx
+ // USER: REFUSE
+ PSBT psbt = BuildPsbt(network, fingerprint, xpub1, keyPath1);
+ var ex = await Assert.ThrowsAsync<HwiException>(async () => await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token));
+ Assert.Equal(HwiErrorCode.ActionCanceled, ex.ErrorCode);
+
+ // USER: CONFIRM
+ PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token);
+
+ Transaction signeTx = signedPsbt.GetOriginalTransaction();
+ Assert.Equal(psbt.GetOriginalTransaction().GetHash(), signeTx.GetHash());
+
+ var checkResult = signeTx.Check();
+ Assert.Equal(TransactionCheckResult.Success, checkResult);
+
// ToDo: --fingerprint
// ToDo: --password
}
}
+
+ private static PSBT BuildPsbt(Network network, HDFingerprint fingerprint, ExtPubKey xpub, KeyPath xpubKeyPath)
+ {
+ var deriveSendFromKeyPath = new KeyPath("1/0");
+ var deriveSendToKeyPath = new KeyPath("0/0");
+
+ KeyPath sendFromKeyPath = xpubKeyPath.Derive(deriveSendFromKeyPath);
+ KeyPath sendToKeyPath = xpubKeyPath.Derive(deriveSendToKeyPath);
+
+ PubKey sendFromPubKey = xpub.Derive(deriveSendFromKeyPath).PubKey;
+ PubKey sendToPubKey = xpub.Derive(deriveSendToKeyPath).PubKey;
+
+ BitcoinAddress sendFromAddress = sendFromPubKey.GetAddress(ScriptPubKeyType.Segwit, network);
+ BitcoinAddress sendToAddress = sendToPubKey.GetAddress(ScriptPubKeyType.Segwit, network);
+
+ TransactionBuilder builder = network.CreateTransactionBuilder();
+ builder = builder.AddCoins(new Coin(uint256.One, 0, Money.Coins(1), sendFromAddress.ScriptPubKey));
+ builder.Send(sendToAddress.ScriptPubKey, Money.Coins(0.99999m));
+ Transaction tx = builder
+ .SendFees(Money.Coins(0.00001m))
+ .BuildTransaction(false);
+ PSBT psbt = builder.BuildPSBT(false);
+
+ var rootKeyPath1 = new RootedKeyPath(fingerprint, sendFromKeyPath);
+ var rootKeyPath2 = new RootedKeyPath(fingerprint, sendToKeyPath);
+
+ psbt.AddKeyPath(sendFromPubKey, rootKeyPath1, sendFromAddress.ScriptPubKey);
+ psbt.AddKeyPath(sendToPubKey, rootKeyPath2, sendToAddress.ScriptPubKey);
+ return psbt;
+ }
}
}

MODIFY
WalletWasabi/Hwi2/HwiClient.cs
WalletWasabi/Hwi2/HwiClient.cs
@@ -119,6 +119,26 @@ namespace WalletWasabi.Hwi2
return address as BitcoinWitPubKeyAddress;
}
+ public async Task<PSBT> SignTxAsync(HardwareWalletVendors deviceType, string devicePath, PSBT psbt, CancellationToken cancel)
+ {
+ var psbtString = psbt.ToBase64();
+
+ var response = await SendCommandAsync(
+ options: new[] { HwiOption.DevicePath(devicePath), HwiOption.DeviceType(deviceType) },
+ command: HwiCommands.SignTx,
+ commandArguments: psbtString,
+ cancel).ConfigureAwait(false);
+
+ PSBT signedPsbt = HwiParser.ParsePsbt(response, Network);
+
+ if (!signedPsbt.IsAllFinalized())
+ {
+ signedPsbt.Finalize();
+ }
+
+ return signedPsbt;
+ }
+
public async Task WipeAsync(HardwareWalletVendors deviceType, string devicePath, CancellationToken cancel)
{
await SendCommandAsync(

MODIFY
WalletWasabi/Hwi2/Parsers/HwiParser.cs
WalletWasabi/Hwi2/Parsers/HwiParser.cs
@@ -176,7 +176,27 @@ namespace WalletWasabi.Hwi2.Parsers
}
else
{
- throw new FormatException($"Could not parse extpubkey: {json}");
+ throw new FormatException($"Could not parse address: {json}");
+ }
+ }
+
+ public static PSBT ParsePsbt(string json, Network network)
+ {
+ // HWI does not support regtest, so the parsing would fail here.
+ if (network == Network.RegTest)
+ {
+ network = Network.TestNet;
+ }
+
+ if (JsonHelpers.TryParseJToken(json, out JToken token))
+ {
+ var psbtString = token["psbt"]?.ToString()?.Trim() ?? null;
+ var psbt = PSBT.Parse(psbtString, network);
+ return psbt;
+ }
+ else
+ {
+ throw new FormatException($"Could not parse PSBT: {json}");
}
}

Implement SignTx