diff --git a/watcher/src/watchers/CosmwasmWatcher.ts b/watcher/src/watchers/CosmwasmWatcher.ts index 65d61391..ca2a8aa3 100644 --- a/watcher/src/watchers/CosmwasmWatcher.ts +++ b/watcher/src/watchers/CosmwasmWatcher.ts @@ -6,6 +6,7 @@ import { makeBlockKey, makeVaaKey } from '../databases/utils'; import { Watcher } from './Watcher'; import { SHA256 } from 'jscrypto/SHA256'; import { Base64 } from 'jscrypto/Base64'; +import { isBase64Encoded } from './utils'; export class CosmwasmWatcher extends Watcher { latestBlockTag: string; @@ -112,14 +113,27 @@ export class CosmwasmWatcher extends Watcher { // only care about _contract_address, message.sender and message.sequence const numAttrs = attrs.length; for (let k = 0; k < numAttrs; k++) { - const key = Buffer.from(attrs[k].key, 'base64').toString().toLowerCase(); + let base64Encoded: boolean = false; + let key: string; + if (isBase64Encoded(attrs[k].key)) { + key = Buffer.from(attrs[k].key, 'base64').toString().toLowerCase(); + base64Encoded = true; + } else { + key = attrs[k].key.toLowerCase(); + } this.logger.debug('Encoded Key = ' + attrs[k].key + ', decoded = ' + key); if (key === 'message.sender') { - emitter = Buffer.from(attrs[k].value, 'base64').toString(); + emitter = base64Encoded + ? Buffer.from(attrs[k].value, 'base64').toString() + : attrs[k].value; } else if (key === 'message.sequence') { - sequence = Buffer.from(attrs[k].value, 'base64').toString(); + sequence = base64Encoded + ? Buffer.from(attrs[k].value, 'base64').toString() + : attrs[k].value; } else if (key === '_contract_address' || key === 'contract_address') { - let addr = Buffer.from(attrs[k].value, 'base64').toString(); + let addr = base64Encoded + ? Buffer.from(attrs[k].value, 'base64').toString() + : attrs[k].value; if (addr === address) { coreContract = true; } diff --git a/watcher/src/watchers/__tests__/CosmwasmWatcher.test.ts b/watcher/src/watchers/__tests__/CosmwasmWatcher.test.ts index 7cfcfd64..48413486 100644 --- a/watcher/src/watchers/__tests__/CosmwasmWatcher.test.ts +++ b/watcher/src/watchers/__tests__/CosmwasmWatcher.test.ts @@ -5,6 +5,7 @@ import { InjectiveExplorerWatcher } from '../InjectiveExplorerWatcher'; import { SeiExplorerWatcher } from '../SeiExplorerWatcher'; import { WormchainWatcher } from '../WormchainWatcher'; import { INITIAL_DEPLOYMENT_BLOCK_BY_CHAIN } from '@wormhole-foundation/wormhole-monitor-common'; +import { isBase64Encoded } from '../utils'; jest.setTimeout(60000); @@ -213,3 +214,24 @@ test('getMessagesForBlocks(wormchain)', async () => { '4D861F1BE86325D227FA006CA2745BBC6748AF5B5E0811DE536D02792928472A:3104/aeb534c45c3049d380b9d9b966f9895f53abd4301bfaff407fa09dea8ae7a924/0', ]); }); + +test('isBase64Encoded', async () => { + const msg1: string = 'message.sender'; + const bmsg1: string = Buffer.from(msg1).toString('base64'); + const msg2: string = 'message.sequence'; + const bmsg2: string = Buffer.from(msg1).toString('base64'); + const msg3: string = '_contract.address'; + const bmsg3: string = Buffer.from(msg1).toString('base64'); + expect(isBase64Encoded(msg1)).toBe(false); + expect(isBase64Encoded(msg2)).toBe(false); + expect(isBase64Encoded(msg3)).toBe(false); + expect(isBase64Encoded(bmsg1)).toBe(true); + expect(isBase64Encoded(bmsg2)).toBe(true); + expect(isBase64Encoded(bmsg3)).toBe(true); + + // This test shows the risk involved with checking for base64 encoding. + // The following is, actually, clear text. But it passes the base64 encoding check. + // So, passing addresses into the check should be done with caution. + const addr: string = 'terra12mrnzvhx3rpej6843uge2yyfppfyd3u9c3uq223q8sl48huz9juqffcnhp'; + expect(isBase64Encoded(addr)).toBe(true); +}); diff --git a/watcher/src/watchers/utils.ts b/watcher/src/watchers/utils.ts index 099d7338..c4f0e5bc 100644 --- a/watcher/src/watchers/utils.ts +++ b/watcher/src/watchers/utils.ts @@ -61,3 +61,17 @@ export function makeFinalizedWatcher(chainName: ChainName): Watcher { throw new Error(`Attempted to create finalized watcher for unsupported chain ${chainName}`); } } + +// This function uses a regex string to check if the input could +// possibly be base64 encoded. +// +// WARNING: There are clear text strings that are NOT base64 encoded +// that will pass this check. +export function isBase64Encoded(input: string): boolean { + const b64Regex = new RegExp('^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$'); + const match = b64Regex.exec(input); + if (match) { + return true; + } + return false; +}