This EIP describes how to assert the authenticity of the script related to some token or smart contract, regardless of how the script was obtained.
Often NFT authors want to provide some user functionality to their tokens through client scripts. This should be done safely, without opening the user to potential scams. Refer to EIP-5169 examples of such scripts.
Although EIP-5169 specified a way to obtain a set of client scripts through URI, it is inapplicable for token contracts that were issued before the creation of EIP-5169. Furthermore, it lacks the finesse to address situations such as:
- A smart contract might have different scripts for different environments or use-cases. Take a subway token as an example. It might invoke a minimal script at the POS to be sent through NFC (Internet might be slow or inaccessible underground), while advanced functions for user retention, such as rewarding user mascot NFT for continued use, or carbon credit for buying carbon-neutral airfare.
- In a specific use case, a token’s script is often localized, and it doesn’t make sense to download and load all language translations.
- A specific use case might be compatible with only a specific version of the token’s script.
EIP-5169 returns an all-purpose, one-version of script for use on the client side.
This EIP offers a way to assert the authenticity of such client scripts disregarding how it is obtained and can work with smart contracts prior to the publication of this ERC.
Although the token/smart contract author and the client script author can be the same person/team, we will assume they are different people in this EIP, and the case that they are the same person/team can be implied.
The steps needed to ensure script authenticity can be summarized as follows:
The script author creates a script signing key, which has an associated verification key address.
The smart contract author, using the smart contract deployment key, signs a certificate, including expiry info and whose subject public key info contains the script signing key’s associated verification key.
Any client script which is deployed gets signed by the script signing key and with a certificate attached.
This process is a deliberate copy of the TLS certification, based on X.509, which has stood the test of time.
The authenticity of the client script may be obtained through the
scriptURI() function call, as in EIP-5169, or supplied separately by the use-cases. However, this ERC is applicable to any code or data that is signed, and a client must validate the signature in the way specified in this ERC. In real-life use-cases, the client scripts can be either supplied by the aforementioned
scriptURI() or offered to the client (wallet) in any way the wallet can work, even through NFC connections or QR codes.
Some possible methods of implementation: Note: we cannot deliver a completely off-line solution unless a hosting app can pre-verify the smart contract deployment key address.
- Simplest (assuming internet connection and written after publication of this EIP):
- JWS object outlined below (containing: The script itself, URI to certificate, signature of the script signed by script signing key) is linked to via scriptURI() in the contract.
- 1: determine smart contract deployment key. Contract should preferably implement standard ‘Ownable’ interface or at least the ‘owner()’ function.
- 2: fetch the JWS object by querying the scriptURI() in the contract.
- 3: fetch the certificate linked to from the JWS in the
- 4: validate that certificate is signed by the smart contract deployment key.
- 5: obtain the address of the script signing key from the
SubjectPublicKeyInfofield of the certificate.
- 6: obtain the script itself; in our example it will be bundled in the JWS object itself, along with the signature attached to the script, in the
- 7: verify that the signature from the
<ds:SignatureValue>tag is the correct signature of the keccak’d script by the script signing key.
- 8: current time is within
- 9: if the script signing key is still valid according to the certificate and the script has been signed correctly by the same key, allow user to interact with the token contract(s) defined in the script via the script interface.
- Example script applied to contract published before this EIP:
- User scans NFC beacon on their mobile.
- Obtains payload with an app intent (eg ticketing app) containing a URL to a JWS object.
- JWS Object is fetched along with the script pointed to from the JWS object.
- Determine origin contract from script.
- Obtain the owner of the script query
owner()or JWS object provides contract creation transaction the hosting app can validate and obtain smart contract deployment key address.
- Continue from
6:as we already have the script.
- If the script signing key is still valid according to the certificate and the script has been signed correctly by the same key, allow user to interact with the token contract(s) defined in the script via the script interface.
The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY” and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
The certificate for the script signing key MUST be in the X.509 format, in accordance with RFC 5280.
Furthermore, the certificate MUST be signed by the smart contract author as the issuer, using the smart contract deployment key.
We furthermore make the following requirements of the content of this certificate:
issuerfield MUST be populated with a Common Name, which MUST be the address of the verification key associated with the smart contract deployment key. E.g.
SubjectPublicKeyInfofield MUST contain the public part of the script signing key.
extensionsfield SHOULD be set to include
KeyUsage(see RFC 5280 sec. 18.104.22.168) with bit 0 set, to indicate that the script signing key is used for signing only. Furthermore the Extended Key Usage extensions SHOULD also be included, with only the id-kp-codeSigning identifier set (See RFC 52080 sec. 22.214.171.124).
If revocation option 1 (see below) is used, then
extensionsMUST also include
cRLDistributionPoints(see RFC 5280 126.96.36.199) which MUST contain at least one
distributionPointelement, containing a
fullNameMUST be a single
uniformResourceIdentifierwhich is an URI pointing to a Certificate Revocation List.
notAfterfield SHOULD be set to limit lifetime of the script signing key reasonably.
versionfield SHOULD be grater than 2, to indicate that the certificate is not a regular X.509 certificate.
We require the signature to be done as a JWS according to RFC 7515. Concretely we have the following requirements to the elements contained in the JWS:
x5uheader MUST be included and stored as part of the
JWS Protected Header. Furthermore, its URI MUST point to the X.509 certificate of the script signing key.
payloadmember MUST be exactly the URL base64 encoding of the client script or an a Keccak of the client script.
This EIP does not specify how a wallet client obtains the signature as this can be realized in multiple ways. The simplest of which is to embed the client script in the
payload of the JWS, as discussed above. This ensure that only a single URI is needed in order to locate both the client script, its signature and the signing key certificate.
If the client script is stored in a directory (e.g. on a webserver), i.e. with a file-name instead of with a hash digest identifier, then the JWS can simply be stored using the same URI as the client script, with “.jws” or “.sig” appended.
While the X.509 certificate SHOULD be issued with a limited lifetime, key leaks can still happen, and thus it should be possible to revoke an already issued X.509 certificate.
This EIP does not dictate how or if it should be done. But it is required that the
notAfter fields in the X.509 certificate are significantly constrained if the no revocation mechanism is used.
We furthermore define 2 OPTIONAL revocation mechanisms:
Using Certificate Revocation Lists (CRL). The smart contract author published a signed list of revoked certificates at one or more URIs. If this option is used, then the X.509 certificate MUST contain information on how to access the CRL, as already discussed. This EIP does not dictate the format of the CRL but recommends keeping it as simple as possible, such as letting it be a JWS, signed with the smart contract deployment key where the
payloadis a URL base64 encoding of a comma-separated list of (hex) Keccak hash digests of each revoked X.509 certificate. In this example, the signature of the CRL SHOULD be validated against the address of the smart contract deployment key which issued the X.509 certificate, when checking for revocation. Furthermore, when checking the revocation list it MUST be verified that the Keccak Hash digest of the X.509 certificate is not included in the CRL.
Storing a list of revoked verification key addresses in the smart Contract. A smart contract
function revokeVerificationAddr(address memory revokedVerificationAddr)could be added to the token smart contract, which MUST append
revokedVerificationAddrto a list, which can then be returned through a
function revokedVerificationAddrs() external view returns(address memory). In this case, the address of the
SubjectPublicKeyInfoin the X.509 certificate MUST be validated to not be in the list of returned
When it comes to validating the authenticity of client script, the following steps MUST be completed. If any of these steps fails, then the script MUST be rejected:
The client script, JWS and X.509 and the address of the smart contract deployment key MUST be fetched. Note that if the client script is embedded as the
payloadof the JWS, then an URI of the JWS uniquely defines how to learn both the signature (embedded in the JWS), the client script (embedded in the JWS), and the X.509 certificate (through the URI in the
The JWS signature is validated according to RFC 7515, using the public key contained in the
SubjectPublicKeyInfofield of the X.509 certificate.
payloadof the JWS does not contain the script, then it MUST be validated that the
payloadis the Keccak hash digest of the client script fetched.
The X.509 certificate is validated according to RFC 5280, with the exception that a value of
versionSHOULD be accepted if and only if it is greater than 2, and that the public key used to sign it is recovered from the signature itself. Furthermore, the recovered public key MUST be checked to represent the same address as stored in the Common Name in the
The address stored in the Common Name of the
issuerfield in the X.509 certificate is validated to be equal to the address of the smart contract deployment key.
If the client script/JWS is pointed to by a
scriptURIin accordance to EIP-5169, then it SHOULD also be validated that the URI of the client script/JWS is contained in the array returned by the smart contract method
If a revocation mechanism is used, then it should validate that the X.509 certificate has not been revoked.