implement messages and operations for FIND
This commit is contained in:
317
pkg/find/verify.go
Normal file
317
pkg/find/verify.go
Normal file
@@ -0,0 +1,317 @@
|
||||
package find
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
"next.orly.dev/pkg/encoders/event"
|
||||
"next.orly.dev/pkg/interfaces/signer/p8k"
|
||||
)
|
||||
|
||||
// VerifyEvent verifies the signature of a Nostr event
|
||||
func VerifyEvent(ev *event.E) error {
|
||||
ok, err := ev.Verify()
|
||||
if err != nil {
|
||||
return fmt.Errorf("signature verification failed: %w", err)
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyTransferAuth verifies a transfer authorization signature
|
||||
func VerifyTransferAuth(name, newOwner, prevOwner string, timestamp time.Time, sigHex string) (bool, error) {
|
||||
// Create the message
|
||||
msgHash := CreateTransferAuthMessage(name, newOwner, timestamp)
|
||||
|
||||
// Decode signature
|
||||
sig, err := hex.Dec(sigHex)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid signature hex: %w", err)
|
||||
}
|
||||
|
||||
// Decode pubkey
|
||||
pubkey, err := hex.Dec(prevOwner)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid pubkey hex: %w", err)
|
||||
}
|
||||
|
||||
// Create verifier with public key
|
||||
verifier, err := p8k.New()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to create verifier: %w", err)
|
||||
}
|
||||
|
||||
if err := verifier.InitPub(pubkey); err != nil {
|
||||
return false, fmt.Errorf("failed to init pubkey: %w", err)
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
ok, err := verifier.Verify(msgHash, sig)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("verification failed: %w", err)
|
||||
}
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// VerifyChallengeProof verifies a certificate challenge proof signature
|
||||
func VerifyChallengeProof(challenge, name, certPubkey, owner string, validUntil time.Time, sigHex string) (bool, error) {
|
||||
// Create the message
|
||||
msgHash := CreateChallengeProofMessage(challenge, name, certPubkey, validUntil)
|
||||
|
||||
// Decode signature
|
||||
sig, err := hex.Dec(sigHex)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid signature hex: %w", err)
|
||||
}
|
||||
|
||||
// Decode pubkey
|
||||
pubkey, err := hex.Dec(owner)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid pubkey hex: %w", err)
|
||||
}
|
||||
|
||||
// Create verifier with public key
|
||||
verifier, err := p8k.New()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to create verifier: %w", err)
|
||||
}
|
||||
|
||||
if err := verifier.InitPub(pubkey); err != nil {
|
||||
return false, fmt.Errorf("failed to init pubkey: %w", err)
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
ok, err := verifier.Verify(msgHash, sig)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("verification failed: %w", err)
|
||||
}
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// VerifyWitnessSignature verifies a witness signature on a certificate
|
||||
func VerifyWitnessSignature(certPubkey, name string, validFrom, validUntil time.Time,
|
||||
challenge, witnessPubkey, sigHex string) (bool, error) {
|
||||
|
||||
// Create the message
|
||||
msgHash := CreateWitnessMessage(certPubkey, name, validFrom, validUntil, challenge)
|
||||
|
||||
// Decode signature
|
||||
sig, err := hex.Dec(sigHex)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid signature hex: %w", err)
|
||||
}
|
||||
|
||||
// Decode pubkey
|
||||
pubkey, err := hex.Dec(witnessPubkey)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid pubkey hex: %w", err)
|
||||
}
|
||||
|
||||
// Create verifier with public key
|
||||
verifier, err := p8k.New()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to create verifier: %w", err)
|
||||
}
|
||||
|
||||
if err := verifier.InitPub(pubkey); err != nil {
|
||||
return false, fmt.Errorf("failed to init pubkey: %w", err)
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
ok, err := verifier.Verify(msgHash, sig)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("verification failed: %w", err)
|
||||
}
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// VerifyNameOwnership checks if a record's owner matches the name state owner
|
||||
func VerifyNameOwnership(nameState *NameState, record *NameRecord) error {
|
||||
recordOwner := hex.Enc(record.Event.Pubkey)
|
||||
if recordOwner != nameState.Owner {
|
||||
return fmt.Errorf("%w: record owner %s != name owner %s",
|
||||
ErrNotOwner, recordOwner, nameState.Owner)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsExpired checks if a time-based expiration has passed
|
||||
func IsExpired(expiration time.Time) bool {
|
||||
return time.Now().After(expiration)
|
||||
}
|
||||
|
||||
// IsInRenewalWindow checks if the current time is within the preferential renewal window
|
||||
// (final 30 days before expiration)
|
||||
func IsInRenewalWindow(expiration time.Time) bool {
|
||||
now := time.Now()
|
||||
renewalWindowStart := expiration.Add(-PreferentialRenewalDays * 24 * time.Hour)
|
||||
return now.After(renewalWindowStart) && now.Before(expiration)
|
||||
}
|
||||
|
||||
// CanRegister checks if a name can be registered based on its state and expiration
|
||||
func CanRegister(nameState *NameState, proposerPubkey string) error {
|
||||
// If no name state exists, anyone can register
|
||||
if nameState == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if name is expired
|
||||
if IsExpired(nameState.Expiration) {
|
||||
// Name is expired, anyone can register
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if in renewal window
|
||||
if IsInRenewalWindow(nameState.Expiration) {
|
||||
// Only current owner can register during renewal window
|
||||
if proposerPubkey != nameState.Owner {
|
||||
return ErrInRenewalWindow
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name is still owned and not in renewal window
|
||||
return fmt.Errorf("name is owned by %s until %s", nameState.Owner, nameState.Expiration)
|
||||
}
|
||||
|
||||
// VerifyProposalExpiration checks if a proposal has expired
|
||||
func VerifyProposalExpiration(proposal *RegistrationProposal) error {
|
||||
if !proposal.Expiration.IsZero() && IsExpired(proposal.Expiration) {
|
||||
return fmt.Errorf("proposal expired at %s", proposal.Expiration)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyAttestationExpiration checks if an attestation has expired
|
||||
func VerifyAttestationExpiration(attestation *Attestation) error {
|
||||
if !attestation.Expiration.IsZero() && IsExpired(attestation.Expiration) {
|
||||
return fmt.Errorf("attestation expired at %s", attestation.Expiration)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyTrustGraphExpiration checks if a trust graph has expired
|
||||
func VerifyTrustGraphExpiration(trustGraph *TrustGraph) error {
|
||||
if !trustGraph.Expiration.IsZero() && IsExpired(trustGraph.Expiration) {
|
||||
return fmt.Errorf("trust graph expired at %s", trustGraph.Expiration)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyNameStateExpiration checks if a name state has expired
|
||||
func VerifyNameStateExpiration(nameState *NameState) error {
|
||||
if !nameState.Expiration.IsZero() && IsExpired(nameState.Expiration) {
|
||||
return ErrNameExpired
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyCertificateValidity checks if a certificate is currently valid
|
||||
func VerifyCertificateValidity(cert *Certificate) error {
|
||||
now := time.Now()
|
||||
|
||||
if now.Before(cert.ValidFrom) {
|
||||
return fmt.Errorf("certificate not yet valid (valid from %s)", cert.ValidFrom)
|
||||
}
|
||||
|
||||
if now.After(cert.ValidUntil) {
|
||||
return fmt.Errorf("certificate expired at %s", cert.ValidUntil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyCertificate performs complete certificate verification
|
||||
func VerifyCertificate(cert *Certificate, nameState *NameState, trustedWitnesses []string) error {
|
||||
// Verify certificate is not expired
|
||||
if err := VerifyCertificateValidity(cert); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify name is not expired
|
||||
if err := VerifyNameStateExpiration(nameState); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify certificate owner matches name owner
|
||||
certOwner := hex.Enc(cert.Event.Pubkey)
|
||||
if certOwner != nameState.Owner {
|
||||
return fmt.Errorf("certificate owner %s != name owner %s", certOwner, nameState.Owner)
|
||||
}
|
||||
|
||||
// Verify challenge proof
|
||||
ok, err := VerifyChallengeProof(cert.Challenge, cert.Name, cert.CertPubkey,
|
||||
nameState.Owner, cert.ValidUntil, cert.ChallengeProof)
|
||||
if err != nil {
|
||||
return fmt.Errorf("challenge proof verification failed: %w", err)
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid challenge proof signature")
|
||||
}
|
||||
|
||||
// Count trusted witnesses
|
||||
trustedCount := 0
|
||||
for _, witness := range cert.Witnesses {
|
||||
// Check if witness is in trusted list
|
||||
isTrusted := false
|
||||
for _, trusted := range trustedWitnesses {
|
||||
if witness.Pubkey == trusted {
|
||||
isTrusted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isTrusted {
|
||||
continue
|
||||
}
|
||||
|
||||
// Verify witness signature
|
||||
ok, err := VerifyWitnessSignature(cert.CertPubkey, cert.Name,
|
||||
cert.ValidFrom, cert.ValidUntil, cert.Challenge,
|
||||
witness.Pubkey, witness.Signature)
|
||||
if err != nil {
|
||||
return fmt.Errorf("witness %s signature verification failed: %w", witness.Pubkey, err)
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid witness %s signature", witness.Pubkey)
|
||||
}
|
||||
|
||||
trustedCount++
|
||||
}
|
||||
|
||||
// Require at least 3 trusted witnesses
|
||||
if trustedCount < 3 {
|
||||
return fmt.Errorf("insufficient trusted witnesses: %d < 3", trustedCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifySubdomainAuthority checks if the proposer owns the parent domain
|
||||
func VerifySubdomainAuthority(name string, proposerPubkey string, parentNameState *NameState) error {
|
||||
parent := GetParentDomain(name)
|
||||
|
||||
// TLDs have no parent
|
||||
if parent == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parent must exist
|
||||
if parentNameState == nil {
|
||||
return fmt.Errorf("parent domain %s does not exist", parent)
|
||||
}
|
||||
|
||||
// Proposer must own parent
|
||||
if proposerPubkey != parentNameState.Owner {
|
||||
return fmt.Errorf("proposer %s does not own parent domain %s (owner: %s)",
|
||||
proposerPubkey, parent, parentNameState.Owner)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user