Skip to content

Commit b0402e1

Browse files
feat: Cherry pick (0.56): HIP-632 isAuthorized system contract method implementation (#16443)
Signed-off-by: David S Bakin <[email protected]>
1 parent 81679f6 commit b0402e1

File tree

38 files changed

+1976
-874
lines changed

38 files changed

+1976
-874
lines changed

hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ public record ContractsConfig(
8181
@ConfigProperty(value = "systemContract.accountService.isAuthorizedRawEnabled", defaultValue = "true")
8282
@NetworkProperty
8383
boolean systemContractAccountServiceIsAuthorizedRawEnabled,
84+
@ConfigProperty(value = "systemContract.accountService.isAuthorizedEnabled", defaultValue = "true")
85+
@NetworkProperty
86+
boolean systemContractAccountServiceIsAuthorizedEnabled,
87+
@ConfigProperty(value = "systemContract.metadataKeyAndFieldSupport.enabled", defaultValue = "false")
88+
@NetworkProperty
89+
boolean metadataKeyAndFieldEnabled,
8490
@ConfigProperty(value = "systemContract.updateCustomFees.enabled", defaultValue = "true") @NetworkProperty
8591
boolean systemContractUpdateCustomFeesEnabled,
8692
@ConfigProperty(value = "systemContract.tokenInfo.v2.enabled", defaultValue = "false") @NetworkProperty

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HasTranslatorsModule.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarallowance.HbarAllowanceTranslator;
2323
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hbarapprove.HbarApproveTranslator;
2424
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hederaaccountnumalias.HederaAccountNumAliasTranslator;
25+
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.isauthorized.IsAuthorizedTranslator;
2526
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.isauthorizedraw.IsAuthorizedRawTranslator;
2627
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.isvalidalias.IsValidAliasTranslator;
2728
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.setunlimitedautoassociations.SetUnlimitedAutoAssociationsTranslator;
@@ -101,6 +102,15 @@ static CallTranslator<HasCallAttempt> provideIsAuthorizedRawTranslator(
101102
return translator;
102103
}
103104

105+
@Provides
106+
@Singleton
107+
@IntoSet
108+
@Named("HasTranslators")
109+
static CallTranslator<HasCallAttempt> provideIsAuthorizedTranslator(
110+
@NonNull final IsAuthorizedTranslator translator) {
111+
return translator;
112+
}
113+
104114
@Provides
105115
@Singleton
106116
@IntoSet

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/HasCallAttempt.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator;
3333
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter;
3434
import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater;
35+
import com.hedera.node.app.spi.signatures.SignatureVerifier;
3536
import com.swirlds.config.api.Configuration;
3637
import edu.umd.cs.findbugs.annotations.NonNull;
3738
import edu.umd.cs.findbugs.annotations.Nullable;
@@ -50,6 +51,9 @@ public class HasCallAttempt extends AbstractCallAttempt<HasCallAttempt> {
5051
@Nullable
5152
private final Account redirectAccount;
5253

54+
@NonNull
55+
private final SignatureVerifier signatureVerifier;
56+
5357
// too many parameters
5458
@SuppressWarnings("java:S107")
5559
public HasCallAttempt(
@@ -61,6 +65,7 @@ public HasCallAttempt(
6165
@NonNull final Configuration configuration,
6266
@NonNull final AddressIdConverter addressIdConverter,
6367
@NonNull final VerificationStrategies verificationStrategies,
68+
@NonNull final SignatureVerifier signatureVerifier,
6469
@NonNull final SystemContractGasCalculator gasCalculator,
6570
@NonNull final List<CallTranslator<HasCallAttempt>> callTranslators,
6671
final boolean isStaticCall) {
@@ -82,6 +87,7 @@ public HasCallAttempt(
8287
} else {
8388
this.redirectAccount = null;
8489
}
90+
this.signatureVerifier = requireNonNull(signatureVerifier);
8591
}
8692

8793
@Override
@@ -153,4 +159,8 @@ public boolean isAccountRedirect() {
153159
return enhancement.nativeOperations().getAccount(addressNum);
154160
}
155161
}
162+
163+
public @NonNull SignatureVerifier signatureVerifier() {
164+
return signatureVerifier;
165+
}
156166
}

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/HasCallFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator;
2828
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.SyntheticIds;
2929
import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils;
30+
import com.hedera.node.app.spi.signatures.SignatureVerifier;
3031
import edu.umd.cs.findbugs.annotations.NonNull;
3132
import java.util.List;
3233
import javax.inject.Inject;
@@ -43,17 +44,20 @@ public class HasCallFactory implements CallFactory<HasCallAttempt> {
4344
private final SyntheticIds syntheticIds;
4445
private final CallAddressChecks addressChecks;
4546
private final VerificationStrategies verificationStrategies;
47+
private final SignatureVerifier signatureVerifier;
4648
private final List<CallTranslator<HasCallAttempt>> callTranslators;
4749

4850
@Inject
4951
public HasCallFactory(
5052
@NonNull final SyntheticIds syntheticIds,
5153
@NonNull final CallAddressChecks addressChecks,
5254
@NonNull final VerificationStrategies verificationStrategies,
55+
@NonNull final SignatureVerifier signatureVerifier,
5356
@NonNull @Named("HasTranslators") final List<CallTranslator<HasCallAttempt>> callTranslators) {
5457
this.syntheticIds = requireNonNull(syntheticIds);
5558
this.addressChecks = requireNonNull(addressChecks);
5659
this.verificationStrategies = requireNonNull(verificationStrategies);
60+
this.signatureVerifier = requireNonNull(signatureVerifier);
5761
this.callTranslators = requireNonNull(callTranslators);
5862
}
5963

@@ -82,6 +86,7 @@ public HasCallFactory(
8286
configOf(frame),
8387
syntheticIds.converterFor(enhancement.nativeOperations()),
8488
verificationStrategies,
89+
signatureVerifier,
8590
systemContractGasCalculatorOf(frame),
8691
callTranslators,
8792
frame.isStatic());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* Copyright (C) 2024 Hedera Hashgraph, LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.isauthorized;
18+
19+
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID;
20+
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION_BODY;
21+
import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS;
22+
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult;
23+
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly;
24+
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.accountNumberForEvmReference;
25+
import static com.hedera.pbj.runtime.io.buffer.Bytes.wrap;
26+
import static java.util.Objects.requireNonNull;
27+
28+
import com.esaulpaugh.headlong.abi.Address;
29+
import com.hedera.hapi.node.base.Key;
30+
import com.hedera.hapi.node.base.ResponseCodeEnum;
31+
import com.hedera.hapi.node.base.SignatureMap;
32+
import com.hedera.hapi.node.base.SignaturePair;
33+
import com.hedera.hapi.node.base.SignaturePair.SignatureOneOfType;
34+
import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCalculator;
35+
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall;
36+
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt;
37+
import com.hedera.node.app.spi.signatures.SignatureVerifier;
38+
import com.hedera.node.app.spi.signatures.SignatureVerifier.MessageType;
39+
import com.hedera.node.app.spi.signatures.SignatureVerifier.SimpleKeyStatus;
40+
import com.hedera.pbj.runtime.OneOf;
41+
import com.hedera.pbj.runtime.ParseException;
42+
import com.hedera.pbj.runtime.io.buffer.Bytes;
43+
import edu.umd.cs.findbugs.annotations.NonNull;
44+
import java.util.ArrayList;
45+
import java.util.List;
46+
import java.util.function.Function;
47+
import org.hyperledger.besu.evm.frame.MessageFrame;
48+
49+
public class IsAuthorizedCall extends AbstractCall {
50+
51+
private final Address address;
52+
private final byte[] message;
53+
private final byte[] signatureBlob;
54+
55+
private final CustomGasCalculator customGasCalculator;
56+
57+
private final SignatureVerifier signatureVerifier;
58+
59+
public IsAuthorizedCall(
60+
@NonNull final HasCallAttempt attempt,
61+
final Address address,
62+
@NonNull final byte[] message,
63+
@NonNull final byte[] signatureBlob,
64+
@NonNull CustomGasCalculator gasCalculator) {
65+
super(attempt.systemContractGasCalculator(), attempt.enhancement(), true);
66+
this.address = requireNonNull(address, "address");
67+
this.message = requireNonNull(message, "message");
68+
this.signatureBlob = requireNonNull(signatureBlob, "signatureBlob");
69+
this.customGasCalculator = requireNonNull(gasCalculator, "gasCalculator");
70+
this.signatureVerifier = requireNonNull(attempt.signatureVerifier());
71+
}
72+
73+
@Override
74+
public @NonNull PricedResult execute(@NonNull final MessageFrame frame) {
75+
76+
final Function<ResponseCodeEnum, PricedResult> bail = rce -> encodedOutput(rce, false, frame.getRemainingGas());
77+
78+
final long accountNum = accountNumberForEvmReference(address, nativeOperations());
79+
if (!isValidAccount(accountNum)) return bail.apply(INVALID_ACCOUNT_ID);
80+
final var account = requireNonNull(enhancement.nativeOperations().getAccount(accountNum));
81+
82+
// Q: Do we get a key for hollow accounts and auto-created accounts?
83+
final var key = account.key();
84+
if (key == null) return bail.apply(INVALID_TRANSACTION_BODY);
85+
86+
SignatureMap sigMap;
87+
try {
88+
sigMap = requireNonNull(SignatureMap.PROTOBUF.parse(wrap(signatureBlob)));
89+
} catch (@NonNull final ParseException | NullPointerException ex) {
90+
return bail.apply(INVALID_TRANSACTION_BODY);
91+
}
92+
sigMap = fixEcSignaturesInMap(sigMap);
93+
94+
final var keyCounts = signatureVerifier.countSimpleKeys(key);
95+
long gasRequirement = keyCounts.numEcdsaKeys() * customGasCalculator.getEcrecPrecompiledContractGasCost()
96+
+ keyCounts.numEddsaKeys() * customGasCalculator.getEdSignatureVerificationSystemContractGasCost();
97+
98+
final var authorized = verifyMessage(
99+
key, wrap(message), MessageType.RAW, sigMap, ky -> SimpleKeyStatus.ONLY_IF_CRYPTO_SIG_VALID);
100+
101+
final var result = encodedOutput(SUCCESS, authorized, gasRequirement);
102+
return result;
103+
}
104+
105+
protected boolean verifyMessage(
106+
@NonNull final Key key,
107+
@NonNull final Bytes message,
108+
@NonNull final MessageType msgType,
109+
@NonNull final SignatureMap signatureMap,
110+
@NonNull final Function<Key, SimpleKeyStatus> keyHandlingHook) {
111+
return signatureVerifier.verifySignature(key, message, msgType, signatureMap, keyHandlingHook);
112+
}
113+
114+
@NonNull
115+
protected PricedResult encodedOutput(
116+
final ResponseCodeEnum rce, final boolean authorized, final long gasRequirement) {
117+
final long code = rce.protoOrdinal();
118+
final var output = IsAuthorizedTranslator.IS_AUTHORIZED.getOutputs().encodeElements(code, authorized);
119+
final var result = gasOnly(successResult(output, gasRequirement), SUCCESS, true);
120+
return result;
121+
}
122+
123+
/**
124+
* The Ethereum world uses 65 byte EC signatures, our cryptography library uses 64 byte EC signatures. The
125+
* difference is the addition of an extra "parity" byte at the end of the 64 byte signature (used so that
126+
* `ECRECOVER` can recover the public key (== Ethereum address) from the signature.
127+
*
128+
* This method is a shim for that mismatch. It strips the extra byte off any 65 byte EC signatures it finds.
129+
*
130+
* @param sigMap Signature map from user - possibly contains 65 byte EC signatures
131+
* @return Signature map with only 64 byte EC signatures (and all else unchanged)
132+
*/
133+
public @NonNull SignatureMap fixEcSignaturesInMap(@NonNull final SignatureMap sigMap) {
134+
final List<SignaturePair> newPairs = new ArrayList<>();
135+
for (var spair : sigMap.sigPair()) {
136+
if (spair.hasEcdsaSecp256k1()) {
137+
final var ecSig = requireNonNull(spair.ecdsaSecp256k1());
138+
if (ecSig.length() == 65) {
139+
spair = new SignaturePair(
140+
spair.pubKeyPrefix(), new OneOf<>(SignatureOneOfType.ECDSA_SECP256K1, ecSig.slice(0, 64)));
141+
}
142+
}
143+
newPairs.add(spair);
144+
}
145+
return new SignatureMap(newPairs);
146+
}
147+
148+
boolean isValidAccount(final long accountNum) {
149+
// invalid if accountNum is negative
150+
if (accountNum < 0) return false;
151+
return true;
152+
}
153+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (C) 2024 Hedera Hashgraph, LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.isauthorized;
18+
19+
import static java.util.Objects.requireNonNull;
20+
21+
import com.esaulpaugh.headlong.abi.Address;
22+
import com.esaulpaugh.headlong.abi.Function;
23+
import com.hedera.node.app.service.contract.impl.annotations.ServicesV051;
24+
import com.hedera.node.app.service.contract.impl.exec.FeatureFlags;
25+
import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCalculator;
26+
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator;
27+
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call;
28+
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt;
29+
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes;
30+
import com.hedera.node.config.data.ContractsConfig;
31+
import edu.umd.cs.findbugs.annotations.NonNull;
32+
import javax.inject.Inject;
33+
import javax.inject.Singleton;
34+
35+
@Singleton
36+
public class IsAuthorizedTranslator extends AbstractCallTranslator<HasCallAttempt> {
37+
38+
public static final Function IS_AUTHORIZED =
39+
new Function("isAuthorized(address,bytes,bytes)", ReturnTypes.RESPONSE_CODE64_BOOL);
40+
private static final int ADDRESS_ARG = 0;
41+
private static final int MESSAGE_ARG = 1;
42+
private static final int SIGNATURE_BLOB_ARG = 2;
43+
44+
private final CustomGasCalculator customGasCalculator;
45+
46+
@Inject
47+
public IsAuthorizedTranslator(
48+
@ServicesV051 @NonNull final FeatureFlags featureFlags,
49+
@NonNull final CustomGasCalculator customGasCalculator) {
50+
requireNonNull(featureFlags, "featureFlags");
51+
this.customGasCalculator = requireNonNull(customGasCalculator);
52+
}
53+
54+
@Override
55+
public boolean matches(@NonNull HasCallAttempt attempt) {
56+
requireNonNull(attempt, "attempt");
57+
58+
final boolean callEnabled = attempt.configuration()
59+
.getConfigData(ContractsConfig.class)
60+
.systemContractAccountServiceIsAuthorizedEnabled();
61+
return callEnabled && attempt.isSelector(IS_AUTHORIZED);
62+
}
63+
64+
@Override
65+
public Call callFrom(@NonNull HasCallAttempt attempt) {
66+
requireNonNull(attempt, "attempt");
67+
68+
if (attempt.isSelector(IS_AUTHORIZED)) {
69+
70+
final var call = IS_AUTHORIZED.decodeCall(attempt.inputBytes());
71+
final var address = (Address) call.get(ADDRESS_ARG);
72+
final var message = (byte[]) call.get(MESSAGE_ARG);
73+
final var signatureBlob = (byte[]) call.get(SIGNATURE_BLOB_ARG);
74+
75+
return new IsAuthorizedCall(attempt, address, message, signatureBlob, customGasCalculator);
76+
}
77+
return null;
78+
}
79+
}

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/isauthorizedraw/IsAuthorizedRawCall.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public IsAuthorizedRawCall(
8383
this.messageHash = requireNonNull(messageHash);
8484
this.signature = requireNonNull(signature);
8585
this.customGasCalculator = requireNonNull(customGasCalculator);
86+
requireNonNull(attempt.signatureVerifier());
8687
}
8788

8889
@NonNull

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ private ReturnTypes() {
9090
public static final String ADDRESS = "(address)";
9191

9292
public static final String RESPONSE_CODE_BOOL = "(int32,bool)";
93+
public static final String RESPONSE_CODE64_BOOL = "(int64,bool)";
9394
public static final String RESPONSE_CODE_INT32 = "(int32,int32)";
9495
public static final String RESPONSE_CODE_UINT256 = "(int64,uint256)";
9596
public static final String RESPONSE_CODE_INT256 = "(int64,int256)";

hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ public class TestHelpers {
707707
"6080604052348015600f57600080fd5b50600061016a905077e4cbd3a7fefefefefefefefefefefefefefefefefefefefe600052366000602037600080366018016008845af43d806000803e8160008114605857816000f35b816000fdfea2646970667358221220d8378feed472ba49a0005514ef7087017f707b45fb9bf56bb81bb93ff19a238b64736f6c634300080b0033";
708708

709709
public static byte[] messageHash = unhex("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad");
710+
public static byte[] message = "This is a message".getBytes();
710711

711712
public static byte[] signature = unhex(
712713
"aca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf200");

0 commit comments

Comments
 (0)