|
| 1 | +Sending messages via the relay server |
| 2 | +========================= |
| 3 | +This document explains how to send messages via the webrelay to a recipient on the OpenBazaar network. |
| 4 | + |
| 5 | +Before begining you will need to compile https://github.com/OpenBazaar/openbazaar-go/blob/master/pb/protos/message.proto to javascript. |
| 6 | + |
| 7 | +The following are the steps needed to send a message: |
| 8 | + |
| 9 | +1) Create the `pb.Envelope` protobuf object. |
| 10 | +```proto |
| 11 | +message Envelope { |
| 12 | + Message message = 1; |
| 13 | + bytes pubkey = 2; |
| 14 | + bytes signature = 3; |
| 15 | +} |
| 16 | +``` |
| 17 | +`pubkey` is the serialized libp2p pubkey key that corresponds to your node's PeerID. |
| 18 | + |
| 19 | +`signature` is a signature produced by signing the serialized `message` with the node's private key which corresponds to the PeerID. |
| 20 | + |
| 21 | +`message` is a `pb.Message` object in this format: |
| 22 | +```proto |
| 23 | +message Message { |
| 24 | + MessageType messageType = 1; |
| 25 | + google.protobuf.Any payload = 2; |
| 26 | +} |
| 27 | +``` |
| 28 | +`messageType` may be one of the following: |
| 29 | +```proto |
| 30 | +enum MessageType { |
| 31 | + PING = 0; |
| 32 | + CHAT = 1; |
| 33 | + FOLLOW = 2; |
| 34 | + UNFOLLOW = 3; |
| 35 | + ORDER = 4; |
| 36 | + ORDER_REJECT = 5; |
| 37 | + ORDER_CANCEL = 6; |
| 38 | + ORDER_CONFIRMATION = 7; |
| 39 | + ORDER_FULFILLMENT = 8; |
| 40 | + ORDER_COMPLETION = 9; |
| 41 | + DISPUTE_OPEN = 10; |
| 42 | + DISPUTE_UPDATE = 11; |
| 43 | + DISPUTE_CLOSE = 12; |
| 44 | + REFUND = 13; |
| 45 | + OFFLINE_ACK = 14; |
| 46 | + OFFLINE_RELAY = 15; |
| 47 | + MODERATOR_ADD = 16; |
| 48 | + MODERATOR_REMOVE = 17; |
| 49 | + STORE = 18; |
| 50 | + BLOCK = 19; |
| 51 | + VENDOR_FINALIZED_PAYMENT = 20; |
| 52 | + ERROR = 500; |
| 53 | +} |
| 54 | +``` |
| 55 | + |
| 56 | +Whereas the `payload` is a protobuf `Any` object which has the following format: |
| 57 | +```proto |
| 58 | +message Any { |
| 59 | + string type_url = 1; |
| 60 | + bytes value = 2; |
| 61 | +} |
| 62 | +``` |
| 63 | +Where `type_url` must be set to "type.googleapis.com/{protobuf object name}" and `value` is a serialized protobuf object. |
| 64 | + |
| 65 | +For example if sending a `Chat` message the `type_url` would be "type.googleapis.com/Chat" and the `value` would be the serialization of |
| 66 | +```proto |
| 67 | +message Chat { |
| 68 | + string messageId = 1; |
| 69 | + string subject = 2; |
| 70 | + string message = 3; |
| 71 | + google.protobuf.Timestamp timestamp = 4; |
| 72 | + Flag flag = 5; |
| 73 | +
|
| 74 | + enum Flag { |
| 75 | + MESSAGE = 0; |
| 76 | + TYPING = 1; |
| 77 | + READ = 2; |
| 78 | + } |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +The protobuf library will usually have a function to create an `Any` object. |
| 83 | + |
| 84 | +The following is an example of creating a a chat message in Go: |
| 85 | + |
| 86 | +```go |
| 87 | + // First we create the `pb.Chat` object |
| 88 | + chatMessage := "hey you bastard" |
| 89 | + subject := "" |
| 90 | + |
| 91 | + // The chat message contains a timestamp. This must be in the protobuf `Timestamp` format. |
| 92 | + timestamp, _ := ptypes.TimestampProto(time.Now()) |
| 93 | + |
| 94 | + // The messageID is derived from the message data. In this case it's the hash of the message, |
| 95 | + // subject, and timestamp which is then multihash encoded. |
| 96 | + idBytes := sha256.Sum256([]byte(chatMessage + subject + ptypes.TimestampString(timestamp))) |
| 97 | + encoded, _ := mh.Encode(idBytes[:], mh.SHA2_256) |
| 98 | + msgId, _ := mh.Cast(encoded) |
| 99 | + |
| 100 | + chatPb := &pb.Chat{ |
| 101 | + MessageId: msgId.B58String(), |
| 102 | + Subject: subject, |
| 103 | + Message: chatMessage, |
| 104 | + Timestamp: timestamp, |
| 105 | + Flag: pb.Chat_MESSAGE, |
| 106 | + } |
| 107 | + |
| 108 | + |
| 109 | + // Now we wrap it in a `pb.Message` object |
| 110 | + payload, _ := ptypes.MarshalAny(chatMessage) |
| 111 | + m := pb.Message{ |
| 112 | + MessageType: pb.Message_CHAT, |
| 113 | + Payload: payload, |
| 114 | + } |
| 115 | + |
| 116 | + |
| 117 | + // Now we wrap it in the envelop object |
| 118 | + pubKeyBytes, _ := n.IpfsNode.PrivateKey.GetPublic().Bytes() |
| 119 | + |
| 120 | + // Use the protobuf serialize function to convert the object to a serialized byte array |
| 121 | + serializedMessage, _ := proto.Marshal(m) |
| 122 | + |
| 123 | + // Sign the serializedMessage with the private key |
| 124 | + signature, _ := n.IpfsNode.PrivateKey.Sign(serializedMessage) |
| 125 | + |
| 126 | + // Create the envelope |
| 127 | + env := pb.Envelope{ |
| 128 | + Message: m, |
| 129 | + Pubkey: pubKeyBytes, |
| 130 | + Signature: signature, |
| 131 | + } |
| 132 | +``` |
| 133 | + |
| 134 | + 2. Encrypt the serialized envelope using the recipient's public key. For this you will need to use an `nacl` library. NOTE for |
| 135 | + this you will need the recipient's public key. We will have to create a server endpoint to get the pubkey. Technically I think the |
| 136 | + gateway already has one but we may need to improve it for this purpose. The public key is also found inside a listing so if you're |
| 137 | + looking at a listing you should already have it. |
| 138 | + |
| 139 | + ```go |
| 140 | + // Serialize the envelope |
| 141 | + serializedEnvelope, _ := proto.Marshal(&env) |
| 142 | + |
| 143 | + // Get the public key |
| 144 | + recipientPublicKey := getPublicKeyFromGatewayOrListing() |
| 145 | + |
| 146 | + // Generate an ephemeral key pair |
| 147 | + ephemPub, ephemPriv, _ := box.GenerateKey(rand.Reader) |
| 148 | + |
| 149 | + // Convert recipient's key into curve25519 |
| 150 | + pk, _ := recipientPublicKey.ToCurve25519() |
| 151 | + |
| 152 | + // Encrypt with nacl |
| 153 | + |
| 154 | + // Nonce must be a random 24 bytes |
| 155 | + var nonce [24]byte |
| 156 | + n := make([]byte, 24) |
| 157 | + rand.Read(n) |
| 158 | + for i := 0; i < 24; i++ { |
| 159 | + nonce[i] = n[i] |
| 160 | + } |
| 161 | + |
| 162 | + // Encrypt |
| 163 | + ciphertext := box.Seal(ciphertext, serializedEnvelope, &nonce, pk, ephemPriv) |
| 164 | + |
| 165 | + // Prepend the ephemeral public key to the ciphertext |
| 166 | + ciphertext = append(ephemPub[:], ciphertext...) |
| 167 | + |
| 168 | + // Prepend nonce to the ephemPubkey+ciphertext |
| 169 | + ciphertext = append(nonce[:], ciphertext...) |
| 170 | + |
| 171 | + // Base64 encode |
| 172 | + encodedCipherText := base64.StdEncoding.EncodeToString(ciphertext) |
| 173 | + ``` |
| 174 | + |
| 175 | + 3. Create a `EncryptedMessage` JSON object to send to the webrelay server |
| 176 | + ```go |
| 177 | + type EncryptedMessage struct { |
| 178 | + Message string `json:"encryptedMessage"` |
| 179 | + Recipient string `json:"recipient"` |
| 180 | + } |
| 181 | +``` |
| 182 | +`Message` is the base64 `encodedCipherText` from the example above |
| 183 | +`Recipient` is the PeerID of the recipient |
| 184 | + |
| 185 | + |
| 186 | + |
| 187 | + |
0 commit comments