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 }