mirror of
				https://source.quilibrium.com/quilibrium/ceremonyclient.git
				synced 2025-10-31 14:27:40 +00:00 
			
		
		
		
	Added support for transcript verification and voucher inclusion checks
This commit is contained in:
		
							parent
							
								
									4d65dff388
								
							
						
					
					
						commit
						32da7a625f
					
				| @ -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
	 Cassandra Heart
						Cassandra Heart