Weekly design meeting, #16 (simple attestation format)

Note that there will be no meeting on 21th since I will be in Melbourne; the next meeting will be on 28th.

This meeting picks up the data-object topic from meeting #14, #13 and #12, and aim to create a minimalist attestation structure. This is an extremely early-stage draft.

We reüse a variation of X.509 for attestation.

Attestation  ::=  SEQUENCE  {
     signedInfo           SignedInfo,
     signatureAlgorithm   AlgorithmIdentifier,
     signatureValue       BIT STRING  }

SignedInfo  ::=  SEQUENCE  {
     version         [0]  EXPLICIT Version,
     serialNumber         CertificateSerialNumber,
     signature            AlgorithmIdentifier,
     issuer               Name OPTIONAL,
     validity             Validity OPTIONAL,
     subject              Name OPTIONAL,
     subjectPublicKeyInfo SubjectPublicKeyInfo OPTIONAL,
     contract             SEQUENCE OF SmartContract OPTIONAL,
     attestsTo            CHOICE {
         dataObject  [?]      TicketDataObject -- any data object, ticket  is an example
         extensions  [3]      EXPLICIT Extensions
     }
}
SubjectPublicKeyInfo  ::=  SEQUENCE  {
    algorithm            AlgorithmIdentifier,
    subjectPublicKey     BIT STRING  }

The last element DataObject depends on the schema of the data object. The only common theme of all data object's schema is that it is contained in an outer SEQUENCE and the first element in the sequence is an optional objectClass identified by an OBJECT IDENTIFIER.

For exmaple, for a data object that represents a football-match ticket, we have:

ticket ::= TicketDataObject
TicketDataObject ::= SEQUENCE {
       objectClass OBJECT IDENTIFIER OPTIONAL,
       numero      INTEGER,
       match       INTEGER {1..51}
       class       ENUMERATED { club(0), lounge(1), silver(2), gold(3) },
       admission   INTEGER
}

In TokenScript its ASN.X representation is used†.

† An example ASN.X schema was given in the medium article, search for the word "enumeration".

A few notes on the differences between attestation and X.509 certificate:

X.509 v3 certificates is a subset of attestations.

An attestation attests to some claims (made in the form of one or more data-objects), not necessarily the key holder's identity.

In this example of a football match ticket, the attestation attests to the right of entering the venue. His identity (traditionally stored as CommonName in X.509 v3) is irrelevant.

An attestation which attests to the key holder's identity (typically through CommonName) should be indistinguishable from an X.509 certificate. Making an identity attestation the same as a X.509 certificate allows us to use the same data in 3 scenarios:

  1. in a blockchain transaction;
  2. for authentication purpose to join a permissioned blockchain;
  3. for the key-exchange purpose to establish a secure connection to a service (e.g. web service).

Which corresponds to the 3 uses of keys: signing, authenticating and encrypting.

Versioning

I feel at this stage our lives will be easier if we don't need to stay compatible with X.509 v2 and v1, hence I removed the DEFAULT value of version.

issuer, subject, validity, subjectPublicKeyInfo all becomes OPTIONAL

subject is optional

subject remains in the form of a Distinguished Name, as per X.509 requirement. For example, for this ticket (represented in JSON format):

{
    "numero": 24,
    "match": 1,
    "class": 2,
    "admission": 1
}

The subject shall be: numero=24.

subject is optional because we don't want to solve the problem of how to grant an OID to the ad-hoc field. There are a few options but we decide to skip them for now. Also, because in simple use-cases, the serialNumber can already be used to identify an attestation. So a ticket can be as simple as this:

{
    "match": 1,
    "class": 2,
    "admission": 1
}

This wouldn't work in more advanced attestations. For example, a driver's licence, once re-issued, would have a different serialNumber but the same licenceNumber because other processes/attestations depend on the licence constant.

issuer is optional

Because the contract may have an internal method to assert who is a trusted issuer (e.g. from the signature alone).

validity is optional

Because the contract may have an internal mechanism by which time the attestation is no longer valid. e.g. an event ticket is invalidated when the event finishes. If the event date is changed, the smart contract is reconfigured without requiring the attestations to be reïssued.

subjectPublicKeyInfo is optional

Because an attestation may bind some claims ("facts") to another attestation instead of a public key.

Consider, for example, that the ticket is granted to anyone who can prove that his email address is join.smith@gmail.com. This makes the ticket attestation depend on another X.509 certificate on that email address. In this case, the certificate has the subjectPublicKeyInfo but the attestation doesn't.

added optional Contract element

It says to which contract, or contracts, the attestation can be used in a transaction. A better way of managing this may come out in the future (e.g. a list of contracts returned by a contract).

Not having Contract element means the attestation can be used on any contracts that don't require a specific Contract element.

An introductory article on why are we modelling data is published on Medium

Unresolved issues related to compatibility with X.509 v3.

Versioning

Should we put a version number on attestations that don't conform X.509?

One idea is to put -1 and grow version number negatively from there. The rational doing this, instead of putting 4 there, is so that if ITU issues an X.509 v4 format, we can include support for that. I feel that this topic is important to prepare for later implementation of Merklised attestations, since that's the moment dataObject start to have other requirements than the optional first element objectClass.

Can we safely drop issuerUniqueID and subjectUniqueID

I also removed issuerUniqueID and subjectUniqueID since I thought they are unused in X.509 v3. I'm much in favour of starting simple and add backwards compatibility if we have to. Note that certificates issued to blockchain identities are probably issued after 2020, and the reason for staying compatibility is mostly to prevent services (e.g. TLS server) ejecting these certificates as malformed, not to reuse existing certificate on the blockchain.

Should we assign a tag to dataObject?

And what it should be?

Unfortunately secp256r1 is not natively supported in Solidity. It is possible to use it by implementing the code for signing and verification in Solidity manually as has been done in this project https://github.com/tdrerup/elliptic-curve-solidity. However, this of course comes at a higher cost in gas than a secp256k1 signing/verification which is natively supported. I am not sure of exactly how expensive it is as I have not looked into the implementation but I would not be surprised if it is possible to leverage some other native calls of Solidity in the implementation. Furthermore it does not seem possible to support secp256k1/Keccak in a standardised way either. The reason is that there does not seem to be an oid on Keccak. Looking for an oid on Keccak on http://oid-info.com only returns the algorithms SHAKE-128, SHAKE-256. Which are based on SHA3, and as SHAKE is an acronym for Standard Hash Algorithm KEccak, one would imagine these to to be Keccak. However, they are not. They simply implement the arbitrary digest length version of SHA3 and thus do not give same outputs as Keccak as can be checked here https://asecuritysite.com/encryption/s3 In conclusion we will only be able to support secp256k1/SHA256

We could also take the x509 version number and prepend a couple of zeros and then use this as a suffix for version counting. This allows attestation versioning to be based on specific versions of x509. For example take x509 v.3 then we use 1003 to be attestation version 1 for X509 v3 or 2001 to be attestation version 2 for x509 v.1

They are basically optional in X.509v3 so it should be are to remove them if we assume all servers in the future will be compatible with X509v3 and if we are sure there is not a use-case their addition might serve for us?

Good idea, except we can't use 1003 - let's keep the version element taking only 1 octet in general. Gas is expensive.

Let's use 0x𝑎𝑏 where 𝑎 is the attestation version and 𝑏 is the x.509 version. This gives us an upper limit of 16 versions. Should that not enough, we can always use the next 1 bis for smaller revision numbers.

0x00 -> attestation version 1
0x02 -> attestation that can act as X.509 v3
0x10 -> attestation version 2
0x12 -> attestation version 2 based on X.509 v3

If by the time we made version 5, we realised that 16 versions are not going to be enough, we may invade another bit, and make the next version 5.1:

0x4A -> attestation version 5.1 based on X.509 v3 

(0xA is 1010 in binary, where the first 1 bit is the minor versions for attestations)

I'll ignore the minor inconsistence that the major version 1 is encoded 0 while the minor version 1 is encoded as 1, since it's a future problem that may or may not manifest.

BTW the oid for ECSDA with SHA256 is 1.2.840.10045.4.3.2. Thus this oid should be used for AlgorithmIdentifier. Note that the specific choice of curve is not included here as Parameters, but are instead part of the public key itself. For this note that the oid of secp256k1 is 1.3.132.0.10.

When some sequences of data elements are optional then there will exist several valid encodings under this module and thus every encoding will not be unique. Such a situation is not allowed by ASN1. There are several ways of fixing this. Originally I updated the schemes to allow for NULL types to ensure a unique decoding. However, I have realised that both issuer and subject, which have type Name, allows for an empty sequence. Thus I think that an empty sequence is a better (or as a minimum another) way of indicating that they are not included rather than forcing a NULL object when empty content is allowed instead.