Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/drand js support #21

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 86 additions & 0 deletions docs/tlock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Timelock Encryption

Our timelock encryption scheme is a hybrid cryptosystem using both AES-GCM and FullIdent (Identity based encryption). The goal is to be able to encrypt any-length messages for future rounds of the ETF post finality gadget.

## Background

### AES-GCM
AES-GCM is a symmetric stream cipher, meaning you need to use the same key and nonce to encrypt and decrypt messages.

1. $ct \leftarrow AES.Enc(message, key, nonce)$
2. $m \leftarrow AES.Dec(ct, key, nonce)$


### BF-IBE


Identity based encryption is a scheme were a message can be encrypted for an arbitrary string, rather than some specific public key. For example, a message could be encrypted for "[email protected]" so that only the owner of the identity "[email protected]" is able to decrypt the message. Our construction uses the BF-IBE "FullIdent" scheme, which is IND-ID-CCA secure.

The scheme is instantiated with a private input of a master secret key and public input as the output of a bilinear Diffie-Hellman parameter generator, which is PPT algorithm that outputs a prime number $q$, the description of two groups $G_1$, $G_2$ of order $q$, and the description of an admissible bilinear map $\hat{e} : G_1 \times G_1 \to G_2$. In our case, we will instead us a bilinear map $e: G_1 \times G_2 \to G_2$ (a type III pairing).

It consists of four PPT algorithms (Setup, Extract, Encrypt, Decrypt) defined as:

- $(pp, s) \leftarrow Setup(1^\lambda)$ where $\lambda$ is the security parameter, $pp$ is the output (system) params and $s$ is the IBE master secret key. The system params are a generator $G \in \mathbb{G}_1$ and commitment to the master key, $P_{pub} = sG$.

- $sk_{ID} \leftarrow Extract(mk, ID)$ outputs the private key for an $ID \in \{0, 1\}^*$.

- $Encrypt(pp, ID, m) \to ct$ outputs the ciphertext $ct$ for any message $m \in \{0, 1\}^*$.

- $Decrypt(sk_{ID}, ct) \to m$ outputs the decrypted message $m$

We use the BF-IBE "FullIdent" scheme to encrypt messages such that their decryption key is broadcast as the output of at specific future time step of the computational reference clock. FullIdent is IND-ID-CCA secure. In FullIdent, public parameters are stored in $\mathbb{G}_1$, and the scheme uses type 1 pairings. We will instead use type 3 pairings, so our public parameters are in $\mathbb{G}_2$ instead.

$\mathbf{Setup}$

Let $e: \mathbb{G}_1 \times \mathbb{G}_2 \to \mathbb{G}_2$ be a bilinear map, $H_1: \{0, 1\}^* \to \mathbb{G}_1$ a hash-to-G1 function, $H_2: \mathbb{G}_2 \to \{0, 1\}^n$ for some $n$, $H_3: \{0, 1\}^n \times \{0, 1\}^n \to \mathbb{Z}_q$, and a cryptographic hash function $H_4: \{0, 1\}^n \to \{0, 1\}^n$. Choose a random $s \xleftarrow{R} \mathbb{Z}_p$ and a generator $P \xleftarrow{R} \mathbb{G}_1$. Then, broadcast the value $P_{pub} = sP$.

$\mathbf{Extract}$

Compute the IBE secret for an identity $ID$ with $d_{ID} = sQ_{ID}$ where $Q_{ID} = H_1(ID)$

$\mathbf{Encryption}$

Let $M \in \{0, 1\}^n$ be the message and $t > 0$ be some future time slot in the CRC $\mathcal{C}$ for which we want to encrypt a message and assume it has a unique id, $ID_t$.

- Compute $Q_{ID_t} = H_1(ID_t) \in \mathbb{G}_1$
- Choose a random $\sigma \in \{0, 1\}^n$
- set $r = H_4(\sigma, M)$
- Calculate the ciphertext
$C = \left<U, V, W\right> = \left< rP, \sigma \oplus H_2(g^r_{ID}), M \oplus H_4(\sigma) \right>$


where $g_{ID} = e(Q_{ID}, P_{pub}) \in \mathbb{G}_2$

$\mathbf{Decryption}$
A benefit of the FullIdent scheme is that the decryption algorithm allows for verification that the ciphertext was properly encrypted, so it's not possible to attempt to decrypt data that isn't yours.

For a ciphertext $C = \left <U, V, W\right >$ encrypted using the time slot $t$. Then $C$ can be decrypted with the private key $d_{ID_t} = s Q_{ID_t} \in \mathbb{G}_1$, where $s$ is the IBE master secret, as such:


- Compute $V \oplus H_2(e(d_{ID_t}, U)) = \sigma$
- Compute $W \oplus H_4(\sigma) = M$
- Set $r = H_3(\sigma, M)$. Check if $U = rP$. If not, reject the ciphertext.
- Output $M$ as the decryption of $C$


## Tlock

### Encryption

We want to encrypt a message $m \in \{0, 1\}^*$ for an identity $ID \in \{0, 1\}^*$ with some threshold $t > 0$.

1. Choose $s \xleftarrow{R} \mathbb{Z}_p$ and broadcast $P_{pub} = sP$ where $P \in \mathbb{G}_2$ is a commonly agreed on generator. Also randomly sample a 96-bit nonce, $N$.
2. Encrypt the message using AES-GCM, producing: $ct \leftarrow AES.Enc(m, s, N)$
3. Encrypt the AES key for the identity using IBE: $ct' \leftarrow IBE.Enc(s, ID)$

Then the ciphertext contains $(ct, ct')$.

### Decryption

Timelock decryption can occur when a threshold of signers have produced valid BLS signatures for the given identity.

Given ciphertexts $(ct, ct')$, a nonce $N$ and an identity $ID$, decryption is as follows:
1. Collect at least a thresold of BLS sigantures and DLEQ proofs. Interpolate the signatures and aggregate the proofs to get $(\sigma = interpolate(\{\sigma_i\}_{i \in [n]}), \pi = \sum_i \pi_i)$ and verify the proof. If it is invalid, then do not proceed.
2. The signature $\sigma$ is the IBE secret associated with the public key $P_{pub}$. Then use the secret to decrypt the ciphertext $ct'$ to recover the AES key: $k \leftarrow IBE.Decrypt(P_{pub}, ct', \sigma)$. If decryption fails, then we stop.
3. Use the recovered $k$ to attempt to decrypt the ciphertext $ct$: $m \leftarrow AES.Decrypt(ct, N, k)$.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ideallabs/timelock.js": "^1.0.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"buffer": "^6.0.3",
"crypto-browserify": "^3.12.1",
"@ideallabs/timelock.js": "^1.0.0-dev",
"js-crypto-hkdf": "^1.0.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"stream-browserify": "^3.0.0",
"uint64be": "^3.0.0",
"vm-browserify": "^1.1.2",
"web-vitals": "^2.1.4"
},
Expand Down
134 changes: 134 additions & 0 deletions examples/web/react-tlock-demo/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright 2024 by Ideal Labs, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import './App.css'
import React, { useEffect, useState } from 'react'
import { Timelock, IdealNetworkIdentityBuilder, DrandIdentityBuilder, SupportedCurve, u8a } from '@ideallabs/timelock.js'
import hkdf from 'js-crypto-hkdf'

function App() {

const [timelockDrand, setTimelockDrand] = useState(null)
const [timelockIdeal, setTimelockIdeal] = useState(null)

useEffect(() => {
Timelock.build(SupportedCurve.BLS12_381).then((tlock) => {
setTimelockDrand(tlock)
})

Timelock.build(SupportedCurve.BLS12_377).then((tlock) => {
setTimelockIdeal(tlock)
})

}, [])

const fromHexString = (hexString) =>
Uint8Array.from(
hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
)

// 83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a
const runDemoDrand = async () => {
// 1. Setup parameters for encryption
// use an hkdf to generate an ephemeral secret key
const seed = new TextEncoder().encode('my-secret-seed')
const hash = 'SHA-256'
const length = 32
const esk = await hkdf.compute(seed, hash, length, '')
const key = Array.from(esk.key)
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('')
// the message to encrypt for the future
const message = 'Hello, Timelock!'
const encodedMessage = new TextEncoder().encode(message)
// A randomness beacon public key (ex: IDN public key)
// We first get it as hex and then convert to a Uint8Array
const pubkey =
'83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a'
// A future round number of the randomness beacon
const roundNumber = 1000
// 2. Encrypt the message
let ct = await timelockDrand.encrypt(
encodedMessage,
roundNumber,
DrandIdentityBuilder,
pubkey,
key
)
console.log('Timelocked ciphertext: ' + JSON.stringify(ct))

// 3. Acquire a signature for decryption from he pulse output by the beacon at the given roundNumber
const sigHex =
'b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39'
// Decrypt the ciphertext with the signature
const plaintext = await timelockDrand.decrypt(ct, sigHex)
// console.log(plaintext)
console.log(`Recovered ${String.fromCharCode(...plaintext)}, Expected ${message}`)
}

const runDemoIdeal = async () => {
// 1. Setup parameters for encryption
// use an hkdf to generate an ephemeral secret key
const seed = new TextEncoder().encode('my-secret-seed')
const hash = 'SHA-256'
const length = 32
const esk = await hkdf.compute(seed, hash, length, '')
const key = Array.from(esk.key)
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('')
// the message to encrypt for the future
const message = 'Hello, Timelock!'
const encodedMessage = new TextEncoder().encode(message)
// A randomness beacon public key (ex: IDN public key)
// We first get it as hex and then convert to a Uint8Array
const pubkey =
'41dc53da3d3617a189c85c8cb51a5f4fdfcebda05c50e81595f69e178d240fce3acdafd97b5fd204553e685836393a00b112f5cd78477d79ac8094c608d35bb42bd5091c5bbedd881e2ee0e8492a4361c69bf15250d75aee44035bc5b7553100'
// A future round number of the randomness beacon
const roundNumber = 10

// 2. Encrypt the message
let ct = await timelockIdeal.encrypt(
encodedMessage,
roundNumber,
IdealNetworkIdentityBuilder,
pubkey,
key
)

console.log('Timelocked ciphertext: ' + JSON.stringify(ct))

// 3. Acquire a signature for decryption from he pulse output by the beacon at the given roundNumber
const sig =
'e6cdf6c9d11c13e013b2c6cfd11dab46d8f1ace226ff845ffff4c7d6f64992892c54fb5d1f0f87dd300ce66f53598e01'
// Decrypt the ciphertext with the signature
const plaintext = await timelockIdeal.decrypt(ct, sig)
console.log(`Recovered ${String.fromCharCode(...plaintext)}, Expected ${message}`)
}

return (
<div className="App">
<h1>Timelock Encryption Demo</h1>
<p>
Open the developer console (F12) and then click the button below to
execute the demo.
</p>
<button onClick={runDemoDrand}>Run Demo (Drand)</button>
<button onClick={runDemoIdeal}>Run Demo (IDN)</button>
</div>
)
}

export default App
Loading