mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-11-02 18:26:43 +00:00
add psbt taproot output fields, jade wallet
This commit is contained in:
parent
759ebb975c
commit
9cb95f2f8c
6 changed files with 119 additions and 15 deletions
|
@ -148,10 +148,11 @@ public class PSBT {
|
|||
for(int outputIndex = 0; outputIndex < outputNodes.size(); outputIndex++) {
|
||||
WalletNode outputNode = outputNodes.get(outputIndex);
|
||||
if(outputNode == null) {
|
||||
PSBTOutput externalRecipientOutput = new PSBTOutput(null, null, Collections.emptyMap(), Collections.emptyMap());
|
||||
PSBTOutput externalRecipientOutput = new PSBTOutput(null, null, null, Collections.emptyMap(), Collections.emptyMap(), null);
|
||||
psbtOutputs.add(externalRecipientOutput);
|
||||
} else {
|
||||
TransactionOutput txOutput = transaction.getOutputs().get(outputIndex);
|
||||
Wallet recipientWallet = outputNode.getWallet();
|
||||
|
||||
//Construct dummy transaction to spend the UTXO created by this wallet's txOutput
|
||||
Transaction transaction = new Transaction();
|
||||
|
@ -168,11 +169,17 @@ public class PSBT {
|
|||
}
|
||||
|
||||
Map<ECKey, KeyDerivation> derivedPublicKeys = new LinkedHashMap<>();
|
||||
for(Keystore keystore : outputNode.getWallet().getKeystores()) {
|
||||
derivedPublicKeys.put(keystore.getPubKey(outputNode), keystore.getKeyDerivation().extend(outputNode.getDerivation()));
|
||||
ECKey tapInternalKey = null;
|
||||
for(Keystore keystore : recipientWallet.getKeystores()) {
|
||||
derivedPublicKeys.put(recipientWallet.getScriptType().getOutputKey(keystore.getPubKey(outputNode)), keystore.getKeyDerivation().extend(outputNode.getDerivation()));
|
||||
|
||||
//TODO: Implement Musig for multisig wallets
|
||||
if(recipientWallet.getScriptType() == ScriptType.P2TR) {
|
||||
tapInternalKey = keystore.getPubKey(outputNode);
|
||||
}
|
||||
}
|
||||
|
||||
PSBTOutput walletOutput = new PSBTOutput(redeemScript, witnessScript, derivedPublicKeys, Collections.emptyMap());
|
||||
PSBTOutput walletOutput = new PSBTOutput(recipientWallet.getScriptType(), redeemScript, witnessScript, derivedPublicKeys, Collections.emptyMap(), tapInternalKey);
|
||||
psbtOutputs.add(walletOutput);
|
||||
}
|
||||
}
|
||||
|
@ -476,7 +483,8 @@ public class PSBT {
|
|||
for(PSBTInput psbtInput : getPsbtInputs()) {
|
||||
List<PSBTEntry> inputEntries = psbtInput.getInputEntries();
|
||||
for(PSBTEntry entry : inputEntries) {
|
||||
if(includeXpubs || entry.getKeyType() != PSBT_IN_BIP32_DERIVATION) {
|
||||
if(includeXpubs || (entry.getKeyType() != PSBT_IN_BIP32_DERIVATION && entry.getKeyType() != PSBT_IN_PROPRIETARY
|
||||
&& entry.getKeyType() != PSBT_IN_TAP_INTERNAL_KEY && entry.getKeyType() != PSBT_IN_TAP_BIP32_DERIVATION)) {
|
||||
entry.serializeToStream(baos);
|
||||
}
|
||||
}
|
||||
|
@ -486,7 +494,9 @@ public class PSBT {
|
|||
for(PSBTOutput psbtOutput : getPsbtOutputs()) {
|
||||
List<PSBTEntry> outputEntries = psbtOutput.getOutputEntries();
|
||||
for(PSBTEntry entry : outputEntries) {
|
||||
if(includeXpubs || (entry.getKeyType() != PSBT_OUT_REDEEM_SCRIPT && entry.getKeyType() != PSBT_OUT_WITNESS_SCRIPT && entry.getKeyType() != PSBT_OUT_BIP32_DERIVATION && entry.getKeyType() != PSBT_OUT_PROPRIETARY)) {
|
||||
if(includeXpubs || (entry.getKeyType() != PSBT_OUT_REDEEM_SCRIPT && entry.getKeyType() != PSBT_OUT_WITNESS_SCRIPT
|
||||
&& entry.getKeyType() != PSBT_OUT_BIP32_DERIVATION && entry.getKeyType() != PSBT_OUT_PROPRIETARY
|
||||
&& entry.getKeyType() != PSBT_OUT_TAP_INTERNAL_KEY && entry.getKeyType() != PSBT_OUT_TAP_BIP32_DERIVATION)) {
|
||||
entry.serializeToStream(baos);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -229,10 +229,12 @@ public class PSBTInput {
|
|||
ECKey tapPublicKey = ECKey.fromPublicOnly(entry.getKeyData());
|
||||
Map<KeyDerivation, List<Sha256Hash>> tapKeyDerivations = parseTaprootKeyDerivation(entry.getData());
|
||||
if(tapKeyDerivations.isEmpty()) {
|
||||
log.warn("PSBT provided an invalid taproot key derivation");
|
||||
log.warn("PSBT provided an invalid input taproot key derivation");
|
||||
} else {
|
||||
this.tapDerivedPublicKeys.put(tapPublicKey, tapKeyDerivations);
|
||||
log.debug("Found input taproot key derivation for key " + Utils.bytesToHex(entry.getKey()));
|
||||
for(KeyDerivation tapKeyDerivation : tapKeyDerivations.keySet()) {
|
||||
log.debug("Found input taproot key derivation for key " + Utils.bytesToHex(entry.getKeyData()) + " with master fingerprint " + tapKeyDerivation.getMasterFingerprint() + " at path " + tapKeyDerivation.getDerivationPath());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PSBT_IN_TAP_INTERNAL_KEY:
|
||||
|
|
|
@ -4,26 +4,30 @@ import com.sparrowwallet.drongo.KeyDerivation;
|
|||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import com.sparrowwallet.drongo.protocol.Script;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import static com.sparrowwallet.drongo.protocol.ScriptType.*;
|
||||
import static com.sparrowwallet.drongo.psbt.PSBTEntry.*;
|
||||
|
||||
public class PSBTOutput {
|
||||
public static final byte PSBT_OUT_REDEEM_SCRIPT = 0x00;
|
||||
public static final byte PSBT_OUT_WITNESS_SCRIPT = 0x01;
|
||||
public static final byte PSBT_OUT_BIP32_DERIVATION = 0x02;
|
||||
public static final byte PSBT_OUT_TAP_INTERNAL_KEY = 0x05;
|
||||
public static final byte PSBT_OUT_TAP_BIP32_DERIVATION = 0x07;
|
||||
public static final byte PSBT_OUT_PROPRIETARY = (byte)0xfc;
|
||||
|
||||
private Script redeemScript;
|
||||
private Script witnessScript;
|
||||
private final Map<ECKey, KeyDerivation> derivedPublicKeys = new LinkedHashMap<>();
|
||||
private final Map<String, String> proprietary = new LinkedHashMap<>();
|
||||
private Map<ECKey, Map<KeyDerivation, List<Sha256Hash>>> tapDerivedPublicKeys = new LinkedHashMap<>();
|
||||
private ECKey tapInternalKey;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PSBTOutput.class);
|
||||
|
||||
|
@ -31,11 +35,22 @@ public class PSBTOutput {
|
|||
//empty constructor
|
||||
}
|
||||
|
||||
PSBTOutput(Script redeemScript, Script witnessScript, Map<ECKey, KeyDerivation> derivedPublicKeys, Map<String, String> proprietary) {
|
||||
PSBTOutput(ScriptType scriptType, Script redeemScript, Script witnessScript, Map<ECKey, KeyDerivation> derivedPublicKeys, Map<String, String> proprietary, ECKey tapInternalKey) {
|
||||
this.redeemScript = redeemScript;
|
||||
this.witnessScript = witnessScript;
|
||||
|
||||
if(scriptType != P2TR) {
|
||||
this.derivedPublicKeys.putAll(derivedPublicKeys);
|
||||
}
|
||||
|
||||
this.proprietary.putAll(proprietary);
|
||||
|
||||
this.tapInternalKey = tapInternalKey;
|
||||
|
||||
if(tapInternalKey != null && !derivedPublicKeys.values().isEmpty()) {
|
||||
KeyDerivation tapKeyDerivation = derivedPublicKeys.values().iterator().next();
|
||||
tapDerivedPublicKeys.put(tapInternalKey, Map.of(tapKeyDerivation, Collections.emptyList()));
|
||||
}
|
||||
}
|
||||
|
||||
PSBTOutput(List<PSBTEntry> outputEntries) throws PSBTParseException {
|
||||
|
@ -64,6 +79,24 @@ public class PSBTOutput {
|
|||
proprietary.put(Utils.bytesToHex(entry.getKeyData()), Utils.bytesToHex(entry.getData()));
|
||||
log.debug("Found proprietary output " + Utils.bytesToHex(entry.getKeyData()) + ": " + Utils.bytesToHex(entry.getData()));
|
||||
break;
|
||||
case PSBT_OUT_TAP_INTERNAL_KEY:
|
||||
entry.checkOneByteKey();
|
||||
this.tapInternalKey = ECKey.fromPublicOnly(entry.getData());
|
||||
log.debug("Found output taproot internal key " + Utils.bytesToHex(entry.getData()));
|
||||
break;
|
||||
case PSBT_OUT_TAP_BIP32_DERIVATION:
|
||||
entry.checkOneBytePlusXOnlyPubKey();
|
||||
ECKey tapPublicKey = ECKey.fromPublicOnly(entry.getKeyData());
|
||||
Map<KeyDerivation, List<Sha256Hash>> tapKeyDerivations = parseTaprootKeyDerivation(entry.getData());
|
||||
if(tapKeyDerivations.isEmpty()) {
|
||||
log.warn("PSBT provided an invalid output taproot key derivation");
|
||||
} else {
|
||||
this.tapDerivedPublicKeys.put(tapPublicKey, tapKeyDerivations);
|
||||
for(KeyDerivation tapKeyDerivation : tapKeyDerivations.keySet()) {
|
||||
log.debug("Found output taproot key derivation for key " + Utils.bytesToHex(entry.getKeyData()) + " with master fingerprint " + tapKeyDerivation.getMasterFingerprint() + " at path " + tapKeyDerivation.getDerivationPath());
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log.warn("PSBT output not recognized key type: " + entry.getKeyType());
|
||||
}
|
||||
|
@ -89,6 +122,16 @@ public class PSBTOutput {
|
|||
entries.add(populateEntry(PSBT_OUT_PROPRIETARY, Utils.hexToBytes(entry.getKey()), Utils.hexToBytes(entry.getValue())));
|
||||
}
|
||||
|
||||
for(Map.Entry<ECKey, Map<KeyDerivation, List<Sha256Hash>>> entry : tapDerivedPublicKeys.entrySet()) {
|
||||
if(!entry.getValue().isEmpty()) {
|
||||
entries.add(populateEntry(PSBT_OUT_TAP_BIP32_DERIVATION, entry.getKey().getPubKeyXCoord(), serializeTaprootKeyDerivation(Collections.emptyList(), entry.getValue().keySet().iterator().next())));
|
||||
}
|
||||
}
|
||||
|
||||
if(tapInternalKey != null) {
|
||||
entries.add(populateEntry(PSBT_OUT_TAP_INTERNAL_KEY, null, tapInternalKey.getPubKeyXCoord()));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
|
@ -103,6 +146,12 @@ public class PSBTOutput {
|
|||
|
||||
derivedPublicKeys.putAll(psbtOutput.derivedPublicKeys);
|
||||
proprietary.putAll(psbtOutput.proprietary);
|
||||
|
||||
tapDerivedPublicKeys.putAll(psbtOutput.tapDerivedPublicKeys);
|
||||
|
||||
if(psbtOutput.tapInternalKey != null) {
|
||||
tapInternalKey = psbtOutput.tapInternalKey;
|
||||
}
|
||||
}
|
||||
|
||||
public Script getRedeemScript() {
|
||||
|
@ -132,4 +181,24 @@ public class PSBTOutput {
|
|||
public Map<String, String> getProprietary() {
|
||||
return proprietary;
|
||||
}
|
||||
|
||||
public Map<ECKey, Map<KeyDerivation, List<Sha256Hash>>> getTapDerivedPublicKeys() {
|
||||
return tapDerivedPublicKeys;
|
||||
}
|
||||
|
||||
public void setTapDerivedPublicKeys(Map<ECKey, Map<KeyDerivation, List<Sha256Hash>>> tapDerivedPublicKeys) {
|
||||
this.tapDerivedPublicKeys = tapDerivedPublicKeys;
|
||||
}
|
||||
|
||||
public ECKey getTapInternalKey() {
|
||||
return tapInternalKey;
|
||||
}
|
||||
|
||||
public void setTapInternalKey(ECKey tapInternalKey) {
|
||||
this.tapInternalKey = tapInternalKey;
|
||||
}
|
||||
|
||||
public void clearNonFinalFields() {
|
||||
tapDerivedPublicKeys.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.sparrowwallet.drongo.policy.PolicyType;
|
|||
import com.sparrowwallet.drongo.protocol.*;
|
||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||
import com.sparrowwallet.drongo.psbt.PSBTInput;
|
||||
import com.sparrowwallet.drongo.psbt.PSBTOutput;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
@ -1557,6 +1558,8 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
|
|||
psbtInput.clearNonFinalFields();
|
||||
}
|
||||
}
|
||||
|
||||
psbt.getPsbtOutputs().forEach(PSBTOutput::clearNonFinalFields);
|
||||
}
|
||||
|
||||
public BitcoinUnit getAutoUnit() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.sparrowwallet.drongo.wallet;
|
||||
|
||||
public enum WalletModel {
|
||||
SEED, SPARROW, BITCOIN_CORE, ELECTRUM, TREZOR_1, TREZOR_T, COLDCARD, LEDGER_NANO_S, LEDGER_NANO_X, DIGITALBITBOX_01, KEEPKEY, SPECTER_DESKTOP, COBO_VAULT, BITBOX_02, SPECTER_DIY, PASSPORT, BLUE_WALLET, KEYSTONE, SEEDSIGNER, CARAVAN, GORDIAN_SEED_TOOL;
|
||||
SEED, SPARROW, BITCOIN_CORE, ELECTRUM, TREZOR_1, TREZOR_T, COLDCARD, LEDGER_NANO_S, LEDGER_NANO_X, DIGITALBITBOX_01, KEEPKEY, SPECTER_DESKTOP, COBO_VAULT, BITBOX_02, SPECTER_DIY, PASSPORT, BLUE_WALLET, KEYSTONE, SEEDSIGNER, CARAVAN, GORDIAN_SEED_TOOL, JADE;
|
||||
|
||||
public static WalletModel getModel(String model) {
|
||||
return valueOf(model.toUpperCase());
|
||||
|
|
|
@ -11,6 +11,9 @@ import org.junit.After;
|
|||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PSBTTest {
|
||||
|
||||
@Test(expected = PSBTParseException.class)
|
||||
|
@ -379,6 +382,23 @@ public class PSBTTest {
|
|||
Assert.assertFalse(PSBT.isPSBT("x"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaproot() throws PSBTParseException {
|
||||
Network.set(Network.TESTNET);
|
||||
String strSignedPsbt = "cHNidP8BAH0CAAAAAdPUSBYKaQKOqAMgU2IcGuM6z7JtbkLe69OmYZoa4UxYAAAAAADmdQAAAp4CAAAAAAAAFgAUg+/zyGWbtKbIJb8ZasbGYDtItZhgrgEAAAAAACJRIJMQKWeQsI/WiRS+lmeeyeJaKFVnlVoBtXeuGTO7XIbrAAAAAE8BBDWHzwM27GqkgAAAAO1YZge2AP67ozhdoF9wgg2hpJw1jbVEXLKfxRQUNGEIAlog/wK83w7jxD37prhWrPenLxjGAJzkJKrj6h0ZPK8WED/ZeB1WAACAAQAAgAAAAIAAAQCJAgAAAAGBuBuu5OIheS4SKtYJufScCJDTWWLopBoXtFWhPDuRWQEAAAAA/f///wKNsQEAAAAAACJRIAjXFSnHwcD+J/obgd9CxVneHsUyFKM9xU7NY5K3DgCxHwIAAAAAAAAiUSAhUe66hJMCB1esHqXtxRVJHmviQ4ZjzFIwDWDPk+1KY6VyIQABASuNsQEAAAAAACJRIAjXFSnHwcD+J/obgd9CxVneHsUyFKM9xU7NY5K3DgCxAQMEAAAAAAETQCG/ZGuefjDVqBhmgVEuV1HbdxoZKDDWWTvTUrq6MJreRzj22k/WcFni6yPn9PGZkptZSNx9waf8ouP28ogJz24hFnRZPMvN82XI+lnim7dRKwFgpHnqiDoMGnIoFoSQRX7bGQA/2XgdVgAAgAEAAIAAAACAAQAAAAIAAAABFyB0WTzLzfNlyPpZ4pu3USsBYKR56og6DBpyKBaEkEV+2wAAIQflpK6JYWolG3K2DU7FU3hlkwSdU/69bglhZxaprqSvyxkAP9l4HVYAAIABAACAAAAAgAEAAAADAAAAAQUg5aSuiWFqJRtytg1OxVN4ZZMEnVP+vW4JYWcWqa6kr8sA";
|
||||
PSBT psbt = PSBT.fromString(strSignedPsbt);
|
||||
|
||||
Assert.assertEquals(0, psbt.getPsbtInputs().get(0).getDerivedPublicKeys().size());
|
||||
Assert.assertEquals("74593ccbcdf365c8fa59e29bb7512b0160a479ea883a0c1a7228168490457edb", Utils.bytesToHex(psbt.getPsbtInputs().get(0).getTapDerivedPublicKeys().keySet().iterator().next().getPubKeyXCoord()));
|
||||
Map<KeyDerivation, List<Sha256Hash>> tapInKeyDerivations = psbt.getPsbtInputs().get(0).getTapDerivedPublicKeys().values().iterator().next();
|
||||
Assert.assertEquals("3fd9781d", tapInKeyDerivations.keySet().iterator().next().getMasterFingerprint());
|
||||
|
||||
Assert.assertEquals(0, psbt.getPsbtOutputs().get(0).getDerivedPublicKeys().size());
|
||||
Assert.assertEquals("e5a4ae89616a251b72b60d4ec553786593049d53febd6e09616716a9aea4afcb", Utils.bytesToHex(psbt.getPsbtOutputs().get(1).getTapDerivedPublicKeys().keySet().iterator().next().getPubKeyXCoord()));
|
||||
Map<KeyDerivation, List<Sha256Hash>> tapOutKeyDerivations = psbt.getPsbtOutputs().get(1).getTapDerivedPublicKeys().values().iterator().next();
|
||||
Assert.assertEquals("3fd9781d", tapOutKeyDerivations.keySet().iterator().next().getMasterFingerprint());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
Network.set(null);
|
||||
|
|
Loading…
Reference in a new issue