Skip to content

Commit

Permalink
Merge pull request #10 from jsign/jsign/satellite
Browse files Browse the repository at this point in the history
Support offline contributions, configurable sequencer URL & direct hex-encoded entropy flag
  • Loading branch information
jsign authored Mar 17, 2023
2 parents e6648dd + 7c7ae3f commit 965cecc
Show file tree
Hide file tree
Showing 11 changed files with 320 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ test:
.PHONY: test

build:
go build -o kzgcli ./cmd/kzgcli
go build ./cmd/kzgcli
.PHONY: build

bench:
Expand Down
50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ This repository contains an implementation of a client to participate in the Pow

For _bls12-381_ elliptic curve operations such as group multiplication and pairings, the implementation uses the [gnark-crypto](https://github.com/ConsenSys/gnark-crypto) library ([audited Oct-2022](https://github.com/ConsenSys/gnark-crypto/blob/master/audit_oct2022.pdf)).

Used by:
- Multiple individual contributors
- [Proof-of-cat](https://proofof.cat/)
- [Dappnode](https://twitter.com/eduadiez/status/1623963202500304896)
- [Dodgekzg](https://www.dogekzg.com/) (WASM)
- [Raspberry Pi contributor](https://twitter.com/bruderbuck/status/1617424902256041985)
- [cryptosat](https://twitter.com/cryptosat) (Planned for "Special contributions" phase)
- [KZGamer](https://hackmd.io/@RoboCopsGoneMad/Bk3zqWDij) (Planned for "Special contributions" phase)



## Table of content
- [Ethereum EIP-4844 Powers of Tau ceremony client](#ethereum-eip-4844-powers-of-tau-ceremony-client)
- [Table of content](#table-of-content)
Expand All @@ -18,7 +29,9 @@ For _bls12-381_ elliptic curve operations such as group multiplication and pairi
- [Step 3 - Contribute!](#step-3---contribute)
- [Step 4 (optional) - Check that your contribution is in the new transcript](#step-4-optional---check-that-your-contribution-is-in-the-new-transcript)
- [External entropy](#external-entropy)
- [Verify the current sequencer transcript ourselves](#verify-the-current-sequencer-transcript-ourselves)
- [Offline contributions](#offline-contributions)
- [Testing ceremony environment](#testing-ceremony-environment)
- [Verify the current sequencer transcript](#verify-the-current-sequencer-transcript)
- [Tests and benchmarks](#tests-and-benchmarks)
- [Side-effects of this ceremony client work](#side-effects-of-this-ceremony-client-work)
- [Potential improvements](#potential-improvements)
Expand Down Expand Up @@ -117,7 +130,40 @@ Success!

If you want to understand in more detail how the external entropy is mixed with the CSRNG, please see [this code section](https://github.com/jsign/go-kzg-ceremony-client/blob/main/contribution/batchcontribution.go#L24-L35).

## Verify the current sequencer transcript ourselves
## Offline contributions
This section is only interesting if you're contributing from constrained environments.

Apart from conforming to the specification for the Powers of Tau protocol, participating in the ceremony involves interacting with the sequencer in a defined API flow. If you are contributing from a constraint environment (e.g: air-gapped or bandwidth constrained), you might be interested in narrowing down the contribution step independently from getting the state and sending the contribution.

The CLI tool provides an _offline_ subcommand:

- `kzgcli offline download-state <file-path>`: downloads the current state of the ceremony from the sequencer and saves it in a file.
- `kzgcli offline contribute <current-state-path> <contribution-path>`: opens a previously downloaded current state of the ceremony, makes the contribution and saves it in a new file.
- `kzgcli offline send-contribution --session-id <...> <contribution-path>`: sends a previously generated contribution file to the sequencer.

You might not need `kzgcli offline download-state` you're pulling the current state out-of-band (e.g: direct download or the sequencer sent it to you). If that isn't the case, you can use it in an environment that has internet access (not necessarily your contribution environment).

The `kzgcli offline contribute` command doesn't require internet access, and will probably be the only command you'll run in your constrained environment. This command also accepts the `--urlrand` and `--hex-entropy` flag if you want to pull entropy from an external source of randomness available in your environment or provided directly to the client, respectively.

The `kzgcli offline send-contribution` command sends the previously generated file by `kzgcli offline contribute` to the sequencer.

An example of running the first two commands:
```
$ kzgcli offline download-state current.json
Downloading current state... OK
Encoding and saving to current.json... OK
Saved current state in current.json
$ kzgcli offline contribute current.json new.json
Opening and parsing offline current state file...OK
Calculating contribution... OK
Success, saved contribution in new.json
```

## Testing ceremony environment

In all commands you can use the `--sequencer-url` flag to override the sequencer API URL to target a different sequencer than in the _mainnet_ environment. For example, `--sequencer-url "https://kzg-ceremony-sequencer-dev.fly.dev"`.

## Verify the current sequencer transcript
The sequencer has [an API that provides a full transcript](https://seq.ceremony.ethereum.org/info/current_state) of all the contributions, so anyone can double-check the calculations to see if the result matches all the received contributions.

Having clients double-check sequencer calculations avoids having to trust that the sequencer is in the latest powers of Tau calculation.
Expand Down
7 changes: 6 additions & 1 deletion cmd/kzgcli/contribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ var contributeCmd = &cobra.Command{
extRandomness = append(extRandomness, urlBytes)
}

client, err := sequencerclient.New()
sequencerURL, err := cmd.Flags().GetString("sequencer-url")
if err != nil {
log.Fatalf("get --sequencer-url flag value: %s", err)
}

client, err := sequencerclient.New(sequencerURL)
if err != nil {
log.Fatalf("creating sequencer client: %s", err)
}
Expand Down
23 changes: 23 additions & 0 deletions cmd/kzgcli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,40 @@ https://github.com/jsign/go-kzg-ceremony-client#i-want-to-participate-in-the-cer
},
}

var offlineCmd = &cobra.Command{
Use: "offline",
Short: "Contains commands for offline contributions",
Run: func(cmd *cobra.Command, args []string) {
if err := cmd.Usage(); err != nil {
log.Fatalf("cmd usage failed: %s", err)
}
},
}

func init() {
rootCmd.CompletionOptions.DisableDefaultCmd = true
rootCmd.PersistentFlags().String("sequencer-url", "https://seq.ceremony.ethereum.org", "The URL of the ceremony sequencer")

rootCmd.AddCommand(statusCmd)

// Online contribution commands.
contributeCmd.Flags().String("session-id", "", "The sesion id as generated in the 'session_id' field in the authentication process")
contributeCmd.Flags().Bool("drand", false, "Pull entropy from the Drand network to be mixed with local CSRNG")
contributeCmd.Flags().String("urlrand", "", "Pull entropy from an HTTP endpoint mixed with local CSRNG")
rootCmd.AddCommand(contributeCmd)

// Verification commands.
rootCmd.AddCommand(verifyTranscriptCmd)

// Offline commands.
offlineContributeCmd.Flags().String("urlrand", "", "Pull entropy from an HTTP endpoint mixed with local CSRNG")
offlineContributeCmd.Flags().String("hex-entropy", "", "Hex encoded entropy to be mixed with local CSRNG")
offlineSendContributionCmd.Flags().String("session-id", "", "The sesion id as generated in the 'session_id' field in the authentication process")

rootCmd.AddCommand(offlineCmd)
offlineCmd.AddCommand(offlineDownloadStateCmd)
offlineCmd.AddCommand(offlineContributeCmd)
offlineCmd.AddCommand(offlineSendContributionCmd)
}

func Execute() {
Expand Down
83 changes: 83 additions & 0 deletions cmd/kzgcli/offline_contribute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package main

import (
"encoding/hex"
"fmt"
"io"
"log"
"os"
"strings"

"github.com/jsign/go-kzg-ceremony-client/contribution"
"github.com/jsign/go-kzg-ceremony-client/extrand"
"github.com/spf13/cobra"
)

var offlineContributeCmd = &cobra.Command{
Use: "contribute <path-current-state-file> <path-contribution-file>",
Short: "Opens a file with the current state of the ceremony, makes the contribution, and saves the new state to a file.",
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
log.Fatalf("two arguments expected")
}

urlrand, err := cmd.Flags().GetString("urlrand")
if err != nil {
log.Fatalf("get --urlrand flag value: %s", err)
}
var extRandomness [][]byte
if urlrand != "" {
fmt.Printf("Pulling entropy from %s... ", urlrand)
urlBytes, err := extrand.GetFromURL(cmd.Context(), urlrand)
if err != nil {
log.Fatalf("get bytes from url: %s", err)
}
fmt.Printf("Got it! (length: %d)\n", len(urlBytes))
extRandomness = append(extRandomness, urlBytes)
}
hexEntropy, err := cmd.Flags().GetString("hex-entropy")
if err != nil {
log.Fatalf("get --hex-entropy flag value: %s", err)
}
if hexEntropy != "" {
hexEntropy := strings.TrimPrefix(hexEntropy, "0x")
hb, err := hex.DecodeString(hexEntropy)
if err != nil {
log.Fatalf("decoding hex entropy: %s", err)
}
extRandomness = append(extRandomness, hb)
}

fmt.Printf("Opening and parsing offline current state file...")
f, err := os.Open(args[0])
if err != nil {
log.Fatalf("opening current state file at %s: %s", args[0], err)
}
defer f.Close()

bytes, err := io.ReadAll(f)
if err != nil {
log.Fatalf("reading current state file: %s", err)
}
contributionBatch, err := contribution.DecodeBatchContribution(bytes)
if err != nil {
log.Fatalf("deserializing file content: %s", err)
}
fmt.Printf("OK\nCalculating contribution... ")

if err := contributionBatch.Contribute(extRandomness...); err != nil {
log.Fatalf("failed on calculating contribution: %s", err)
}

nbytes, err := contribution.Encode(contributionBatch, true)
if err != nil {
log.Fatalf("encoding contribution: %s", err)
}

if err := os.WriteFile(args[1], nbytes, os.ModePerm); err != nil {
log.Fatalf("writing contribution file to %s: %s", args[1], err)
}

fmt.Printf("OK\nSuccess, saved contribution in %s\n", args[1])
},
}
57 changes: 57 additions & 0 deletions cmd/kzgcli/offline_currentstate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package main

import (
"fmt"
"log"
"os"

"github.com/jsign/go-kzg-ceremony-client/contribution"
"github.com/jsign/go-kzg-ceremony-client/sequencerclient"
"github.com/spf13/cobra"
)

var offlineDownloadStateCmd = &cobra.Command{
Use: "download-state <path>",
Short: "Downloads the current state of the ceremony, and saves it in a file",
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
log.Fatalf("one argument exected")
}
sequencerURL, err := cmd.Flags().GetString("sequencer-url")
if err != nil {
log.Fatalf("get --sequencer-url flag value: %s", err)
}
client, err := sequencerclient.New(sequencerURL)
if err != nil {
log.Fatalf("creating sequencer client: %s", err)
}

fmt.Printf("Downloading current state... ")
transcript, err := client.GetCurrentTranscript(cmd.Context())
if err != nil {
log.Fatalf("getting current transcript: %s", err)
}
fmt.Printf("OK\n")

bc := contribution.BatchContribution{
Contributions: make([]contribution.Contribution, len(transcript.Transcripts)),
}
for i, transcript := range transcript.Transcripts {
bc.Contributions[i].NumG1Powers = transcript.NumG1Powers
bc.Contributions[i].NumG2Powers = transcript.NumG2Powers
bc.Contributions[i].PowersOfTau = transcript.PowersOfTau
}

fmt.Printf("Encoding and saving to %s... ", args[0])
bytes, err := contribution.Encode(&bc, true)
if err != nil {
log.Fatalf("encoding current state to json: %s", err)
}

if err := os.WriteFile(args[0], bytes, os.ModePerm); err != nil {
log.Fatalf("writing current state to file: %s", err)
}

fmt.Printf("OK\nSaved current state in %s\n", args[0])
},
}
90 changes: 90 additions & 0 deletions cmd/kzgcli/offline_sendcontribution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package main

import (
"encoding/json"
"fmt"
"log"
"os"
"time"

"github.com/jsign/go-kzg-ceremony-client/contribution"
"github.com/jsign/go-kzg-ceremony-client/sequencerclient"
"github.com/spf13/cobra"
)

var offlineSendContributionCmd = &cobra.Command{
Use: "send-contribution <path-contribution-file>",
Short: "Sends a previously generated contribution to the sequencer",
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
log.Fatalf("one argument expected")
}

sessionID, err := cmd.Flags().GetString("session-id")
if err != nil {
log.Fatalf("get --session-id flag value: %s", err)
}
if sessionID == "" {
log.Fatalf("the session id can't be empty")
}

contributionBytes, err := os.ReadFile(args[0])
if err != nil {
log.Fatalf("reading contribution file: %s", err)
}
contributionBatch, err := contribution.DecodeBatchContribution(contributionBytes)
if err != nil {
log.Fatalf("decoding contribution file: %s", err)
}

sequencerURL, err := cmd.Flags().GetString("sequencer-url")
if err != nil {
log.Fatalf("get --sequencer-url flag value: %s", err)
}
client, err := sequencerclient.New(sequencerURL)
if err != nil {
log.Fatalf("creating sequencer client: %s", err)
}

for {
_, ok, err := client.TryContribute(cmd.Context(), sessionID)
if err != nil {
fmt.Printf("%v Waiting for our turn failed (err: %s), retrying in %v...\n", time.Now().Format("2006-01-02 15:04:05"), err, tryContributeAttemptDelay)
time.Sleep(tryContributeAttemptDelay)
continue
}
if !ok {
fmt.Printf("%v Can't enter the lobby, are you sure we're on your reserved slot? Waiting %v for retrying...\n", time.Now().Format("2006-01-02 15:04:05"), tryContributeAttemptDelay)
time.Sleep(tryContributeAttemptDelay)
continue
}
break
}

fmt.Printf("Sending our precomputed contribution %s to the sequencer...\n", args[0])
var contributionReceipt *sequencerclient.ContributionReceipt
for {
var err error
contributionReceipt, err = client.Contribute(cmd.Context(), sessionID, contributionBatch)
if err != nil {
fmt.Printf("Failed sending contribution!: %s\n", err)
fmt.Printf("Retrying sending contribution in %v\n", sendContributionRetryDelay)
time.Sleep(sendContributionRetryDelay)
continue
}
break
}

// Persist the receipt and contribution.
receiptJSON, _ := json.Marshal(contributionReceipt)
if err := os.WriteFile(fmt.Sprintf("contribution_receipt_%s.json", sessionID), receiptJSON, os.ModePerm); err != nil {
log.Fatalf("failed to save the contribution receipt (err: %s), printing to stdout as last resort: %s", err, receiptJSON)
}
ourContributionBatchJSON, _ := contribution.Encode(contributionBatch, true)
if err := os.WriteFile(fmt.Sprintf("my_contribution_%s.json", sessionID), ourContributionBatchJSON, os.ModePerm); err != nil {
log.Fatalf("failed to save the contribution (err: %s), printing to stdout as last resort: %s", err, ourContributionBatchJSON)
}

fmt.Printf("Success!\n")
},
}
6 changes: 5 additions & 1 deletion cmd/kzgcli/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ var statusCmd = &cobra.Command{
Use: "status",
Short: "Returns the current status of the sequencer",
Run: func(cmd *cobra.Command, args []string) {
client, err := sequencerclient.New()
sequencerURL, err := cmd.Flags().GetString("sequencer-url")
if err != nil {
log.Fatalf("get --sequencer-url flag value: %s", err)
}
client, err := sequencerclient.New(sequencerURL)
if err != nil {
log.Fatalf("creating sequencer client: %s", err)
}
Expand Down
6 changes: 5 additions & 1 deletion cmd/kzgcli/verifytranscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ var verifyTranscriptCmd = &cobra.Command{
Use: "verify-transcript",
Short: "Pulls and verifies the current sequencer transcript",
Run: func(cmd *cobra.Command, args []string) {
client, err := sequencerclient.New()
sequencerURL, err := cmd.Flags().GetString("sequencer-url")
if err != nil {
log.Fatalf("get --sequencer-url flag value: %s", err)
}
client, err := sequencerclient.New(sequencerURL)
if err != nil {
log.Fatalf("creating sequencer client: %s", err)
}
Expand Down
Loading

0 comments on commit 965cecc

Please sign in to comment.