diff --git a/src/main/java/com/sparrowwallet/drongo/ExtendedKey.java b/src/main/java/com/sparrowwallet/drongo/ExtendedKey.java index 232e41b..7135f53 100644 --- a/src/main/java/com/sparrowwallet/drongo/ExtendedKey.java +++ b/src/main/java/com/sparrowwallet/drongo/ExtendedKey.java @@ -73,11 +73,12 @@ public class ExtendedKey { return buffer.array(); } - public static ExtendedKey fromDescriptor(String extPubKey) { - byte[] serializedKey = Base58.decodeChecked(extPubKey); + public static ExtendedKey fromDescriptor(String descriptor) { + byte[] serializedKey = Base58.decodeChecked(descriptor); ByteBuffer buffer = ByteBuffer.wrap(serializedKey); - int header = buffer.getInt(); - if(!Header.isValidHeader(header)) { + int headerInt = buffer.getInt(); + Header header = Header.getHeader(headerInt); + if(header == null) { throw new IllegalArgumentException("Unknown header bytes: " + DeterministicKey.toBase58(serializedKey).substring(0, 4)); } @@ -106,8 +107,13 @@ public class ExtendedKey { throw new IllegalArgumentException("Found unexpected data in key"); } - DeterministicKey pubKey = new DeterministicKey(path, chainCode, new LazyECPoint(ECKey.CURVE.getCurve(), data), depth, parentFingerprint); - return new ExtendedKey(pubKey, parentFingerprint, childNumber); + if(header.isPrivate()) { + DeterministicKey prvKey = HDKeyDerivation.createMasterPrivKeyFromBytes(Arrays.copyOfRange(data, 1, 33), chainCode, path); + return new ExtendedKey(prvKey, parentFingerprint, childNumber); + } else { + DeterministicKey pubKey = new DeterministicKey(path, chainCode, new LazyECPoint(ECKey.CURVE.getCurve(), data), depth, parentFingerprint); + return new ExtendedKey(pubKey, parentFingerprint, childNumber); + } } public static boolean isValid(String extPubKey) { @@ -206,14 +212,14 @@ public class ExtendedKey { return name.endsWith("prv"); } - public static boolean isValidHeader(int header) { + public static Header getHeader(int header) { for(Header extendedKeyHeader : Header.values()) { if(header == extendedKeyHeader.header) { - return true; + return extendedKeyHeader; } } - return false; + return null; } } } \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java index 4018366..0aed3c8 100644 --- a/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java @@ -84,7 +84,7 @@ public class PSBT { Map derivedPublicKeys = new LinkedHashMap<>(); for(Keystore keystore : wallet.getKeystores()) { WalletNode walletNode = utxoEntry.getValue(); - derivedPublicKeys.put(keystore.getKey(walletNode.getKeyPurpose(), walletNode.getIndex()), keystore.getKeyDerivation()); + derivedPublicKeys.put(keystore.getPubKey(walletNode), keystore.getKeyDerivation()); } PSBTInput psbtInput = new PSBTInput(wallet.getScriptType(), transaction, inputIndex, utxo, utxoIndex, redeemScript, witnessScript, derivedPublicKeys, Collections.emptyMap()); @@ -119,7 +119,7 @@ public class PSBT { Map derivedPublicKeys = new LinkedHashMap<>(); for(Keystore keystore : wallet.getKeystores()) { - derivedPublicKeys.put(keystore.getKey(outputNode.getKeyPurpose(), outputNode.getIndex()), keystore.getKeyDerivation()); + derivedPublicKeys.put(keystore.getPubKey(outputNode), keystore.getKeyDerivation()); } PSBTOutput walletOutput = new PSBTOutput(redeemScript, witnessScript, derivedPublicKeys, Collections.emptyMap()); diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java index 1c0335c..74aa4ef 100644 --- a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java @@ -280,7 +280,7 @@ public class PSBTInput { } } - boolean sign(ECKey privKey) { + public boolean sign(ECKey privKey) { SigHash localSigHash = getSigHash(); if(localSigHash == null) { //Assume SigHash.ALL diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java b/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java index 67cdf5a..4af5037 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java @@ -101,14 +101,26 @@ public class Keystore { public ExtendedKey getExtendedPrivateKey() throws MnemonicException { List derivation = getKeyDerivation().getDerivation(); DeterministicKey derivedKey = getExtendedMasterPrivateKey().getKey(derivation); - return new ExtendedKey(derivedKey, derivedKey.getParentFingerprint(), derivation.get(derivation.size() - 1)); + ExtendedKey xprv = new ExtendedKey(derivedKey, derivedKey.getParentFingerprint(), derivation.get(derivation.size() - 1)); + //Recreate from xprv string to reset path to single ChildNumber at the derived depth + return ExtendedKey.fromDescriptor(xprv.toString()); } - public DeterministicKey getKey(WalletNode walletNode) { + public DeterministicKey getKey(WalletNode walletNode) throws MnemonicException { return getKey(walletNode.getKeyPurpose(), walletNode.getIndex()); } - public DeterministicKey getKey(KeyPurpose keyPurpose, int keyIndex) { + public DeterministicKey getKey(KeyPurpose keyPurpose, int keyIndex) throws MnemonicException { + ExtendedKey extendedPrivateKey = getExtendedPrivateKey(); + List derivation = List.of(extendedPrivateKey.getKeyChildNumber(), keyPurpose.getPathIndex(), new ChildNumber(keyIndex)); + return extendedPrivateKey.getKey(derivation); + } + + public DeterministicKey getPubKey(WalletNode walletNode) { + return getPubKey(walletNode.getKeyPurpose(), walletNode.getIndex()); + } + + public DeterministicKey getPubKey(KeyPurpose keyPurpose, int keyIndex) { List derivation = List.of(extendedPublicKey.getKeyChildNumber(), keyPurpose.getPathIndex(), new ChildNumber(keyIndex)); return extendedPublicKey.getKey(derivation); } diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java index 3490da5..ef832ea 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java @@ -169,7 +169,7 @@ public class Wallet { } Keystore keystore = getKeystores().get(0); - return keystore.getKey(keyPurpose, index); + return keystore.getPubKey(keyPurpose, index); } public List getPubKeys(WalletNode node) { @@ -183,7 +183,7 @@ public class Wallet { throw new UnsupportedOperationException("Cannot determine public keys for a custom policy"); } - return getKeystores().stream().map(keystore -> keystore.getKey(keyPurpose, index)).collect(Collectors.toList()); + return getKeystores().stream().map(keystore -> keystore.getPubKey(keyPurpose, index)).collect(Collectors.toList()); } public Address getAddress(WalletNode node) { @@ -615,7 +615,7 @@ public class Wallet { for(PSBTInput psbtInput : signingNodes.keySet()) { WalletNode walletNode = signingNodes.get(psbtInput); - Map keystoreKeysForNode = getKeystores().stream().collect(Collectors.toMap(keystore -> keystore.getKey(walletNode), Function.identity(), + Map keystoreKeysForNode = getKeystores().stream().collect(Collectors.toMap(keystore -> keystore.getPubKey(walletNode), Function.identity(), (u, v) -> { throw new IllegalStateException("Duplicate keys from different keystores for node " + walletNode.getDerivationPath()); }, LinkedHashMap::new)); @@ -628,6 +628,19 @@ public class Wallet { return signedKeystores; } + public void sign(PSBT psbt) throws MnemonicException { + Map signingNodes = getSigningNodes(psbt); + for(Keystore keystore : getKeystores()) { + if(keystore.hasSeed()) { + for(Map.Entry signingEntry : signingNodes.entrySet()) { + ECKey privKey = keystore.getKey(signingEntry.getValue()); + PSBTInput psbtInput = signingEntry.getKey(); + psbtInput.sign(privKey); + } + } + } + } + public BitcoinUnit getAutoUnit() { for(KeyPurpose keyPurpose : KeyPurpose.values()) { for(WalletNode addressNode : getNode(keyPurpose).getChildren()) {