2

If you look at how this go-cardano-client is making it's handshake request payload:

https://github.com/gocardano/go-cardano-client/blob/master/shelley/handshake.go#L64

  versionTable.Add(cbor.NewPositiveInteger8(1), cbor.NewPositiveInteger(764824073))
  versionTable.Add(cbor.NewPositiveInteger16(32770), cbor.NewPositiveInteger(764824073))
  versionTable.Add(cbor.NewPositiveInteger16(32771), cbor.NewPositiveInteger(764824073))

But the grpc generated struct is:

type HandshakeRequest struct {
  // Nonce for the server to authenticate its node ID with.
  Nonce []byte `protobuf:"bytes,1,opt,name=nonce,proto3" json:"nonce,omitempty"`
}

And this []byte needs to come through for the nonce referenced:

https://github.com/input-output-hk/jormungandr/blob/master/jormungandr/src/network/service.rs#L60

It's length 32:

https://github.com/input-output-hk/jormungandr/blob/master/jormungandr/src/network/client/connect.rs#L58

https://github.com/input-output-hk/jormungandr/blob/6f324b706a13273afb6a0808e589735020bb59da/jormungandr/src/network/mod.rs#L73

So this line in the golang code:

  versionTable.Add(cbor.NewPositiveInteger8(1), cbor.NewPositiveInteger(764824073))

can't be length 32 []byte right? How do I encode this:

  req := HandshakeRequest{}
  req.Nonce = []byte{}
  for i := 0; i < 32; i++ {
    req.Nonce = append(req.Nonce, byte(rand.Intn(256)))
  }

into this versionTable "params"?

see also and proto

user3840170
  • 26,597
  • 4
  • 30
  • 62
Andrew Arrow
  • 4,248
  • 9
  • 53
  • 80
  • rs client generates random bytes with length of 32 of nonce and sends them to server. Server responses with encrypted nonce. So what you want to do is generate nonce with 32bit? – Billy Yuan Mar 25 '21 at 06:09

1 Answers1

0

Edit: You seem to assume that the the handshake from gocardano/go-cardano-client and the one described in node.proto somehow are related to the same implementation. Actually, I don't think they do.

The TCP-based handshake follows the Shelley protocol specs and sends a payload with the encoded versionTable. The gRPC-based HandshakeRequest instead is, as you also considered, just a nonce. There's nothing in the proto schema that hints to the Shelley protocol. The comments on the Nonce field also say that quite explicitly: "Nonce for the server to authenticate its node ID with."

So it would be a bit strange to assume that this nonce and the versionTable payload have anything in common at all.

Edit 2: In addition, it seems the "Jormungandr" rust node implementation does not support Shelley at all, so when you say you can't connect to the nodes in the relay topology, I think you shouldn't look for answers in the Jormungandr repository. Instead, I think the relays run the Haskell implementations of the Ouroboros network.

Now as for why you can't connect, the go-cardano client panics on some unchecked type assertions, because after the QueryTip Shelley message chainSyncBlocks.RequestNext, the relay servers respond with a different mini-protocol altogether, transactionSubmission.msgRequestTxIds as shown by running the client with TCP and tracing the messages:

MiniProtocol: 4 / MessageMode: 1 / f1bb7f80800400058400f50003
Array: [4]
  PositiveInteger8(0)
  False
  PositiveInteger8(0)
  PositiveInteger8(3)

You'll also have the same result when sending a sync chain request with MiniProtocol 2 (ChainSyncHeaders). I checked the Shelley protocol specs but couldn't find an explicit indication about why the server would switch protocols... Unfortunately I'm not familiar enough with Haskell to gain further insight from the Ouroboros sources.


In the unexpected case that the nonce in the proto HandshakeRequest is indeed related to the Shelley protocol, its content might be the CBOR array in your linked Cardano client (speculations follows):

    arr := cbor.NewArray()
    arr.Add(cbor.NewPositiveInteger8(handshakeMessagePropose))
    versionTable := cbor.NewMap()
    arr.Add(versionTable)

    versionTable.Add(...)
    versionTable.Add(...)
    versionTable.Add(...)

    return []cbor.DataItem{arr}

By inspecting the client where the handshake request is used, we can see:

messageResponse, err := c.queryNode(multiplex.MiniProtocolIDMuxControl, handshakeRequest())

and then in queryNode:

sdu := multiplex.NewServiceDataUnit(miniProtocol, multiplex.MessageModeInitiator, dataItems)
...
c.socket.Write(sdu.Bytes())

The sdu.Bytes() method serializes the entire payload, in particular:

// EncodeList return CBOR representation for each item in the list
func EncodeList(list []DataItem) []byte {
    result := []byte{}
    for _, item := range list {
        result = append(result, item.EncodeCBOR()...)
    }
    return result
}

The EncodeCBOR() method is implemented by both the Array and the Map used in the handshakeRequest() []cbor.DataItem function. Watch out that the handshake function returns a slice []cbor.DataItem which contains one Array item which contains (as documented) the handshakeMessagePropose and the versionTable map.

If you carefully follow how the serialization proceeds, you'll eventually obtain the breakdown of the byte array — hereafter in decimal:

[130 0 163 1 26 45 150 74 9 25 128 2 26 45 150 74 9 25 128 3 26 45 150 74 9]

Where:

  • 130 is the array data item prefix
  • 0 is the handshakeMessagePropose
  • 163 is the map data item prefix
  • and the subsequent bytes are the versionTable map

It is 25 bytes in total. At this point, I don't know if the multiplex wrapper built in queryNode function is part of the nonce or not. With the full wrapper, the length of the serialized byte array goes up to 33. So excluding some control bits or whatnot, this might be what you're supposed to write into the HandshakeRequest.Nonce.

blackgreen
  • 34,072
  • 23
  • 111
  • 129
  • great info thanks! Question though, if the entire thing is the nonce, and it hard codes 764824073 how is that really a nonce? Should that 764824073 number be random vs always the same? – Andrew Arrow Mar 25 '21 at 22:13