1 package btcwire
2 3 import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "fmt"
8 "io"
9 10 "git.smesh.lol/nostrino/pkg/crypto/chainhash"
11 )
12 13 // Network magic values used to identify the network a frame belongs to.
14 //
15 // The values are the conventional four-byte strings in the order they appear
16 // on the wire, so MainNet is f9 be b4 d9 and RegTest is fa bf b5 da. They are
17 // therefore written and read big endian: the digits of the constant are the
18 // bytes on the wire, in the order every Bitcoin document prints them.
19 const (
20 // MainNet is the magic for the Bitcoin main network.
21 MainNet uint32 = 0xf9beb4d9
22 23 // TestNet3 is the magic for the Bitcoin test network, version 3.
24 TestNet3 uint32 = 0x0b110907
25 26 // RegTest is the magic for a Bitcoin regression test network.
27 RegTest uint32 = 0xfabfb5da
28 29 // SigNet is the magic for a Bitcoin signet.
30 SigNet uint32 = 0x0a03cf40
31 )
32 33 // Frame layout constants.
34 const (
35 // CommandSize is the fixed size of the command field in a frame header.
36 CommandSize = 12
37 38 // MessageHeaderSize is the size of a frame header in bytes.
39 MessageHeaderSize = 24
40 41 // ChecksumSize is the number of payload SHA256d bytes carried by a frame
42 // header.
43 ChecksumSize = 4
44 45 // MaxPayload is the largest payload this package will encode or accept.
46 MaxPayload = 32 << 20
47 )
48 49 // Command strings used by the messages this package implements. A command is
50 // at most CommandSize bytes and consists of printable ASCII without NUL.
51 const (
52 CmdVersion = "version"
53 CmdVerAck = "verack"
54 CmdPing = "ping"
55 CmdPong = "pong"
56 CmdGetHeaders = "getheaders"
57 CmdHeaders = "headers"
58 CmdGetCFilters = "getcfilters"
59 CmdCFilter = "cfilter"
60 CmdGetCFHeaders = "getcfheaders"
61 CmdCFHeaders = "cfheaders"
62 CmdGetCFCheckpt = "getcfcheckpt"
63 CmdCFCheckpt = "cfcheckpt"
64 CmdGetData = "getdata"
65 CmdTx = "tx"
66 CmdBlock = "block"
67 CmdInv = "inv"
68 CmdNotFound = "notfound"
69 CmdSendHeaders = "sendheaders"
70 CmdWtxidRelay = "wtxidrelay"
71 CmdSendAddrV2 = "sendaddrv2"
72 CmdFeeFilter = "feefilter"
73 // CmdSendCmpct is how a peer announces compact-block support. A node sends it
74 // unprompted right after the handshake even to a client that never asks for BIP152,
75 // so a gateway that does not recognise it drops every session.
76 CmdSendCmpct = "sendcmpct"
77 )
78 79 // Message is a decoded frame: a command name and its raw payload.
80 type Message struct {
81 // Command is the command name without NUL padding.
82 Command string
83 84 // Payload is the raw message payload.
85 Payload []byte
86 }
87 88 // Payload is implemented by the message payload types in this package, which
89 // convert themselves to and from the bytes carried inside a frame.
90 type Payload interface {
91 // EncodePayload returns the wire encoding of the payload.
92 EncodePayload() ([]byte, error)
93 94 // DecodePayload decodes the payload from b.
95 DecodePayload(b []byte) error
96 }
97 98 // Checksum returns the first four bytes of SHA256d(payload), the value carried
99 // by a frame header.
100 func Checksum(payload []byte) [ChecksumSize]byte {
101 sum := chainhash.DoubleHashB(payload)
102 var out [ChecksumSize]byte
103 copy(out[:], sum[:ChecksumSize])
104 return out
105 }
106 107 // encodeCommand validates command and returns it NUL padded to CommandSize
108 // bytes.
109 func encodeCommand(command string) ([CommandSize]byte, error) {
110 var cmd [CommandSize]byte
111 switch {
112 case len(command) == 0:
113 return cmd, fmt.Errorf("%w: empty command", ErrInvalidCommand)
114 case len(command) > CommandSize:
115 return cmd, fmt.Errorf(
116 "%w: %q is %d bytes, maximum is %d",
117 ErrInvalidCommand, command, len(command), CommandSize,
118 )
119 }
120 for i := 0; i < len(command); i++ {
121 c := command[i]
122 if c < 0x20 || c > 0x7e {
123 return cmd, fmt.Errorf(
124 "%w: %q contains non-printable byte 0x%02x at index %d",
125 ErrInvalidCommand, command, c, i,
126 )
127 }
128 cmd[i] = c
129 }
130 return cmd, nil
131 }
132 133 // decodeCommand validates a NUL padded command field and returns it without
134 // the padding.
135 func decodeCommand(cmd []byte) (string, error) {
136 if len(cmd) != CommandSize {
137 return "", fmt.Errorf(
138 "%w: command field is %d bytes, want %d",
139 ErrInvalidCommand, len(cmd), CommandSize,
140 )
141 }
142 n := CommandSize
143 for n > 0 && cmd[n-1] == 0 {
144 n--
145 }
146 if n == 0 {
147 return "", fmt.Errorf("%w: command is all NUL padding", ErrInvalidCommand)
148 }
149 for i := 0; i < n; i++ {
150 if cmd[i] < 0x20 || cmd[i] > 0x7e {
151 return "", fmt.Errorf(
152 "%w: command contains non-printable byte 0x%02x at index %d",
153 ErrInvalidCommand, cmd[i], i,
154 )
155 }
156 }
157 return string(cmd[:n]), nil
158 }
159 160 // EncodeMessage returns a single complete frame for command and payload.
161 //
162 // The frame is the network magic, the NUL padded command, the payload length,
163 // the payload checksum, and the payload itself. The caller keeps ownership of
164 // payload; it is copied into the returned frame.
165 func EncodeMessage(magic uint32, command string, payload []byte) ([]byte, error) {
166 cmd, err := encodeCommand(command)
167 if err != nil {
168 return nil, err
169 }
170 if len(payload) > MaxPayload {
171 return nil, fmt.Errorf(
172 "%w: %s payload is %d bytes, maximum is %d",
173 ErrPayloadTooLarge, command, len(payload), MaxPayload,
174 )
175 }
176 frame := make([]byte, MessageHeaderSize+len(payload))
177 putMagic(frame[0:4], magic)
178 copy(frame[4:4+CommandSize], cmd[:])
179 binary.LittleEndian.PutUint32(frame[16:20], uint32(len(payload)))
180 sum := Checksum(payload)
181 copy(frame[20:24], sum[:])
182 copy(frame[24:], payload)
183 return frame, nil
184 }
185 186 // EncodeMessagePayload encodes p and returns a frame carrying it under
187 // command.
188 func EncodeMessagePayload(magic uint32, command string, p Payload) ([]byte, error) {
189 payload, err := p.EncodePayload()
190 if err != nil {
191 return nil, fmt.Errorf("btcwire: encoding %s payload: %w", command, err)
192 }
193 return EncodeMessage(magic, command, payload)
194 }
195 196 // DecodeMessage reads and validates one frame from r. It validates the network
197 // magic, the command characters, the payload length bound, and the payload
198 // checksum.
199 //
200 // A clean end of input before any header byte is reported as io.EOF; a partial
201 // header or payload is reported as an error wrapping io.ErrUnexpectedEOF.
202 func DecodeMessage(magic uint32, r io.Reader) (*Message, error) {
203 return decodeMessageLimit(magic, r, MaxPayload)
204 }
205 206 // decodeMessageLimit is DecodeMessage with a payload cap for this frame.
207 //
208 // The cap is applied before the payload is allocated, so a peer that answers a request for an
209 // 8 MiB block with a 30 MiB frame costs a header read and nothing else.
210 func decodeMessageLimit(magic uint32, r io.Reader, limit int) (*Message, error) {
211 var header [MessageHeaderSize]byte
212 if _, err := io.ReadFull(r, header[:]); err != nil {
213 return nil, fmt.Errorf("btcwire: reading message header: %w", err)
214 }
215 gotMagic := readMagic(header[0:4])
216 if gotMagic != magic {
217 return nil, fmt.Errorf(
218 "%w: frame has magic 0x%08x, want 0x%08x",
219 ErrBadMagic, gotMagic, magic,
220 )
221 }
222 command, err := decodeCommand(header[4 : 4+CommandSize])
223 if err != nil {
224 return nil, err
225 }
226 length := binary.LittleEndian.Uint32(header[16:20])
227 if limit <= 0 || limit > MaxPayload {
228 limit = MaxPayload
229 }
230 if length > uint32(limit) {
231 return nil, fmt.Errorf(
232 "%w: %s payload length %d, maximum is %d",
233 ErrPayloadTooLarge, command, length, limit,
234 )
235 }
236 payload := make([]byte, length)
237 if _, err := io.ReadFull(r, payload); err != nil {
238 // A zero-byte read at end of stream is reported as io.EOF by
239 // io.ReadFull, but inside a frame it means truncation, not a clean
240 // end of the message stream.
241 if errors.Is(err, io.EOF) {
242 err = io.ErrUnexpectedEOF
243 }
244 return nil, fmt.Errorf(
245 "btcwire: reading %s payload of %d bytes: %w",
246 command, length, err,
247 )
248 }
249 want := Checksum(payload)
250 if !bytes.Equal(header[20:24], want[:]) {
251 return nil, fmt.Errorf(
252 "%w: %s payload of %d bytes has checksum %x, want %x",
253 ErrBadChecksum, command, length, header[20:24], want,
254 )
255 }
256 return &Message{Command: command, Payload: payload}, nil
257 }
258 259 // Decode decodes the message payload into p.
260 func (m *Message) Decode(p Payload) error {
261 if err := p.DecodePayload(m.Payload); err != nil {
262 return fmt.Errorf("btcwire: decoding %s payload: %w", m.Command, err)
263 }
264 return nil
265 }
266 267 // putMagic writes the four network magic bytes into b, which must be four
268 // bytes long.
269 //
270 // The magic is the one field of the frame header that is big endian. Bitcoin
271 // prints it as the byte string that goes on the wire ("f9beb4d9" for mainnet),
272 // so the constant is that byte string read as a big-endian integer and the
273 // bytes come back out in the order they were printed.
274 func putMagic(b []byte, magic uint32) {
275 binary.BigEndian.PutUint32(b, magic)
276 }
277 278 // readMagic reads the four network magic bytes in b.
279 func readMagic(b []byte) uint32 {
280 return binary.BigEndian.Uint32(b)
281 }
282 283 // Reader reads a stream of frames that all belong to the same network.
284 type Reader struct {
285 r io.Reader
286 magic uint32
287 limit int
288 }
289 290 // SetLimit caps the payload of the next frames this reader decodes. Zero or a value above
291 // MaxPayload restores the package maximum.
292 func (r *Reader) SetLimit(max int) { r.limit = max }
293 294 // NewReader returns a Reader that reads frames carrying magic from r.
295 func NewReader(r io.Reader, magic uint32) *Reader {
296 return &Reader{r: r, magic: magic}
297 }
298 299 // Next reads the next message. It returns io.EOF once the underlying reader is
300 // exhausted at a frame boundary.
301 func (r *Reader) Next() (*Message, error) {
302 limit := r.limit
303 if limit <= 0 || limit > MaxPayload {
304 limit = MaxPayload
305 }
306 msg, err := decodeMessageLimit(r.magic, r.r, limit)
307 if err != nil {
308 if errors.Is(err, io.EOF) {
309 return nil, io.EOF
310 }
311 return nil, err
312 }
313 return msg, nil
314 }
315 316 // Magic returns the network magic the Reader validates frames against.
317 func (r *Reader) Magic() uint32 {
318 return r.magic
319 }
320