Skip to content

Commit

Permalink
Merge pull request #687 from xmtp/rygine/frames-validator
Browse files Browse the repository at this point in the history
Add `frames-validator` package
  • Loading branch information
rygine authored Oct 18, 2024
2 parents 9c1ae0a + f6c756b commit c34654f
Show file tree
Hide file tree
Showing 24 changed files with 756 additions and 25 deletions.
2 changes: 1 addition & 1 deletion content-types/content-type-primitives/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1",
"@types/node": "^20.16.11",
"@types/node": "^20.16.12",
"happy-dom": "^15.7.4",
"rimraf": "^6.0.1",
"rollup": "^4.24.0",
Expand Down
2 changes: 1 addition & 1 deletion content-types/content-type-reaction/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1",
"@types/node": "^20.16.11",
"@types/node": "^20.16.12",
"@xmtp/xmtp-js": "^11.6.3",
"buffer": "^6.0.3",
"ethers": "^6.11.1",
Expand Down
2 changes: 1 addition & 1 deletion content-types/content-type-read-receipt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1",
"@types/node": "^20.16.11",
"@types/node": "^20.16.12",
"@xmtp/xmtp-js": "^11.6.3",
"buffer": "^6.0.3",
"ethers": "^6.11.1",
Expand Down
2 changes: 1 addition & 1 deletion content-types/content-type-remote-attachment/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1",
"@types/node": "^20.16.11",
"@types/node": "^20.16.12",
"@xmtp/rollup-plugin-resolve-extensions": "^1.0.1",
"@xmtp/xmtp-js": "^11.6.3",
"buffer": "^6.0.3",
Expand Down
2 changes: 1 addition & 1 deletion content-types/content-type-reply/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1",
"@types/node": "^20.16.11",
"@types/node": "^20.16.12",
"@xmtp/content-type-remote-attachment": "workspace:*",
"@xmtp/xmtp-js": "^11.6.3",
"buffer": "^6.0.3",
Expand Down
2 changes: 1 addition & 1 deletion content-types/content-type-text/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1",
"@types/node": "^20.16.11",
"@types/node": "^20.16.12",
"@xmtp/xmtp-js": "^11.6.3",
"buffer": "^6.0.3",
"ethers": "^6.11.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1",
"@types/node": "^20.16.11",
"@types/node": "^20.16.12",
"@xmtp/xmtp-js": "^11.6.3",
"buffer": "^6.0.3",
"ethers": "^6.11.1",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@eslint/js": "^9.12.0",
"@ianvs/prettier-plugin-sort-imports": "^4.3.1",
"@types/eslint__js": "^8.42.3",
"@types/node": "^20.16.11",
"@types/node": "^20.16.12",
"eslint": "^9.12.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
Expand Down
73 changes: 73 additions & 0 deletions packages/frames-validator/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# @xmtp/frames-validator

## 0.6.2

### Patch Changes

- [#260](https://github.com/xmtp/xmtp-node-js-tools/pull/260) [`57bf55d`](https://github.com/xmtp/xmtp-node-js-tools/commit/57bf55d89bce8a52a1dfaf8b7fc649054aaa6fd5) Thanks [@neekolas](https://github.com/neekolas)! - Update dependencies

## 0.6.1

### Patch Changes

- [#232](https://github.com/xmtp/xmtp-node-js-tools/pull/232) [`15c5032`](https://github.com/xmtp/xmtp-node-js-tools/commit/15c50320b06a80e50d666fa36da201cc754d3d68) Thanks [@daria-github](https://github.com/daria-github)! - Bumped version of proto package.

## 0.6.0

### Minor Changes

- [#191](https://github.com/xmtp/xmtp-node-js-tools/pull/191) [`da721b9`](https://github.com/xmtp/xmtp-node-js-tools/commit/da721b981ba7b225345c7086952f343592796992) Thanks [@alexrisch](https://github.com/alexrisch)! - Added State handling

## 0.5.2

### Patch Changes

- [#169](https://github.com/xmtp/xmtp-node-js-tools/pull/169) [`ea52fb6`](https://github.com/xmtp/xmtp-node-js-tools/commit/ea52fb63562d611307c7005c8fba472bc286e7e7) Thanks [@neekolas](https://github.com/neekolas)! - Add state field

## 0.5.1

### Patch Changes

- [#161](https://github.com/xmtp/xmtp-node-js-tools/pull/161) [`0c3cbb8`](https://github.com/xmtp/xmtp-node-js-tools/commit/0c3cbb8fb3aa392ec72787e1512d177c7c49a011) Thanks [@neekolas](https://github.com/neekolas)! - Upgrade xmtp proto

## 0.5.0

### Minor Changes

- [#154](https://github.com/xmtp/xmtp-node-js-tools/pull/154) [`7530777`](https://github.com/xmtp/xmtp-node-js-tools/commit/7530777be8e863a87bc5cad6136db8202eb9bea7) Thanks [@neekolas](https://github.com/neekolas)! - Switch out encryption library for better commonjs support

## 0.4.0

### Minor Changes

- [#147](https://github.com/xmtp/xmtp-node-js-tools/pull/147) [`9ad92d8`](https://github.com/xmtp/xmtp-node-js-tools/commit/9ad92d801ce58a0610078016640a4e611b73e662) Thanks [@neekolas](https://github.com/neekolas)! - Adds support for an Open Frames validator

## 0.3.1

### Patch Changes

- [#145](https://github.com/xmtp/xmtp-node-js-tools/pull/145) [`5fb6232`](https://github.com/xmtp/xmtp-node-js-tools/commit/5fb623267505a3e964281e3527c76c6a1c752c14) Thanks [@neekolas](https://github.com/neekolas)! - Export all the types

## 0.3.0

### Minor Changes

- [#143](https://github.com/xmtp/xmtp-node-js-tools/pull/143) [`050c529`](https://github.com/xmtp/xmtp-node-js-tools/commit/050c52986414773dba01796ed86d1ea5ec365be8) Thanks [@neekolas](https://github.com/neekolas)! - Configure to export for both Node.js and ESM

## 0.2.0

### Minor Changes

- [#140](https://github.com/xmtp/xmtp-node-js-tools/pull/140) [`4010423`](https://github.com/xmtp/xmtp-node-js-tools/commit/40104235bb8f5ab62cd98e35214d62e268816c93) Thanks [@neekolas](https://github.com/neekolas)! - Update to latest version of our protos

## 0.1.1

### Patch Changes

- [#133](https://github.com/xmtp/xmtp-node-js-tools/pull/133) [`ee73b40`](https://github.com/xmtp/xmtp-node-js-tools/commit/ee73b40f72f22d62bd3d341ce691cc30e18c3ec3) Thanks [@neekolas](https://github.com/neekolas)! - Fix import error

## 0.1.0

### Minor Changes

- [#131](https://github.com/xmtp/xmtp-node-js-tools/pull/131) [`03a6083`](https://github.com/xmtp/xmtp-node-js-tools/commit/03a608352ec9814edda449ad75610a78ad6c4110) Thanks [@neekolas](https://github.com/neekolas)! - Initialize frames-validator package
20 changes: 20 additions & 0 deletions packages/frames-validator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Frames Validator

A set of tools for validating POST payloads from XMTP Frames

## Usage

```ts
import { validateFramesPost } from "@xmtp/frames-validator"

export function handler(requestBody: any) {
// This is an XMTP payload
if (requestBody.untrustedData?.clientType === "xmtp") {
const { verifiedWalletAddress } = await validateFramesPost(requestBody)
return doSomethingWithWalletAddress(verifiedWalletAddress)
} else {
// This is a Farcaster POST payload
return doSomethingWithFarcasterPayload(requestBody)
}
}
```
57 changes: 57 additions & 0 deletions packages/frames-validator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "@xmtp/frames-validator",
"version": "0.6.2",
"description": "A validator for XMTP frames requests",
"homepage": "https://github.com/xmtp/xmtp-node-js-tools#readme",
"bugs": {
"url": "https://github.com/xmtp/xmtp-node-js-tools/issues"
},
"license": "MIT",
"author": "XMTP Labs <[email protected]>",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "yarn clean:dist && yarn rollup -c",
"clean": "rm -rf .turbo && rm -rf node_modules && yarn clean:dist",
"clean:dist": "rm -rf dist",
"test": "vitest run",
"typecheck": "tsc"
},
"dependencies": {
"@noble/curves": "^1.3.0",
"@noble/hashes": "^1.4.0",
"@xmtp/proto": "3.61.1",
"viem": "^2.16.5"
},
"devDependencies": {
"@open-frames/types": "^0.1.1",
"@rollup/plugin-typescript": "^12.1.1",
"@xmtp/frames-client": "^0.5.4",
"@xmtp/xmtp-js": "^12.1.0",
"ethers": "^6.10.0",
"rollup": "^4.24.0",
"rollup-plugin-dts": "^6.1.1",
"typescript": "^5.6.3",
"vitest": "^2.1.3"
},
"packageManager": "[email protected]",
"engines": {
"node": ">=20"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}
49 changes: 49 additions & 0 deletions packages/frames-validator/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import typescript from "@rollup/plugin-typescript";
import { defineConfig } from "rollup";
import { dts } from "rollup-plugin-dts";

const external = [
"@noble/curves/abstract/utils",
"@noble/curves/secp256k1",
"@noble/hashes/sha256",
"@xmtp/proto",
"viem/utils",
];

const plugins = [
typescript({
declaration: false,
declarationMap: false,
}),
];

export default defineConfig([
{
input: "src/index.ts",
output: {
file: "dist/index.js",
format: "es",
sourcemap: true,
},
plugins,
external,
},
{
input: "src/index.ts",
output: {
file: "dist/index.cjs",
format: "cjs",
sourcemap: true,
},
plugins,
external,
},
{
input: "src/index.ts",
output: {
file: "dist/index.d.ts",
format: "es",
},
plugins: [dts()],
},
]);
109 changes: 109 additions & 0 deletions packages/frames-validator/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { FramesClient } from "@xmtp/frames-client";
import { fetcher, frames } from "@xmtp/proto";
import { Client, PrivateKeyBundleV2 } from "@xmtp/xmtp-js";
import { Wallet } from "ethers";
import { beforeEach, describe, expect, it } from "vitest";
import { deserializeProtoMessage, validateFramesPost } from ".";

const { b64Decode, b64Encode } = fetcher;

function scrambleBytes(bytes: Uint8Array) {
const scrambled = new Uint8Array(bytes.length);
for (let i = 0; i < bytes.length; i++) {
scrambled[i] = bytes[bytes.length - i - 1];
}
return scrambled;
}

describe("validations", () => {
let client: Client;
let framesClient: FramesClient;

const FRAME_URL = "https://frame.xyz";
const CONVERSATION_TOPIC = "/xmtp/0/1234";
const PARTICIPANT_ACCOUNT_ADDRESSES = ["0x1234", "0x5678"];
const BUTTON_INDEX = 2;

beforeEach(async () => {
const wallet = Wallet.createRandom();
client = await Client.create(wallet);
framesClient = new FramesClient(client);
});

it("succeeds in the happy path", async () => {
const postData = await framesClient.signFrameAction({
buttonIndex: BUTTON_INDEX,
frameUrl: FRAME_URL,
conversationTopic: CONVERSATION_TOPIC,
participantAccountAddresses: PARTICIPANT_ACCOUNT_ADDRESSES,
});
const validated = validateFramesPost(postData);
expect(validated.verifiedWalletAddress).toEqual(client.address);
});

it("fails if the signature verification fails", async () => {
const postData = await framesClient.signFrameAction({
buttonIndex: BUTTON_INDEX,
frameUrl: FRAME_URL,
conversationTopic: CONVERSATION_TOPIC,
participantAccountAddresses: PARTICIPANT_ACCOUNT_ADDRESSES,
});
// Monkey around with the signature
const deserialized = deserializeProtoMessage(
b64Decode(postData.trustedData.messageBytes),
);

if (!deserialized.signature.ecdsaCompact?.bytes) {
throw new Error("Signature bytes are empty");
}

deserialized.signature.ecdsaCompact.bytes = scrambleBytes(
deserialized.signature.ecdsaCompact.bytes,
);
const reserialized = frames.FrameAction.encode({
signature: deserialized.signature,
actionBody: deserialized.actionBodyBytes,
signedPublicKeyBundle: deserialized.signedPublicKeyBundle,
}).finish();

postData.trustedData.messageBytes = b64Encode(
reserialized,
0,
reserialized.length,
);

expect(() => validateFramesPost(postData)).toThrow();
});

it("fails if the wallet address doesn't match", async () => {
const postData = await framesClient.signFrameAction({
buttonIndex: BUTTON_INDEX,
frameUrl: FRAME_URL,
conversationTopic: CONVERSATION_TOPIC,
participantAccountAddresses: PARTICIPANT_ACCOUNT_ADDRESSES,
});
// Monkey around with the signature
const deserialized = deserializeProtoMessage(
b64Decode(postData.trustedData.messageBytes),
);

const throwAwayWallet = Wallet.createRandom();
const wrongPublicKeyBundle = (
await PrivateKeyBundleV2.generate(throwAwayWallet)
).getPublicKeyBundle();

const reserialized = frames.FrameAction.encode({
signature: deserialized.signature,
actionBody: deserialized.actionBodyBytes,
signedPublicKeyBundle: wrongPublicKeyBundle,
}).finish();

postData.trustedData.messageBytes = b64Encode(
reserialized,
0,
reserialized.length,
);

expect(() => validateFramesPost(postData)).toThrow();
});
});
2 changes: 2 additions & 0 deletions packages/frames-validator/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./openFrames.js";
export * from "./validation.js";
Loading

0 comments on commit c34654f

Please sign in to comment.