package gnarlring import ( "crypto/rand" "errors" "io" ) // EpochState holds a coordinator's view of one group epoch, collecting // child commitments and producing a root NTRU signature binding them. // The coordinator holds an NTRU keypair; children hold individual keypairs. type EpochState struct { Counter uint64 Coordinator *NTRUPublicKey Agg *AggregatedCommitment RootSig *NTRUSignature finalized bool } // StartEpoch initializes a new epoch. func StartEpoch(counter uint64, coordinatorPK *NTRUPublicKey) *EpochState { return &EpochState{ Counter: counter, Coordinator: coordinatorPK, Agg: NewAggregatedCommitment(), } } // AddCommitment inserts a child commitment into the epoch. func (es *EpochState) AddCommitment(cc *ChildCommitment) error { if es.finalized { return errors.New("gnarlring: epoch already finalized") } if !es.Agg.Add(cc) { return errors.New("gnarlring: commitment slot occupied or index out of range") } return nil } // IsComplete reports whether all N slots are filled. func (es *EpochState) IsComplete() bool { return es.Agg.IsComplete() } // Finalize produces the root signature binding all commitments under // the coordinator's NTRU key. Returns error if not all slots are filled. func (es *EpochState) Finalize(coordinatorSK *NTRUPrivateKey, msg []byte) error { return es.FinalizeFrom(coordinatorSK, msg, rand.Reader) } // FinalizeFrom signs with a given randomness source. func (es *EpochState) FinalizeFrom(coordinatorSK *NTRUPrivateKey, msg []byte, rng io.Reader) error { if !es.IsComplete() { return errors.New("gnarlring: epoch not complete") } if es.finalized { return errors.New("gnarlring: epoch already finalized") } target := es.Agg.Target(msg, es.Counter) sig := NTRUSignTarget(coordinatorSK, target, rng) es.RootSig = sig es.finalized = true return nil } // Verify checks the epoch state: reconstructs target from commitments, // verifies root sig under coordinator's public key. func (es *EpochState) Verify(coordinatorPK *NTRUPublicKey, msg []byte) bool { if !es.finalized || es.RootSig == nil { return false } target := es.Agg.Target(msg, es.Counter) return NTRUVerifyTarget(coordinatorPK, target, es.RootSig) } // RotateMember replaces one child's commitment. Marks epoch as not finalized. func (es *EpochState) RotateMember(index uint8, newCC *ChildCommitment) { if index >= N { return } es.Agg.Remove(index) es.Agg.Add(newCC) es.finalized = false es.RootSig = nil } // IsFinalized reports whether RootSig has been produced. func (es *EpochState) IsFinalized() bool { return es.finalized }