Skip to content

Commit

Permalink
Merge pull request rsksmart#2728 from rsksmart/remove-signatures
Browse files Browse the repository at this point in the history
Create a utility method to remove signatures from a bitcoin transaction
  • Loading branch information
josedahlquist authored Sep 26, 2024
2 parents c19fe27 + 16d8a99 commit 2c00b2f
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 40 deletions.
42 changes: 20 additions & 22 deletions rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public class BridgeSupport {
private static final PanicProcessor panicProcessor = new PanicProcessor();

private final BridgeConstants bridgeConstants;
private final NetworkParameters networkParameters;
private final BridgeStorageProvider provider;
private final Repository rskRepository;
private final BridgeEventLogger eventLogger;
Expand Down Expand Up @@ -144,6 +145,7 @@ public BridgeSupport(
this.provider = provider;
this.rskExecutionBlock = executionBlock;
this.bridgeConstants = bridgeConstants;
this.networkParameters = bridgeConstants.getBtcParams();
this.eventLogger = eventLogger;
this.btcLockSenderProvider = btcLockSenderProvider;
this.peginInstructionsProvider = peginInstructionsProvider;
Expand All @@ -163,12 +165,12 @@ public List<ProgramSubtrace> getSubtraces() {

@VisibleForTesting
InputStream getCheckPoints() {
String resourceName = "/rskbitcoincheckpoints/" + bridgeConstants.getBtcParams().getId() + ".checkpoints";
String resourceName = "/rskbitcoincheckpoints/" + networkParameters.getId() + ".checkpoints";
InputStream checkpoints = BridgeSupport.class.getResourceAsStream(resourceName);
logger.debug("[getCheckPoints] Looking for checkpoint {}. Found? {}", resourceName, checkpoints != null);
if (checkpoints == null) {
// If we don't have a custom checkpoints file, try to use bitcoinj's default checkpoints for that network
checkpoints = BridgeSupport.class.getResourceAsStream("/" + bridgeConstants.getBtcParams().getId() + ".checkpoints");
checkpoints = BridgeSupport.class.getResourceAsStream("/" + networkParameters.getId() + ".checkpoints");
}
return checkpoints;
}
Expand Down Expand Up @@ -374,7 +376,7 @@ public void registerBtcTransaction(
throw new RegisterBtcTransactionException("Could not validate transaction");
}

BtcTransaction btcTx = new BtcTransaction(bridgeConstants.getBtcParams(), btcTxSerialized);
BtcTransaction btcTx = new BtcTransaction(networkParameters, btcTxSerialized);
btcTx.verify();
logger.debug("[registerBtcTransaction][rsk tx {}] Btc tx hash without witness {}", rskTxHash, btcTx.getHash(false));

Expand Down Expand Up @@ -832,8 +834,7 @@ public void releaseBtc(Transaction rskTx) throws IOException {
}

Context.propagate(btcContext);
NetworkParameters btcParams = bridgeConstants.getBtcParams();
Address btcDestinationAddress = BridgeUtils.recoverBtcAddressFromEthTransaction(rskTx, btcParams);
Address btcDestinationAddress = BridgeUtils.recoverBtcAddressFromEthTransaction(rskTx, networkParameters);
logger.debug("[releaseBtc] BTC destination address: {}", btcDestinationAddress);

requestRelease(btcDestinationAddress, pegoutValueInSatoshis, rskTx);
Expand Down Expand Up @@ -2146,7 +2147,7 @@ public void registerBtcCoinbaseTransaction(byte[] btcTxSerialized, Sha256Hash bl
Sha256Hash merkleRoot;

try {
PartialMerkleTree pmt = new PartialMerkleTree(bridgeConstants.getBtcParams(), pmtSerialized, 0);
PartialMerkleTree pmt = new PartialMerkleTree(networkParameters, pmtSerialized, 0);
List<Sha256Hash> hashesInPmt = new ArrayList<>();
merkleRoot = pmt.getTxnHashAndMerkleRoot(hashesInPmt);
if (!hashesInPmt.contains(btcTxHash)) {
Expand Down Expand Up @@ -2178,7 +2179,7 @@ public void registerBtcCoinbaseTransaction(byte[] btcTxSerialized, Sha256Hash bl
return;
}

BtcTransaction btcTx = new BtcTransaction(bridgeConstants.getBtcParams(), btcTxSerialized);
BtcTransaction btcTx = new BtcTransaction(networkParameters, btcTxSerialized);
btcTx.verify();

Sha256Hash witnessCommitment = Sha256Hash.twiceOf(witnessMerkleRoot.getReversedBytes(), witnessReservedValue);
Expand Down Expand Up @@ -2294,7 +2295,7 @@ public BigInteger registerFlyoverBtcTransaction(
return BigInteger.valueOf(FlyoverTxResponseCodes.UNPROCESSABLE_TX_VALIDATIONS_ERROR.value());
}

BtcTransaction btcTx = new BtcTransaction(bridgeConstants.getBtcParams(), btcTxSerialized);
BtcTransaction btcTx = new BtcTransaction(networkParameters, btcTxSerialized);
btcTx.verify();

Sha256Hash btcTxHashWithoutWitness = btcTx.getHash(false);
Expand All @@ -2312,7 +2313,7 @@ public BigInteger registerFlyoverBtcTransaction(
}

FlyoverFederationInformation flyoverActiveFederationInformation = createFlyoverFederationInformation(flyoverDerivationHash);
Address flyoverActiveFederationAddress = flyoverActiveFederationInformation.getFlyoverFederationAddress(bridgeConstants.getBtcParams());
Address flyoverActiveFederationAddress = flyoverActiveFederationInformation.getFlyoverFederationAddress(networkParameters);
Federation retiringFederation = getRetiringFederation();
Optional<FlyoverFederationInformation> flyoverRetiringFederationInformation = Optional.empty();

Expand All @@ -2322,7 +2323,7 @@ public BigInteger registerFlyoverBtcTransaction(
if (activations.isActive(RSKIP293) && retiringFederation != null) {
flyoverRetiringFederationInformation = Optional.of(createFlyoverFederationInformation(flyoverDerivationHash, retiringFederation));
Address flyoverRetiringFederationAddress = flyoverRetiringFederationInformation.get().getFlyoverFederationAddress(
bridgeConstants.getBtcParams()
networkParameters
);
addresses.add(flyoverRetiringFederationAddress);
logger.debug("[registerFlyoverBtcTransaction] flyover retiring federation address: {}", flyoverRetiringFederationAddress);
Expand All @@ -2343,7 +2344,7 @@ public BigInteger registerFlyoverBtcTransaction(

Coin totalAmount = BridgeUtils.getAmountSentToAddresses(
activations,
bridgeConstants.getBtcParams(),
networkParameters,
btcContext,
btcTx,
addresses
Expand Down Expand Up @@ -2375,7 +2376,7 @@ public BigInteger registerFlyoverBtcTransaction(

List<UTXO> utxosForFlyoverActiveFed = BridgeUtils.getUTXOsSentToAddresses(
activations,
bridgeConstants.getBtcParams(),
networkParameters,
btcContext,
btcTx,
Collections.singletonList(flyoverActiveFederationAddress)
Expand All @@ -2395,11 +2396,11 @@ public BigInteger registerFlyoverBtcTransaction(
if (activations.isActive(RSKIP293) && flyoverRetiringFederationInformation.isPresent()) {
List<UTXO> utxosForRetiringFed = BridgeUtils.getUTXOsSentToAddresses(
activations,
bridgeConstants.getBtcParams(),
networkParameters,
btcContext,
btcTx,
Collections.singletonList(
flyoverRetiringFederationInformation.get().getFlyoverFederationAddress(bridgeConstants.getBtcParams())
flyoverRetiringFederationInformation.get().getFlyoverFederationAddress(networkParameters)
)
);

Expand Down Expand Up @@ -2449,7 +2450,7 @@ private WalletProvider createFlyoverWalletProvider(
return (BtcTransaction btcTx, List<Address> addresses) -> {
List<UTXO> utxosList = BridgeUtils.getUTXOsSentToAddresses(
activations,
bridgeConstants.getBtcParams(),
networkParameters,
btcContext,
btcTx,
addresses
Expand Down Expand Up @@ -2561,10 +2562,10 @@ private StoredBlock getBtcBlockchainChainHead() throws IOException, BlockStoreEx
private StoredBlock getLowestBlock() throws IOException {
InputStream checkpoints = this.getCheckPoints();
if (checkpoints == null) {
BtcBlock genesis = bridgeConstants.getBtcParams().getGenesisBlock();
BtcBlock genesis = networkParameters.getGenesisBlock();
return new StoredBlock(genesis, genesis.getWork(), 0);
}
CheckpointManager manager = new CheckpointManager(bridgeConstants.getBtcParams(), checkpoints);
CheckpointManager manager = new CheckpointManager(networkParameters, checkpoints);
long time = getActiveFederation().getCreationTime().toEpochMilli();
// Go back 1 week to match CheckpointManager.checkpoint() behaviour
time -= 86400 * 7;
Expand Down Expand Up @@ -2635,14 +2636,12 @@ private void ensureBtcBlockStore() throws IOException, BlockStoreException {
provider,
activations
);
NetworkParameters btcParams = this.bridgeConstants.getBtcParams();

if (this.btcBlockStore.getChainHead().getHeader().getHash().equals(btcParams.getGenesisBlock().getHash())) {
if (this.btcBlockStore.getChainHead().getHeader().getHash().equals(networkParameters.getGenesisBlock().getHash())) {
// We are building the blockstore for the first time, so we have not set the checkpoints yet.
long time = federationSupport.getActiveFederation().getCreationTime().toEpochMilli();
InputStream checkpoints = this.getCheckPoints();
if (time > 0 && checkpoints != null) {
CheckpointManager.checkpoint(btcParams, checkpoints, this.btcBlockStore, time);
CheckpointManager.checkpoint(networkParameters, checkpoints, this.btcBlockStore, time);
}
}
}
Expand Down Expand Up @@ -2809,7 +2808,6 @@ protected boolean validationsForRegisterBtcTransaction(Sha256Hash btcTxHash, int
// Calculates merkleRoot
Sha256Hash merkleRoot;
try {
NetworkParameters networkParameters = bridgeConstants.getBtcParams();
merkleRoot = BridgeUtils.calculateMerkleRoot(networkParameters, pmtSerialized, btcTxHash);
if (merkleRoot == null) {
return false;
Expand Down
29 changes: 26 additions & 3 deletions rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import co.rsk.bitcoinj.core.Sha256Hash;
import co.rsk.bitcoinj.core.TransactionInput;
import co.rsk.bitcoinj.script.Script;
import co.rsk.bitcoinj.script.ScriptBuilder;
import co.rsk.bitcoinj.script.ScriptChunk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -41,13 +42,13 @@ public static Optional<Script> extractRedeemScriptFromInput(TransactionInput txI
return Optional.empty();
}

byte[] program = chunks.get(chunks.size() - 1).data;
if (program == null) {
byte[] redeemScriptProgram = chunks.get(chunks.size() - 1).data;
if (redeemScriptProgram == null) {
return Optional.empty();
}

try {
Script redeemScript = new Script(program);
Script redeemScript = new Script(redeemScriptProgram);
return Optional.of(redeemScript);
} catch (ScriptException e) {
logger.debug(
Expand All @@ -58,4 +59,26 @@ public static Optional<Script> extractRedeemScriptFromInput(TransactionInput txI
return Optional.empty();
}
}

public static void removeSignaturesFromTransactionWithP2shMultiSigInputs(BtcTransaction transaction) {
if (transaction.hasWitness()) {
String message = "Removing signatures from SegWit transactions is not allowed.";
logger.error("[removeSignaturesFromTransactionWithP2shMultiSigInputs] {}", message);
throw new IllegalArgumentException(message);
}

for (TransactionInput input : transaction.getInputs()) {
Script inputRedeemScript = extractRedeemScriptFromInput(input)
.orElseThrow(
() -> {
String message = "Cannot remove signatures from transaction inputs that do not have p2sh multisig input script.";
logger.error("[removeSignaturesFromTransactionWithP2shMultiSigInputs] {}", message);
return new IllegalArgumentException(message);
}
);
Script p2shScript = ScriptBuilder.createP2SHOutputScript(inputRedeemScript);
Script emptyInputScript = p2shScript.createEmptyInputScript(null, inputRedeemScript);
input.setScriptSig(emptyInputScript);
}
}
}
32 changes: 31 additions & 1 deletion rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
import org.bouncycastle.util.encoders.Hex;
import org.ethereum.crypto.HashUtil;

import static co.rsk.bitcoinj.script.ScriptBuilder.createP2SHOutputScript;
import static co.rsk.peg.bitcoin.BitcoinUtils.extractRedeemScriptFromInput;

public class BitcoinTestUtils {

public static List<BtcECKey> getBtcEcKeysFromSeeds(String[] seeds, boolean sorted) {
Expand All @@ -37,7 +40,7 @@ public static Address createP2PKHAddress(NetworkParameters networkParameters, St

public static Address createP2SHMultisigAddress(NetworkParameters networkParameters, List<BtcECKey> keys) {
Script redeemScript = ScriptBuilder.createRedeemScript((keys.size() / 2) + 1, keys);
Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript);
Script outputScript = createP2SHOutputScript(redeemScript);

return Address.fromP2SHScript(networkParameters, outputScript);
}
Expand Down Expand Up @@ -125,4 +128,31 @@ public static byte[] flatKeysAsByteArray(List<BtcECKey> keys) {

return flatPubKeys;
}

public static void signTransactionInputFromP2shMultiSig(BtcTransaction transaction, int inputIndex, List<BtcECKey> keys) {
if (transaction.getWitness(inputIndex).getPushCount() == 0) {
signLegacyTransactionInputFromP2shMultiSig(transaction, inputIndex, keys);
}
}

private static void signLegacyTransactionInputFromP2shMultiSig(BtcTransaction transaction, int inputIndex, List<BtcECKey> keys) {
TransactionInput input = transaction.getInput(inputIndex);

Script inputRedeemScript = extractRedeemScriptFromInput(input)
.orElseThrow(() -> new IllegalArgumentException("Cannot sign inputs that are not from a p2sh multisig"));

Script outputScript = createP2SHOutputScript(inputRedeemScript);
Sha256Hash sigHash = transaction.hashForSignature(inputIndex, inputRedeemScript, BtcTransaction.SigHash.ALL, false);
Script inputScriptSig = input.getScriptSig();

for (BtcECKey key : keys) {
BtcECKey.ECDSASignature sig = key.sign(sigHash);
TransactionSignature txSig = new TransactionSignature(sig, BtcTransaction.SigHash.ALL, false);
byte[] txSigEncoded = txSig.encodeToBitcoin();

int keyIndex = inputScriptSig.getSigInsertionIndex(sigHash, key);
inputScriptSig = outputScript.getScriptSigWithSignature(inputScriptSig, txSigEncoded, keyIndex);
input.setScriptSig(inputScriptSig);
}
}
}
Loading

0 comments on commit 2c00b2f

Please sign in to comment.