add support for sending silent payments

This commit is contained in:
Craig Raw 2025-09-11 16:33:57 +02:00
parent 0b3b1a5c3f
commit 6c7662ca09
7 changed files with 636 additions and 9 deletions

View file

@ -328,14 +328,43 @@ public class ECKey {
/** Multiply the public point by the provided private key */
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);
return ECKey.fromPublicOnly(point, false);
return ECKey.fromPublicOnly(point, compressed);
}
/** Add to the public point by the provided public key */
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());
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 */

View file

@ -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;
}
}

View file

@ -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 {
Wallet spWallet = new Wallet();
spWallet.setPolicyType(PolicyType.SINGLE);

View file

@ -3,16 +3,21 @@ package com.sparrowwallet.drongo.silentpayments;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.crypto.ECKey;
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.NativeSecp256k1Util;
import org.bitcoin.Secp256k1Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
import static com.sparrowwallet.drongo.protocol.ScriptType.P2TR;
public class SilentPaymentUtils {
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
};
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) {
if(!containsTaprootOutput(tx)) {
return false;
@ -169,7 +178,7 @@ public class SilentPaymentUtils {
byte[] combinedPubKey = NativeSecp256k1.pubKeyCombine(inputPubKeys, true);
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);
} catch(NativeSecp256k1Util.AssertFailException e) {
log.error("Error computing tweak", e);
@ -177,4 +186,93 @@ public class SilentPaymentUtils {
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);
}
}

View file

@ -13,6 +13,8 @@ import com.sparrowwallet.drongo.protocol.*;
import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.psbt.PSBTInput;
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.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;
}
public boolean canSendSilentPayments() {
return getKeystores().size() == 1 && getKeystores().getFirst().hasPrivateKey() && policyType == PolicyType.SINGLE
&& SilentPayment.VALID_INPUT_SCRIPT_TYPES.contains(scriptType);
}
public StandardAccount getStandardAccountType() {
int accountIndex = getAccountIndex();
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);
}
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
for(Payment payment : txPayments) {
transaction.addOutput(payment.getAmount(), payment.getAddress());

View file

@ -37,6 +37,18 @@ public class SilentPaymentScanAddressTest {
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
public void tearDown() {
Network.set(null);

View file

@ -2,12 +2,17 @@ package com.sparrowwallet.drongo.silentpayments;
import com.sparrowwallet.drongo.Utils;
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.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.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
public class SilentPaymentUtilsTest {
@Test
@ -59,4 +64,437 @@ public class SilentPaymentUtilsTest {
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;
}
}
}