various sp wallet related fixes and improvements
This commit is contained in:
parent
15e5aaa4b9
commit
bc526c90e1
@ -671,9 +671,14 @@ public class OutputDescriptor {
|
||||
private static OutputDescriptor parseTwoArgSp(String scanArg, String spendArg, Map<String, Integer> annotations) {
|
||||
KeyDerivationAndKey originResult = parseKeyOrigin(scanArg);
|
||||
KeyDerivation keyDerivation = originResult.keyDerivation();
|
||||
scanArg = originResult.key();
|
||||
if(keyDerivation.getDerivation().size() > 2) {
|
||||
List<ChildNumber> accountDerivation = keyDerivation.getDerivation().subList(0, keyDerivation.getDerivation().size() - 2);
|
||||
if(KeyDerivation.getBip352ScanDerivation(accountDerivation).equals(keyDerivation.getDerivation())) {
|
||||
keyDerivation = new KeyDerivation(keyDerivation.getMasterFingerprint(), accountDerivation);
|
||||
}
|
||||
}
|
||||
|
||||
ECKey scanPrivateKey = parseSilentPaymentScanKey(scanArg);
|
||||
ECKey scanPrivateKey = parseSilentPaymentScanKey(originResult.key());
|
||||
ECKey spendKey = parseSilentPaymentSpendKey(spendArg);
|
||||
|
||||
ECKey spendPubKey = spendKey.isPubKeyOnly() ? spendKey : ECKey.fromPublicOnly(spendKey.getPubKey());
|
||||
@ -965,6 +970,25 @@ public class OutputDescriptor {
|
||||
return keyBuilder.toString();
|
||||
}
|
||||
|
||||
public static String writeKey(ECKey ecKey, KeyDerivation keyDerivation, boolean addKeyOrigin) {
|
||||
return writeKey(ecKey, keyDerivation, addKeyOrigin, false);
|
||||
}
|
||||
|
||||
public static String writeKey(ECKey ecKey, KeyDerivation keyDerivation, boolean addKeyOrigin, boolean useApostrophes) {
|
||||
StringBuilder keyBuilder = new StringBuilder();
|
||||
if(addKeyOrigin && keyDerivation != null && keyDerivation.getMasterFingerprint() != null && keyDerivation.getMasterFingerprint().length() == 8 && Utils.isHex(keyDerivation.getMasterFingerprint())) {
|
||||
keyBuilder.append("[");
|
||||
keyBuilder.append(keyDerivation.getMasterFingerprint());
|
||||
if(!keyDerivation.getDerivation().isEmpty()) {
|
||||
keyBuilder.append(KeyDerivation.writePath(keyDerivation.getDerivation(), useApostrophes).substring(1));
|
||||
}
|
||||
keyBuilder.append("]");
|
||||
}
|
||||
keyBuilder.append(ecKey.hasPrivKey() ? ecKey.getPrivateKeyEncoded().toString() : Utils.bytesToHex(ecKey.getPubKey()));
|
||||
|
||||
return keyBuilder.toString();
|
||||
}
|
||||
|
||||
public static String writeKey(ExtendedKey pubKey, KeyDerivation keyDerivation, String childDerivation, boolean addKeyOrigin, boolean addKey) {
|
||||
return writeKey(pubKey, keyDerivation, childDerivation, addKeyOrigin, addKey, false);
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import com.sparrowwallet.drongo.ExtendedKey;
|
||||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||
import com.sparrowwallet.drongo.protocol.*;
|
||||
import com.sparrowwallet.drongo.silentpayments.*;
|
||||
import com.sparrowwallet.drongo.wallet.*;
|
||||
@ -161,7 +162,7 @@ public class PSBT {
|
||||
byte[] silentPaymentsTweak = walletNode.getSilentPaymentTweak();
|
||||
Map<ECKey, KeyDerivation> spSpendDerivations = new LinkedHashMap<>();
|
||||
for(Keystore keystore : signingWallet.getKeystores()) {
|
||||
if(silentPaymentsTweak != null && keystore.getSilentPaymentScanAddress() != null) {
|
||||
if(silentPaymentsTweak != null && keystore.getSilentPaymentScanAddress() != null && signingWallet.getPolicyType() == PolicyType.SINGLE_SP) {
|
||||
ECKey spendPubKey = keystore.getSilentPaymentScanAddress().getSpendKey();
|
||||
KeyDerivation spendKeyDerivation = new KeyDerivation(keystore.getKeyDerivation().getMasterFingerprint(), KeyDerivation.writePath(KeyDerivation.getBip352SpendDerivation(keystore.getKeyDerivation().getDerivation())));
|
||||
spSpendDerivations.put(spendPubKey, spendKeyDerivation);
|
||||
@ -209,9 +210,9 @@ public class PSBT {
|
||||
SilentPaymentAddress outputSpAddress = null;
|
||||
Long outputSpLabel = null;
|
||||
for(Keystore keystore : recipientWallet.getKeystores()) {
|
||||
if(outputNode.getSilentPaymentTweak() != null && keystore.getSilentPaymentScanAddress() != null) {
|
||||
if(outputNode.getSilentPaymentTweak() != null && keystore.getSilentPaymentScanAddress() != null && recipientWallet.getPolicyType() == PolicyType.SINGLE_SP) {
|
||||
SilentPaymentScanAddress changeAddress = keystore.getSilentPaymentScanAddress().getChangeAddress();
|
||||
outputSpAddress = new SilentPaymentAddress(ECKey.fromPublicOnly(changeAddress.getScanKey()), changeAddress.getSpendKey());
|
||||
outputSpAddress = changeAddress.getSilentPaymentAddress();
|
||||
outputSpLabel = 0L;
|
||||
} else {
|
||||
derivedPublicKeys.put(recipientWallet.getScriptType().getOutputKey(recipientWallet.getPolicyType(), keystore.getPubKey(outputNode)), keystore.getKeyDerivation().extend(outputNode.getDerivation()));
|
||||
@ -630,7 +631,7 @@ public class PSBT {
|
||||
WalletNode walletNode = signingNodes.get(psbtInput);
|
||||
if(walletNode != null && walletNode.getWallet() != null) {
|
||||
for(Keystore keystore : signingWallet.getKeystores()) {
|
||||
if(psbtInput.getSilentPaymentsTweak() != null && keystore.getSilentPaymentScanAddress() != null) {
|
||||
if(psbtInput.getSilentPaymentsTweak() != null && keystore.getSilentPaymentScanAddress() != null && signingWallet.getPolicyType() == PolicyType.SINGLE_SP) {
|
||||
ECKey spendPubKey = keystore.getSilentPaymentScanAddress().getSpendKey();
|
||||
KeyDerivation spendKeyDerivation = new KeyDerivation(keystore.getKeyDerivation().getMasterFingerprint(), KeyDerivation.writePath(KeyDerivation.getBip352SpendDerivation(keystore.getKeyDerivation().getDerivation())));
|
||||
psbtInput.getSilentPaymentsSpendDerivations().put(spendPubKey, spendKeyDerivation);
|
||||
|
||||
@ -56,6 +56,10 @@ public class SilentPaymentScanAddress extends SilentPaymentAddress {
|
||||
return new SilentPaymentScanAddress(scanPrivateKey, spendPublicKey);
|
||||
}
|
||||
|
||||
public SilentPaymentAddress getSilentPaymentAddress() {
|
||||
return new SilentPaymentAddress(ECKey.fromPublicOnly(getScanKey()), getSpendKey());
|
||||
}
|
||||
|
||||
public SilentPaymentScanAddress copy() {
|
||||
return new SilentPaymentScanAddress(getScanKey(), getSpendKey());
|
||||
}
|
||||
@ -68,6 +72,16 @@ public class SilentPaymentScanAddress extends SilentPaymentAddress {
|
||||
return Utils.concat(getScanKey().getPrivKeyBytes(), getSpendKey().getPubKey(true));
|
||||
}
|
||||
|
||||
public static boolean isValid(String encoded) {
|
||||
try {
|
||||
fromKeyString(encoded);
|
||||
} catch(Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static SilentPaymentScanAddress fromKeyString(String encoded) {
|
||||
Bech32.Bech32Data data = Bech32.decode(encoded, 1023);
|
||||
if(data.encoding != Bech32.Encoding.BECH32M) {
|
||||
|
||||
@ -204,10 +204,14 @@ public class SilentPaymentUtils {
|
||||
* @throws InvalidSilentPaymentException if the computed shared secrets or addresses are invalid
|
||||
*/
|
||||
public static Map<ECKey, EcdhShareAndProof> computeOutputAddresses(List<SilentPayment> silentPayments, Map<HashIndex, WalletNode> utxos) throws InvalidSilentPaymentException {
|
||||
ECKey summedPrivateKey = getSummedPrivateKey(utxos.values());
|
||||
return computeOutputAddresses(silentPayments, summedPrivateKey, utxos.keySet());
|
||||
}
|
||||
|
||||
public static Map<ECKey, EcdhShareAndProof> computeOutputAddresses(List<SilentPayment> silentPayments, ECKey summedPrivateKey, Set<HashIndex> outpoints) throws InvalidSilentPaymentException {
|
||||
Map<ECKey, EcdhShareAndProof> scanKeyProofs = new LinkedHashMap<>();
|
||||
SecureRandom random = new SecureRandom();
|
||||
ECKey summedPrivateKey = getSummedPrivateKey(utxos.values());
|
||||
BigInteger inputHash = getInputHash(utxos.keySet(), summedPrivateKey);
|
||||
BigInteger inputHash = getInputHash(outpoints, summedPrivateKey);
|
||||
Map<ECKey, List<SilentPayment>> scanKeyGroups = getScanKeyGroups(silentPayments);
|
||||
for(Map.Entry<ECKey, List<SilentPayment>> scanKeyGroup : scanKeyGroups.entrySet()) {
|
||||
ECKey scanKey = scanKeyGroup.getKey();
|
||||
|
||||
@ -37,6 +37,7 @@ public class Keystore extends Persistable {
|
||||
|
||||
//Avoid performing repeated expensive seed derivation checks
|
||||
private transient boolean extendedPublicKeyChecked;
|
||||
private transient boolean silentPaymentScanAddressChecked;
|
||||
|
||||
public Keystore() {
|
||||
this(DEFAULT_LABEL);
|
||||
@ -112,6 +113,7 @@ public class Keystore extends Persistable {
|
||||
|
||||
public void setSilentPaymentScanAddress(SilentPaymentScanAddress silentPaymentScanAddress) {
|
||||
this.silentPaymentScanAddress = silentPaymentScanAddress;
|
||||
this.silentPaymentScanAddressChecked = false;
|
||||
}
|
||||
|
||||
public byte[] getDeviceRegistration() {
|
||||
@ -227,10 +229,16 @@ public class Keystore extends Persistable {
|
||||
}
|
||||
|
||||
public ECKey getKey(WalletNode walletNode) throws MnemonicException {
|
||||
if(silentPaymentScanAddress != null && walletNode.getSilentPaymentTweak() != null) {
|
||||
if(silentPaymentScanAddress != null && walletNode.getWallet().getPolicyType() == PolicyType.SINGLE_SP) {
|
||||
ECKey spendPrivKey = getSpendPrivateKey(Collections.emptyMap());
|
||||
ECKey tweakKey = ECKey.fromPrivate(walletNode.getSilentPaymentTweak());
|
||||
return spendPrivKey.addPrivate(tweakKey);
|
||||
byte[] tweak = walletNode.getSilentPaymentTweak();
|
||||
if(tweak == null) {
|
||||
if(walletNode.isPurposeNode()) {
|
||||
return spendPrivKey;
|
||||
}
|
||||
throw new IllegalStateException("Silent payment tweak is required for address node " + walletNode.getDerivationPath());
|
||||
}
|
||||
return spendPrivKey.addPrivate(ECKey.fromPrivate(tweak));
|
||||
}
|
||||
|
||||
if(source == KeystoreSource.SW_PAYMENT_CODE) {
|
||||
@ -256,9 +264,16 @@ public class Keystore extends Persistable {
|
||||
}
|
||||
|
||||
public ECKey getPubKey(WalletNode walletNode) {
|
||||
if(silentPaymentScanAddress != null && walletNode.getSilentPaymentTweak() != null) {
|
||||
if(silentPaymentScanAddress != null && walletNode.getWallet().getPolicyType() == PolicyType.SINGLE_SP) {
|
||||
ECKey spendKey = silentPaymentScanAddress.getSpendKey();
|
||||
ECKey tweakPoint = ECKey.fromPublicOnly(ECKey.fromPrivate(walletNode.getSilentPaymentTweak()));
|
||||
byte[] tweak = walletNode.getSilentPaymentTweak();
|
||||
if(tweak == null) {
|
||||
if(walletNode.isPurposeNode()) {
|
||||
return spendKey;
|
||||
}
|
||||
throw new IllegalStateException("Silent payment tweak is required for address node " + walletNode.getDerivationPath());
|
||||
}
|
||||
ECKey tweakPoint = ECKey.fromPublicOnly(ECKey.fromPrivate(tweak));
|
||||
return spendKey.add(tweakPoint, true);
|
||||
}
|
||||
|
||||
@ -382,7 +397,7 @@ public class Keystore extends Persistable {
|
||||
throw new InvalidKeystoreException("Source of " + source + " but no seed or master private key is present");
|
||||
}
|
||||
|
||||
if(!extendedPublicKeyChecked && ((seed != null && !seed.isEncrypted()) || (masterPrivateExtendedKey != null && !masterPrivateExtendedKey.isEncrypted()))) {
|
||||
if(!extendedPublicKeyChecked && extendedPublicKey != null && ((seed != null && !seed.isEncrypted()) || (masterPrivateExtendedKey != null && !masterPrivateExtendedKey.isEncrypted()))) {
|
||||
try {
|
||||
List<ChildNumber> derivation = getKeyDerivation().getDerivation();
|
||||
DeterministicKey derivedKey = getExtendedMasterPrivateKey().getKey(derivation);
|
||||
@ -396,6 +411,21 @@ public class Keystore extends Persistable {
|
||||
throw new InvalidKeystoreException("Invalid mnemonic specified for seed", e);
|
||||
}
|
||||
}
|
||||
|
||||
if(!silentPaymentScanAddressChecked && silentPaymentScanAddress != null && ((seed != null && !seed.isEncrypted()) || (masterPrivateExtendedKey != null && !masterPrivateExtendedKey.isEncrypted()))) {
|
||||
try {
|
||||
List<ChildNumber> derivation = getKeyDerivation().getDerivation();
|
||||
DeterministicKey derivedScanKey = getExtendedMasterPrivateKey().getKey(KeyDerivation.getBip352ScanDerivation(derivation));
|
||||
DeterministicKey derivedSpendKey = getExtendedMasterPrivateKey().getKey(KeyDerivation.getBip352SpendDerivation(derivation));
|
||||
SilentPaymentScanAddress derivedScanAddress = new SilentPaymentScanAddress(derivedScanKey, derivedSpendKey);
|
||||
if(!derivedScanAddress.equals(getSilentPaymentScanAddress())) {
|
||||
throw new InvalidKeystoreException("Specified silent payments scan address does not match scan and spend keys derived from seed");
|
||||
}
|
||||
silentPaymentScanAddressChecked = true;
|
||||
} catch(MnemonicException e) {
|
||||
throw new InvalidKeystoreException("Invalid mnemonic specified for seed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(source == KeystoreSource.SW_PAYMENT_CODE) {
|
||||
|
||||
@ -150,12 +150,14 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
|
||||
Keystore derivedKeystore = keystore.hasSeed() ? Keystore.fromSeed(keystore.getSeed(), childDerivation) : Keystore.fromMasterPrivateExtendedKey(keystore.getMasterPrivateExtendedKey(), childDerivation);
|
||||
keystore.setKeyDerivation(derivedKeystore.getKeyDerivation());
|
||||
keystore.setExtendedPublicKey(derivedKeystore.getExtendedPublicKey());
|
||||
keystore.setSilentPaymentScanAddress(derivedKeystore.getSilentPaymentScanAddress());
|
||||
} catch(Exception e) {
|
||||
throw new IllegalStateException("Cannot derive keystore for " + standardAccount + " account", e);
|
||||
}
|
||||
} else {
|
||||
keystore.setKeyDerivation(new KeyDerivation(null, KeyDerivation.writePath(childDerivation)));
|
||||
keystore.setExtendedPublicKey(null);
|
||||
keystore.setSilentPaymentScanAddress(null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -552,7 +554,7 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
|
||||
}
|
||||
|
||||
public int getGapLimit() {
|
||||
return gapLimit == null ? DEFAULT_LOOKAHEAD : gapLimit;
|
||||
return policyType == PolicyType.SINGLE_SP ? 0 : (gapLimit == null ? DEFAULT_LOOKAHEAD : gapLimit);
|
||||
}
|
||||
|
||||
public void gapLimit(Integer gapLimit) {
|
||||
@ -1732,7 +1734,7 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
|
||||
PSBTInput psbtInput = signingEntry.getKey();
|
||||
|
||||
if(!psbtInput.isSigned()) {
|
||||
if(psbtInput.getSilentPaymentsTweak() != null && keystore.getSilentPaymentScanAddress() != null) {
|
||||
if(psbtInput.getSilentPaymentsTweak() != null && keystore.getSilentPaymentScanAddress() != null && signingWallet.getPolicyType() == PolicyType.SINGLE_SP) {
|
||||
ECKey spendPrivKey = keystore.getSpendPrivateKey(psbtInput.getSilentPaymentsSpendDerivations());
|
||||
psbtInput.signSilentPayments(spendPrivKey);
|
||||
} else {
|
||||
|
||||
@ -99,6 +99,10 @@ public class WalletNode extends Persistable implements Comparable<WalletNode> {
|
||||
return derivation;
|
||||
}
|
||||
|
||||
public boolean isPurposeNode() {
|
||||
return getDerivation().size() == 1;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user