support p2tr script type wallet creation

This commit is contained in:
Craig Raw 2021-07-09 13:33:35 +02:00
parent 5013a0ef2f
commit e53574ea54
4 changed files with 64 additions and 13 deletions

View file

@ -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.
*/

View file

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

View file

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

View file

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