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 }