mirror of
https://github.com/luxfi/corona.git
synced 2026-07-27 02:50:34 +00:00
Leftover from Corona's "Pulsar-R" lineage. Pulsar (M-LWE) and Corona
(R-LWE) are independent constructions with separate hardness assumptions,
so their cSHAKE personalisation strings must be distinct.
Changes (Go, non-luxcpp):
- hash tags: PULSAR-HC-v1 -> CORONA-HC-v1, etc. (HU, TRANSCRIPT, PRF, MAC, PAIRWISE)
- profile IDs: "Pulsar-SHA3" -> "Corona-SHA3", "Pulsar-BLAKE3" -> "Corona-BLAKE3"
- context strings: pulsar.dkg2.A.v1 -> corona.dkg2.A.v1, etc.
- env vars: PULSAR_RESHARE_KAT_PATH -> CORONA_RESHARE_KAT_PATH, etc.
- struct names: pulsarSHA3 -> coronaSHA3
- KAT derive roots: sign_e2e_pulsar -> sign_e2e_corona
What's preserved (different scope):
- luxcpp/crypto/pulsar/* path references in comments (separate repo,
out of scope; the C++ side will rename in its own commit)
- Cross-runtime KAT files on disk (will regenerate next CI run)
All 11 packages test green: dkg, dkg2, hash, keyera, networking,
primitives, reshare, sign, threshold, utils, wire.
128 lines
4.7 KiB
Go
128 lines
4.7 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package reshare
|
|
|
|
// Transcript binding for Verifiable Secret Resharing.
|
|
//
|
|
// Every resharing message — Round 1 commits + private share deliveries,
|
|
// Round 1.5 commit-digest broadcasts, Round 2 complaints, and the final
|
|
// activation certificate — is bound to a transcript hash that commits
|
|
// to the (chain_id, network_id, group_id, era, generations, epochs,
|
|
// sets, thresholds, group_pk_hash, nebula_root, hash_suite_id,
|
|
// implementation_version) tuple. The transcript hash is a single point
|
|
// of equality the new committee uses when it threshold-signs the
|
|
// activation message under the unchanged GroupKey: the chain accepts
|
|
// the new epoch only when the activation signature verifies against
|
|
// this hash.
|
|
//
|
|
// Domain separation. Every input field is unambiguously length-prefixed
|
|
// (TupleHash framing under Corona-SHA3, hand-rolled length prefixes
|
|
// under the legacy Corona-BLAKE3 suite). The customization tag is
|
|
// "CORONA-TRANSCRIPT-v1" — distinct from any DKG transcript tag (e.g.
|
|
// "corona.dkg2.commit.v1") and from any Corona Sign transcript tag.
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
|
|
"github.com/luxfi/corona/hash"
|
|
)
|
|
|
|
// TranscriptPersonalization is the legacy BLAKE3 personalization
|
|
// string. Kept as an exported symbol because some external consumers
|
|
// (cross-language KAT loaders) reference it; the canonical transcript
|
|
// binding is now produced via the HashSuite layer.
|
|
const TranscriptPersonalization = "corona.reshare.transcript.v1"
|
|
|
|
// TranscriptInputs holds the public binding fields for one resharing
|
|
// invocation. All fields are mandatory; the transcript hash is well-
|
|
// defined only when every byte below is fixed.
|
|
type TranscriptInputs struct {
|
|
ChainID []byte
|
|
NetworkID []byte
|
|
GroupID []byte
|
|
KeyEraID uint64
|
|
OldGeneration uint64
|
|
NewGeneration uint64
|
|
OldEpochID uint64
|
|
NewEpochID uint64
|
|
OldSetHash [32]byte
|
|
NewSetHash [32]byte
|
|
ThresholdOld uint32
|
|
ThresholdNew uint32
|
|
GroupPublicKeyHash [32]byte
|
|
NebulaRoot [32]byte
|
|
HashSuiteID string
|
|
ImplementationVersion string
|
|
Variant string
|
|
}
|
|
|
|
// Hash returns the canonical 32-byte transcript binding for the inputs
|
|
// under the supplied HashSuite. nil resolves to the production default
|
|
// (Corona-SHA3); pass hash.NewCoronaBLAKE3() to reproduce legacy bytes.
|
|
func (t *TranscriptInputs) Hash(suite hash.HashSuite) [32]byte {
|
|
s := hash.Resolve(suite)
|
|
parts := buildTranscriptParts(t)
|
|
return s.TranscriptHash(parts...)
|
|
}
|
|
|
|
// buildTranscriptParts canonicalizes a TranscriptInputs into the
|
|
// ordered byte-string list that drives TranscriptHash.
|
|
func buildTranscriptParts(t *TranscriptInputs) [][]byte {
|
|
enc64 := func(v uint64) []byte {
|
|
var b [8]byte
|
|
binary.BigEndian.PutUint64(b[:], v)
|
|
return b[:]
|
|
}
|
|
enc32 := func(v uint32) []byte {
|
|
var b [4]byte
|
|
binary.BigEndian.PutUint32(b[:], v)
|
|
return b[:]
|
|
}
|
|
return [][]byte{
|
|
[]byte("variant"), []byte(t.Variant),
|
|
[]byte("chain_id"), t.ChainID,
|
|
[]byte("network_id"), t.NetworkID,
|
|
[]byte("group_id"), t.GroupID,
|
|
[]byte("key_era_id"), enc64(t.KeyEraID),
|
|
[]byte("old_generation"), enc64(t.OldGeneration),
|
|
[]byte("new_generation"), enc64(t.NewGeneration),
|
|
[]byte("old_epoch_id"), enc64(t.OldEpochID),
|
|
[]byte("new_epoch_id"), enc64(t.NewEpochID),
|
|
[]byte("old_set_hash"), t.OldSetHash[:],
|
|
[]byte("new_set_hash"), t.NewSetHash[:],
|
|
[]byte("threshold_old"), enc32(t.ThresholdOld),
|
|
[]byte("threshold_new"), enc32(t.ThresholdNew),
|
|
[]byte("group_public_key_hash"), t.GroupPublicKeyHash[:],
|
|
[]byte("nebula_root"), t.NebulaRoot[:],
|
|
[]byte("hash_suite_id"), []byte(t.HashSuiteID),
|
|
[]byte("implementation_version"), []byte(t.ImplementationVersion),
|
|
}
|
|
}
|
|
|
|
// ValidatorSetHash returns the canonical hash of a validator set.
|
|
// suite=nil resolves to the production default (Corona-SHA3).
|
|
func ValidatorSetHash(publicKeys [][]byte, suite hash.HashSuite) [32]byte {
|
|
s := hash.Resolve(suite)
|
|
sorted := make([][]byte, len(publicKeys))
|
|
copy(sorted, publicKeys)
|
|
for i := 1; i < len(sorted); i++ {
|
|
for j := i; j > 0 && bytes.Compare(sorted[j-1], sorted[j]) > 0; j-- {
|
|
sorted[j-1], sorted[j] = sorted[j], sorted[j-1]
|
|
}
|
|
}
|
|
parts := make([][]byte, 0, 2+len(sorted))
|
|
parts = append(parts, []byte("corona.reshare.validator-set.v1"))
|
|
var nBuf [4]byte
|
|
binary.BigEndian.PutUint32(nBuf[:], uint32(len(sorted)))
|
|
parts = append(parts, nBuf[:])
|
|
for _, pk := range sorted {
|
|
if len(pk) > 0xFFFF {
|
|
panic("reshare: validator public key longer than 65535 bytes")
|
|
}
|
|
parts = append(parts, pk)
|
|
}
|
|
return s.TranscriptHash(parts...)
|
|
}
|