// Copyright 2023 The AthanorLabs/atomic-swap Authors
// SPDX-License-Identifier: LGPL-3.0-only

// Package protocol has functions that are used by both the maker and taker during execution of the swap.
package protocol

import (
	"bytes"
	"fmt"

	"github.com/athanorlabs/atomic-swap/common"
	mcrypto "github.com/athanorlabs/atomic-swap/crypto/monero"
	"github.com/athanorlabs/atomic-swap/crypto/secp256k1"
	"github.com/athanorlabs/atomic-swap/dleq"
)

// KeysAndProof contains a DLEq proof, a secp256k1 public key,
// and ed25519 public and private keypairs.
type KeysAndProof struct {
	DLEqProof          *dleq.Proof
	Secp256k1PublicKey *secp256k1.PublicKey
	PrivateKeyPair     *mcrypto.PrivateKeyPair
	PublicKeyPair      *mcrypto.PublicKeyPair
}

// GenerateKeysAndProof generates keys on the secp256k1 and ed25519 curves as well as
// a DLEq proof between the two.
func GenerateKeysAndProof() (*KeysAndProof, error) {
	d := &dleq.DefaultDLEq{}
	proof, err := d.Prove()
	if err != nil {
		return nil, err
	}

	res, err := d.Verify(proof)
	if err != nil {
		return nil, err
	}

	secret := proof.Secret()
	sk, err := mcrypto.NewPrivateSpendKey(common.Reverse(secret[:]))
	if err != nil {
		return nil, fmt.Errorf("failed to create private spend key: %w", err)
	}

	kp, err := sk.AsPrivateKeyPair()
	if err != nil {
		return nil, err
	}

	return &KeysAndProof{
		DLEqProof:          proof,
		Secp256k1PublicKey: res.Secp256k1PublicKey(),
		PrivateKeyPair:     kp,
		PublicKeyPair:      kp.PublicKeyPair(),
	}, nil
}

// VerifyResult is returned from verifying a DLEq proof.
type VerifyResult struct {
	Secp256k1PublicKey *secp256k1.PublicKey
	Ed25519PublicKey   *mcrypto.PublicKey
}

// VerifyKeysAndProof verifies the given DLEq proof and asserts that the resulting secp256k1 key corresponds
// to the given key.
func VerifyKeysAndProof(
	proofData []byte,
	secp256k1Pub *secp256k1.PublicKey,
	ed25519Pub *mcrypto.PublicKey,
) (*VerifyResult, error) {
	d := &dleq.DefaultDLEq{}
	proof := dleq.NewProofWithoutSecret(proofData)
	res, err := d.Verify(proof)
	if err != nil {
		return nil, err
	}

	if !bytes.Equal(res.Secp256k1PublicKey().Bytes(), secp256k1Pub.Bytes()) {
		return nil, errInvalidSecp256k1Key
	}

	if !bytes.Equal(res.Ed25519PublicKey().Bytes(), ed25519Pub.Bytes()) {
		return nil, errInvalidEd25519Key
	}

	return &VerifyResult{
		Secp256k1PublicKey: secp256k1Pub,
		Ed25519PublicKey:   ed25519Pub,
	}, nil
}
