resolve bip353 hrns over tcp via the socks proxy when one is configured
What changed, and why it matters
This commit changes Sparrow Wallet so that BIP353 human-readable payment names (like a Bitcoin email address) are resolved through the user's configured SOCKS proxy instead of directly over the internet. It also updates a related database field from seconds to milliseconds for storing key/seed creation times. The main security angle is privacy: without the proxy, DNS-style lookups for payment recipients could leak the recipient name and the user's IP address to DNS servers or observers. The change routes that traffic through Tor or another proxy if one is set. The database rename is a code-quality change and does not appear to be a security fix by itself.
Treat as a privacy-hardening change. Verify the drongo submodule diff to confirm the SOCKS proxy is used for the entire TCP DNS resolution path and that no fallback to direct resolution remains. No urgent security patch action is indicated, but users relying on Tor/proxy should ensure they upgrade to avoid leaking BIP353 lookup metadata.
Security signals we found
BIP353 DNS payment resolution now routed through configured SOCKS proxy
Potential privacy leak of recipient HRN and client IP address mitigated when proxy is configured
No direct cryptographic or authentication fix visible in diff
Database parameter rename from seconds to milliseconds does not alter schema
Evidence from the diff
The commit modifies DnsPaymentResolver calls in SendToManyDialog.java and PaymentController.java to pass AppServices.getProxy(), causing BIP353 HRN resolution to use a configured SOCKS proxy. The drongo submodule bump likely contains the underlying resolver implementation change. Separately, KeystoreDao.java renames the creationTimeSeconds parameter/argument to creationTimeMillis and switches callers from getCreationTimeSeconds() to getCreationTimeMillis(); the SQL column name remains creationTimeSeconds, so this is a naming/API alignment change rather than a schema or security fix.
Changed components
BIP353 DNS payment resolver (drongo submodule)SendToManyDialog.javaPaymentController.javaKeystoreDao.javaInspect captured patch +15 / −15
### drongo
@@ -1 +1 @@
-Subproject commit 4407ca734298a0ed19f323ef39569d17779fe26a
+Subproject commit 203cfc191813a23b2b441b467375f3ce9c9272ca
### src/main/java/com/sparrowwallet/sparrow/control/SendToManyDialog.java
@@ -562,7 +562,7 @@ public Payment toPayment(String label, long value, boolean sendMax) throws DnsPa
DnsPayment dnsPayment = DnsPaymentCache.getDnsPayment(hrn);
if(dnsPayment == null) {
DnsPaymentResolver resolver = new DnsPaymentResolver(hrn);
- Optional<DnsPayment> optDnsPayment = resolver.resolve();
+ Optional<DnsPayment> optDnsPayment = resolver.resolve(AppServices.getProxy());
if(optDnsPayment.isPresent()) {
dnsPayment = optDnsPayment.get();
if(dnsPayment.hasAddress()) {
### src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java
@@ -23,17 +23,17 @@ public interface KeystoreDao {
@SqlUpdate("insert into masterPrivateExtendedKey (privateKey, chainCode, initialisationVector, encryptedBytes, keySalt, deriver, crypter, creationTimeSeconds) values (?, ?, ?, ?, ?, ?, ?, ?)")
@GetGeneratedKeys("id")
- long insertMasterPrivateExtendedKey(byte[] privateKey, byte[] chainCode, byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, Integer deriver, Integer crypter, long creationTimeSeconds);
+ long insertMasterPrivateExtendedKey(byte[] privateKey, byte[] chainCode, byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, Integer deriver, Integer crypter, long creationTimeMillis);
@SqlUpdate("update masterPrivateExtendedKey set privateKey = ?, chainCode = ?, initialisationVector = ?, encryptedBytes = ?, keySalt = ?, deriver = ?, crypter = ?, creationTimeSeconds = ? where id = ?")
- void updateMasterPrivateExtendedKey(byte[] privateKey, byte[] chainCode, byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, Integer deriver, Integer crypter, long creationTimeSeconds, long id);
+ void updateMasterPrivateExtendedKey(byte[] privateKey, byte[] chainCode, byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, Integer deriver, Integer crypter, long creationTimeMillis, long id);
@SqlUpdate("insert into seed (type, mnemonicString, initialisationVector, encryptedBytes, keySalt, deriver, crypter, needsPassphrase, creationTimeSeconds) values (?, ?, ?, ?, ?, ?, ?, ?, ?)")
@GetGeneratedKeys("id")
- long insertSeed(int type, String mnemonicString, byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, Integer deriver, Integer crypter, boolean needsPassphrase, long creationTimeSeconds);
+ long insertSeed(int type, String mnemonicString, byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, Integer deriver, Integer crypter, boolean needsPassphrase, long creationTimeMillis);
@SqlUpdate("update seed set type = ?, mnemonicString = ?, initialisationVector = ?, encryptedBytes = ?, keySalt = ?, deriver = ?, crypter = ?, needsPassphrase = ?, creationTimeSeconds = ? where id = ?")
- void updateSeed(int type, String mnemonicString, byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, Integer deriver, Integer crypter, boolean needsPassphrase, long creationTimeSeconds, long id);
+ void updateSeed(int type, String mnemonicString, byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, Integer deriver, Integer crypter, boolean needsPassphrase, long creationTimeMillis, long id);
@SqlUpdate("update keystore set label = ? where id = ?")
void updateLabel(String label, long id);
@@ -48,10 +48,10 @@ default void addKeystores(Wallet wallet) {
MasterPrivateExtendedKey mpek = keystore.getMasterPrivateExtendedKey();
if(mpek.isEncrypted()) {
EncryptedData data = mpek.getEncryptedData();
- long id = insertMasterPrivateExtendedKey(null, null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), mpek.getCreationTimeSeconds());
+ long id = insertMasterPrivateExtendedKey(null, null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), mpek.getCreationTimeMillis());
mpek.setId(id);
} else {
- long id = insertMasterPrivateExtendedKey(mpek.getPrivateKey().getPrivKeyBytes(), mpek.getPrivateKey().getChainCode(), null, null, null, null, null, mpek.getCreationTimeSeconds());
+ long id = insertMasterPrivateExtendedKey(mpek.getPrivateKey().getPrivKeyBytes(), mpek.getPrivateKey().getChainCode(), null, null, null, null, null, mpek.getCreationTimeMillis());
mpek.setId(id);
}
}
@@ -60,10 +60,10 @@ default void addKeystores(Wallet wallet) {
DeterministicSeed seed = keystore.getSeed();
if(seed.isEncrypted()) {
EncryptedData data = seed.getEncryptedData();
- long id = insertSeed(seed.getType().ordinal(), null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), seed.needsPassphrase(), seed.getCreationTimeSeconds());
+ long id = insertSeed(seed.getType().ordinal(), null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), seed.needsPassphrase(), seed.getCreationTimeMillis());
seed.setId(id);
} else {
- long id = insertSeed(seed.getType().ordinal(), seed.getMnemonicString(true).asString(), null, null, null, null, null, seed.needsPassphrase(), seed.getCreationTimeSeconds());
+ long id = insertSeed(seed.getType().ordinal(), seed.getMnemonicString(true).asString(), null, null, null, null, null, seed.needsPassphrase(), seed.getCreationTimeMillis());
seed.setId(id);
}
}
@@ -86,19 +86,19 @@ default void updateKeystoreEncryption(Keystore keystore) {
MasterPrivateExtendedKey mpek = keystore.getMasterPrivateExtendedKey();
if(mpek.isEncrypted()) {
EncryptedData data = mpek.getEncryptedData();
- updateMasterPrivateExtendedKey(null, null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), mpek.getCreationTimeSeconds(), mpek.getId());
+ updateMasterPrivateExtendedKey(null, null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), mpek.getCreationTimeMillis(), mpek.getId());
} else {
- updateMasterPrivateExtendedKey(mpek.getPrivateKey().getPrivKeyBytes(), mpek.getPrivateKey().getChainCode(), null, null, null, null, null, mpek.getCreationTimeSeconds(), mpek.getId());
+ updateMasterPrivateExtendedKey(mpek.getPrivateKey().getPrivKeyBytes(), mpek.getPrivateKey().getChainCode(), null, null, null, null, null, mpek.getCreationTimeMillis(), mpek.getId());
}
}
if(keystore.hasSeed()) {
DeterministicSeed seed = keystore.getSeed();
if(seed.isEncrypted()) {
EncryptedData data = seed.getEncryptedData();
- updateSeed(seed.getType().ordinal(), null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), seed.needsPassphrase(), seed.getCreationTimeSeconds(), seed.getId());
+ updateSeed(seed.getType().ordinal(), null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), seed.needsPassphrase(), seed.getCreationTimeMillis(), seed.getId());
} else {
- updateSeed(seed.getType().ordinal(), seed.getMnemonicString(true).asString(), null, null, null, null, null, seed.needsPassphrase(), seed.getCreationTimeSeconds(), seed.getId());
+ updateSeed(seed.getType().ordinal(), seed.getMnemonicString(true).asString(), null, null, null, null, null, seed.needsPassphrase(), seed.getCreationTimeMillis(), seed.getId());
}
}
}
### src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java
@@ -961,7 +961,7 @@ protected Task<Optional<DnsPayment>> createTask() {
@Override
protected Optional<DnsPayment> call() throws Exception {
DnsPaymentResolver resolver = new DnsPaymentResolver(hrn);
- return resolver.resolve();
+ return resolver.resolve(AppServices.getProxy());
}
};
}Why this scored 38/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.