Skip to content

Commit 769d945

Browse files
committed
fix(net): dedupe PBFT quorum by recovered SR address
Allowing 65-68 byte PBFT signatures permitted trailing-byte variants of the same physical signature to be counted as distinct votes: the quorum set was keyed by raw signature bytes while ECDSA recovery only consumes the first 65 bytes, so a single signer could submit padded copies and inflate their vote weight. Switch validPbftSign to count unique recovered SR addresses instead, and add a regression test exercising sigs padded to 65/66/67/68 bytes from the same signer.
1 parent 9150745 commit 769d945

2 files changed

Lines changed: 57 additions & 13 deletions

File tree

framework/src/main/java/org/tron/core/net/messagehandler/PbftDataSyncHandler.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -124,19 +124,13 @@ private boolean validPbftSign(Raw raw, List<ByteString> srSignList,
124124
List<ByteString> currentSrList) {
125125
//valid sr list
126126
if (srSignList.size() != 0) {
127-
Set<ByteString> srSignSet = new ConcurrentSet();
128-
srSignSet.addAll(srSignList);
129-
if (srSignSet.size() < Param.getInstance().getAgreeNodeCount()) {
130-
logger.error("sr sign count {} < sr count * 2/3 + 1 == {}", srSignSet.size(),
131-
Param.getInstance().getAgreeNodeCount());
132-
return false;
133-
}
134127
byte[] dataHash = Sha256Hash.hash(true, raw.toByteArray());
135128
Set<ByteString> srSet = Sets.newHashSet(currentSrList);
129+
Set<ByteString> validSrAddressSet = new ConcurrentSet();
136130
List<Future<Boolean>> futureList = new ArrayList<>();
137131
for (ByteString sign : srSignList) {
138132
futureList.add(executorService.submit(
139-
new ValidPbftSignTask(raw.getViewN(), srSignSet, dataHash, srSet, sign)));
133+
new ValidPbftSignTask(raw.getViewN(), validSrAddressSet, dataHash, srSet, sign)));
140134
}
141135
for (Future<Boolean> future : futureList) {
142136
try {
@@ -147,7 +141,9 @@ private boolean validPbftSign(Raw raw, List<ByteString> srSignList,
147141
logger.error("", e);
148142
}
149143
}
150-
if (srSignSet.size() != 0) {
144+
if (validSrAddressSet.size() < Param.getInstance().getAgreeNodeCount()) {
145+
logger.error("sr sign count {} < sr count * 2/3 + 1 == {}", validSrAddressSet.size(),
146+
Param.getInstance().getAgreeNodeCount());
151147
return false;
152148
}
153149
}
@@ -157,15 +153,15 @@ private boolean validPbftSign(Raw raw, List<ByteString> srSignList,
157153
private class ValidPbftSignTask implements Callable<Boolean> {
158154

159155
private long viewN;
160-
private Set<ByteString> srSignSet;
156+
private Set<ByteString> validSrAddressSet;
161157
private byte[] dataHash;
162158
private Set<ByteString> srSet;
163159
private ByteString sign;
164160

165-
ValidPbftSignTask(long viewN, Set<ByteString> srSignSet,
161+
ValidPbftSignTask(long viewN, Set<ByteString> validSrAddressSet,
166162
byte[] dataHash, Set<ByteString> srSet, ByteString sign) {
167163
this.viewN = viewN;
168-
this.srSignSet = srSignSet;
164+
this.validSrAddressSet = validSrAddressSet;
169165
this.dataHash = dataHash;
170166
this.srSet = srSet;
171167
this.sign = sign;
@@ -186,7 +182,7 @@ public Boolean call() throws Exception {
186182
ByteArray.toHexString(srAddress));
187183
return false;
188184
}
189-
srSignSet.remove(sign);
185+
validSrAddressSet.add(ByteString.copyFrom(srAddress));
190186
} catch (SignatureException e) {
191187
logger.error("viewN {} valid sr list sign fail!", viewN, e);
192188
return false;

framework/src/test/java/org/tron/core/net/messagehandler/PbftDataSyncHandlerTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@
55
import java.lang.reflect.Field;
66
import java.lang.reflect.Method;
77
import java.util.ArrayList;
8+
import java.util.Arrays;
89
import java.util.Collections;
10+
import java.util.List;
911
import java.util.Map;
12+
import org.bouncycastle.util.encoders.Hex;
1013
import org.junit.Assert;
1114
import org.junit.Test;
1215
import org.mockito.Mockito;
16+
import org.tron.common.crypto.SignInterface;
17+
import org.tron.common.crypto.SignUtils;
18+
import org.tron.common.utils.PublicMethod;
1319
import org.tron.common.utils.Sha256Hash;
1420
import org.tron.consensus.base.Param;
1521
import org.tron.core.ChainBaseManager;
@@ -89,4 +95,46 @@ public void testValidPbftSignPaddedSigOsakaRejected() throws Exception {
8995
Collections.singletonList(ByteString.copyFrom(new byte[21])));
9096
Assert.assertFalse(valid);
9197
}
98+
99+
@Test
100+
public void testValidPbftSignTrailingBytePaddedDuplicatesRejected() throws Exception {
101+
PbftDataSyncHandler pbftDataSyncHandler = new PbftDataSyncHandler();
102+
DynamicPropertiesStore dynamicPropertiesStore = Mockito.mock(DynamicPropertiesStore.class);
103+
ChainBaseManager chainBaseManager = Mockito.mock(ChainBaseManager.class);
104+
Mockito.when(chainBaseManager.getDynamicPropertiesStore()).thenReturn(dynamicPropertiesStore);
105+
Mockito.when(dynamicPropertiesStore.getAllowTvmOsaka()).thenReturn(1L);
106+
107+
Field field = PbftDataSyncHandler.class.getDeclaredField("chainBaseManager");
108+
field.setAccessible(true);
109+
field.set(pbftDataSyncHandler, chainBaseManager);
110+
111+
Param.getInstance().setAgreeNodeCount(2);
112+
Method method = PbftDataSyncHandler.class.getDeclaredMethod("validPbftSign",
113+
Protocol.PBFTMessage.Raw.class, java.util.List.class, java.util.List.class);
114+
method.setAccessible(true);
115+
116+
Protocol.PBFTMessage.Raw raw = Protocol.PBFTMessage.Raw.newBuilder()
117+
.setViewN(1)
118+
.setEpoch(0)
119+
.setDataType(Protocol.PBFTMessage.DataType.BLOCK)
120+
.setMsgType(Protocol.PBFTMessage.MsgType.COMMIT)
121+
.setData(ByteString.copyFromUtf8("block"))
122+
.build();
123+
124+
SignInterface signer = SignUtils.fromPrivate(
125+
Hex.decode(PublicMethod.getRandomPrivateKey()), true);
126+
byte[] sig65 = signer.Base64toBytes(
127+
signer.signHash(Sha256Hash.hash(true, raw.toByteArray())));
128+
List<ByteString> paddedSigs = Arrays.asList(
129+
ByteString.copyFrom(sig65),
130+
ByteString.copyFrom(Arrays.copyOf(sig65, 66)),
131+
ByteString.copyFrom(Arrays.copyOf(sig65, 67)),
132+
ByteString.copyFrom(Arrays.copyOf(sig65, 68)));
133+
List<ByteString> currentSrList = Collections.singletonList(
134+
ByteString.copyFrom(signer.getAddress()));
135+
136+
boolean valid = (boolean) method.invoke(pbftDataSyncHandler, raw,
137+
paddedSigs, currentSrList);
138+
Assert.assertFalse("trailing-byte padded duplicates must not inflate quorum", valid);
139+
}
92140
}

0 commit comments

Comments
 (0)