Skip to content

Commit

Permalink
Merge pull request #2079 from rsksmart/spendfromerp-tests
Browse files Browse the repository at this point in the history
Move the tests that spend from an ERP federation to it's own utils class
  • Loading branch information
Vovchyk authored Jul 26, 2023
2 parents 07a9a52 + 3ab4060 commit 1de825f
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 203 deletions.
2 changes: 1 addition & 1 deletion rskj-core/src/main/java/co/rsk/peg/P2shErpFederation.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public final Script getRedeemScript() {
public final Script getStandardRedeemScript() {
if (standardRedeemScript == null) {
standardRedeemScript = P2shErpFederationRedeemScriptParser.extractStandardRedeemScript(
getRedeemScript().getChunks()
getRedeemScript().getChunks()
);
}
return standardRedeemScript;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import co.rsk.crypto.Keccak256;
import co.rsk.db.MutableTrieCache;
import co.rsk.db.MutableTrieImpl;
import co.rsk.peg.bitcoin.BitcoinTestUtils;
import co.rsk.peg.utils.BridgeEventLogger;
import co.rsk.peg.utils.BridgeEventLoggerImpl;
import co.rsk.peg.utils.RejectedPegoutReason;
Expand Down Expand Up @@ -965,7 +966,7 @@ void test_processPegoutsIndividually_before_rskip_271_no_funds_to_process_any_re
for (int i = 0; i < entriesSizeAboveMaxIterations; i++) {
entries.add(
new ReleaseRequestQueue.Entry(
PegTestUtils.createP2PKHBtcAddress(bridgeConstants.getBtcParams(), i+2),
BitcoinTestUtils.createP2PKHAddress(bridgeConstants.getBtcParams(), String.valueOf(i)),
Coin.COIN.multiply(5)
)
);
Expand Down
188 changes: 50 additions & 138 deletions rskj-core/src/test/java/co/rsk/peg/ErpFederationTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package co.rsk.peg;

import static co.rsk.peg.ReleaseTransactionBuilder.BTC_TX_VERSION_2;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
Expand All @@ -12,17 +11,15 @@
import co.rsk.bitcoinj.core.Coin;
import co.rsk.bitcoinj.core.NetworkParameters;
import co.rsk.bitcoinj.core.ScriptException;
import co.rsk.bitcoinj.core.Sha256Hash;
import co.rsk.bitcoinj.core.Utils;
import co.rsk.bitcoinj.core.VerificationException;
import co.rsk.bitcoinj.crypto.TransactionSignature;
import co.rsk.bitcoinj.script.ErpFederationRedeemScriptParser;
import co.rsk.bitcoinj.script.Script;
import co.rsk.bitcoinj.script.ScriptBuilder;
import co.rsk.bitcoinj.script.ScriptOpCodes;
import co.rsk.config.BridgeConstants;
import co.rsk.config.BridgeMainNetConstants;
import co.rsk.config.BridgeTestNetConstants;
import co.rsk.peg.bitcoin.BitcoinTestUtils;
import co.rsk.peg.resources.TestConstants;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
Expand All @@ -34,12 +31,13 @@
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.bouncycastle.util.encoders.Hex;
import org.ethereum.config.blockchain.upgrades.ActivationConfig;
import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest;
import org.ethereum.config.blockchain.upgrades.ConsensusRule;
import org.ethereum.crypto.ECKey;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
Expand Down Expand Up @@ -84,7 +82,6 @@ void getRedeemScript_before_RSKIP293() {
activationDelayValue,
false
);

}

@Test
Expand Down Expand Up @@ -704,113 +701,6 @@ void spendFromErpFed_after_RSKIP293_mainnet_using_standard_multisig() {
));
}

private void spendFromErpFed(
NetworkParameters networkParameters,
long activationDelay,
boolean isRskip293Active,
boolean signWithEmergencyMultisig) {

// Created with GenNodeKeyId using seed 'fed1'
byte[] publicKeyBytes = Hex.decode("043267e382e076cbaa199d49ea7362535f95b135de181caf66b391f541bf39ab0e75b8577faac2183782cb0d76820cf9f356831d216e99d886f8a6bc47fe696939");
BtcECKey btcKey = BtcECKey.fromPublicOnly(publicKeyBytes);
ECKey rskKey = ECKey.fromPublicOnly(publicKeyBytes);
FederationMember fed1 = new FederationMember(btcKey, rskKey, rskKey);
BtcECKey fed1PrivKey = BtcECKey.fromPrivate(Hex.decode("529822842595a3a6b3b3e51e9cffa0db66452599f7beec542382a02b1e42be4b"));

// Created with GenNodeKeyId using seed 'fed3', used for fed2 to keep keys sorted
publicKeyBytes = Hex.decode("0443e106d90183e2eef7d5cb7538a634439bf1301d731787c6736922ff19e750ed39e74a76731fed620aeedbcd77e4de403fc4148efd3b5dbfc6cef550aa63c377");
btcKey = BtcECKey.fromPublicOnly(publicKeyBytes);
rskKey = ECKey.fromPublicOnly(publicKeyBytes);
FederationMember fed2 = new FederationMember(btcKey, rskKey, rskKey);
BtcECKey fed2PrivKey = BtcECKey.fromPrivate(Hex.decode("b2889610e66cd3f7de37c81c20c786b576349b80b3f844f8409e3a29d95c0c7c"));

// Created with GenNodeKeyId using seed 'fed2', used for fed3 to keep keys sorted
publicKeyBytes = Hex.decode("04bd5b51b1c5d799da190285c8078a2712b8e5dc6f73c799751e6256bb89a4bd04c6444b00289fc76ee853fcfa52b3083d66c42e84f8640f53a4cdf575e4d4a399");
btcKey = BtcECKey.fromPublicOnly(publicKeyBytes);
rskKey = ECKey.fromPublicOnly(publicKeyBytes);
FederationMember fed3 = new FederationMember(btcKey, rskKey, rskKey);
BtcECKey fed3PrivKey = BtcECKey.fromPrivate(Hex.decode("fa013890aa14dd269a0ca16003cabde1688021358b662d17b1e8c555f5cccc6e"));

// Created with GenNodeKeyId using seed 'erp1'
publicKeyBytes = Hex.decode("048f5a88b08d75765b36951254e68060759de5be7e559972c37c67fc8cedafeb2643a4a8a618125530e275fe310c72dbdd55fa662cdcf8e134012f8a8d4b7e8400");
BtcECKey erp1Key = BtcECKey.fromPublicOnly(publicKeyBytes);
BtcECKey erp1PrivKey = BtcECKey.fromPrivate(Hex.decode("1f28656deb5f108f8cdf14af34ac4ff7a5643a7ac3f77b8de826b9ad9775f0ca"));

// Created with GenNodeKeyId using seed 'erp2'
publicKeyBytes = Hex.decode("04deba35a96add157b6de58f48bb6e23bcb0a17037bed1beb8ba98de6b0a0d71d60f3ce246954b78243b41337cf8f93b38563c3bcd6a5329f1d68c057d0e5146e8");
BtcECKey erp2Key = BtcECKey.fromPublicOnly(publicKeyBytes);
BtcECKey erp2PrivKey = BtcECKey.fromPrivate(Hex.decode("4e58ebe9cd04ffea5ab81dd2aded3ab8a63e44f3b47aef334e369d895c351646"));

// Created with GenNodeKeyId using seed 'erp3'
publicKeyBytes = Hex.decode("04c34fcd05cef2733ea7337c37f50ae26245646aba124948c6ff8dcdf82128499808fc9148dfbc0e0ab510b4f4a78bf7a58f8b6574e03dae002533c5059973b61f");
BtcECKey erp3Key = BtcECKey.fromPublicOnly(publicKeyBytes);
BtcECKey erp3PrivKey = BtcECKey.fromPrivate(Hex.decode("57e8d2cd51c3b076ca96a1043c8c6d32c6c18447e411a6279cda29d70650977b"));

ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class);
when(activations.isActive(ConsensusRule.RSKIP284)).thenReturn(true);
when(activations.isActive(ConsensusRule.RSKIP293)).thenReturn(isRskip293Active);
ErpFederation erpFed = new ErpFederation(
Arrays.asList(fed1, fed2, fed3),
ZonedDateTime.parse("2017-06-10T02:30:00Z").toInstant(),
0L,
networkParameters,
Arrays.asList(erp1Key, erp2Key, erp3Key),
activationDelay,
activations
);

// Below code can be used to create a transaction spending from the emergency multisig in testnet or mainnet
// String RAW_FUND_TX = "";
// BtcTransaction pegInTx = new BtcTransaction(networkParameters, Hex.decode(RAW_FUND_TX));
int outputIndex = 0; // Remember to change this value accordingly in case of using an existing raw tx
BtcTransaction pegInTx = new BtcTransaction(networkParameters);
pegInTx.addOutput(Coin.valueOf(990_000), erpFed.getAddress());

Address destinationAddress = PegTestUtils.createRandomP2PKHBtcAddress(networkParameters);
BtcTransaction pegOutTx = new BtcTransaction(networkParameters);
pegOutTx.addInput(pegInTx.getOutput(outputIndex));
pegOutTx.addOutput(Coin.valueOf(900_000), destinationAddress);
pegOutTx.setVersion(BTC_TX_VERSION_2);
pegOutTx.getInput(0).setSequenceNumber(activationDelay);

// Create signatures
Sha256Hash sigHash = pegOutTx.hashForSignature(
0,
erpFed.getRedeemScript(),
BtcTransaction.SigHash.ALL,
false
);

BtcECKey.ECDSASignature signature1;
BtcECKey.ECDSASignature signature2;
BtcECKey.ECDSASignature signature3;
if (signWithEmergencyMultisig) {
signature1 = erp1PrivKey.sign(sigHash);
signature2 = erp2PrivKey.sign(sigHash);
signature3 = erp3PrivKey.sign(sigHash);
} else {
signature1 = fed1PrivKey.sign(sigHash);
signature2 = fed2PrivKey.sign(sigHash);
signature3 = fed3PrivKey.sign(sigHash);
}

// Try different signature permutations
Script inputScript = createInputScript(erpFed.getRedeemScript(), signature1, signature2, signWithEmergencyMultisig);
pegOutTx.getInput(0).setScriptSig(inputScript);
inputScript.correctlySpends(pegOutTx,0, pegInTx.getOutput(outputIndex).getScriptPubKey());

inputScript = createInputScript(erpFed.getRedeemScript(), signature1, signature3, signWithEmergencyMultisig);
pegOutTx.getInput(0).setScriptSig(inputScript);
inputScript.correctlySpends(pegOutTx,0, pegInTx.getOutput(outputIndex).getScriptPubKey());

inputScript = createInputScript(erpFed.getRedeemScript(), signature2, signature3, signWithEmergencyMultisig);
pegOutTx.getInput(0).setScriptSig(inputScript);
inputScript.correctlySpends(pegOutTx,0, pegInTx.getOutput(outputIndex).getScriptPubKey());

// Uncomment to print the raw tx in console and broadcast https://blockstream.info/testnet/tx/push
// System.out.println(Hex.toHexString(pegOutTx.bitcoinSerialize()));
}

private void createErpFederation(BridgeConstants constants, boolean isRskip293Active) {
when(activations.isActive(ConsensusRule.RSKIP293)).thenReturn(isRskip293Active);

Expand All @@ -833,35 +723,57 @@ private void createErpFederation(BridgeConstants constants, boolean isRskip293Ac
);
}

private Script createInputScript(
Script fedRedeemScript,
BtcECKey.ECDSASignature signature1,
BtcECKey.ECDSASignature signature2,
boolean signWithTheEmergencyMultisig) {
private void spendFromErpFed(
NetworkParameters networkParameters,
long activationDelay,
boolean isRskip293Active,
boolean signWithEmergencyMultisig) {

List<BtcECKey> standardKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds(
new String[]{"fed1", "fed2", "fed3", "fed4", "fed5", "fed6", "fed7", "fed8", "fed9", "fed10"},
true
);

TransactionSignature txSignature1 = new TransactionSignature(
signature1,
BtcTransaction.SigHash.ALL,
false
List<BtcECKey> emergencyKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds(
new String[]{"erp1", "erp2", "erp3", "erp4"},
true
);
byte[] txSignature1Encoded = txSignature1.encodeToBitcoin();

TransactionSignature txSignature2 = new TransactionSignature(
signature2,
BtcTransaction.SigHash.ALL,
false
List<ConsensusRule> except = isRskip293Active ?
Collections.emptyList() :
Collections.singletonList(ConsensusRule.RSKIP293);
ActivationConfig activations = ActivationConfigsForTest.hop400(except);

ErpFederation erpFed = new ErpFederation(
FederationMember.getFederationMembersFromKeys(standardKeys),
ZonedDateTime.parse("2017-06-10T02:30:00Z").toInstant(),
0L,
networkParameters,
emergencyKeys,
activationDelay,
activations.forBlock(0)
);

Coin value = Coin.valueOf(1_000_000);
Coin fee = Coin.valueOf(10_000);
BtcTransaction fundTx = new BtcTransaction(networkParameters);
fundTx.addOutput(value, erpFed.getAddress());

Address destinationAddress = BitcoinTestUtils.createP2PKHAddress(
networkParameters,
"destination"
);

FederationTestUtils.spendFromErpFed(
networkParameters,
erpFed,
signWithEmergencyMultisig ? emergencyKeys : standardKeys,
fundTx.getHash(),
0,
destinationAddress,
value.minus(fee),
signWithEmergencyMultisig
);
byte[] txSignature2Encoded = txSignature2.encodeToBitcoin();

int flowOpCode = signWithTheEmergencyMultisig ? 1 : 0;
ScriptBuilder scriptBuilder = new ScriptBuilder();
return scriptBuilder
.number(0)
.data(txSignature1Encoded)
.data(txSignature2Encoded)
.number(flowOpCode)
.data(fedRedeemScript.getProgram())
.build();
}

private ErpFederation createDefaultErpFederation() {
Expand Down
98 changes: 93 additions & 5 deletions rskj-core/src/test/java/co/rsk/peg/FederationTestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,24 @@

package co.rsk.peg;

import static co.rsk.peg.ReleaseTransactionBuilder.BTC_TX_VERSION_2;

import co.rsk.bitcoinj.core.Address;
import co.rsk.bitcoinj.core.BtcECKey;
import co.rsk.bitcoinj.core.BtcTransaction;
import co.rsk.bitcoinj.core.Coin;
import co.rsk.bitcoinj.core.NetworkParameters;
import org.ethereum.crypto.ECKey;

import co.rsk.bitcoinj.core.Sha256Hash;
import co.rsk.bitcoinj.crypto.TransactionSignature;
import co.rsk.bitcoinj.script.Script;
import co.rsk.bitcoinj.script.ScriptBuilder;
import java.math.BigInteger;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.ethereum.crypto.ECKey;

public class FederationTestUtils {

Expand Down Expand Up @@ -62,9 +70,9 @@ public static List<FederationMember> getFederationMembersFromPks(Integer... pks)
}

public static List<FederationMember> getFederationMembersWithBtcKeys(List<BtcECKey> keys) {
return keys.stream().map(btcKey ->
new FederationMember(btcKey, new ECKey(), new ECKey())
).collect(Collectors.toList());
return keys.stream()
.map(btcKey -> new FederationMember(btcKey, new ECKey(), new ECKey()))
.collect(Collectors.toList());
}

public static List<FederationMember> getFederationMembersWithKeys(List<BtcECKey> pks) {
Expand All @@ -75,4 +83,84 @@ public static FederationMember getFederationMemberWithKey(BtcECKey pk) {
ECKey ethKey = ECKey.fromPublicOnly(pk.getPubKey());
return new FederationMember(pk, ethKey, ethKey);
}

public static void spendFromErpFed(
NetworkParameters networkParameters,
ErpFederation federation,
List<BtcECKey> signers,
Sha256Hash fundTxHash,
int outputIndex,
Address receiver,
Coin value,
boolean signWithEmergencyMultisig) {

BtcTransaction spendTx = new BtcTransaction(networkParameters);
spendTx.addInput(fundTxHash, outputIndex, new Script(new byte[]{}));
spendTx.addOutput(value, receiver);

if (signWithEmergencyMultisig) {
spendTx.setVersion(BTC_TX_VERSION_2);
spendTx.getInput(0).setSequenceNumber(federation.getActivationDelay());
}

// Create signatures
Sha256Hash sigHash = spendTx.hashForSignature(
0,
federation.getRedeemScript(),
BtcTransaction.SigHash.ALL,
false
);

int totalSigners = signers.size();
List<BtcECKey.ECDSASignature> allSignatures = new ArrayList<>();
for (int i = 0; i < totalSigners; i++) {
BtcECKey keyToSign = signers.get(i);
BtcECKey.ECDSASignature signature = keyToSign.sign(sigHash);
allSignatures.add(signature);
}

// Try different signature permutations
int requiredSignatures = totalSigners / 2 + 1;
int permutations = totalSigners % 2 == 0 ? requiredSignatures - 1 : requiredSignatures;
for (int i = 0; i < permutations; i++) {
List<BtcECKey.ECDSASignature> signatures = allSignatures.subList(i, requiredSignatures + i);
Script inputScript = createInputScriptSig(federation.getRedeemScript(), signatures, signWithEmergencyMultisig);
spendTx.getInput(0).setScriptSig(inputScript);
inputScript.correctlySpends(
spendTx,
0,
federation.getP2SHScript(),
Script.ALL_VERIFY_FLAGS
);
}

// Uncomment to print the raw tx in console and broadcast https://blockstream.info/testnet/tx/push
// System.out.println(Hex.toHexString(spendTx.bitcoinSerialize()));
}

private static Script createInputScriptSig(
Script fedRedeemScript,
List<BtcECKey.ECDSASignature> signatures,
boolean signWithTheEmergencyMultisig) {

ScriptBuilder scriptBuilder = new ScriptBuilder();

scriptBuilder = scriptBuilder.number(0);
for (BtcECKey.ECDSASignature signature : signatures) {
TransactionSignature txSignature = new TransactionSignature(
signature,
BtcTransaction.SigHash.ALL,
false
);
byte[] txSignatureEncoded = txSignature.encodeToBitcoin();

scriptBuilder = scriptBuilder.data(txSignatureEncoded);
}
int flowOpCode = signWithTheEmergencyMultisig ? 1 : 0;

return scriptBuilder
.number(flowOpCode)
.data(fedRedeemScript.getProgram())
.build();
}
}
Loading

0 comments on commit 1de825f

Please sign in to comment.