add trezor safe 7 noise config implementation
What changed, and why it matters
This commit adds support for pairing the Trezor Safe 7 hardware wallet with Sparrow Wallet. It introduces a JavaFX dialog flow that asks the user to confirm pairing and enter a pairing code shown on the device. The change is a feature addition for a new hardware device and does not, on its own, appear to be a security vulnerability. The pairing secrets are stored in a local JSON file, similar to how the existing BitBox02 pairing is handled.
No immediate security action is required. As a defensive review, verify that TrezorFileNoiseConfig in the lark submodule stores only the intended pairing secret, uses appropriate file permissions, and that the pairing code entry dialog cannot be spoofed by another application. Review the lark submodule changes separately if they become available.
Security signals we found
New local secret storage file introduced (trezor.json under lark/)
User is prompted to confirm pairing and enter a device-shown code
Pairing success/failure is surfaced via UI dialogs
Static prompt flag made volatile (minor concurrency hardening)
No input validation, deserialization, or network changes visible in diff
Evidence from the diff
The patch extends Hwi.java to configure a TrezorFileNoiseConfig implementation for the Lark hardware-wallet communication library. It adds UI prompts for pairing confirmation, pairing-code entry, and success/failure notifications. The pairing data is persisted to Storage.getSparrowHome()/lark/trezor.json. The static isPromptActive flag is made volatile, which is a minor thread-safety improvement. No cryptographic code, input parsing, or privilege changes are visible in the diff.
Changed components
src/main/java/com/sparrowwallet/sparrow/io/Hwi.javaTrezor Safe 7 hardware wallet integrationLark library Trezor noise/pairing configInspect captured patch +82 / −2
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java b/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java
index ffa1074..baf426b 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java
@@ -11,12 +11,16 @@ import com.sparrowwallet.drongo.wallet.WalletModel;
import com.sparrowwallet.lark.DeviceException;
import com.sparrowwallet.lark.Lark;
import com.sparrowwallet.lark.bitbox02.BitBoxFileNoiseConfig;
+import com.sparrowwallet.lark.trezor.TrezorFileNoiseConfig;
import com.sparrowwallet.sparrow.AppServices;
+import com.sparrowwallet.sparrow.SparrowWallet;
import com.sparrowwallet.sparrow.control.BitBoxPairingDialog;
+import com.sparrowwallet.sparrow.control.TextfieldDialog;
import javafx.application.Platform;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
+import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,7 +30,9 @@ import javax.smartcardio.CardNotPresentException;
import java.io.File;
import java.nio.file.Path;
import java.util.*;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
public class Hwi {
@@ -34,8 +40,9 @@ public class Hwi {
private static final String HWI_HOME_DIR = "hwi";
private static final String LARK_HOME_DIR = "lark";
private static final String BITBOX_FILENAME = "bitbox02.json";
+ private static final String TREZOR_FILENAME = "trezor.json";
- private static boolean isPromptActive = false;
+ private static volatile boolean isPromptActive = false;
private final Set<byte[]> newDeviceRegistrations = new HashSet<>();
@@ -223,6 +230,7 @@ public class Hwi {
private Lark getLark(String passphrase, OutputDescriptor walletDescriptor, String walletName, byte[] walletRegistration) {
Lark lark = new Lark(AppServices.getHttpClientService());
lark.setBitBoxNoiseConfig(new BitBoxFxNoiseConfig());
+ lark.setTrezorNoiseConfig(new TrezorFxNoiseConfig());
if(passphrase != null) {
lark.setPassphrase(passphrase);
}
@@ -536,4 +544,76 @@ public class Hwi {
return confirmedDevice.get();
}
}
+
+ private static final class TrezorFxNoiseConfig extends TrezorFileNoiseConfig {
+ public TrezorFxNoiseConfig() {
+ super(Path.of(Storage.getSparrowHome().getAbsolutePath(), LARK_HOME_DIR, TREZOR_FILENAME).toFile());
+ }
+
+ @Override
+ public String promptForPairingCode() {
+ CompletableFuture<String> future = new CompletableFuture<>();
+
+ Platform.runLater(() -> {
+ TextfieldDialog textfieldDialog = new TextfieldDialog();
+ textfieldDialog.initOwner(AppServices.getActiveWindow());
+ textfieldDialog.setTitle("Enter Pairing Code");
+ textfieldDialog.setHeaderText("Enter the code shown on the device");
+ textfieldDialog.getDialogPane().setPrefWidth(300);
+ textfieldDialog.getEditor().setOnAction(_ -> textfieldDialog.setResult(textfieldDialog.getEditor().getText()));
+ textfieldDialog.getEditor().requestFocus();
+ textfieldDialog.showAndWait().ifPresentOrElse(future::complete, () -> future.complete(null));
+ });
+
+ try {
+ isPromptActive = true;
+ return future.get(); // Block until dialog is closed
+ } catch (InterruptedException | ExecutionException e) {
+ Thread.currentThread().interrupt();
+ return null;
+ } finally {
+ isPromptActive = false;
+ }
+ }
+
+ @Override
+ public boolean confirmPairing(String deviceInfo) {
+ CompletableFuture<ButtonType> future = new CompletableFuture<>();
+
+ Platform.runLater(() -> {
+ AppServices.showAlertDialog("Pairing Required", "Pair the " + deviceInfo + " with " + SparrowWallet.APP_NAME + "?",
+ Alert.AlertType.CONFIRMATION, ButtonType.YES, ButtonType.NO).ifPresentOrElse(future::complete, () -> future.complete(null));
+ });
+
+ try {
+ isPromptActive = true;
+ return future.get() == ButtonType.YES; // Block until dialog is closed
+ } catch (InterruptedException | ExecutionException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ } finally {
+ isPromptActive = false;
+ }
+ }
+
+ @Override
+ public void displayPairingCode(String code) {
+ super.displayPairingCode(code);
+ }
+
+ @Override
+ public String getAppName() {
+ return SparrowWallet.APP_NAME;
+ }
+
+ @Override
+ public void pairingFailed(String reason) {
+ Platform.runLater(() -> AppServices.showErrorDialog("Pairing Failed", "Pairing failed: " + reason));
+ }
+
+ @Override
+ public void pairingSuccessful(String deviceInfo) {
+ Platform.runLater(() -> AppServices.showSuccessDialog("Pairing Successful", "The " + deviceInfo + " has been successfully paired."));
+ }
+ }
}
Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.