From 305c6e4221e66fb31bc3e829ebbc32de73b3b78e Mon Sep 17 00:00:00 2001 From: federico Date: Thu, 9 Apr 2026 19:51:50 +0800 Subject: [PATCH] fix(crypto): harden shielded transaction safety and input validation --- .../tron/core/vm/PrecompiledContracts.java | 40 +++--- .../tron/common/zksnark/JLibrustzcash.java | 2 +- .../org/tron/core/services/RpcApiService.java | 133 ++++++++++++++---- .../ScanShieldedTRC20NotesByIvkServlet.java | 23 ++- .../zen/ShieldedTRC20ParametersBuilder.java | 3 + .../tron/core/zen/ZenTransactionBuilder.java | 5 +- .../core/services/RpcApiServicesTest.java | 85 +++++++++++ 7 files changed, 241 insertions(+), 50 deletions(-) diff --git a/actuator/src/main/java/org/tron/core/vm/PrecompiledContracts.java b/actuator/src/main/java/org/tron/core/vm/PrecompiledContracts.java index 634f7f2d3d1..9ba63cdf116 100644 --- a/actuator/src/main/java/org/tron/core/vm/PrecompiledContracts.java +++ b/actuator/src/main/java/org/tron/core/vm/PrecompiledContracts.java @@ -1148,8 +1148,8 @@ public abstract static class VerifyProof extends PrecompiledContract { new LibrustzcashParam.MerkleHashParams( i, UNCOMMITTED[i], UNCOMMITTED[i], UNCOMMITTED[i + 1])); } - } catch (Throwable any) { - logger.info("Initialize UNCOMMITTED array failed:{}", any.getMessage()); + } catch (ZksnarkException e) { + throw new RuntimeException("Failed to initialize UNCOMMITTED array", e); } } @@ -1258,7 +1258,7 @@ protected Pair insertLeaves( if (success) { return Pair.of(true, merge(DataWord.ONE().getData(), result)); } else { - return Pair.of(true, DataWord.ZERO().getData()); + return Pair.of(false, EMPTY_BYTE_ARRAY); } } } @@ -1282,6 +1282,10 @@ public Pair execute(byte[] data) { } boolean result; long ctx = JLibrustzcash.librustzcashSaplingVerificationCtxInit(); + if (ctx == 0) { + logger.info("VerifyMintProof: failed to init verification context"); + return Pair.of(false, EMPTY_BYTE_ARRAY); + } try { byte[] cm = new byte[32]; byte[] cv = new byte[32]; @@ -1328,7 +1332,7 @@ public Pair execute(byte[] data) { } finally { JLibrustzcash.librustzcashSaplingVerificationCtxFree(ctx); } - return Pair.of(true, DataWord.ZERO().getData()); + return Pair.of(false, EMPTY_BYTE_ARRAY); } } @@ -1477,6 +1481,10 @@ public Pair execute(byte[] data) { boolean withNoTimeout = countDownLatch.await(getCPUTimeLeftInNanoSecond(), TimeUnit.NANOSECONDS); + if (!withNoTimeout) { + futures.forEach(f -> f.cancel(true)); + return Pair.of(true, DataWord.ZERO().getData()); + } boolean checkResult = true; for (Future future : futures) { boolean eachTaskResult = future.get(); @@ -1497,7 +1505,7 @@ public Pair execute(byte[] data) { } logger.info("VerifyTransferProof exception: " + errorMsg); } - return Pair.of(true, DataWord.ZERO().getData()); + return Pair.of(false, EMPTY_BYTE_ARRAY); } private static class SaplingCheckSpendTask implements Callable { @@ -1527,17 +1535,13 @@ private static class SaplingCheckSpendTask implements Callable { @Override public Boolean call() throws ZksnarkException { - boolean result; try { - result = JLibrustzcash.librustzcashSaplingCheckSpendNew( + return JLibrustzcash.librustzcashSaplingCheckSpendNew( new LibrustzcashParam.CheckSpendNewParams(this.cv, this.anchor, this.nullifier, this.rk, this.zkproof, this.spendAuthSig, this.signHash)); - } catch (ZksnarkException e) { - throw e; } finally { countDownLatch.countDown(); } - return result; } } @@ -1561,17 +1565,13 @@ private static class SaplingCheckOutputTask implements Callable { @Override public Boolean call() throws ZksnarkException { - boolean result; try { - result = JLibrustzcash.librustzcashSaplingCheckOutputNew( + return JLibrustzcash.librustzcashSaplingCheckOutputNew( new LibrustzcashParam.CheckOutputNewParams(this.cv, this.cm, this.ephemeralKey, this.zkproof)); - } catch (ZksnarkException e) { - throw e; } finally { countDownLatch.countDown(); } - return result; } } @@ -1602,18 +1602,14 @@ private static class SaplingCheckBingdingSig implements Callable { @Override public Boolean call() throws ZksnarkException { - boolean result; try { - result = JLibrustzcash.librustzcashSaplingFinalCheckNew( + return JLibrustzcash.librustzcashSaplingFinalCheckNew( new LibrustzcashParam.FinalCheckNewParams(this.valueBalance, this.bindingSig, this.signHash, this.spendCvs, this.spendCvLen, this.receiveCvs, this.receiveCvLen)); - } catch (ZksnarkException e) { - throw e; } finally { countDownLatch.countDown(); } - return result; } } } @@ -1637,6 +1633,10 @@ public Pair execute(byte[] data) { } boolean result; long ctx = JLibrustzcash.librustzcashSaplingVerificationCtxInit(); + if (ctx == 0) { + logger.info("VerifyBurnProof: failed to init verification context"); + return Pair.of(false, EMPTY_BYTE_ARRAY); + } try { byte[] nullifier = new byte[32]; byte[] anchor = new byte[32]; diff --git a/chainbase/src/main/java/org/tron/common/zksnark/JLibrustzcash.java b/chainbase/src/main/java/org/tron/common/zksnark/JLibrustzcash.java index 3700d300411..448686171a1 100644 --- a/chainbase/src/main/java/org/tron/common/zksnark/JLibrustzcash.java +++ b/chainbase/src/main/java/org/tron/common/zksnark/JLibrustzcash.java @@ -29,7 +29,7 @@ @Slf4j public class JLibrustzcash { - private static Librustzcash INSTANCE = LibrustzcashWrapper.getInstance(); + private static final Librustzcash INSTANCE = LibrustzcashWrapper.getInstance(); public static void librustzcashZip32XskMaster(Zip32XskMasterParams params) { INSTANCE.librustzcashZip32XskMaster(params.getData(), params.getSize(), params.getM_bytes()); diff --git a/framework/src/main/java/org/tron/core/services/RpcApiService.java b/framework/src/main/java/org/tron/core/services/RpcApiService.java index 63e7ba03fc7..f5884f0f0ef 100755 --- a/framework/src/main/java/org/tron/core/services/RpcApiService.java +++ b/framework/src/main/java/org/tron/core/services/RpcApiService.java @@ -664,15 +664,21 @@ public void scanNoteByIvk(GrpcAPI.IvkDecryptParameters request, StreamObserver responseObserver) { long startNum = request.getStartBlockIndex(); long endNum = request.getEndBlockIndex(); + byte[] ivk = request.getIvk().toByteArray(); + if (ivk.length != 32) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("ivk must be 32 bytes").asRuntimeException()); + return; + } try { DecryptNotes decryptNotes = wallet - .scanNoteByIvk(startNum, endNum, request.getIvk().toByteArray()); + .scanNoteByIvk(startNum, endNum, ivk); responseObserver.onNext(decryptNotes); + responseObserver.onCompleted(); } catch (BadItemException | ZksnarkException e) { responseObserver.onError(getRunTimeException(e)); } - responseObserver.onCompleted(); } @Override @@ -680,18 +686,25 @@ public void scanAndMarkNoteByIvk(GrpcAPI.IvkDecryptAndMarkParameters request, StreamObserver responseObserver) { long startNum = request.getStartBlockIndex(); long endNum = request.getEndBlockIndex(); + byte[] ivk = request.getIvk().toByteArray(); + byte[] ak = request.getAk().toByteArray(); + byte[] nk = request.getNk().toByteArray(); + if (ivk.length != 32 || ak.length != 32 || nk.length != 32) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("ivk, ak, nk must each be 32 bytes") + .asRuntimeException()); + return; + } try { DecryptNotesMarked decryptNotes = wallet.scanAndMarkNoteByIvk(startNum, endNum, - request.getIvk().toByteArray(), - request.getAk().toByteArray(), - request.getNk().toByteArray()); + ivk, ak, nk); responseObserver.onNext(decryptNotes); + responseObserver.onCompleted(); } catch (BadItemException | ZksnarkException | InvalidProtocolBufferException | ItemNotFoundException e) { responseObserver.onError(getRunTimeException(e)); } - responseObserver.onCompleted(); } @Override @@ -699,24 +712,30 @@ public void scanNoteByOvk(GrpcAPI.OvkDecryptParameters request, StreamObserver responseObserver) { long startNum = request.getStartBlockIndex(); long endNum = request.getEndBlockIndex(); + byte[] ovk = request.getOvk().toByteArray(); + if (ovk.length != 32) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("ovk must be 32 bytes").asRuntimeException()); + return; + } try { DecryptNotes decryptNotes = wallet - .scanNoteByOvk(startNum, endNum, request.getOvk().toByteArray()); + .scanNoteByOvk(startNum, endNum, ovk); responseObserver.onNext(decryptNotes); + responseObserver.onCompleted(); } catch (BadItemException | ZksnarkException e) { responseObserver.onError(getRunTimeException(e)); } - responseObserver.onCompleted(); } @Override public void isSpend(NoteParameters request, StreamObserver responseObserver) { try { responseObserver.onNext(wallet.isSpend(request)); + responseObserver.onCompleted(); } catch (Exception e) { responseObserver.onError(getRunTimeException(e)); } - responseObserver.onCompleted(); } @Override @@ -728,17 +747,28 @@ public void scanShieldedTRC20NotesByIvk(IvkDecryptTRC20Parameters request, byte[] ivk = request.getIvk().toByteArray(); byte[] ak = request.getAk().toByteArray(); byte[] nk = request.getNk().toByteArray(); + if (ivk.length != 32 || ak.length != 32 || nk.length != 32) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("ivk, ak, nk must each be 32 bytes") + .asRuntimeException()); + return; + } + if (contractAddress.length != 21) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("contractAddress must be 21 bytes") + .asRuntimeException()); + return; + } ProtocolStringList topicsList = request.getEventsList(); try { responseObserver.onNext( wallet.scanShieldedTRC20NotesByIvk(startNum, endNum, contractAddress, ivk, ak, nk, topicsList)); - + responseObserver.onCompleted(); } catch (Exception e) { responseObserver.onError(getRunTimeException(e)); } - responseObserver.onCompleted(); } @Override @@ -748,15 +778,26 @@ public void scanShieldedTRC20NotesByOvk(OvkDecryptTRC20Parameters request, long endNum = request.getEndBlockIndex(); byte[] contractAddress = request.getShieldedTRC20ContractAddress().toByteArray(); byte[] ovk = request.getOvk().toByteArray(); + if (ovk.length != 32) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("ovk must be 32 bytes").asRuntimeException()); + return; + } + if (contractAddress.length != 21) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("contractAddress must be 21 bytes") + .asRuntimeException()); + return; + } ProtocolStringList topicList = request.getEventsList(); try { responseObserver .onNext(wallet .scanShieldedTRC20NotesByOvk(startNum, endNum, ovk, contractAddress, topicList)); + responseObserver.onCompleted(); } catch (Exception e) { responseObserver.onError(getRunTimeException(e)); } - responseObserver.onCompleted(); } @Override @@ -2270,10 +2311,16 @@ public void scanNoteByIvk(GrpcAPI.IvkDecryptParameters request, StreamObserver responseObserver) { long startNum = request.getStartBlockIndex(); long endNum = request.getEndBlockIndex(); + byte[] ivk = request.getIvk().toByteArray(); + if (ivk.length != 32) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("ivk must be 32 bytes").asRuntimeException()); + return; + } try { DecryptNotes decryptNotes = wallet - .scanNoteByIvk(startNum, endNum, request.getIvk().toByteArray()); + .scanNoteByIvk(startNum, endNum, ivk); responseObserver.onNext(decryptNotes); } catch (BadItemException | ZksnarkException e) { responseObserver.onError(getRunTimeException(e)); @@ -2288,12 +2335,19 @@ public void scanAndMarkNoteByIvk(GrpcAPI.IvkDecryptAndMarkParameters request, StreamObserver responseObserver) { long startNum = request.getStartBlockIndex(); long endNum = request.getEndBlockIndex(); + byte[] ivk = request.getIvk().toByteArray(); + byte[] ak = request.getAk().toByteArray(); + byte[] nk = request.getNk().toByteArray(); + if (ivk.length != 32 || ak.length != 32 || nk.length != 32) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("ivk, ak, nk must each be 32 bytes") + .asRuntimeException()); + return; + } try { DecryptNotesMarked decryptNotes = wallet.scanAndMarkNoteByIvk(startNum, endNum, - request.getIvk().toByteArray(), - request.getAk().toByteArray(), - request.getNk().toByteArray()); + ivk, ak, nk); responseObserver.onNext(decryptNotes); } catch (BadItemException | ZksnarkException | InvalidProtocolBufferException | ItemNotFoundException e) { @@ -2308,10 +2362,16 @@ public void scanNoteByOvk(GrpcAPI.OvkDecryptParameters request, StreamObserver responseObserver) { long startNum = request.getStartBlockIndex(); long endNum = request.getEndBlockIndex(); + byte[] ovk = request.getOvk().toByteArray(); + if (ovk.length != 32) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("ovk must be 32 bytes").asRuntimeException()); + return; + } try { DecryptNotes decryptNotes = wallet - .scanNoteByOvk(startNum, endNum, request.getOvk().toByteArray()); + .scanNoteByOvk(startNum, endNum, ovk); responseObserver.onNext(decryptNotes); } catch (BadItemException | ZksnarkException e) { responseObserver.onError(getRunTimeException(e)); @@ -2414,13 +2474,25 @@ public void scanShieldedTRC20NotesByIvk( StreamObserver responseObserver) { long startNum = request.getStartBlockIndex(); long endNum = request.getEndBlockIndex(); + byte[] contractAddress = request.getShieldedTRC20ContractAddress().toByteArray(); + byte[] ivk = request.getIvk().toByteArray(); + byte[] ak = request.getAk().toByteArray(); + byte[] nk = request.getNk().toByteArray(); + if (ivk.length != 32 || ak.length != 32 || nk.length != 32) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("ivk, ak, nk must each be 32 bytes") + .asRuntimeException()); + return; + } + if (contractAddress.length != 21) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("contractAddress must be 21 bytes") + .asRuntimeException()); + return; + } try { DecryptNotesTRC20 decryptNotes = wallet.scanShieldedTRC20NotesByIvk(startNum, endNum, - request.getShieldedTRC20ContractAddress().toByteArray(), - request.getIvk().toByteArray(), - request.getAk().toByteArray(), - request.getNk().toByteArray(), - request.getEventsList()); + contractAddress, ivk, ak, nk, request.getEventsList()); responseObserver.onNext(decryptNotes); } catch (BadItemException | ZksnarkException e) { responseObserver.onError(getRunTimeException(e)); @@ -2440,11 +2512,22 @@ public void scanShieldedTRC20NotesByOvk( StreamObserver responseObserver) { long startNum = request.getStartBlockIndex(); long endNum = request.getEndBlockIndex(); + byte[] contractAddress = request.getShieldedTRC20ContractAddress().toByteArray(); + byte[] ovk = request.getOvk().toByteArray(); + if (ovk.length != 32) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("ovk must be 32 bytes").asRuntimeException()); + return; + } + if (contractAddress.length != 21) { + responseObserver.onError(Status.INVALID_ARGUMENT + .withDescription("contractAddress must be 21 bytes") + .asRuntimeException()); + return; + } try { DecryptNotesTRC20 decryptNotes = wallet.scanShieldedTRC20NotesByOvk(startNum, endNum, - request.getOvk().toByteArray(), - request.getShieldedTRC20ContractAddress().toByteArray(), - request.getEventsList()); + ovk, contractAddress, request.getEventsList()); responseObserver.onNext(decryptNotes); } catch (Exception e) { responseObserver.onError(getRunTimeException(e)); diff --git a/framework/src/main/java/org/tron/core/services/http/ScanShieldedTRC20NotesByIvkServlet.java b/framework/src/main/java/org/tron/core/services/http/ScanShieldedTRC20NotesByIvkServlet.java index 2b52883f490..7155e31e2b5 100644 --- a/framework/src/main/java/org/tron/core/services/http/ScanShieldedTRC20NotesByIvkServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/ScanShieldedTRC20NotesByIvkServlet.java @@ -70,11 +70,30 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { String ak = request.getParameter("ak"); String nk = request.getParameter("nk"); + String events = request.getParameter("events"); + IvkDecryptTRC20Parameters.Builder builder = IvkDecryptTRC20Parameters.newBuilder(); + if (events != null && !events.isEmpty()) { + JSONArray eventsArray = JSONArray.parseArray(events); + for (int i = 0; i < eventsArray.size(); i++) { + builder.addEvents(eventsArray.getString(i)); + } + } + + byte[] contractAddressBytes = ByteArray.fromHexString(contractAddress); + byte[] ivkBytes = ByteArray.fromHexString(ivk); + byte[] akBytes = ByteArray.fromHexString(ak); + byte[] nkBytes = ByteArray.fromHexString(nk); + if (ivkBytes.length != 32 || akBytes.length != 32 || nkBytes.length != 32) { + throw new IllegalArgumentException("ivk, ak, nk must each be 32 bytes"); + } + if (contractAddressBytes.length != 21) { + throw new IllegalArgumentException("contractAddress must be 21 bytes"); + } GrpcAPI.DecryptNotesTRC20 notes = wallet .scanShieldedTRC20NotesByIvk(startNum, endNum, - ByteArray.fromHexString(contractAddress), ByteArray.fromHexString(ivk), - ByteArray.fromHexString(ak), ByteArray.fromHexString(nk), null); + contractAddressBytes, ivkBytes, akBytes, nkBytes, + builder.getEventsList()); response.getWriter().println(convertOutput(notes, visible)); } catch (Exception e) { Util.processError(e, response); diff --git a/framework/src/main/java/org/tron/core/zen/ShieldedTRC20ParametersBuilder.java b/framework/src/main/java/org/tron/core/zen/ShieldedTRC20ParametersBuilder.java index 95e4eeb0ccd..53e8fc8c2e3 100644 --- a/framework/src/main/java/org/tron/core/zen/ShieldedTRC20ParametersBuilder.java +++ b/framework/src/main/java/org/tron/core/zen/ShieldedTRC20ParametersBuilder.java @@ -251,6 +251,9 @@ public ShieldedTRC20Parameters build(boolean withAsk) throws ZksnarkException { ShieldedTRC20Parameters shieldedTRC20Parameters; long ctx = JLibrustzcash.librustzcashSaplingProvingCtxInit(); + if (ctx == 0) { + throw new ZksnarkException("Failed to initialize proving context"); + } try { switch (shieldedTRC20ParametersType) { case MINT: diff --git a/framework/src/main/java/org/tron/core/zen/ZenTransactionBuilder.java b/framework/src/main/java/org/tron/core/zen/ZenTransactionBuilder.java index 2e531e44d44..ff002dd5192 100644 --- a/framework/src/main/java/org/tron/core/zen/ZenTransactionBuilder.java +++ b/framework/src/main/java/org/tron/core/zen/ZenTransactionBuilder.java @@ -137,6 +137,9 @@ public TransactionCapsule build() throws ZksnarkException { public TransactionCapsule build(boolean withAsk) throws ZksnarkException { TransactionCapsule transactionCapsule; long ctx = JLibrustzcash.librustzcashSaplingProvingCtxInit(); + if (ctx == 0) { + throw new ZksnarkException("Failed to initialize proving context"); + } try { // Create SpendDescriptions @@ -176,8 +179,6 @@ public TransactionCapsule build(boolean withAsk) throws ZksnarkException { bindingSig) ); contractBuilder.setBindingSignature(ByteString.copyFrom(bindingSig)); - } catch (ZksnarkException e) { - throw e; } finally { JLibrustzcash.librustzcashSaplingProvingCtxFree(ctx); } diff --git a/framework/src/test/java/org/tron/core/services/RpcApiServicesTest.java b/framework/src/test/java/org/tron/core/services/RpcApiServicesTest.java index f40ec48e035..f4da82e1df3 100644 --- a/framework/src/test/java/org/tron/core/services/RpcApiServicesTest.java +++ b/framework/src/test/java/org/tron/core/services/RpcApiServicesTest.java @@ -9,6 +9,8 @@ import com.google.protobuf.ByteString; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; import java.io.IOException; import java.util.Objects; import java.util.concurrent.ExecutorService; @@ -493,9 +495,11 @@ public void testGetBurnTrx() { @Test public void testScanNoteByIvk() { + ByteString dummyKey32 = ByteString.copyFrom(new byte[32]); IvkDecryptParameters message = IvkDecryptParameters.newBuilder() .setStartBlockIndex(0) .setEndBlockIndex(1) + .setIvk(dummyKey32) .build(); assertNotNull(blockingStubFull.scanNoteByIvk(message)); assertNotNull(blockingStubSolidity.scanNoteByIvk(message)); @@ -504,9 +508,13 @@ public void testScanNoteByIvk() { @Test public void testScanAndMarkNoteByIvk() { + ByteString dummyKey32 = ByteString.copyFrom(new byte[32]); IvkDecryptAndMarkParameters message = IvkDecryptAndMarkParameters.newBuilder() .setStartBlockIndex(0) .setEndBlockIndex(1) + .setIvk(dummyKey32) + .setAk(dummyKey32) + .setNk(dummyKey32) .build(); assertNotNull(blockingStubFull.scanAndMarkNoteByIvk(message)); assertNotNull(blockingStubSolidity.scanAndMarkNoteByIvk(message)); @@ -536,9 +544,15 @@ public void test08ScanNoteByOvk() { @Test public void testScanShieldedTRC20NotesByIvk() { + ByteString dummyKey32 = ByteString.copyFrom(new byte[32]); + ByteString dummyAddr21 = ByteString.copyFrom(new byte[21]); IvkDecryptTRC20Parameters message = IvkDecryptTRC20Parameters.newBuilder() .setStartBlockIndex(1) .setEndBlockIndex(10) + .setIvk(dummyKey32) + .setAk(dummyKey32) + .setNk(dummyKey32) + .setShieldedTRC20ContractAddress(dummyAddr21) .build(); assertNotNull(blockingStubFull.scanShieldedTRC20NotesByIvk(message)); assertNotNull(blockingStubSolidity.scanShieldedTRC20NotesByIvk(message)); @@ -547,15 +561,86 @@ public void testScanShieldedTRC20NotesByIvk() { @Test public void testScanShieldedTRC20NotesByOvk() { + ByteString dummyKey32 = ByteString.copyFrom(new byte[32]); + ByteString dummyAddr21 = ByteString.copyFrom(new byte[21]); OvkDecryptTRC20Parameters message = OvkDecryptTRC20Parameters.newBuilder() .setStartBlockIndex(1) .setEndBlockIndex(10) + .setOvk(dummyKey32) + .setShieldedTRC20ContractAddress(dummyAddr21) .build(); assertNotNull(blockingStubFull.scanShieldedTRC20NotesByOvk(message)); assertNotNull(blockingStubSolidity.scanShieldedTRC20NotesByOvk(message)); assertNotNull(blockingStubPBFT.scanShieldedTRC20NotesByOvk(message)); } + @Test + public void testScanNoteByIvkRejectsInvalidLength() { + IvkDecryptParameters message = IvkDecryptParameters.newBuilder() + .setStartBlockIndex(0) + .setEndBlockIndex(1) + .setIvk(ByteString.copyFrom(new byte[31])) + .build(); + try { + blockingStubFull.scanNoteByIvk(message); + Assert.fail("Expected INVALID_ARGUMENT"); + } catch (StatusRuntimeException e) { + Assert.assertEquals(Status.Code.INVALID_ARGUMENT, e.getStatus().getCode()); + } + } + + @Test + public void testScanAndMarkNoteByIvkRejectsInvalidLength() { + ByteString dummyKey32 = ByteString.copyFrom(new byte[32]); + IvkDecryptAndMarkParameters message = IvkDecryptAndMarkParameters.newBuilder() + .setStartBlockIndex(0) + .setEndBlockIndex(1) + .setIvk(ByteString.copyFrom(new byte[31])) + .setAk(dummyKey32) + .setNk(dummyKey32) + .build(); + try { + blockingStubFull.scanAndMarkNoteByIvk(message); + Assert.fail("Expected INVALID_ARGUMENT"); + } catch (StatusRuntimeException e) { + Assert.assertEquals(Status.Code.INVALID_ARGUMENT, e.getStatus().getCode()); + } + } + + @Test + public void testScanShieldedTRC20NotesByIvkRejectsInvalidLength() { + IvkDecryptTRC20Parameters message = IvkDecryptTRC20Parameters.newBuilder() + .setStartBlockIndex(1) + .setEndBlockIndex(10) + .setIvk(ByteString.copyFrom(new byte[31])) + .setAk(ByteString.copyFrom(new byte[32])) + .setNk(ByteString.copyFrom(new byte[32])) + .setShieldedTRC20ContractAddress(ByteString.copyFrom(new byte[21])) + .build(); + try { + blockingStubFull.scanShieldedTRC20NotesByIvk(message); + Assert.fail("Expected INVALID_ARGUMENT"); + } catch (StatusRuntimeException e) { + Assert.assertEquals(Status.Code.INVALID_ARGUMENT, e.getStatus().getCode()); + } + } + + @Test + public void testScanShieldedTRC20NotesByOvkRejectsInvalidLength() { + OvkDecryptTRC20Parameters message = OvkDecryptTRC20Parameters.newBuilder() + .setStartBlockIndex(1) + .setEndBlockIndex(10) + .setOvk(ByteString.copyFrom(new byte[31])) + .setShieldedTRC20ContractAddress(ByteString.copyFrom(new byte[21])) + .build(); + try { + blockingStubFull.scanShieldedTRC20NotesByOvk(message); + Assert.fail("Expected INVALID_ARGUMENT"); + } catch (StatusRuntimeException e) { + Assert.assertEquals(Status.Code.INVALID_ARGUMENT, e.getStatus().getCode()); + } + } + // @Test // public void testIsShieldedTRC20ContractNoteSpent() { // NfTRC20Parameters message = NfTRC20Parameters.newBuilder().build();