Skip to content

Commit 8f2e3ab

Browse files
authored
Merge branch 'main' into worktree-fix-validated-ledger-optional-age
2 parents 7d1c5c7 + 1a964ed commit 8f2e3ab

168 files changed

Lines changed: 16325 additions & 35 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/workflow.yml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,92 @@ jobs:
216216
- name: Build
217217
run: mvn clean install -Dmaven.javadoc.skip=true -Pandroid
218218

219+
# Verifies the mpt-crypto native libraries (loaded via JNA) on each platform we ship binaries for.
220+
build_confidential_native_its:
221+
name: confidential_native_${{ matrix.platform }}_${{ matrix.network }}
222+
needs: resolve_xrpld_image
223+
strategy:
224+
fail-fast: false
225+
matrix:
226+
include:
227+
# Linux x86-64 runners have Docker, so the confidential MPT ITs run against the local
228+
# rippled Testcontainer and exercise the real native library end-to-end.
229+
- os: ubuntu-latest
230+
platform: linux-x86-64
231+
java: '8'
232+
network: local-rippled
233+
it_flags: ''
234+
# The rippleci/xrpld image is published for linux/amd64 only, so the ARM Linux runner
235+
# registers QEMU binfmt handlers to run the amd64 rippled container under emulation.
236+
- os: ubuntu-24.04-arm
237+
platform: linux-aarch64
238+
java: '8'
239+
network: local-rippled
240+
it_flags: ''
241+
setup_qemu: true
242+
# Intel macOS runners have no Docker preinstalled, but support virtualization, so
243+
# Colima provides a Docker engine for the rippled Testcontainer.
244+
- os: macos-15-intel
245+
platform: darwin-x86-64
246+
java: '8'
247+
network: local-rippled
248+
it_flags: ''
249+
setup_docker_via_colima: true
250+
# ARM macOS runners cannot run Docker at all (Apple's Virtualization Framework does not
251+
# support nested virtualization), and Windows runners cannot run Linux containers, so
252+
# these platforms run the confidential MPT ITs against Devnet instead of a local rippled.
253+
# Temurin doesn't ship JDK 8 for macOS ARM64, so we use JDK 11 there.
254+
- os: macos-14
255+
platform: darwin-aarch64
256+
java: '11'
257+
network: devnet
258+
it_flags: '-DuseDevnet'
259+
- os: windows-latest
260+
platform: win32-x86-64
261+
java: '8'
262+
network: devnet
263+
it_flags: '-DuseDevnet'
264+
runs-on: ${{ matrix.os }}
265+
env:
266+
XRPLD_DOCKER_IMAGE: ${{ needs.resolve_xrpld_image.outputs.image }}
267+
defaults:
268+
run:
269+
shell: bash
270+
steps:
271+
# Checks-out the repository under $GITHUB_WORKSPACE
272+
- uses: actions/checkout@v6
273+
- name: Set up JDK ${{ matrix.java }}
274+
uses: actions/setup-java@v5
275+
with:
276+
distribution: 'temurin'
277+
java-version: ${{ matrix.java }}
278+
cache: 'maven'
279+
- name: Set up QEMU for amd64 emulation
280+
if: matrix.setup_qemu
281+
uses: docker/setup-qemu-action@v3
282+
with:
283+
platforms: amd64
284+
- name: Set up Docker via Colima
285+
if: matrix.setup_docker_via_colima
286+
run: |
287+
brew install colima docker
288+
colima start --cpus 3 --memory 8
289+
echo "DOCKER_HOST=unix://$HOME/.colima/default/docker.sock" >> "$GITHUB_ENV"
290+
echo "TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock" >> "$GITHUB_ENV"
291+
# Only the local-rippled rows pull an xrpld image, so only they need the registry login. This step must follow
292+
# the Colima setup above, which is what provides the Docker daemon on the macOS Intel runner.
293+
- name: Log in to private registry
294+
if: matrix.network == 'local-rippled'
295+
uses: ./.github/actions/xrpld-login
296+
with:
297+
is_private: ${{ needs.resolve_xrpld_image.outputs.is_private }}
298+
username: ${{ vars.GITLAB_REGISTRY_USERNAME }}
299+
token: ${{ secrets.GITLAB_REGISTRY_TOKEN }}
300+
- name: Build and run unit tests
301+
run: mvn clean install -DskipITs -Dmaven.javadoc.skip=true
302+
- name: Run Confidential MPT ITs
303+
run: mvn -pl xrpl4j-integration-tests verify -Dit.test='Confidential*IT' -Dfailsafe.failIfNoSpecifiedTests=true ${{ matrix.it_flags }} -Dmaven.javadoc.skip=true
304+
219305
build_devnet_its:
220306
runs-on: ubuntu-latest
221307
steps:

pom.xml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@
5151
<artifactId>xrpl4j-client</artifactId>
5252
<version>${project.version}</version>
5353
</dependency>
54+
<dependency>
55+
<groupId>${project.groupId}</groupId>
56+
<artifactId>xrpl4j-mpt-crypto</artifactId>
57+
<version>${project.version}</version>
58+
</dependency>
5459
<dependency>
5560
<groupId>com.fasterxml.jackson</groupId>
5661
<artifactId>jackson-bom</artifactId>
@@ -172,6 +177,11 @@
172177
<artifactId>bcprov-jdk18on</artifactId>
173178
<version>1.84</version>
174179
</dependency>
180+
<dependency>
181+
<groupId>net.java.dev.jna</groupId>
182+
<artifactId>jna</artifactId>
183+
<version>5.18.1</version>
184+
</dependency>
175185
<dependency>
176186
<groupId>org.awaitility</groupId>
177187
<artifactId>awaitility</artifactId>
@@ -278,6 +288,7 @@
278288

279289
<modules>
280290
<module>xrpl4j-core</module>
291+
<module>xrpl4j-mpt-crypto</module>
281292
<module>xrpl4j-integration-tests</module>
282293
<module>xrpl4j-client</module>
283294
<module>xrpl4j-bom</module>
@@ -314,6 +325,8 @@
314325
<ignoredDependency>com.google.code.findbugs:annotations</ignoredDependency>
315326
<ignoredDependency>com.google.errorprone:error_prone_annotations</ignoredDependency>
316327
<ignoredDependency>ch.qos.logback:logback-classic</ignoredDependency>
328+
<!-- Loaded via reflection-->
329+
<ignoredDependency>org.xrpl:xrpl4j-mpt-crypto</ignoredDependency>
317330
<!-- As part of SkyStreamer, but only for test scope-->
318331
<ignoredDependency>com.vaadin.external.google:android-json</ignoredDependency>
319332
<ignoredDependency>org.junit.jupiter:junit-jupiter-engine</ignoredDependency>
@@ -494,7 +507,7 @@
494507
<plugin>
495508
<groupId>org.sonatype.central</groupId>
496509
<artifactId>central-publishing-maven-plugin</artifactId>
497-
<version>0.5.0</version>
510+
<version>0.11.0</version>
498511
<extensions>true</extensions>
499512
<configuration>
500513
<publishingServerId>central</publishingServerId>

xrpl4j-bom/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
<artifactId>xrpl4j-core</artifactId>
2626
<version>${project.version}</version>
2727
</dependency>
28+
<dependency>
29+
<groupId>${project.groupId}</groupId>
30+
<artifactId>xrpl4j-mpt-crypto</artifactId>
31+
<version>${project.version}</version>
32+
</dependency>
2833
</dependencies>
2934
</dependencyManagement>
3035
</project>

xrpl4j-core/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@
9191
<groupId>org.bouncycastle</groupId>
9292
<artifactId>bcprov-jdk18on</artifactId>
9393
</dependency>
94+
<dependency>
95+
<groupId>net.java.dev.jna</groupId>
96+
<artifactId>jna</artifactId>
97+
</dependency>
9498
<dependency>
9599
<groupId>com.jayway.jsonpath</groupId>
96100
<artifactId>json-path-assert</artifactId>

xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/addresses/SeedCodec.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
* =========================LICENSE_END==================================
2121
*/
2222

23+
import com.google.common.collect.ImmutableSet;
2324
import com.google.common.collect.Lists;
2425
import com.google.common.primitives.UnsignedInteger;
26+
import org.xrpl.xrpl4j.codec.addresses.exceptions.DecodeException;
2527
import org.xrpl.xrpl4j.codec.addresses.exceptions.EncodeException;
2628
import org.xrpl.xrpl4j.codec.addresses.exceptions.EncodingFormatException;
2729

@@ -36,6 +38,11 @@ public class SeedCodec {
3638

3739
private static final SeedCodec INSTANCE = new SeedCodec();
3840

41+
// A 16-byte ED25519 or SECP256K1 seed Base58Check-encodes to 31 or 29 characters, respectively; a 32-byte
42+
// SECP256K1 seed (used only for ElGamal seeds; see #encodeSeed) encodes to 51 characters. No other length is
43+
// decodable -- e.g. a 32-byte ED25519 seed would encode to 53 characters, but #encodeSeed refuses to produce one.
44+
private static final ImmutableSet<Integer> VALID_SEED_LENGTHS = ImmutableSet.of(29, 31, 51);
45+
3946
public static SeedCodec getInstance() {
4047
return INSTANCE;
4148
}
@@ -52,6 +59,24 @@ public static SeedCodec getInstance() {
5259
public Decoded decodeSeed(final String base58EncodedSeed) throws EncodingFormatException {
5360
Objects.requireNonNull(base58EncodedSeed);
5461

62+
if (!VALID_SEED_LENGTHS.contains(base58EncodedSeed.length())) {
63+
throw new DecodeException(
64+
String.format("Invalid seed length %s; expected %s.", base58EncodedSeed.length(), VALID_SEED_LENGTHS)
65+
);
66+
}
67+
68+
// A 32-byte secp256k1 seed (used only for ElGamal seeds; see #encodeSeed) Base58Check-encodes to 51 characters,
69+
// versus 29 for the standard 16-byte payload. Detect that length here since AddressBase58.decode needs the
70+
// expected payload size up front to pick the right branch.
71+
if (base58EncodedSeed.length() == 51) {
72+
return AddressBase58.decode(
73+
base58EncodedSeed,
74+
Lists.newArrayList(KeyType.SECP256K1),
75+
Lists.newArrayList(Version.FAMILY_SEED),
76+
Optional.of(UnsignedInteger.valueOf(32))
77+
);
78+
}
79+
5580
return AddressBase58.decode(
5681
base58EncodedSeed,
5782
Lists.newArrayList(KeyType.ED25519, KeyType.SECP256K1),
@@ -72,11 +97,22 @@ public String encodeSeed(final UnsignedByteArray entropy, final KeyType type) {
7297
Objects.requireNonNull(entropy);
7398
Objects.requireNonNull(type);
7499

75-
if (entropy.getUnsignedBytes().size() != 16) {
76-
throw new EncodeException("entropy must have length 16.");
100+
if (entropy.getUnsignedBytes().size() != 16 && entropy.getUnsignedBytes().size() != 32) {
101+
throw new EncodeException("entropy must have length 16 or 32.");
102+
}
103+
104+
// 32-byte entropy exists only to support ElGamal secp256k1 seeds (see Seed#elGamalSecp256k1SeedFromEntropy).
105+
// Encoding 32 bytes under any non-secp256k1 prefix yields a seed that decodeSeed cannot decode -- e.g. the ED25519
106+
// prefix produces a 53-character seed that matches neither the 51-character secp256k1 branch nor the 16-byte
107+
// fallback -- so it would be silently unrecoverable. Gate on SECP256K1 (rather than excluding ED25519) so a
108+
// future KeyType with 32-byte entropy is rejected by default rather than mistakenly accepted.
109+
if (entropy.getUnsignedBytes().size() == 32 && !type.equals(KeyType.SECP256K1)) {
110+
throw new EncodeException("32-byte entropy is only supported for SECP256K1 seeds, but was " + type + ".");
77111
}
78112

79113
Version version = type.equals(KeyType.ED25519) ? Version.ED25519_SEED : Version.FAMILY_SEED;
80-
return AddressBase58.encode(entropy, Lists.newArrayList(version), UnsignedInteger.valueOf(16));
114+
return AddressBase58.encode(
115+
entropy, Lists.newArrayList(version), UnsignedInteger.valueOf(entropy.getUnsignedBytes().size())
116+
);
81117
}
82118
}

xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/UInt192Type.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.xrpl.xrpl4j.codec.binary.types;
22

33
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.node.TextNode;
45
import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray;
56
import org.xrpl.xrpl4j.codec.binary.serdes.BinaryParser;
67

@@ -28,4 +29,9 @@ public UInt192Type fromParser(BinaryParser parser) {
2829
public UInt192Type fromJson(JsonNode node) {
2930
return new UInt192Type(UnsignedByteArray.fromHex(node.asText()));
3031
}
32+
33+
@Override
34+
public JsonNode toJson() {
35+
return new TextNode(toHex());
36+
}
3137
}

xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/UInt64Type.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public class UInt64Type extends UIntType<UInt64Type> {
3838
* These fields are represented as base 10 Strings in JSON, whereas all other STUInt64s are represented in base16.
3939
*/
4040
protected static final Set<String> BASE_10_UINT64_FIELD_NAMES = Sets.newHashSet(
41-
"MaximumAmount", "OutstandingAmount", "MPTAmount", "LockedAmount"
41+
"MaximumAmount", "OutstandingAmount", "MPTAmount", "LockedAmount", "ConfidentialOutstandingAmount"
4242
);
4343

4444
public UInt64Type() {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package org.xrpl.xrpl4j.crypto.confidential;
2+
3+
/*-
4+
* ========================LICENSE_START=================================
5+
* xrpl4j :: core
6+
* %%
7+
* Copyright (C) 2020 - 2026 XRPL Foundation and its contributors
8+
* %%
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
* =========================LICENSE_END==================================
21+
*/
22+
23+
import com.google.common.base.Preconditions;
24+
import org.immutables.value.Value;
25+
import org.xrpl.xrpl4j.model.transactions.Transaction;
26+
27+
import java.util.Optional;
28+
29+
/**
30+
* One entry in a confidential Batch: either a {@link ConfidentialMptOp} the assembler builds into a transaction and
31+
* proof, or a pre-built plain (non-confidential) {@link Transaction} to interleave.
32+
*
33+
* <p>A plain inner is passed through untouched, so the caller must supply it already shaped as an inner-batch
34+
* transaction — {@code Fee: 0}, the {@code tfInnerBatchTxn} flag, an empty {@code SigningPubKey}, and its own
35+
* {@code Sequence} or {@code TicketSequence} (the assembler cannot re-shape an arbitrary immutable transaction, and
36+
* xrpl4j has no autofill). The {@code RawTransactionWrapper}/{@code Batch} validation rejects a malformed plain inner.
37+
* The assembler advances the submitter's sequence counter past a plain inner that consumed a regular sequence, so a
38+
* later confidential inner for the same account is numbered correctly.</p>
39+
*/
40+
@Value.Immutable
41+
public interface ConfidentialBatchInner {
42+
43+
/**
44+
* A confidential-operation inner.
45+
*
46+
* @param operation The {@link ConfidentialMptOp} to build.
47+
*
48+
* @return A {@link ConfidentialBatchInner} wrapping {@code operation}.
49+
*/
50+
static ConfidentialBatchInner of(final ConfidentialMptOp operation) {
51+
return ImmutableConfidentialBatchInner.builder().operation(operation).build();
52+
}
53+
54+
/**
55+
* A pre-built plain (non-confidential) inner, already shaped as an inner-batch transaction.
56+
*
57+
* @param plainTransaction The plain {@link Transaction} to pass through.
58+
*
59+
* @return A {@link ConfidentialBatchInner} wrapping {@code plainTransaction}.
60+
*/
61+
static ConfidentialBatchInner ofPlain(final Transaction plainTransaction) {
62+
return ImmutableConfidentialBatchInner.builder().plainTransaction(plainTransaction).build();
63+
}
64+
65+
/**
66+
* The confidential operation, if this inner is a confidential op-spec.
67+
*
68+
* @return An optionally-present {@link ConfidentialMptOp}.
69+
*/
70+
Optional<ConfidentialMptOp> operation();
71+
72+
/**
73+
* The plain transaction, if this inner is a pre-built plain transaction.
74+
*
75+
* @return An optionally-present {@link Transaction}.
76+
*/
77+
Optional<Transaction> plainTransaction();
78+
79+
/**
80+
* Enforces that exactly one of {@link #operation()} or {@link #plainTransaction()} is present.
81+
*/
82+
@Value.Check
83+
default void check() {
84+
Preconditions.checkArgument(
85+
operation().isPresent() ^ plainTransaction().isPresent(),
86+
"a ConfidentialBatchInner must hold exactly one of a confidential operation or a plain transaction"
87+
);
88+
}
89+
}

0 commit comments

Comments
 (0)