mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-26 00:25:17 +00:00
Added support for transcript verification and voucher inclusion checks
This commit is contained in:
parent
4d65dff388
commit
32da7a625f
@ -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.
|
||||||
|
276
bootstrap.go
276
bootstrap.go
@ -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
5
go.mod
@ -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
2
go.sum
@ -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
23
main.go
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user