Skip to content

Commit

Permalink
feat: simplify contact bundle api (#182)
Browse files Browse the repository at this point in the history
* feat: simplify contact bundle api

* fix: encode bundle correctly
  • Loading branch information
mkobetic authored Oct 5, 2022
1 parent a7f9cb3 commit 8603d76
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 88 deletions.
3 changes: 1 addition & 2 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,8 +625,7 @@ async function getUserContactFromNetwork(

for await (const env of stream) {
if (!env.message) continue
const bundle = decodeContactBundle(b64Decode(env.message.toString()))
const keyBundle = bundle.keyBundle
const keyBundle = decodeContactBundle(b64Decode(env.message.toString()))

const address = keyBundle?.walletSignatureAddress()
// TODO: Ignore SignedPublicKeyBundles for now.
Expand Down
86 changes: 26 additions & 60 deletions src/ContactBundle.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,39 @@
import { contact, publicKey } from '@xmtp/proto'
import { PublicKeyBundle, SignedPublicKeyBundle } from './crypto'

// ContactBundle packages all the information which a client uses to advertise on the network.
// V1 uses the legacy PublicKeyBundle.
export class ContactBundleV1 implements contact.ContactBundleV1 {
keyBundle: PublicKeyBundle

constructor(bundle: contact.ContactBundleV1) {
if (!bundle.keyBundle) {
throw new Error('missing keyBundle')
}
this.keyBundle = new PublicKeyBundle(bundle.keyBundle)
}

toBytes(): Uint8Array {
return contact.ContactBundle.encode({
v1: {
keyBundle: this.keyBundle,
},
v2: undefined,
}).finish()
}
}

// ContactBundle packages all the information which a client uses to advertise on the network.
// V2 uses the SignedPublicKeyBundle.
export class ContactBundleV2 implements contact.ContactBundleV2 {
keyBundle: SignedPublicKeyBundle

constructor(bundle: contact.ContactBundleV2) {
if (!bundle.keyBundle) {
throw new Error('missing keyBundle')
}
this.keyBundle = new SignedPublicKeyBundle(bundle.keyBundle)
}

toBytes(): Uint8Array {
return contact.ContactBundle.encode({
v1: undefined,
v2: {
keyBundle: this.keyBundle,
},
}).finish()
}

static fromLegacyBundle(bundle: ContactBundleV1): ContactBundleV2 {
return new ContactBundleV2({
keyBundle: SignedPublicKeyBundle.fromLegacyBundle(bundle.keyBundle),
})
}
}

// This is the union of all supported bundle versions.
export type ContactBundle = ContactBundleV1 | ContactBundleV2

// This is the primary function for reading contact bundles off the wire.
export function decodeContactBundle(bytes: Uint8Array): ContactBundle {
// Decodes contact bundles from the contact topic.
export function decodeContactBundle(
bytes: Uint8Array
): PublicKeyBundle | SignedPublicKeyBundle {
let cb: contact.ContactBundle
try {
cb = contact.ContactBundle.decode(bytes)
} catch (e) {
const pb = publicKey.PublicKeyBundle.decode(bytes)
cb = { v1: { keyBundle: new PublicKeyBundle(pb) }, v2: undefined }
}
if (cb.v1) {
return new ContactBundleV1(cb.v1)
if (cb.v1?.keyBundle) {
return new PublicKeyBundle(cb.v1.keyBundle)
}
if (cb.v2) {
return new ContactBundleV2(cb.v2)
if (cb.v2?.keyBundle) {
return new SignedPublicKeyBundle(cb.v2.keyBundle)
}
throw new Error('unknown or invalid contact bundle')
}

// Encodes public key bundle for the contact topic.
export function encodeContactBundle(
bundle: PublicKeyBundle | SignedPublicKeyBundle
): Uint8Array {
if (bundle instanceof PublicKeyBundle) {
return contact.ContactBundle.encode({
v1: { keyBundle: bundle },
v2: undefined,
}).finish()
} else {
return contact.ContactBundle.encode({
v1: undefined,
v2: { keyBundle: bundle },
}).finish()
}
throw new Error('unknown contact bundle version')
}
34 changes: 8 additions & 26 deletions test/ContactBundle.test.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,29 @@
import * as assert from 'assert'
import {
ContactBundleV1,
ContactBundleV2,
decodeContactBundle,
} from '../src/ContactBundle'
import { decodeContactBundle, encodeContactBundle } from '../src/ContactBundle'
import {
PrivateKeyBundleV1,
PrivateKeyBundleV2,
PublicKeyBundle,
SignedPublicKeyBundle,
PrivateKey,
} from '../src'
import { newWallet } from './helpers'

describe('ContactBundles', function () {
it('roundtrip', async function () {
const priv = await PrivateKeyBundleV1.generate()
const pub = priv.getPublicKeyBundle()
let bytes = pub.toBytes()
let bytes = encodeContactBundle(pub)
const cb = decodeContactBundle(bytes)
expect(cb.keyBundle).toBeInstanceOf(PublicKeyBundle)
assert.ok(pub.equals(cb.keyBundle as PublicKeyBundle))

const cb1 = new ContactBundleV1({ keyBundle: priv.getPublicKeyBundle() })
bytes = cb1.toBytes()
const cb2 = decodeContactBundle(bytes)
expect(cb2.keyBundle).toBeInstanceOf(PublicKeyBundle)
assert.ok(pub.equals(cb2.keyBundle as PublicKeyBundle))

const bytes2 = cb2.toBytes()
assert.deepEqual(bytes, bytes2)
expect(cb).toBeInstanceOf(PublicKeyBundle)
assert.ok(pub.equals(cb as PublicKeyBundle))
})
it('roundtrip v2', async function () {
const wallet = newWallet()
const priv = await PrivateKeyBundleV2.generate(wallet)
const pub = priv.getPublicKeyBundle()
const cb1 = new ContactBundleV2({ keyBundle: priv.getPublicKeyBundle() })
let bytes = cb1.toBytes()
const cb2 = decodeContactBundle(bytes)
expect(cb2.keyBundle).toBeInstanceOf(SignedPublicKeyBundle)
assert.ok(pub.equals(cb2.keyBundle as SignedPublicKeyBundle))

const bytes2 = cb2.toBytes()
assert.deepEqual(bytes, bytes2)
let bytes = encodeContactBundle(pub)
const cb = decodeContactBundle(bytes)
expect(cb).toBeInstanceOf(SignedPublicKeyBundle)
assert.ok(pub.equals(cb as SignedPublicKeyBundle))
})
})

0 comments on commit 8603d76

Please sign in to comment.