Skip to content

Latest commit

 

History

History
671 lines (487 loc) · 22.1 KB

zencode.md

File metadata and controls

671 lines (487 loc) · 22.1 KB

Smart contracts in human language

Zenroom is software inspired by the language-theoretical security research and it allows to express cryptographic operations in a readable domain-specific language called zenCode.

For the theoretical background see the zenCode Whitepaper.

For an introduction see this blog post: Smart contracts for the English speaker.

Here we go with the tutorial to learn the zenCode language!

Syntax and Memory model

zenCode contracts operate in 3 phases:

  1. Given - validates the input
  2. When - processes the contents
  3. Then - prints out the results

The 3 separate blocks of code also correspond to 3 separate memory areas, sealed by security measures.

If any single line in a zenCode contract fails, Zenroom stops executing and returns the error.

zenCode documentation diagram

All data processed has first to pass the validation phase according to scenario specific data schemas.

Good Practice: start your zenCode noting down the Zenroom version you are using!

rule check version 1.0.0

Symmetric encryption

This is a simple technique to hide a secret using a common password known to all participants.

The algorithm used is AES-GCM with a random IV and an optional authenticated header (AEAD)

The encryption is applied using 3 arguments:

  • password can be any string (or file) used to lock and unlock the secret
  • message can be any string (or file) to be encrypted and decrypted
  • header is a fixed name and optional argument to indicate an authenticated header

These 3 arguments can be written or imported, but must given before using the I encrypt block:

The output is returned in secret message and it looks like:

{"secret_message":{"iv":"u64:-tU2gbox9kATCeC2k_zkhYM-PBA3IzvN7HtfyVXdzB4",
	"header":"u64:dGhpc19pc19mb3JfQm9i",
	"text":"u64:cw4M3FBO3zaPRAB26d6y8SMPGgAo_0AmJUrhg5dmKwoEB7BWLAAD_A2h",
	"checksum":"u64:UugLrIuxRX46BETc1-XkrA"}}

To decode make sure to have that secret password and that a valid secret message is given, then use:

So let's imagine I want to share a secret with someone and send secret messages encrypted with it:

sequenceDiagram
        participant A as Anon1
        participant B as Anon2
        A->>A: Think of a secret password
    A->>B: Tell the password to a friend
        A->>A: Encrypt a secret message with the password
    A->>B: Send the secret message to the friend
    B->>B: Decrypts the secret message with the password
Loading

Of course the password must be known by all participants and that's the dangerous part, since it could be stolen.

We mitigate this risk using public-key cryptography, also known as a-symmetric encryption, explained below.


Asymmetric encryption

We use asymmetric encryption (or public key cryptography) when we want to introduce the possession of keypairs (public and private) both by Alice and Bob: this way there is no need for a single secret to be known to both.

Fortunately it is pretty simple to do using zenCode in 2 steps

  • Key generation and exchange (SETUP)
  • Public-key Encryption or signature (ECDH and ECDSA)

Key generation and exchange

In this phase each participant will create his/her own keypair, store it and communicate the public key to the other.

The statement to generate a keypair (public and private keys) is simple:

It will produce something like this:

"Alice": {
   "keypair": {
      "private_key": "u64:F_NaS3Y6Xw6BW...",
      "public_key": "u64:BLG0OGDwzP_gY41TZgGpUB4lTYCgpx9BJVScxSQAfwqEi..."
   }
}

Where the public key is usually a longer octet and actually an Elliptic Curve Point coordinate.

There is nothing preventing an host application to separate these JSON fields and store them in any secure way.

Here we demonstrate how to create keypairs as well separate them using zenCode:

  • 2 contracts to create Alice and Bob keypairs
  • 2 contracts to separate the public key from the private key for each
sequenceDiagram
    participant A as Alice
    participant B as Bob
    A->>A: create the keypair
    A->>B: publish the public key
    B->>B: create the keypair
    B->>A: publish the public key
Loading

After both Alice and Bob have their own keypairs and they both know each other public key we can move forward to do asymmetric encryption and signatures.

"Alice": {
   "keypair": {
      "private_key": "u64:F_NaS3Y6Xw6BW...",
      "public_key": "u64:BLG0OGDwzP_gY41TZgGpUB4lTYCgpx9BJVScxSQAfwqEi..."
   }
}

"Alice": {
   "public_key": "u64:BLG0OGDwzP_gY41TZgGpUB4lTYCgpx9BJVScxSQAfwqEi..."
}

The advantage of using zenCode here is the use of the valid keyword which effectively parses the public key object and verifies it as valid, in this case as being a valid point on the elliptic curve in use. This greatly reduces the possibility of common mistakes.

Public-key Encryption (ECDH)

Public key encryption is similar to the asymmetric encryption explained in the previous section, with a difference: the from and for clauses indicating the public key of the recipient.

Before getting to the encryption 2 other objects must be given:

  • keypair is one's own public and private keys
  • public key from the intended recipient

So with an input separated between DATA and KEYS or grouped together in an array like:

[
  {"Bob": {"public_key":"u64:BGF59uMP0DkHoTjMT..."} },
  {"Alice": { "keypair": {
      "private_key": "u64:F_NaS3Y6Xw6BW...",
      "public_key": "u64:BLG0OGDwzP_gY41TZgGpUB4lTYCgpx9BJVScxSQAfwqEi..." } } }
]

which encrypts and stores results in secret message; also in this case header may be given, then is included in the encryption as an authenticated clear-text section.

sequenceDiagram
        participant A as Alice
        participant B as Bob
    A->>A: prepare the keyring
    A->>A: encrypt the message
#    Note over A,B: Given that I am known as 'Alice'<br/>and I have my 'keypair'<br/>and I have a 'public key' from 'Bob'<br/>When I write 'my secret' in 'draft'<br/>and I encrypt the 'draft' to 'secret message' for 'Bob'<br/>Then print the 'secret message'<br/>
        A->>B: send the secret message
#       Note over A,B: Given that I am 'Bob'<br/>and I have my valid 'keypair'<br/>and I have a 'public key' from 'Alice'<br/>Then print my 'keypair'<br/>and print the 'public key'
    B->>B: prepare the keyring
#       Note over A,B: Given that I am known as 'Bob'<br/>and I have my valid 'keypair'<br/>and I have a 'public key' from 'Alice'<br/>and I have a valid 'secret message'<br/>When I decrypt the 'secret message' from 'Alice' to 'clear text'<br/>Then print as 'string' the 'clear text'<br/>and print the 'header' inside 'secret message'<br/>
        B->>B: decrypt the message
Loading

1. Alice encrypts the message using Bob's public key

2. Bob prepares a keyring with Alice's public key

3. Bob decrypts the message using Alice's public key

In this basic example the session key for encryption is made combining the private key of Alice and the public key of Bob (or vice versa).

When I write 'my secret for you' in 'message'
and I write 'an authenticated message' in 'header'

The decryption will always check that the header hasn't changed, maintaining the integrity of the string which may contain important public information that accompany the secret.

Public-key Signature (ECDSA)

Public-key signing allows to verify the integrity of a message by knowing the public key of all those who have signed it.

It is very useful when in need of authenticating documents: any change to the content of a document, even one single bit, will make the verification fail, showing that something has been tampered with.

The one signing only needs his/her own keypair, so the key setup will be made by the lines:

Given that I am known as 'Alice'
and I have my valid 'keypair'

then assuming that the document to sign is in draft, Alice can proceed signing it with:

and I create the signature of 'draft'

which will produce a new object signature to be printed along the draft: the original message stays intact and the signature is detached.

On the other side Bob will need Alice's public key to verify the signature with the line:

When I verify the 'draft' is signed by 'Alice'

which will fail in case the signature is invalid or the document has been tampered with.

sequenceDiagram
        participant A as Alice
        participant B as Bob
#    Note over A,B: Given that I am known as 'Alice'<br/>and I have my 'keypair'<br/>When I write 'This is my signed message to Bob.' in 'draft'<br/>and I sign the 'draft' as 'signed message'<br/>Then print my 'signed message'
    A->>A: sign the message     as Alice
        A->>B: send the signed message
#       Note over A,B: Given that I am 'Bob'<br/>and I have my valid 'keypair'<br/>and I have a 'public key' from 'Alice'<br/>Then print my 'keypair'<br/>and print the 'public key'
    B->>B: prepare the keyring
#       Note over A,B: Given that I am known as 'Bob'<br/>and I have inside 'Alice' a valid 'public key'<br/>and I have a draft inside 'Alice'<br/>and I have a valid 'signed message'<br/>When I verify the 'signed message' is authentic<br/>Then print 'signature' 'correct'<br/>and print as 'string' the 'text' inside 'signed message'
        B->>B: verify the signature by Alice
Loading

Here we continue assuming that the keyrings are already prepared with public/private keypairs and the public keypair of the correspondent.

1. Alice signs a message for Bob

1. Bob verifies the signed message from Alice

In this example Alice uses her private key to sign and authenticate a message. Bob or anyone else can use Alice's public key to prove that the integrity of the message is kept intact and that she signed it.


Attribute Based Credentials

Alice in Wonderland

Attribute Based Credentials are a powerful and complex feature implemented using the Coconut crypto scheme. This is the most complex functionality available in Zenroom and it will show how the zenCode language really simplifies it.

Let's imagine 3 different subjects for our scenarios:

  1. Mad Hatter is a well known issuer in Wonderland
  2. Wonderland is an open space (a blockchain!) and all inhabitants can check the validity of proofs
  3. Alice just arrived: to create proofs she'll request a credential to the issuer MadHatter

When Alice is in possession of credentials then she can create a proof any time she wants using as input:

  • the credentials
  • her credential keypair
  • the verifier by MadHatter

All these "things" (credentials, proofs, etc.) are data structures that can be used as input and received as output of zenCode functions. For instance a proof can be print in JSON format and looks a bit list this:

{
   "credential_proof" : {
      "pi_v" : {
         "c" : "u64:tBrCGawWYEAi55_hHIPq0JT3OaapOebSHVW0GhjJcAk",
         "rr" : "u64:J7R3FXsI2dcfyZRCqWA8fDYijG39P16LvGpX90wtCWw",
         "rm" : "u64:QoG-28CNTAY3Ir4SQqVoK1ZpTlzOnXxX6Xtq5KMIxpo"
      },
      "nu" : "u64:BA77WYvBRsc53uAyrqTjuUdptJPZbcTlzr9icizm0...",
      "sigma_prime" : {
         "h_prime" : "u64:BB9AM5xjWPxsZ47zh1WAmFymru66W6YuK...",
         "s_prime" : "u64:BAGYNM6JO0wRAGE87_-bQVuhUXeEoeJrh..."
      },
      "kappa" : "u64:GFVYsudbHOJNzPl3ZL0_VzB_DRvrPKF26OCZR9..."
   },
   "zenroom" : {
      "scenario" : "coconut", "encoding" : "url64", "version" : "1.0.0"
   }
}

Anyone can verify proofs using as input:

  • the credential proof
  • the verifier by MadHatter

What is so special about these proofs? Well! Alice cannot be followed by her trail of proofs: she can produce an infinite number of proofs, always different from one another, for anyone to recognise the credential without even knowing who she is.

even the MadHatter is surprised

Imagine that once Alice is holding credentials she can enter any room in Wonderland and drop a proof in the chest at its entrance: this proof can be verified by anyone without disclosing Alice's identity.

The flow described above is pretty simple, but the steps to setup the credential are a bit more complex. Lets start using real names from now on:

  • Alice is a credential Holder
  • MadHatter is a credential Issuer
  • Wonderland is a public Blockchain
  • Anyone is any peer connected to the blockchain
graph LR
          subgraph Sign
                           iKP>issuer keypair] --- I(Issuer)
                           hRQ --> I
                           I --> iSIG
          end
          subgraph Blockchain
                           iKP --> Verifier
                           Proof
          end
          subgraph Request
                           H --> hKP> credential keypair ]
                           hKP --> hRQ[request]
          end
          iSIG[signature] --> H(Holder)
          H --> CRED(( Credential ))
          CRED --> Proof
          Proof --> Anyone
      Verifier --> Anyone
Loading

To add more detail, the sequence is:

sequenceDiagram
        participant H as Holder
        participant I as Issuer
        participant B as Blockchain
        I->>I: 1 create a issuer keypair
        I->>B: 1a publish the verifier
        H->>H: 2 create a credential keypair
        H->>I: 3 send a credential request
        I->>H: 4 reply with the credential signature
        H->>H: 5 aggregate the credentials
        H--xB: 6 create and publish a blind credential proof
        B->>B: 7 anyone can verify the proof
Loading

1 MadHatter generates an issuer keypair

InputzenCodeOutput
-

issuer_keypair

1a MadHatter publishes the verification key

InputzenCodeOutput
issuer_keypair

issuer_verifier

2 Alice generates her credential keypair

InputzenCodeOutput
-

credential_keypair

3 Alice sends her credential signature request

InputzenCodeOutput
credential_keypair

credential_request

4 MadHatter decides to sign a credential signature request

InputzenCodeOutput
credential_request
issuer_keypair

issuer_signature

5 Alice receives and aggregates the signed credential

InputzenCodeOutput
issuer_signature
credential_keypair

credential

Centralized credential issuance

Lets see how flexible is zenCode.

The flow described above is for a fully decentralized issuance of credentials where only the Holder is in possession of the credential keypair needed to produce a credential proof.

But let's imagine a much more simple use-case for a more centralized system where the Issuer provides the Holder with everything ready to go to produce zero knowledge credential proofs.

The implementation is very, very simple: just line up all the When blocks where the different operations are done at different times and print the results all together!

Scenario coconut
Given that I am known as 'Issuer'
When I create the issuer keypair
and I create the credential keypair
and I create the credential request
and I create the credential signature
and I create the credentials
Then print the 'credentials'
and print the 'credential keypair'

This will produce credentials that anyone can take and run. Just beware that in this simplified version of ABC the Issuer may maliciously keep the credential keypair and impersonate the Holder.

Try it on your system!

Impatient to give it a spin? run zenCode scripts locally to see what are the files produced!

Make sure that Zenroom is installed on your PC and then go to the...

[Online Interactive Demo](/demo) [Shell Script Examples](/examples/shell_scripts)

Zero Knowledge Proofs

There is more to this of course: zenCode supports several features based on pairing elliptic curve arithmetics and in particular:

  • non-interactive zero knowledge proofs (also known as ZKP or ZK-Snarks)
  • threshold credentials with multiple decentralised issuers
  • homomorphic encryption for numeric counters

These are all very useful features for architectures based on the decentralisation of trust, typical of DLT and blockchain based systems, as well for off-line and non-interactive authentication.

The zenCode language leverages two main scenarios, more will be implemented in the future.

  1. Attribute Based Credentials (ABC) where issuer verification keys represent specific credentials
  2. A Petition system based on ABC and homomorphic encryption

Three more are in the work and they are:

  1. Anonymous proxy validation scheme
  2. Token thumbler to privately transfer numeric assets
  3. Private credential revocation

Import, validate and transform data

Given

Self introduction

This affects my statements

   Given I introduce myself as ''
   Given I am known as ''
   Given I am ''
   Given I have my ''
   Given I have my valid ''

Data provided as input (from data and keys) is all imported automatically from JSON or CBOR binary formats.

Scenarios can add Schema for specific data validation mapped to words like: signature, proof or secret.

Data input

   Given I have a ''
   Given I have a valid ''
   Given I have a '' inside ''
   Given I have a valid '' inside ''
   Given I have a '' from ''
   Given I have a valid '' from ''
   Given the '' is valid

or check emptiness:

   Given nothing

When valid is specified then extra checks are made on input value, mostly according to the scenario

Settings

rule input encoding [ url64 | base64 | hex | bin ]
rule input format [ json | cbor ]

When

Processing data is done in the when block. Also scenarios add statements to this block.

Without extensions, these are the basic functions available

when:
  - {when: 'I append '''' to '''''}
  - {when: 'I write '''' in '''''}
  - {when: 'I set '''' to '''''}
  - {when: 'I create a random '''''}
  - {when: 'I create a random array of '''' elements'}
  - {when: 'I create a random '''' bit array of '''' elements'}
  - {when: 'I set '''' as '''' with '''''}
  - {when: 'I append '''' as '''' to '''''}
  - {when: 'I write '''' as '''' in '''''}

Then

Output is all exported in JSON or CBOR

then:
  - {then: 'print '''' '''''}
  - {then: 'print all data'}
  - {then: 'print my data'}
  - {then: 'print my data'}
  - {then: 'print my '''''}
  - {then: 'print as '''' my '''''}
  - {then: 'print my '''' as '''''}
  - {then: 'print the '''''}
  - {then: 'print as '''' the '''''}
  - {then: 'print as '''' the '''' inside '''''}

Settings:

rule output encoding [ url64 | base64 | hex | bin ]
rule output format [ json | cbor ]