diff --git a/actuator/src/main/java/org/tron/core/actuator/VMActuator.java b/actuator/src/main/java/org/tron/core/actuator/VMActuator.java index 1b0e8a6637..0a9045a158 100644 --- a/actuator/src/main/java/org/tron/core/actuator/VMActuator.java +++ b/actuator/src/main/java/org/tron/core/actuator/VMActuator.java @@ -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; @@ -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(); @@ -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 @@ -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 @@ -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 diff --git a/actuator/src/main/java/org/tron/core/utils/TransactionUtil.java b/actuator/src/main/java/org/tron/core/utils/TransactionUtil.java index e487373951..3d89dd45c7 100644 --- a/actuator/src/main/java/org/tron/core/utils/TransactionUtil.java +++ b/actuator/src/main/java/org/tron/core/utils/TransactionUtil.java @@ -222,27 +222,28 @@ public TransactionSignWeight getTransactionSignWeight(Transaction trx) { } } tswBuilder.setPermission(permission); - if (trx.getSignatureCount() > 0 || trx.getPqAuthSigCount() > 0) { - List 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 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 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 { diff --git a/chainbase/src/main/java/org/tron/common/utils/LocalWitnesses.java b/chainbase/src/main/java/org/tron/common/utils/LocalWitnesses.java index bdd0fad426..3abc43cb71 100644 --- a/chainbase/src/main/java/org/tron/common/utils/LocalWitnesses.java +++ b/chainbase/src/main/java/org/tron/common/utils/LocalWitnesses.java @@ -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; @@ -37,9 +38,10 @@ public class LocalWitnesses { private List 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). * *

Configured directly (rather than derived from a seed on the node) so * the runtime path is not exposed to potential cross-platform floating-point @@ -47,17 +49,9 @@ public class LocalWitnesses { * off-line and ship both halves to the node. */ @Getter - private List pqPrivateKeys = Lists.newArrayList(); + private List 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 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; @@ -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 pqPrivateKeys, - final List 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 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) { diff --git a/chainbase/src/main/java/org/tron/core/capsule/BlockCapsule.java b/chainbase/src/main/java/org/tron/core/capsule/BlockCapsule.java index 9324f47a08..ad0e90662c 100755 --- a/chainbase/src/main/java/org/tron/core/capsule/BlockCapsule.java +++ b/chainbase/src/main/java/org/tron/core/capsule/BlockCapsule.java @@ -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") @@ -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) + .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()); @@ -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( @@ -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)) { + 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(); @@ -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 diff --git a/chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java b/chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java index 0dd908a185..cf83ceeb8e 100755 --- a/chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java +++ b/chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java @@ -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; @@ -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 signedAddresses = new java.util.HashSet<>(); List 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"); } @@ -723,13 +722,15 @@ void logSlowSigVerify(long startNs) { * part of {@code raw_data}. * */ - public static long validatePQSignature(Transaction transaction, Permission permission, - java.util.Set signedAddresses, + public static long validatePQSignatureGetWeight(Transaction transaction, Permission permission, DynamicPropertiesStore dynamicPropertiesStore, List approveList) throws PermissionException { + byte[] digest = computeRawHash(transaction).getBytes(); + Set signedAddresses = new HashSet<>(approveList); + long weight = 0L; for (PQAuthSig witness : transaction.getPqAuthSigList()) { PQScheme scheme = witness.getScheme(); diff --git a/crypto/src/main/java/org/tron/common/crypto/pqc/PqKeypair.java b/crypto/src/main/java/org/tron/common/crypto/pqc/PqKeypair.java new file mode 100644 index 0000000000..d10d694839 --- /dev/null +++ b/crypto/src/main/java/org/tron/common/crypto/pqc/PqKeypair.java @@ -0,0 +1,19 @@ +package org.tron.common.crypto.pqc; + +import lombok.ToString; +import lombok.Value; + +/** + * Immutable hex-encoded post-quantum keypair (private + public key). Bundles + * the two halves so the public/private lists can no longer drift out of + * index-alignment by construction. + * + *

{@code privateKey} is excluded from {@link #toString()} to prevent + * accidental leakage of secret-key material into logs. + */ +@Value +public class PqKeypair { + @ToString.Exclude + String privateKey; + String publicKey; +} diff --git a/framework/src/main/java/org/tron/core/config/args/Args.java b/framework/src/main/java/org/tron/core/config/args/Args.java index a4b6e9a499..c10186a35f 100644 --- a/framework/src/main/java/org/tron/core/config/args/Args.java +++ b/framework/src/main/java/org/tron/core/config/args/Args.java @@ -45,6 +45,7 @@ import org.tron.common.args.Witness; import org.tron.common.cron.CronExpression; import org.tron.common.crypto.pqc.PQSchemeRegistry; +import org.tron.common.crypto.pqc.PqKeypair; import org.tron.common.logsfilter.EventPluginConfig; import org.tron.common.logsfilter.FilterQuery; import org.tron.common.logsfilter.TriggerConfig; @@ -990,13 +991,12 @@ private static void initLocalWitnesses(Config config, CLIParameter cmd) { } // Each entry is the extended private key f‖g‖F‖h (priv ‖ pub) hex, // sized (privLen + pubLen) bytes for the active scheme. We split here - // so downstream consumers (ConsensusService, LocalWitnesses) keep the - // same priv/pub split they already use — derivePublicKey(priv) replaces - // the previous explicit `pub` config field. + // into PqKeypair entries so downstream consumers (ConsensusService, + // LocalWitnesses) get the priv/pub halves bundled — derivePublicKey + // (priv) replaces the previous explicit `pub` config field. int privHexLen = PQSchemeRegistry.getPrivateKeyLength(scheme) * 2; int extHexLen = privHexLen + PQSchemeRegistry.getPublicKeyLength(scheme) * 2; - List pqPrivateKeys = new ArrayList<>(pqEntries.size()); - List pqPublicKeys = new ArrayList<>(pqEntries.size()); + List pqKeypairs = new ArrayList<>(pqEntries.size()); for (int i = 0; i < pqEntries.size(); i++) { String hex = pqEntries.get(i); String stripped = hex; @@ -1010,11 +1010,12 @@ private static void initLocalWitnesses(Config config, CLIParameter cmd) { stripped == null ? 0 : stripped.length()), TronError.ErrCode.WITNESS_INIT); } - pqPrivateKeys.add(stripped.substring(0, privHexLen)); - pqPublicKeys.add(stripped.substring(privHexLen)); + pqKeypairs.add(new PqKeypair( + stripped.substring(0, privHexLen), + stripped.substring(privHexLen))); } localWitnesses = WitnessInitializer.initFromPQOnly( - scheme, pqPrivateKeys, pqPublicKeys, lwConfig.getAccountAddress()); + scheme, pqKeypairs, lwConfig.getAccountAddress()); return; } } diff --git a/framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java b/framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java index 52819df084..fd6a10f385 100644 --- a/framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java +++ b/framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java @@ -8,6 +8,7 @@ import org.apache.commons.lang3.StringUtils; import org.tron.common.crypto.SignInterface; import org.tron.common.crypto.pqc.PQSchemeRegistry; +import org.tron.common.crypto.pqc.PqKeypair; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Commons; import org.tron.common.utils.LocalWitnesses; @@ -121,34 +122,23 @@ public static LocalWitnesses initFromKeystore( * byte[])}. */ public static LocalWitnesses initFromPQOnly(PQScheme scheme, - List pqPrivateKeys, List pqPublicKeys, - String witnessAccountAddress) { - if (pqPublicKeys == null || pqPublicKeys.isEmpty()) { - throw new TronError( - "PQ public keys must be set for PQ-only witness nodes", - TronError.ErrCode.WITNESS_INIT); - } - if (pqPrivateKeys == null || pqPrivateKeys.isEmpty()) { - throw new TronError( - "PQ private keys must be set for PQ-only witness nodes", - TronError.ErrCode.WITNESS_INIT); - } - if (pqPrivateKeys.size() != pqPublicKeys.size()) { + List pqKeypairs, String witnessAccountAddress) { + if (pqKeypairs == null || pqKeypairs.isEmpty()) { throw new TronError( - "PQ private/public key count mismatch", + "PQ keypairs must be set for PQ-only witness nodes", TronError.ErrCode.WITNESS_INIT); } LocalWitnesses witnesses = new LocalWitnesses(); witnesses.setPqScheme(scheme); - witnesses.setPqKeypairs(pqPrivateKeys, pqPublicKeys); + witnesses.setPqKeypairs(pqKeypairs); byte[] address; if (StringUtils.isBlank(witnessAccountAddress)) { - byte[] firstPubKey = ByteArray.fromHexString(pqPublicKeys.get(0)); + byte[] firstPubKey = ByteArray.fromHexString(pqKeypairs.get(0).getPublicKey()); address = PQSchemeRegistry.computeAddress(scheme, firstPubKey); logger.debug("Derived PQ-only witness address from public key"); } else { - if (pqPublicKeys.size() != 1) { + if (pqKeypairs.size() != 1) { throw new TronError( "LocalWitnessAccountAddress can only be set when there is only one PQ keypair", TronError.ErrCode.WITNESS_INIT); diff --git a/framework/src/main/java/org/tron/core/consensus/ConsensusService.java b/framework/src/main/java/org/tron/core/consensus/ConsensusService.java index 08ad9cd8a7..600262c624 100644 --- a/framework/src/main/java/org/tron/core/consensus/ConsensusService.java +++ b/framework/src/main/java/org/tron/core/consensus/ConsensusService.java @@ -12,6 +12,7 @@ import org.tron.common.crypto.SignUtils; import org.tron.common.crypto.pqc.PQSchemeRegistry; import org.tron.common.crypto.pqc.PQSignature; +import org.tron.common.crypto.pqc.PqKeypair; import org.tron.common.parameter.CommonParameter; import org.tron.consensus.Consensus; import org.tron.consensus.base.Param; @@ -50,15 +51,8 @@ public void start() { param.setAgreeNodeCount(parameter.getAgreeNodeCount()); List miners = new ArrayList<>(); List privateKeys = Args.getLocalWitnesses().getPrivateKeys(); - List pqPrivateKeys = Args.getLocalWitnesses().getPqPrivateKeys(); - List pqPublicKeys = Args.getLocalWitnesses().getPqPublicKeys(); - if (pqPublicKeys.size() != pqPrivateKeys.size()) { - throw new TronError( - "localwitness_pq.keys size mismatch: " + pqPrivateKeys.size() - + " private vs " + pqPublicKeys.size() + " public", - TronError.ErrCode.WITNESS_INIT); - } - if (!privateKeys.isEmpty() && !pqPrivateKeys.isEmpty()) { + List pqKeypairs = Args.getLocalWitnesses().getPqKeypairs(); + if (!privateKeys.isEmpty() && !pqKeypairs.isEmpty()) { throw new TronError( "legacy localwitness keys and localwitness_pq.keys are mutually exclusive", TronError.ErrCode.WITNESS_INIT); @@ -93,12 +87,12 @@ public void start() { Miner miner = param.new Miner(privateKey, ByteString.copyFrom(privateKeyAddress), ByteString.copyFrom(witnessAddress)); miners.add(miner); - } else if (pqPrivateKeys.size() > 1) { + } else if (pqKeypairs.size() > 1) { PQScheme scheme = Args.getLocalWitnesses().getPqScheme(); requireSupportedPqScheme(scheme); - for (int i = 0; i < pqPrivateKeys.size(); i++) { - byte[] privBytes = fromHexString(pqPrivateKeys.get(i)); - byte[] pubBytes = fromHexString(pqPublicKeys.get(i)); + for (PqKeypair kp : pqKeypairs) { + byte[] privBytes = fromHexString(kp.getPrivateKey()); + byte[] pubBytes = fromHexString(kp.getPublicKey()); PQSignature keypair = PQSchemeRegistry.fromKeypair(scheme, privBytes, pubBytes); byte[] sk = keypair.getPrivateKey(); byte[] pk = keypair.getPublicKey(); @@ -117,8 +111,8 @@ public void start() { logger.info("Add {} witness (from configured keypair): {}, size: {}", scheme, Hex.toHexString(pqAddress), miners.size()); } - } else if (pqPrivateKeys.size() == 1) { - miners.add(buildPQOnlyMinerFromKeypair(param, pqPrivateKeys.get(0), pqPublicKeys.get(0))); + } else if (pqKeypairs.size() == 1) { + miners.add(buildPQOnlyMinerFromKeypair(param, pqKeypairs.get(0))); } param.setMiners(miners); @@ -128,12 +122,11 @@ public void start() { logger.info("consensus service start success"); } - private Miner buildPQOnlyMinerFromKeypair(Param param, String pqPrivateKey, - String pqPublicKey) { + private Miner buildPQOnlyMinerFromKeypair(Param param, PqKeypair pqKeypair) { PQScheme scheme = Args.getLocalWitnesses().getPqScheme(); requireSupportedPqScheme(scheme); - byte[] privBytes = fromHexString(pqPrivateKey); - byte[] pubBytes = fromHexString(pqPublicKey); + byte[] privBytes = fromHexString(pqKeypair.getPrivateKey()); + byte[] pubBytes = fromHexString(pqKeypair.getPublicKey()); PQSignature keypair = PQSchemeRegistry.fromKeypair(scheme, privBytes, pubBytes); byte[] sk = keypair.getPrivateKey(); byte[] pk = keypair.getPublicKey(); diff --git a/framework/src/main/java/org/tron/core/db/Manager.java b/framework/src/main/java/org/tron/core/db/Manager.java index 7dd5a9b7a2..e51215163b 100644 --- a/framework/src/main/java/org/tron/core/db/Manager.java +++ b/framework/src/main/java/org/tron/core/db/Manager.java @@ -1552,9 +1552,10 @@ public TransactionInfo processTransaction(final TransactionCapsule trxCap, Block trace.exec(); trace.setResult(); logger.info("Retry result when push: {}, for tx id: {}, tx resultCode in receipt: {}.", - blockCap.hasWitnessSignature(), txId, trace.getReceipt().getResult()); + blockCap.hasWitnessSignature(chainBaseManager.getDynamicPropertiesStore()), txId, + trace.getReceipt().getResult()); } - if (blockCap.hasWitnessSignature()) { + if (blockCap.hasWitnessSignature(chainBaseManager.getDynamicPropertiesStore())) { trace.check(); } } @@ -1600,7 +1601,9 @@ public TransactionInfo processTransaction(final TransactionCapsule trxCap, Block if (cost > 100) { String type = "broadcast"; if (Objects.nonNull(blockCap)) { - type = blockCap.hasWitnessSignature() ? "apply" : "pack"; + type = + blockCap.hasWitnessSignature(chainBaseManager.getDynamicPropertiesStore()) ? "apply" : + "pack"; } logger.info("Process transaction {} cost {} ms during {}, {}", Hex.toHexString(transactionInfo.getId()), cost, type, contract.getType().name()); diff --git a/framework/src/main/java/org/tron/core/net/service/relay/RelayService.java b/framework/src/main/java/org/tron/core/net/service/relay/RelayService.java index 55ac1063f5..e78e90341a 100644 --- a/framework/src/main/java/org/tron/core/net/service/relay/RelayService.java +++ b/framework/src/main/java/org/tron/core/net/service/relay/RelayService.java @@ -18,6 +18,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.common.es.ExecutorServiceManager; import org.tron.common.log.layout.DesensitizedConverter; import org.tron.common.parameter.CommonParameter; @@ -72,7 +73,7 @@ public class RelayService { private final int keySize = Args.getLocalWitnesses().getPrivateKeys().size(); - private final int pqKeySize = Args.getLocalWitnesses().getPqPrivateKeys().size(); + private final int pqKeySize = Args.getLocalWitnesses().getPqKeypairs().size(); private final ByteString witnessAddress = Args.getLocalWitnesses().getWitnessAccountAddress() != null ? ByteString @@ -135,8 +136,9 @@ public void fillHelloMessage(HelloMessage message, Channel channel) { } else { LocalWitnesses lw = Args.getLocalWitnesses(); PQScheme scheme = lw.getPqScheme(); - byte[] privKey = ByteArray.fromHexString(lw.getPqPrivateKeys().get(0)); - byte[] pubKey = ByteArray.fromHexString(lw.getPqPublicKeys().get(0)); + PqKeypair kp = lw.getPqKeypairs().get(0); + byte[] privKey = ByteArray.fromHexString(kp.getPrivateKey()); + byte[] pubKey = ByteArray.fromHexString(kp.getPublicKey()); byte[] sig = PQSchemeRegistry.sign(scheme, privKey, digest); builder.setPqAuthSig(PQAuthSig.newBuilder() .setScheme(scheme) diff --git a/framework/src/test/java/org/tron/common/utils/LocalWitnessesTest.java b/framework/src/test/java/org/tron/common/utils/LocalWitnessesTest.java index b48995b89a..6564ab79be 100644 --- a/framework/src/test/java/org/tron/common/utils/LocalWitnessesTest.java +++ b/framework/src/test/java/org/tron/common/utils/LocalWitnessesTest.java @@ -10,6 +10,7 @@ import org.junit.BeforeClass; import org.junit.Test; import org.tron.common.crypto.pqc.FNDSA512; +import org.tron.common.crypto.pqc.PqKeypair; import org.tron.core.exception.TronError; import org.tron.protos.Protocol.PQScheme; @@ -36,27 +37,18 @@ public static void generateKeypairs() { @Test public void fnDsa512AcceptsValidKeypair() { LocalWitnesses lw = new LocalWitnesses(); - lw.setPqKeypairs(Collections.singletonList(priv), Collections.singletonList(pub)); + lw.setPqKeypairs(Collections.singletonList(new PqKeypair(priv, pub))); assertEquals(PQScheme.FN_DSA_512, lw.getPqScheme()); - assertEquals(1, lw.getPqPrivateKeys().size()); - assertEquals(1, lw.getPqPublicKeys().size()); + assertEquals(1, lw.getPqKeypairs().size()); } @Test public void fnDsa512AcceptsMultipleKeypairs() { LocalWitnesses lw = new LocalWitnesses(); - lw.setPqKeypairs(Arrays.asList(priv, priv2), Arrays.asList(pub, pub2)); - assertEquals(2, lw.getPqPrivateKeys().size()); - assertEquals(2, lw.getPqPublicKeys().size()); - } - - @Test - public void mismatchedListSizesRejected() { - LocalWitnesses lw = new LocalWitnesses(); - TronError err = assertThrows(TronError.class, - () -> lw.setPqKeypairs(Arrays.asList(priv, priv2), Collections.singletonList(pub))); - assertEquals(TronError.ErrCode.WITNESS_INIT, err.getErrCode()); - assertTrue(err.getMessage().contains("size mismatch")); + lw.setPqKeypairs(Arrays.asList( + new PqKeypair(priv, pub), + new PqKeypair(priv2, pub2))); + assertEquals(2, lw.getPqKeypairs().size()); } @Test @@ -64,8 +56,7 @@ public void wrongLengthPrivateKeyRejected() { LocalWitnesses lw = new LocalWitnesses(); String shortPriv = priv.substring(2); TronError err = assertThrows(TronError.class, - () -> lw.setPqKeypairs(Collections.singletonList(shortPriv), - Collections.singletonList(pub))); + () -> lw.setPqKeypairs(Collections.singletonList(new PqKeypair(shortPriv, pub)))); assertEquals(TronError.ErrCode.WITNESS_INIT, err.getErrCode()); assertTrue(err.getMessage().contains("PQ private key")); // FN-DSA-512 private key is 1280 bytes = 2560 hex chars. @@ -77,8 +68,7 @@ public void wrongLengthPublicKeyRejected() { LocalWitnesses lw = new LocalWitnesses(); String shortPub = pub.substring(2); TronError err = assertThrows(TronError.class, - () -> lw.setPqKeypairs(Collections.singletonList(priv), - Collections.singletonList(shortPub))); + () -> lw.setPqKeypairs(Collections.singletonList(new PqKeypair(priv, shortPub)))); assertEquals(TronError.ErrCode.WITNESS_INIT, err.getErrCode()); assertTrue(err.getMessage().contains("PQ public key")); // FN-DSA-512 public key is 896 bytes = 1792 hex chars. @@ -90,8 +80,7 @@ public void nonHexPrivateKeyRejected() { LocalWitnesses lw = new LocalWitnesses(); String badPriv = "zz" + priv.substring(2); TronError err = assertThrows(TronError.class, - () -> lw.setPqKeypairs(Collections.singletonList(badPriv), - Collections.singletonList(pub))); + () -> lw.setPqKeypairs(Collections.singletonList(new PqKeypair(badPriv, pub)))); assertEquals(TronError.ErrCode.WITNESS_INIT, err.getErrCode()); assertTrue(err.getMessage().contains("hex")); } @@ -123,10 +112,9 @@ public void supportedSchemeAccepted() { @Test public void emptyKeypairsAreNoop() { LocalWitnesses lw = new LocalWitnesses(); - lw.setPqKeypairs(Collections.emptyList(), Collections.emptyList()); - lw.setPqKeypairs(null, null); - assertEquals(0, lw.getPqPrivateKeys().size()); - assertEquals(0, lw.getPqPublicKeys().size()); + lw.setPqKeypairs(Collections.emptyList()); + lw.setPqKeypairs(null); + assertEquals(0, lw.getPqKeypairs().size()); } @Test @@ -134,18 +122,15 @@ public void zeroXPrefixedHexAccepted() { // validatePqKey strips a leading "0x" before measuring the length, so // hex strings with the prefix must be accepted. LocalWitnesses lw = new LocalWitnesses(); - lw.setPqKeypairs( - Collections.singletonList("0x" + priv), - Collections.singletonList("0x" + pub)); - assertEquals(1, lw.getPqPrivateKeys().size()); + lw.setPqKeypairs(Collections.singletonList(new PqKeypair("0x" + priv, "0x" + pub))); + assertEquals(1, lw.getPqKeypairs().size()); } @Test public void blankKeyRejected() { LocalWitnesses lw = new LocalWitnesses(); TronError err = assertThrows(TronError.class, - () -> lw.setPqKeypairs(Collections.singletonList(""), - Collections.singletonList(pub))); + () -> lw.setPqKeypairs(Collections.singletonList(new PqKeypair("", pub)))); assertEquals(TronError.ErrCode.WITNESS_INIT, err.getErrCode()); assertTrue(err.getMessage().contains("PQ private key")); } diff --git a/framework/src/test/java/org/tron/core/capsule/BlockCapsulePQTest.java b/framework/src/test/java/org/tron/core/capsule/BlockCapsulePQTest.java index c20df12299..e2ae68efab 100644 --- a/framework/src/test/java/org/tron/core/capsule/BlockCapsulePQTest.java +++ b/framework/src/test/java/org/tron/core/capsule/BlockCapsulePQTest.java @@ -111,9 +111,11 @@ private PQAuthSig buildPQAuthSig(byte[] signature) { public void hasWitnessSignatureTrueForPqOnlyBlock() { byte[] parentHash = new byte[32]; BlockCapsule block = buildUnsignedBlock(parentHash); - Assert.assertFalse(block.hasWitnessSignature()); + Assert.assertFalse(block.hasWitnessSignature(dbManager.getDynamicPropertiesStore())); + + dbManager.getDynamicPropertiesStore().saveAllowFnDsa512(1L); block.setPqAuthSig(buildPQAuthSig(signPQ(block.getRawHashBytes()))); - Assert.assertTrue(block.hasWitnessSignature()); + Assert.assertTrue(block.hasWitnessSignature(dbManager.getDynamicPropertiesStore())); } @Test diff --git a/framework/src/test/java/org/tron/core/capsule/BlockCapsuleTest.java b/framework/src/test/java/org/tron/core/capsule/BlockCapsuleTest.java index ca0844c2c1..b89d600336 100644 --- a/framework/src/test/java/org/tron/core/capsule/BlockCapsuleTest.java +++ b/framework/src/test/java/org/tron/core/capsule/BlockCapsuleTest.java @@ -15,12 +15,14 @@ import org.tron.common.TestConstants; import org.tron.common.utils.ByteArray; import org.tron.common.utils.LocalWitnesses; +import org.mockito.Mockito; import org.tron.common.utils.PublicMethod; import org.tron.common.utils.Sha256Hash; import org.tron.core.Wallet; import org.tron.core.config.args.Args; import org.tron.core.exception.BadBlockException; import org.tron.core.exception.BadItemException; +import org.tron.core.store.DynamicPropertiesStore; import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.contract.BalanceContract.TransferContract; @@ -169,10 +171,11 @@ public void testHasWitnessSignature() { localWitnesses.initWitnessAccountAddress(null, true); Args.setLocalWitnesses(localWitnesses); - Assert.assertFalse(blockCapsule0.hasWitnessSignature()); + DynamicPropertiesStore dps = Mockito.mock(DynamicPropertiesStore.class); + Assert.assertFalse(blockCapsule0.hasWitnessSignature(dps)); blockCapsule0 .sign(ByteArray.fromHexString(Args.getLocalWitnesses().getPrivateKey())); - Assert.assertTrue(blockCapsule0.hasWitnessSignature()); + Assert.assertTrue(blockCapsule0.hasWitnessSignature(dps)); } @Test