Added support for transcript verification and voucher inclusion checks

This commit is contained in:
Cassandra Heart 2023-04-18 21:53:45 -05:00
parent 4d65dff388
commit 32da7a625f
No known key found for this signature in database
GPG Key ID: 6352152859385958
5 changed files with 309 additions and 3 deletions

View File

@ -7,3 +7,7 @@ KZG Ceremony client for Quilibrium.
Run with `go run ./... <voucher_filename>` or omit the filename to write to quil_voucher.hex. Run with `go run ./... <voucher_filename>` or omit the filename to write to quil_voucher.hex.
If you have docker installed you can participate in the ceremony by simply running `make participate`. Your voucher will be written to `vouchers/`. If you have docker installed you can participate in the ceremony by simply running `make participate`. Your voucher will be written to `vouchers/`.
## Additional Features
Run with `go run ./... verify-transcript` to verify the latest state of the sequencer, or `go run ./... check-voucher <voucher_filename>` to verify voucher inclusion in the latest state. Please keep in mind voucher inclusions are not immediate after contribution the current batch must be processed before it will appear, and if there was an error response from the sequencer when contributing the voucher will not be included.

View File

@ -6,6 +6,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -14,6 +15,7 @@ import (
"sync" "sync"
"github.com/cloudflare/circl/sign/ed448" "github.com/cloudflare/circl/sign/ed448"
"golang.org/x/sync/errgroup"
bls48581 "source.quilibrium.com/quilibrium/ceremonyclient/ec/bls48581" bls48581 "source.quilibrium.com/quilibrium/ceremonyclient/ec/bls48581"
) )
@ -39,6 +41,18 @@ type PowersOfTau struct {
G2Affines []*bls48581.ECP8 G2Affines []*bls48581.ECP8
} }
type CeremonyState struct {
PowersOfTau PowersOfTauJson `json:"powersOfTau"`
PotPubKey string `json:"potPubKey"`
Witness Witness `json:"witness"`
VoucherPubKeys []string `json:"voucherPubKeys"`
}
type Witness struct {
RunningProducts []string `json:"runningProducts"`
PotPubKeys []string `json:"potPubKeys"`
}
type Contribution struct { type Contribution struct {
NumG1Powers int NumG1Powers int
NumG2Powers int NumG2Powers int
@ -249,3 +263,265 @@ func ContributeAndGetVoucher() {
fmt.Println(hex.EncodeToString(voucher)) fmt.Println(hex.EncodeToString(voucher))
} }
} }
func VerifyState() {
csjRes, err := http.DefaultClient.Post(HOST+"current_state", "application/json", bytes.NewBufferString("{}"))
if err != nil {
panic(err)
}
defer csjRes.Body.Close()
csjBytes, err := io.ReadAll(csjRes.Body)
if err != nil {
panic(err)
}
currentStateJson := &CeremonyState{}
if err := json.Unmarshal(csjBytes, currentStateJson); err != nil {
// message is not conformant, we are in validating phase
panic(err)
}
verifyState(currentStateJson)
}
func CheckVoucherInclusion(path string) {
csjRes, err := http.DefaultClient.Post(HOST+"current_state", "application/json", bytes.NewBufferString("{}"))
if err != nil {
panic(err)
}
defer csjRes.Body.Close()
csjBytes, err := io.ReadAll(csjRes.Body)
if err != nil {
panic(err)
}
currentStateJson := &CeremonyState{}
if err := json.Unmarshal(csjBytes, currentStateJson); err != nil {
// message is not conformant, we are in validating phase
panic(err)
}
voucherHex, err := os.ReadFile(path)
if err != nil {
panic(err)
}
decodedVoucher, err := hex.DecodeString(string(voucherHex))
if err != nil {
panic(err)
}
privKey := ed448.PrivateKey(decodedVoucher)
verifyPubKey := "0x" + hex.EncodeToString(privKey.Public().(ed448.PublicKey))
for i, v := range currentStateJson.VoucherPubKeys {
if v == verifyPubKey {
fmt.Printf("Voucher pubkey found at index %d\n", i)
os.Exit(0)
}
}
panic(errors.New("voucher not found"))
}
func verifyState(currentState *CeremonyState) {
wg := &errgroup.Group{}
// This limit needs to be low this check is a very painfully CPU intensive operation
wg.SetLimit(8)
fmt.Println("Checking running products of witnesses...")
// check the pairings
for j := 0; j < len(currentState.Witness.RunningProducts)-1; j++ {
j := j
wg.Go(func() error {
fmt.Printf("Checking witness at %d\n", j)
currRunningProductHex := strings.TrimPrefix(currentState.Witness.RunningProducts[j], "0x")
currRunningProductBytes, err := hex.DecodeString(currRunningProductHex)
if err != nil {
return fmt.Errorf("could not decode G1 at %d", j)
}
currRunningProduct := bls48581.ECP_fromBytes(currRunningProductBytes)
if currRunningProduct == nil {
return fmt.Errorf("could not convert G1 at %d", j)
}
nextRunningProductHex := strings.TrimPrefix(currentState.Witness.RunningProducts[j+1], "0x")
nextRunningProductBytes, err := hex.DecodeString(nextRunningProductHex)
if err != nil {
return fmt.Errorf("could not decode next G1 at %d", j)
}
nextRunningProduct := bls48581.ECP_fromBytes(nextRunningProductBytes)
if nextRunningProduct == nil {
return fmt.Errorf("could not convert next G1 at %d", j)
}
potPubKeyHex := strings.TrimPrefix(currentState.Witness.PotPubKeys[j+1], "0x")
potPubKeyBytes, err := hex.DecodeString(potPubKeyHex)
if err != nil {
return fmt.Errorf("could not decode POT pubkey at %d", j)
}
potPubKey := bls48581.ECP8_fromBytes(potPubKeyBytes)
if potPubKey == nil {
return fmt.Errorf("could not convert POT pubkey at %d", j)
}
prevPotPubKeyHex := strings.TrimPrefix(currentState.Witness.PotPubKeys[j], "0x")
prevPotPubKeyBytes, err := hex.DecodeString(prevPotPubKeyHex)
if err != nil {
return fmt.Errorf("could not decode POT pubkey at %d", j)
}
prevPotPubKey := bls48581.ECP8_fromBytes(prevPotPubKeyBytes)
if prevPotPubKey == nil {
return fmt.Errorf("could not convert POT pubkey at %d", j)
}
if !pairCheck(potPubKey, currRunningProduct, prevPotPubKey, nextRunningProduct) {
return fmt.Errorf("pairing check failed")
}
return nil
})
}
fmt.Println("Checking latest witness parity...")
// Check that the last running product is equal to G1 first power.
lastRunningProductIdx := len(currentState.Witness.RunningProducts) - 1
lastRunningProduct := currentState.Witness.RunningProducts[lastRunningProductIdx]
if lastRunningProduct != currentState.PowersOfTau.G1Affines[1] {
panic("mismatched running products for G1")
}
// Check that the first running product is the tau^0 power.
firstRunningProduct := currentState.Witness.RunningProducts[0]
if firstRunningProduct != currentState.PowersOfTau.G1Affines[0] {
panic("mismatched first product for G1")
}
fmt.Println("Checking coherency of G1 powers...")
// Check coherency of powers
for j := 0; j < 65535; j++ {
j := j
wg.Go(func() error {
fmt.Printf("Checking coherency of G1 at %d\n", j)
baseTauG2Hex := strings.TrimPrefix(currentState.PowersOfTau.G2Affines[1], "0x")
baseTauG2Bytes, err := hex.DecodeString(baseTauG2Hex)
if err != nil {
return fmt.Errorf("failed to decode for G2 at %d", j)
}
baseTauG2 := bls48581.ECP8_fromBytes(baseTauG2Bytes)
if baseTauG2 == nil {
return fmt.Errorf("failed to convert for G2 at %d", j)
}
currG1Hex := strings.TrimPrefix(currentState.PowersOfTau.G1Affines[j], "0x")
currG1Bytes, err := hex.DecodeString(currG1Hex)
if err != nil {
return fmt.Errorf("failed to decode for G1 at %d", j)
}
currG1 := bls48581.ECP_fromBytes(currG1Bytes)
if currG1 == nil {
return fmt.Errorf("failed to convert for G1 at %d", j)
}
nextG1Hex := strings.TrimPrefix(currentState.PowersOfTau.G1Affines[j+1], "0x")
nextG1Bytes, err := hex.DecodeString(nextG1Hex)
if err != nil {
return fmt.Errorf("failed to decode for G1 at %d", j+1)
}
nextG1 := bls48581.ECP_fromBytes(nextG1Bytes)
if nextG1 == nil {
return fmt.Errorf("failed to convert for G1 at %d", j+1)
}
if !pairCheck(baseTauG2, currG1, bls48581.ECP8_generator(), nextG1) {
return fmt.Errorf("pairing check failed")
}
return nil
})
}
fmt.Println("Checking coherency of G2 powers...")
// Check G2 powers are coherent
for j := 0; j < 256; j++ {
j := j
wg.Go(func() error {
fmt.Printf("Checking coherency of G2 at %d\n", j)
baseTauG1Hex := strings.TrimPrefix(currentState.PowersOfTau.G1Affines[1], "0x")
baseTauG1Bytes, err := hex.DecodeString(baseTauG1Hex)
if err != nil {
return fmt.Errorf("failed to decode for G1 at %d", j)
}
baseTauG1 := bls48581.ECP_fromBytes(baseTauG1Bytes)
if baseTauG1 == nil {
return fmt.Errorf("failed to convert for G1 at %d", j)
}
currG2Hex := strings.TrimPrefix(currentState.PowersOfTau.G2Affines[j], "0x")
currG2Bytes, err := hex.DecodeString(currG2Hex)
if err != nil {
return fmt.Errorf("failed to decode for G2 at %d", j)
}
currG2 := bls48581.ECP8_fromBytes(currG2Bytes)
if currG2 == nil {
return fmt.Errorf("failed to convert for G1 at %d", j)
}
nextG2Hex := strings.TrimPrefix(currentState.PowersOfTau.G2Affines[j+1], "0x")
nextG2Bytes, err := hex.DecodeString(nextG2Hex)
if err != nil {
return fmt.Errorf("failed to decode for G2 at %d", j+1)
}
nextG2 := bls48581.ECP8_fromBytes(nextG2Bytes)
if nextG2 == nil {
return fmt.Errorf("failed to convert for G2 at %d", j+1)
}
if !pairCheck(currG2, baseTauG1, nextG2, bls48581.ECP_generator()) {
return fmt.Errorf("pairing check failed")
}
return nil
})
}
if err := wg.Wait(); err != nil {
panic(fmt.Errorf("error validating transcript: %w", err))
}
fmt.Println("Current state is valid Powers of Tau!")
}
func pairCheck(G21 *bls48581.ECP8, G11 *bls48581.ECP, G22 *bls48581.ECP8, G12 *bls48581.ECP) bool {
G12.Neg()
v := bls48581.Ate2(G21, G11, G22, G12)
v = bls48581.Fexp(v)
if !v.Isunity() {
fmt.Println("pairing check failed")
return false
}
return true
}

5
go.mod
View File

@ -4,4 +4,7 @@ go 1.18
require github.com/cloudflare/circl v1.3.2 require github.com/cloudflare/circl v1.3.2
require golang.org/x/sys v0.6.0 // indirect require (
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
)

2
go.sum
View File

@ -1,4 +1,6 @@
github.com/cloudflare/circl v1.3.2 h1:VWp8dY3yH69fdM7lM6A1+NhhVoDu9vqK0jOgmkQHFWk= github.com/cloudflare/circl v1.3.2 h1:VWp8dY3yH69fdM7lM6A1+NhhVoDu9vqK0jOgmkQHFWk=
github.com/cloudflare/circl v1.3.2/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw= github.com/cloudflare/circl v1.3.2/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

23
main.go
View File

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"os"
"time" "time"
) )
@ -11,12 +12,32 @@ func main() {
PrintLogo() PrintLogo()
PrintVersion() PrintVersion()
ParseArgs()
}
func ParseArgs() {
if len(os.Args) > 1 {
switch os.Args[1] {
case "verify-transcript":
VerifyState()
os.Exit(0)
case "check-voucher":
CheckVoucherInclusion(os.Args[2])
os.Exit(0)
default:
break
}
}
WaitForSequencerToBeReady() WaitForSequencerToBeReady()
JoinLobby() JoinLobby()
Bootstrap() Bootstrap()
fmt.Println("New Pubkey: ") fmt.Println("New PoT Pubkey: ")
fmt.Println(bcj.PotPubKey) fmt.Println(bcj.PotPubKey)
fmt.Println()
fmt.Println("Voucher Pubkey: ")
fmt.Println(bcj.VoucherPubKey)
ContributeAndGetVoucher() ContributeAndGetVoucher()
} }