// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

package key

import (
	"errors"

	"go4.org/mem"
	"tailscale.com/types/structs"
)

const (
	// chalPublicHexPrefix is the prefix used to identify a
	// hex-encoded challenge public key.
	//
	// This prefix is used in the control protocol, so cannot be
	// changed.
	chalPublicHexPrefix = "chalpub:"
)

// ChallengePrivate is a challenge key, used to test whether clients control a
// key they want to prove ownership of.
//
// A ChallengePrivate is ephemeral and not serialized to the disk or network.
type ChallengePrivate struct {
	_ structs.Incomparable // because == isn't constant-time
	k [32]byte
}

// NewChallenge creates and returns a new node private key.
func NewChallenge() ChallengePrivate {
	return ChallengePrivate(NewNode())
}

// Public returns the ChallengePublic for k.
// Panics if ChallengePublic is zero.
func (k ChallengePrivate) Public() ChallengePublic {
	pub := NodePrivate(k).Public()
	return ChallengePublic(pub)
}

// MarshalText implements encoding.TextMarshaler, but by returning an error.
// It shouldn't need to be marshalled anywhere.
func (k ChallengePrivate) MarshalText() ([]byte, error) {
	return nil, errors.New("refusing to marshal")
}

// SealToChallenge is like SealTo, but for a ChallengePublic.
func (k NodePrivate) SealToChallenge(p ChallengePublic, cleartext []byte) (ciphertext []byte) {
	return k.SealTo(NodePublic(p), cleartext)
}

// OpenFrom opens the NaCl box ciphertext, which must be a value
// created by NodePrivate.SealToChallenge, and returns the inner cleartext if
// ciphertext is a valid box from p to k.
func (k ChallengePrivate) OpenFrom(p NodePublic, ciphertext []byte) (cleartext []byte, ok bool) {
	return NodePrivate(k).OpenFrom(p, ciphertext)
}

// ChallengePublic is the public portion of a ChallengePrivate.
type ChallengePublic struct {
	k [32]byte
}

// String returns the output of MarshalText as a string.
func (k ChallengePublic) String() string {
	bs, err := k.MarshalText()
	if err != nil {
		panic(err)
	}
	return string(bs)
}

// AppendText implements encoding.TextAppender.
func (k ChallengePublic) AppendText(b []byte) ([]byte, error) {
	return appendHexKey(b, chalPublicHexPrefix, k.k[:]), nil
}

// MarshalText implements encoding.TextMarshaler.
func (k ChallengePublic) MarshalText() ([]byte, error) {
	return k.AppendText(nil)
}

// UnmarshalText implements encoding.TextUnmarshaler.
func (k *ChallengePublic) UnmarshalText(b []byte) error {
	return parseHex(k.k[:], mem.B(b), mem.S(chalPublicHexPrefix))
}

// IsZero reports whether k is the zero value.
func (k ChallengePublic) IsZero() bool { return k == ChallengePublic{} }
