implement persistence for sp wallets and related drongo changes
What changed, and why it matters
This commit adds database and file-saving support for a new type of Bitcoin wallet called 'silent payments' in Sparrow Wallet. It renames some internal policy labels, adds new database columns to store silent-payment data, and updates how wallets are read from and written to disk. There is no indication in the commit or supplied references that this fixes a security vulnerability.
No security action required; treat as a normal feature commit. If reviewing for release readiness, verify the new database migration runs cleanly on existing wallets and that the renamed PolicyType values are handled compatibly in both JSON and H2 persistence paths.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change implements persistence for silent-payment (SP) wallets. It renames PolicyType enum constants (SINGLE_SILENT_PAYMENTS -> SINGLE_SP, MULTI -> MULTI_HD) and adds Gson serializers/deserializers for backward-compatible JSON wallet files. It adds a Flyway migration V10__SilentPayments.sql adding wallet.birthHeight, walletNode.silentPaymentTweak, and keystore.silentPaymentScanAddress columns, and updates the JDBI DAOs/mappers to persist and load those fields. ElectrumPersonalServer export is updated to use the new enum names. No security bug is described or evident in the diff.
Changed components
com.sparrowwallet.sparrow.io.JsonPersistencecom.sparrowwallet.sparrow.io.Storagecom.sparrowwallet.sparrow.io.db.DbPersistencecom.sparrowwallet.sparrow.io.db.KeystoreDaocom.sparrowwallet.sparrow.io.db.KeystoreMappercom.sparrowwallet.sparrow.io.db.WalletDaocom.sparrowwallet.sparrow.io.db.WalletMappercom.sparrowwallet.sparrow.io.db.WalletNodeDaocom.sparrowwallet.sparrow.io.db.WalletNodeMappercom.sparrowwallet.sparrow.io.ElectrumPersonalServersrc/main/resources/com/sparrowwallet/sparrow/sql/V10__SilentPayments.sqlInspect captured patch +58 / −22
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java
index 23f9554..37c6a5e 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java
@@ -30,7 +30,7 @@ public class ElectrumPersonalServer implements WalletExport {
@Override
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
- if(wallet.getPolicyType() == PolicyType.SINGLE_SILENT_PAYMENTS) {
+ if(wallet.getPolicyType() == PolicyType.SINGLE_SP) {
throw new ExportException(getName() + " does not support silent payments wallets.");
}
@@ -65,7 +65,7 @@ public class ElectrumPersonalServer implements WalletExport {
writer.write(wallet.getFullName().replace(' ', '_') + " = ");
ExtendedKey.Header xpubHeader = ExtendedKey.Header.fromScriptType(wallet.getScriptType(), false);
- if(wallet.getPolicyType() == PolicyType.MULTI) {
+ if(wallet.getPolicyType() == PolicyType.MULTI_HD) {
writer.write(wallet.getDefaultPolicy().getNumSignaturesRequired() + " ");
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/JsonPersistence.java b/src/main/java/com/sparrowwallet/sparrow/io/JsonPersistence.java
index 3bb12da..72b4005 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/JsonPersistence.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/JsonPersistence.java
@@ -12,6 +12,7 @@ import com.sparrowwallet.drongo.crypto.AsymmetricKeyDeriver;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction;
+import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletNode;
@@ -337,6 +338,8 @@ public class JsonPersistence implements Persistence {
gsonBuilder.registerTypeAdapter(Transaction.class, new TransactionDeserializer());
gsonBuilder.registerTypeAdapter(Address.class, new AddressSerializer());
gsonBuilder.registerTypeAdapter(Address.class, new AddressDeserializer());
+ gsonBuilder.registerTypeAdapter(PolicyType.class, new PolicyTypeSerializer());
+ gsonBuilder.registerTypeAdapter(PolicyType.class, new PolicyTypeDeserializer());
if(includeWalletSerializers) {
gsonBuilder.registerTypeAdapter(Keystore.class, new KeystoreSerializer());
gsonBuilder.registerTypeAdapter(WalletNode.class, new NodeSerializer());
@@ -453,6 +456,30 @@ public class JsonPersistence implements Persistence {
}
}
+ private static class PolicyTypeSerializer implements JsonSerializer<PolicyType> {
+ @Override
+ public JsonElement serialize(PolicyType src, Type typeOfSrc, JsonSerializationContext context) {
+ return switch(src) {
+ case SINGLE_HD -> new JsonPrimitive("SINGLE");
+ case MULTI_HD -> new JsonPrimitive("MULTI");
+ default -> new JsonPrimitive(src.name());
+ };
+ }
+ }
+
+ private static class PolicyTypeDeserializer implements JsonDeserializer<PolicyType> {
+ @Override
+ public PolicyType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+ String value = json.getAsJsonPrimitive().getAsString();
+
+ return switch(value) {
+ case "SINGLE" -> PolicyType.SINGLE_HD;
+ case "MULTI" -> PolicyType.MULTI_HD;
+ default -> PolicyType.valueOf(value);
+ };
+ }
+ }
+
private static class KeystoreSerializer implements JsonSerializer<Keystore> {
@Override
public JsonElement serialize(Keystore keystore, Type typeOfSrc, JsonSerializationContext context) {
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java
index dface64..71c2f1e 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java
@@ -188,7 +188,7 @@ public class Storage {
keystore.setExtendedPublicKey(derivedKeystore.getExtendedPublicKey());
keystore.getSeed().setPassphrase(copyKeystore.getSeed().getPassphrase());
keystore.setBip47ExtendedPrivateKey(derivedKeystore.getBip47ExtendedPrivateKey());
- keystore.setSilentPaymentScanAddress(wallet.getPolicyType() == PolicyType.SINGLE_SILENT_PAYMENTS ? derivedKeystore.getSilentPaymentScanAddress() : null);
+ keystore.setSilentPaymentScanAddress(wallet.getPolicyType() == PolicyType.SINGLE_SP ? derivedKeystore.getSilentPaymentScanAddress() : null);
copyKeystore.getSeed().clear();
} else if(keystore.hasMasterPrivateExtendedKey()) {
Keystore copyKeystore = copy.getKeystores().get(i);
@@ -196,7 +196,7 @@ public class Storage {
keystore.setKeyDerivation(derivedKeystore.getKeyDerivation());
keystore.setExtendedPublicKey(derivedKeystore.getExtendedPublicKey());
keystore.setBip47ExtendedPrivateKey(derivedKeystore.getBip47ExtendedPrivateKey());
- keystore.setSilentPaymentScanAddress(wallet.getPolicyType() == PolicyType.SINGLE_SILENT_PAYMENTS ? derivedKeystore.getSilentPaymentScanAddress() : null);
+ keystore.setSilentPaymentScanAddress(wallet.getPolicyType() == PolicyType.SINGLE_SP ? derivedKeystore.getSilentPaymentScanAddress() : null);
copyKeystore.getMasterPrivateKey().clear();
}
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java b/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java
index b18bb17..08ee1fe 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java
@@ -249,11 +249,11 @@ public class DbPersistence implements Persistence {
if(addressNode.getId() == null) {
WalletNode purposeNode = wallet.getNode(addressNode.getKeyPurpose());
if(purposeNode.getId() == null) {
- long purposeNodeId = walletNodeDao.insertWalletNode(purposeNode.getDerivationPath(), purposeNode.getLabel(), wallet.getId(), null, null);
+ long purposeNodeId = walletNodeDao.insertWalletNode(purposeNode.getDerivationPath(), purposeNode.getLabel(), wallet.getId(), null, null, null);
purposeNode.setId(purposeNodeId);
}
- long nodeId = walletNodeDao.insertWalletNode(addressNode.getDerivationPath(), addressNode.getLabel(), wallet.getId(), purposeNode.getId(), addressNode.getAddressData());
+ long nodeId = walletNodeDao.insertWalletNode(addressNode.getDerivationPath(), addressNode.getLabel(), wallet.getId(), purposeNode.getId(), addressNode.getAddressData(), addressNode.getSilentPaymentTweak());
addressNode.setId(nodeId);
} else if(addressNode.getAddress() != null) {
walletNodeDao.updateNodeAddressData(addressNode.getId(), addressNode.getAddressData());
@@ -308,11 +308,11 @@ public class DbPersistence implements Persistence {
if(addressNode.getId() == null) {
WalletNode purposeNode = wallet.getNode(addressNode.getKeyPurpose());
if(purposeNode.getId() == null) {
- long purposeNodeId = walletNodeDao.insertWalletNode(purposeNode.getDerivationPath(), purposeNode.getLabel(), wallet.getId(), null, null);
+ long purposeNodeId = walletNodeDao.insertWalletNode(purposeNode.getDerivationPath(), purposeNode.getLabel(), wallet.getId(), null, null, null);
purposeNode.setId(purposeNodeId);
}
- long nodeId = walletNodeDao.insertWalletNode(addressNode.getDerivationPath(), addressNode.getLabel(), wallet.getId(), purposeNode.getId(), addressNode.getAddressData());
+ long nodeId = walletNodeDao.insertWalletNode(addressNode.getDerivationPath(), addressNode.getLabel(), wallet.getId(), purposeNode.getId(), addressNode.getAddressData(), addressNode.getSilentPaymentTweak());
addressNode.setId(nodeId);
} else if(addressNode.getAddress() != null) {
walletNodeDao.updateNodeAddressData(addressNode.getId(), addressNode.getAddressData());
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java b/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java
index b5b1a23..e417322 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java
@@ -10,16 +10,16 @@ import org.jdbi.v3.sqlobject.statement.SqlUpdate;
import java.util.List;
public interface KeystoreDao {
- @SqlQuery("select keystore.id, keystore.label, keystore.source, keystore.walletModel, keystore.masterFingerprint, keystore.derivationPath, keystore.extendedPublicKey, keystore.externalPaymentCode, keystore.deviceRegistration, " +
+ @SqlQuery("select keystore.id, keystore.label, keystore.source, keystore.walletModel, keystore.masterFingerprint, keystore.derivationPath, keystore.extendedPublicKey, keystore.externalPaymentCode, keystore.silentPaymentScanAddress, keystore.deviceRegistration, " +
"masterPrivateExtendedKey.id, masterPrivateExtendedKey.privateKey, masterPrivateExtendedKey.chainCode, masterPrivateExtendedKey.initialisationVector, masterPrivateExtendedKey.encryptedBytes, masterPrivateExtendedKey.keySalt, masterPrivateExtendedKey.deriver, masterPrivateExtendedKey.crypter, " +
"seed.id, seed.type, seed.mnemonicString, seed.initialisationVector, seed.encryptedBytes, seed.keySalt, seed.deriver, seed.crypter, seed.needsPassphrase, seed.creationTimeSeconds " +
"from keystore left join masterPrivateExtendedKey on keystore.masterPrivateExtendedKey = masterPrivateExtendedKey.id left join seed on keystore.seed = seed.id where keystore.wallet = ? order by keystore.index asc")
@RegisterRowMapper(KeystoreMapper.class)
List<Keystore> getForWalletId(Long id);
- @SqlUpdate("insert into keystore (label, source, walletModel, masterFingerprint, derivationPath, extendedPublicKey, externalPaymentCode, deviceRegistration, masterPrivateExtendedKey, seed, wallet, index) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
+ @SqlUpdate("insert into keystore (label, source, walletModel, masterFingerprint, derivationPath, extendedPublicKey, externalPaymentCode, silentPaymentScanAddress, deviceRegistration, masterPrivateExtendedKey, seed, wallet, index) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
@GetGeneratedKeys("id")
- long insert(String label, int source, int walletModel, String masterFingerprint, String derivationPath, String extendedPublicKey, String externalPaymentCode, byte[] deviceRegistration, Long masterPrivateExtendedKey, Long seed, long wallet, int index);
+ long insert(String label, int source, int walletModel, String masterFingerprint, String derivationPath, String extendedPublicKey, String externalPaymentCode, byte[] silentPaymentScanAddress, byte[] deviceRegistration, Long masterPrivateExtendedKey, Long seed, long wallet, int index);
@SqlUpdate("insert into masterPrivateExtendedKey (privateKey, chainCode, initialisationVector, encryptedBytes, keySalt, deriver, crypter, creationTimeSeconds) values (?, ?, ?, ?, ?, ?, ?, ?)")
@GetGeneratedKeys("id")
@@ -73,6 +73,7 @@ public interface KeystoreDao {
keystore.getKeyDerivation().getDerivationPath(),
keystore.hasMasterPrivateKey() || wallet.isBip47() ? null : keystore.getExtendedPublicKey().toString(),
keystore.getExternalPaymentCode() == null ? null : keystore.getExternalPaymentCode().toString(),
+ keystore.getSilentPaymentScanAddress() == null ? null : keystore.getSilentPaymentScanAddress().toBytes(),
keystore.getDeviceRegistration(),
keystore.getMasterPrivateExtendedKey() == null ? null : keystore.getMasterPrivateExtendedKey().getId(),
keystore.getSeed() == null ? null : keystore.getSeed().getId(), wallet.getId(), i);
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreMapper.java b/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreMapper.java
index 4a019b9..b680b31 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreMapper.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreMapper.java
@@ -5,6 +5,7 @@ import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.bip47.PaymentCode;
import com.sparrowwallet.drongo.crypto.EncryptedData;
import com.sparrowwallet.drongo.crypto.EncryptionType;
+import com.sparrowwallet.drongo.silentpayments.SilentPaymentScanAddress;
import com.sparrowwallet.drongo.wallet.*;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.statement.StatementContext;
@@ -25,6 +26,7 @@ public class KeystoreMapper implements RowMapper<Keystore> {
keystore.setKeyDerivation(new KeyDerivation(rs.getString("keystore.masterFingerprint"), rs.getString("keystore.derivationPath")));
keystore.setExtendedPublicKey(rs.getString("keystore.extendedPublicKey") == null ? null : ExtendedKey.fromDescriptor(rs.getString("keystore.extendedPublicKey")));
keystore.setExternalPaymentCode(rs.getString("keystore.externalPaymentCode") == null ? null : PaymentCode.fromString(rs.getString("keystore.externalPaymentCode")));
+ keystore.setSilentPaymentScanAddress(rs.getBytes("keystore.silentPaymentScanAddress") == null ? null : SilentPaymentScanAddress.fromBytes(rs.getBytes("keystore.silentPaymentScanAddress")));
keystore.setDeviceRegistration(rs.getBytes("keystore.deviceRegistration"));
if(rs.getBytes("masterPrivateExtendedKey.privateKey") != null) {
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletDao.java b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletDao.java
index ed00728..0ee0281 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletDao.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletDao.java
@@ -42,21 +42,21 @@ public interface WalletDao {
@CreateSqlObject
UtxoMixDataDao createUtxoMixDataDao();
- @SqlQuery("select wallet.id, wallet.name, wallet.label, wallet.network, wallet.policyType, wallet.scriptType, wallet.storedBlockHeight, wallet.gapLimit, wallet.watchLast, wallet.birthDate, policy.id, policy.name, policy.script from wallet left join policy on wallet.defaultPolicy = policy.id")
+ @SqlQuery("select wallet.id, wallet.name, wallet.label, wallet.network, wallet.policyType, wallet.scriptType, wallet.storedBlockHeight, wallet.gapLimit, wallet.watchLast, wallet.birthDate, wallet.birthHeight, policy.id, policy.name, policy.script from wallet left join policy on wallet.defaultPolicy = policy.id")
@RegisterRowMapper(WalletMapper.class)
List<Wallet> loadAllWallets();
- @SqlQuery("select wallet.id, wallet.name, wallet.label, wallet.network, wallet.policyType, wallet.scriptType, wallet.storedBlockHeight, wallet.gapLimit, wallet.watchLast, wallet.birthDate, policy.id, policy.name, policy.script from wallet left join policy on wallet.defaultPolicy = policy.id where wallet.id = 1")
+ @SqlQuery("select wallet.id, wallet.name, wallet.label, wallet.network, wallet.policyType, wallet.scriptType, wallet.storedBlockHeight, wallet.gapLimit, wallet.watchLast, wallet.birthDate, wallet.birthHeight, policy.id, policy.name, policy.script from wallet left join policy on wallet.defaultPolicy = policy.id where wallet.id = 1")
@RegisterRowMapper(WalletMapper.class)
Wallet loadMainWallet();
- @SqlQuery("select wallet.id, wallet.name, wallet.label, wallet.network, wallet.policyType, wallet.scriptType, wallet.storedBlockHeight, wallet.gapLimit, wallet.watchLast, wallet.birthDate, policy.id, policy.name, policy.script from wallet left join policy on wallet.defaultPolicy = policy.id where wallet.id != 1")
+ @SqlQuery("select wallet.id, wallet.name, wallet.label, wallet.network, wallet.policyType, wallet.scriptType, wallet.storedBlockHeight, wallet.gapLimit, wallet.watchLast, wallet.birthDate, wallet.birthHeight, policy.id, policy.name, policy.script from wallet left join policy on wallet.defaultPolicy = policy.id where wallet.id != 1")
@RegisterRowMapper(WalletMapper.class)
List<Wallet> loadChildWallets();
- @SqlUpdate("insert into wallet (name, label, network, policyType, scriptType, storedBlockHeight, gapLimit, watchLast, birthDate, defaultPolicy) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
+ @SqlUpdate("insert into wallet (name, label, network, policyType, scriptType, storedBlockHeight, gapLimit, watchLast, birthDate, birthHeight, defaultPolicy) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
@GetGeneratedKeys("id")
- long insert(String name, String label, int network, int policyType, int scriptType, Integer storedBlockHeight, Integer gapLimit, Integer watchLast, Date birthDate, long defaultPolicy);
+ long insert(String name, String label, int network, int policyType, int scriptType, Integer storedBlockHeight, Integer gapLimit, Integer watchLast, Date birthDate, Integer birthHeight, long defaultPolicy);
@SqlUpdate("update wallet set name = :name where id = :id")
void updateName(@Bind("id") long id, @Bind("name") String name);
@@ -137,7 +137,7 @@ public interface WalletDao {
setSchema(schema);
createPolicyDao().addPolicy(wallet.getDefaultPolicy());
- long id = insert(truncate(wallet.getName()), truncate(wallet.getLabel()), wallet.getNetwork().ordinal(), wallet.getPolicyType().ordinal(), wallet.getScriptType().ordinal(), wallet.getStoredBlockHeight(), wallet.gapLimit(), wallet.getWatchLast(), wallet.getBirthDate(), wallet.getDefaultPolicy().getId());
+ long id = insert(truncate(wallet.getName()), truncate(wallet.getLabel()), wallet.getNetwork().ordinal(), wallet.getPolicyType().ordinal(), wallet.getScriptType().ordinal(), wallet.getStoredBlockHeight(), wallet.gapLimit(), wallet.getWatchLast(), wallet.getBirthDate(), wallet.getBirthHeight(), wallet.getDefaultPolicy().getId());
wallet.setId(id);
createKeystoreDao().addKeystores(wallet);
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletMapper.java b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletMapper.java
index c1886d8..8cd0f0a 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletMapper.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletMapper.java
@@ -34,6 +34,8 @@ public class WalletMapper implements RowMapper<Wallet> {
int watchLast = rs.getInt("wallet.watchLast");
wallet.setWatchLast(rs.wasNull() ? null : watchLast);
wallet.setBirthDate(rs.getTimestamp("wallet.birthDate"));
+ int birthHeight = rs.getInt("wallet.birthHeight");
+ wallet.setBirthHeight(rs.wasNull() ? null : birthHeight);
return wallet;
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletNodeDao.java b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletNodeDao.java
index 4af56fe..4c011f2 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletNodeDao.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletNodeDao.java
@@ -16,7 +16,7 @@ import java.util.Date;
import java.util.List;
public interface WalletNodeDao {
- @SqlQuery("select walletNode.id, walletNode.derivationPath, walletNode.label, walletNode.parent, walletNode.addressData, ?, " +
+ @SqlQuery("select walletNode.id, walletNode.derivationPath, walletNode.label, walletNode.parent, walletNode.addressData, walletNode.silentPaymentTweak, ?, " +
"blockTransactionHashIndex.id, blockTransactionHashIndex.hash, blockTransactionHashIndex.height, blockTransactionHashIndex.date, blockTransactionHashIndex.fee, blockTransactionHashIndex.label, " +
"blockTransactionHashIndex.index, blockTransactionHashIndex.outputValue, blockTransactionHashIndex.status, blockTransactionHashIndex.spentBy, blockTransactionHashIndex.node " +
"from walletNode left join blockTransactionHashIndex on walletNode.id = blockTransactionHashIndex.node where walletNode.wallet = ? order by walletNode.parent asc nulls first, blockTransactionHashIndex.spentBy asc nulls first")
@@ -25,9 +25,9 @@ public interface WalletNodeDao {
@UseRowReducer(WalletNodeReducer.class)
List<WalletNode> getForWalletId(int scriptType, Long id);
- @SqlUpdate("insert into walletNode (derivationPath, label, wallet, parent, addressData) values (?, ?, ?, ?, ?)")
+ @SqlUpdate("insert into walletNode (derivationPath, label, wallet, parent, addressData, silentPaymentTweak) values (?, ?, ?, ?, ?, ?)")
@GetGeneratedKeys("id")
- long insertWalletNode(String derivationPath, String label, long wallet, Long parent, byte[] addressData);
+ long insertWalletNode(String derivationPath, String label, long wallet, Long parent, byte[] addressData, byte[] silentPaymentTweak);
@SqlUpdate("insert into blockTransactionHashIndex (hash, height, date, fee, label, index, outputValue, status, spentBy, node) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
@GetGeneratedKeys("id")
@@ -62,12 +62,12 @@ public interface WalletNodeDao {
default void addWalletNodes(Wallet wallet) {
for(WalletNode purposeNode : wallet.getPurposeNodes()) {
- long purposeNodeId = insertWalletNode(purposeNode.getDerivationPath(), truncate(purposeNode.getLabel()), wallet.getId(), null, null);
+ long purposeNodeId = insertWalletNode(purposeNode.getDerivationPath(), truncate(purposeNode.getLabel()), wallet.getId(), null, null, null);
purposeNode.setId(purposeNodeId);
addTransactionOutputs(purposeNode);
List<WalletNode> childNodes = new ArrayList<>(purposeNode.getChildren());
for(WalletNode addressNode : childNodes) {
- long addressNodeId = insertWalletNode(addressNode.getDerivationPath(), truncate(addressNode.getLabel()), wallet.getId(), purposeNodeId, addressNode.getAddressData());
+ long addressNodeId = insertWalletNode(addressNode.getDerivationPath(), truncate(addressNode.getLabel()), wallet.getId(), purposeNodeId, addressNode.getAddressData(), addressNode.getSilentPaymentTweak());
addressNode.setId(addressNodeId);
addTransactionOutputs(addressNode);
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletNodeMapper.java b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletNodeMapper.java
index 0b1738e..a23b86b 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletNodeMapper.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletNodeMapper.java
@@ -19,6 +19,7 @@ public class WalletNodeMapper implements RowMapper<WalletNode> {
ScriptType scriptType = ScriptType.values()[rs.getInt(6)];
walletNode.setAddress(scriptType.getAddress(addressData));
}
+ walletNode.setSilentPaymentTweak(rs.getBytes("walletNode.silentPaymentTweak"));
return walletNode;
}
}
diff --git a/src/main/resources/com/sparrowwallet/sparrow/sql/V10__SilentPayments.sql b/src/main/resources/com/sparrowwallet/sparrow/sql/V10__SilentPayments.sql
new file mode 100644
index 0000000..9ed678a
--- /dev/null
+++ b/src/main/resources/com/sparrowwallet/sparrow/sql/V10__SilentPayments.sql
@@ -0,0 +1,3 @@
+alter table wallet add column birthHeight integer after birthDate;
+alter table walletNode add column silentPaymentTweak varbinary(32) after addressData;
+alter table keystore add column silentPaymentScanAddress varbinary(65) after externalPaymentCode;
\ No newline at end of file
Why this scored 12/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.