Skip to content

Commit

Permalink
Merge pull request #67 from xmtp/message-id
Browse files Browse the repository at this point in the history
Add Message.id
  • Loading branch information
snormore authored Feb 15, 2022
2 parents fbfff3e + 075f145 commit 2ada505
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 6 deletions.
29 changes: 23 additions & 6 deletions src/Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
encrypt,
} from './crypto'
import { NoMatchingPreKeyError } from './crypto/errors'
import { bytesToHex } from './crypto/utils'
import { sha256 } from './crypto/encryption'

// Message is basic unit of communication on the network.
// Message header carries the sender and recipient keys used to protect message.
Expand All @@ -17,20 +19,35 @@ export default class Message implements proto.Message {
ciphertext: Ciphertext | undefined
decrypted: string | undefined
error?: Error
/**
* Identifier that is deterministically derived from the bytes of the message
* header and ciphertext, where all those bytes are authenticated. This can
* be used in determining uniqueness of messages.
*/
id: string
private bytes: Uint8Array

constructor(obj: proto.Message) {
constructor(id: string, bytes: Uint8Array, obj: proto.Message) {
this.id = id
this.bytes = bytes
this.header = obj.header
if (obj.ciphertext) {
this.ciphertext = new Ciphertext(obj.ciphertext)
}
}

toBytes(): Uint8Array {
return proto.Message.encode(this).finish()
return this.bytes
}

static fromBytes(bytes: Uint8Array): Message {
return new Message(proto.Message.decode(bytes))
static async create(obj: proto.Message): Promise<Message> {
const bytes = proto.Message.encode(obj).finish()
const id = bytesToHex(await sha256(bytes))
return new Message(id, bytes, obj)
}

static async fromBytes(bytes: Uint8Array): Promise<Message> {
return Message.create(proto.Message.decode(bytes))
}

get text(): string | undefined {
Expand Down Expand Up @@ -84,7 +101,7 @@ export default class Message implements proto.Message {
const headerBytes = proto.Message_Header.encode(header).finish()
const ciphertext = await encrypt(bytes, secret, headerBytes)

const msg = new Message({
const msg = await Message.create({
header,
ciphertext,
})
Expand Down Expand Up @@ -138,7 +155,7 @@ export default class Message implements proto.Message {
throw new Error('missing message ciphertext')
}
const ciphertext = new Ciphertext(message.ciphertext)
const msg = new Message(message)
const msg = await Message.create(message)
let secret: Uint8Array
try {
if (viewer.identityKey.matches(sender.identityKey)) {
Expand Down
14 changes: 14 additions & 0 deletions test/Message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import assert from 'assert'
import { newWallet } from './helpers'
import { Message, PrivateKeyBundle } from '../src'
import { NoMatchingPreKeyError } from '../src/crypto/errors'
import { bytesToHex } from '../src/crypto/utils'
import { sha256 } from '../src/crypto/encryption'

describe('Message', function () {
it('fully encodes/decodes messages', async function () {
Expand Down Expand Up @@ -66,4 +68,16 @@ describe('Message', function () {
msg.recipientAddress
}).toThrow('key is not signed')
})

it('id returns bytes as hex string of sha256 hash', async () => {
const alice = await PrivateKeyBundle.generate()
const msg = await Message.encode(
alice,
alice.getPublicKeyBundle(),
'hi',
new Date()
)
assert.equal(msg.id.length, 64)
assert.equal(msg.id, bytesToHex(await sha256(msg.toBytes())))
})
})

0 comments on commit 2ada505

Please sign in to comment.