From 9f67b532ae73dc34e2bcf259ef5452c733939a7c Mon Sep 17 00:00:00 2001 From: Patrick McClurg Date: Mon, 8 Aug 2022 11:06:45 +0200 Subject: [PATCH] timelockEncrypt no longer needs a client info object * added a README * added an eslint config --- .eslintrc.cjs | 11 ++++++++++ README.md | 31 +++++++++++++++++++++++++++ src/drand/drand-client.ts | 7 +++++- src/index.ts | 11 +++++----- test/drand/mock-drand-client.ts | 5 +++++ test/drand/timelock-decrypter.test.ts | 4 ++-- test/drand/timelock.test.ts | 7 +++--- 7 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 .eslintrc.cjs create mode 100644 README.md diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..3ac3376 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,11 @@ +module.exports = { + root: true, + parser: "@typescript-eslint/parser", + plugins: [ + "@typescript-eslint", + ], + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + ], +}; diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba2a164 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# tlock-js + +A typescript library for encrypting data which can only be decrypted at a set time in the future using [drand](https://drand.love). +tlock-js uses [AGE](https://age-encryption.org/v1) to symmetrically encrypt a payload, and encrypts the symmetric key using [pairing-based cryptography](https://drand.love/docs/cryptography/#pairing-based-cryptography) ensuring that it can only be decrypted when the drand's [threshold network](https://drand.love/docs/cryptography/#randomness-generation) has generated randomness at a future point. + +## Prerequisites +- Node 16+ + +## Quickstart +- install the dependencies by running `npm install` +- run the tests with `npm test` + +### `timelockEncrypt` +This encrypts a payload that can only be decrypted when the `roundNumber` has been reached. +The time of this `roundNumber` depends on the genesis and round frequency of the network you connect to. +By default, the drand testnet HTTP client will be used, but you can implement your own and pass it in here. +The output ciphertext should be compatible with any of the drand tlock implementations + +### `timelockDecrypt` +This takes a payload that has been encrypted with any of the drand tlock implementations, reads the `roundNumber` from it and attempts to decrypt it. +If the round number has not yet been reached by the network, an error will be thrown. +It accepts both armored and unarmored payloads. + +### `roundForTime` +Given a `NetworkInfo` object, it calculates what the latest-emitted round will have been at that `time` + +### `timeForRound` +Given a `NetworkInfo` object, it calculates the approximate time the given `round` will be emitted at (approximate because the network must work together to create the randomness). + +## Possible issues +- you may need a `fetch` polyfill on some versions of node, e.g. [isomorphic fetch](https://www.npmjs.com/package/isomorphic-fetch). You can provide your own `DrandHttpClientOptions` to the `DrandHttpClient` if you don't want to use fetch, but it may be necessary to declare a fake `fetch` somewhere for compilation diff --git a/src/drand/drand-client.ts b/src/drand/drand-client.ts index 3b8c002..c759640 100644 --- a/src/drand/drand-client.ts +++ b/src/drand/drand-client.ts @@ -2,6 +2,7 @@ import * as bls from "@noble/bls12-381" export interface DrandClient { get(round: number): Promise + info(): Promise } export type Beacon = { @@ -86,7 +87,11 @@ class DrandHttpClient implements DrandClient { } } - static createFetchClient(options: DrandNetworkInfo = defaultClientInfo): DrandHttpClient { + async info() { + return this.options + } + + static createFetchClient(options: DrandNetworkInfo): DrandHttpClient { const fetchSuccess = async (url: string) => { const response = await fetch(url) if (response.status === 404) { diff --git a/src/index.ts b/src/index.ts index c8ead88..cebbefd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,24 +1,23 @@ -import {defaultClientInfo, DrandClient, DrandHttpClient, DrandNetworkInfo, roundForTime, timeForRound} from "./drand/drand-client" +import {defaultClientInfo, DrandClient, DrandHttpClient, roundForTime, timeForRound} from "./drand/drand-client" import {createTimelockEncrypter} from "./drand/timelock-encrypter" import {decryptAge, encryptAge} from "./age/age-encrypt-decrypt" import {decodeArmor, encodeArmor, isProbablyArmored} from "./age/armor" import {createTimelockDecrypter} from "./drand/timelock-decrypter" export async function timelockEncrypt( - config: DrandNetworkInfo, roundNumber: number, payload: string, - drandHttpClient: DrandClient = DrandHttpClient.createFetchClient(), + drandHttpClient: DrandClient = DrandHttpClient.createFetchClient(defaultClientInfo), ): Promise { - // probably should get `chainInfo` through /info - const timelockEncrypter = createTimelockEncrypter(defaultClientInfo, drandHttpClient, roundNumber) + const chainInfo = await drandHttpClient.info() + const timelockEncrypter = createTimelockEncrypter(chainInfo, drandHttpClient, roundNumber) const agePayload = await encryptAge(Buffer.from(payload), timelockEncrypter) return encodeArmor(agePayload) } export async function timelockDecrypt( ciphertext: string, - drandHttpClient: DrandClient = DrandHttpClient.createFetchClient() + drandHttpClient: DrandClient = DrandHttpClient.createFetchClient(defaultClientInfo) ): Promise { const timelockDecrypter = createTimelockDecrypter(drandHttpClient) diff --git a/test/drand/mock-drand-client.ts b/test/drand/mock-drand-client.ts index 73d0629..da85865 100644 --- a/test/drand/mock-drand-client.ts +++ b/test/drand/mock-drand-client.ts @@ -1,4 +1,5 @@ import {Beacon, DrandClient} from "../../src/drand/drand-client" +import {defaultClientInfo} from "../../src" class MockDrandClient implements DrandClient { @@ -8,6 +9,10 @@ class MockDrandClient implements DrandClient { get(_: number): Promise { return Promise.resolve(this.beacon) } + + async info() { + return defaultClientInfo + } } const validBeacon = { diff --git a/test/drand/timelock-decrypter.test.ts b/test/drand/timelock-decrypter.test.ts index 755b125..3cde3b4 100644 --- a/test/drand/timelock-decrypter.test.ts +++ b/test/drand/timelock-decrypter.test.ts @@ -1,6 +1,6 @@ import {expect} from "chai" import {MockDrandClient} from "./mock-drand-client" -import {defaultClientInfo} from "../../src/drand/drand-client" +import {defaultClientInfo} from "../../src" import {readAge} from "../../src/age/age-reader-writer" import {decodeArmor} from "../../src/age/armor" import {createTimelockDecrypter} from "../../src/drand/timelock-decrypter" @@ -18,7 +18,7 @@ describe("timelock decrypter", () => { it("should decrypt for stanzas created using tlock", async () => { const plaintext = "hello world" - const ciphertext = await timelockEncrypt(defaultClientInfo, 1, plaintext, mockClient) + const ciphertext = await timelockEncrypt(1, plaintext, mockClient) const parsedAgeEncryption = readAge(decodeArmor(ciphertext)) const decryptedFileKey = await createTimelockDecrypter(mockClient)(parsedAgeEncryption.header.recipients) diff --git a/test/drand/timelock.test.ts b/test/drand/timelock.test.ts index 9c8ba93..b815171 100644 --- a/test/drand/timelock.test.ts +++ b/test/drand/timelock.test.ts @@ -1,5 +1,4 @@ import {expect} from "chai" -import {defaultClientInfo} from "../../src/drand/drand-client" import {assertError} from "../utils" import {MockDrandClient} from "./mock-drand-client" import {timelockDecrypt, timelockEncrypt} from "../../src" @@ -7,10 +6,10 @@ import {timelockDecrypt, timelockEncrypt} from "../../src" describe("timelock", () => { describe("encryption", () => { it("should fail for roundNumber less than 0", async () => { - await assertError(() => timelockEncrypt(defaultClientInfo, -1, "hello world")) + await assertError(() => timelockEncrypt(-1, "hello world")) }) it("should pass for a valid roundNumber", async () => { - expect(await timelockEncrypt(defaultClientInfo, 1, "hello world")).to.have.length.greaterThan(0) + expect(await timelockEncrypt(1, "hello world")).to.have.length.greaterThan(0) }) }) @@ -23,7 +22,7 @@ describe("timelock", () => { const mockClient = new MockDrandClient(validBeacon) it("should succeed for a correctly timelock encrypted payload", async () => { const plaintext = "hello world" - const decryptedPayload = await timelockDecrypt(await timelockEncrypt(defaultClientInfo, 1, plaintext, mockClient), mockClient) + const decryptedPayload = await timelockDecrypt(await timelockEncrypt(1, plaintext, mockClient), mockClient) expect(decryptedPayload).to.equal(plaintext) })