mirror of
				https://source.quilibrium.com/quilibrium/ceremonyclient.git
				synced 2025-04-04 19:36:54 +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. | 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
	 Cassandra Heart
						Cassandra Heart