Blockchain getting started ethereum source code analysis encryption link between p2p-rlpx nodes 2

Source: Internet
Author: User
// Sign known message: static-shared-secret ^ nonce // This place should be directly using static sharing secrets. A Shared Secret generated by using your private key and the public key of the other party. Token, err = H. staticsharedsecret (PRV) If Err! = Nil {return nil, err} // here I understand that this initnonce is encrypted by sharing secrets. Signed: = XOR (token, H. initnonce) // use a random private key to encrypt this information. Signature, err: = crypto. Sign (signed, H. randomprivkey. exportecdsa () If Err! = Nil {return nil, err} MSG: = new (authmsgv4) Copy (msg. Signature [:], signature) // inform the initiator of the public key here. In this way, the other party can use its own private key and the public key to generate static shared secrets. Copy (MSG. initiatorpubkey [:], crypto. fromecdsapub (& Prv. publickey) [1:]) Copy (MSG. nonce [:], H. initnonce) MSG. version = 4 return MSG, nil} // staticsharedsecret returns the static Shared Secret, The result // of key agreement between the local and remote static node key. func (H * enchandshake) staticsharedsecret (PRV * ECDSA. privatekey) ([] Byte, error) {return ecies. importecdsa (PRV ). generateshared (H. remotepub, ssklen, ssklen )}

Sealeip8 method. This method is a group package method that performs RLP encoding on MSG. Fill in some data. Then, use the public key of the other party to encrypt the data. This means that only the private key of the other party can decrypt this information.

func sealEIP8(msg interface{}, h *encHandshake) ([]byte, error) {    buf := new(bytes.Buffer)    if err := rlp.Encode(buf, msg); err != nil {        return nil, err    }    // pad with random amount of data. the amount needs to be at least 100 bytes to make    // the message distinguishable from pre-EIP-8 handshakes.    pad := padSpace[:mrand.Intn(len(padSpace)-100)+100]    buf.Write(pad)    prefix := make([]byte, 2)    binary.BigEndian.PutUint16(prefix, uint16(buf.Len()+eciesOverhead))    enc, err := ecies.Encrypt(rand.Reader, h.remotePub, buf.Bytes(), nil, prefix)    return append(prefix, enc...), err}

Readhandshakemsg is called in two places. One is in initiatorenchandshake. One is in receiverenchandshake. This method is relatively simple. First, try Decoding in a format. If not, use another method. It should be a compatibility setting. Basically, it is to use its own private key for decoding and then call RLP to decode it into a struct. The structure description is the authrespv4 below. The most important thing in it is the random public key of the Peer end. Both parties can share the same secret through their own private key and the random public key at the peer end. However, this shared secret cannot be obtained by a third party.

// RLPx v4 handshake response (defined in EIP-8).type authRespV4 struct {    RandomPubkey [pubLen]byte    Nonce [shaLen]byte    Version uint    // Ignore additional fields (forward-compatibility)    Rest []rlp.RawValue `rlp:"tail"`}func readHandshakeMsg(msg plainDecoder, plainSize int, prv *ecdsa.PrivateKey, r io.Reader) ([]byte, error) {    buf := make([]byte, plainSize)    if _, err := io.ReadFull(r, buf); err != nil {        return buf, err    }    // Attempt decoding pre-EIP-8 "plain" format.    key := ecies.ImportECDSA(prv)    if dec, err := key.Decrypt(rand.Reader, buf, nil, nil); err == nil {        msg.decodePlain(dec)        return buf, nil    }    // Could be EIP-8 format, try that.    prefix := buf[:2]    size := binary.BigEndian.Uint16(prefix)    if size < uint16(plainSize) {        return buf, fmt.Errorf("size underflow, need at least %d bytes", plainSize)    }    buf = append(buf, make([]byte, size-uint16(plainSize)+2)...)    if _, err := io.ReadFull(r, buf[plainSize:]); err != nil {        return buf, err    }    dec, err := key.Decrypt(rand.Reader, buf[2:], nil, prefix)    if err != nil {        return buf, err    }    // Can‘t use rlp.DecodeBytes here because it rejects    // trailing data (forward-compatibility).    s := rlp.NewStream(bytes.NewReader(dec), 0)    return buf, s.Decode(msg)}

Handleauthresp this method is very simple.
Func (HEnchandshake) handleauthresp (msgAuthrespv4) (ERR error ){
H. respnonce = msg. nonce [:]
H. remoterandompub, err = importpublickey (msg. randompubkey [:])
Return err
}
The last is the secrets function, which is called after handshake is complete. It generates a sharing secret through its random private key and the public key at the peer end. This sharing secret is instantaneous (only exists in the current link ). So one day, the private key is cracked. The previous messages are still secure.
// Secrets is called after the handshake is completed.
// It extracts the connection secrets from the handshake values.
Func (H * enchandshake) secrets (auth, authresp [] Byte) (secrets, error ){
Ecdhesecret, err: = H. randomprivkey. generateshared (H. remoterandompub, ssklen, ssklen)
If Err! = Nil {
Return secrets {}, err
}

// Derive base secrets from ephemeral Key Agreement sharedsecret: = crypto. keccak256 (ecdhesecret, crypto. keccak256 (H. respnonce, H. initnonce) aessecret: = crypto. keccak256 (ecdhesecret, sharedsecret) // The Mac actually protects the sharing secret of ecdhesecret. Respnonce and initnonce values: S: = secrets {remoteid: H. remoteid, AES: aessecret, Mac: crypto. keccak256 (ecdhesecret, aessecret),} // setup sha3 instances for the MACs mac1: = sha3.newkeccak256 () mac1.write (XOR (S. mac, H. respnonce) mac1.write (auth) mac2: = sha3.newkeccak256 () mac2.write (XOR (S. mac, H. initnonce) mac2.write (authresp) // each packet received will check whether its MAC value meets the calculation result. If not, the problem occurs. If H. initiator {S. egressmac, S. ingressmac = mac1, mac2} else {S. egressmac, S. ingressmac = mac2, mac1} return S, nil}

The content of the receiverenchandshake function is roughly the same as that of initiatorenchandshake. But the order is somewhat different.

// receiverEncHandshake negotiates a session token on conn.// it should be called on the listening side of the connection.//// prv is the local client‘s private key.// token is the token from a previous session with this node.func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, token []byte) (s secrets, err error) {    authMsg := new(authMsgV4)    authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn)    if err != nil {        return s, err    }    h := new(encHandshake)    if err := h.handleAuthMsg(authMsg, prv); err != nil {        return s, err    }    authRespMsg, err := h.makeAuthResp()    if err != nil {        return s, err    }    var authRespPacket []byte    if authMsg.gotPlain {        authRespPacket, err = authRespMsg.sealPlain(h)    } else {        authRespPacket, err = sealEIP8(authRespMsg, h)    }    if err != nil {        return s, err    }    if _, err = conn.Write(authRespPacket); err != nil {        return s, err    }    return h.secrets(authPacket, authRespPacket)}

Doprotocolhandshake
This method is simple and the encrypted channel has been created. We can see that we only agree whether to use snappy encryption and then exit.
// Doenchandshake runs the protocol handshake using authenticated
// Messages. The protocol handshake is the first authenticated message
// And also verifies whether the encryption handshake 'worked' and
// Remote side actually provided the right public key.
Func (TRlpx) doprotohandshake (ourProtohandshake) (their * protohandshake, err error ){
// Writing our handshake happens concurrently, we prefer
// Returning the handshake read error. If the remote side
// Disconnects us early with a valid reason, we shoshould return it
// As the error so it can be tracked elsewhere.
Werr: = make (Chan error, 1)
Go func () {werr <-send (T. RW, handshakemsg, our )}()
If their, err = readprotocolhandshake (T. RW, our); Err! = Nil {
<-Werr // make sure the write terminates too
Return nil, err
}
If err: = <-werr; Err! = Nil {
Return nil, FMT. errorf ("write error: % v", err)
}
// If the Protocol version supports snappy encoding, upgrade immediately
T. RW. Snappy = their. version> = snappyprotocolversion

    return their, nil}

Rlpxframerw data frame Separation
Data frame splitting is mainly completed through the rlpxframerw class.
// Rlpxframerw implements a simplified version of rlpx framing.
// Chunked messages are not supported and all headers are equal
// Zeroheader.
//
// Rlpxframerw is not safe for concurrent use from multiple goroutines.
Type rlpxframerw struct {
Conn Io. readwriter
ENC cipher. Stream
Dec cipher. Stream

    macCipher cipher.Block    egressMAC hash.Hash    ingressMAC hash.Hash    snappy bool}

After the two handshakes are completed. Call the newrlpxframerw method to create this object.

t.rw = newRLPXFrameRW(t.fd, sec)

Then the readmsg and writemsg methods are provided. The two methods directly call the readmsg and writemsg of rlpxframerw.

func (t *rlpx) ReadMsg() (Msg, error) {    t.rmu.Lock()    defer t.rmu.Unlock()    t.fd.SetReadDeadline(time.Now().Add(frameReadTimeout))    return t.rw.ReadMsg()}func (t *rlpx) WriteMsg(msg Msg) error {    t.wmu.Lock()    defer t.wmu.Unlock()    t.fd.SetWriteDeadline(time.Now().Add(frameWriteTimeout))    return t.rw.WriteMsg(msg)}

Writemsg

func (rw *rlpxFrameRW) WriteMsg(msg Msg) error {    ptype, _ := rlp.EncodeToBytes(msg.Code)    // if snappy is enabled, compress message now    if rw.snappy {        if msg.Size > maxUint24 {            return errPlainMessageTooLarge        }        payload, _ := ioutil.ReadAll(msg.Payload)        payload = snappy.Encode(nil, payload)        msg.Payload = bytes.NewReader(payload)        msg.Size = uint32(len(payload))    }    // write header    headbuf := make([]byte, 32)    fsize := uint32(len(ptype)) + msg.Size    if fsize > maxUint24 {        return errors.New("message size overflows uint24")    }    putInt24(fsize, headbuf) // TODO: check overflow    copy(headbuf[3:], zeroHeader)    rw.enc.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now encrypted    // write header MAC    copy(headbuf[16:], updateMAC(rw.egressMAC, rw.macCipher, headbuf[:16]))    if _, err := rw.conn.Write(headbuf); err != nil {        return err    }    // write encrypted frame, updating the egress MAC hash with    // the data written to conn.    tee := cipher.StreamWriter{S: rw.enc, W: io.MultiWriter(rw.conn, rw.egressMAC)}    if _, err := tee.Write(ptype); err != nil {        return err    }    if _, err := io.Copy(tee, msg.Payload); err != nil {        return err    }    if padding := fsize % 16; padding > 0 {        if _, err := tee.Write(zero16[:16-padding]); err != nil {            return err        }    }    // write frame MAC. egress MAC hash is up to date because    // frame content was written to it as well.    fmacseed := rw.egressMAC.Sum(nil)    mac := updateMAC(rw.egressMAC, rw.macCipher, fmacseed)    _, err := rw.conn.Write(mac)    return err}

Readmsg

func (rw *rlpxFrameRW) ReadMsg() (msg Msg, err error) {    // read the header    headbuf := make([]byte, 32)    if _, err := io.ReadFull(rw.conn, headbuf); err != nil {        return msg, err    }    // verify header mac    shouldMAC := updateMAC(rw.ingressMAC, rw.macCipher, headbuf[:16])    if !hmac.Equal(shouldMAC, headbuf[16:]) {        return msg, errors.New("bad header MAC")    }    rw.dec.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now decrypted    fsize := readInt24(headbuf)    // ignore protocol type for now    // read the frame content    var rsize = fsize // frame size rounded up to 16 byte boundary    if padding := fsize % 16; padding > 0 {        rsize += 16 - padding    }    framebuf := make([]byte, rsize)    if _, err := io.ReadFull(rw.conn, framebuf); err != nil {        return msg, err    }    // read and validate frame MAC. we can re-use headbuf for that.    rw.ingressMAC.Write(framebuf)    fmacseed := rw.ingressMAC.Sum(nil)    if _, err := io.ReadFull(rw.conn, headbuf[:16]); err != nil {        return msg, err    }    shouldMAC = updateMAC(rw.ingressMAC, rw.macCipher, fmacseed)    if !hmac.Equal(shouldMAC, headbuf[:16]) {        return msg, errors.New("bad frame MAC")    }    // decrypt frame content    rw.dec.XORKeyStream(framebuf, framebuf)    // decode message code    content := bytes.NewReader(framebuf[:fsize])    if err := rlp.Decode(content, &msg.Code); err != nil {        return msg, err    }    msg.Size = uint32(content.Len())    msg.Payload = content    // if snappy is enabled, verify and decompress message    if rw.snappy {        payload, err := ioutil.ReadAll(msg.Payload)        if err != nil {            return msg, err        }        size, err := snappy.DecodedLen(payload)        if err != nil {            return msg, err        }        if size > int(maxUint24) {            return msg, errPlainMessageTooLarge        }        payload, err = snappy.Decode(nil, payload)        if err != nil {            return msg, err        }        msg.Size, msg.Payload = uint32(size), bytes.NewReader(payload)    }    return msg, nil}

Frame Structure

 normal = not chunked chunked-0 = First frame of a multi-frame packet chunked-n = Subsequent frames for multi-frame packet || is concatenate ^ is xorSingle-frame packet:header || header-mac || frame || frame-macMulti-frame packet:header || header-mac || frame-0 ||[ header || header-mac || frame-n || ... || ]header || header-mac || frame-last || frame-macheader: frame-size || header-data || paddingframe-size: 3-byte integer size of frame, big endian encoded (excludes padding)header-data: normal: rlp.list(protocol-type[, context-id]) chunked-0: rlp.list(protocol-type, context-id, total-packet-size) chunked-n: rlp.list(protocol-type, context-id) values: protocol-type: < 2**16 context-id: < 2**16 (optional for normal frames) total-packet-size: < 2**32padding: zero-fill to 16-byte boundaryheader-mac: right128 of egress-mac.update(aes(mac-secret,egress-mac) ^ header-ciphertext).digestframe: normal: rlp(packet-type) [|| rlp(packet-data)] || padding chunked-0: rlp(packet-type) || rlp(packet-data...) chunked-n: rlp(...packet-data) || paddingpadding: zero-fill to 16-byte boundary (only necessary for last frame)frame-mac: right128 of egress-mac.update(aes(mac-secret,egress-mac) ^ right128(egress-mac.update(frame-ciphertext).digest))egress-mac: h256, continuously updated with egress-bytes*ingress-mac: h256, continuously updated with ingress-bytes*

I am not familiar with encryption and decryption algorithms, so the analysis here is not thorough. The general process is analyzed for the moment. There are still many details not confirmed.

Blockchain getting started ethereum source code analysis encryption link between p2p-rlpx nodes 2

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.