mirror of https://github.com/status-im/go-waku.git
feat(noise): WakuPairing
This commit is contained in:
parent
636e6b284e
commit
f1fd8b354e
|
@ -147,11 +147,11 @@ var (
|
|||
Choices: []string{"DEBUG", "INFO", "WARN", "ERROR", "DPANIC", "PANIC", "FATAL"},
|
||||
Value: &options.LogLevel,
|
||||
},
|
||||
Usage: "Define the logging level,",
|
||||
Usage: "Define the logging level (allowed values: DEBUG, INFO, WARN, ERROR, DPANIC, PANIC, FATAL)",
|
||||
}
|
||||
LogEncoding = &cli.GenericFlag{
|
||||
Name: "log-encoding",
|
||||
Usage: "Define the encoding used for the logs",
|
||||
Usage: "Define the encoding used for the logs (allowed values: console, nocolor, json)",
|
||||
Value: &cliutils.ChoiceValue{
|
||||
Choices: []string{"console", "nocolor", "json"},
|
||||
Value: &options.LogEncoding,
|
||||
|
|
4
go.mod
4
go.mod
|
@ -33,7 +33,7 @@ require (
|
|||
github.com/gorilla/mux v1.8.0
|
||||
github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98
|
||||
github.com/waku-org/go-zerokit-rln v0.1.7-wakuorg
|
||||
github.com/waku-org/noise v1.0.2
|
||||
github.com/waku-org/noise v1.0.3
|
||||
golang.org/x/text v0.4.0
|
||||
)
|
||||
|
||||
|
@ -149,7 +149,7 @@ require (
|
|||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220920183852-bf014ff85ad5 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -1522,8 +1522,8 @@ github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98 h1:xwY0kW5XZF
|
|||
github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98/go.mod h1:eBHgM6T4EG0RZzxpxKy+rGz/6Dw2Nd8DWxS0lm9ESDw=
|
||||
github.com/waku-org/go-zerokit-rln v0.1.7-wakuorg h1:2vVIBCtBih2w1K9ll8YnToTDZvbxcgbsClsPlJS/kkg=
|
||||
github.com/waku-org/go-zerokit-rln v0.1.7-wakuorg/go.mod h1:GlyaVeEWNEBxVJrWC6jFTvb4LNb9d9qnjdS6EiWVUvk=
|
||||
github.com/waku-org/noise v1.0.2 h1:7WmlhpJ0eliBzwzKz6SoTqQznaEU2IuebHF3oCekqqs=
|
||||
github.com/waku-org/noise v1.0.2/go.mod h1:emThr8WZLeAtKqFW+/nXfHn9VucuXTh8aHap03UXP84=
|
||||
github.com/waku-org/noise v1.0.3 h1:BIecnRG0J0JlZmqcZTHphQ8yUeqqwkIaUAVk2JNK9VQ=
|
||||
github.com/waku-org/noise v1.0.3/go.mod h1:emThr8WZLeAtKqFW+/nXfHn9VucuXTh8aHap03UXP84=
|
||||
github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee h1:lYbXeSvJi5zk5GLKVuid9TVjS9a0OmLIDKTfoZBL6Ow=
|
||||
github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1:m2aV4LZI4Aez7dP5PMyVKEHhUyEJ/RjmPEDOpDvudHg=
|
||||
github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package noise
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
)
|
||||
|
||||
// Commits a public key pk for randomness r as H(pk || s)
|
||||
func CommitPublicKey(publicKey ed25519.PublicKey, r []byte) []byte {
|
||||
input := []byte{}
|
||||
input = append(input, []byte(publicKey)...)
|
||||
input = append(input, r...)
|
||||
res := sha256.Sum256(input)
|
||||
return res[:]
|
||||
}
|
|
@ -1,22 +1,28 @@
|
|||
package noise
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
n "github.com/waku-org/noise"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
// WakuNoiseProtocolID indicates the protocol ID defined according to https://rfc.vac.dev/spec/35/#specification
|
||||
type WakuNoiseProtocolID = byte
|
||||
|
||||
var (
|
||||
None = WakuNoiseProtocolID(0)
|
||||
Noise_K1K1_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID(10)
|
||||
Noise_XK1_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID(11)
|
||||
Noise_XX_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID(12)
|
||||
Noise_XXpsk0_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID(13)
|
||||
ChaChaPoly = WakuNoiseProtocolID(30)
|
||||
None = WakuNoiseProtocolID(0)
|
||||
Noise_K1K1_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID(10)
|
||||
Noise_XK1_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID(11)
|
||||
Noise_XX_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID(12)
|
||||
Noise_XXpsk0_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID(13)
|
||||
Noise_WakuPairing_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID(14)
|
||||
ChaChaPoly = WakuNoiseProtocolID(30)
|
||||
)
|
||||
|
||||
const NoisePaddingBlockSize = 248
|
||||
|
@ -26,7 +32,7 @@ var ErrorHandshakeComplete = errors.New("handshake complete")
|
|||
// All protocols share same cipher suite
|
||||
var cipherSuite = n.NewCipherSuite(n.DH25519, n.CipherChaChaPoly, n.HashSHA256)
|
||||
|
||||
func newHandshakeState(pattern n.HandshakePattern, initiator bool, staticKeypair n.DHKey, prologue []byte, presharedKey []byte, peerStatic []byte, peerEphemeral []byte) (hs *n.HandshakeState, err error) {
|
||||
func newHandshakeState(pattern n.HandshakePattern, initiator bool, staticKeypair n.DHKey, ephemeralKeyPair n.DHKey, prologue []byte, presharedKey []byte, peerStatic []byte, peerEphemeral []byte) (hs *n.HandshakeState, err error) {
|
||||
defer func() {
|
||||
if rerr := recover(); rerr != nil {
|
||||
err = fmt.Errorf("panic in Noise handshake: %s", rerr)
|
||||
|
@ -34,14 +40,15 @@ func newHandshakeState(pattern n.HandshakePattern, initiator bool, staticKeypair
|
|||
}()
|
||||
|
||||
cfg := n.Config{
|
||||
CipherSuite: cipherSuite,
|
||||
Pattern: pattern,
|
||||
Initiator: initiator,
|
||||
StaticKeypair: staticKeypair,
|
||||
Prologue: prologue,
|
||||
PresharedKey: presharedKey,
|
||||
PeerStatic: peerStatic,
|
||||
PeerEphemeral: peerEphemeral,
|
||||
CipherSuite: cipherSuite,
|
||||
Pattern: pattern,
|
||||
Initiator: initiator,
|
||||
StaticKeypair: staticKeypair,
|
||||
EphemeralKeypair: ephemeralKeyPair,
|
||||
Prologue: prologue,
|
||||
PresharedKey: presharedKey,
|
||||
PeerStatic: peerStatic,
|
||||
PeerEphemeral: peerEphemeral,
|
||||
}
|
||||
|
||||
return n.NewHandshakeState(cfg)
|
||||
|
@ -54,8 +61,10 @@ type Handshake struct {
|
|||
|
||||
hsBuff []byte
|
||||
|
||||
enc *n.CipherState
|
||||
dec *n.CipherState
|
||||
enc *n.CipherState
|
||||
dec *n.CipherState
|
||||
nametagsInbound *MessageNametagBuffer
|
||||
nametagsOutbound *MessageNametagBuffer
|
||||
|
||||
initiator bool
|
||||
shouldWrite bool
|
||||
|
@ -77,19 +86,21 @@ func getHandshakePattern(protocol WakuNoiseProtocolID) (n.HandshakePattern, erro
|
|||
return HandshakeXX, nil
|
||||
case Noise_XXpsk0_25519_ChaChaPoly_SHA256:
|
||||
return HandshakeXXpsk0, nil
|
||||
case Noise_WakuPairing_25519_ChaChaPoly_SHA256:
|
||||
return HandshakeWakuPairing, nil
|
||||
default:
|
||||
return n.HandshakePattern{}, errors.New("unsupported handshake pattern")
|
||||
}
|
||||
}
|
||||
|
||||
// NewHandshake creates a new handshake using aa WakuNoiseProtocolID that is maped to a handshake pattern.
|
||||
func NewHandshake(protocolID WakuNoiseProtocolID, initiator bool, staticKeypair n.DHKey, prologue []byte, presharedKey []byte, peerStatic []byte, peerEphemeral []byte) (*Handshake, error) {
|
||||
func NewHandshake(protocolID WakuNoiseProtocolID, initiator bool, staticKeypair n.DHKey, ephemeralKeyPair n.DHKey, prologue []byte, presharedKey []byte, peerStatic []byte, peerEphemeral []byte) (*Handshake, error) {
|
||||
hsPattern, err := getHandshakePattern(protocolID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hsState, err := newHandshakeState(hsPattern, initiator, staticKeypair, prologue, presharedKey, peerStatic, peerEphemeral)
|
||||
hsState, err := newHandshakeState(hsPattern, initiator, staticKeypair, ephemeralKeyPair, prologue, presharedKey, peerStatic, peerEphemeral)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -103,11 +114,24 @@ func NewHandshake(protocolID WakuNoiseProtocolID, initiator bool, staticKeypair
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (hs *Handshake) Hash() hash.Hash {
|
||||
return hs.state.Hash()
|
||||
}
|
||||
|
||||
func (hs *Handshake) H() []byte {
|
||||
return hs.state.H()
|
||||
}
|
||||
|
||||
func (hs *Handshake) RS() []byte {
|
||||
return hs.state.RS()
|
||||
}
|
||||
|
||||
// Step advances a step in the handshake. Each user in a handshake alternates writing and reading of handshake messages.
|
||||
// If the user is writing the handshake message, the transport message (if not empty) has to be passed to transportMessage and readPayloadV2 can be left to its default value
|
||||
// It the user is reading the handshake message, the read payload v2 has to be passed to readPayloadV2 and the transportMessage can be left to its default values.
|
||||
// TODO: this might be refactored into a separate `sendHandshakeMessage` and `receiveHandshakeMessage`
|
||||
func (hs *Handshake) Step(readPayloadV2 *PayloadV2, transportMessage []byte) (*HandshakeStepResult, error) {
|
||||
// TODO: this might be refactored into a separate `sendHandshakeMessage` and `receiveHandshakeMessage`.
|
||||
// The messageNameTag is an optional value and can be used to identify missing messages
|
||||
func (hs *Handshake) Step(readPayloadV2 *PayloadV2, transportMessage []byte, messageNametag *MessageNametag) (*HandshakeStepResult, error) {
|
||||
if hs.enc != nil || hs.dec != nil {
|
||||
return nil, ErrorHandshakeComplete
|
||||
}
|
||||
|
@ -129,13 +153,22 @@ func (hs *Handshake) Step(readPayloadV2 *PayloadV2, transportMessage []byte) (*H
|
|||
return nil, err
|
||||
}
|
||||
|
||||
msg, noisePubKeys, cs1, cs2, err = hs.state.WriteMessageAndGetPK(hs.hsBuff, [][]byte{}, payload)
|
||||
var mtag MessageNametag
|
||||
if messageNametag != nil {
|
||||
mtag = *messageNametag
|
||||
}
|
||||
|
||||
msg, noisePubKeys, cs1, cs2, err = hs.state.WriteMessageAndGetPK(hs.hsBuff, [][]byte{}, payload, mtag[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hs.shouldWrite = false
|
||||
|
||||
if messageNametag != nil {
|
||||
result.Payload2.MessageNametag = *messageNametag
|
||||
}
|
||||
|
||||
result.Payload2.TransportMessage = msg
|
||||
for _, npk := range noisePubKeys {
|
||||
result.Payload2.HandshakeMessage = append(result.Payload2.HandshakeMessage, byteToNoisePublicKey(npk))
|
||||
|
@ -146,10 +179,20 @@ func (hs *Handshake) Step(readPayloadV2 *PayloadV2, transportMessage []byte) (*H
|
|||
return nil, errors.New("readPayloadV2 is required")
|
||||
}
|
||||
|
||||
var mtag MessageNametag
|
||||
if messageNametag != nil {
|
||||
mtag = *messageNametag
|
||||
if !bytes.Equal(readPayloadV2.MessageNametag[:], mtag[:]) {
|
||||
return nil, ErrNametagNotExpected
|
||||
}
|
||||
}
|
||||
|
||||
readTMessage := readPayloadV2.TransportMessage
|
||||
|
||||
// We retrieve and store the (decrypted) received transport message by passing the messageNametag as extra additional data
|
||||
|
||||
// Since we only read, nothing meanigful (i.e. public keys) is returned. (hsBuffer is not affected)
|
||||
msg, cs1, cs2, err = hs.state.ReadMessage(nil, readTMessage)
|
||||
msg, cs1, cs2, err = hs.state.ReadMessage(nil, readTMessage, mtag[:]...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -167,7 +210,10 @@ func (hs *Handshake) Step(readPayloadV2 *PayloadV2, transportMessage []byte) (*H
|
|||
}
|
||||
|
||||
if cs1 != nil && cs2 != nil {
|
||||
hs.setCipherStates(cs1, cs2)
|
||||
err = hs.setCipherStates(cs1, cs2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
|
@ -179,18 +225,36 @@ func (hs *Handshake) HandshakeComplete() bool {
|
|||
}
|
||||
|
||||
// This is called when the final handshake message is processed
|
||||
func (hs *Handshake) setCipherStates(cs1, cs2 *n.CipherState) {
|
||||
func (hs *Handshake) setCipherStates(cs1, cs2 *n.CipherState) error {
|
||||
// Optional: We derive a secret for the nametag derivation
|
||||
nms1, nms2, err := hs.messageNametagSecrets()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hs.initiator {
|
||||
hs.enc = cs1
|
||||
hs.dec = cs2
|
||||
// and nametags secrets
|
||||
hs.nametagsInbound = NewMessageNametagBuffer(nms1)
|
||||
hs.nametagsOutbound = NewMessageNametagBuffer(nms2)
|
||||
} else {
|
||||
hs.enc = cs2
|
||||
hs.dec = cs1
|
||||
// and nametags secrets
|
||||
hs.nametagsInbound = NewMessageNametagBuffer(nms2)
|
||||
hs.nametagsOutbound = NewMessageNametagBuffer(nms1)
|
||||
}
|
||||
|
||||
// We initialize the message nametags inbound/outbound buffers
|
||||
hs.nametagsInbound.Init()
|
||||
hs.nametagsOutbound.Init()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encrypt calls the cipher's encryption. It encrypts the provided plaintext and returns a PayloadV2
|
||||
func (hs *Handshake) Encrypt(plaintext []byte) (*PayloadV2, error) {
|
||||
func (hs *Handshake) Encrypt(plaintext []byte, outboundMessageNametagBuffer ...*MessageNametagBuffer) (*PayloadV2, error) {
|
||||
if hs.enc == nil {
|
||||
return nil, errors.New("cannot encrypt, handshake incomplete")
|
||||
}
|
||||
|
@ -204,7 +268,15 @@ func (hs *Handshake) Encrypt(plaintext []byte) (*PayloadV2, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
cyphertext, err := hs.enc.Encrypt(nil, nil, paddedTransportMessage)
|
||||
// We set the message nametag using the input buffer
|
||||
var messageNametag MessageNametag
|
||||
if len(outboundMessageNametagBuffer) != 0 {
|
||||
messageNametag = outboundMessageNametagBuffer[0].Pop()
|
||||
} else {
|
||||
messageNametag = hs.nametagsOutbound.Pop()
|
||||
}
|
||||
|
||||
cyphertext, err := hs.enc.Encrypt(nil, messageNametag[:], paddedTransportMessage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -214,11 +286,12 @@ func (hs *Handshake) Encrypt(plaintext []byte) (*PayloadV2, error) {
|
|||
return &PayloadV2{
|
||||
ProtocolId: None,
|
||||
TransportMessage: cyphertext,
|
||||
MessageNametag: messageNametag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Decrypt calls the cipher's decryption. It decrypts the provided payload and returns the message in plaintext
|
||||
func (hs *Handshake) Decrypt(payload *PayloadV2) ([]byte, error) {
|
||||
func (hs *Handshake) Decrypt(payload *PayloadV2, inboundMessageNametagBuffer ...*MessageNametagBuffer) ([]byte, error) {
|
||||
if hs.dec == nil {
|
||||
return nil, errors.New("cannot decrypt, handshake incomplete")
|
||||
}
|
||||
|
@ -227,34 +300,94 @@ func (hs *Handshake) Decrypt(payload *PayloadV2) ([]byte, error) {
|
|||
return nil, errors.New("no payload to decrypt")
|
||||
}
|
||||
|
||||
// If the message nametag does not correspond to the nametag expected in the inbound message nametag buffer
|
||||
// an error is raised (to be handled externally, i.e. re-request lost messages, discard, etc.)
|
||||
if len(inboundMessageNametagBuffer) != 0 {
|
||||
err := inboundMessageNametagBuffer[0].CheckNametag(payload.MessageNametag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err := hs.nametagsInbound.CheckNametag(payload.MessageNametag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(payload.TransportMessage) == 0 {
|
||||
return nil, errors.New("tried to decrypt empty ciphertext")
|
||||
}
|
||||
|
||||
paddedMessage, err := hs.dec.Decrypt(nil, nil, payload.TransportMessage)
|
||||
// Decryption is done with messageNametag as associated data
|
||||
paddedMessage, err := hs.dec.Decrypt(nil, payload.MessageNametag[:], payload.TransportMessage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The message successfully decrypted, we can delete the first element of the inbound Message Nametag Buffer
|
||||
hs.nametagsInbound.Delete(1)
|
||||
|
||||
return PKCS7_Unpad(paddedMessage, NoisePaddingBlockSize)
|
||||
}
|
||||
|
||||
func getHKDF(h func() hash.Hash, ck []byte, ikm []byte, numBytes int) ([]byte, error) {
|
||||
hkdf := hkdf.New(h, ikm, ck, nil)
|
||||
result := make([]byte, numBytes)
|
||||
if _, err := io.ReadFull(hkdf, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Generates an 8 decimal digits authorization code using HKDF and the handshake state
|
||||
func (hs *Handshake) Authcode() (string, error) {
|
||||
output0, err := getHKDF(hs.Hash, hs.H(), nil, 8)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
bn := new(big.Int)
|
||||
bn.SetBytes(output0)
|
||||
code := new(big.Int)
|
||||
code.Mod(bn, big.NewInt(100_000_000))
|
||||
return fmt.Sprintf("'%8s'", code.String()), nil
|
||||
}
|
||||
|
||||
func (hs *Handshake) messageNametagSecrets() (nms1 []byte, nms2 []byte, err error) {
|
||||
output, err := getHKDF(hs.Hash, hs.H(), nil, 64)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
nms1 = output[0:32]
|
||||
nms2 = output[32:]
|
||||
return
|
||||
}
|
||||
|
||||
// Uses the cryptographic information stored in the input handshake state to generate a random message nametag
|
||||
// In current implementation the messageNametag = HKDF(handshake hash value), but other derivation mechanisms can be implemented
|
||||
func (hs *Handshake) ToMessageNametag() (MessageNametag, error) {
|
||||
output, err := getHKDF(hs.Hash, hs.H(), nil, 32)
|
||||
if err != nil {
|
||||
return [16]byte{}, err
|
||||
}
|
||||
return BytesToMessageNametag(output), nil
|
||||
}
|
||||
|
||||
// NewHandshake_XX_25519_ChaChaPoly_SHA256 creates a handshake where the initiator and receiver are not aware of each other static keys
|
||||
func NewHandshake_XX_25519_ChaChaPoly_SHA256(staticKeypair n.DHKey, initiator bool, prologue []byte) (*Handshake, error) {
|
||||
return NewHandshake(Noise_XX_25519_ChaChaPoly_SHA256, initiator, staticKeypair, prologue, nil, nil, nil)
|
||||
return NewHandshake(Noise_XX_25519_ChaChaPoly_SHA256, initiator, staticKeypair, n.DHKey{}, prologue, nil, nil, nil)
|
||||
}
|
||||
|
||||
// NewHandshake_XXpsk0_25519_ChaChaPoly_SHA256 creates a handshake where the initiator and receiver are not aware of each other static keys
|
||||
// and use a preshared secret to strengthen their mutual authentication
|
||||
func NewHandshake_XXpsk0_25519_ChaChaPoly_SHA256(staticKeypair n.DHKey, initiator bool, presharedKey []byte, prologue []byte) (*Handshake, error) {
|
||||
return NewHandshake(Noise_XXpsk0_25519_ChaChaPoly_SHA256, initiator, staticKeypair, prologue, presharedKey, nil, nil)
|
||||
return NewHandshake(Noise_XXpsk0_25519_ChaChaPoly_SHA256, initiator, staticKeypair, n.DHKey{}, prologue, presharedKey, nil, nil)
|
||||
}
|
||||
|
||||
// NewHandshake_K1K1_25519_ChaChaPoly_SHA256 creates a handshake where both initiator and recever know each other handshake. Only ephemeral keys
|
||||
// are exchanged. This handshake is useful in case the initiator needs to instantiate a new separate encrypted communication
|
||||
// channel with the receiver
|
||||
func NewHandshake_K1K1_25519_ChaChaPoly_SHA256(staticKeypair n.DHKey, initiator bool, peerStaticKey []byte, prologue []byte) (*Handshake, error) {
|
||||
return NewHandshake(Noise_K1K1_25519_ChaChaPoly_SHA256, initiator, staticKeypair, prologue, nil, peerStaticKey, nil)
|
||||
return NewHandshake(Noise_K1K1_25519_ChaChaPoly_SHA256, initiator, staticKeypair, n.DHKey{}, prologue, nil, peerStaticKey, nil)
|
||||
}
|
||||
|
||||
// NewHandshake_XK1_25519_ChaChaPoly_SHA256 creates a handshake where the initiator knows the receiver public static key. Within this handshake,
|
||||
|
@ -265,5 +398,11 @@ func NewHandshake_XK1_25519_ChaChaPoly_SHA256(staticKeypair n.DHKey, initiator b
|
|||
if !initiator && len(peerStaticKey) != 0 {
|
||||
return nil, errors.New("recipient shouldnt know initiator key")
|
||||
}
|
||||
return NewHandshake(Noise_XK1_25519_ChaChaPoly_SHA256, initiator, staticKeypair, prologue, nil, peerStaticKey, nil)
|
||||
return NewHandshake(Noise_XK1_25519_ChaChaPoly_SHA256, initiator, staticKeypair, n.DHKey{}, prologue, nil, peerStaticKey, nil)
|
||||
}
|
||||
|
||||
// NewHandshake_WakuPairing_25519_ChaChaPoly_SHA256
|
||||
func NewHandshake_WakuPairing_25519_ChaChaPoly_SHA256(staticKeypair n.DHKey, ephemeralKeyPair n.DHKey, initiator bool, prologue []byte, presharedKey []byte) (*Handshake, error) {
|
||||
peerEphemeral := presharedKey[0:32]
|
||||
return NewHandshake(Noise_WakuPairing_25519_ChaChaPoly_SHA256, initiator, staticKeypair, ephemeralKeyPair, prologue, presharedKey, nil, peerEphemeral)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
package noise
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type MessageNametag = [MessageNametagLength]byte
|
||||
|
||||
const MessageNametagLength = 16
|
||||
const MessageNametagBufferSize = 50
|
||||
|
||||
var (
|
||||
ErrNametagNotFound = errors.New("message nametag not found in buffer")
|
||||
ErrNametagNotExpected = errors.New("message nametag is present in buffer but is not the next expected nametag. One or more messages were probably lost")
|
||||
)
|
||||
|
||||
// Converts a sequence or array (arbitrary size) to a MessageNametag
|
||||
func BytesToMessageNametag(input []byte) MessageNametag {
|
||||
var result MessageNametag
|
||||
copy(result[:], input)
|
||||
return result
|
||||
}
|
||||
|
||||
type MessageNametagBuffer struct {
|
||||
buffer []MessageNametag
|
||||
counter uint64
|
||||
secret []byte
|
||||
}
|
||||
|
||||
func NewMessageNametagBuffer(secret []byte) *MessageNametagBuffer {
|
||||
return &MessageNametagBuffer{
|
||||
secret: secret,
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes the empty Message nametag buffer. The n-th nametag is equal to HKDF( secret || n )
|
||||
func (m *MessageNametagBuffer) Init() {
|
||||
// We default the counter and buffer fields
|
||||
m.counter = 0
|
||||
m.buffer = make([]MessageNametag, MessageNametagBufferSize)
|
||||
if len(m.secret) != 0 {
|
||||
for i := range m.buffer {
|
||||
counterBytesLE := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(counterBytesLE, m.counter)
|
||||
toHash := []byte{}
|
||||
toHash = append(toHash, m.secret...)
|
||||
toHash = append(toHash, counterBytesLE...)
|
||||
d := sha256.Sum256(toHash)
|
||||
m.buffer[i] = BytesToMessageNametag(d[:])
|
||||
m.counter++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MessageNametagBuffer) Pop() MessageNametag {
|
||||
// Note that if the input MessageNametagBuffer is set to default, an all 0 messageNametag is returned
|
||||
if len(m.buffer) == 0 {
|
||||
var m MessageNametag
|
||||
return m
|
||||
} else {
|
||||
messageNametag := m.buffer[0]
|
||||
m.Delete(1)
|
||||
return messageNametag
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the input messageNametag is contained in the input MessageNametagBuffer
|
||||
func (m *MessageNametagBuffer) CheckNametag(messageNametag MessageNametag) error {
|
||||
if len(m.buffer) != MessageNametagBufferSize {
|
||||
return nil
|
||||
}
|
||||
|
||||
index := -1
|
||||
for i, x := range m.buffer {
|
||||
if bytes.Equal(x[:], messageNametag[:]) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if index == -1 {
|
||||
return ErrNametagNotFound
|
||||
} else if index > 0 {
|
||||
return ErrNametagNotExpected
|
||||
}
|
||||
|
||||
// index is 0, hence the read message tag is the next expected one
|
||||
return nil
|
||||
}
|
||||
|
||||
func rotateLeft(elems []MessageNametag, k int) []MessageNametag {
|
||||
if k < 0 || len(elems) == 0 {
|
||||
return elems
|
||||
}
|
||||
r := len(elems) - k%len(elems)
|
||||
|
||||
result := elems[r:]
|
||||
result = append(result, elems[:r]...)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Deletes the first n elements in buffer and appends n new ones
|
||||
func (m *MessageNametagBuffer) Delete(n int) {
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// We ensure n is at most MessageNametagBufferSize (the buffer will be fully replaced)
|
||||
if n > MessageNametagBufferSize {
|
||||
n = MessageNametagBufferSize
|
||||
}
|
||||
|
||||
// We update the last n values in the array if a secret is set
|
||||
// Note that if the input MessageNametagBuffer is set to default, nothing is done here
|
||||
if len(m.secret) != 0 {
|
||||
m.buffer = rotateLeft(m.buffer, n)
|
||||
for i := 0; i < n; i++ {
|
||||
counterBytesLE := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(counterBytesLE, m.counter)
|
||||
toHash := []byte{}
|
||||
toHash = append(toHash, m.secret...)
|
||||
toHash = append(toHash, counterBytesLE...)
|
||||
d := sha256.Sum256(toHash)
|
||||
m.buffer[len(m.buffer)-n+i] = BytesToMessageNametag(d[:])
|
||||
m.counter++
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,11 +48,11 @@ func handshakeTest(t *testing.T, hsAlice *Handshake, hsBob *Handshake) {
|
|||
// By being the handshake initiator, Alice writes a Waku2 payload v2 containing her handshake message
|
||||
// and the (encrypted) transport message
|
||||
sentTransportMessage := generateRandomBytes(t, 32)
|
||||
aliceStep, err := hsAlice.Step(nil, sentTransportMessage)
|
||||
aliceStep, err := hsAlice.Step(nil, sentTransportMessage, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him
|
||||
bobStep, err := hsBob.Step(&aliceStep.Payload2, nil)
|
||||
bobStep, err := hsBob.Step(&aliceStep.Payload2, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// check:
|
||||
|
@ -64,11 +64,11 @@ func handshakeTest(t *testing.T, hsAlice *Handshake, hsBob *Handshake) {
|
|||
|
||||
// At this step, Bob writes and returns a payload
|
||||
sentTransportMessage = generateRandomBytes(t, 32)
|
||||
bobStep, err = hsBob.Step(nil, sentTransportMessage)
|
||||
bobStep, err = hsBob.Step(nil, sentTransportMessage, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// While Alice reads and returns the (decrypted) transport message
|
||||
aliceStep, err = hsAlice.Step(&bobStep.Payload2, nil)
|
||||
aliceStep, err = hsAlice.Step(&bobStep.Payload2, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// check:
|
||||
|
@ -80,11 +80,11 @@ func handshakeTest(t *testing.T, hsAlice *Handshake, hsBob *Handshake) {
|
|||
|
||||
// Similarly as in first step, Alice writes a Waku2 payload containing the handshake message and the (encrypted) transport message
|
||||
sentTransportMessage = generateRandomBytes(t, 32)
|
||||
aliceStep, err = hsAlice.Step(nil, sentTransportMessage)
|
||||
aliceStep, err = hsAlice.Step(nil, sentTransportMessage, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him
|
||||
bobStep, err = hsBob.Step(&aliceStep.Payload2, nil)
|
||||
bobStep, err = hsBob.Step(&aliceStep.Payload2, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// check:
|
||||
|
@ -95,10 +95,10 @@ func handshakeTest(t *testing.T, hsAlice *Handshake, hsBob *Handshake) {
|
|||
require.True(t, hsAlice.HandshakeComplete())
|
||||
require.True(t, hsBob.HandshakeComplete())
|
||||
|
||||
_, err = hsAlice.Step(nil, generateRandomBytes(t, 32))
|
||||
_, err = hsAlice.Step(nil, generateRandomBytes(t, 32), nil)
|
||||
require.ErrorIs(t, err, ErrorHandshakeComplete)
|
||||
|
||||
_, err = hsBob.Step(nil, generateRandomBytes(t, 32))
|
||||
_, err = hsBob.Step(nil, generateRandomBytes(t, 32), nil)
|
||||
require.ErrorIs(t, err, ErrorHandshakeComplete)
|
||||
|
||||
// #########################
|
||||
|
@ -107,14 +107,16 @@ func handshakeTest(t *testing.T, hsAlice *Handshake, hsBob *Handshake) {
|
|||
|
||||
// We test read/write of random messages exchanged between Alice and Bob
|
||||
|
||||
defaultMessageNametagBuffer := NewMessageNametagBuffer(nil)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
// Alice writes to Bob
|
||||
message := generateRandomBytes(t, 32)
|
||||
|
||||
encryptedPayload, err := hsAlice.Encrypt(message)
|
||||
encryptedPayload, err := hsAlice.Encrypt(message, defaultMessageNametagBuffer)
|
||||
require.NoError(t, err)
|
||||
|
||||
plaintext, err := hsBob.Decrypt(encryptedPayload)
|
||||
plaintext, err := hsBob.Decrypt(encryptedPayload, defaultMessageNametagBuffer)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, message, plaintext)
|
||||
|
@ -122,10 +124,10 @@ func handshakeTest(t *testing.T, hsAlice *Handshake, hsBob *Handshake) {
|
|||
// Bob writes to Alice
|
||||
message = generateRandomBytes(t, 32)
|
||||
|
||||
encryptedPayload, err = hsBob.Encrypt(message)
|
||||
encryptedPayload, err = hsBob.Encrypt(message, defaultMessageNametagBuffer)
|
||||
require.NoError(t, err)
|
||||
|
||||
plaintext, err = hsAlice.Decrypt(encryptedPayload)
|
||||
plaintext, err = hsAlice.Decrypt(encryptedPayload, defaultMessageNametagBuffer)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, message, plaintext)
|
||||
|
|
|
@ -75,3 +75,13 @@ var HandshakeXXpsk0 = n.HandshakePattern{
|
|||
{n.MessagePatternS, n.MessagePatternDHSE},
|
||||
},
|
||||
}
|
||||
|
||||
var HandshakeWakuPairing = n.HandshakePattern{
|
||||
Name: "WakuPairing",
|
||||
ResponderPreMessages: []n.MessagePattern{n.MessagePatternE},
|
||||
Messages: [][]n.MessagePattern{
|
||||
{n.MessagePatternE, n.MessagePatternDHEE},
|
||||
{n.MessagePatternS, n.MessagePatternDHES},
|
||||
{n.MessagePatternS, n.MessagePatternDHSE, n.MessagePatternDHSS},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -116,6 +116,7 @@ type PayloadV2 struct {
|
|||
ProtocolId byte
|
||||
HandshakeMessage []*NoisePublicKey
|
||||
TransportMessage []byte
|
||||
MessageNametag MessageNametag
|
||||
}
|
||||
|
||||
// Checks equality between two PayloadsV2 objects
|
||||
|
@ -165,7 +166,8 @@ func (p *PayloadV2) Serialize() ([]byte, error) {
|
|||
// payload = ( protocolId || serializedHandshakeMessageLen || serializedHandshakeMessage || transportMessageLen || transportMessage)
|
||||
|
||||
// We declare it as a byte sequence of length accordingly to the PayloadV2 information read
|
||||
payload := make([]byte, 0, 1+ // 1 byte for protocol ID
|
||||
payload := make([]byte, 0, MessageNametagLength+
|
||||
1+ // 1 byte for protocol ID
|
||||
1+ // 1 byte for length of serializedHandshakeMessage field
|
||||
serializedHandshakeMessageLen+ // serializedHandshakeMessageLen bytes for serializedHandshakeMessage
|
||||
8+ // 8 bytes for transportMessageLen
|
||||
|
@ -174,6 +176,10 @@ func (p *PayloadV2) Serialize() ([]byte, error) {
|
|||
|
||||
payloadBuf := bytes.NewBuffer(payload)
|
||||
|
||||
if _, err := payloadBuf.Write(p.MessageNametag[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The protocol ID (1 byte) and handshake message length (1 byte) can be directly casted to byte to allow direct copy to the payload byte sequence
|
||||
if err := payloadBuf.WriteByte(p.ProtocolId); err != nil {
|
||||
return nil, err
|
||||
|
@ -215,7 +221,12 @@ func DeserializePayloadV2(payload []byte) (*PayloadV2, error) {
|
|||
|
||||
result := &PayloadV2{}
|
||||
|
||||
// We start reading the Protocol ID
|
||||
// We start by reading the messageNametag
|
||||
if err := binary.Read(payloadBuf, binary.BigEndian, &result.MessageNametag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We read the Protocol ID
|
||||
// TODO: when the list of supported protocol ID is defined, check if read protocol ID is supported
|
||||
if err := binary.Read(payloadBuf, binary.BigEndian, &result.ProtocolId); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
package noise
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
b64 "encoding/base64"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type QR struct {
|
||||
applicationName string
|
||||
applicationVersion string
|
||||
shardId string
|
||||
ephemeralKey ed25519.PublicKey
|
||||
committedStaticKey []byte
|
||||
}
|
||||
|
||||
func NewQR(applicationName, applicationVersion, shardId string, ephemeralKey ed25519.PublicKey, committedStaticKey []byte) QR {
|
||||
return QR{
|
||||
applicationName: applicationName,
|
||||
applicationVersion: applicationVersion,
|
||||
shardId: shardId,
|
||||
ephemeralKey: ephemeralKey,
|
||||
committedStaticKey: committedStaticKey,
|
||||
}
|
||||
}
|
||||
|
||||
// Serializes input parameters to a base64 string for exposure through QR code (used by WakuPairing)
|
||||
func (qr QR) String() string {
|
||||
return b64.StdEncoding.EncodeToString([]byte(qr.applicationName)) + ":" +
|
||||
b64.StdEncoding.EncodeToString([]byte(qr.applicationVersion)) + ":" +
|
||||
b64.StdEncoding.EncodeToString([]byte(qr.shardId)) + ":" +
|
||||
b64.StdEncoding.EncodeToString(qr.ephemeralKey) + ":" +
|
||||
b64.StdEncoding.EncodeToString(qr.committedStaticKey[:])
|
||||
}
|
||||
|
||||
func (qr QR) Bytes() []byte {
|
||||
return []byte(qr.String())
|
||||
}
|
||||
|
||||
// Deserializes input string in base64 to the corresponding (applicationName, applicationVersion, shardId, ephemeralKey, committedStaticKey)
|
||||
func StringToQR(qrString string) (QR, error) {
|
||||
values := strings.Split(qrString, ":")
|
||||
if len(values) != 5 {
|
||||
return QR{}, errors.New("invalid qr string")
|
||||
}
|
||||
|
||||
applicationName, err := b64.StdEncoding.DecodeString(values[0])
|
||||
if err != nil {
|
||||
return QR{}, err
|
||||
}
|
||||
|
||||
applicationVersion, err := b64.StdEncoding.DecodeString(values[1])
|
||||
if err != nil {
|
||||
return QR{}, err
|
||||
}
|
||||
|
||||
shardId, err := b64.StdEncoding.DecodeString(values[2])
|
||||
if err != nil {
|
||||
return QR{}, err
|
||||
}
|
||||
|
||||
ephemeralKey, err := b64.StdEncoding.DecodeString(values[3])
|
||||
if err != nil {
|
||||
return QR{}, err
|
||||
}
|
||||
|
||||
committedStaticKey, err := b64.StdEncoding.DecodeString(values[4])
|
||||
if err != nil {
|
||||
return QR{}, err
|
||||
}
|
||||
|
||||
return QR{
|
||||
applicationName: string(applicationName),
|
||||
applicationVersion: string(applicationVersion),
|
||||
shardId: string(shardId),
|
||||
ephemeralKey: ephemeralKey,
|
||||
committedStaticKey: committedStaticKey,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
package noise
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
n "github.com/waku-org/noise"
|
||||
)
|
||||
|
||||
func TestWakuPairing(t *testing.T) {
|
||||
// Pairing Phase
|
||||
// ==========
|
||||
|
||||
// Alice static/ephemeral key initialization and commitment
|
||||
aliceStaticKey, _ := n.DH25519.GenerateKeypair(rand.Reader)
|
||||
aliceEphemeralKey, _ := n.DH25519.GenerateKeypair(rand.Reader)
|
||||
s := generateRandomBytes(t, 32)
|
||||
aliceCommittedStaticKey := CommitPublicKey(aliceStaticKey.Public, s)
|
||||
|
||||
// Bob static/ephemeral key initialization and commitment
|
||||
bobStaticKey, _ := n.DH25519.GenerateKeypair(rand.Reader)
|
||||
bobEphemeralKey, _ := n.DH25519.GenerateKeypair(rand.Reader)
|
||||
r := generateRandomBytes(t, 32)
|
||||
bobCommittedStaticKey := CommitPublicKey(bobStaticKey.Public, r)
|
||||
|
||||
// Content topic information
|
||||
applicationName := "waku-noise-sessions"
|
||||
applicationVersion := "0.1"
|
||||
shardId := "10"
|
||||
qrMessageNameTag := BytesToMessageNametag(generateRandomBytes(t, MessageNametagLength))
|
||||
|
||||
// Out-of-band Communication
|
||||
|
||||
// Bob prepares the QR and sends it out-of-band to Alice
|
||||
qr := NewQR(applicationName, applicationVersion, shardId, bobEphemeralKey.Public, bobCommittedStaticKey)
|
||||
|
||||
// Alice deserializes the QR code
|
||||
readQR, err := StringToQR(qr.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
// We check if QR serialization/deserialization works
|
||||
require.Equal(t, applicationName, readQR.applicationName)
|
||||
require.Equal(t, applicationVersion, readQR.applicationVersion)
|
||||
require.Equal(t, shardId, readQR.shardId)
|
||||
require.True(t, bytes.Equal(bobEphemeralKey.Public, readQR.ephemeralKey))
|
||||
require.True(t, bytes.Equal(bobCommittedStaticKey[:], readQR.committedStaticKey[:]))
|
||||
|
||||
// Pre-handshake message
|
||||
// <- eB {H(sB||r), contentTopicParams, messageNametag}
|
||||
preMessagePKs := bobEphemeralKey.Public
|
||||
|
||||
// We initialize the Handshake states.
|
||||
// Note that we pass the whole qr serialization as prologue information
|
||||
|
||||
aliceHS, err := NewHandshake_WakuPairing_25519_ChaChaPoly_SHA256(aliceStaticKey, aliceEphemeralKey, true, qr.Bytes(), preMessagePKs)
|
||||
require.NoError(t, err)
|
||||
|
||||
bobHS, err := NewHandshake_WakuPairing_25519_ChaChaPoly_SHA256(bobStaticKey, bobEphemeralKey, false, qr.Bytes(), preMessagePKs)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Pairing Handshake
|
||||
// ==========
|
||||
|
||||
// Write and read calls alternate between Alice and Bob: the handhshake progresses by alternatively calling stepHandshake for each user
|
||||
|
||||
// 1st step
|
||||
// -> eA, eAeB {H(sA||s)} [authcode]
|
||||
|
||||
// The messageNametag for the first handshake message is randomly generated and exchanged out-of-band
|
||||
// and corresponds to qrMessageNametag
|
||||
|
||||
// We set the transport message to be H(sA||s)
|
||||
sentTransportMessage := aliceCommittedStaticKey
|
||||
|
||||
// By being the handshake initiator, Alice writes a Waku2 payload v2 containing her handshake message
|
||||
// and the (encrypted) transport message
|
||||
// The message is sent with a messageNametag equal to the one received through the QR code
|
||||
aliceStep, err := aliceHS.Step(nil, sentTransportMessage, &qrMessageNameTag)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him
|
||||
// Note that Bob verifies if the received payloadv2 has the expected messageNametag set
|
||||
bobStep, err := bobHS.Step(&aliceStep.Payload2, nil, &qrMessageNameTag)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, bytes.Equal(bobStep.TransportMessage, sentTransportMessage))
|
||||
|
||||
// We generate an authorization code using the handshake state
|
||||
aliceAuthcode, err := aliceHS.Authcode()
|
||||
require.NoError(t, err)
|
||||
|
||||
bobAuthcode, err := bobHS.Authcode()
|
||||
require.NoError(t, err)
|
||||
|
||||
// We check that they are equal. Note that this check has to be confirmed with a user interaction.
|
||||
require.Equal(t, aliceAuthcode, bobAuthcode)
|
||||
|
||||
// 2nd step
|
||||
// <- sB, eAsB {r}
|
||||
|
||||
// Alice and Bob update their local next messageNametag using the available handshake information
|
||||
// During the handshake, messageNametag = HKDF(h), where h is the handshake hash value at the end of the last processed message
|
||||
aliceMessageNametag, err := aliceHS.ToMessageNametag()
|
||||
require.NoError(t, err)
|
||||
|
||||
bobMessageNametag, err := bobHS.ToMessageNametag()
|
||||
require.NoError(t, err)
|
||||
|
||||
// We set as a transport message the commitment randomness r
|
||||
sentTransportMessage = r
|
||||
|
||||
// At this step, Bob writes and returns a payload
|
||||
bobStep, err = bobHS.Step(nil, sentTransportMessage, &bobMessageNametag)
|
||||
require.NoError(t, err)
|
||||
|
||||
// While Alice reads and returns the (decrypted) transport message
|
||||
aliceStep, err = aliceHS.Step(&bobStep.Payload2, nil, &aliceMessageNametag)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, aliceStep.TransportMessage, sentTransportMessage)
|
||||
|
||||
// Alice further checks if Bob's commitment opens to Bob's static key she just received
|
||||
expectedBobCommittedStaticKey := CommitPublicKey(aliceHS.RS(), aliceStep.TransportMessage)
|
||||
require.True(t, bytes.Equal(expectedBobCommittedStaticKey, bobCommittedStaticKey))
|
||||
|
||||
// 3rd step
|
||||
// -> sA, sAeB, sAsB {s}
|
||||
|
||||
// Alice and Bob update their local next messageNametag using the available handshake information
|
||||
aliceMessageNametag, err = aliceHS.ToMessageNametag()
|
||||
require.NoError(t, err)
|
||||
|
||||
bobMessageNametag, err = bobHS.ToMessageNametag()
|
||||
require.NoError(t, err)
|
||||
|
||||
// We set as a transport message the commitment randomness s
|
||||
sentTransportMessage = s
|
||||
|
||||
// Similarly as in first step, Alice writes a Waku2 payload containing the handshake message and the (encrypted) transport message
|
||||
aliceStep, err = aliceHS.Step(nil, sentTransportMessage, &aliceMessageNametag)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him
|
||||
bobStep, err = bobHS.Step(&aliceStep.Payload2, nil, &bobMessageNametag)
|
||||
require.NoError(t, err)
|
||||
require.True(t, bytes.Equal(bobStep.TransportMessage, sentTransportMessage))
|
||||
|
||||
// Bob further checks if Alice's commitment opens to Alice's static key he just received
|
||||
expectedAliceCommittedStaticKey := CommitPublicKey(bobHS.RS(), bobStep.TransportMessage)
|
||||
|
||||
require.True(t, bytes.Equal(expectedAliceCommittedStaticKey, aliceCommittedStaticKey))
|
||||
|
||||
// Secure Transfer Phase
|
||||
// ==========
|
||||
|
||||
// We test read/write of random messages exchanged between Alice and Bob
|
||||
// Note that we exchange more than the number of messages contained in the nametag buffer to test if they are filled correctly as the communication proceeds
|
||||
for i := 0; i < 10*MessageNametagBufferSize; i++ {
|
||||
// Alice writes to Bob
|
||||
message := generateRandomBytes(t, 32)
|
||||
payload, err := aliceHS.Encrypt(message)
|
||||
require.NoError(t, err)
|
||||
|
||||
readMessage, err := bobHS.Decrypt(payload)
|
||||
require.NoError(t, err)
|
||||
require.True(t, bytes.Equal(message, readMessage))
|
||||
|
||||
// Bob writes to Alice
|
||||
message = generateRandomBytes(t, 32)
|
||||
payload, err = bobHS.Encrypt(message)
|
||||
require.NoError(t, err)
|
||||
|
||||
readMessage, err = aliceHS.Decrypt(payload)
|
||||
require.NoError(t, err)
|
||||
require.True(t, bytes.Equal(message, readMessage))
|
||||
}
|
||||
|
||||
// We test how nametag buffers help in detecting lost messages
|
||||
// Alice writes two messages to Bob, but only the second is received
|
||||
message := generateRandomBytes(t, 32)
|
||||
_, err = aliceHS.Encrypt(message)
|
||||
require.NoError(t, err)
|
||||
|
||||
message = generateRandomBytes(t, 32)
|
||||
payload2, err := aliceHS.Encrypt(message)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = bobHS.Decrypt(payload2)
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, ErrNametagNotExpected)
|
||||
|
||||
// We adjust bob nametag buffer for next test (i.e. the missed message is correctly recovered)
|
||||
bobHS.nametagsInbound.Delete(2)
|
||||
message = generateRandomBytes(t, 32)
|
||||
payload2, err = bobHS.Encrypt(message)
|
||||
require.NoError(t, err)
|
||||
readMessage, err := aliceHS.Decrypt(payload2)
|
||||
require.NoError(t, err)
|
||||
require.True(t, bytes.Equal(message, readMessage))
|
||||
|
||||
// We test if a missing nametag is correctly detected
|
||||
message = generateRandomBytes(t, 32)
|
||||
payload2, err = aliceHS.Encrypt(message)
|
||||
require.NoError(t, err)
|
||||
bobHS.nametagsInbound.Delete(1)
|
||||
_, err = bobHS.Decrypt(payload2)
|
||||
require.ErrorIs(t, err, ErrNametagNotFound)
|
||||
}
|
Loading…
Reference in New Issue