waku-org/go-waku

View on GitHub
library/encoding.go

Summary

Maintainability
A
3 hrs
Test Coverage
package library

import (
    "crypto/ecdsa"
    "encoding/json"
    "errors"

    "github.com/ethereum/go-ethereum/common/hexutil"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/crypto/secp256k1"
    "github.com/waku-org/go-waku/waku/v2/payload"
    "github.com/waku-org/go-waku/waku/v2/protocol/pb"
    "github.com/waku-org/go-waku/waku/v2/utils"
    "google.golang.org/protobuf/proto"
)

func wakuMessage(messageJSON string) (*pb.WakuMessage, error) {
    var msg *pb.WakuMessage
    err := json.Unmarshal([]byte(messageJSON), &msg)
    return msg, err
}

func EncodeSymmetric(messageJSON string, symmetricKey string, optionalSigningKey string) (string, error) {
    msg, err := wakuMessage(messageJSON)
    if err != nil {
        return "", err
    }

    msgPayload := payload.Payload{
        Data: msg.Payload,
        Key: &payload.KeyInfo{
            Kind: payload.Symmetric,
        },
    }

    keyBytes, err := utils.DecodeHexString(symmetricKey)
    if err != nil {
        return "", err
    }

    msgPayload.Key.SymKey = keyBytes

    if optionalSigningKey != "" {
        signingKeyBytes, err := utils.DecodeHexString(optionalSigningKey)
        if err != nil {
            return "", err
        }

        msgPayload.Key.PrivKey, err = crypto.ToECDSA(signingKeyBytes)
        if err != nil {
            return "", err
        }
    }

    msg.Version = proto.Uint32(payload.V1Encryption)
    msg.Payload, err = msgPayload.Encode(1)
    if err != nil {
        return "", err
    }

    encodedMsg, err := json.Marshal(msg)
    if err != nil {
        return "", err
    }

    return string(encodedMsg), err
}

func EncodeAsymmetric(messageJSON string, publicKey string, optionalSigningKey string) (string, error) {
    msg, err := wakuMessage(messageJSON)
    if err != nil {
        return "", err
    }

    msgPayload := payload.Payload{
        Data: msg.Payload,
        Key: &payload.KeyInfo{
            Kind: payload.Asymmetric,
        },
    }

    keyBytes, err := utils.DecodeHexString(publicKey)
    if err != nil {
        return "", err
    }

    msgPayload.Key.PubKey, err = unmarshalPubkey(keyBytes)
    if err != nil {
        return "", err
    }

    if optionalSigningKey != "" {
        signingKeyBytes, err := utils.DecodeHexString(optionalSigningKey)
        if err != nil {
            return "", err
        }

        msgPayload.Key.PrivKey, err = crypto.ToECDSA(signingKeyBytes)
        if err != nil {
            return "", err
        }
    }

    msg.Version = proto.Uint32(payload.V1Encryption)
    msg.Payload, err = msgPayload.Encode(payload.V1Encryption)
    if err != nil {
        return "", err
    }

    encodedMsg, err := json.Marshal(msg)
    if err != nil {
        return "", err
    }

    return string(encodedMsg), err
}

func extractPubKeyAndSignature(payload *payload.DecodedPayload) (pubkey string, signature string) {
    pkBytes := crypto.FromECDSAPub(payload.PubKey)
    if len(pkBytes) != 0 {
        pubkey = hexutil.Encode(pkBytes)
    }

    if len(payload.Signature) != 0 {
        signature = hexutil.Encode(payload.Signature)
    }

    return
}

type v0Response struct {
    Data    []byte `json:"data"`
    Padding []byte `json:"padding"`
}

type v1Response struct {
    PubKey    string `json:"pubkey,omitempty"`
    Signature string `json:"signature,omitempty"`
    Data      []byte `json:"data"`
    Padding   []byte `json:"padding"`
}

// DecodeSymmetric decodes a waku message using a 32 bytes symmetric key. The key must be a hex encoded string with "0x" prefix
func DecodeSymmetric(messageJSON string, symmetricKey string) (string, error) {
    var msg pb.WakuMessage
    err := json.Unmarshal([]byte(messageJSON), &msg)
    if err != nil {
        return "", err
    }

    if msg.GetVersion() == payload.Unencrypted {
        return marshalJSON(v0Response{
            Data: msg.Payload,
        })
    } else if msg.GetVersion() != payload.V1Encryption {
        return "", errors.New("unsupported wakumessage version")
    }

    keyInfo := &payload.KeyInfo{
        Kind: payload.Symmetric,
    }

    keyInfo.SymKey, err = utils.DecodeHexString(symmetricKey)
    if err != nil {
        return "", err
    }

    payload, err := payload.DecodePayload(&msg, keyInfo)
    if err != nil {
        return "", err
    }

    pubkey, signature := extractPubKeyAndSignature(payload)

    response := v1Response{
        PubKey:    pubkey,
        Signature: signature,
        Data:      payload.Data,
        Padding:   payload.Padding,
    }

    return marshalJSON(response)
}

// DecodeAsymmetric decodes a waku message using a secp256k1 private key. The key must be a hex encoded string with "0x" prefix
func DecodeAsymmetric(messageJSON string, privateKey string) (string, error) {
    var msg pb.WakuMessage
    err := json.Unmarshal([]byte(messageJSON), &msg)
    if err != nil {
        return "", err
    }

    if msg.GetVersion() == payload.Unencrypted {
        return marshalJSON(v0Response{
            Data: msg.Payload,
        })
    } else if msg.GetVersion() != payload.V1Encryption {
        return "", errors.New("unsupported wakumessage version")
    }

    keyInfo := &payload.KeyInfo{
        Kind: payload.Asymmetric,
    }

    keyBytes, err := utils.DecodeHexString(privateKey)
    if err != nil {
        return "", err
    }

    keyInfo.PrivKey, err = crypto.ToECDSA(keyBytes)
    if err != nil {
        return "", err
    }

    payload, err := payload.DecodePayload(&msg, keyInfo)
    if err != nil {
        return "", err
    }

    pubkey, signature := extractPubKeyAndSignature(payload)

    response := v1Response{
        PubKey:    pubkey,
        Signature: signature,
        Data:      payload.Data,
        Padding:   payload.Padding,
    }

    return marshalJSON(response)
}

func unmarshalPubkey(pub []byte) (ecdsa.PublicKey, error) {
    x, y := secp256k1.S256().Unmarshal(pub)
    if x == nil {
        return ecdsa.PublicKey{}, errors.New("invalid public key")
    }
    return ecdsa.PublicKey{Curve: secp256k1.S256(), X: x, Y: y}, nil
}