mirror of
https://github.com/tlsnotary/tlsn-extension.git
synced 2026-01-09 21:18:02 -05:00
chore: moved all example plugins to the boilerplate repository (#90)
https://github.com/tlsnotary/tlsn-plugin-boilerplate
This commit is contained in:
@@ -1,48 +1 @@
|
||||
# Plugin Development for the TLSNotary Browser Extension
|
||||
|
||||
This folder is dedicated to the development of plugins for the TLSNotary browser extension, utilizing the Extism framework. Currently, the folder includes a TypeScript-based plugin example, `twitter_profile`, with plans to add more plugins showcasing different programming languages and functionalities.
|
||||
|
||||
## Installation of Extism-js
|
||||
|
||||
1. **Download and Install Extism-js**: Begin by setting up `extism-js`, which enables you to compile and manage your plugins. Run these commands to download and install it:
|
||||
|
||||
```sh
|
||||
curl -O https://raw.githubusercontent.com/extism/js-pdk/main/install.sh
|
||||
sh install.sh
|
||||
```
|
||||
|
||||
This script installs the Extism JavaScript Plugin Development Kit from its GitHub repository, preparing your environment for plugin compilation.
|
||||
|
||||
## Building the Twitter Profile Plugin
|
||||
|
||||
Navigate to the `twitter_profile` directory within this folder and run the following command to build the plugin:
|
||||
|
||||
```sh
|
||||
extism-js index.js -i index.d.ts -o index.wasm
|
||||
```
|
||||
This command compiles the TypeScript code in index.js into a WebAssembly module, ready for integration with the TLSNotary extension.
|
||||
|
||||
### Running the Twitter Plugin Example:
|
||||
|
||||
1. Build the `twitter_profile` plugin as explained above.
|
||||
2. Build and install the `tlsn-extension` as documented in the [main README.md](../README.md).
|
||||
3. [Run a local notary server](https://github.com/tlsnotary/tlsn/blob/main/notary-server/README.md), ensuring `TLS` is disabled in the [config file](https://github.com/tlsnotary/tlsn/blob/main/notary-server/config/config.yaml#L18).
|
||||
4. Install the plugin: Click the **Add a Plugin (+)** button and select the `index.wasm` file you built in step 1. A **Twitter Profile** button should then appear below the default buttons.
|
||||
5. Click the **Twitter Profile** button. This action opens the Twitter webpage along with a TLSNotary sidebar.
|
||||
6. Follow the steps in the TLSNotary sidebar.
|
||||
7. Access the TLSNotary results by clicking the **History** button in the TLSNotary extension.
|
||||
|
||||
## Future Plugins
|
||||
|
||||
This directory will be expanded with more plugins designed to demonstrate the functionality of the TLSNotary extension. Plugins enable flexible use of the TLSNotary across a broad range of applications. The use of Extism facilitates plugin development in various languages, further enhancing flexibility.
|
||||
|
||||
## Create an icon
|
||||
|
||||
1. resize to 320x320 pixels:
|
||||
```sh
|
||||
convert icon.png -resize 320x320! icon_320.png
|
||||
```
|
||||
2. convert to base64
|
||||
```sh
|
||||
base64 -i icon_320.png -o icon_320.txt
|
||||
```
|
||||
You can find example plugins at https://github.com/tlsnotary/tlsn-plugin-boilerplate/tree/main/examples
|
||||
@@ -1,3 +0,0 @@
|
||||
# Disclaimer
|
||||
|
||||
Please note that these example plug-ins are experimental, provided "as-is", and are intended solely for educational and illustrative purposes. Use them at your own risk. The authors of and contributors to this repository are not responsible for your misuse of these example plug-ins, including violations of various terms of service, legal infractions, or other unauthorized activities. Users are advised to review and comply with the terms of service of any platforms they interact with. These examples may not be actively maintained, and there is no ongoing commitment to update or support them.
|
||||
4
plugins/hello/hello.d.ts
vendored
4
plugins/hello/hello.d.ts
vendored
@@ -1,4 +0,0 @@
|
||||
declare module 'main' {
|
||||
// Extism exports take no params and return an I32
|
||||
export function hello(): I32;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
function hello() {
|
||||
const name = Host.inputString();
|
||||
Host.outputString(`Hello, ${name}`);
|
||||
}
|
||||
|
||||
module.exports = { hello };
|
||||
Binary file not shown.
14
plugins/reddit/index.d.ts
vendored
14
plugins/reddit/index.d.ts
vendored
@@ -1,14 +0,0 @@
|
||||
declare module 'main' {
|
||||
// Extism exports take no params and return an I32
|
||||
export function start(): I32;
|
||||
export function two(): I32;
|
||||
export function three(): I32;
|
||||
export function config(): I32;
|
||||
}
|
||||
|
||||
declare module 'extism:host' {
|
||||
interface user {
|
||||
redirect(ptr: I64): void;
|
||||
notarize(ptr: I64): I64;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
14
plugins/twitter_dm/index.d.ts
vendored
14
plugins/twitter_dm/index.d.ts
vendored
@@ -1,14 +0,0 @@
|
||||
declare module 'main' {
|
||||
// Extism exports take no params and return an I32
|
||||
export function start(): I32;
|
||||
export function two(): I32;
|
||||
export function three(): I32;
|
||||
export function config(): I32;
|
||||
}
|
||||
|
||||
declare module 'extism:host' {
|
||||
interface user {
|
||||
redirect(ptr: I64): void;
|
||||
notarize(ptr: I64): I64;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
15
plugins/twitter_profile/index.d.ts
vendored
15
plugins/twitter_profile/index.d.ts
vendored
@@ -1,15 +0,0 @@
|
||||
declare module 'main' {
|
||||
// Extism exports take no params and return an I32
|
||||
export function start(): I32;
|
||||
export function two(): I32;
|
||||
export function parseTwitterResp(): I32;
|
||||
export function three(): I32;
|
||||
export function config(): I32;
|
||||
}
|
||||
|
||||
declare module 'extism:host' {
|
||||
interface user {
|
||||
redirect(ptr: I64): void;
|
||||
notarize(ptr: I64): I64;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
3
plugins/twitter_profile_ts/.gitignore
vendored
3
plugins/twitter_profile_ts/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
node_modules
|
||||
dist
|
||||
src/iconBase64.ts
|
||||
@@ -1,17 +0,0 @@
|
||||
# TLSNotary TypeScript plugin demo: Prove ownership of a Twitter handle
|
||||
|
||||
This is a demo demo plugin for the TLSNotary browser extension.
|
||||
|
||||
## Building
|
||||
|
||||
Build the plugin:
|
||||
```
|
||||
npm i
|
||||
npm run build
|
||||
```
|
||||
This will the wasm binary in `dist/index.wasm`.
|
||||
You can load the plugin in the browser extension by clicking **Add a plugin** in the main menu.
|
||||
|
||||
## More info
|
||||
|
||||
TLSNotary's plugin system is using [Extism](https://github.com/extism). For more documentation check https://github.com/extism/js-pdk.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB |
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"title": "Twitter Profile",
|
||||
"description": "Notarize ownership of a twitter profile",
|
||||
"steps": [
|
||||
{
|
||||
"title": "Visit Twitter website",
|
||||
"cta": "Go to x.com",
|
||||
"action": "start"
|
||||
},
|
||||
{
|
||||
"title": "Collect credentials",
|
||||
"description": "Login to your account if you haven't already",
|
||||
"cta": "Check cookies",
|
||||
"action": "two"
|
||||
},
|
||||
{
|
||||
"title": "Notarize twitter profile",
|
||||
"cta": "Notarize",
|
||||
"action": "three",
|
||||
"prover": true
|
||||
}
|
||||
],
|
||||
"hostFunctions": [
|
||||
"redirect",
|
||||
"notarize"
|
||||
],
|
||||
"cookies": [
|
||||
"api.x.com"
|
||||
],
|
||||
"headers": [
|
||||
"api.x.com"
|
||||
],
|
||||
"requests": [
|
||||
{
|
||||
"url": "https: //api.x.com/1.1/account/settings.json",
|
||||
"method": "GET"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
const esbuild = require('esbuild');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { promisify } = require('util');
|
||||
const { name } = require('./package.json');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// Promisify fs.readFile and fs.stat for convenience
|
||||
const readFileAsync = promisify(fs.readFile);
|
||||
const statAsync = promisify(fs.stat);
|
||||
const mkdirAsync = promisify(fs.mkdir);
|
||||
|
||||
/**
|
||||
* Generates a Base64 encoded icon file.
|
||||
* It checks if the output file already exists and is up-to-date before generating a new one.
|
||||
*/
|
||||
async function generateBase64Icon() {
|
||||
const iconPath = path.join(__dirname, 'assets', "icon.png");
|
||||
const outputDir = path.join(__dirname, 'dist', 'assets');
|
||||
const outputPath = path.join(outputDir, 'icon.ts');
|
||||
|
||||
try {
|
||||
// Ensure the output directory exists
|
||||
await mkdirAsync(outputDir, { recursive: true });
|
||||
const [iconStat, outputStat] = await Promise.all([
|
||||
statAsync(iconPath).catch(() => null),
|
||||
statAsync(outputPath).catch(() => null)
|
||||
]);
|
||||
|
||||
// Check if output file exists and is newer than the icon file
|
||||
if (outputStat && iconStat && outputStat.mtime > iconStat.mtime) {
|
||||
console.log('Base64 icon file is up-to-date.');
|
||||
return;
|
||||
}
|
||||
|
||||
const fileBuffer = await readFileAsync(iconPath);
|
||||
const base64Icon = `data:image/png;base64,${fileBuffer.toString('base64')}`;
|
||||
|
||||
const outputContent = `// This is a generated file. Do not edit directly.
|
||||
// This is a Base64 encoded version of the plugin's icon ('icon.png') used in the plugin's config.
|
||||
// This file is automatically generated by esBuild.js whenever the icon is changed.
|
||||
// There is no need to add it to version control.
|
||||
|
||||
export const icon = "${base64Icon}";\n`;
|
||||
|
||||
fs.writeFileSync(outputPath, outputContent);
|
||||
console.log('Base64 icon file generated successfully.');
|
||||
} catch (error) {
|
||||
console.error(`Failed to generate base64 icon: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const outputDir = 'dist';
|
||||
const entryFile = 'src/index.ts';
|
||||
const outputFile = path.join(outputDir, 'index.js');
|
||||
const outputWasm = path.join(outputDir, `${name}.tlsn.wasm`);
|
||||
|
||||
async function build() {
|
||||
await generateBase64Icon();
|
||||
|
||||
try {
|
||||
await esbuild.build({
|
||||
entryPoints: [entryFile],
|
||||
bundle: true,
|
||||
outdir: outputDir, // Use outdir for directory output
|
||||
sourcemap: true,
|
||||
minify: false, // might want to use true for production build
|
||||
format: 'cjs', // needs to be CJS for now
|
||||
target: ['es2020'], // don't go over es2020 because quickjs doesn't support it
|
||||
});
|
||||
|
||||
console.log('esbuild completed successfully.');
|
||||
|
||||
// Run extism-js to generate the wasm file
|
||||
const extismCommand = `extism-js ${outputFile} -i src/index.d.ts -o ${outputWasm}`;
|
||||
execSync(extismCommand, { stdio: 'inherit' });
|
||||
console.log('extism-js completed successfully.');
|
||||
} catch (error) {
|
||||
console.error('Build process failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
build();
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "twitter_profile",
|
||||
"version": "1.0.0",
|
||||
"description": "Demo TLSNotary plugin to notarize the ownership of a twitter profile",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "node esbuild.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "TLSNotary",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@extism/js-pdk": "^1.0.1",
|
||||
"esbuild": "^0.19.6",
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
}
|
||||
15
plugins/twitter_profile_ts/src/index.d.ts
vendored
15
plugins/twitter_profile_ts/src/index.d.ts
vendored
@@ -1,15 +0,0 @@
|
||||
declare module 'main' {
|
||||
// Extism exports take no params and return an I32
|
||||
export function start(): I32;
|
||||
export function two(): I32;
|
||||
export function parseTwitterResp(): I32;
|
||||
export function three(): I32;
|
||||
export function config(): I32;
|
||||
}
|
||||
|
||||
declare module 'extism:host' {
|
||||
interface user {
|
||||
redirect(ptr: I64): void;
|
||||
notarize(ptr: I64): I64;
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
import { icon } from '../dist/assets/icon';
|
||||
import config_json from '../config.json';
|
||||
/**
|
||||
* Plugin configuration
|
||||
* This configurations defines the plugin, most importantly:
|
||||
* * the different steps
|
||||
* * the user data (headers, cookies) it will access
|
||||
* * the web requests it will query (or notarize)
|
||||
*/
|
||||
export function config() {
|
||||
Host.outputString(
|
||||
JSON.stringify({
|
||||
...config_json,
|
||||
icon: icon
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function isValidHost(urlString: string) {
|
||||
const url = new URL(urlString);
|
||||
return url.hostname === 'twitter.com' || url.hostname === 'x.com';
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the browser window to x.com
|
||||
* This uses the `redirect` host function (see index.d.ts)
|
||||
*/
|
||||
function gotoTwitter() {
|
||||
const { redirect } = Host.getFunctions() as any;
|
||||
const mem = Memory.fromString('https://x.com');
|
||||
redirect(mem.offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the first (start) plugin step
|
||||
*/
|
||||
export function start() {
|
||||
if (!isValidHost(Config.get('tabUrl'))) {
|
||||
gotoTwitter();
|
||||
Host.outputString(JSON.stringify(false));
|
||||
return;
|
||||
}
|
||||
Host.outputString(JSON.stringify(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of step "two".
|
||||
* This step collects and validates authentication cookies and headers for 'api.x.com'.
|
||||
* If all required information, it creates the request object.
|
||||
* Note that the url needs to be specified in the `config` too, otherwise the request will be refused.
|
||||
*/
|
||||
export function two() {
|
||||
const cookies = JSON.parse(Config.get('cookies'))['api.x.com'];
|
||||
const headers = JSON.parse(Config.get('headers'))['api.x.com'];
|
||||
if (
|
||||
!cookies.auth_token ||
|
||||
!cookies.ct0 ||
|
||||
!headers['x-csrf-token'] ||
|
||||
!headers['authorization']
|
||||
) {
|
||||
Host.outputString(JSON.stringify(false));
|
||||
return;
|
||||
}
|
||||
|
||||
Host.outputString(
|
||||
JSON.stringify({
|
||||
url: 'https://api.x.com/1.1/account/settings.json',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-twitter-client-language': 'en',
|
||||
'x-csrf-token': headers['x-csrf-token'],
|
||||
Host: 'api.x.com',
|
||||
authorization: headers.authorization,
|
||||
Cookie: `lang=en; auth_token=${cookies.auth_token}; ct0=${cookies.ct0}`,
|
||||
'Accept-Encoding': 'identity',
|
||||
Connection: 'close',
|
||||
},
|
||||
secretHeaders: [
|
||||
`x-csrf-token: ${headers['x-csrf-token']}`,
|
||||
`cookie: lang=en; auth_token=${cookies.auth_token}; ct0=${cookies.ct0}`,
|
||||
`authorization: ${headers.authorization}`,
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to parse the Twitter response and specify what information is revealed (i.e. **not** redacted)
|
||||
* This method is optional in the notarization request. When it is not specified nothing is redacted.
|
||||
*
|
||||
* In this example it locates the `screen_name` and excludes that range from the revealed response.
|
||||
*/
|
||||
export function parseTwitterResp() {
|
||||
const bodyString = Host.inputString();
|
||||
const params = JSON.parse(bodyString);
|
||||
|
||||
// console.log("params");
|
||||
// console.log(JSON.stringify(params));
|
||||
|
||||
if (params.screen_name) {
|
||||
const revealed = `"screen_name":"${params.screen_name}"`;
|
||||
const selectionStart = bodyString.indexOf(revealed);
|
||||
const selectionEnd =
|
||||
selectionStart + revealed.length;
|
||||
const secretResps = [
|
||||
bodyString.substring(0, selectionStart),
|
||||
bodyString.substring(selectionEnd, bodyString.length),
|
||||
];
|
||||
Host.outputString(JSON.stringify(secretResps));
|
||||
} else {
|
||||
Host.outputString(JSON.stringify(false));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3: calls the `notarize` host function
|
||||
*/
|
||||
export function three() {
|
||||
const params = JSON.parse(Host.inputString());
|
||||
const { notarize } = Host.getFunctions() as any;
|
||||
|
||||
if (!params) {
|
||||
Host.outputString(JSON.stringify(false));
|
||||
} else {
|
||||
const mem = Memory.fromString(JSON.stringify({
|
||||
...params,
|
||||
getSecretResponse: 'parseTwitterResp',
|
||||
}));
|
||||
const idOffset = notarize(mem.offset);
|
||||
const id = Memory.find(idOffset).readString();
|
||||
Host.outputString(JSON.stringify(id));
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [],
|
||||
"types": [
|
||||
"@extism/js-pdk"
|
||||
],
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user