hashgraph / hedera-services

Crypto, token, consensus, file, and smart contract services for the Hedera public ledger
Apache License 2.0
280 stars 124 forks source link

feat: Expose signature verification from app spi module to other services (e.g., smart contracts) #14248

Closed david-bakin-sl closed 1 month ago

david-bakin-sl commented 1 month ago

There needs to be a proper way for smart contracts service to get access to the app's ability to verify an arbitrary set of signatures, given an account, the data, and the signatures. Currently no service has the ability to do that - the only thing exposed is the list of verified signatures (that signed the transaction). But isAuthorized{Raw} requires it. (IsAuthorized requires it for accounts with keys of arbitrary complexity and cryptography type, isAuthorizedRaw only for accounts with a single "simple" key.)

isAuthorized takes a protobuf containing (arbitrarily many) signature pairs (public key "prefix" and the signature bytes), as well as an account and the message (hash). (HIP-632 link for 'isAuthorized`)

Currently isAuthorizedRaw does it by directly using a platform Cryptography crypto engine, but:

A fee calculator (or something) needs to be part of the API because the signature verification - encapsulated in the app service - has a wide range of cost (keys can be very complex) that the system contract will need to charge.

(Additionally, before a new fee schedule is available we should get a much closer estimate of gas costs for signature verification - but perhaps this should be a separate ticket.)

This is a prerequisite for implementing isAuthorized in the smart contracts service.

david-bakin-sl commented 1 month ago

Per @netopyr:

We will introduce a new interface, SignatureVerifier, which will have the Singleton-Scope. To make it available, we will introduce an AppContext, which is given to all services during initialization. Most services will ignore it, but the Smart Contract Service keeps the reference to the SignatureVerifier, which contains the required functionality.

We decided to split counting the primitive keys and validate the signature. This will allow calculating the fees and checking them before calling isSigned().

The method isSigned()has an optional parameter verificationAssistant to influence the signature verification. The method will iterate through the Key-tree and call the verificationAssistant for each primitive key it encounters. If the response is VALID or INVALID, it will use the response. If the response is DELEGATE_TO_CRYPTOGRAPHIC_SIGNATURES, it does the regular check.

(My comment: The optional verificationAssistant will allow smart contracts to handle contract ID keys.)

// This interface will be provided to all services during initialization
interface AppContext {
    SignatureVerifier signatureVerifier()
}

interface SignatureVerifier {
    KeyCount countKeys(Key key);
    boolean isSigned(Key key, Bytes message, SignatureMap signatureMap, @Nullable VerificationAssistant verificationAssistant);
}

record KeyCount(int numEcdsaKeys, int numEddsaKeys);

interface VerificationAssistant {
    /**
    * Given a key that is not a KeyList or ThresholdKey, returns a decision of how to verify its signature.
    */
    VerificationDecision decideFor(Key simpleKey);
}

enum VerificationDecision { VALID, INVALID, DELEGATE_TO_CRYPTOGRAPHIC_SIGNATURES }