Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi: introduce v2transport and implement BIP324 #2260

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

Crypt-iQ
Copy link
Collaborator

@Crypt-iQ Crypt-iQ commented Oct 2, 2024

Depends on #2219

This PR implements the BIP324 encrypted version 2 handshake. The service flag is added to the list of supported services so that btcd will optimistically try to make v2 connections.

The BIP324 ElligatorSwift test vectors are also included.
This package introduces FSChaCha20 and FSChaCha20Poly1305 primitives
that are behind the v2 encrypted handshake. Handshake code is also
introduced which allows a caller to initiate a v2 handshake.
This adds the v2 software flag to the list of services btcd supports
and adds functionality to optionally use v2 transport if the peer
supports it.
@saubyk
Copy link

saubyk commented Oct 7, 2024

cc: @kcalvinalvin for review

@kcalvinalvin
Copy link
Collaborator

Haven’t yet taken a look at other parts in details but seems like the PR is missing NODE_P2P_V2 signaling support. Not sure if this part of the code was out of scope for the PR but

Should add


SFNodeP2PV2 = 1 << 11

here:

btcd/wire/protocol.go

Lines 72 to 105 in 24eb815

const (
// SFNodeNetwork is a flag used to indicate a peer is a full node.
SFNodeNetwork ServiceFlag = 1 << iota
// SFNodeGetUTXO is a flag used to indicate a peer supports the
// getutxos and utxos commands (BIP0064).
SFNodeGetUTXO
// SFNodeBloom is a flag used to indicate a peer supports bloom
// filtering.
SFNodeBloom
// SFNodeWitness is a flag used to indicate a peer supports blocks
// and transactions including witness data (BIP0144).
SFNodeWitness
// SFNodeXthin is a flag used to indicate a peer supports xthin blocks.
SFNodeXthin
// SFNodeBit5 is a flag used to indicate a peer supports a service
// defined by bit 5.
SFNodeBit5
// SFNodeCF is a flag used to indicate a peer supports committed
// filters (CFs).
SFNodeCF
// SFNode2X is a flag used to indicate a peer is running the Segwit2X
// software.
SFNode2X
// SFNodeNetWorkLimited is a flag used to indicate a peer supports serving
// the last 288 blocks.
SFNodeNetworkLimited = 1 << 10

And other necessary code at:

btcd/server.go

Lines 2723 to 2743 in 24eb815

func newServer(listenAddrs, agentBlacklist, agentWhitelist []string,
db database.DB, chainParams *chaincfg.Params,
interrupt <-chan struct{}) (*server, error) {
services := defaultServices
if cfg.NoPeerBloomFilters {
services &^= wire.SFNodeBloom
}
if cfg.NoCFilters {
services &^= wire.SFNodeCF
}
if cfg.Prune != 0 {
services &^= wire.SFNodeNetwork
}
amgr := addrmgr.New(cfg.DataDir, btcdLookup)
var listeners []net.Listener
var nat NAT
if !cfg.DisableListen {
var err error

Copy link
Collaborator

@kcalvinalvin kcalvinalvin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial round of review. Went through BIP324 and compared the flows and the pseudocode provided in the BIP with the code in transport.go and chacha.go

if err != nil {
return err
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we’re missing the check for the below which is stated in bip324.

If the first 4 received bytes do not match the network magic,
but the 12 bytes after that do match the version message
encoding above, implementations may interpret this as a v1
peer of a different network, and disconnect them.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh hmm seems like this is just the pseudocode specified in the BIP. Maybe it's the BIP text that I'm confusing.

return err
}

var data []byte
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can allocate here instead of the append at the bottom doing the allocation for us.

data := make([]byte, 0, 64+garbageLen)


versionBytes := []byte("version\x00\x00\x00\x00\x00")

v1Prefix = append(v1Prefix, magic[:]...)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can pre-allocate v1Prefix

v1Prefix := make([]byte, 0, 4+12)


// Send over our ElligatorSwift-encoded pubkey followed by our
// randomly generated garbage.
var data []byte
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. Can pre-allocate.

data := make([]byte, 0, 64+garbageLen)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually maybe refactor this out into a function since it's the same exact code.


var receivedPrefix []byte
if initiating {
receivedPrefix = make([]byte, 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't do anything right? Maybe something like

receivedPrefix = make([]byte, 0, 64)

Since the length is still 0 but you've now pre-allocated.

func NewFSChaCha20(initialKey []byte) (*FSChaCha20, error) {
var initialNonce [12]byte
numRekeys := 0
binary.LittleEndian.PutUint64(initialNonce[4:12], uint64(numRekeys))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/uint64(numRekeys)/0? Since we're not using numRekeys anywhere else.


header := []byte{byte(ignoreNum)}

plaintext := make([]byte, 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can pre-allocate here:

// 1 for the header
plaintext := make([]byte, 0, contentLen+1)

return nil, 0, err
}

encPacket := make([]byte, 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can pre-allocate here:

encPacket := make([]byte, 0, len(encContentsLen)+len(aeadCiphertext))

return nil, err
}

plaintext, err := p.recvP.Decrypt(aad, aeadCiphertext)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the BIP there's a check to see if the plaintext is nil or empty. Not sure if this is a requirement or implementation specific but brining it up just to sanity check

// XOR the text with the keystream to get either the cipher or plaintext.
textLen := len(text)
dst := make([]byte, textLen)
f.cipher.XORKeyStream(dst, text)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not need th block_counter that's mentioned in the BIP because the standard library handles it all for us? Was a bit confused reading the code for Crypt since it was missing get_keystream_bytes and was wondering if the relevant code isn't needed for us.

@litecomb
Copy link

litecomb commented Nov 2, 2024

WriteV2MessageN should allow specify the garbage []byte, if nil default garbage is used
ReadV2MessageN should allow reading the garbage []byte

Rationale: this will permit custom nonstandard packets communication in the protocol, while still being compatible with bitcoin.

@litecomb
Copy link

litecomb commented Nov 9, 2024

It would be great if v2transport wasn't promoted to go module to ease reviewing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants