mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-08-12 09:33:23 +00:00
implement persistence for sp wallets and related drongo changes
This commit is contained in:
@@ -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() + " ");
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user