Skip to content
Merged
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
12 changes: 7 additions & 5 deletions actuator/src/main/java/org/tron/core/actuator/VMActuator.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.tron.core.db.TransactionContext;
import org.tron.core.exception.ContractExeException;
import org.tron.core.exception.ContractValidateException;
import org.tron.core.store.DynamicPropertiesStore;
import org.tron.core.utils.TransactionUtil;
import org.tron.core.vm.EnergyCost;
import org.tron.core.vm.LogInfoTriggerParser;
Expand Down Expand Up @@ -177,7 +178,8 @@ public void execute(Object object) throws ContractExeException {
ProgramResult result = context.getProgramResult();
try {
if (program != null) {
if (null != blockCap && blockCap.generatedByMyself && blockCap.hasWitnessSignature()
if (null != blockCap && blockCap.generatedByMyself && blockCap.hasWitnessSignature(context.getStoreFactory().getChainBaseManager()
.getDynamicPropertiesStore())
&& null != TransactionUtil.getContractRet(trx)
&& contractResult.OUT_OF_TIME == TransactionUtil.getContractRet(trx)) {
result = program.getResult();
Expand Down Expand Up @@ -400,7 +402,7 @@ private void create()

long thisTxCPULimitInUs = calculateCpuLimitInUs(isConstantCall,
rootRepository.getDynamicPropertiesStore().getMaxCpuTimeOfOneTx(),
getCpuLimitInUsRatio(), CommonParameter.getInstance().getConstantCallTimeoutMs());
getCpuLimitInUsRatio(rootRepository.getDynamicPropertiesStore()), CommonParameter.getInstance().getConstantCallTimeoutMs());
long vmStartInUs = System.nanoTime() / VMConstant.ONE_THOUSAND;
long vmShouldEndInUs = vmStartInUs + thisTxCPULimitInUs;
ProgramInvoke programInvoke = ProgramInvokeFactory
Expand Down Expand Up @@ -514,7 +516,7 @@ private void call()

long thisTxCPULimitInUs = calculateCpuLimitInUs(isConstantCall,
rootRepository.getDynamicPropertiesStore().getMaxCpuTimeOfOneTx(),
getCpuLimitInUsRatio(), CommonParameter.getInstance().getConstantCallTimeoutMs());
getCpuLimitInUsRatio(rootRepository.getDynamicPropertiesStore()), CommonParameter.getInstance().getConstantCallTimeoutMs());
long vmStartInUs = System.nanoTime() / VMConstant.ONE_THOUSAND;
long vmShouldEndInUs = vmStartInUs + thisTxCPULimitInUs;
ProgramInvoke programInvoke = ProgramInvokeFactory
Expand Down Expand Up @@ -666,14 +668,14 @@ public void checkTokenValueAndId(long tokenValue, long tokenId) throws ContractV
}


private double getCpuLimitInUsRatio() {
private double getCpuLimitInUsRatio(DynamicPropertiesStore dynamicPropertiesStore) {

double cpuLimitRatio;

if (ExecutorType.ET_NORMAL_TYPE == executorType) {
// self witness generates block
if (blockCap != null && blockCap.generatedByMyself
&& !blockCap.hasWitnessSignature()) {
&& !blockCap.hasWitnessSignature(dynamicPropertiesStore)) {
cpuLimitRatio = 1.0;
} else {
// self witness or other witness or fullnode verifies block
Expand Down
39 changes: 20 additions & 19 deletions actuator/src/main/java/org/tron/core/utils/TransactionUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -222,27 +222,28 @@ public TransactionSignWeight getTransactionSignWeight(Transaction trx) {
}
}
tswBuilder.setPermission(permission);
if (trx.getSignatureCount() > 0 || trx.getPqAuthSigCount() > 0) {
List<ByteString> approveList = new ArrayList<>();
long currentWeight = 0L;
if (trx.getSignatureCount() > 0) {
currentWeight = TransactionCapsule.checkWeight(permission, trx.getSignatureList(),
Sha256Hash.hash(CommonParameter.getInstance()
.isECKeyCryptoEngine(), trx.getRawData().toByteArray()), approveList);
}
if (trx.getPqAuthSigCount() > 0) {
java.util.Set<ByteString> signedAddresses = new java.util.HashSet<>(approveList);
try {
currentWeight = StrictMathWrapper.addExact(currentWeight,
TransactionCapsule.validatePQSignature(trx, permission, signedAddresses,
chainBaseManager.getDynamicPropertiesStore(), approveList));
} catch (ArithmeticException e) {
throw new PermissionException("weight overflow");
}
long currentWeight = 0L;
List<ByteString> approveList = new ArrayList<>();
if (trx.getSignatureCount() > 0 ) {
currentWeight = TransactionCapsule.checkWeight(permission, trx.getSignatureList(),
Sha256Hash.hash(CommonParameter.getInstance()
.isECKeyCryptoEngine(), trx.getRawData().toByteArray()), approveList);
}
if (chainBaseManager.getDynamicPropertiesStore().isAnyPqSchemeAllowed()
&& trx.getPqAuthSigCount() > 0) {
try {
long pqWeight = TransactionCapsule.validatePQSignatureGetWeight(trx, permission,
chainBaseManager.getDynamicPropertiesStore(), approveList);
// sum all signature weight
currentWeight = StrictMathWrapper.addExact(currentWeight,pqWeight);
} catch (ArithmeticException e) {
throw new PermissionException("weight overflow");
}
tswBuilder.addAllApprovedList(approveList);
tswBuilder.setCurrentWeight(currentWeight);
}

tswBuilder.addAllApprovedList(approveList);
tswBuilder.setCurrentWeight(currentWeight);

if (tswBuilder.getCurrentWeight() >= permission.getThreshold()) {
resultBuilder.setCode(Result.response_code.ENOUGH_PERMISSION);
} else {
Expand Down
50 changes: 17 additions & 33 deletions chainbase/src/main/java/org/tron/common/utils/LocalWitnesses.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.tron.common.crypto.SignInterface;
import org.tron.common.crypto.SignUtils;
import org.tron.common.crypto.pqc.PQSchemeRegistry;
import org.tron.common.crypto.pqc.PqKeypair;
import org.tron.core.config.Parameter.ChainConstant;
import org.tron.core.exception.TronError;
import org.tron.protos.Protocol.PQScheme;
Expand All @@ -37,27 +38,20 @@ public class LocalWitnesses {
private List<String> privateKeys = Lists.newArrayList();

/**
* Pre-derived PQ private keys in hex format, one per witness. The expected
* byte length depends on {@link #pqScheme}: 1280 bytes (2560 hex chars) for
* FN-DSA-512. Index-aligned with {@link #pqPublicKeys}.
* Pre-derived PQ keypairs (private + public, hex), one per witness. The
* expected byte lengths depend on {@link #pqScheme}: for FN-DSA-512 each
* private key is 1280 bytes (2560 hex chars) and each public key is 896
* bytes (1792 hex chars).
*
* <p>Configured directly (rather than derived from a seed on the node) so
* the runtime path is not exposed to potential cross-platform floating-point
* non-determinism in BC's Falcon keygen — operators generate the keypair
* off-line and ship both halves to the node.
*/
@Getter
private List<String> pqPrivateKeys = Lists.newArrayList();
private List<PqKeypair> pqKeypairs = Lists.newArrayList();

/**
* PQ public keys in hex format, one per witness. The expected byte length
* depends on {@link #pqScheme}: 896 bytes (1792 hex chars) for FN-DSA-512.
* Index-aligned with {@link #pqPrivateKeys}.
*/
@Getter
private List<String> pqPublicKeys = Lists.newArrayList();

/** PQ signature scheme used by the configured {@link #pqPrivateKeys}. */
/** PQ signature scheme used by the configured {@link #pqKeypairs}. */
@Getter
private PQScheme pqScheme = PQScheme.FN_DSA_512;

Expand Down Expand Up @@ -134,32 +128,22 @@ public void addPrivateKeys(String privateKey) {

/**
* Pre-derived PQ keypairs (priv + pub) used as signing keys under
* {@link #pqScheme}. The two lists must be the same length and index-aligned;
* each entry must be a hex string whose byte length matches the scheme's
* required private/public key size. Callers must therefore set the scheme
* via {@link #setPqScheme(PQScheme)} before calling this method when
* targeting a non-default scheme.
* {@link #pqScheme}. Each entry's private/public hex byte length must match
* the scheme's required size. Callers must therefore set the scheme via
* {@link #setPqScheme(PQScheme)} before calling this method when targeting a
* non-default scheme.
*/
public void setPqKeypairs(final List<String> pqPrivateKeys,
final List<String> pqPublicKeys) {
int privCount = CollectionUtils.isEmpty(pqPrivateKeys) ? 0 : pqPrivateKeys.size();
int pubCount = CollectionUtils.isEmpty(pqPublicKeys) ? 0 : pqPublicKeys.size();
if (privCount == 0 && pubCount == 0) {
public void setPqKeypairs(final List<PqKeypair> pqKeypairs) {
if (CollectionUtils.isEmpty(pqKeypairs)) {
return;
}
if (privCount != pubCount) {
throw new TronError(String.format(
"PQ keypair list size mismatch: priv=%d, pub=%d", privCount, pubCount),
TronError.ErrCode.WITNESS_INIT);
}
int expectedPrivLen = PQSchemeRegistry.getPrivateKeyLength(pqScheme);
int expectedPubLen = PQSchemeRegistry.getPublicKeyLength(pqScheme);
for (int i = 0; i < privCount; i++) {
validatePqKey(pqPrivateKeys.get(i), expectedPrivLen, "PQ private key");
validatePqKey(pqPublicKeys.get(i), expectedPubLen, "PQ public key");
for (PqKeypair kp : pqKeypairs) {
validatePqKey(kp.getPrivateKey(), expectedPrivLen, "PQ private key");
validatePqKey(kp.getPublicKey(), expectedPubLen, "PQ public key");
}
this.pqPrivateKeys = pqPrivateKeys;
this.pqPublicKeys = pqPublicKeys;
this.pqKeypairs = pqKeypairs;
}

private static void validatePqKey(String key, int expectedLen, String label) {
Expand Down
95 changes: 39 additions & 56 deletions chainbase/src/main/java/org/tron/core/capsule/BlockCapsule.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,8 @@
import org.tron.core.store.DynamicPropertiesStore;
import org.tron.protos.Protocol.Block;
import org.tron.protos.Protocol.BlockHeader;
import org.tron.protos.Protocol.Key;
import org.tron.protos.Protocol.PQScheme;
import org.tron.protos.Protocol.PQAuthSig;
import org.tron.protos.Protocol.Permission;
import org.tron.protos.Protocol.Transaction;

@Slf4j(topic = "capsule")
Expand Down Expand Up @@ -203,41 +201,36 @@ private Sha256Hash getRawHash() {
public boolean validateSignature(DynamicPropertiesStore dynamicPropertiesStore,
AccountStore accountStore) throws ValidateSignatureException {
BlockHeader header = block.getBlockHeader();
boolean hasLegacy = !header.getWitnessSignature().isEmpty();
boolean hasPq = header.hasPqAuthSig();
byte[] witnessAccountAddress = header.getRawData().getWitnessAddress()
.toByteArray();

if (hasLegacy && hasPq) {
throw new ValidateSignatureException(
"witness_signature and pq_auth_sig are mutually exclusive");
}
if (!hasLegacy && !hasPq) {
throw new ValidateSignatureException("missing witness signature");
byte[] witnessPermissionAddress;
if (dynamicPropertiesStore.getAllowMultiSign() != 1) {
witnessPermissionAddress = witnessAccountAddress;
} else {
witnessPermissionAddress = accountStore.get(witnessAccountAddress)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing null-check on witness account can throw NullPointerException during signature validation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At chainbase/src/main/java/org/tron/core/capsule/BlockCapsule.java, line 211:

<comment>Missing null-check on witness account can throw NullPointerException during signature validation.</comment>

<file context>
@@ -203,41 +201,27 @@ private Sha256Hash getRawHash() {
+    if (dynamicPropertiesStore.getAllowMultiSign() != 1) {
+      witnessPermissionAddress = witnessAccountAddress;
+    } else {
+      witnessPermissionAddress = accountStore.get(witnessAccountAddress)
+          .getWitnessPermissionAddress();
     }
</file context>

.getWitnessPermissionAddress();
}

byte[] witnessAccountAddress = header.getRawData().getWitnessAddress().toByteArray();
if (hasPq) {
return validatePQSignature(dynamicPropertiesStore, accountStore,
witnessAccountAddress, header.getPqAuthSig());
if (dynamicPropertiesStore.isAnyPqSchemeAllowed()) {
boolean hasLegacy = !header.getWitnessSignature().isEmpty();
boolean hasPq = header.hasPqAuthSig();
if (hasLegacy && hasLegacy) {
throw new ValidateSignatureException(
"witness_signature and pq_auth_sig are mutually exclusive");
}
if (!hasLegacy && !hasPq) {
throw new ValidateSignatureException("missing witness signature");
}
return validatePQSignature(dynamicPropertiesStore, accountStore, witnessPermissionAddress,
header.getPqAuthSig());
}
return validateLegacySignature(dynamicPropertiesStore, accountStore, witnessAccountAddress);
}

private boolean validateLegacySignature(DynamicPropertiesStore dynamicPropertiesStore,
AccountStore accountStore, byte[] witnessAccountAddress)
throws ValidateSignatureException {
try {
byte[] sigAddress = SignUtils.signatureToAddress(getRawHash().getBytes(),
TransactionCapsule.getBase64FromByteString(
block.getBlockHeader().getWitnessSignature()),
TransactionCapsule.getBase64FromByteString(header.getWitnessSignature()),
CommonParameter.getInstance().isECKeyCryptoEngine());
if (dynamicPropertiesStore.getAllowMultiSign() != 1) {
return Arrays.equals(sigAddress, witnessAccountAddress);
}
AccountCapsule witnessAccount = accountStore.get(witnessAccountAddress);
if (witnessAccount == null) {
throw new ValidateSignatureException("witness account does not exist");
}
byte[] witnessPermissionAddress = witnessAccount.getWitnessPermissionAddress();

return Arrays.equals(sigAddress, witnessPermissionAddress);
} catch (SignatureException e) {
throw new ValidateSignatureException(e.getMessage());
Expand All @@ -250,8 +243,11 @@ private boolean validateLegacySignature(DynamicPropertiesStore dynamicProperties
* the witness account's Witness Permission keys[].
*/
private boolean validatePQSignature(DynamicPropertiesStore dynamicPropertiesStore,
AccountStore accountStore, byte[] witnessAccountAddress, PQAuthSig pqAuthSig)
AccountStore accountStore, byte[] witnessPermissionAddress, PQAuthSig pqAuthSig)
throws ValidateSignatureException {
/*
Verify the PQ scheme is supported and proposal opened
*/
PQScheme scheme = pqAuthSig.getScheme();
if (!PQSchemeRegistry.contains(scheme)) {
throw new ValidateSignatureException(
Expand All @@ -262,38 +258,22 @@ private boolean validatePQSignature(DynamicPropertiesStore dynamicPropertiesStor
"pq_auth_sig scheme " + scheme + " is not activated");
}

AccountCapsule accountCapsule = accountStore.get(witnessAccountAddress);
Permission witnessPermission = null;
if (accountCapsule != null && accountCapsule.getInstance().hasWitnessPermission()) {
witnessPermission = accountCapsule.getInstance().getWitnessPermission();
}
if (witnessPermission == null || witnessPermission.getKeysCount() == 0) {
throw new ValidateSignatureException(
"pq_auth_sig present but witness permission is not configured");
}

byte[] publicKey = pqAuthSig.getPublicKey().toByteArray();
if (publicKey.length != PQSchemeRegistry.getPublicKeyLength(scheme)) {
throw new ValidateSignatureException(
"pq_auth_sig public key length mismatch for scheme " + scheme);
}
byte[] signature = pqAuthSig.getSignature().toByteArray();
if (!PQSchemeRegistry.isValidSignatureLength(scheme, signature.length)) {
throw new ValidateSignatureException(
"pq_auth_sig signature length mismatch for scheme " + scheme);
}

byte[] derivedAddr = PQSchemeRegistry.computeAddress(scheme, publicKey);
Key matched = null;
for (Key k : witnessPermission.getKeysList()) {
if (Arrays.equals(k.getAddress().toByteArray(), derivedAddr)) {
matched = k;
break;
}
if (!Arrays.equals(derivedAddr, witnessPermissionAddress)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: PQ signature address matching is restricted to a single witness key, which can reject valid signatures from other configured witness permission keys.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At chainbase/src/main/java/org/tron/core/capsule/BlockCapsule.java, line 259:

<comment>PQ signature address matching is restricted to a single witness key, which can reject valid signatures from other configured witness permission keys.</comment>

<file context>
@@ -262,38 +249,22 @@ private boolean validatePQSignature(DynamicPropertiesStore dynamicPropertiesStor
-        matched = k;
-        break;
-      }
+    if (!Arrays.equals(derivedAddr, witnessPermissionAddress)) {
+      throw new ValidateSignatureException(
+          "pq_auth_sig public key does not match witness permission address");
</file context>

throw new ValidateSignatureException(
"pq_auth_sig public key does not match witness permission address");
}
if (matched == null) {

byte[] signature = pqAuthSig.getSignature().toByteArray();
if (!PQSchemeRegistry.isValidSignatureLength(scheme, signature.length)) {
throw new ValidateSignatureException(
"pq_auth_sig public key does not match any witness permission key");
"pq_auth_sig signature length mismatch for scheme " + scheme);
}

byte[] digest = getRawHash().getBytes();
Expand Down Expand Up @@ -419,10 +399,13 @@ public long getTimeStamp() {
return this.block.getBlockHeader().getRawData().getTimestamp();
}

public boolean hasWitnessSignature() {
public boolean hasWitnessSignature(DynamicPropertiesStore dynamicPropertiesStore) {
BlockHeader header = getInstance().getBlockHeader();
return !header.getWitnessSignature().isEmpty()
|| !header.getPqAuthSig().getSignature().isEmpty();
boolean hasLegacySignature = !header.getWitnessSignature().isEmpty();
if (!dynamicPropertiesStore.isAnyPqSchemeAllowed()) {
return hasLegacySignature;
}
return hasLegacySignature || !header.getPqAuthSig().getSignature().isEmpty();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -495,16 +497,13 @@ public static boolean validateSignature(Transaction transaction,
// Hybrid weight: ECDSA signatures and PQ witnesses share one threshold
// check. The two domains derive distinct addresses (Keccak vs SHA-256
// tagged with 0x41), so a key entry contributes to at most one path.
java.util.Set<ByteString> signedAddresses = new java.util.HashSet<>();
List<ByteString> approveList = new ArrayList<>();
long weight = checkWeight(permission, transaction.getSignatureList(), hash, approveList);
signedAddresses.addAll(approveList);

if (transaction.getPqAuthSigCount() > 0) {
if (dynamicPropertiesStore.isAnyPqSchemeAllowed() && transaction.getPqAuthSigCount() > 0) {
try {
weight = StrictMathWrapper.addExact(weight,
validatePQSignature(transaction, permission, signedAddresses,
dynamicPropertiesStore, approveList));
validatePQSignatureGetWeight(transaction, permission, dynamicPropertiesStore, approveList));
} catch (ArithmeticException e) {
throw new PermissionException("weight overflow");
}
Expand Down Expand Up @@ -723,13 +722,15 @@ void logSlowSigVerify(long startNs) {
* part of {@code raw_data}.</li>
* </ol>
*/
public static long validatePQSignature(Transaction transaction, Permission permission,
java.util.Set<ByteString> signedAddresses,
public static long validatePQSignatureGetWeight(Transaction transaction, Permission permission,
DynamicPropertiesStore dynamicPropertiesStore,
List<ByteString> approveList)
throws PermissionException {

byte[] digest = computeRawHash(transaction).getBytes();

Set<ByteString> signedAddresses = new HashSet<>(approveList);

long weight = 0L;
for (PQAuthSig witness : transaction.getPqAuthSigList()) {
PQScheme scheme = witness.getScheme();
Expand Down
Loading