mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-25 17:46:44 +00:00
support p2tr script type wallet creation
This commit is contained in:
parent
5013a0ef2f
commit
e53574ea54
4 changed files with 64 additions and 13 deletions
|
@ -17,10 +17,7 @@ import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
|
|||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.math.ec.ECAlgorithms;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
|
||||
import org.bouncycastle.math.ec.FixedPointUtil;
|
||||
import org.bouncycastle.math.ec.*;
|
||||
import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
|
||||
import org.bouncycastle.util.Properties;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
@ -30,6 +27,7 @@ import org.slf4j.LoggerFactory;
|
|||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SignatureException;
|
||||
|
@ -628,6 +626,40 @@ public class ECKey implements EncryptableItem {
|
|||
}
|
||||
}
|
||||
|
||||
public ECKey getTweakedOutputKey() {
|
||||
ECPoint internalKey = liftX(getPubKeyXCoord());
|
||||
byte[] taggedHash = taggedHash("TapTweak", internalKey.getXCoord().getEncoded());
|
||||
ECPoint outputKey = internalKey.add(ECKey.fromPrivate(taggedHash).getPubKeyPoint());
|
||||
return ECKey.fromPublicOnly(outputKey, true);
|
||||
}
|
||||
|
||||
private static ECPoint liftX(byte[] bytes) {
|
||||
SecP256K1Curve secP256K1Curve = (SecP256K1Curve)CURVE_PARAMS.getCurve();
|
||||
BigInteger x = new BigInteger(1, bytes);
|
||||
BigInteger p = secP256K1Curve.getQ();
|
||||
if(x.compareTo(p) > -1) {
|
||||
throw new IllegalArgumentException("Provided bytes must be less than secp256k1 field size");
|
||||
}
|
||||
|
||||
BigInteger y_sq = x.modPow(BigInteger.valueOf(3), p).add(BigInteger.valueOf(7)).mod(p);
|
||||
BigInteger y = y_sq.modPow(p.add(BigInteger.valueOf(1)).divide(BigInteger.valueOf(4)), p);
|
||||
if(!y.modPow(BigInteger.valueOf(2), p).equals(y_sq)) {
|
||||
throw new IllegalStateException("Calculated invalid y_sq when solving for y co-ordinate");
|
||||
}
|
||||
|
||||
return secP256K1Curve.createPoint(x, y.and(BigInteger.ONE).equals(BigInteger.ZERO) ? y : p.subtract(y));
|
||||
}
|
||||
|
||||
private static byte[] taggedHash(String tag, byte[] msg) {
|
||||
byte[] hash = Sha256Hash.hash(tag.getBytes(StandardCharsets.UTF_8));
|
||||
ByteBuffer buffer = ByteBuffer.allocate(hash.length + hash.length + msg.length);
|
||||
buffer.put(hash);
|
||||
buffer.put(hash);
|
||||
buffer.put(msg);
|
||||
|
||||
return Sha256Hash.hash(buffer.array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given pubkey is canonical, i.e. the correct length taking into account compression.
|
||||
*/
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.util.regex.Pattern;
|
|||
|
||||
public class Miniscript {
|
||||
private static final Pattern SINGLE_PATTERN = Pattern.compile("pkh?\\(");
|
||||
private static final Pattern TAPROOT_PATTERN = Pattern.compile("tr\\(");
|
||||
private static final Pattern MULTI_PATTERN = Pattern.compile("multi\\(([\\d+])");
|
||||
|
||||
private String script;
|
||||
|
@ -27,6 +28,11 @@ public class Miniscript {
|
|||
return 1;
|
||||
}
|
||||
|
||||
Matcher taprootMatcher = TAPROOT_PATTERN.matcher(script);
|
||||
if(taprootMatcher.find()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
Matcher multiMatcher = MULTI_PATTERN.matcher(script);
|
||||
if(multiMatcher.find()) {
|
||||
String threshold = multiMatcher.group(1);
|
||||
|
|
|
@ -175,6 +175,11 @@ public class Script {
|
|||
}
|
||||
}
|
||||
|
||||
//Special handling for taproot tweaked keys - we don't want to tweak them again
|
||||
if(P2TR.isScriptType(this)) {
|
||||
return new Address[] { new P2TRAddress(P2TR.getPublicKeyFromScript(this).getPubKeyXCoord()) };
|
||||
}
|
||||
|
||||
for(ScriptType scriptType : SINGLE_KEY_TYPES) {
|
||||
if(scriptType.isScriptType(this)) {
|
||||
return new Address[] { scriptType.getAddress(scriptType.getPublicKeyFromScript(this)) };
|
||||
|
|
|
@ -975,15 +975,15 @@ public enum ScriptType {
|
|||
return List.of(MULTI, CUSTOM);
|
||||
}
|
||||
},
|
||||
P2TR("P2TR", "Taproot (P2TR)", "m/6789'/0'/0'") {
|
||||
P2TR("P2TR", "Taproot (P2TR)", "m/86'/0'/0'") {
|
||||
@Override
|
||||
public Address getAddress(byte[] pubKey) {
|
||||
return new P2TRAddress(pubKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress(ECKey key) {
|
||||
return getAddress(key.getPubKeyXCoord());
|
||||
public Address getAddress(ECKey derivedKey) {
|
||||
return getAddress(derivedKey.getTweakedOutputKey().getPubKeyXCoord());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1001,8 +1001,8 @@ public enum ScriptType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Script getOutputScript(ECKey key) {
|
||||
return getOutputScript(key.getPubKeyXCoord());
|
||||
public Script getOutputScript(ECKey derivedKey) {
|
||||
return getOutputScript(derivedKey.getTweakedOutputKey().getPubKeyXCoord());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1011,8 +1011,8 @@ public enum ScriptType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getOutputDescriptor(ECKey key) {
|
||||
return getDescriptor() + Utils.bytesToHex(key.getPubKeyXCoord()) + getCloseDescriptor();
|
||||
public String getOutputDescriptor(ECKey derivedKey) {
|
||||
return getDescriptor() + Utils.bytesToHex(derivedKey.getPubKeyXCoord()) + getCloseDescriptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1052,7 +1052,15 @@ public enum ScriptType {
|
|||
|
||||
@Override
|
||||
public Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature) {
|
||||
throw new UnsupportedOperationException("Constructing Taproot inputs is not yet supported");
|
||||
if(!isScriptType(scriptPubKey)) {
|
||||
throw new ProtocolException("Provided scriptPubKey is not a " + getName() + " script");
|
||||
}
|
||||
|
||||
if(!scriptPubKey.equals(getOutputScript(pubKey))) {
|
||||
throw new ProtocolException("Provided P2TR scriptPubKey does not match constructed pubkey script");
|
||||
}
|
||||
|
||||
return new Script(new byte[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1072,7 +1080,7 @@ public enum ScriptType {
|
|||
|
||||
@Override
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return Collections.emptyList();
|
||||
return Network.get() == Network.REGTEST || Network.get() == Network.SIGNET ? List.of(SINGLE) : Collections.emptyList();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue