This repository was archived by the owner on Jul 7, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathsigned_operation.go
More file actions
196 lines (176 loc) · 7.1 KB
/
signed_operation.go
File metadata and controls
196 lines (176 loc) · 7.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package tezosprotocol
import (
"crypto"
"crypto/ecdsa"
"github.com/btcsuite/btcd/btcec/v2"
btcecdsa "github.com/btcsuite/btcd/btcec/v2/ecdsa"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/ed25519"
"golang.org/x/xerrors"
)
// SignedOperation represents a signed operation
type SignedOperation struct {
Operation *Operation
Signature Signature
}
// Watermark is the first byte of a signable payload that indicates
// the type of data represented.
type Watermark byte
// References: https://gitlab.com/tezos/tezos/blob/master/src/lib_crypto/signature.ml#L43
const (
// BlockHeaderWatermark is the special byte prepended to serialized block headers before signing
BlockHeaderWatermark Watermark = 1
// EndorsementWatermark is the special byte prepended to serialized endorsements before signing
EndorsementWatermark Watermark = 2
// OperationWatermark is the special byte prepended to serialized operations before signing
OperationWatermark Watermark = 3
// CustomWatermark is for custom purposes
CustomWatermark Watermark = 4
// TextWatermark is the special byte prepended to plaintext messages before signing. It is not
// yet part of the standard but has some precedent here:
// https://tezos.stackexchange.com/questions/1177/whats-the-easiest-way-for-an-account-holder-to-verify-sign-that-they-are-the-ri/1178#1178
TextWatermark Watermark = 5
)
// SignOperation signs the given tezos operation using the provided
// signing key. The returned bytes are the signed operation, encoded as
// (operation bytes || signature bytes).
func SignOperation(operation *Operation, privateKey PrivateKey) (SignedOperation, error) {
// serialize operation
operationBytes, err := operation.MarshalBinary()
if err != nil {
return SignedOperation{}, xerrors.Errorf("failed to marshal operation: %s: %w", operation, err)
}
// sign
signature, err := signGeneric(OperationWatermark, operationBytes, privateKey)
return SignedOperation{Operation: operation, Signature: signature}, err
}
// MarshalBinary implements encoding.BinaryMarshaler
func (s SignedOperation) MarshalBinary() ([]byte, error) {
opBytes, err := s.Operation.MarshalBinary()
if err != nil {
return nil, xerrors.Errorf("failed to marshal operation: %w", err)
}
sigBytes, err := s.Signature.MarshalBinary()
if err != nil {
return nil, xerrors.Errorf("failed to marshal signature: %w", err)
}
return append(opBytes, sigBytes...), nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler. In cases where
// the signature type cannot be inferred, PrefixGenericSignature is used instead.
func (s *SignedOperation) UnmarshalBinary(data []byte) error {
if len(data) < OperationSignatureLen {
return xerrors.Errorf("signed operation too short, probably not a signed operation: %d", len(data))
}
// operation
operationLen := len(data) - OperationSignatureLen
s.Operation = &Operation{}
err := s.Operation.UnmarshalBinary(data[:operationLen])
if err != nil {
return xerrors.Errorf("failed to unmarshal operation in signed operation: %w", err)
}
// signature
signatureBytes := data[operationLen:]
for _, content := range s.Operation.Contents {
sourceableContent, ok := content.(interface{ GetSource() ContractID })
if ok {
sourceContract := sourceableContent.GetSource()
var sourceContractType Base58CheckPrefix
sourceContractType, _, err = Base58CheckDecode(string(sourceContract))
if err != nil {
return err
}
var signature string
switch sourceContractType {
case PrefixEd25519PublicKeyHash:
signature, err = Base58CheckEncode(PrefixEd25519Signature, signatureBytes)
s.Signature = Signature(signature)
return err
case PrefixP256PublicKeyHash:
signature, err = Base58CheckEncode(PrefixP256Signature, signatureBytes)
s.Signature = Signature(signature)
return err
case PrefixSecp256k1PublicKeyHash:
signature, err = Base58CheckEncode(PrefixSecp256k1Signature, signatureBytes)
s.Signature = Signature(signature)
return err
case PrefixContractHash:
// manager (signer) not known -- continue searching operation contents
}
}
}
// could not determine signature type -- most likely because the source is an originated account
signature, err := Base58CheckEncode(PrefixGenericSignature, signatureBytes)
s.Signature = Signature(signature)
return err
}
// GetHash returns the hash of a signed operation.
func (s SignedOperation) GetHash() (OperationHash, error) {
signedOpBytes, err := s.MarshalBinary()
if err != nil {
return "", err
}
hashBytes := blake2b.Sum256(signedOpBytes)
var hashEncoded OperationHash
err = hashEncoded.UnmarshalBinary(hashBytes[:])
return hashEncoded, err
}
// SignMessage signs the given text based message using the provided
// signing key. It returns the base58check-encoded signature which does not include the message.
// It uses the 0x04 non-standard watermark.
func SignMessage(message string, privateKey PrivateKey) (Signature, error) {
return signGeneric(TextWatermark, []byte(message), privateKey)
}
func signGeneric(watermark Watermark, message []byte, privateKey PrivateKey) (Signature, error) {
// prepend the tezos operation watermark
bytesWithWatermark := append([]byte{byte(watermark)}, message...)
// hash unsigned operation
payloadHash := blake2b.Sum256(bytesWithWatermark)
// sign the hash
cryptoPrivateKey, err := privateKey.CryptoPrivateKey()
if err != nil {
return "", err
}
switch key := cryptoPrivateKey.(type) {
case ed25519.PrivateKey:
signatureBytes := ed25519.Sign(key, payloadHash[:])
signature, err := Base58CheckEncode(PrefixEd25519Signature, signatureBytes)
return Signature(signature), err
case ecdsa.PrivateKey:
btcecPrivKey, _ := btcec.PrivKeyFromBytes(key.D.Bytes())
btcecSignature := btcecdsa.Sign(btcecPrivKey, payloadHash[:])
signature, err := Base58CheckEncode(PrefixGenericSignature, btcecSignature.Serialize())
return Signature(signature), err
default:
return "", xerrors.Errorf("unsupported private key type: %T", cryptoPrivateKey)
}
}
// VerifyMessage verifies the signature on a human readable message
func VerifyMessage(message string, signature Signature, publicKey crypto.PublicKey) error {
return verifyGeneric(TextWatermark, []byte(message), signature, publicKey)
}
func verifyGeneric(watermark Watermark, message []byte, signature Signature, publicKey crypto.PublicKey) error {
// prepend the tezos operation watermark
bytesWithWatermark := append([]byte{byte(watermark)}, message...)
// hash
payloadHash := blake2b.Sum256(bytesWithWatermark)
// verify signature over hash
sigPrefix, sigBytes, err := Base58CheckDecode(string(signature))
if err != nil {
return xerrors.Errorf("failed to decode signature: %s: %w", signature, err)
}
var ok bool
switch key := publicKey.(type) {
case ed25519.PublicKey:
if sigPrefix != PrefixEd25519Signature && sigPrefix != PrefixGenericSignature {
return xerrors.Errorf("signature type %s does not match public key type %T", sigPrefix, publicKey)
}
ok = ed25519.Verify(key, payloadHash[:], sigBytes)
default:
return xerrors.Errorf("unsupported public key type: %T", publicKey)
}
if !ok {
return xerrors.Errorf("invalid signature %s for public key %s", signature, publicKey)
}
return nil
}