Skip to content

Commit

Permalink
secp256k1/schnorr: update error types.
Browse files Browse the repository at this point in the history
This updates the Schnorr 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 committed Jul 20, 2020
1 parent 69a4172 commit 338e49f
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 117 deletions.
119 changes: 27 additions & 92 deletions dcrec/secp256k1/schnorr/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,146 +5,81 @@

package schnorr

import (
"fmt"
)

// ErrorCode identifies a kind of signature-related 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 RuleError.
const (
// ErrInvalidHashLen indicates that the input hash to sign or verify is not
// the required length.
ErrInvalidHashLen ErrorCode = iota
ErrInvalidHashLen = ErrorKind("ErrInvalidHashLen")

// ErrPrivateKeyIsZero indicates an attempt was made to sign a message with
// a private key that is equal to zero.
ErrPrivateKeyIsZero
ErrPrivateKeyIsZero = ErrorKind("ErrPrivateKeyIsZero")

// ErrSchnorrHashValue indicates that the hash of (R || m) was too large and
// so a new nonce should be used.
ErrSchnorrHashValue
ErrSchnorrHashValue = ErrorKind("ErrSchnorrHashValue")

// ErrPubKeyNotOnCurve indicates that a point was not on the given elliptic
// curve.
ErrPubKeyNotOnCurve
ErrPubKeyNotOnCurve = ErrorKind("ErrPubKeyNotOnCurve")

// ErrSigRYIsOdd indicates that the calculated Y value of R was odd.
ErrSigRYIsOdd
ErrSigRYIsOdd = ErrorKind("ErrSigRYIsOdd")

// ErrSigRNotOnCurve indicates that the calculated or given point R for some
// signature was not on the curve.
ErrSigRNotOnCurve
ErrSigRNotOnCurve = ErrorKind("ErrSigRNotOnCurve")

// ErrUnequalRValues indicates that the calculated point R for some
// signature was not the same as the given R value for the signature.
ErrUnequalRValues
ErrUnequalRValues = ErrorKind("ErrUnequalRValues")

// ErrSigTooShort is returned when a signature that should be a Schnorr
// signature is too short.
ErrSigTooShort
ErrSigTooShort = ErrorKind("ErrSigTooShort")

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

// ErrSigRTooBig is returned when a signature has r with a value that is
// greater than or equal to the prime of the field underlying the group.
ErrSigRTooBig
ErrSigRTooBig = ErrorKind("ErrSigRTooBig")

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

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

// Map of ErrorCode values back to their constant names for pretty printing.
var errorCodeStrings = map[ErrorCode]string{
ErrInvalidHashLen: "ErrInvalidHashLen",
ErrPrivateKeyIsZero: "ErrPrivateKeyIsZero",
ErrSchnorrHashValue: "ErrSchnorrHashValue",
ErrPubKeyNotOnCurve: "ErrPubKeyNotOnCurve",
ErrSigRYIsOdd: "ErrSigRYIsOdd",
ErrSigRNotOnCurve: "ErrSigRNotOnCurve",
ErrUnequalRValues: "ErrUnequalRValues",
ErrSigTooShort: "ErrSigTooShort",
ErrSigTooLong: "ErrSigTooLong",
ErrSigRTooBig: "ErrSigRTooBig",
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 an Error and the error codes match
// - The target is an 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 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.
type Error struct {
ErrorCode ErrorCode // Describes the kind of error
Description string // Human readable description of the issue
Description string
Err error
}

// Error satisfies the error interface and prints human-readable errors.
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 an Error and the error codes match
// - The target is an ErrorCode and 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 an Error given a set of arguments.
func signatureError(c ErrorCode, desc string) Error {
return Error{ErrorCode: c, Description: desc}
func signatureError(kind ErrorKind, desc string) Error {
return Error{Err: kind, Description: desc}
}
37 changes: 12 additions & 25 deletions dcrec/secp256k1/schnorr/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.
func TestErrorCodeStringer(t *testing.T) {
// TestErrorKindStringer tests the stringized output for the ErrorKind type.
func TestErrorKindStringer(t *testing.T) {
tests := []struct {
in ErrorCode
in ErrorKind
want string
}{
{ErrInvalidHashLen, "ErrInvalidHashLen"},
Expand All @@ -26,17 +26,10 @@ func TestErrorCodeStringer(t *testing.T) {
{ErrSigTooLong, "ErrSigTooLong"},
{ErrSigRTooBig, "ErrSigRTooBig"},
{ErrSigSTooBig, "ErrSigSTooBig"},
{0xffff, "Unknown ErrorCode (65535)"},
}

// Detect additional error codes that don't have the stringer added.
if len(tests)-1 != int(numErrorCodes) {
t.Fatalf("It appears an 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 @@ -66,15 +59,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.
// TestErrorKindIsAs ensures both ErrorKind and Error can be identified as being
// a specific error via errors.Is and unwrapped via errors.As.
func TestErrorCodeIsAs(t *testing.T) {
tests := []struct {
name string
err error
target error
wantMatch bool
wantAs ErrorCode
wantAs ErrorKind
}{{
name: "ErrInvalidHashLen == ErrInvalidHashLen",
err: ErrInvalidHashLen,
Expand All @@ -87,12 +80,6 @@ func TestErrorCodeIsAs(t *testing.T) {
target: ErrInvalidHashLen,
wantMatch: true,
wantAs: ErrInvalidHashLen,
}, {
name: "ErrInvalidHashLen == Error.ErrInvalidHashLen",
err: ErrInvalidHashLen,
target: signatureError(ErrInvalidHashLen, ""),
wantMatch: true,
wantAs: ErrInvalidHashLen,
}, {
name: "Error.ErrInvalidHashLen == Error.ErrInvalidHashLen",
err: signatureError(ErrInvalidHashLen, ""),
Expand Down Expand Up @@ -134,15 +121,15 @@ func TestErrorCodeIsAs(t *testing.T) {
continue
}

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

0 comments on commit 338e49f

Please sign in to comment.