Skip to content

Commit e5a4ca0

Browse files
committed
WIP
1 parent dc1eac5 commit e5a4ca0

File tree

60 files changed

+1725
-2061
lines changed

Some content is hidden

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

60 files changed

+1725
-2061
lines changed

xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/keys/bc/BcKeyUtils.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,6 @@ public static PrivateKey toPrivateKey(final Ed25519PrivateKeyParameters ed25519P
9292
);
9393
}
9494

95-
/**
96-
* Convert from a {@link ECPrivateKeyParameters} to a {@link PrivateKey} with SECP256K1 key type.
97-
*
98-
* @param ecPrivateKeyParameters A {@link ECPrivateKeyParameters}.
99-
*
100-
* @return A {@link PrivateKey}.
101-
*/
102-
public static PrivateKey toPrivateKey(final ECPrivateKeyParameters ecPrivateKeyParameters) {
103-
return toPrivateKey(ecPrivateKeyParameters, KeyType.SECP256K1);
104-
}
105-
10695
/**
10796
* Convert from a {@link ECPrivateKeyParameters} to a {@link PrivateKey}.
10897
*

xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/mpt/Secp256k1Operations.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import org.bouncycastle.asn1.x9.X9ECParameters;
55
import org.bouncycastle.crypto.ec.CustomNamedCurves;
66
import org.bouncycastle.math.ec.ECPoint;
7+
import org.xrpl.xrpl4j.crypto.keys.PrivateKey;
8+
import org.xrpl.xrpl4j.crypto.keys.PublicKey;
79

810
import java.math.BigInteger;
911
import java.util.Arrays;
@@ -236,6 +238,22 @@ public static boolean isValidScalar(byte[] scalar) {
236238
return scalarInt.compareTo(BigInteger.ZERO) > 0 && scalarInt.compareTo(CURVE_ORDER) < 0;
237239
}
238240

241+
/**
242+
* Reduces a 32-byte hash to a valid scalar (mod curve order).
243+
*/
244+
public static byte[] reduceToScalar(byte[] hash) {
245+
BigInteger hashInt = new BigInteger(1, hash);
246+
BigInteger reduced = hashInt.mod(Secp256k1Operations.getCurveOrder());
247+
return Secp256k1Operations.toBytes32(reduced);
248+
}
249+
250+
public static int appendPoint(byte[] buffer, int offset, ECPoint point) {
251+
byte[] pointBytes = Secp256k1Operations.serializeCompressed(point);
252+
System.arraycopy(pointBytes, 0, buffer, offset, 33);
253+
return offset + 33;
254+
}
255+
256+
239257
/**
240258
* Returns the scalar representing (n - 1), which is equivalent to -1 mod n.
241259
*
@@ -340,4 +358,42 @@ public static byte[] serializeUncompressedWithoutPrefix(ECPoint point) {
340358
System.arraycopy(uncompressedWithPrefix, 1, result, 0, 64);
341359
return result;
342360
}
361+
362+
// ============================================================================
363+
// PublicKey / PrivateKey Conversion Utilities
364+
// ============================================================================
365+
366+
/**
367+
* Converts a {@link PublicKey} to an {@link ECPoint}.
368+
*
369+
* <p>This method extracts the compressed public key bytes and deserializes them
370+
* to an EC point on the secp256k1 curve.</p>
371+
*
372+
* @param publicKey The public key to convert.
373+
*
374+
* @return The corresponding EC point.
375+
*
376+
* @throws NullPointerException if publicKey is null.
377+
*/
378+
public static ECPoint toEcPoint(PublicKey publicKey) {
379+
Objects.requireNonNull(publicKey, "publicKey must not be null");
380+
return deserialize(publicKey.value().toByteArray());
381+
}
382+
383+
/**
384+
* Converts a {@link PrivateKey} to a {@link BigInteger} scalar.
385+
*
386+
* <p>This method extracts the natural (unprefixed) private key bytes and converts
387+
* them to a BigInteger for use in elliptic curve operations.</p>
388+
*
389+
* @param privateKey The private key to convert.
390+
*
391+
* @return The private key as a BigInteger scalar.
392+
*
393+
* @throws NullPointerException if privateKey is null.
394+
*/
395+
public static BigInteger toScalar(PrivateKey privateKey) {
396+
Objects.requireNonNull(privateKey, "privateKey must not be null");
397+
return new BigInteger(1, privateKey.naturalBytes().toByteArray());
398+
}
343399
}
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
package org.xrpl.xrpl4j.crypto.mpt.bulletproofs;
2+
3+
import com.google.common.hash.Hashing;
4+
import org.bouncycastle.math.ec.ECPoint;
5+
import org.xrpl.xrpl4j.crypto.mpt.Secp256k1Operations;
6+
7+
import java.math.BigInteger;
8+
import java.nio.charset.StandardCharsets;
9+
import java.util.List;
10+
11+
public class ChallengeUtils {
12+
13+
/**
14+
* Builds the challenge hash for the Schnorr proof.
15+
*
16+
* <p>The challenge is computed as: SHA256("MPT_POK_SK_REGISTER" || P || T [|| contextId])</p>
17+
*
18+
* @param publicKey The public key point P.
19+
* @param T The commitment point (k * G).
20+
* @param contextId The optional 32-byte context identifier. Can be null.
21+
*
22+
* @return A 32-byte challenge hash.
23+
*/
24+
public static byte[] secretKeyProofChallenge(ECPoint publicKey, ECPoint T, byte[] contextId) {
25+
String DOMAIN_SEPARATOR = "MPT_POK_SK_REGISTER";
26+
27+
byte[] domainBytes = DOMAIN_SEPARATOR.getBytes(StandardCharsets.UTF_8);
28+
byte[] pkBytes = Secp256k1Operations.serializeCompressed(publicKey);
29+
byte[] tBytes = Secp256k1Operations.serializeCompressed(T);
30+
31+
int contextIdLength = (contextId != null) ? 32 : 0;
32+
byte[] hashInput = new byte[domainBytes.length + 33 + 33 + contextIdLength];
33+
int offset = 0;
34+
System.arraycopy(domainBytes, 0, hashInput, offset, domainBytes.length);
35+
offset += domainBytes.length;
36+
System.arraycopy(pkBytes, 0, hashInput, offset, 33);
37+
offset += 33;
38+
System.arraycopy(tBytes, 0, hashInput, offset, 33);
39+
offset += 33;
40+
if (contextId != null) {
41+
System.arraycopy(contextId, 0, hashInput, offset, 32);
42+
}
43+
44+
byte[] sha256Hash = Hashing.sha256().hashBytes(hashInput).asBytes();
45+
46+
// Reduce modulo curve order (equivalent to secp256k1_mpt_scalar_reduce32 in C)
47+
BigInteger hashInt = new BigInteger(1, sha256Hash);
48+
BigInteger reduced = hashInt.mod(Secp256k1Operations.getCurveOrder());
49+
return Secp256k1Operations.toBytes32(reduced);
50+
}
51+
52+
/**
53+
* Computes the Fiat-Shamir challenge hash.
54+
*
55+
* <p>Hash( Domain || {R_i, S_i, Pk_i} || Tm || {TrG_i, TrP_i} || TxID )</p>
56+
*/
57+
public static byte[] samePlaintextProofChallenge(
58+
int n,
59+
List<ECPoint> R,
60+
List<ECPoint> S,
61+
List<ECPoint> Pk,
62+
ECPoint Tm,
63+
List<ECPoint> TrG,
64+
List<ECPoint> TrP,
65+
byte[] txId
66+
) {
67+
String DOMAIN_SEPARATOR = "MPT_POK_SAME_PLAINTEXT_PROOF";
68+
// Calculate total size for buffer
69+
// Domain + n*(R+S+Pk) + Tm + n*(TrG+TrP) + txId
70+
int domainLen = DOMAIN_SEPARATOR.getBytes(StandardCharsets.UTF_8).length;
71+
int pointsLen = (3 * n + 1 + 2 * n) * 33; // (R,S,Pk)*n + Tm + (TrG,TrP)*n
72+
int txIdLen = (txId != null) ? 32 : 0;
73+
74+
byte[] buffer = new byte[domainLen + pointsLen + txIdLen];
75+
int offset = 0;
76+
77+
// Domain separator
78+
byte[] domainBytes = DOMAIN_SEPARATOR.getBytes(StandardCharsets.UTF_8);
79+
System.arraycopy(domainBytes, 0, buffer, offset, domainBytes.length);
80+
offset += domainBytes.length;
81+
82+
// Public inputs: {R_i, S_i, Pk_i}
83+
for (int i = 0; i < n; i++) {
84+
byte[] rBytes = Secp256k1Operations.serializeCompressed(R.get(i));
85+
System.arraycopy(rBytes, 0, buffer, offset, 33);
86+
offset += 33;
87+
88+
byte[] sBytes = Secp256k1Operations.serializeCompressed(S.get(i));
89+
System.arraycopy(sBytes, 0, buffer, offset, 33);
90+
offset += 33;
91+
92+
byte[] pkBytes = Secp256k1Operations.serializeCompressed(Pk.get(i));
93+
System.arraycopy(pkBytes, 0, buffer, offset, 33);
94+
offset += 33;
95+
}
96+
97+
// Commitments: Tm
98+
byte[] tmBytes = Secp256k1Operations.serializeCompressed(Tm);
99+
System.arraycopy(tmBytes, 0, buffer, offset, 33);
100+
offset += 33;
101+
102+
// Commitments: {TrG_i, TrP_i}
103+
for (int i = 0; i < n; i++) {
104+
byte[] trgBytes = Secp256k1Operations.serializeCompressed(TrG.get(i));
105+
System.arraycopy(trgBytes, 0, buffer, offset, 33);
106+
offset += 33;
107+
108+
byte[] trpBytes = Secp256k1Operations.serializeCompressed(TrP.get(i));
109+
System.arraycopy(trpBytes, 0, buffer, offset, 33);
110+
offset += 33;
111+
}
112+
113+
// Context (tx_id)
114+
if (txId != null) {
115+
System.arraycopy(txId, 0, buffer, offset, 32);
116+
}
117+
118+
// SHA256 and reduce to scalar
119+
byte[] hash = Hashing.sha256().hashBytes(buffer).asBytes();
120+
return Secp256k1Operations.reduceToScalar(hash);
121+
}
122+
123+
124+
/**
125+
* Computes the challenge hash for the equality plaintext proof.
126+
*/
127+
public static byte[] plaintextEqualityProofChallenge(
128+
ECPoint c1,
129+
ECPoint c2,
130+
ECPoint publicKey,
131+
ECPoint mG,
132+
ECPoint T1,
133+
ECPoint T2,
134+
byte[] contextId
135+
) {
136+
String DOMAIN_SEPARATOR = "MPT_POK_PLAINTEXT_PROOF";
137+
// Calculate total size: domain + c1 + c2 + pk + [mG] + T1 + T2 + context
138+
int size = DOMAIN_SEPARATOR.length() + 33 + 33 + 33 + 33 + 33 + 32;
139+
if (mG != null) {
140+
size += 33;
141+
}
142+
143+
byte[] hashInput = new byte[size];
144+
int offset = 0;
145+
146+
// Domain separator
147+
byte[] domainBytes = DOMAIN_SEPARATOR.getBytes(StandardCharsets.UTF_8);
148+
System.arraycopy(domainBytes, 0, hashInput, offset, domainBytes.length);
149+
offset += domainBytes.length;
150+
151+
// C1, C2, Pk
152+
byte[] c1Bytes = Secp256k1Operations.serializeCompressed(c1);
153+
System.arraycopy(c1Bytes, 0, hashInput, offset, 33);
154+
offset += 33;
155+
156+
byte[] c2Bytes = Secp256k1Operations.serializeCompressed(c2);
157+
System.arraycopy(c2Bytes, 0, hashInput, offset, 33);
158+
offset += 33;
159+
160+
byte[] pkBytes = Secp256k1Operations.serializeCompressed(publicKey);
161+
System.arraycopy(pkBytes, 0, hashInput, offset, 33);
162+
offset += 33;
163+
164+
// mG (only if nonzero)
165+
if (mG != null) {
166+
byte[] mGBytes = Secp256k1Operations.serializeCompressed(mG);
167+
System.arraycopy(mGBytes, 0, hashInput, offset, 33);
168+
offset += 33;
169+
}
170+
171+
// T1, T2
172+
byte[] t1Bytes = Secp256k1Operations.serializeCompressed(T1);
173+
System.arraycopy(t1Bytes, 0, hashInput, offset, 33);
174+
offset += 33;
175+
176+
byte[] t2Bytes = Secp256k1Operations.serializeCompressed(T2);
177+
System.arraycopy(t2Bytes, 0, hashInput, offset, 33);
178+
offset += 33;
179+
180+
// Context ID (always required)
181+
System.arraycopy(contextId, 0, hashInput, offset, 32);
182+
183+
// SHA256 and reduce mod curve order
184+
byte[] sha256Hash = Hashing.sha256().hashBytes(hashInput).asBytes();
185+
BigInteger hashInt = new BigInteger(1, sha256Hash);
186+
BigInteger reduced = hashInt.mod(Secp256k1Operations.getCurveOrder());
187+
return Secp256k1Operations.toBytes32(reduced);
188+
}
189+
190+
public static byte[] pedersenLinkProofChallenge(
191+
ECPoint c1, ECPoint c2, ECPoint pk, ECPoint pcm,
192+
ECPoint T1, ECPoint T2, ECPoint T3,
193+
byte[] contextId
194+
) {
195+
String DOMAIN_SEPARATOR = "MPT_ELGAMAL_PEDERSEN_LINK";
196+
byte[] domainBytes = DOMAIN_SEPARATOR.getBytes(StandardCharsets.UTF_8);
197+
int contextLen = (contextId != null) ? 32 : 0;
198+
int bufferSize = domainBytes.length + (7 * 33) + contextLen;
199+
200+
byte[] buffer = new byte[bufferSize];
201+
int offset = 0;
202+
203+
System.arraycopy(domainBytes, 0, buffer, offset, domainBytes.length);
204+
offset += domainBytes.length;
205+
206+
offset = Secp256k1Operations.appendPoint(buffer, offset, c1);
207+
offset = Secp256k1Operations.appendPoint(buffer, offset, c2);
208+
offset = Secp256k1Operations.appendPoint(buffer, offset, pk);
209+
offset = Secp256k1Operations.appendPoint(buffer, offset, pcm);
210+
offset = Secp256k1Operations.appendPoint(buffer, offset, T1);
211+
offset = Secp256k1Operations.appendPoint(buffer, offset, T2);
212+
offset = Secp256k1Operations.appendPoint(buffer, offset, T3);
213+
214+
if (contextId != null) {
215+
System.arraycopy(contextId, 0, buffer, offset, 32);
216+
}
217+
218+
byte[] hash = Hashing.sha256().hashBytes(buffer).asBytes();
219+
return Secp256k1Operations.reduceToScalar(hash);
220+
}
221+
222+
}

xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/mpt/bulletproofs/LinkageProofType.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
* encode the same plaintext amount. The proof parameters are ordered differently
2828
* depending on whether we're proving linkage for an amount commitment or a balance commitment.</p>
2929
*
30-
* @see ElGamalPedersenLinkProofGenerator
30+
* @see PedersenLinkProofGenerator
3131
*/
3232
public enum LinkageProofType {
3333

xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/mpt/bulletproofs/ElGamalPedersenLinkProofGenerator.java renamed to xrpl4j-core/src/main/java/org/xrpl/xrpl4j/crypto/mpt/bulletproofs/PedersenLinkProofGenerator.java

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package org.xrpl.xrpl4j.crypto.mpt.bulletproofs;
22

33
import com.google.common.primitives.UnsignedLong;
4+
import org.xrpl.xrpl4j.crypto.keys.PublicKey;
45
import org.xrpl.xrpl4j.crypto.mpt.BlindingFactor;
56
import org.xrpl.xrpl4j.crypto.mpt.context.LinkProofContext;
67
import org.xrpl.xrpl4j.crypto.mpt.elgamal.ElGamalCiphertext;
7-
import org.xrpl.xrpl4j.crypto.mpt.keys.ElGamalPrivateKeyable;
8-
import org.xrpl.xrpl4j.crypto.mpt.keys.ElGamalPublicKey;
98
import org.xrpl.xrpl4j.crypto.mpt.wrapper.ElGamalPedersenLinkProof;
109
import org.xrpl.xrpl4j.crypto.mpt.wrapper.PedersenCommitment;
1110

@@ -59,13 +58,11 @@
5958
* <li>srho (32 bytes) - Response for Pedersen blinding factor</li>
6059
* </ul>
6160
*
62-
* @param <P> The type of private key this generator accepts, must extend {@link ElGamalPrivateKeyable}.
63-
*
6461
* @see LinkageProofType
6562
* @see ElGamalPedersenLinkProof
6663
* @see <a href="ConfidentialMPT_20260201.pdf">Spec Section 3.3.5</a>
6764
*/
68-
public interface ElGamalPedersenLinkProofGenerator<P extends ElGamalPrivateKeyable> {
65+
public interface PedersenLinkProofGenerator {
6966

7067
/**
7168
* Generates a proof linking an ElGamal ciphertext and a Pedersen commitment.
@@ -98,38 +95,12 @@ public interface ElGamalPedersenLinkProofGenerator<P extends ElGamalPrivateKeyab
9895
ElGamalPedersenLinkProof generateProof(
9996
LinkageProofType proofType,
10097
ElGamalCiphertext ciphertext,
101-
ElGamalPublicKey publicKey,
98+
PublicKey publicKey,
10299
PedersenCommitment commitment,
103100
UnsignedLong amount,
104101
BlindingFactor elGamalBlindingFactor,
105102
BlindingFactor pedersenBlindingFactor,
106103
LinkProofContext context
107104
);
108-
109-
/**
110-
* Verifies a proof linking an ElGamal ciphertext and a Pedersen commitment.
111-
*
112-
* <p>The {@link LinkageProofType} determines how the ciphertext and public key parameters
113-
* are interpreted for verification, matching the ordering used during proof generation.</p>
114-
*
115-
* @param proofType The type of linkage proof being verified.
116-
* @param proof The proof to verify.
117-
* @param ciphertext The ElGamal ciphertext.
118-
* @param publicKey The ElGamal public key.
119-
* @param commitment The Pedersen commitment.
120-
* @param context The context for domain separation. Can be null for no context.
121-
*
122-
* @return {@code true} if the proof is valid, {@code false} otherwise.
123-
*
124-
* @throws NullPointerException if proof, ciphertext, publicKey, or commitment is null.
125-
*/
126-
boolean verify(
127-
LinkageProofType proofType,
128-
ElGamalPedersenLinkProof proof,
129-
ElGamalCiphertext ciphertext,
130-
ElGamalPublicKey publicKey,
131-
PedersenCommitment commitment,
132-
LinkProofContext context
133-
);
134105
}
135106

0 commit comments

Comments
 (0)