diff --git a/src/main/java/org/stellar/sdk/Address.java b/src/main/java/org/stellar/sdk/Address.java index 77e1ae78b..41a12e7f8 100644 --- a/src/main/java/org/stellar/sdk/Address.java +++ b/src/main/java/org/stellar/sdk/Address.java @@ -12,6 +12,9 @@ import org.stellar.sdk.xdr.SCAddress; import org.stellar.sdk.xdr.SCVal; import org.stellar.sdk.xdr.SCValType; +import org.stellar.sdk.xdr.Uint256; +import org.stellar.sdk.xdr.Uint64; +import org.stellar.sdk.xdr.XdrUnsignedHyperInteger; /** * Represents a single address in the Stellar network. An address can represent an account, @@ -117,8 +120,8 @@ public static Address fromSCAddress(SCAddress scAddress) { return fromMuxedAccount( StrKey.toRawMuxedAccountStrKey( new StrKey.RawMuxedAccountStrKeyParameter( - scAddress.getMuxedAccount().getEd25519(), - scAddress.getMuxedAccount().getId()))); + scAddress.getMuxedAccount().getEd25519().getUint256(), + scAddress.getMuxedAccount().getId().getUint64().getNumber()))); case SC_ADDRESS_TYPE_CLAIMABLE_BALANCE: if (scAddress.getClaimableBalanceId().getDiscriminant() != ClaimableBalanceIDType.CLAIMABLE_BALANCE_ID_TYPE_V0) { @@ -174,8 +177,8 @@ public SCAddress toSCAddress() { StrKey.fromRawMuxedAccountStrKey(this.key); MuxedEd25519Account muxedEd25519Account = MuxedEd25519Account.builder() - .id(parameter.getId()) - .ed25519(parameter.getEd25519()) + .id(new Uint64(new XdrUnsignedHyperInteger(parameter.getId()))) + .ed25519(new Uint256(parameter.getEd25519())) .build(); scAddress.setMuxedAccount(muxedEd25519Account); break; diff --git a/src/main/java/org/stellar/sdk/MuxedAccount.java b/src/main/java/org/stellar/sdk/MuxedAccount.java index 8e079615b..f587b8ea0 100644 --- a/src/main/java/org/stellar/sdk/MuxedAccount.java +++ b/src/main/java/org/stellar/sdk/MuxedAccount.java @@ -70,8 +70,8 @@ public MuxedAccount(@NonNull String address) { byte[] rawMed25519 = StrKey.decodeMed25519PublicKey(address); StrKey.RawMuxedAccountStrKeyParameter parameter = StrKey.fromRawMuxedAccountStrKey(rawMed25519); - this.accountId = StrKey.encodeEd25519PublicKey(parameter.getEd25519().getUint256()); - this.muxedId = parameter.getId().getUint64().getNumber(); + this.accountId = StrKey.encodeEd25519PublicKey(parameter.getEd25519()); + this.muxedId = parameter.getId(); } else { throw new IllegalArgumentException("Invalid address"); } @@ -87,10 +87,10 @@ public String getAddress() { if (muxedId == null) { return accountId; } - org.stellar.sdk.xdr.MuxedAccount.MuxedAccountMed25519 med25519 = toXdr().getMed25519(); return StrKey.encodeMed25519PublicKey( StrKey.toRawMuxedAccountStrKey( - new StrKey.RawMuxedAccountStrKeyParameter(med25519.getEd25519(), med25519.getId()))); + new StrKey.RawMuxedAccountStrKeyParameter( + StrKey.decodeEd25519PublicKey(accountId), muxedId))); } /** diff --git a/src/main/java/org/stellar/sdk/StrKey.java b/src/main/java/org/stellar/sdk/StrKey.java index 5173d6455..b6ed2a625 100644 --- a/src/main/java/org/stellar/sdk/StrKey.java +++ b/src/main/java/org/stellar/sdk/StrKey.java @@ -10,9 +10,6 @@ import lombok.NonNull; import lombok.Value; import org.stellar.sdk.exception.UnexpectedException; -import org.stellar.sdk.xdr.Uint256; -import org.stellar.sdk.xdr.Uint64; -import org.stellar.sdk.xdr.XdrUnsignedHyperInteger; /** * StrKey is a helper class that allows encoding and decoding Stellar keys to/from strings, i.e. @@ -20,6 +17,7 @@ */ public class StrKey { + private static final BigInteger UINT64_MAX = new BigInteger("18446744073709551615"); private static final byte[] b32Table = decodingTable(); private static final Base32 base32Codec = Base32Factory.getInstance(); @@ -584,11 +582,22 @@ private static boolean isInAlphabet(final byte[] arrayOctet) { @Value static class RawMuxedAccountStrKeyParameter { - @NonNull Uint256 ed25519; - @NonNull Uint64 id; + byte @NonNull [] ed25519; + @NonNull BigInteger id; } static byte[] toRawMuxedAccountStrKey(RawMuxedAccountStrKeyParameter parameter) { + byte[] ed25519Bytes = parameter.getEd25519(); + if (ed25519Bytes.length != 32) { + throw new IllegalArgumentException( + "Muxed account ed25519 bytes must be 32 bytes long, got " + ed25519Bytes.length); + } + if (parameter.getId().compareTo(BigInteger.ZERO) < 0 + || parameter.getId().compareTo(UINT64_MAX) > 0) { + throw new IllegalArgumentException( + "Muxed account ID must be between 0 and 2^64 - 1 inclusive"); + } + // Get the 64-bit ID. This is the critical part of the explanation. // // THE KEY INSIGHT: Why using .longValue() is safe for a uint64 @@ -633,8 +642,7 @@ static byte[] toRawMuxedAccountStrKey(RawMuxedAccountStrKeyParameter parameter) // buffer, // it correctly serializes the original uint64 value into 8 bytes, regardless of whether Java // interpreted the intermediate `long` as positive or negative. - long idLong = parameter.getId().getUint64().getNumber().longValue(); - byte[] ed25519Bytes = parameter.getEd25519().getUint256(); + long idLong = parameter.getId().longValue(); return ByteBuffer.allocate(ed25519Bytes.length + 8).put(ed25519Bytes).putLong(idLong).array(); } @@ -648,9 +656,7 @@ static RawMuxedAccountStrKeyParameter fromRawMuxedAccountStrKey(byte @NonNull [] buffer.get(ed25519Bytes); byte[] idBytes = new byte[8]; buffer.get(idBytes); - Uint256 ed25519 = new Uint256(ed25519Bytes); - Uint64 id = new Uint64(new XdrUnsignedHyperInteger(new BigInteger(1, idBytes))); - return new RawMuxedAccountStrKeyParameter(ed25519, id); + return new RawMuxedAccountStrKeyParameter(ed25519Bytes, new BigInteger(1, idBytes)); } enum VersionByte { diff --git a/src/test/kotlin/org/stellar/sdk/StrKeyTest.kt b/src/test/kotlin/org/stellar/sdk/StrKeyTest.kt index e79da148a..2309b99bc 100644 --- a/src/test/kotlin/org/stellar/sdk/StrKeyTest.kt +++ b/src/test/kotlin/org/stellar/sdk/StrKeyTest.kt @@ -461,17 +461,14 @@ class StrKeyTest : ), ) { testCase -> val ed25519Bytes = Util.hexToBytes(testCase.ed25519Hex) - val ed25519 = org.stellar.sdk.xdr.Uint256(ed25519Bytes) - val id = - org.stellar.sdk.xdr.Uint64(org.stellar.sdk.xdr.XdrUnsignedHyperInteger(testCase.id)) - val param = StrKey.RawMuxedAccountStrKeyParameter(ed25519, id) + val param = StrKey.RawMuxedAccountStrKeyParameter(ed25519Bytes, testCase.id) val bytes = StrKey.toRawMuxedAccountStrKey(param) bytes.size shouldBe 40 val decoded = StrKey.fromRawMuxedAccountStrKey(bytes) - decoded.ed25519.uint256 shouldBe ed25519Bytes - decoded.id.uint64.number shouldBe testCase.id + decoded.ed25519 shouldBe ed25519Bytes + decoded.id shouldBe testCase.id } }