mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +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.ECPrivateKeyParameters;
|
||||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||||
import org.bouncycastle.math.ec.ECAlgorithms;
|
import org.bouncycastle.math.ec.*;
|
||||||
import org.bouncycastle.math.ec.ECPoint;
|
|
||||||
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
|
|
||||||
import org.bouncycastle.math.ec.FixedPointUtil;
|
|
||||||
import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
|
import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
|
||||||
import org.bouncycastle.util.Properties;
|
import org.bouncycastle.util.Properties;
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
@ -30,6 +27,7 @@ import org.slf4j.LoggerFactory;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.SignatureException;
|
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.
|
* 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 {
|
public class Miniscript {
|
||||||
private static final Pattern SINGLE_PATTERN = Pattern.compile("pkh?\\(");
|
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 static final Pattern MULTI_PATTERN = Pattern.compile("multi\\(([\\d+])");
|
||||||
|
|
||||||
private String script;
|
private String script;
|
||||||
|
@ -27,6 +28,11 @@ public class Miniscript {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Matcher taprootMatcher = TAPROOT_PATTERN.matcher(script);
|
||||||
|
if(taprootMatcher.find()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
Matcher multiMatcher = MULTI_PATTERN.matcher(script);
|
Matcher multiMatcher = MULTI_PATTERN.matcher(script);
|
||||||
if(multiMatcher.find()) {
|
if(multiMatcher.find()) {
|
||||||
String threshold = multiMatcher.group(1);
|
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) {
|
for(ScriptType scriptType : SINGLE_KEY_TYPES) {
|
||||||
if(scriptType.isScriptType(this)) {
|
if(scriptType.isScriptType(this)) {
|
||||||
return new Address[] { scriptType.getAddress(scriptType.getPublicKeyFromScript(this)) };
|
return new Address[] { scriptType.getAddress(scriptType.getPublicKeyFromScript(this)) };
|
||||||
|
|
|
@ -975,15 +975,15 @@ public enum ScriptType {
|
||||||
return List.of(MULTI, CUSTOM);
|
return List.of(MULTI, CUSTOM);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
P2TR("P2TR", "Taproot (P2TR)", "m/6789'/0'/0'") {
|
P2TR("P2TR", "Taproot (P2TR)", "m/86'/0'/0'") {
|
||||||
@Override
|
@Override
|
||||||
public Address getAddress(byte[] pubKey) {
|
public Address getAddress(byte[] pubKey) {
|
||||||
return new P2TRAddress(pubKey);
|
return new P2TRAddress(pubKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Address getAddress(ECKey key) {
|
public Address getAddress(ECKey derivedKey) {
|
||||||
return getAddress(key.getPubKeyXCoord());
|
return getAddress(derivedKey.getTweakedOutputKey().getPubKeyXCoord());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1001,8 +1001,8 @@ public enum ScriptType {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Script getOutputScript(ECKey key) {
|
public Script getOutputScript(ECKey derivedKey) {
|
||||||
return getOutputScript(key.getPubKeyXCoord());
|
return getOutputScript(derivedKey.getTweakedOutputKey().getPubKeyXCoord());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1011,8 +1011,8 @@ public enum ScriptType {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getOutputDescriptor(ECKey key) {
|
public String getOutputDescriptor(ECKey derivedKey) {
|
||||||
return getDescriptor() + Utils.bytesToHex(key.getPubKeyXCoord()) + getCloseDescriptor();
|
return getDescriptor() + Utils.bytesToHex(derivedKey.getPubKeyXCoord()) + getCloseDescriptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1052,7 +1052,15 @@ public enum ScriptType {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature) {
|
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
|
@Override
|
||||||
|
@ -1072,7 +1080,7 @@ public enum ScriptType {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PolicyType> getAllowedPolicyTypes() {
|
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