mirror of
https://github.com/sparrowwallet/drongo.git
synced 2025-11-05 11:56:38 +00:00
add support for sending silent payments
This commit is contained in:
parent
0b3b1a5c3f
commit
6c7662ca09
7 changed files with 636 additions and 9 deletions
|
|
@ -328,14 +328,43 @@ public class ECKey {
|
||||||
|
|
||||||
/** Multiply the public point by the provided private key */
|
/** Multiply the public point by the provided private key */
|
||||||
public ECKey multiply(BigInteger privKey) {
|
public ECKey multiply(BigInteger privKey) {
|
||||||
|
return multiply(privKey, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Multiply the public point by the provided private key */
|
||||||
|
public ECKey multiply(BigInteger privKey, boolean compressed) {
|
||||||
ECPoint point = pub.get().multiply(privKey);
|
ECPoint point = pub.get().multiply(privKey);
|
||||||
return ECKey.fromPublicOnly(point, false);
|
return ECKey.fromPublicOnly(point, compressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Add to the public point by the provided public key */
|
/** Add to the public point by the provided public key */
|
||||||
public ECKey add(ECKey pubKey) {
|
public ECKey add(ECKey pubKey) {
|
||||||
|
return add(pubKey, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add to the public point by the provided public key */
|
||||||
|
public ECKey add(ECKey pubKey, boolean compressed) {
|
||||||
ECPoint point = pub.get().add(pubKey.getPubKeyPoint());
|
ECPoint point = pub.get().add(pubKey.getPubKeyPoint());
|
||||||
return ECKey.fromPublicOnly(point, false);
|
return ECKey.fromPublicOnly(point, compressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add to the private key by the provided private key using modular arithmetic */
|
||||||
|
public ECKey addPrivate(ECKey privKey) {
|
||||||
|
if(this.priv == null || privKey.priv == null) {
|
||||||
|
throw new IllegalStateException("Key did not contain a private key");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ECKey.fromPrivate(this.priv.add(privKey.priv).mod(CURVE.getN()), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Negate the provided private key */
|
||||||
|
public ECKey negate() {
|
||||||
|
if(priv == null) {
|
||||||
|
throw new IllegalStateException("Key did not contain a private key");
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInteger negatedPrivKey = CURVE.getN().subtract(priv);
|
||||||
|
return ECKey.fromPrivate(negatedPrivKey, isCompressed());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calculate the value of the public key point modulo the secp256k1 curve order */
|
/** Calculate the value of the public key point modulo the secp256k1 curve order */
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.sparrowwallet.drongo.silentpayments;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.Utils;
|
||||||
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
|
import com.sparrowwallet.drongo.address.P2TRAddress;
|
||||||
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Payment;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class SilentPayment extends Payment {
|
||||||
|
public static final Set<ScriptType> VALID_INPUT_SCRIPT_TYPES = Set.of(ScriptType.P2PKH, ScriptType.P2SH_P2WPKH, ScriptType.P2WPKH, ScriptType.P2TR);
|
||||||
|
|
||||||
|
private final SilentPaymentAddress silentPaymentAddress;
|
||||||
|
|
||||||
|
public SilentPayment(SilentPaymentAddress silentPaymentAddress, String label, long amount, boolean sendMax) {
|
||||||
|
super(getDummyAddress(), label, amount, sendMax);
|
||||||
|
this.silentPaymentAddress = silentPaymentAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Address getDummyAddress() {
|
||||||
|
return new P2TRAddress(Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SilentPaymentAddress getSilentPaymentAddress() {
|
||||||
|
return silentPaymentAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,15 @@ public class SilentPaymentScanAddress extends SilentPaymentAddress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SilentPaymentScanAddress getChangeAddress() {
|
||||||
|
return getLabelledAddress(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SilentPaymentScanAddress getLabelledAddress(int labelIndex) {
|
||||||
|
ECKey labelledSpendKey = SilentPaymentUtils.getLabelledSpendKey(getScanKey(), getSpendKey(), labelIndex);
|
||||||
|
return new SilentPaymentScanAddress(getScanKey(), labelledSpendKey);
|
||||||
|
}
|
||||||
|
|
||||||
public static SilentPaymentScanAddress from(DeterministicSeed deterministicSeed, int account) throws MnemonicException {
|
public static SilentPaymentScanAddress from(DeterministicSeed deterministicSeed, int account) throws MnemonicException {
|
||||||
Wallet spWallet = new Wallet();
|
Wallet spWallet = new Wallet();
|
||||||
spWallet.setPolicyType(PolicyType.SINGLE);
|
spWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,21 @@ package com.sparrowwallet.drongo.silentpayments;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
import com.sparrowwallet.drongo.protocol.*;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
|
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
|
||||||
|
import com.sparrowwallet.drongo.wallet.MnemonicException;
|
||||||
|
import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||||
import org.bitcoin.NativeSecp256k1;
|
import org.bitcoin.NativeSecp256k1;
|
||||||
import org.bitcoin.NativeSecp256k1Util;
|
import org.bitcoin.NativeSecp256k1Util;
|
||||||
import org.bitcoin.Secp256k1Context;
|
import org.bitcoin.Secp256k1Context;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.math.BigInteger;
|
||||||
import java.util.Arrays;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.List;
|
import java.nio.ByteOrder;
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static com.sparrowwallet.drongo.protocol.ScriptType.P2TR;
|
||||||
|
|
||||||
public class SilentPaymentUtils {
|
public class SilentPaymentUtils {
|
||||||
private static final Logger log = LoggerFactory.getLogger(SilentPaymentUtils.class);
|
private static final Logger log = LoggerFactory.getLogger(SilentPaymentUtils.class);
|
||||||
|
|
@ -26,6 +31,10 @@ public class SilentPaymentUtils {
|
||||||
(byte) 0x47, (byte) 0xbf, (byte) 0xee, (byte) 0x9a, (byte) 0xce, (byte) 0x80, (byte) 0x3a, (byte) 0xc0
|
(byte) 0x47, (byte) 0xbf, (byte) 0xee, (byte) 0x9a, (byte) 0xce, (byte) 0x80, (byte) 0x3a, (byte) 0xc0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static final String BIP_0352_INPUTS_TAG = "BIP0352/Inputs";
|
||||||
|
public static final String BIP_0352_SHARED_SECRET_TAG = "BIP0352/SharedSecret";
|
||||||
|
public static final String BIP_0352_LABEL_TAG = "BIP0352/Label";
|
||||||
|
|
||||||
public static boolean isEligible(Transaction tx, Map<HashIndex, Script> spentScriptPubKeys) {
|
public static boolean isEligible(Transaction tx, Map<HashIndex, Script> spentScriptPubKeys) {
|
||||||
if(!containsTaprootOutput(tx)) {
|
if(!containsTaprootOutput(tx)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -169,7 +178,7 @@ public class SilentPaymentUtils {
|
||||||
byte[] combinedPubKey = NativeSecp256k1.pubKeyCombine(inputPubKeys, true);
|
byte[] combinedPubKey = NativeSecp256k1.pubKeyCombine(inputPubKeys, true);
|
||||||
byte[] smallestOutpoint = tx.getInputs().stream().map(input -> input.getOutpoint().bitcoinSerialize()).min(new Utils.LexicographicByteArrayComparator()).orElseThrow();
|
byte[] smallestOutpoint = tx.getInputs().stream().map(input -> input.getOutpoint().bitcoinSerialize()).min(new Utils.LexicographicByteArrayComparator()).orElseThrow();
|
||||||
|
|
||||||
byte[] inputHash = Utils.taggedHash("BIP0352/Inputs", Utils.concat(smallestOutpoint, combinedPubKey));
|
byte[] inputHash = Utils.taggedHash(BIP_0352_INPUTS_TAG, Utils.concat(smallestOutpoint, combinedPubKey));
|
||||||
return NativeSecp256k1.pubKeyTweakMul(combinedPubKey, inputHash, true);
|
return NativeSecp256k1.pubKeyTweakMul(combinedPubKey, inputHash, true);
|
||||||
} catch(NativeSecp256k1Util.AssertFailException e) {
|
} catch(NativeSecp256k1Util.AssertFailException e) {
|
||||||
log.error("Error computing tweak", e);
|
log.error("Error computing tweak", e);
|
||||||
|
|
@ -177,4 +186,93 @@ public class SilentPaymentUtils {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void updateSilentPayments(List<SilentPayment> silentPayments, Map<BlockTransactionHashIndex, WalletNode> utxos) {
|
||||||
|
ECKey summedPrivateKey = getSummedPrivateKey(utxos.values());
|
||||||
|
BigInteger inputHash = getInputHash(utxos.keySet(), summedPrivateKey);
|
||||||
|
Map<ECKey, List<SilentPayment>> scanKeyGroups = getScanKeyGroups(silentPayments);
|
||||||
|
for(Map.Entry<ECKey, List<SilentPayment>> scanKeyGroup : scanKeyGroups.entrySet()) {
|
||||||
|
ECKey scanKey = scanKeyGroup.getKey();
|
||||||
|
ECKey sharedSecret = scanKey.multiply(inputHash).multiply(summedPrivateKey.getPrivKey(), true);
|
||||||
|
int k = 0;
|
||||||
|
for(SilentPayment silentPayment : scanKeyGroup.getValue()) {
|
||||||
|
BigInteger tk = new BigInteger(1, Utils.taggedHash(BIP_0352_SHARED_SECRET_TAG,
|
||||||
|
Utils.concat(sharedSecret.getPubKey(), ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(k).array())));
|
||||||
|
if(tk.equals(BigInteger.ZERO) || tk.compareTo(ECKey.CURVE.getCurve().getOrder()) >= 0) {
|
||||||
|
throw new IllegalArgumentException("The tk value is invalid for the eligible silent payments inputs");
|
||||||
|
}
|
||||||
|
ECKey spendKey = silentPayment.getSilentPaymentAddress().getSpendKey();
|
||||||
|
ECKey pkm = spendKey.add(ECKey.fromPublicOnly(ECKey.publicPointFromPrivate(tk).getEncoded(true)), true);
|
||||||
|
silentPayment.setAddress(P2TR.getAddress(pkm.getPubKeyXCoord()));
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<ECKey, List<SilentPayment>> getScanKeyGroups(Collection<SilentPayment> silentPayments) {
|
||||||
|
Map<ECKey, List<SilentPayment>> scanKeyGroups = new LinkedHashMap<>();
|
||||||
|
for(SilentPayment silentPayment : silentPayments) {
|
||||||
|
SilentPaymentAddress address = silentPayment.getSilentPaymentAddress();
|
||||||
|
List<SilentPayment> scanKeyGroup = scanKeyGroups.computeIfAbsent(address.getScanKey(), _ -> new ArrayList<>());
|
||||||
|
scanKeyGroup.add(silentPayment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scanKeyGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BigInteger getInputHash(Set<BlockTransactionHashIndex> outpoints, ECKey summedPrivateKey) {
|
||||||
|
byte[] smallestOutpoint = getSmallestOutpoint(outpoints);
|
||||||
|
byte[] concat = Utils.concat(smallestOutpoint, summedPrivateKey.getPubKey());
|
||||||
|
BigInteger inputHash = new BigInteger(1, Utils.taggedHash(BIP_0352_INPUTS_TAG, concat));
|
||||||
|
if(inputHash.equals(BigInteger.ZERO) || inputHash.compareTo(ECKey.CURVE.getCurve().getOrder()) >= 0) {
|
||||||
|
throw new IllegalArgumentException("The input hash is invalid for the eligible silent payments inputs");
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ECKey getSummedPrivateKey(Collection<WalletNode> walletNodes) {
|
||||||
|
ECKey summedPrivateKey = null;
|
||||||
|
for(WalletNode walletNode : walletNodes) {
|
||||||
|
if(!walletNode.getWallet().canSendSilentPayments()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ECKey privateKey = walletNode.getWallet().getKeystores().getFirst().getKey(walletNode);
|
||||||
|
if(walletNode.getWallet().getScriptType() == P2TR && !privateKey.getPubKeyPoint().getYCoord().toBigInteger().mod(BigInteger.TWO).equals(BigInteger.ZERO)) {
|
||||||
|
privateKey = privateKey.negate();
|
||||||
|
}
|
||||||
|
if(summedPrivateKey == null) {
|
||||||
|
summedPrivateKey = privateKey;
|
||||||
|
} else {
|
||||||
|
summedPrivateKey = summedPrivateKey.addPrivate(privateKey);
|
||||||
|
}
|
||||||
|
} catch(MnemonicException e) {
|
||||||
|
throw new IllegalArgumentException("Invalid wallet mnemonic for sending silent payment", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(summedPrivateKey == null) {
|
||||||
|
throw new IllegalArgumentException("There are no eligible inputs to derive a silent payments shared secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(summedPrivateKey.getPrivKey().equals(BigInteger.ZERO)) {
|
||||||
|
throw new IllegalArgumentException("The summed private key is zero for the eligible silent payments inputs");
|
||||||
|
}
|
||||||
|
|
||||||
|
return summedPrivateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] getSmallestOutpoint(Set<BlockTransactionHashIndex> outpoints) {
|
||||||
|
return outpoints.stream().map(outpoint -> new TransactionOutPoint(outpoint.getHash(), outpoint.getIndex())).map(TransactionOutPoint::bitcoinSerialize)
|
||||||
|
.sorted(new Utils.LexicographicByteArrayComparator())
|
||||||
|
.findFirst().orElseThrow(() -> new IllegalArgumentException("No inputs provided to calculate silent payments input hash"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ECKey getLabelledSpendKey(ECKey scanPrivateKey, ECKey spendPublicKey, int labelIndex) {
|
||||||
|
BigInteger labelTweak = new BigInteger(1, Utils.taggedHash(BIP_0352_LABEL_TAG,
|
||||||
|
Utils.concat(scanPrivateKey.getPrivKeyBytes(), ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(labelIndex).array())));
|
||||||
|
return spendPublicKey.add(ECKey.fromPublicOnly(ECKey.publicPointFromPrivate(labelTweak).getEncoded(true)), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ import com.sparrowwallet.drongo.protocol.*;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBTInput;
|
import com.sparrowwallet.drongo.psbt.PSBTInput;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBTOutput;
|
import com.sparrowwallet.drongo.psbt.PSBTOutput;
|
||||||
|
import com.sparrowwallet.drongo.silentpayments.SilentPayment;
|
||||||
|
import com.sparrowwallet.drongo.silentpayments.SilentPaymentUtils;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
@ -319,6 +321,11 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
|
||||||
return !isMasterWallet() && getKeystores().size() == 1 && getKeystores().get(0).getSource() == KeystoreSource.SW_PAYMENT_CODE;
|
return !isMasterWallet() && getKeystores().size() == 1 && getKeystores().get(0).getSource() == KeystoreSource.SW_PAYMENT_CODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean canSendSilentPayments() {
|
||||||
|
return getKeystores().size() == 1 && getKeystores().getFirst().hasPrivateKey() && policyType == PolicyType.SINGLE
|
||||||
|
&& SilentPayment.VALID_INPUT_SCRIPT_TYPES.contains(scriptType);
|
||||||
|
}
|
||||||
|
|
||||||
public StandardAccount getStandardAccountType() {
|
public StandardAccount getStandardAccountType() {
|
||||||
int accountIndex = getAccountIndex();
|
int accountIndex = getAccountIndex();
|
||||||
return Arrays.stream(StandardAccount.values()).filter(standardAccount -> standardAccount.getChildNumber().num() == accountIndex).findFirst().orElse(null);
|
return Arrays.stream(StandardAccount.values()).filter(standardAccount -> standardAccount.getChildNumber().num() == accountIndex).findFirst().orElse(null);
|
||||||
|
|
@ -1063,6 +1070,12 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
|
||||||
txPayments.add(fakeMixPayment);
|
txPayments.add(fakeMixPayment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<SilentPayment> silentPayments = payments.stream().filter(payment -> payment instanceof SilentPayment)
|
||||||
|
.map(payment -> (SilentPayment)payment).collect(Collectors.toList());
|
||||||
|
if(!silentPayments.isEmpty()) {
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, selectedUtxos);
|
||||||
|
}
|
||||||
|
|
||||||
//Add recipient outputs
|
//Add recipient outputs
|
||||||
for(Payment payment : txPayments) {
|
for(Payment payment : txPayments) {
|
||||||
transaction.addOutput(payment.getAmount(), payment.getAddress());
|
transaction.addOutput(payment.getAmount(), payment.getAddress());
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,18 @@ public class SilentPaymentScanAddressTest {
|
||||||
Assertions.assertEquals("tsp1qqgksl44sjwjkedsmrfmf2xqsnyt2njtjp5plk2kzjlnd9el2n76awqe5j974lvkf2utv7nrg0eaug55z86n6n3v4e9alnftdzgqk6pqmm5dphvxn", silentPaymentScanAddress.getAddress());
|
Assertions.assertEquals("tsp1qqgksl44sjwjkedsmrfmf2xqsnyt2njtjp5plk2kzjlnd9el2n76awqe5j974lvkf2utv7nrg0eaug55z86n6n3v4e9alnftdzgqk6pqmm5dphvxn", silentPaymentScanAddress.getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLabels() {
|
||||||
|
ECKey scanPrivateKey = ECKey.fromPrivate(Utils.hexToBytes("0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c"));
|
||||||
|
ECKey spendPrivateKey = ECKey.fromPrivate(Utils.hexToBytes("9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3"));
|
||||||
|
|
||||||
|
SilentPaymentScanAddress unlabelled = SilentPaymentScanAddress.from(scanPrivateKey, ECKey.fromPublicOnly(spendPrivateKey));
|
||||||
|
Assertions.assertEquals("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", unlabelled.getAddress());
|
||||||
|
Assertions.assertEquals("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", unlabelled.getLabelledAddress(2).getAddress());
|
||||||
|
Assertions.assertEquals("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", unlabelled.getLabelledAddress(3).getAddress());
|
||||||
|
Assertions.assertEquals("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5", unlabelled.getLabelledAddress(1001337).getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
Network.set(null);
|
Network.set(null);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,17 @@ package com.sparrowwallet.drongo.silentpayments;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
|
import com.sparrowwallet.drongo.policy.Policy;
|
||||||
|
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||||
import com.sparrowwallet.drongo.protocol.*;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
|
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class SilentPaymentUtilsTest {
|
public class SilentPaymentUtilsTest {
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -59,4 +64,437 @@ public class SilentPaymentUtilsTest {
|
||||||
|
|
||||||
Assertions.assertFalse(SilentPaymentUtils.containsTaprootOutput(transaction));
|
Assertions.assertFalse(SilentPaymentUtils.containsTaprootOutput(transaction));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleSendTwoInputs() {
|
||||||
|
Wallet sendWallet = new Wallet();
|
||||||
|
sendWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
sendWallet.setScriptType(ScriptType.P2WPKH);
|
||||||
|
Map<BlockTransactionHashIndex, WalletNode> utxos = new LinkedHashMap<>();
|
||||||
|
Map<WalletNode, ECKey> privateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
WalletNode walletNode0 = new WalletNode(sendWallet, "/0/0");
|
||||||
|
BlockTransactionHashIndex ref0 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey0 = ECKey.fromPrivate(Utils.hexToBytes("eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1"));
|
||||||
|
utxos.put(ref0, walletNode0);
|
||||||
|
privateKeys.put(walletNode0, privKey0);
|
||||||
|
|
||||||
|
WalletNode walletNode1 = new WalletNode(sendWallet, "/0/1");
|
||||||
|
BlockTransactionHashIndex ref1 = new BlockTransactionHashIndex(Sha256Hash.wrap("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey1 = ECKey.fromPrivate(Utils.hexToBytes("93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16"));
|
||||||
|
utxos.put(ref1, walletNode1);
|
||||||
|
privateKeys.put(walletNode1, privKey1);
|
||||||
|
|
||||||
|
TestKeystore sendKeystore = new TestKeystore(privateKeys);
|
||||||
|
sendWallet.getKeystores().add(sendKeystore);
|
||||||
|
sendWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, sendWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
List<SilentPayment> silentPayments = List.of(new SilentPayment(silentPaymentAddress, "", 0, false));
|
||||||
|
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, utxos);
|
||||||
|
Assertions.assertEquals(1, silentPayments.size());
|
||||||
|
Assertions.assertEquals("3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", Utils.bytesToHex(silentPayments.getFirst().getAddress().getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleSendTwoInputsReversed() {
|
||||||
|
Wallet sendWallet = new Wallet();
|
||||||
|
sendWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
sendWallet.setScriptType(ScriptType.P2WPKH);
|
||||||
|
Map<BlockTransactionHashIndex, WalletNode> utxos = new LinkedHashMap<>();
|
||||||
|
Map<WalletNode, ECKey> privateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
WalletNode walletNode0 = new WalletNode(sendWallet, "/0/0");
|
||||||
|
BlockTransactionHashIndex ref0 = new BlockTransactionHashIndex(Sha256Hash.wrap("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey0 = ECKey.fromPrivate(Utils.hexToBytes("eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1"));
|
||||||
|
utxos.put(ref0, walletNode0);
|
||||||
|
privateKeys.put(walletNode0, privKey0);
|
||||||
|
|
||||||
|
WalletNode walletNode1 = new WalletNode(sendWallet, "/0/1");
|
||||||
|
BlockTransactionHashIndex ref1 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey1 = ECKey.fromPrivate(Utils.hexToBytes("93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16"));
|
||||||
|
utxos.put(ref1, walletNode1);
|
||||||
|
privateKeys.put(walletNode1, privKey1);
|
||||||
|
|
||||||
|
TestKeystore sendKeystore = new TestKeystore(privateKeys);
|
||||||
|
sendWallet.getKeystores().add(sendKeystore);
|
||||||
|
sendWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, sendWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
List<SilentPayment> silentPayments = List.of(new SilentPayment(silentPaymentAddress, "", 0, false));
|
||||||
|
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, utxos);
|
||||||
|
Assertions.assertEquals(1, silentPayments.size());
|
||||||
|
Assertions.assertEquals("3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", Utils.bytesToHex(silentPayments.getFirst().getAddress().getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleSendTwoInputsSameTransaction() {
|
||||||
|
Wallet sendWallet = new Wallet();
|
||||||
|
sendWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
sendWallet.setScriptType(ScriptType.P2WPKH);
|
||||||
|
Map<BlockTransactionHashIndex, WalletNode> utxos = new LinkedHashMap<>();
|
||||||
|
Map<WalletNode, ECKey> privateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
WalletNode walletNode0 = new WalletNode(sendWallet, "/0/0");
|
||||||
|
BlockTransactionHashIndex ref0 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 3, 0);
|
||||||
|
ECKey privKey0 = ECKey.fromPrivate(Utils.hexToBytes("eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1"));
|
||||||
|
utxos.put(ref0, walletNode0);
|
||||||
|
privateKeys.put(walletNode0, privKey0);
|
||||||
|
|
||||||
|
WalletNode walletNode1 = new WalletNode(sendWallet, "/0/1");
|
||||||
|
BlockTransactionHashIndex ref1 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 7, 0);
|
||||||
|
ECKey privKey1 = ECKey.fromPrivate(Utils.hexToBytes("93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16"));
|
||||||
|
utxos.put(ref1, walletNode1);
|
||||||
|
privateKeys.put(walletNode1, privKey1);
|
||||||
|
|
||||||
|
TestKeystore sendKeystore = new TestKeystore(privateKeys);
|
||||||
|
sendWallet.getKeystores().add(sendKeystore);
|
||||||
|
sendWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, sendWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
List<SilentPayment> silentPayments = List.of(new SilentPayment(silentPaymentAddress, "", 0, false));
|
||||||
|
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, utxos);
|
||||||
|
Assertions.assertEquals(1, silentPayments.size());
|
||||||
|
Assertions.assertEquals("79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6", Utils.bytesToHex(silentPayments.getFirst().getAddress().getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleSendTwoInputsSameTransactionReversed() {
|
||||||
|
Wallet sendWallet = new Wallet();
|
||||||
|
sendWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
sendWallet.setScriptType(ScriptType.P2WPKH);
|
||||||
|
Map<BlockTransactionHashIndex, WalletNode> utxos = new LinkedHashMap<>();
|
||||||
|
Map<WalletNode, ECKey> privateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
WalletNode walletNode0 = new WalletNode(sendWallet, "/0/0");
|
||||||
|
BlockTransactionHashIndex ref0 = new BlockTransactionHashIndex(Sha256Hash.wrap("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"), 0, null, null, 7, 0);
|
||||||
|
ECKey privKey0 = ECKey.fromPrivate(Utils.hexToBytes("eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1"));
|
||||||
|
utxos.put(ref0, walletNode0);
|
||||||
|
privateKeys.put(walletNode0, privKey0);
|
||||||
|
|
||||||
|
WalletNode walletNode1 = new WalletNode(sendWallet, "/0/1");
|
||||||
|
BlockTransactionHashIndex ref1 = new BlockTransactionHashIndex(Sha256Hash.wrap("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"), 0, null, null, 3, 0);
|
||||||
|
ECKey privKey1 = ECKey.fromPrivate(Utils.hexToBytes("93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16"));
|
||||||
|
utxos.put(ref1, walletNode1);
|
||||||
|
privateKeys.put(walletNode1, privKey1);
|
||||||
|
|
||||||
|
TestKeystore sendKeystore = new TestKeystore(privateKeys);
|
||||||
|
sendWallet.getKeystores().add(sendKeystore);
|
||||||
|
sendWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, sendWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
List<SilentPayment> silentPayments = List.of(new SilentPayment(silentPaymentAddress, "", 0, false));
|
||||||
|
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, utxos);
|
||||||
|
Assertions.assertEquals(1, silentPayments.size());
|
||||||
|
Assertions.assertEquals("f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38", Utils.bytesToHex(silentPayments.getFirst().getAddress().getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOutpointOrderingIndex() {
|
||||||
|
Wallet sendWallet = new Wallet();
|
||||||
|
sendWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
sendWallet.setScriptType(ScriptType.P2WPKH);
|
||||||
|
Map<BlockTransactionHashIndex, WalletNode> utxos = new LinkedHashMap<>();
|
||||||
|
Map<WalletNode, ECKey> privateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
WalletNode walletNode0 = new WalletNode(sendWallet, "/0/0");
|
||||||
|
BlockTransactionHashIndex ref0 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 1, 0);
|
||||||
|
ECKey privKey0 = ECKey.fromPrivate(Utils.hexToBytes("eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1"));
|
||||||
|
utxos.put(ref0, walletNode0);
|
||||||
|
privateKeys.put(walletNode0, privKey0);
|
||||||
|
|
||||||
|
WalletNode walletNode1 = new WalletNode(sendWallet, "/0/1");
|
||||||
|
BlockTransactionHashIndex ref1 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 256, 0);
|
||||||
|
ECKey privKey1 = ECKey.fromPrivate(Utils.hexToBytes("93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16"));
|
||||||
|
utxos.put(ref1, walletNode1);
|
||||||
|
privateKeys.put(walletNode1, privKey1);
|
||||||
|
|
||||||
|
TestKeystore sendKeystore = new TestKeystore(privateKeys);
|
||||||
|
sendWallet.getKeystores().add(sendKeystore);
|
||||||
|
sendWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, sendWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
List<SilentPayment> silentPayments = List.of(new SilentPayment(silentPaymentAddress, "", 0, false));
|
||||||
|
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, utxos);
|
||||||
|
Assertions.assertEquals(1, silentPayments.size());
|
||||||
|
Assertions.assertEquals("a85ef8701394b517a4b35217c4bd37ac01ebeed4b008f8d0879f9e09ba95319c", Utils.bytesToHex(silentPayments.getFirst().getAddress().getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSingleRecipientSamePubKey() {
|
||||||
|
Wallet sendWallet = new Wallet();
|
||||||
|
sendWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
sendWallet.setScriptType(ScriptType.P2WPKH);
|
||||||
|
Map<BlockTransactionHashIndex, WalletNode> utxos = new LinkedHashMap<>();
|
||||||
|
Map<WalletNode, ECKey> privateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
WalletNode walletNode0 = new WalletNode(sendWallet, "/0/0");
|
||||||
|
BlockTransactionHashIndex ref0 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey0 = ECKey.fromPrivate(Utils.hexToBytes("eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1"));
|
||||||
|
utxos.put(ref0, walletNode0);
|
||||||
|
privateKeys.put(walletNode0, privKey0);
|
||||||
|
|
||||||
|
WalletNode walletNode1 = new WalletNode(sendWallet, "/0/1");
|
||||||
|
BlockTransactionHashIndex ref1 = new BlockTransactionHashIndex(Sha256Hash.wrap("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey1 = ECKey.fromPrivate(Utils.hexToBytes("eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1"));
|
||||||
|
utxos.put(ref1, walletNode1);
|
||||||
|
privateKeys.put(walletNode1, privKey1);
|
||||||
|
|
||||||
|
TestKeystore sendKeystore = new TestKeystore(privateKeys);
|
||||||
|
sendWallet.getKeystores().add(sendKeystore);
|
||||||
|
sendWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, sendWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
List<SilentPayment> silentPayments = List.of(new SilentPayment(silentPaymentAddress, "", 0, false));
|
||||||
|
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, utxos);
|
||||||
|
Assertions.assertEquals(1, silentPayments.size());
|
||||||
|
Assertions.assertEquals("548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566", Utils.bytesToHex(silentPayments.getFirst().getAddress().getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSingleRecipientTaprootOnlyEvenY() {
|
||||||
|
Wallet sendWallet = new Wallet();
|
||||||
|
sendWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
sendWallet.setScriptType(ScriptType.P2TR);
|
||||||
|
Map<BlockTransactionHashIndex, WalletNode> utxos = new LinkedHashMap<>();
|
||||||
|
Map<WalletNode, ECKey> privateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
WalletNode walletNode0 = new WalletNode(sendWallet, "/0/0");
|
||||||
|
BlockTransactionHashIndex ref0 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey0 = ECKey.fromPrivate(Utils.hexToBytes("eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1"));
|
||||||
|
utxos.put(ref0, walletNode0);
|
||||||
|
privateKeys.put(walletNode0, privKey0);
|
||||||
|
|
||||||
|
WalletNode walletNode1 = new WalletNode(sendWallet, "/0/1");
|
||||||
|
BlockTransactionHashIndex ref1 = new BlockTransactionHashIndex(Sha256Hash.wrap("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey1 = ECKey.fromPrivate(Utils.hexToBytes("fc8716a97a48ba9a05a98ae47b5cd201a25a7fd5d8b73c203c5f7b6b6b3b6ad7"));
|
||||||
|
utxos.put(ref1, walletNode1);
|
||||||
|
privateKeys.put(walletNode1, privKey1);
|
||||||
|
|
||||||
|
TestKeystore sendKeystore = new TestKeystore(privateKeys);
|
||||||
|
sendWallet.getKeystores().add(sendKeystore);
|
||||||
|
sendWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, sendWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
List<SilentPayment> silentPayments = List.of(new SilentPayment(silentPaymentAddress, "", 0, false));
|
||||||
|
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, utxos);
|
||||||
|
Assertions.assertEquals(1, silentPayments.size());
|
||||||
|
Assertions.assertEquals("de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb", Utils.bytesToHex(silentPayments.getFirst().getAddress().getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSingleRecipientTaprootOnlyMixedY() {
|
||||||
|
Wallet sendWallet = new Wallet();
|
||||||
|
sendWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
sendWallet.setScriptType(ScriptType.P2TR);
|
||||||
|
Map<BlockTransactionHashIndex, WalletNode> utxos = new LinkedHashMap<>();
|
||||||
|
Map<WalletNode, ECKey> privateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
WalletNode walletNode0 = new WalletNode(sendWallet, "/0/0");
|
||||||
|
BlockTransactionHashIndex ref0 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey0 = ECKey.fromPrivate(Utils.hexToBytes("eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1"));
|
||||||
|
utxos.put(ref0, walletNode0);
|
||||||
|
privateKeys.put(walletNode0, privKey0);
|
||||||
|
|
||||||
|
WalletNode walletNode1 = new WalletNode(sendWallet, "/0/1");
|
||||||
|
BlockTransactionHashIndex ref1 = new BlockTransactionHashIndex(Sha256Hash.wrap("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey1 = ECKey.fromPrivate(Utils.hexToBytes("1d37787c2b7116ee983e9f9c13269df29091b391c04db94239e0d2bc2182c3bf"));
|
||||||
|
utxos.put(ref1, walletNode1);
|
||||||
|
privateKeys.put(walletNode1, privKey1);
|
||||||
|
|
||||||
|
TestKeystore sendKeystore = new TestKeystore(privateKeys);
|
||||||
|
sendWallet.getKeystores().add(sendKeystore);
|
||||||
|
sendWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, sendWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
List<SilentPayment> silentPayments = List.of(new SilentPayment(silentPaymentAddress, "", 0, false));
|
||||||
|
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, utxos);
|
||||||
|
Assertions.assertEquals(1, silentPayments.size());
|
||||||
|
Assertions.assertEquals("77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1", Utils.bytesToHex(silentPayments.getFirst().getAddress().getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSingleRecipientTaprootEvenYAndNonTaproot() {
|
||||||
|
Wallet taprootWallet = new Wallet();
|
||||||
|
taprootWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
taprootWallet.setScriptType(ScriptType.P2TR);
|
||||||
|
Map<WalletNode, ECKey> taprootPrivateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
Wallet segwitWallet = new Wallet();
|
||||||
|
segwitWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
segwitWallet.setScriptType(ScriptType.P2WPKH);
|
||||||
|
Map<WalletNode, ECKey> segwitPrivateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
Map<BlockTransactionHashIndex, WalletNode> utxos = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
WalletNode walletNode0 = new WalletNode(taprootWallet, "/0/0");
|
||||||
|
BlockTransactionHashIndex ref0 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey0 = ECKey.fromPrivate(Utils.hexToBytes("eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1"));
|
||||||
|
utxos.put(ref0, walletNode0);
|
||||||
|
taprootPrivateKeys.put(walletNode0, privKey0);
|
||||||
|
|
||||||
|
WalletNode walletNode1 = new WalletNode(segwitWallet, "/0/1");
|
||||||
|
BlockTransactionHashIndex ref1 = new BlockTransactionHashIndex(Sha256Hash.wrap("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey1 = ECKey.fromPrivate(Utils.hexToBytes("8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3"));
|
||||||
|
utxos.put(ref1, walletNode1);
|
||||||
|
segwitPrivateKeys.put(walletNode1, privKey1);
|
||||||
|
|
||||||
|
TestKeystore taprootKeystore = new TestKeystore(taprootPrivateKeys);
|
||||||
|
taprootWallet.getKeystores().add(taprootKeystore);
|
||||||
|
taprootWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, taprootWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
TestKeystore segwitKeystore = new TestKeystore(segwitPrivateKeys);
|
||||||
|
segwitWallet.getKeystores().add(segwitKeystore);
|
||||||
|
segwitWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, segwitWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
List<SilentPayment> silentPayments = List.of(new SilentPayment(silentPaymentAddress, "", 0, false));
|
||||||
|
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, utxos);
|
||||||
|
Assertions.assertEquals(1, silentPayments.size());
|
||||||
|
Assertions.assertEquals("30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0", Utils.bytesToHex(silentPayments.getFirst().getAddress().getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSingleRecipientTaprootOddYAndNonTaproot() {
|
||||||
|
Wallet taprootWallet = new Wallet();
|
||||||
|
taprootWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
taprootWallet.setScriptType(ScriptType.P2TR);
|
||||||
|
Map<WalletNode, ECKey> taprootPrivateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
Wallet segwitWallet = new Wallet();
|
||||||
|
segwitWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
segwitWallet.setScriptType(ScriptType.P2WPKH);
|
||||||
|
Map<WalletNode, ECKey> segwitPrivateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
Map<BlockTransactionHashIndex, WalletNode> utxos = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
WalletNode walletNode0 = new WalletNode(taprootWallet, "/0/0");
|
||||||
|
BlockTransactionHashIndex ref0 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey0 = ECKey.fromPrivate(Utils.hexToBytes("1d37787c2b7116ee983e9f9c13269df29091b391c04db94239e0d2bc2182c3bf"));
|
||||||
|
utxos.put(ref0, walletNode0);
|
||||||
|
taprootPrivateKeys.put(walletNode0, privKey0);
|
||||||
|
|
||||||
|
WalletNode walletNode1 = new WalletNode(segwitWallet, "/0/1");
|
||||||
|
BlockTransactionHashIndex ref1 = new BlockTransactionHashIndex(Sha256Hash.wrap("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey1 = ECKey.fromPrivate(Utils.hexToBytes("8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3"));
|
||||||
|
utxos.put(ref1, walletNode1);
|
||||||
|
segwitPrivateKeys.put(walletNode1, privKey1);
|
||||||
|
|
||||||
|
TestKeystore taprootKeystore = new TestKeystore(taprootPrivateKeys);
|
||||||
|
taprootWallet.getKeystores().add(taprootKeystore);
|
||||||
|
taprootWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, taprootWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
TestKeystore segwitKeystore = new TestKeystore(segwitPrivateKeys);
|
||||||
|
segwitWallet.getKeystores().add(segwitKeystore);
|
||||||
|
segwitWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, segwitWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
List<SilentPayment> silentPayments = List.of(new SilentPayment(silentPaymentAddress, "", 0, false));
|
||||||
|
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, utxos);
|
||||||
|
Assertions.assertEquals(1, silentPayments.size());
|
||||||
|
Assertions.assertEquals("359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a", Utils.bytesToHex(silentPayments.getFirst().getAddress().getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleOutputsSameRecipient() {
|
||||||
|
Wallet sendWallet = new Wallet();
|
||||||
|
sendWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
sendWallet.setScriptType(ScriptType.P2WPKH);
|
||||||
|
Map<BlockTransactionHashIndex, WalletNode> utxos = new LinkedHashMap<>();
|
||||||
|
Map<WalletNode, ECKey> privateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
WalletNode walletNode0 = new WalletNode(sendWallet, "/0/0");
|
||||||
|
BlockTransactionHashIndex ref0 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey0 = ECKey.fromPrivate(Utils.hexToBytes("eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1"));
|
||||||
|
utxos.put(ref0, walletNode0);
|
||||||
|
privateKeys.put(walletNode0, privKey0);
|
||||||
|
|
||||||
|
WalletNode walletNode1 = new WalletNode(sendWallet, "/0/1");
|
||||||
|
BlockTransactionHashIndex ref1 = new BlockTransactionHashIndex(Sha256Hash.wrap("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey1 = ECKey.fromPrivate(Utils.hexToBytes("0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a"));
|
||||||
|
utxos.put(ref1, walletNode1);
|
||||||
|
privateKeys.put(walletNode1, privKey1);
|
||||||
|
|
||||||
|
TestKeystore sendKeystore = new TestKeystore(privateKeys);
|
||||||
|
sendWallet.getKeystores().add(sendKeystore);
|
||||||
|
sendWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, sendWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
SilentPaymentAddress silentPaymentAddress0 = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
SilentPaymentAddress silentPaymentAddress1 = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
List<SilentPayment> silentPayments = List.of(new SilentPayment(silentPaymentAddress0, "First", 0, false), new SilentPayment(silentPaymentAddress1, "Second", 0, false));
|
||||||
|
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, utxos);
|
||||||
|
Assertions.assertEquals(2, silentPayments.size());
|
||||||
|
Assertions.assertEquals("First", silentPayments.getFirst().getLabel());
|
||||||
|
Assertions.assertEquals("f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", Utils.bytesToHex(silentPayments.getFirst().getAddress().getData()));
|
||||||
|
Assertions.assertEquals("Second", silentPayments.getLast().getLabel());
|
||||||
|
Assertions.assertEquals("e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", Utils.bytesToHex(silentPayments.getLast().getAddress().getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleOutputsMultipleRecipients() {
|
||||||
|
Wallet sendWallet = new Wallet();
|
||||||
|
sendWallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
sendWallet.setScriptType(ScriptType.P2WPKH);
|
||||||
|
Map<BlockTransactionHashIndex, WalletNode> utxos = new LinkedHashMap<>();
|
||||||
|
Map<WalletNode, ECKey> privateKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
WalletNode walletNode0 = new WalletNode(sendWallet, "/0/0");
|
||||||
|
BlockTransactionHashIndex ref0 = new BlockTransactionHashIndex(Sha256Hash.wrap("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey0 = ECKey.fromPrivate(Utils.hexToBytes("eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1"));
|
||||||
|
utxos.put(ref0, walletNode0);
|
||||||
|
privateKeys.put(walletNode0, privKey0);
|
||||||
|
|
||||||
|
WalletNode walletNode1 = new WalletNode(sendWallet, "/0/1");
|
||||||
|
BlockTransactionHashIndex ref1 = new BlockTransactionHashIndex(Sha256Hash.wrap("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"), 0, null, null, 0, 0);
|
||||||
|
ECKey privKey1 = ECKey.fromPrivate(Utils.hexToBytes("0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a"));
|
||||||
|
utxos.put(ref1, walletNode1);
|
||||||
|
privateKeys.put(walletNode1, privKey1);
|
||||||
|
|
||||||
|
TestKeystore sendKeystore = new TestKeystore(privateKeys);
|
||||||
|
sendWallet.getKeystores().add(sendKeystore);
|
||||||
|
sendWallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, sendWallet.getKeystores(), 1));
|
||||||
|
|
||||||
|
SilentPaymentAddress silentPaymentAddress0 = SilentPaymentAddress.from("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv");
|
||||||
|
SilentPaymentAddress silentPaymentAddress1 = SilentPaymentAddress.from("sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn");
|
||||||
|
SilentPaymentAddress silentPaymentAddress2 = SilentPaymentAddress.from("sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn");
|
||||||
|
List<SilentPayment> silentPayments = List.of(new SilentPayment(silentPaymentAddress0, "First", 0, false), new SilentPayment(silentPaymentAddress1, "Second", 0, false), new SilentPayment(silentPaymentAddress2, "Third", 0, false));
|
||||||
|
|
||||||
|
SilentPaymentUtils.updateSilentPayments(silentPayments, utxos);
|
||||||
|
Assertions.assertEquals(3, silentPayments.size());
|
||||||
|
Assertions.assertEquals("First", silentPayments.getFirst().getLabel());
|
||||||
|
Assertions.assertEquals("f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", Utils.bytesToHex(silentPayments.getFirst().getAddress().getData()));
|
||||||
|
Assertions.assertEquals("Second", silentPayments.get(1).getLabel());
|
||||||
|
Assertions.assertEquals("841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", Utils.bytesToHex(silentPayments.get(1).getAddress().getData()));
|
||||||
|
Assertions.assertEquals("Third", silentPayments.getLast().getLabel());
|
||||||
|
Assertions.assertEquals("2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", Utils.bytesToHex(silentPayments.getLast().getAddress().getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestKeystore extends Keystore {
|
||||||
|
private final Map<WalletNode, ECKey> privateKeys;
|
||||||
|
|
||||||
|
private TestKeystore(Map<WalletNode, ECKey> privateKeys) {
|
||||||
|
this.privateKeys = privateKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ECKey getKey(WalletNode walletNode) {
|
||||||
|
return privateKeys.get(walletNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPrivateKey() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue