Skip to content

Commit

Permalink
Add unit tests to validate signature in tx hash (#526)
Browse files Browse the repository at this point in the history
* Add unit tests to validate signature in tx hash

---------

Co-authored-by: nkramer44 <[email protected]>
  • Loading branch information
sappenin and nkramer44 authored Oct 30, 2024
1 parent 2966227 commit d97ffb2
Showing 1 changed file with 123 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,28 @@
import org.xrpl.xrpl4j.model.flags.TransactionFlags;
import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory;
import org.xrpl.xrpl4j.model.transactions.Address;
import org.xrpl.xrpl4j.model.transactions.Memo;
import org.xrpl.xrpl4j.model.transactions.MemoWrapper;
import org.xrpl.xrpl4j.model.transactions.Payment;
import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount;

import java.util.Arrays;
import java.util.Collections;

/**
* Unit tests for {@link SingleSignedTransaction}.
*/
class SignedTransactionTest {

/**
* This test constructs the transaction found here:
* https://livenet.xrpl.org/transactions/A7AE53FE15B02E6E2F3C610FB4BA30B12392EB110F1D5E8C20880555E8639B05 to check
* that the hash that's on livenet matches what this library computes. The hash you see in this test is different than
* the hash found on livenet because the real transaction did not set any flags on the transaction and {@link Payment}
* requires a flags field (Even if you set flags to 0, it affects the hash). However, we made {@link Payment#flags()}
* nullable during development and verified that the hashes match, so we are confident that our hash calculation is
* accurate.
* This test constructs the transaction with hash A7AE53FE15B02E6E2F3C610FB4BA30B12392EB110F1D5E8C20880555E8639B05 to
* check that the hash that's on livenet matches what this library computes. The hash you see in this test is
* different from the hash found on livenet because the real transaction did not set any flags on the transaction and
* {@link Payment} requires a flags field (Even if you set flags to 0, it affects the hash). However, we made
* {@link Payment#flags()} nullable during development and verified that the hashes match, so we are confident that
* our hash calculation is accurate.
*
* @see "https://livenet.xrpl.org/transactions/A7AE53FE15B02E6E2F3C610FB4BA30B12392EB110F1D5E8C20880555E8639B05"
*/
@Test
public void computesCorrectTransactionHash() throws JsonProcessingException {
Expand All @@ -65,31 +71,132 @@ public void computesCorrectTransactionHash() throws JsonProcessingException {
.destinationTag(UnsignedInteger.valueOf(371969))
.build();

final Signature signature = Signature.fromBase16(
"304502210093257D8E88D2A92CE55977641F72CCD235AB76B1AE189BE3377F30A69B131C49" +
"02200B79836114069F0D331418D05818908D85DE755AE5C2DDF42E9637FE1C11754F"
);

final Payment signedPayment = Payment.builder().from(unsignedTransaction)
.transactionSignature(Signature.fromBase16(
"304502210093257D8E88D2A92CE55977641F72CCD235AB76B1AE189BE3377F30A6" +
"9B131C4902200B79836114069F0D331418D05818908D85DE755AE5C2DDF42E9637FE1C11754F"
.transactionSignature(signature)
.build();

SingleSignedTransaction<Payment> signedTransaction = SingleSignedTransaction.<Payment>builder()
.signedTransaction(signedPayment)
.signature(signature)
.unsignedTransaction(unsignedTransaction)
.build();

String expectedHash = "F847C96B2EEB0609F16C9DB9D74A0CB123B5EAF5B626207977335BF0A1EF53C3";
assertThat(signedTransaction.hash().value()).isEqualTo(expectedHash);
assertThat(signedTransaction.unsignedTransaction()).isEqualTo(unsignedTransaction);
assertThat(signedTransaction.signedTransaction()).isEqualTo(signedPayment);
assertThat(signedTransaction.signedTransactionBytes().hexValue()).isEqualTo(
XrplBinaryCodec.getInstance().encode(ObjectMapperFactory.create().writeValueAsString(signedPayment))
);
}

/**
* This test constructs the transaction with hash 1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997 to
* check that the hash that's on livenet matches what this library computes.
*
* @see "https://livenet.xrpl.org/transactions/1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997"
*/
@Test
public void computesCorrectTransactionHashWithUnsetFlags() throws JsonProcessingException {
final Payment unsignedTransaction = Payment.builder()
.account(Address.of("rGWx7VAsnwVKRbPFPpvy8Lo4nFf5xjj6Zb"))
.amount(XrpCurrencyAmount.ofDrops(1))
.destination(Address.of("rxRpSNb1VktvzBz8JF2oJC6qaww6RZ7Lw"))
.fee(XrpCurrencyAmount.ofDrops(12))
.flags(PaymentFlags.of(TransactionFlags.UNSET.getValue())) // 0
.lastLedgerSequence(UnsignedInteger.valueOf(86481544))
.memos(Collections.singletonList(
MemoWrapper.builder()
.memo(Memo.builder()
.memoData("7B226F70223A226D696E74222C22616D6F756E74223A22313030303030303030222C22677061223A2230227D")
.build())
.build()
))
.sequence(UnsignedInteger.valueOf(84987644))
.signingPublicKey(
PublicKey.fromBase16EncodedPublicKey("ED05DC98B76FCD734BD44CDF153C34F79728485D2F24F9381CF7A284223EA258CE")
)
.build();

final Signature signature = Signature.builder().value(
UnsignedByteArray.of(BaseEncoding.base16()
.decode("304502210093257D8E88D2A92CE55977641F72CCD235AB76B1AE189BE3377F30A69B131C49" +
"02200B79836114069F0D331418D05818908D85DE755AE5C2DDF42E9637FE1C11754F"))
).build();
final Signature signature = Signature.fromBase16(
"ED6F91CCF14EE94EB072C7671A397A313E3E5CBDAFE773BB6B2F07A0E75A7E65F84B5516268DAEE12902265256" +
"EA1EF046B200148E14FF4E720C06519FD7F40F"
);

final Payment signedPayment = Payment.builder().from(unsignedTransaction)
.transactionSignature(signature)
.build();

SingleSignedTransaction<Payment> signedTransaction = SingleSignedTransaction.<Payment>builder()
.signedTransaction(signedPayment)
.signature(signature)
.unsignedTransaction(unsignedTransaction)
.build();

String expectedHash = "F847C96B2EEB0609F16C9DB9D74A0CB123B5EAF5B626207977335BF0A1EF53C3";
String expectedHash = "1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997";
assertThat(signedTransaction.hash().value()).isEqualTo(expectedHash);
assertThat(signedTransaction.unsignedTransaction()).isEqualTo(unsignedTransaction);
assertThat(signedTransaction.signedTransaction()).isEqualTo(signedPayment);
assertThat(signedTransaction.signedTransactionBytes().hexValue()).isEqualTo(
XrplBinaryCodec.getInstance().encode(ObjectMapperFactory.create().writeValueAsString(signedPayment))
);
}

/**
* This test constructs the transaction with hash 1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997 to
* check that the hash that's on livenet _does not_ match when the signature is supplied incorrectly (i.e., this test
* validates that a transaction's signature is always used to compute a transaction hash).
*
* @see "https://livenet.xrpl.org/transactions/1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997"
*/
@Test
public void computesIncorrectTransactionHashWithoutSignature() throws JsonProcessingException {
final Payment unsignedTransaction = Payment.builder()
.account(Address.of("rGWx7VAsnwVKRbPFPpvy8Lo4nFf5xjj6Zb"))
.amount(XrpCurrencyAmount.ofDrops(1))
.destination(Address.of("rxRpSNb1VktvzBz8JF2oJC6qaww6RZ7Lw"))
.fee(XrpCurrencyAmount.ofDrops(12))
.flags(PaymentFlags.of(TransactionFlags.UNSET.getValue())) // 0
.lastLedgerSequence(UnsignedInteger.valueOf(86481544))
.memos(Collections.singletonList(
MemoWrapper.builder()
.memo(Memo.builder()
.memoData("7B226F70223A226D696E74222C22616D6F756E74223A22313030303030303030222C22677061223A2230227D")
.build())
.build()
))
.sequence(UnsignedInteger.valueOf(84987644))
.signingPublicKey(
PublicKey.fromBase16EncodedPublicKey("ED05DC98B76FCD734BD44CDF153C34F79728485D2F24F9381CF7A284223EA258CE")
)
.build();

final Signature emptySignature = Signature.fromBase16("");

final Payment signedPayment = Payment.builder().from(unsignedTransaction)
.transactionSignature(emptySignature)
.build();

SingleSignedTransaction<Payment> signedTransaction = SingleSignedTransaction.<Payment>builder()
.signedTransaction(signedPayment)
.signature(emptySignature)
.unsignedTransaction(unsignedTransaction)
.build();

String expectedHash = "1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997";
assertThat(signedTransaction.hash().value()).isNotEqualTo(expectedHash);
assertThat(signedTransaction.hash().value()).isEqualTo(
"8E0EDE65ECE8A03ABDD7926B994B2F6F14514FDBD46714F4F511143A1F01A6D0"
);
assertThat(signedTransaction.unsignedTransaction()).isEqualTo(unsignedTransaction);
assertThat(signedTransaction.signedTransaction()).isEqualTo(signedPayment);
assertThat(signedTransaction.signedTransactionBytes().hexValue()).isEqualTo(
XrplBinaryCodec.getInstance().encode(ObjectMapperFactory.create().writeValueAsString(signedPayment))
);
}
}

0 comments on commit d97ffb2

Please sign in to comment.