Skip to content

Commit 4b45b4d

Browse files
committed
refactor(pqc): harden ALLOW_FN_DSA_512 proposal validation and PQ-only witness init
1 parent 7f36a3b commit 4b45b4d

4 files changed

Lines changed: 94 additions & 60 deletions

File tree

actuator/src/main/java/org/tron/core/utils/ProposalUtil.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -942,13 +942,18 @@ public static void validator(DynamicPropertiesStore dynamicPropertiesStore,
942942
break;
943943
}
944944
case ALLOW_FN_DSA_512: {
945-
if (dynamicPropertiesStore.getAllowFnDsa512() == 1) {
945+
if (!forkController.pass(ForkBlockVersionEnum.VERSION_4_8_2)) {
946946
throw new ContractValidateException(
947-
"[ALLOW_FN_DSA_512] has been valid, no need to propose again");
947+
"Bad chain parameter id [ALLOW_FN_DSA_512]");
948948
}
949-
if (value != 1) {
949+
if (value != 0 && value != 1) {
950950
throw new ContractValidateException(
951-
"This value[ALLOW_FN_DSA_512] is only allowed to be 1");
951+
"This value[ALLOW_FN_DSA_512] is only allowed to be 0 or 1");
952+
}
953+
if (dynamicPropertiesStore.getAllowFnDsa512() == value) {
954+
throw new ContractValidateException(
955+
"[ALLOW_FN_DSA_512] has been set to " + value
956+
+ ", no need to propose again");
952957
}
953958
break;
954959
}
@@ -1041,7 +1046,7 @@ public enum ProposalType { // current value, value range
10411046
ALLOW_TVM_OSAKA(96), // 0, 1
10421047
ALLOW_HARDEN_RESOURCE_CALCULATION(97), // 0, 1
10431048
ALLOW_HARDEN_EXCHANGE_CALCULATION(98), // 0, 1
1044-
ALLOW_FN_DSA_512(100); // 0, 1
1049+
ALLOW_FN_DSA_512(99); // 0, 1
10451050

10461051
private long code;
10471052

framework/src/main/java/org/tron/core/config/args/Args.java

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -962,29 +962,26 @@ private static void initLocalWitnesses(Config config, CLIParameter cmd) {
962962
if (config.hasPath(ConfigKey.LOCAL_WITNESS_PQ_KEYS)) {
963963
List<String> pqEntries = config.getStringList(ConfigKey.LOCAL_WITNESS_PQ_KEYS);
964964
if (!pqEntries.isEmpty()) {
965-
localWitnesses = new LocalWitnesses();
966-
// Scheme must be applied before keypairs — key-length validation depends on it.
965+
PQScheme scheme = PQScheme.FN_DSA_512;
967966
if (config.hasPath(ConfigKey.LOCAL_WITNESS_PQ_SCHEME)) {
968967
String schemeName = config.getString(ConfigKey.LOCAL_WITNESS_PQ_SCHEME);
969968
try {
970-
PQScheme scheme = PQScheme.valueOf(schemeName);
971-
if (!WITNESS_PQ_SCHEMES.contains(scheme)) {
972-
throw new TronError("invalid " + ConfigKey.LOCAL_WITNESS_PQ_SCHEME
973-
+ ": " + schemeName + "; valid values: " + WITNESS_PQ_SCHEMES,
974-
TronError.ErrCode.WITNESS_INIT);
975-
}
976-
localWitnesses.setPqScheme(scheme);
969+
scheme = PQScheme.valueOf(schemeName);
977970
} catch (IllegalArgumentException e) {
978971
throw new TronError("invalid " + ConfigKey.LOCAL_WITNESS_PQ_SCHEME
979972
+ ": " + schemeName, TronError.ErrCode.WITNESS_INIT);
980973
}
974+
if (!WITNESS_PQ_SCHEMES.contains(scheme)) {
975+
throw new TronError("invalid " + ConfigKey.LOCAL_WITNESS_PQ_SCHEME
976+
+ ": " + schemeName + "; valid values: " + WITNESS_PQ_SCHEMES,
977+
TronError.ErrCode.WITNESS_INIT);
978+
}
981979
}
982980
// Each entry is the extended private key f‖g‖F‖h (priv ‖ pub) hex,
983981
// sized (privLen + pubLen) bytes for the active scheme. We split here
984982
// so downstream consumers (ConsensusService, LocalWitnesses) keep the
985983
// same priv/pub split they already use — derivePublicKey(priv) replaces
986984
// the previous explicit `pub` config field.
987-
PQScheme scheme = localWitnesses.getPqScheme();
988985
int privHexLen = PQSchemeRegistry.getPrivateKeyLength(scheme) * 2;
989986
int extHexLen = privHexLen + PQSchemeRegistry.getPublicKeyLength(scheme) * 2;
990987
List<String> pqPrivateKeys = new ArrayList<>(pqEntries.size());
@@ -1002,11 +999,8 @@ private static void initLocalWitnesses(Config config, CLIParameter cmd) {
1002999
pqPrivateKeys.add(stripped.substring(0, privHexLen));
10031000
pqPublicKeys.add(stripped.substring(privHexLen));
10041001
}
1005-
localWitnesses.setPqKeypairs(pqPrivateKeys, pqPublicKeys);
1006-
byte[] address = WitnessInitializer.resolvePqAuthSigAddress(lwConfig.getAccountAddress());
1007-
if (address != null) {
1008-
localWitnesses.setWitnessAccountAddress(address);
1009-
}
1002+
localWitnesses = WitnessInitializer.initFromPQOnly(
1003+
scheme, pqPrivateKeys, pqPublicKeys, lwConfig.getAccountAddress());
10101004
return;
10111005
}
10121006
}

framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
import lombok.extern.slf4j.Slf4j;
88
import org.apache.commons.lang3.StringUtils;
99
import org.tron.common.crypto.SignInterface;
10+
import org.tron.common.crypto.pqc.PQSchemeRegistry;
1011
import org.tron.common.utils.ByteArray;
1112
import org.tron.common.utils.Commons;
1213
import org.tron.common.utils.LocalWitnesses;
1314
import org.tron.core.exception.CipherException;
1415
import org.tron.core.exception.TronError;
1516
import org.tron.keystore.Credentials;
1617
import org.tron.keystore.WalletUtils;
18+
import org.tron.protos.Protocol.PQScheme;
1719

1820
@Slf4j
1921
public class WitnessInitializer {
@@ -113,42 +115,39 @@ public static LocalWitnesses initFromKeystore(
113115
}
114116

115117
/**
116-
* Init for PQ-only witness nodes (no legacy ECDSA key). The witness account
117-
* address must be supplied explicitly because there is no ECDSA key to derive it from.
118+
* Init for PQ-only witness nodes (no legacy ECDSA key). When
119+
* {@code witnessAccountAddress} is blank, the address is derived from the
120+
* first PQ public key via {@link PQSchemeRegistry#computeAddress(PQScheme,
121+
* byte[])}.
118122
*/
119-
public static LocalWitnesses initFromPQOnly(String witnessAccountAddress) {
120-
if (StringUtils.isBlank(witnessAccountAddress)) {
121-
throw new TronError(
122-
"localWitnessAccountAddress must be set for PQ-only witness nodes",
123-
TronError.ErrCode.WITNESS_INIT);
124-
}
125-
byte[] address = Commons.decodeFromBase58Check(witnessAccountAddress);
126-
if (address == null) {
123+
public static LocalWitnesses initFromPQOnly(PQScheme scheme,
124+
List<String> pqPrivateKeys, List<String> pqPublicKeys,
125+
String witnessAccountAddress) {
126+
if (pqPublicKeys == null || pqPublicKeys.isEmpty()) {
127127
throw new TronError(
128-
"LocalWitnessAccountAddress format is incorrect",
128+
"PQ public keys must be set for PQ-only witness nodes",
129129
TronError.ErrCode.WITNESS_INIT);
130130
}
131131
LocalWitnesses witnesses = new LocalWitnesses();
132-
witnesses.initWitnessAccountAddress(address, false);
133-
logger.debug("Initialised PQ-only witness with address {}", witnessAccountAddress);
134-
return witnesses;
135-
}
132+
witnesses.setPqScheme(scheme);
133+
witnesses.setPqKeypairs(pqPrivateKeys, pqPublicKeys);
136134

137-
/**
138-
* Resolve witness address for PQ seed configuration.
139-
*/
140-
public static byte[] resolvePqAuthSigAddress(String witnessAccountAddress) {
141-
if (StringUtils.isEmpty(witnessAccountAddress)) {
142-
return null;
143-
}
144-
byte[] address = Commons.decodeFromBase58Check(witnessAccountAddress);
145-
if (address != null) {
146-
logger.debug("Got localWitnessAccountAddress from config.conf");
135+
byte[] address;
136+
if (StringUtils.isBlank(witnessAccountAddress)) {
137+
byte[] firstPubKey = ByteArray.fromHexString(pqPublicKeys.get(0));
138+
address = PQSchemeRegistry.computeAddress(scheme, firstPubKey);
139+
logger.debug("Derived PQ-only witness address from public key");
147140
} else {
148-
throw new TronError("LocalWitnessAccountAddress format from config is incorrect",
149-
TronError.ErrCode.WITNESS_INIT);
141+
address = Commons.decodeFromBase58Check(witnessAccountAddress);
142+
if (address == null) {
143+
throw new TronError(
144+
"LocalWitnessAccountAddress format is incorrect",
145+
TronError.ErrCode.WITNESS_INIT);
146+
}
147+
logger.debug("Got localWitnessAccountAddress from config.conf");
150148
}
151-
return address;
149+
witnesses.initWitnessAccountAddress(address, false);
150+
return witnesses;
152151
}
153152

154153
static byte[] resolveWitnessAddress(

framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -801,26 +801,62 @@ public void blockVersionCheck() {
801801
@Test
802802
public void validateAllowFnDsa512() {
803803
long code = ProposalType.ALLOW_FN_DSA_512.getCode();
804+
ThrowingRunnable proposeZero = () -> ProposalUtil.validator(dynamicPropertiesStore, forkUtils,
805+
code, 0);
806+
ThrowingRunnable proposeOne = () -> ProposalUtil.validator(dynamicPropertiesStore, forkUtils,
807+
code, 1);
808+
ThrowingRunnable proposeTwo = () -> ProposalUtil.validator(dynamicPropertiesStore, forkUtils,
809+
code, 2);
804810

805-
ContractValidateException thrown = assertThrows(ContractValidateException.class,
806-
() -> ProposalUtil.validator(dynamicPropertiesStore, forkUtils, code, 0));
807-
assertEquals("This value[ALLOW_FN_DSA_512] is only allowed to be 1", thrown.getMessage());
811+
byte[] stats = new byte[27];
812+
forkUtils.getManager().getDynamicPropertiesStore()
813+
.statsByVersion(ForkBlockVersionEnum.VERSION_4_8_1.getValue(), stats);
814+
long maintenanceTimeInterval = forkUtils.getManager().getDynamicPropertiesStore()
815+
.getMaintenanceTimeInterval();
816+
long hardForkTime =
817+
((ForkBlockVersionEnum.VERSION_4_8_2.getHardForkTime() - 1) / maintenanceTimeInterval + 1)
818+
* maintenanceTimeInterval;
819+
forkUtils.getManager().getDynamicPropertiesStore()
820+
.saveLatestBlockHeaderTimestamp(hardForkTime - 1);
808821

809-
thrown = assertThrows(ContractValidateException.class,
810-
() -> ProposalUtil.validator(dynamicPropertiesStore, forkUtils, code, 2));
811-
assertEquals("This value[ALLOW_FN_DSA_512] is only allowed to be 1", thrown.getMessage());
822+
// 1) before fork 4.8.2 -> rejected
823+
ContractValidateException thrown = assertThrows(ContractValidateException.class, proposeOne);
824+
assertEquals("Bad chain parameter id [ALLOW_FN_DSA_512]", thrown.getMessage());
812825

826+
forkUtils.getManager().getDynamicPropertiesStore()
827+
.saveLatestBlockHeaderTimestamp(hardForkTime + 1);
828+
Arrays.fill(stats, (byte) 1);
829+
forkUtils.getManager().getDynamicPropertiesStore()
830+
.statsByVersion(ForkBlockVersionEnum.VERSION_4_8_2.getValue(), stats);
831+
832+
// 2) value not in {0, 1} -> rejected
833+
thrown = assertThrows(ContractValidateException.class, proposeTwo);
834+
assertEquals("This value[ALLOW_FN_DSA_512] is only allowed to be 0 or 1", thrown.getMessage());
835+
836+
// 3) current value is 0 (default), proposing 0 again -> rejected
837+
thrown = assertThrows(ContractValidateException.class, proposeZero);
838+
assertEquals("[ALLOW_FN_DSA_512] has been set to 0, no need to propose again",
839+
thrown.getMessage());
840+
841+
// 4) value=1 to enable -> ok
813842
try {
814-
ProposalUtil.validator(dynamicPropertiesStore, forkUtils, code, 1);
815-
} catch (ContractValidateException e) {
816-
Assert.fail("value=1 should be accepted: " + e.getMessage());
843+
proposeOne.run();
844+
} catch (Throwable e) {
845+
Assert.fail("Should pass when toggling 0 -> 1: " + e.getMessage());
817846
}
818847

848+
// 5) after activation, proposing 1 again -> rejected
819849
dynamicPropertiesStore.saveAllowFnDsa512(1L);
820-
thrown = assertThrows(ContractValidateException.class,
821-
() -> ProposalUtil.validator(dynamicPropertiesStore, forkUtils, code, 1));
822-
assertEquals("[ALLOW_FN_DSA_512] has been valid, no need to propose again",
850+
thrown = assertThrows(ContractValidateException.class, proposeOne);
851+
assertEquals("[ALLOW_FN_DSA_512] has been set to 1, no need to propose again",
823852
thrown.getMessage());
853+
854+
// 6) value=0 to disable -> ok (toggle back off)
855+
try {
856+
proposeZero.run();
857+
} catch (Throwable e) {
858+
Assert.fail("Should pass when toggling 1 -> 0: " + e.getMessage());
859+
}
824860
dynamicPropertiesStore.saveAllowFnDsa512(0L);
825861
}
826862
}

0 commit comments

Comments
 (0)