Abstract
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.
Motivation
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.
Overview
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.
Implementation
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
x5u
header. - 4: validate that certificate is signed by the smart contract deployment key.
- 5: obtain the address of the script signing key from the
SubjectPublicKeyInfo
field 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
<ds:SignatureValue>
tag. - 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
notBefore
andnotAfter
. - 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
3:
above, skipping6:
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.
Specification
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.
Format of the certificate and signature
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:
-
The
issuer
field 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.CN=0x12345678901234567890
. -
The
SubjectPublicKeyInfo
field MUST contain the public part of the script signing key. -
The
extensions
field SHOULD be set to includeKeyUsage
(see RFC 5280 sec. 4.2.1.3) 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. 4.2.1.12). -
If revocation option 1 (see below) is used, then
extensions
MUST also includecRLDistributionPoints
(see RFC 5280 4.2.1.13) which MUST contain at least onedistributionPoint
element, containing afullName
element. ThefullName
MUST be a singleIA5String
for auniformResourceIdentifier
which is an URI pointing to a Certificate Revocation List. -
The
notBefore
andnotAfter
field SHOULD be set to limit lifetime of the script signing key reasonably. -
The
version
field 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:
-
The
x5u
header MUST be included and stored as part of theJWS Protected Header
. Furthermore, its URI MUST point to the X.509 certificate of the script signing key. -
The
payload
member MUST be exactly the URL base64 encoding of the client script or an a Keccak of the client script.
Format to attach signature
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.
Revocation
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 notBefore
and 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
payload
is 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 appendrevokedVerificationAddr
to a list, which can then be returned through afunction revokedVerificationAddrs() external view returns(address[] memory)
. In this case, the address of theSubjectPublicKeyInfo
in the X.509 certificate MUST be validated to not be in the list of returnedrevokedVerificationAddrs
.
Validation
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
payload
of 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 thex5u
header). -
The JWS signature is validated according to RFC 7515, using the public key contained in the
SubjectPublicKeyInfo
field of the X.509 certificate. -
If the
payload
of the JWS does not contain the script, then it MUST be validated that thepayload
is 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
version
SHOULD 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 theissuer
field. -
The address stored in the Common Name of the
issuer
field 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
scriptURI
in 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 methodscriptURI()
. -
If a revocation mechanism is used, then it should validate that the X.509 certificate has not been revoked.