Skip to content

Commit

Permalink
secp256k1/ecdsa: update error types.
Browse files Browse the repository at this point in the history
This updates the ECDSA error types to leverage go
1.13 errors.Is/As functionality as well as conform
to the error infrastructure best practices.
  • Loading branch information
dnldd authored and davecgh committed Dec 16, 2020
1 parent e4e085c commit 50699ed
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 135 deletions.
143 changes: 35 additions & 108 deletions dcrec/secp256k1/ecdsa/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,155 +4,99 @@

package ecdsa

import (
"fmt"
)

// ErrorCode identifies a kind of signature error. It has full support
// for errors.Is and errors.As, so the caller can directly check against an
// error code when determining the reason for an error.
type ErrorCode int
// ErrorKind identifies a kind of error. It has full support for
// errors.Is and errors.As, so the caller can directly check against
// an error kind when determining the reason for an error.
type ErrorKind string

// These constants are used to identify a specific Error.
const (
// ErrSigTooShort is returned when a signature that should be a DER
// signature is too short.
ErrSigTooShort ErrorCode = iota
ErrSigTooShort = ErrorKind("ErrSigTooShort")

// ErrSigTooLong is returned when a signature that should be a DER signature
// is too long.
ErrSigTooLong
ErrSigTooLong = ErrorKind("ErrSigTooLong")

// ErrSigInvalidSeqID is returned when a signature that should be a DER
// signature does not have the expected ASN.1 sequence ID.
ErrSigInvalidSeqID
ErrSigInvalidSeqID = ErrorKind("ErrSigInvalidSeqID")

// ErrSigInvalidDataLen is returned when a signature that should be a DER
// signature does not specify the correct number of remaining bytes for the
// R and S portions.
ErrSigInvalidDataLen
ErrSigInvalidDataLen = ErrorKind("ErrSigInvalidDataLen")

// ErrSigMissingSTypeID is returned when a signature that should be a DER
// signature does not provide the ASN.1 type ID for S.
ErrSigMissingSTypeID
ErrSigMissingSTypeID = ErrorKind("ErrSigMissingSTypeID")

// ErrSigMissingSLen is returned when a signature that should be a DER
// signature does not provide the length of S.
ErrSigMissingSLen
ErrSigMissingSLen = ErrorKind("ErrSigMissingSLen")

// ErrSigInvalidSLen is returned when a signature that should be a DER
// signature does not specify the correct number of bytes for the S portion.
ErrSigInvalidSLen
ErrSigInvalidSLen = ErrorKind("ErrSigInvalidSLen")

// ErrSigInvalidRIntID is returned when a signature that should be a DER
// signature does not have the expected ASN.1 integer ID for R.
ErrSigInvalidRIntID
ErrSigInvalidRIntID = ErrorKind("ErrSigInvalidRIntID")

// ErrSigZeroRLen is returned when a signature that should be a DER
// signature has an R length of zero.
ErrSigZeroRLen
ErrSigZeroRLen = ErrorKind("ErrSigZeroRLen")

// ErrSigNegativeR is returned when a signature that should be a DER
// signature has a negative value for R.
ErrSigNegativeR
ErrSigNegativeR = ErrorKind("ErrSigNegativeR")

// ErrSigTooMuchRPadding is returned when a signature that should be a DER
// signature has too much padding for R.
ErrSigTooMuchRPadding
ErrSigTooMuchRPadding = ErrorKind("ErrSigTooMuchRPadding")

// ErrSigRIsZero is returned when a signature has R set to the value zero.
ErrSigRIsZero
ErrSigRIsZero = ErrorKind("ErrSigRIsZero")

// ErrSigRTooBig is returned when a signature has R with a value that is
// greater than or equal to the group order.
ErrSigRTooBig
ErrSigRTooBig = ErrorKind("ErrSigRTooBig")

// ErrSigInvalidSIntID is returned when a signature that should be a DER
// signature does not have the expected ASN.1 integer ID for S.
ErrSigInvalidSIntID
ErrSigInvalidSIntID = ErrorKind("ErrSigInvalidSIntID")

// ErrSigZeroSLen is returned when a signature that should be a DER
// signature has an S length of zero.
ErrSigZeroSLen
ErrSigZeroSLen = ErrorKind("ErrSigZeroSLen")

// ErrSigNegativeS is returned when a signature that should be a DER
// signature has a negative value for S.
ErrSigNegativeS
ErrSigNegativeS = ErrorKind("ErrSigNegativeS")

// ErrSigTooMuchSPadding is returned when a signature that should be a DER
// signature has too much padding for S.
ErrSigTooMuchSPadding
ErrSigTooMuchSPadding = ErrorKind("ErrSigTooMuchSPadding")

// ErrSigSIsZero is returned when a signature has S set to the value zero.
ErrSigSIsZero
ErrSigSIsZero = ErrorKind("ErrSigSIsZero")

// ErrSigSTooBig is returned when a signature has S with a value that is
// greater than or equal to the group order.
ErrSigSTooBig

// numSigErrorCodes is the maximum error code number used in tests. This
// entry MUST be the last entry in the enum.
numSigErrorCodes
ErrSigSTooBig = ErrorKind("ErrSigSTooBig")
)

// Map of ErrorCode values back to their constant names for pretty printing.
var errorCodeStrings = map[ErrorCode]string{
ErrSigTooShort: "ErrSigTooShort",
ErrSigTooLong: "ErrSigTooLong",
ErrSigInvalidSeqID: "ErrSigInvalidSeqID",
ErrSigInvalidDataLen: "ErrSigInvalidDataLen",
ErrSigMissingSTypeID: "ErrSigMissingSTypeID",
ErrSigMissingSLen: "ErrSigMissingSLen",
ErrSigInvalidSLen: "ErrSigInvalidSLen",
ErrSigInvalidRIntID: "ErrSigInvalidRIntID",
ErrSigZeroRLen: "ErrSigZeroRLen",
ErrSigNegativeR: "ErrSigNegativeR",
ErrSigTooMuchRPadding: "ErrSigTooMuchRPadding",
ErrSigRIsZero: "ErrSigRIsZero",
ErrSigRTooBig: "ErrSigRTooBig",
ErrSigInvalidSIntID: "ErrSigInvalidSIntID",
ErrSigZeroSLen: "ErrSigZeroSLen",
ErrSigNegativeS: "ErrSigNegativeS",
ErrSigTooMuchSPadding: "ErrSigTooMuchSPadding",
ErrSigSIsZero: "ErrSigSIsZero",
ErrSigSTooBig: "ErrSigSTooBig",
}

// String returns the ErrorCode as a human-readable name.
func (e ErrorCode) String() string {
if s := errorCodeStrings[e]; s != "" {
return s
}
return fmt.Sprintf("Unknown ErrorCode (%d)", int(e))
}

// Error implements the error interface.
func (e ErrorCode) Error() string {
return e.String()
}

// Is implements the interface to work with the standard library's errors.Is.
//
// It returns true in the following cases:
// - The target is a Error and the error codes match
// - The target is a ErrorCode and the error codes match
func (e ErrorCode) Is(target error) bool {
switch target := target.(type) {
case Error:
return e == target.ErrorCode

case ErrorCode:
return e == target
}

return false
// Error satisfies the error interface and prints human-readable errors.
func (e ErrorKind) Error() string {
return string(e)
}

// Error identifies a signature-related error. It has full support for
// errors.Is and errors.As, so the caller can ascertain the specific reason for
// the error by checking the underlying error code.
// Error identifies an error related to an ECDSA signature. It has full
// support for errors.Is and errors.As, so the caller can ascertain the
// specific reason for the error by checking the underlying error.
type Error struct {
ErrorCode ErrorCode
Err error
Description string
}

Expand All @@ -161,29 +105,12 @@ func (e Error) Error() string {
return e.Description
}

// Is implements the interface to work with the standard library's errors.Is.
//
// It returns true in the following cases:
// - The target is a Error and the error codes match
// - The target is a ErrorCode and it the error codes match
func (e Error) Is(target error) bool {
switch target := target.(type) {
case Error:
return e.ErrorCode == target.ErrorCode

case ErrorCode:
return target == e.ErrorCode
}

return false
}

// Unwrap returns the underlying wrapped error code.
// Unwrap returns the underlying wrapped error.
func (e Error) Unwrap() error {
return e.ErrorCode
return e.Err
}

// signatureError creates a Error given a set of arguments.
func signatureError(c ErrorCode, desc string) Error {
return Error{ErrorCode: c, Description: desc}
// signatureError creates an Error given a set of arguments.
func signatureError(kind ErrorKind, desc string) Error {
return Error{Err: kind, Description: desc}
}
41 changes: 14 additions & 27 deletions dcrec/secp256k1/ecdsa/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (
"testing"
)

// TestErrorCodeStringer tests the stringized output for the ErrorCode type.
// TestErrorKindStringer tests the stringized output for the ErrorKind type.
func TestErrorCodeStringer(t *testing.T) {
tests := []struct {
in ErrorCode
in ErrorKind
want string
}{
{ErrSigTooShort, "ErrSigTooShort"},
Expand All @@ -34,17 +34,10 @@ func TestErrorCodeStringer(t *testing.T) {
{ErrSigTooMuchSPadding, "ErrSigTooMuchSPadding"},
{ErrSigSIsZero, "ErrSigSIsZero"},
{ErrSigSTooBig, "ErrSigSTooBig"},
{0xffff, "Unknown ErrorCode (65535)"},
}

// Detect additional error codes that don't have the stringer added.
if len(tests)-1 != int(numSigErrorCodes) {
t.Errorf("It appears a signature error code was added without adding " +
"an associated stringer test")
}

for i, test := range tests {
result := test.in.String()
result := test.in.Error()
if result != test.want {
t.Errorf("#%d: got: %s want: %s", i, result, test.want)
continue
Expand Down Expand Up @@ -74,15 +67,15 @@ func TestError(t *testing.T) {
}
}

// TestErrorCodeIsAs ensures both ErrorCode and Error can be identified as being
// a specific error code via errors.Is and unwrapped via errors.As.
func TestErrorCodeIsAs(t *testing.T) {
// TestErrorKindIsAs ensures both ErrorKind and Error can be identified as being
// a specific error kind via errors.Is and unwrapped via errors.As.
func TestErrorKindIsAs(t *testing.T) {
tests := []struct {
name string
err error
target error
wantMatch bool
wantAs ErrorCode
wantAs ErrorKind
}{{
name: "ErrSigTooShort == ErrSigTooShort",
err: ErrSigTooShort,
Expand All @@ -95,12 +88,6 @@ func TestErrorCodeIsAs(t *testing.T) {
target: ErrSigTooShort,
wantMatch: true,
wantAs: ErrSigTooShort,
}, {
name: "ErrSigTooShort == Error.ErrSigTooShort",
err: ErrSigTooShort,
target: signatureError(ErrSigTooShort, ""),
wantMatch: true,
wantAs: ErrSigTooShort,
}, {
name: "Error.ErrSigTooShort == Error.ErrSigTooShort",
err: signatureError(ErrSigTooShort, ""),
Expand Down Expand Up @@ -142,16 +129,16 @@ func TestErrorCodeIsAs(t *testing.T) {
continue
}

// Ensure the underlying error code can be unwrapped is and is the
// Ensure the underlying error kind can be unwrapped is and is the
// expected code.
var code ErrorCode
if !errors.As(test.err, &code) {
t.Errorf("%s: unable to unwrap to error code", test.name)
var kind ErrorKind
if !errors.As(test.err, &kind) {
t.Errorf("%s: unable to unwrap to error", test.name)
continue
}
if code != test.wantAs {
t.Errorf("%s: unexpected unwrapped error code -- got %v, want %v",
test.name, code, test.wantAs)
if !errors.Is(kind, test.wantAs) {
t.Errorf("%s: unexpected unwrapped error -- got %v, want %v",
test.name, kind, test.wantAs)
continue
}
}
Expand Down

0 comments on commit 50699ed

Please sign in to comment.