Skip to content

Commit

Permalink
Merge pull request #233 from xmtp/ar/address-lowercase
Browse files Browse the repository at this point in the history
feat: Added support for handling lowercase addresses
  • Loading branch information
alexrisch authored Feb 2, 2024
2 parents 670f54d + ed8f7cc commit 9b08182
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 10 deletions.
56 changes: 56 additions & 0 deletions example/src/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -869,3 +869,59 @@ test('returns keyMaterial for conversations', async () => {

return true
})

test('correctly handles lowercase addresses', async () => {
const bob = await Client.createRandom({ env: 'local' })
await delayToPropogate()
const alice = await Client.createRandom({ env: 'local' })
await delayToPropogate()
if (bob.address === alice.address) {
throw new Error('bob and alice should be different')
}

const bobConversation = await bob.conversations.newConversation(
alice.address.toLocaleLowerCase()
)
await delayToPropogate()
if (!bobConversation) {
throw new Error('bobConversation should exist')
}
const aliceConversation = (await alice.conversations.list())[0]
if (!aliceConversation) {
throw new Error('aliceConversation should exist')
}

await bob.contacts.deny([aliceConversation.peerAddress.toLocaleLowerCase()])
await delayToPropogate()
const deniedState = await bob.contacts.isDenied(aliceConversation.peerAddress)
const allowedState = await bob.contacts.isAllowed(
aliceConversation.peerAddress
)
if (!deniedState) {
throw new Error(`contacts denied by bo should be denied not ${deniedState}`)
}

if (allowedState) {
throw new Error(
`contacts denied by bo should be denied not ${allowedState}`
)
}
const deniedLowercaseState = await bob.contacts.isDenied(
aliceConversation.peerAddress.toLocaleLowerCase()
)
const allowedLowercaseState = await bob.contacts.isAllowed(
aliceConversation.peerAddress.toLocaleLowerCase()
)
if (!deniedLowercaseState) {
throw new Error(
`contacts denied by bo should be denied not ${deniedLowercaseState}`
)
}

if (allowedLowercaseState) {
throw new Error(
`contacts denied by bo should be denied not ${allowedLowercaseState}`
)
}
return true
})
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@
"dependencies": {
"@ethersproject/bytes": "^5.7.0",
"@msgpack/msgpack": "^3.0.0-beta2",
"@noble/hashes": "^1.3.3",
"@xmtp/proto": "^3.25.0",
"buffer": "^6.0.3"
"buffer": "^6.0.3",
"text-encoding": "^0.7.0"
},
"devDependencies": {
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
Expand Down
11 changes: 8 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { Conversation } from './lib/Conversation'
import { DecodedMessage } from './lib/DecodedMessage'
import type { Query } from './lib/Query'
import { getAddress } from './utils/address'

export { ReactionCodec } from './lib/NativeCodecs/ReactionCodec'
export { ReplyCodec } from './lib/NativeCodecs/ReplyCodec'
Expand Down Expand Up @@ -105,15 +106,19 @@ export async function canMessage(
clientAddress: string,
peerAddress: string
): Promise<boolean> {
return await XMTPModule.canMessage(clientAddress, peerAddress)
return await XMTPModule.canMessage(clientAddress, getAddress(peerAddress))
}

export async function staticCanMessage(
peerAddress: string,
environment: 'local' | 'dev' | 'production',
appVersion?: string | undefined
): Promise<boolean> {
return await XMTPModule.staticCanMessage(peerAddress, environment, appVersion)
return await XMTPModule.staticCanMessage(
getAddress(peerAddress),
environment,
appVersion
)
}

export async function encryptAttachment(
Expand Down Expand Up @@ -212,7 +217,7 @@ export async function createConversation<ContentTypes>(
JSON.parse(
await XMTPModule.createConversation(
client.address,
peerAddress,
getAddress(peerAddress),
JSON.stringify(context || {})
)
)
Expand Down
17 changes: 13 additions & 4 deletions src/lib/Contacts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Client } from './Client'
import { ConsentListEntry } from './ConsentListEntry'
import * as XMTPModule from '../index'
import { getAddress } from '../utils/address'

export default class Contacts {
client: Client<any>
Expand All @@ -10,19 +11,27 @@ export default class Contacts {
}

async isAllowed(address: string): Promise<boolean> {
return await XMTPModule.isAllowed(this.client.address, address)
return await XMTPModule.isAllowed(this.client.address, getAddress(address))
}

async isDenied(address: string): Promise<boolean> {
return await XMTPModule.isDenied(this.client.address, address)
return await XMTPModule.isDenied(this.client.address, getAddress(address))
}

async deny(addresses: string[]): Promise<void> {
return await XMTPModule.denyContacts(this.client.address, addresses)
const checkSummedAddresses = addresses.map((address) => getAddress(address))
return await XMTPModule.denyContacts(
this.client.address,
checkSummedAddresses
)
}

async allow(addresses: string[]): Promise<void> {
return await XMTPModule.allowContacts(this.client.address, addresses)
const checkSummedAddresses = addresses.map((address) => getAddress(address))
return await XMTPModule.allowContacts(
this.client.address,
checkSummedAddresses
)
}

async refreshConsentList(): Promise<ConsentListEntry[]> {
Expand Down
4 changes: 3 additions & 1 deletion src/lib/Conversations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Conversation } from './Conversation'
import { DecodedMessage } from './DecodedMessage'
import { ConversationContext } from '../XMTP.types'
import * as XMTPModule from '../index'
import { getAddress } from '../utils/address'

export default class Conversations<ContentTypes> {
client: Client<ContentTypes>
Expand Down Expand Up @@ -50,9 +51,10 @@ export default class Conversations<ContentTypes> {
peerAddress: string,
context?: ConversationContext
): Promise<Conversation<ContentTypes>> {
const checksumAddress = getAddress(peerAddress)
return await XMTPModule.createConversation(
this.client,
peerAddress,
checksumAddress,
context
)
}
Expand Down
50 changes: 50 additions & 0 deletions src/utils/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { keccak_256 } from '@noble/hashes/sha3'
import { TextEncoder } from 'text-encoding'

const addressRegex = /^0x[a-fA-F0-9]{40}$/
const encoder = new TextEncoder()

export function stringToBytes(value: string): Uint8Array {
const bytes = encoder.encode(value)
return bytes
}

export function keccak256(value: Uint8Array): Uint8Array {
const bytes = keccak_256(value)
return bytes
}

export function isAddress(address: string): boolean {
return addressRegex.test(address)
}

export function checksumAddress(address_: string, chainId?: number): string {
const hexAddress = chainId
? `${chainId}${address_.toLowerCase()}`
: address_.substring(2).toLowerCase()
const hash = keccak256(stringToBytes(hexAddress))

const address = (
chainId ? hexAddress.substring(`${chainId}0x`.length) : hexAddress
).split('')
for (let i = 0; i < 40; i += 2) {
if (hash[i >> 1] >> 4 >= 8 && address[i]) {
address[i] = address[i].toUpperCase()
}
if ((hash[i >> 1] & 0x0f) >= 8 && address[i + 1]) {
address[i + 1] = address[i + 1].toUpperCase()
}
}

return `0x${address.join('')}`
}

const addressCache = new Map<string, string>()

export function getAddress(address: string, chainId?: number): string {
if (addressCache.has(address)) return addressCache.get(address) as string
if (!isAddress(address)) throw new Error('Invalid address' + address)
const checksumedAddress = checksumAddress(address, chainId)
addressCache.set(address, checksumedAddress)
return checksumedAddress
}
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1536,7 +1536,7 @@
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39"
integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==

"@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2":
"@noble/hashes@^1.3.3", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699"
integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==
Expand Down Expand Up @@ -7958,6 +7958,11 @@ test-exclude@^6.0.0:
glob "^7.1.4"
minimatch "^3.0.4"

text-encoding@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.7.0.tgz#f895e836e45990624086601798ea98e8f36ee643"
integrity sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==

text-extensions@^2.0.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-2.4.0.tgz#a1cfcc50cf34da41bfd047cc744f804d1680ea34"
Expand Down

0 comments on commit 9b08182

Please sign in to comment.