Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,6 @@ public TransactionSignWeight getTransactionSignWeight(Transaction trx) {
.isECKeyCryptoEngine(), trx.getRawData().toByteArray()), approveList);
}
if (trx.getPqAuthSigCount() > 0) {
if (!chainBaseManager.getDynamicPropertiesStore().isAnyPqSchemeAllowed()) {
throw new PermissionException(
"pq_auth_sig not allowed: no post-quantum scheme is activated");
}
try {
long pqWeight = TransactionCapsule.validatePQSignatureGetWeight(trx, permission,
chainBaseManager.getDynamicPropertiesStore(), approveList);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,6 @@ public boolean validateSignature(DynamicPropertiesStore dynamicPropertiesStore,
}

if (hasPq) {
if (!dynamicPropertiesStore.isAnyPqSchemeAllowed()) {
throw new ValidateSignatureException(
"pq_auth_sig not allowed: no post-quantum scheme is activated");
}
return validatePQSignature(dynamicPropertiesStore, witnessPermissionAddress,
header.getPqAuthSig());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ public static boolean validateSignature(Transaction transaction,
List<ByteString> approveList = new ArrayList<>();
long weight = checkWeight(permission, transaction.getSignatureList(), hash, approveList);

if (transaction.getPqAuthSigCount() > 0 && dynamicPropertiesStore.isAnyPqSchemeAllowed()) {
if (transaction.getPqAuthSigCount() > 0) {
try {
weight = StrictMathWrapper.addExact(weight,
validatePQSignatureGetWeight(transaction, permission, dynamicPropertiesStore,
Expand Down Expand Up @@ -767,18 +767,12 @@ public static long validatePQSignatureGetWeight(Transaction transaction, Permiss
if (!signedAddresses.add(addrBs)) {
throw new PermissionException(encode58Check(derivedAddr) + " has signed twice!");
}
Key matched = null;
for (Key k : permission.getKeysList()) {
if (k.getAddress().equals(addrBs)) {
matched = k;
break;
}
}
if (matched == null) {
throw new PermissionException(
"pq_auth_sig public key derives to " + encode58Check(derivedAddr)
+ " but it is not contained of permission.");
}
Key matched = permission.getKeysList().stream()
.filter(k -> k.getAddress().equals(addrBs))
.findFirst()
.orElseThrow(() -> new PermissionException(
"pq_auth_sig public key derives to " + encode58Check(derivedAddr)
+ " but it is not contained of permission."));
if (!PQSchemeRegistry.verify(scheme, pk, digest, sig)) {
throw new SignatureException("pq sig invalid");
}
Expand Down
6 changes: 6 additions & 0 deletions framework/src/main/java/org/tron/core/Wallet.java
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,12 @@ public GrpcAPI.Return broadcastTransaction(Transaction signedTransaction) {
.setMessage(ByteString.copyFromUtf8("Server busy.")).build();
}

if (dbManager.isPqPendingFull(signedTransaction)) {
logger.warn("Broadcast transaction {} has failed, PQ pending pool full.", txID);
return builder.setResult(false).setCode(response_code.SERVER_BUSY)
.setMessage(ByteString.copyFromUtf8("PQ pending pool is full.")).build();
}

if (trxCacheEnable) {
if (dbManager.getTransactionIdCache().getIfPresent(txID) != null) {
logger.warn("Broadcast transaction {} has failed, it already exists.", txID);
Expand Down
9 changes: 7 additions & 2 deletions framework/src/main/java/org/tron/core/db/Manager.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import static org.tron.common.math.Maths.min;
import static org.tron.common.utils.Commons.adjustBalance;
import static org.tron.core.Constant.TRANSACTION_MAX_BYTE_SIZE;
import static org.tron.core.exception.BadBlockException.TypeEnum.CALC_MERKLE_ROOT_FAILED;
import static org.tron.protos.Protocol.Transaction.Contract.ContractType.TransferContract;
import static org.tron.protos.Protocol.Transaction.Result.contractResult.SUCCESS;

Expand Down Expand Up @@ -50,7 +49,6 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.tron.api.GrpcAPI;
import org.tron.api.GrpcAPI.TransactionInfoList;
import org.tron.common.args.GenesisBlock;
import org.tron.common.bloom.Bloom;
Expand Down Expand Up @@ -936,6 +934,8 @@ public boolean pushTransaction(final TransactionCapsule trx)
}
if (isPQTransaction(trx.getInstance())
&& pqTransInPendingCounts.get() >= pqTransInPendingMaxCounts) {
logger.warn("pushTransaction {} rejected: PQ pending pool full ({}/{}).",
trx.getTransactionId(), pqTransInPendingCounts.get(), pqTransInPendingMaxCounts);
return false;
}
if (!session.valid()) {
Expand Down Expand Up @@ -2178,6 +2178,11 @@ public boolean isTooManyPending() {
return getCachedTransactionSize() > maxTransactionPendingSize;
}

public boolean isPqPendingFull(Protocol.Transaction transaction) {
return isPQTransaction(transaction)
&& pqTransInPendingCounts.get() >= pqTransInPendingMaxCounts;
}

private void preValidateTransactionSign(List<TransactionCapsule> txs)
throws InterruptedException, ValidateSignatureException {
int transSize = txs.size();
Expand Down
26 changes: 26 additions & 0 deletions framework/src/test/java/org/tron/core/WalletMockTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.tron.core;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
Expand Down Expand Up @@ -568,6 +569,31 @@ private void mockEnv(Wallet wallet, TronException tronException) throws Exceptio
field3.set(wallet, false);
}

@Test
public void testBroadcastTransactionPqPendingFull() throws Exception {
Wallet wallet = new Wallet();
injectTotalSignNum(wallet, 5);

TronNetDelegate tronNetDelegateMock = mock(TronNetDelegate.class);
Manager managerMock = mock(Manager.class);
when(tronNetDelegateMock.isBlockUnsolidified()).thenReturn(false);
when(managerMock.isTooManyPending()).thenReturn(false);
when(managerMock.isPqPendingFull(any())).thenReturn(true);

Field field = wallet.getClass().getDeclaredField("tronNetDelegate");
field.setAccessible(true);
field.set(wallet, tronNetDelegateMock);
Field field2 = wallet.getClass().getDeclaredField("dbManager");
field2.setAccessible(true);
field2.set(wallet, managerMock);

GrpcAPI.Return ret = wallet.broadcastTransaction(Protocol.Transaction.newBuilder().build());
assertEquals(GrpcAPI.Return.response_code.SERVER_BUSY, ret.getCode());
assertFalse(ret.getResult());
assertTrue(ret.getMessage().toStringUtf8().contains("PQ"));
Mockito.verify(managerMock, Mockito.never()).pushTransaction(any());
}

@Test
public void testBroadcastTransactionValidateSignatureException() throws Exception {
try (MockedConstruction<TransactionMessage> mocked = mockConstruction(TransactionMessage.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import ch.qos.logback.core.read.ListAppender;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
Expand All @@ -30,6 +31,8 @@
import org.tron.common.utils.StringUtil;
import org.tron.core.Wallet;
import org.tron.core.config.args.Args;
import org.tron.core.exception.PermissionException;
import org.tron.core.exception.SignatureFormatException;
import org.tron.core.exception.ValidateSignatureException;
import org.tron.protos.Protocol.AccountType;
import org.tron.protos.Protocol.Key;
Expand Down Expand Up @@ -154,8 +157,6 @@ public void toStringRendersPqSignWithEcdsa() {
s.contains("sign=") && s.contains("pq_sign(FN_DSA_512)="));
}

// --------------------- FN-DSA pq_auth_sig verification (V2) ---------------------

private static final String PQ_OWNER_HEX = "41abd4b9367799eaa3197fecb144eb71de1e049abc";
private static final String PQ_TO_HEX = "41548794500882809695a8a687866e76d4271a1abc";

Expand Down Expand Up @@ -754,4 +755,122 @@ public void toStringRendersEmptyContractList() {
String rendered = new TransactionCapsule(empty).toString();
Assert.assertTrue(rendered.contains("contract list is empty"));
}

@Test
public void pqWrongKeyLengthThrows() throws Exception {
dbManager.getDynamicPropertiesStore().saveAllowFnDsa512(1L);
Transaction tx = buildTransferTx(PQ_OWNER_HEX, 0);
// Permission with one slot so count check passes; length check fires first.
Permission permission = Permission.newBuilder()
.setType(PermissionType.Owner).setThreshold(1)
.addKeys(Key.newBuilder()
.setAddress(ByteString.copyFrom(new byte[20])).setWeight(1))
.build();
PQAuthSig badLen = PQAuthSig.newBuilder()
.setScheme(PQScheme.FN_DSA_512)
.setPublicKey(ByteString.copyFrom(new byte[1])) // 1 != 896
.setSignature(ByteString.copyFrom(new byte[1]))
.build();
Transaction withBad = tx.toBuilder().addPqAuthSig(badLen).build();
try {
TransactionCapsule.validatePQSignatureGetWeight(withBad, permission,
dbManager.getDynamicPropertiesStore(), new ArrayList<>());
Assert.fail("wrong pk length should throw SignatureFormatException");
} catch (SignatureFormatException e) {
Assert.assertTrue(e.getMessage().contains("length mismatch"));
}
}

@Test
public void pqWeightOverflowThrows() throws Exception {
dbManager.getDynamicPropertiesStore().saveAllowFnDsa512(1L);
FNDSA512 kp1 = new FNDSA512();
FNDSA512 kp2 = new FNDSA512();
Transaction tx = buildTransferTx(PQ_OWNER_HEX, 0);
byte[] txid = txId(tx);
byte[] sig1 = FNDSA512.sign(kp1.getPrivateKey(), txid);
byte[] sig2 = FNDSA512.sign(kp2.getPrivateKey(), txid);
Permission permission = Permission.newBuilder()
.setType(PermissionType.Owner).setThreshold(1)
.addKeys(Key.newBuilder()
.setAddress(ByteString.copyFrom(
PQSchemeRegistry.computeAddress(PQScheme.FN_DSA_512, kp1.getPublicKey())))
.setWeight(Long.MAX_VALUE))
.addKeys(Key.newBuilder()
.setAddress(ByteString.copyFrom(
PQSchemeRegistry.computeAddress(PQScheme.FN_DSA_512, kp2.getPublicKey())))
.setWeight(Long.MAX_VALUE))
.build();
Transaction signed = tx.toBuilder()
.addPqAuthSig(PQAuthSig.newBuilder()
.setScheme(PQScheme.FN_DSA_512)
.setPublicKey(ByteString.copyFrom(kp1.getPublicKey()))
.setSignature(ByteString.copyFrom(sig1)))
.addPqAuthSig(PQAuthSig.newBuilder()
.setScheme(PQScheme.FN_DSA_512)
.setPublicKey(ByteString.copyFrom(kp2.getPublicKey()))
.setSignature(ByteString.copyFrom(sig2)))
.build();
try {
TransactionCapsule.validatePQSignatureGetWeight(signed, permission,
dbManager.getDynamicPropertiesStore(), new ArrayList<>());
Assert.fail("Long.MAX_VALUE + Long.MAX_VALUE should throw PermissionException");
} catch (PermissionException e) {
Assert.assertTrue(e.getMessage().contains("weight overflow"));
}
}

@Test
public void pqKeyNotInPermissionThrows() throws Exception {
dbManager.getDynamicPropertiesStore().saveAllowFnDsa512(1L);
FNDSA512 knownKp = new FNDSA512();
FNDSA512 strangerKp = new FNDSA512();
Transaction tx = buildTransferTx(PQ_OWNER_HEX, 0);
byte[] sig = FNDSA512.sign(strangerKp.getPrivateKey(), txId(tx));
Permission permission = Permission.newBuilder()
.setType(PermissionType.Owner).setThreshold(1)
.addKeys(Key.newBuilder()
.setAddress(ByteString.copyFrom(
PQSchemeRegistry.computeAddress(PQScheme.FN_DSA_512, knownKp.getPublicKey())))
.setWeight(1))
.build();
Transaction signed = tx.toBuilder()
.addPqAuthSig(PQAuthSig.newBuilder()
.setScheme(PQScheme.FN_DSA_512)
.setPublicKey(ByteString.copyFrom(strangerKp.getPublicKey()))
.setSignature(ByteString.copyFrom(sig)))
.build();
try {
TransactionCapsule.validatePQSignatureGetWeight(signed, permission,
dbManager.getDynamicPropertiesStore(), new ArrayList<>());
Assert.fail("key not in permission should throw PermissionException");
} catch (PermissionException e) {
Assert.assertTrue(e.getMessage().contains("not contained of permission"));
}
}

@Test
public void pqKeyFoundReturnsWeight() throws Exception {
dbManager.getDynamicPropertiesStore().saveAllowFnDsa512(1L);
FNDSA512 kp = new FNDSA512();
Transaction tx = buildTransferTx(PQ_OWNER_HEX, 0);
byte[] sig = FNDSA512.sign(kp.getPrivateKey(), txId(tx));
long expectedWeight = 7L;
Permission permission = Permission.newBuilder()
.setType(PermissionType.Owner).setThreshold(1)
.addKeys(Key.newBuilder()
.setAddress(ByteString.copyFrom(
PQSchemeRegistry.computeAddress(PQScheme.FN_DSA_512, kp.getPublicKey())))
.setWeight(expectedWeight))
.build();
Transaction signed = tx.toBuilder()
.addPqAuthSig(PQAuthSig.newBuilder()
.setScheme(PQScheme.FN_DSA_512)
.setPublicKey(ByteString.copyFrom(kp.getPublicKey()))
.setSignature(ByteString.copyFrom(sig)))
.build();
long weight = TransactionCapsule.validatePQSignatureGetWeight(signed, permission,
dbManager.getDynamicPropertiesStore(), new ArrayList<>());
Assert.assertEquals(expectedWeight, weight);
}
}
37 changes: 37 additions & 0 deletions framework/src/test/java/org/tron/core/db/ManagerMockTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -702,6 +703,42 @@ public void testSwitchForkPassesValidSignatureBlockToApply() {
verify(goodBlock, atLeastOnce()).setSwitch(true);
}

@SneakyThrows
@Test
public void testPushTransactionPqPendingFull() {
Manager dbManager = spy(new Manager());

ChainBaseManager cbm = mock(ChainBaseManager.class);
DynamicPropertiesStore dps = mock(DynamicPropertiesStore.class);
AccountStore accountStore = mock(AccountStore.class);
when(cbm.getDynamicPropertiesStore()).thenReturn(dps);
when(cbm.getAccountStore()).thenReturn(accountStore);
setField(dbManager, "chainBaseManager", cbm);

BalanceContract.TransferContract tc = BalanceContract.TransferContract.newBuilder()
.setOwnerAddress(ByteString.copyFromUtf8("aaa"))
.setToAddress(ByteString.copyFromUtf8("bbb"))
.setAmount(1)
.build();
Protocol.Transaction txn = Protocol.Transaction.newBuilder()
.setRawData(Protocol.Transaction.raw.newBuilder()
.addContract(Protocol.Transaction.Contract.newBuilder()
.setParameter(com.google.protobuf.Any.pack(tc))
.setType(Protocol.Transaction.Contract.ContractType.TransferContract)))
.addPqAuthSig(Protocol.PQAuthSig.getDefaultInstance())
.build();
TransactionCapsule trxMock = mock(TransactionCapsule.class);
when(trxMock.getInstance()).thenReturn(txn);
when(trxMock.validateSignature(
any(AccountStore.class), any(DynamicPropertiesStore.class))).thenReturn(true);
when(trxMock.getTransactionId()).thenReturn(Sha256Hash.ZERO_HASH);

setField(dbManager, "pqTransInPendingCounts", new AtomicInteger(Integer.MAX_VALUE));

boolean result = dbManager.pushTransaction(trxMock);
Assert.assertFalse(result);
}

private static void setField(Object target, String name, Object value) throws Exception {
Field f = target.getClass().getSuperclass() != null
? findField(target.getClass(), name)
Expand Down
Loading