mirror of
				https://source.quilibrium.com/quilibrium/ceremonyclient.git
				synced 2025-11-04 07:17:44 +00:00 
			
		
		
		
	Merge pull request #4 from QuilibriumNetwork/cli-flags
Added support for transcript verification and voucher inclusion checks
This commit is contained in:
		
						commit
						e745f31bbf
					
				@ -6,4 +6,8 @@ KZG Ceremony client for Quilibrium.
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
@ -14,6 +15,7 @@ import (
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/cloudflare/circl/sign/ed448"
 | 
			
		||||
	"golang.org/x/sync/errgroup"
 | 
			
		||||
	bls48581 "source.quilibrium.com/quilibrium/ceremonyclient/ec/bls48581"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -39,6 +41,18 @@ type PowersOfTau struct {
 | 
			
		||||
	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 {
 | 
			
		||||
	NumG1Powers int
 | 
			
		||||
	NumG2Powers int
 | 
			
		||||
@ -249,3 +263,265 @@ func ContributeAndGetVoucher() {
 | 
			
		||||
		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 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/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										23
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								main.go
									
									
									
									
									
								
							@ -2,6 +2,7 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -11,12 +12,32 @@ func main() {
 | 
			
		||||
	PrintLogo()
 | 
			
		||||
	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()
 | 
			
		||||
 | 
			
		||||
	JoinLobby()
 | 
			
		||||
	Bootstrap()
 | 
			
		||||
	fmt.Println("New Pubkey: ")
 | 
			
		||||
	fmt.Println("New PoT Pubkey: ")
 | 
			
		||||
	fmt.Println(bcj.PotPubKey)
 | 
			
		||||
	fmt.Println()
 | 
			
		||||
	fmt.Println("Voucher Pubkey: ")
 | 
			
		||||
	fmt.Println(bcj.VoucherPubKey)
 | 
			
		||||
	ContributeAndGetVoucher()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user