github.com/decred/politeia@v1.4.0/util/signature.go (about)

     1  // Copyright (c) 2020-2021 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package util
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/base64"
    10  	"encoding/hex"
    11  	"fmt"
    12  
    13  	"github.com/decred/dcrd/chaincfg/chainhash"
    14  	"github.com/decred/dcrd/chaincfg/v3"
    15  	"github.com/decred/dcrd/dcrec/secp256k1/v3/ecdsa"
    16  	"github.com/decred/dcrd/dcrutil/v3"
    17  	"github.com/decred/dcrd/wire"
    18  	"github.com/decred/politeia/politeiad/api/v1/identity"
    19  )
    20  
    21  // ErrorStatusT represents an error that occurred during signature validation.
    22  type ErrorStatusT int
    23  
    24  const (
    25  	// ErrorStatusInvalid is an invalid error status.
    26  	ErrorStatusInvalid ErrorStatusT = 0
    27  
    28  	// ErrorStatusPublicKeyInvalid is returned when a public key is not
    29  	// a hex encoded ed25519 public key.
    30  	ErrorStatusPublicKeyInvalid ErrorStatusT = 1
    31  
    32  	// ErrorStatusSignatureInvalid is returned when a signature is
    33  	// either not a valid hex encoded ed25519 signature or the
    34  	// signature is wrong for the provided public key and message.
    35  	ErrorStatusSignatureInvalid ErrorStatusT = 2
    36  
    37  	// errorStatusLast represents the last entry in the error statuses
    38  	// list. It is used by a unit test to verify that all error codes
    39  	// have a corresponding entry in the ErrorStatuses map. This error
    40  	// code will never be returned.
    41  	errorStatusLast ErrorStatusT = 3
    42  )
    43  
    44  // ErrorStatuses contains the human readable signature error messages.
    45  var ErrorStatuses = map[ErrorStatusT]string{
    46  	ErrorStatusInvalid:          "signature error invalid",
    47  	ErrorStatusPublicKeyInvalid: "public key invalid",
    48  	ErrorStatusSignatureInvalid: "signature invalid",
    49  }
    50  
    51  // SignatureError represents an error that was caused while verifying a
    52  // signature.
    53  type SignatureError struct {
    54  	ErrorCode    ErrorStatusT
    55  	ErrorContext string
    56  }
    57  
    58  // Error satisfies the error interface.
    59  func (e SignatureError) Error() string {
    60  	if e.ErrorContext == "" {
    61  		return fmt.Sprintf("could not verify signature: %v",
    62  			ErrorStatuses[e.ErrorCode])
    63  	}
    64  	return fmt.Sprintf("could not verify signature: %v: %v",
    65  		ErrorStatuses[e.ErrorCode], e.ErrorContext)
    66  }
    67  
    68  // VerifySignature verifies a hex encoded Ed25519 signature.
    69  func VerifySignature(signature, pubKey, msg string) error {
    70  	sig, err := ConvertSignature(signature)
    71  	if err != nil {
    72  		return SignatureError{
    73  			ErrorCode:    ErrorStatusSignatureInvalid,
    74  			ErrorContext: err.Error(),
    75  		}
    76  	}
    77  	b, err := hex.DecodeString(pubKey)
    78  	if err != nil {
    79  		return SignatureError{
    80  			ErrorCode:    ErrorStatusPublicKeyInvalid,
    81  			ErrorContext: "key is not hex",
    82  		}
    83  	}
    84  	pk, err := identity.PublicIdentityFromBytes(b)
    85  	if err != nil {
    86  		return SignatureError{
    87  			ErrorCode:    ErrorStatusPublicKeyInvalid,
    88  			ErrorContext: err.Error(),
    89  		}
    90  	}
    91  	if !pk.VerifyMessage([]byte(msg), sig) {
    92  		return SignatureError{
    93  			ErrorCode: ErrorStatusSignatureInvalid,
    94  		}
    95  	}
    96  	return nil
    97  }
    98  
    99  // VerifyMessage verifies a message that was signed using a decred P2PKH
   100  // address.
   101  //
   102  // Copied from:
   103  // github.com/decred/dcrd/blob/0fc55252f912756c23e641839b1001c21442c38a/rpcserver.go#L5605
   104  func VerifyMessage(address, message, signature string, net *chaincfg.Params) (bool, error) {
   105  	// Decode the provided address.
   106  	addr, err := dcrutil.DecodeAddress(address, net)
   107  	if err != nil {
   108  		return false, fmt.Errorf("Could not decode address: %v",
   109  			err)
   110  	}
   111  
   112  	// Only P2PKH addresses are valid for signing.
   113  	if _, ok := addr.(*dcrutil.AddressPubKeyHash); !ok {
   114  		return false, fmt.Errorf("Address is not a pay-to-pubkey-hash "+
   115  			"address: %v", address)
   116  	}
   117  
   118  	// Decode base64 signature.
   119  	sig, err := base64.StdEncoding.DecodeString(signature)
   120  	if err != nil {
   121  		return false, fmt.Errorf("Malformed base64 encoding: %v", err)
   122  	}
   123  
   124  	// Validate the signature - this just shows that it was valid at all.
   125  	// we will compare it with the key next.
   126  	var buf bytes.Buffer
   127  	wire.WriteVarString(&buf, 0, "Decred Signed Message:\n")
   128  	wire.WriteVarString(&buf, 0, message)
   129  	expectedMessageHash := chainhash.HashB(buf.Bytes())
   130  	pk, wasCompressed, err := ecdsa.RecoverCompact(sig,
   131  		expectedMessageHash)
   132  	if err != nil {
   133  		// Mirror Bitcoin Core behavior, which treats error in
   134  		// RecoverCompact as invalid signature.
   135  		return false, nil
   136  	}
   137  
   138  	// Reconstruct the pubkey hash.
   139  	dcrPK := pk
   140  	var serializedPK []byte
   141  	if wasCompressed {
   142  		serializedPK = dcrPK.SerializeCompressed()
   143  	} else {
   144  		serializedPK = dcrPK.SerializeUncompressed()
   145  	}
   146  	a, err := dcrutil.NewAddressSecpPubKey(serializedPK, net)
   147  	if err != nil {
   148  		// Again mirror Bitcoin Core behavior, which treats error in
   149  		// public key reconstruction as invalid signature.
   150  		return false, nil
   151  	}
   152  
   153  	// Return boolean if addresses match.
   154  	return a.Address() == address, nil
   155  }