add sp support for bip322 message signing

This commit is contained in:
Craig Raw 2026-05-18 14:55:15 +02:00
parent 698f8b08a1
commit dfd947cb69
2 changed files with 83 additions and 0 deletions

View File

@ -1,5 +1,6 @@
package com.sparrowwallet.drongo.crypto;
import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.policy.PolicyType;
@ -43,6 +44,46 @@ public class Bip322 {
return psbt;
}
public static PSBT getBip322PsbtSp(Address address, String message, byte[] silentPaymentsTweak, Map<ECKey, KeyDerivation> spendDerivations) {
if(silentPaymentsTweak == null) {
throw new IllegalArgumentException("Silent payments tweak is required");
}
PSBT psbt = getBip322Psbt(P2TR, address, message);
PSBTInput psbtInput = psbt.getPsbtInputs().getFirst();
psbtInput.setSilentPaymentsTweak(silentPaymentsTweak);
if(spendDerivations != null && !spendDerivations.isEmpty()) {
psbtInput.getSilentPaymentsSpendDerivations().putAll(spendDerivations);
}
return psbt;
}
public static String signMessageBip322Sp(Address address, String message, ECKey spendPrivKey, byte[] silentPaymentsTweak) {
PSBT psbt = getBip322PsbtSp(address, message, silentPaymentsTweak, Collections.emptyMap());
PSBTInput psbtInput = psbt.getPsbtInputs().getFirst();
if(!psbtInput.signSilentPayments(spendPrivKey)) {
throw new IllegalStateException("Failed to sign BIP322 PSBT with silent payments tweak");
}
return getBip322SignatureFromPsbtSp(psbt);
}
public static String getBip322SignatureFromPsbtSp(PSBT signedPsbt) {
PSBTInput psbtInput = signedPsbt.getPsbtInputs().getFirst();
TransactionSignature signature = psbtInput.getTapKeyPathSignature();
if(signature == null) {
throw new IllegalArgumentException("PSBT does not contain a taproot keypath signature");
}
Transaction finalizeTransaction = new Transaction();
TransactionWitness witness = new TransactionWitness(finalizeTransaction, signature);
finalizeTransaction.addInput(Sha256Hash.ZERO_HASH, 0, new Script(new byte[0]), witness);
return Base64.getEncoder().encodeToString(witness.toByteArray());
}
public static String getBip322SignatureFromPsbt(ScriptType scriptType, PSBT signedPsbt, ECKey pubKey) {
checkScriptType(scriptType);

View File

@ -167,6 +167,48 @@ public class Bip322Test {
Assertions.assertThrows(IllegalArgumentException.class, () -> Bip322.getBip322SignatureFromPsbt(ScriptType.P2WPKH, psbt, pubKey));
}
@Test
public void signMessageBip322Sp() throws SignatureException {
ECKey spendPrivKey = DumpedPrivateKey.fromBase58("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k").getKey();
byte[] tweak = Utils.hexToBytes("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
ECKey spendPubKey = ECKey.fromPublicOnly(spendPrivKey);
ECKey tweakPoint = ECKey.fromPublicOnly(ECKey.fromPrivate(tweak));
ECKey outputKey = spendPubKey.add(tweakPoint, true);
Address address = ScriptType.P2TR.getAddress(PolicyType.SINGLE_SP, outputKey);
String signature = Bip322.signMessageBip322Sp(address, "Hello World", spendPrivKey, tweak);
Assertions.assertTrue(Bip322.verifyMessageBip322(ScriptType.P2TR, address, "Hello World", signature));
//An SP signature for the same key but a different tweak must not verify against the first address
byte[] tweak2 = Utils.hexToBytes("fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210");
ECKey tweakPoint2 = ECKey.fromPublicOnly(ECKey.fromPrivate(tweak2));
ECKey outputKey2 = spendPubKey.add(tweakPoint2, true);
Address address2 = ScriptType.P2TR.getAddress(PolicyType.SINGLE_SP, outputKey2);
String signature2 = Bip322.signMessageBip322Sp(address2, "Hello World", spendPrivKey, tweak2);
Assertions.assertTrue(Bip322.verifyMessageBip322(ScriptType.P2TR, address2, "Hello World", signature2));
Assertions.assertFalse(Bip322.verifyMessageBip322(ScriptType.P2TR, address, "Hello World", signature2));
}
@Test
public void getBip322PsbtSp() {
ECKey spendPrivKey = DumpedPrivateKey.fromBase58("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k").getKey();
byte[] tweak = Utils.hexToBytes("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
ECKey spendPubKey = ECKey.fromPublicOnly(spendPrivKey);
ECKey tweakPoint = ECKey.fromPublicOnly(ECKey.fromPrivate(tweak));
ECKey outputKey = spendPubKey.add(tweakPoint, true);
Address address = ScriptType.P2TR.getAddress(PolicyType.SINGLE_SP, outputKey);
PSBT psbt = Bip322.getBip322PsbtSp(address, "Hello World", tweak, java.util.Collections.emptyMap());
Assertions.assertEquals(1, psbt.getPsbtInputs().size());
PSBTInput psbtInput = psbt.getPsbtInputs().get(0);
Assertions.assertNotNull(psbtInput.getWitnessUtxo());
Assertions.assertArrayEquals(tweak, psbtInput.getSilentPaymentsTweak());
Assertions.assertNull(psbtInput.getTapInternalKey());
Assertions.assertTrue(psbtInput.getTapDerivedPublicKeys().isEmpty());
}
@Test
public void verifyMessageBip322Multisig() throws SignatureException, InvalidAddressException {
Address address = Address.fromString("bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3");