genesis Ed25519 verification — working against live mainnet + preprod
- verify package: ComputeProtocolMessageHash mirrors Rust's BTreeMap-ordered SHA256 over key||value concatenation (enum declaration order, not alpha) - DecodeGenesisVerifyKey unpacks Mithril's 'hex of ASCII [b0,b1,...,b31]' wrapping convention; also accepts plain 64-char hex - Genesis() verifies the Ed25519 signature over the ASCII BYTES of the signed_message HEX string (critical subtlety from upstream) - networks: real genesis vkeys for mainnet + preprod + preview from mithril-infra/configuration/*/genesis.vkey - cmd: 'verify genesis' walks head→genesis chain, verifies the terminal cert; 'verify head' and 'verify <hash>' also wired; JSON output supported - exit codes honored: 3 network, 4 integrity, 5 bad sig verified: mainnet genesis cert 25acfcfe… epoch 539 Ed25519 ✓ preprod genesis cert 69bc3bdf… epoch 196 Ed25519 ✓ next: STM BLS12-381 aggregate verification (the big one)
This commit is contained in:
parent
326d75d91a
commit
97a9434106
3 changed files with 273 additions and 34 deletions
|
|
@ -25,6 +25,7 @@ import (
|
|||
"git.sulkta.coop/Sulkta-Coop/mithril-go/internal/aggregator"
|
||||
"git.sulkta.coop/Sulkta-Coop/mithril-go/internal/artifact"
|
||||
"git.sulkta.coop/Sulkta-Coop/mithril-go/internal/networks"
|
||||
"git.sulkta.coop/Sulkta-Coop/mithril-go/internal/verify"
|
||||
)
|
||||
|
||||
const version = "0.0.3-dev"
|
||||
|
|
@ -264,8 +265,116 @@ func cmdDownload(ctx context.Context, args []string) int {
|
|||
}
|
||||
|
||||
func cmdVerify(ctx context.Context, args []string) int {
|
||||
fmt.Fprintln(os.Stderr, "verify: not yet implemented (STM BLS sprint pending)")
|
||||
return 1
|
||||
fs := flag.NewFlagSet("verify", flag.ExitOnError)
|
||||
asJSON := fs.Bool("json", false, "emit structured JSON")
|
||||
n, rest, err := resolveNetwork(fs, args)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return exitUsage
|
||||
}
|
||||
if len(rest) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "verify: cert hash required (or 'head' / 'genesis')")
|
||||
return exitUsage
|
||||
}
|
||||
mode := rest[0] // "head" = verify head cert (STM, not yet), "genesis" = walk chain + verify genesis, or a specific hash
|
||||
c := aggregator.New(n.AggregatorURL)
|
||||
|
||||
switch mode {
|
||||
case "genesis":
|
||||
return runVerifyGenesis(ctx, c, n, *asJSON)
|
||||
case "head":
|
||||
return runVerifyHead(ctx, c, n, *asJSON)
|
||||
default:
|
||||
// Treat as a literal cert hash: fetch + verify
|
||||
return runVerifySingle(ctx, c, n, mode, *asJSON)
|
||||
}
|
||||
}
|
||||
|
||||
func runVerifyGenesis(ctx context.Context, c *aggregator.Client, n networks.Network, asJSON bool) int {
|
||||
// Find the head snapshot's cert, walk to genesis, verify Ed25519 on the genesis cert.
|
||||
snap, err := resolveSnapshot(ctx, c, "latest")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "resolve:", err)
|
||||
return exitNetwork
|
||||
}
|
||||
chain, err := c.CertChain(ctx, snap.CertificateHash, 2048)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "chain:", err)
|
||||
return exitNetwork
|
||||
}
|
||||
if len(chain) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "empty chain")
|
||||
return exitGeneric
|
||||
}
|
||||
gen := chain[len(chain)-1]
|
||||
if gen.GenesisSignature == "" {
|
||||
fmt.Fprintln(os.Stderr, "tail of chain is not a genesis certificate")
|
||||
return exitGeneric
|
||||
}
|
||||
return verifyGenesisCert(n, gen, asJSON)
|
||||
}
|
||||
|
||||
func runVerifyHead(ctx context.Context, c *aggregator.Client, n networks.Network, asJSON bool) int {
|
||||
snap, err := resolveSnapshot(ctx, c, "latest")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "resolve:", err)
|
||||
return exitNetwork
|
||||
}
|
||||
return runVerifySingle(ctx, c, n, snap.CertificateHash, asJSON)
|
||||
}
|
||||
|
||||
func runVerifySingle(ctx context.Context, c *aggregator.Client, n networks.Network, hash string, asJSON bool) int {
|
||||
cert, err := c.GetCertificate(ctx, hash)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cert:", err)
|
||||
return exitNetwork
|
||||
}
|
||||
if cert.GenesisSignature != "" {
|
||||
return verifyGenesisCert(n, cert, asJSON)
|
||||
}
|
||||
// STM cert
|
||||
if asJSON {
|
||||
return emitJSON(map[string]any{
|
||||
"cert_hash": cert.Hash,
|
||||
"kind": "stm",
|
||||
"verified": false,
|
||||
"error": "STM BLS verification not yet implemented",
|
||||
})
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "cert is STM-signed; STM verification not yet implemented")
|
||||
return exitGeneric
|
||||
}
|
||||
|
||||
func verifyGenesisCert(n networks.Network, cert *aggregator.Certificate, asJSON bool) int {
|
||||
vk, err := verify.DecodeGenesisVerifyKey(n.GenesisVerifyKey)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "decode genesis key:", err)
|
||||
return exitGeneric
|
||||
}
|
||||
err = verify.GenesisFromJSON(vk, cert.SignedMessage, cert.GenesisSignature, cert.ProtocolMessage)
|
||||
if asJSON {
|
||||
result := map[string]any{
|
||||
"cert_hash": cert.Hash,
|
||||
"kind": "genesis",
|
||||
"signed_message": cert.SignedMessage,
|
||||
"epoch": cert.Epoch,
|
||||
"verified": err == nil,
|
||||
}
|
||||
if err != nil {
|
||||
result["error"] = err.Error()
|
||||
}
|
||||
code := emitJSON(result)
|
||||
if err != nil {
|
||||
return exitBadSig
|
||||
}
|
||||
return code
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "verify genesis:", err)
|
||||
return exitBadSig
|
||||
}
|
||||
fmt.Printf("genesis cert %s epoch=%d Ed25519 ✓\n", cert.Hash, cert.Epoch)
|
||||
return exitOK
|
||||
}
|
||||
|
||||
func cmdCert(ctx context.Context, args []string) int {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue