From b1cdfe01f1fe9f1e95ad5763e27978083014b544 Mon Sep 17 00:00:00 2001 From: Saketh Kotamraju Date: Thu, 26 Jun 2025 11:25:00 -0700 Subject: [PATCH 1/9] key generation script for well known file - domain verification key generation script for well known file - yarn generate-key-script - readme doc on how to use it --- docs/domain-verification-key-generation.md | 101 ++++++++++++++++++ package.json | 5 +- packages/client/package.json | 3 +- packages/client/scripts/README.md | 25 +++++ .../client/scripts/generate-domain-keys.ts | 96 +++++++++++++++++ 5 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 docs/domain-verification-key-generation.md create mode 100644 packages/client/scripts/README.md create mode 100644 packages/client/scripts/generate-domain-keys.ts diff --git a/docs/domain-verification-key-generation.md b/docs/domain-verification-key-generation.md new file mode 100644 index 0000000..55aa9d2 --- /dev/null +++ b/docs/domain-verification-key-generation.md @@ -0,0 +1,101 @@ +# Domain Verification Key Generation + +This script generates a secp256k1 key pair and outputs a JWKS (JSON Web Key Set) file for Coinbase domain verification, as well as a private key for signing. + +## What is JWKS? + +JWKS (JSON Web Key Set) is an industry standard format (RFC 7517) for representing cryptographic keys in JSON. It's widely used in OAuth 2.0, OpenID Connect, and other security protocols. + +## Where is the Public Key? + +Your public key is stored as **x and y coordinates** in the JWKS: + +```json +{ + "x": "base64url-encoded-x-coordinate", + "y": "base64url-encoded-y-coordinate" +} +``` + +### How to Reconstruct the Full Public Key: + +The full public key is: `04 + [x-coordinate] + [y-coordinate]` + +- `04` = uncompressed point prefix +- `x` = 32-byte x-coordinate (base64url decoded) +- `y` = 32-byte y-coordinate (base64url decoded) + +**Total: 65 bytes** (1 + 32 + 32) + +## What does it do? +- Generates a secp256k1 key pair +- Outputs a `.well-known/jwks.json` file in the current directory (for hosting at `https://yourdomain.com/.well-known/jwks.json`) +- Outputs a `domain-verification-private-key.txt` file (for secure storage and use with the SDK) + +## Usage + +From the root directory of the repo, run: + +```sh +yarn generate-key-script +``` + +## Output +- `.well-known/jwks.json`: Public key in JWKS format for domain verification +- `domain-verification-private-key.txt`: Private key (keep this secure!) + +## Getting Started with Origin Verification + +### Step 1: Generate Your Key Pair + +First, generate your domain verification keys: + +```sh +yarn generate-key-script +``` + +This will create: +- `.well-known/jwks.json` - Your public key (host this on your domain) +- `domain-verification-private-key.txt` - Your private key (keep secure!) + +### Step 2: Host Your Public Key + +Host the `jwks.json` file at: +``` +https://yourdomain.com/.well-known/jwks.json +``` + +This allows Coinbase to verify signatures from your domain. + +## Example JWKS Output +```json +{ + "version": "1.0", + "keys": [ + { + "kty": "EC", + "crv": "secp256k1", + "x": "base64url-encoded-x-coordinate", + "y": "base64url-encoded-y-coordinate", + "use": "sig", + "kid": "coinbase-domain-verification", + "alg": "ES256K" + } + ] +} +``` + +### JWKS Field Descriptions: +- `version`: Format version for tracking changes +- `kty`: Key type ("EC" for Elliptic Curve) +- `crv`: Curve name ("secp256k1" for Bitcoin/Ethereum curve) +- `x`, `y`: Base64URL-encoded x and y coordinates of the public key +- `use`: Key usage ("sig" for signing) +- `kid`: Key ID for identification ("coinbase-domain-verification") +- `alg`: Algorithm ("ES256K" for ECDSA with secp256k1 and SHA-256) + +## Why? +This enables secure domain verification for Coinbase Wallet SDK integrations. The SDK will use the private key to sign requests, and Coinbase will verify signatures using the public key hosted at your domain. + +--- +**Keep your private key safe!** Never share it or commit it to source control. \ No newline at end of file diff --git a/package.json b/package.json index 911781e..eadd431 100644 --- a/package.json +++ b/package.json @@ -6,5 +6,8 @@ "workspaces": [ "packages/*" ], - "packageManager": "yarn@4.4.0" + "packageManager": "yarn@4.4.0", + "scripts": { + "generate-key-script": "yarn workspace @mobile-wallet-protocol/client generate-key-script --cwd \"$PWD\"" + } } diff --git a/packages/client/package.json b/packages/client/package.json index 3963a5c..0dfd05d 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -29,7 +29,8 @@ "build": "tsc -p ./tsconfig.build.json && tsc-alias", "dev": "tsc --watch & nodemon --watch dist --delay 1 --exec tsc-alias", "typecheck": "tsc --noEmit", - "lint": "eslint . --ext .ts,.tsx --fix" + "lint": "eslint . --ext .ts,.tsx --fix", + "generate-key-script": "ts-node scripts/generate-domain-keys.ts" }, "dependencies": { "@noble/ciphers": "^0.5.3", diff --git a/packages/client/scripts/README.md b/packages/client/scripts/README.md new file mode 100644 index 0000000..d388485 --- /dev/null +++ b/packages/client/scripts/README.md @@ -0,0 +1,25 @@ +# Scripts + +This directory contains utility scripts for the Mobile Wallet Protocol client. + +## Available Scripts + +### `generate-domain-keys.ts` + +Generates secp256k1 key pairs for Coinbase domain verification. + +**Usage:** +```bash +yarn generate-key-script +``` + +**What it does:** +- Generates a secp256k1 key pair +- Creates a `.well-known/jwks.json` file in JWKS format +- Creates a `domain-verification-private-key.txt` file with the private key + +**Output:** +- `.well-known/jwks.json` - Public key in JWKS format for domain verification +- `domain-verification-private-key.txt` - Private key (keep secure!) + +For detailed documentation, see [Domain Verification Key Generation](../../docs/domain-verification-key-generation.md). \ No newline at end of file diff --git a/packages/client/scripts/generate-domain-keys.ts b/packages/client/scripts/generate-domain-keys.ts new file mode 100644 index 0000000..b97a7ee --- /dev/null +++ b/packages/client/scripts/generate-domain-keys.ts @@ -0,0 +1,96 @@ +#!/usr/bin/env node + +import { secp256k1 } from '@noble/curves/secp256k1'; +import { randomBytes } from '@noble/hashes/utils'; +import { mkdirSync, writeFileSync } from 'fs'; +import { join } from 'path'; + +// Base64URL encoding function +function base64url(buffer: Uint8Array): string { + return Buffer.from(buffer) + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); +} + +function getOutputDir(): string { + // Check for --cwd argument + const cwdArgIndex = process.argv.indexOf('--cwd'); + if (cwdArgIndex !== -1 && process.argv[cwdArgIndex + 1]) { + return process.argv[cwdArgIndex + 1]; + } + // Fallbacks + return process.env.INIT_CWD || process.cwd(); +} + +function generateDomainVerificationKeys() { + // Generate a random private key + const privateKeyBytes = randomBytes(32); + const privateKey = base64url(privateKeyBytes); + + // Get the uncompressed public key from the private key + const publicKey = secp256k1.getPublicKey(privateKeyBytes, false); // false = uncompressed + + // Extract x and y coordinates from the public key + // secp256k1 uncompressed public key is 65 bytes: 1 byte prefix + 32 bytes x + 32 bytes y + const x = base64url(publicKey.slice(1, 33)); + const y = base64url(publicKey.slice(33)); + + // Create the JWK + const jwk = { + kty: 'EC', + crv: 'secp256k1', + x, + y, + use: 'sig', + kid: 'coinbase-domain-verification', + alg: 'ES256K' + }; + + // Create the JWKS + const jwks = { + version: "1.0", + keys: [jwk] + }; + + return { jwks, privateKey }; +} + +function main() { + try { + const outputDir = getOutputDir(); + + console.log('šŸ”‘ Generating Coinbase domain verification keys...\n'); + + const { jwks, privateKey } = generateDomainVerificationKeys(); + + // Create .well-known directory if it doesn't exist + const wellKnownDir = join(outputDir, '.well-known'); + mkdirSync(wellKnownDir, { recursive: true }); + + // Write the JWKS file + const jwksPath = join(wellKnownDir, 'jwks.json'); + writeFileSync(jwksPath, JSON.stringify(jwks, null, 2)); + + // Write the private key to a separate file + const privateKeyPath = join(outputDir, 'domain-verification-private-key.txt'); + writeFileSync(privateKeyPath, privateKey); + + console.log('āœ… Successfully generated domain verification keys!\n'); + console.log('šŸ“ Files created:'); + console.log(` • ${jwksPath} - JWKS file for your domain`); + console.log(` • ${privateKeyPath} - Private key (keep this secure!)\n`); + + console.log('🌐 Next steps:'); + console.log(' 1. Host the jwks.json file at: https://yourdomain.com/.well-known/jwks.json'); + console.log(' 2. Store the private key securely for use with the SDK'); + console.log(' 3. Use the private key with the SDK\'s generateSignature function\n'); + + } catch (error) { + console.error('āŒ Error generating domain verification keys:', error); + process.exit(1); + } +} + +main(); \ No newline at end of file From a3465d6fc531feb84a0550b7df2e4fcc9f615978 Mon Sep 17 00:00:00 2001 From: Saketh Kotamraju Date: Fri, 27 Jun 2025 00:18:36 -0700 Subject: [PATCH 2/9] Update README.md --- packages/client/scripts/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/client/scripts/README.md b/packages/client/scripts/README.md index d388485..d63bf91 100644 --- a/packages/client/scripts/README.md +++ b/packages/client/scripts/README.md @@ -20,6 +20,4 @@ yarn generate-key-script **Output:** - `.well-known/jwks.json` - Public key in JWKS format for domain verification -- `domain-verification-private-key.txt` - Private key (keep secure!) - -For detailed documentation, see [Domain Verification Key Generation](../../docs/domain-verification-key-generation.md). \ No newline at end of file +- `domain-verification-private-key.txt` - Private key (keep secure!) \ No newline at end of file From 35c812595f2faab190be06ce40decf83448929df Mon Sep 17 00:00:00 2001 From: Saketh Kotamraju Date: Fri, 27 Jun 2025 00:36:20 -0700 Subject: [PATCH 3/9] changed from jwks.json to base-jwks.json --- docs/domain-verification-key-generation.md | 10 +++++----- packages/client/scripts/README.md | 4 ++-- packages/client/scripts/generate-domain-keys.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/domain-verification-key-generation.md b/docs/domain-verification-key-generation.md index 55aa9d2..5a2fc65 100644 --- a/docs/domain-verification-key-generation.md +++ b/docs/domain-verification-key-generation.md @@ -29,7 +29,7 @@ The full public key is: `04 + [x-coordinate] + [y-coordinate]` ## What does it do? - Generates a secp256k1 key pair -- Outputs a `.well-known/jwks.json` file in the current directory (for hosting at `https://yourdomain.com/.well-known/jwks.json`) +- Outputs a `.well-known/base-jwks.json` file in the current directory (for hosting at `https://yourdomain.com/.well-known/base-jwks.json`) - Outputs a `domain-verification-private-key.txt` file (for secure storage and use with the SDK) ## Usage @@ -41,7 +41,7 @@ yarn generate-key-script ``` ## Output -- `.well-known/jwks.json`: Public key in JWKS format for domain verification +- `.well-known/base-jwks.json`: Public key in JWKS format for domain verification - `domain-verification-private-key.txt`: Private key (keep this secure!) ## Getting Started with Origin Verification @@ -55,14 +55,14 @@ yarn generate-key-script ``` This will create: -- `.well-known/jwks.json` - Your public key (host this on your domain) +- `.well-known/base-jwks.json` - Your public key (host this on your domain) - `domain-verification-private-key.txt` - Your private key (keep secure!) ### Step 2: Host Your Public Key -Host the `jwks.json` file at: +Host the `base-jwks.json` file at: ``` -https://yourdomain.com/.well-known/jwks.json +https://yourdomain.com/.well-known/base-jwks.json ``` This allows Coinbase to verify signatures from your domain. diff --git a/packages/client/scripts/README.md b/packages/client/scripts/README.md index d63bf91..4ae6284 100644 --- a/packages/client/scripts/README.md +++ b/packages/client/scripts/README.md @@ -15,9 +15,9 @@ yarn generate-key-script **What it does:** - Generates a secp256k1 key pair -- Creates a `.well-known/jwks.json` file in JWKS format +- Creates a `.well-known/base-jwks.json` file in JWKS format - Creates a `domain-verification-private-key.txt` file with the private key **Output:** -- `.well-known/jwks.json` - Public key in JWKS format for domain verification +- `.well-known/base-jwks.json` - Public key in JWKS format for domain verification - `domain-verification-private-key.txt` - Private key (keep secure!) \ No newline at end of file diff --git a/packages/client/scripts/generate-domain-keys.ts b/packages/client/scripts/generate-domain-keys.ts index b97a7ee..b190861 100644 --- a/packages/client/scripts/generate-domain-keys.ts +++ b/packages/client/scripts/generate-domain-keys.ts @@ -70,7 +70,7 @@ function main() { mkdirSync(wellKnownDir, { recursive: true }); // Write the JWKS file - const jwksPath = join(wellKnownDir, 'jwks.json'); + const jwksPath = join(wellKnownDir, 'base-jwks.json'); writeFileSync(jwksPath, JSON.stringify(jwks, null, 2)); // Write the private key to a separate file @@ -83,7 +83,7 @@ function main() { console.log(` • ${privateKeyPath} - Private key (keep this secure!)\n`); console.log('🌐 Next steps:'); - console.log(' 1. Host the jwks.json file at: https://yourdomain.com/.well-known/jwks.json'); + console.log(' 1. Host the base-jwks.json file at: https://yourdomain.com/.well-known/base-jwks.json'); console.log(' 2. Store the private key securely for use with the SDK'); console.log(' 3. Use the private key with the SDK\'s generateSignature function\n'); From 69a0a2d70a179c783c6b3c4daa4f21b8610e2a55 Mon Sep 17 00:00:00 2001 From: Saketh Kotamraju Date: Mon, 30 Jun 2025 11:54:14 -0700 Subject: [PATCH 4/9] fixed changes for pr - no .well-known output directory - changed 'Coinbase' to 'base' --- docs/domain-verification-key-generation.md | 10 +++--- packages/client/scripts/README.md | 2 +- .../client/scripts/generate-domain-keys.ts | 33 +++++++------------ 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/docs/domain-verification-key-generation.md b/docs/domain-verification-key-generation.md index 5a2fc65..f649de6 100644 --- a/docs/domain-verification-key-generation.md +++ b/docs/domain-verification-key-generation.md @@ -1,6 +1,6 @@ # Domain Verification Key Generation -This script generates a secp256k1 key pair and outputs a JWKS (JSON Web Key Set) file for Coinbase domain verification, as well as a private key for signing. +This script generates a secp256k1 key pair and outputs a JWKS (JSON Web Key Set) file for Base domain verification, as well as a private key for signing. ## What is JWKS? @@ -65,7 +65,7 @@ Host the `base-jwks.json` file at: https://yourdomain.com/.well-known/base-jwks.json ``` -This allows Coinbase to verify signatures from your domain. +This allows Base to verify signatures from your domain. ## Example JWKS Output ```json @@ -78,7 +78,7 @@ This allows Coinbase to verify signatures from your domain. "x": "base64url-encoded-x-coordinate", "y": "base64url-encoded-y-coordinate", "use": "sig", - "kid": "coinbase-domain-verification", + "kid": "base-domain-verification", "alg": "ES256K" } ] @@ -91,11 +91,11 @@ This allows Coinbase to verify signatures from your domain. - `crv`: Curve name ("secp256k1" for Bitcoin/Ethereum curve) - `x`, `y`: Base64URL-encoded x and y coordinates of the public key - `use`: Key usage ("sig" for signing) -- `kid`: Key ID for identification ("coinbase-domain-verification") +- `kid`: Key ID for identification ("base-domain-verification") - `alg`: Algorithm ("ES256K" for ECDSA with secp256k1 and SHA-256) ## Why? -This enables secure domain verification for Coinbase Wallet SDK integrations. The SDK will use the private key to sign requests, and Coinbase will verify signatures using the public key hosted at your domain. +This enables secure domain verification for Base Wallet SDK integrations. The SDK will use the private key to sign requests, and Base will verify signatures using the public key hosted at your domain. --- **Keep your private key safe!** Never share it or commit it to source control. \ No newline at end of file diff --git a/packages/client/scripts/README.md b/packages/client/scripts/README.md index 4ae6284..9691cad 100644 --- a/packages/client/scripts/README.md +++ b/packages/client/scripts/README.md @@ -6,7 +6,7 @@ This directory contains utility scripts for the Mobile Wallet Protocol client. ### `generate-domain-keys.ts` -Generates secp256k1 key pairs for Coinbase domain verification. +Generates secp256k1 key pairs for Base domain verification. **Usage:** ```bash diff --git a/packages/client/scripts/generate-domain-keys.ts b/packages/client/scripts/generate-domain-keys.ts index b190861..3af600f 100644 --- a/packages/client/scripts/generate-domain-keys.ts +++ b/packages/client/scripts/generate-domain-keys.ts @@ -2,7 +2,7 @@ import { secp256k1 } from '@noble/curves/secp256k1'; import { randomBytes } from '@noble/hashes/utils'; -import { mkdirSync, writeFileSync } from 'fs'; +import { writeFileSync } from 'fs'; import { join } from 'path'; // Base64URL encoding function @@ -14,16 +14,6 @@ function base64url(buffer: Uint8Array): string { .replace(/=/g, ''); } -function getOutputDir(): string { - // Check for --cwd argument - const cwdArgIndex = process.argv.indexOf('--cwd'); - if (cwdArgIndex !== -1 && process.argv[cwdArgIndex + 1]) { - return process.argv[cwdArgIndex + 1]; - } - // Fallbacks - return process.env.INIT_CWD || process.cwd(); -} - function generateDomainVerificationKeys() { // Generate a random private key const privateKeyBytes = randomBytes(32); @@ -44,7 +34,7 @@ function generateDomainVerificationKeys() { x, y, use: 'sig', - kid: 'coinbase-domain-verification', + kid: 'base-domain-verification', alg: 'ES256K' }; @@ -59,22 +49,23 @@ function generateDomainVerificationKeys() { function main() { try { - const outputDir = getOutputDir(); - console.log('šŸ”‘ Generating Coinbase domain verification keys...\n'); const { jwks, privateKey } = generateDomainVerificationKeys(); - // Create .well-known directory if it doesn't exist - const wellKnownDir = join(outputDir, '.well-known'); - mkdirSync(wellKnownDir, { recursive: true }); + // Try to get the project root by going up from the current directory + let projectRoot = process.cwd(); + if (projectRoot.includes('packages/client')) { + // If we're in packages/client, go up to the project root + projectRoot = projectRoot.replace('/packages/client', ''); + } - // Write the JWKS file - const jwksPath = join(wellKnownDir, 'base-jwks.json'); + // Write the JWKS file in the project root directory + const jwksPath = join(projectRoot, 'base-jwks.json'); writeFileSync(jwksPath, JSON.stringify(jwks, null, 2)); - // Write the private key to a separate file - const privateKeyPath = join(outputDir, 'domain-verification-private-key.txt'); + // Write the private key to a separate file in the project root directory + const privateKeyPath = join(projectRoot, 'domain-verification-private-key.txt'); writeFileSync(privateKeyPath, privateKey); console.log('āœ… Successfully generated domain verification keys!\n'); From ecdc4e0b21973a4d245b0c9bf6c92e630077b8d5 Mon Sep 17 00:00:00 2001 From: Saketh Kotamraju Date: Mon, 7 Jul 2025 22:18:05 -0700 Subject: [PATCH 5/9] signature verification script --- verify-signature.js | 68 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 verify-signature.js diff --git a/verify-signature.js b/verify-signature.js new file mode 100644 index 0000000..2e2aa68 --- /dev/null +++ b/verify-signature.js @@ -0,0 +1,68 @@ +const crypto = require('crypto'); +const fs = require('fs'); +const secp256k1 = require('secp256k1'); + +// Mock transaction data +const mockRequestData = { + method: 'eth_sendTransaction', + params: [ + { + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: '0xde0b6b3a7640000', + gas: '0x5208', + gasPrice: '0x3b9aca00', + }, + ], +}; + +// Generate a mock nonce +const nonce = crypto.randomUUID(); +const domain = 'www.example.com'; + +// Read the private key from file +const privateKeyBase64 = fs.readFileSync('./domain-verification-private-key.txt', 'utf8').trim(); +const privateKey = Buffer.from(privateKeyBase64, 'base64'); + +// Read the JWKS file to get public key +const jwksData = JSON.parse(fs.readFileSync('./base-jwks.json', 'utf8')); +const publicKeyData = jwksData.keys[0]; + +// Convert JWKS coordinates to public key buffer +const x = Buffer.from(publicKeyData.x, 'base64url'); +const y = Buffer.from(publicKeyData.y, 'base64url'); +const publicKey = Buffer.concat([Buffer.from([0x04]), x, y]); + +console.log('šŸ”‘ Private Key:', privateKeyBase64); +console.log('�� Domain:', domain); +console.log('šŸŽ² Nonce:', nonce); +console.log('šŸ“ Request Data:', JSON.stringify(mockRequestData, null, 2)); + +// Create the payload to sign +const payload = { + domain: domain, + nonce: nonce, + requestData: mockRequestData, +}; + +// Serialize payload deterministically +const serializedPayload = JSON.stringify(payload, Object.keys(payload).sort()); +console.log('\nšŸ“¦ Serialized Payload:', serializedPayload); + +// Create message hash +const messageHash = crypto.createHash('sha256').update(serializedPayload).digest(); +console.log('šŸ” Message Hash:', messageHash.toString('hex')); + +// Sign the message hash +const signature = secp256k1.ecdsaSign(messageHash, privateKey); +const signatureHex = '0x' + signature.signature.toString('hex'); +console.log('āœļø Signature:', signatureHex); + +// Verify the signature +const isValid = secp256k1.ecdsaVerify(signature.signature, messageHash, publicKey); +console.log('\nāœ… Signature Verification:', isValid ? 'PASSED' : 'FAILED'); + +if (isValid) { + console.log('šŸŽ‰ Domain verification simulation successful!'); +} else { + console.log('āŒ Domain verification simulation failed!'); +} \ No newline at end of file From 48b98c41ed5b8c56d9108ebe60754761043218eb Mon Sep 17 00:00:00 2001 From: Saketh Kotamraju Date: Wed, 9 Jul 2025 12:20:06 -0700 Subject: [PATCH 6/9] validation script fixes - doesn't base64 encode the private key - yarn validate-key-script for testing sec256kp1 encoding/decoding. --- package.json | 3 +- packages/client/package.json | 6 +- .../client/scripts/generate-domain-keys.ts | 2 +- packages/client/scripts/verify-signature.js | 105 ++++++++++++++++++ verify-signature.js | 68 ------------ 5 files changed, 112 insertions(+), 72 deletions(-) create mode 100644 packages/client/scripts/verify-signature.js delete mode 100644 verify-signature.js diff --git a/package.json b/package.json index eadd431..8c9b365 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ ], "packageManager": "yarn@4.4.0", "scripts": { - "generate-key-script": "yarn workspace @mobile-wallet-protocol/client generate-key-script --cwd \"$PWD\"" + "generate-key-script": "yarn workspace @mobile-wallet-protocol/client generate-key-script --cwd \"$PWD\"", + "validate-key-script": "yarn workspace @mobile-wallet-protocol/client validate-key-script --cwd \"$PWD\"" } } diff --git a/packages/client/package.json b/packages/client/package.json index 0dfd05d..50be65c 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -30,7 +30,8 @@ "dev": "tsc --watch & nodemon --watch dist --delay 1 --exec tsc-alias", "typecheck": "tsc --noEmit", "lint": "eslint . --ext .ts,.tsx --fix", - "generate-key-script": "ts-node scripts/generate-domain-keys.ts" + "generate-key-script": "ts-node scripts/generate-domain-keys.ts", + "validate-key-script": "node scripts/verify-signature.js" }, "dependencies": { "@noble/ciphers": "^0.5.3", @@ -81,6 +82,7 @@ "ts-node": "^10.9.1", "tsc-alias": "^1.8.8", "tslib": "^2.6.0", - "typescript": "^5.1.6" + "typescript": "^5.1.6", + "secp256k1": "^5.0.0" } } diff --git a/packages/client/scripts/generate-domain-keys.ts b/packages/client/scripts/generate-domain-keys.ts index 3af600f..79f64d2 100644 --- a/packages/client/scripts/generate-domain-keys.ts +++ b/packages/client/scripts/generate-domain-keys.ts @@ -17,7 +17,7 @@ function base64url(buffer: Uint8Array): string { function generateDomainVerificationKeys() { // Generate a random private key const privateKeyBytes = randomBytes(32); - const privateKey = base64url(privateKeyBytes); + const privateKey = Buffer.from(privateKeyBytes).toString('hex'); // Get the uncompressed public key from the private key const publicKey = secp256k1.getPublicKey(privateKeyBytes, false); // false = uncompressed diff --git a/packages/client/scripts/verify-signature.js b/packages/client/scripts/verify-signature.js new file mode 100644 index 0000000..e997c6b --- /dev/null +++ b/packages/client/scripts/verify-signature.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +const crypto = require('crypto'); +const fs = require('fs'); +const secp256k1 = require('secp256k1'); +const path = require('path'); + +// Mock transaction data +const mockRequestData = { + method: 'eth_sendTransaction', + params: [ + { + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: '0xde0b6b3a7640000', + gas: '0x5208', + gasPrice: '0x3b9aca00', + }, + ], +}; + +// Generate a mock nonce +const nonce = crypto.randomUUID(); +const domain = 'www.example.com'; + +// Get the project root directory +let projectRoot = process.cwd(); +if (projectRoot.includes('packages/client')) { + // If we're in packages/client, go up to the project root + projectRoot = projectRoot.replace('/packages/client', ''); +} + +// Check if required files exist +const privateKeyPath = path.join(projectRoot, 'domain-verification-private-key.txt'); +const jwksPath = path.join(projectRoot, 'base-jwks.json'); + +if (!fs.existsSync(privateKeyPath)) { + console.error('āŒ Error: domain-verification-private-key.txt not found!'); + console.error(` Expected location: ${privateKeyPath}`); + console.error(' Please run "yarn generate-key-script" first to generate the required files.'); + process.exit(1); +} + +if (!fs.existsSync(jwksPath)) { + console.error('āŒ Error: base-jwks.json not found!'); + console.error(` Expected location: ${jwksPath}`); + console.error(' Please run "yarn generate-key-script" first to generate the required files.'); + process.exit(1); +} + +try { + // Read the private key from file (now in hex format) + const privateKeyHex = fs.readFileSync(privateKeyPath, 'utf8').trim(); + const privateKey = Buffer.from(privateKeyHex, 'hex'); + + // Read the JWKS file to get public key + const jwksData = JSON.parse(fs.readFileSync(jwksPath, 'utf8')); + const publicKeyData = jwksData.keys[0]; + + // Convert JWKS coordinates to public key buffer + const x = Buffer.from(publicKeyData.x, 'base64url'); + const y = Buffer.from(publicKeyData.y, 'base64url'); + const publicKey = Buffer.concat([Buffer.from([0x04]), x, y]); + + console.log('šŸ”‘ Private Key (hex):', privateKeyHex); + console.log('🌐 Domain:', domain); + console.log('šŸŽ² Nonce:', nonce); + console.log('šŸ“ Request Data:', JSON.stringify(mockRequestData, null, 2)); + + // Create the payload to sign + const payload = { + domain: domain, + nonce: nonce, + requestData: mockRequestData, + }; + + // Serialize payload deterministically + const serializedPayload = JSON.stringify(payload); + console.log('\nšŸ“¦ Serialized Payload:', serializedPayload); + + // Create message hash + const messageHash = crypto.createHash('sha256').update(serializedPayload).digest(); + console.log('šŸ” Message Hash:', messageHash.toString('hex')); + + // Sign the message hash + const signature = secp256k1.ecdsaSign(messageHash, privateKey); + const signatureHex = '0x' + signature.signature.toString('hex'); + console.log('āœļø Signature:', signatureHex); + + // Verify the signature + const isValid = secp256k1.ecdsaVerify(signature.signature, messageHash, publicKey); + console.log('\nāœ… Signature Verification:', isValid ? 'PASSED' : 'FAILED'); + + if (isValid) { + console.log('šŸŽ‰ Domain verification simulation successful!'); + } else { + console.log('āŒ Domain verification simulation failed!'); + } +} catch (error) { + console.error('āŒ Error during signature verification:', error.message); + if (error.code === 'ENOENT') { + console.error(' This usually means one of the required files is missing or corrupted.'); + console.error(' Please run "yarn generate-key-script" to regenerate the files.'); + } + process.exit(1); +} \ No newline at end of file diff --git a/verify-signature.js b/verify-signature.js deleted file mode 100644 index 2e2aa68..0000000 --- a/verify-signature.js +++ /dev/null @@ -1,68 +0,0 @@ -const crypto = require('crypto'); -const fs = require('fs'); -const secp256k1 = require('secp256k1'); - -// Mock transaction data -const mockRequestData = { - method: 'eth_sendTransaction', - params: [ - { - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: '0xde0b6b3a7640000', - gas: '0x5208', - gasPrice: '0x3b9aca00', - }, - ], -}; - -// Generate a mock nonce -const nonce = crypto.randomUUID(); -const domain = 'www.example.com'; - -// Read the private key from file -const privateKeyBase64 = fs.readFileSync('./domain-verification-private-key.txt', 'utf8').trim(); -const privateKey = Buffer.from(privateKeyBase64, 'base64'); - -// Read the JWKS file to get public key -const jwksData = JSON.parse(fs.readFileSync('./base-jwks.json', 'utf8')); -const publicKeyData = jwksData.keys[0]; - -// Convert JWKS coordinates to public key buffer -const x = Buffer.from(publicKeyData.x, 'base64url'); -const y = Buffer.from(publicKeyData.y, 'base64url'); -const publicKey = Buffer.concat([Buffer.from([0x04]), x, y]); - -console.log('šŸ”‘ Private Key:', privateKeyBase64); -console.log('�� Domain:', domain); -console.log('šŸŽ² Nonce:', nonce); -console.log('šŸ“ Request Data:', JSON.stringify(mockRequestData, null, 2)); - -// Create the payload to sign -const payload = { - domain: domain, - nonce: nonce, - requestData: mockRequestData, -}; - -// Serialize payload deterministically -const serializedPayload = JSON.stringify(payload, Object.keys(payload).sort()); -console.log('\nšŸ“¦ Serialized Payload:', serializedPayload); - -// Create message hash -const messageHash = crypto.createHash('sha256').update(serializedPayload).digest(); -console.log('šŸ” Message Hash:', messageHash.toString('hex')); - -// Sign the message hash -const signature = secp256k1.ecdsaSign(messageHash, privateKey); -const signatureHex = '0x' + signature.signature.toString('hex'); -console.log('āœļø Signature:', signatureHex); - -// Verify the signature -const isValid = secp256k1.ecdsaVerify(signature.signature, messageHash, publicKey); -console.log('\nāœ… Signature Verification:', isValid ? 'PASSED' : 'FAILED'); - -if (isValid) { - console.log('šŸŽ‰ Domain verification simulation successful!'); -} else { - console.log('āŒ Domain verification simulation failed!'); -} \ No newline at end of file From 9dc9c49bb7d5667aa6150627b7a967b6be1fe1ac Mon Sep 17 00:00:00 2001 From: Saketh Kotamraju Date: Thu, 10 Jul 2025 08:57:28 -0700 Subject: [PATCH 7/9] fixed output file name --- docs/domain-verification-key-generation.md | 6 +++--- packages/client/scripts/README.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/domain-verification-key-generation.md b/docs/domain-verification-key-generation.md index f649de6..e62bf18 100644 --- a/docs/domain-verification-key-generation.md +++ b/docs/domain-verification-key-generation.md @@ -29,7 +29,7 @@ The full public key is: `04 + [x-coordinate] + [y-coordinate]` ## What does it do? - Generates a secp256k1 key pair -- Outputs a `.well-known/base-jwks.json` file in the current directory (for hosting at `https://yourdomain.com/.well-known/base-jwks.json`) +- Outputs a `base-jwks.json` file in the current directory (for hosting at `https://yourdomain.com/.well-known/base-jwks.json`) - Outputs a `domain-verification-private-key.txt` file (for secure storage and use with the SDK) ## Usage @@ -41,7 +41,7 @@ yarn generate-key-script ``` ## Output -- `.well-known/base-jwks.json`: Public key in JWKS format for domain verification +- `base-jwks.json`: Public key in JWKS format for domain verification - `domain-verification-private-key.txt`: Private key (keep this secure!) ## Getting Started with Origin Verification @@ -55,7 +55,7 @@ yarn generate-key-script ``` This will create: -- `.well-known/base-jwks.json` - Your public key (host this on your domain) +- `base-jwks.json` - Your public key (host this on your domain) - `domain-verification-private-key.txt` - Your private key (keep secure!) ### Step 2: Host Your Public Key diff --git a/packages/client/scripts/README.md b/packages/client/scripts/README.md index 9691cad..123ba72 100644 --- a/packages/client/scripts/README.md +++ b/packages/client/scripts/README.md @@ -15,9 +15,9 @@ yarn generate-key-script **What it does:** - Generates a secp256k1 key pair -- Creates a `.well-known/base-jwks.json` file in JWKS format +- Creates a `base-jwks.json` file in JWKS format - Creates a `domain-verification-private-key.txt` file with the private key **Output:** -- `.well-known/base-jwks.json` - Public key in JWKS format for domain verification +- `base-jwks.json` - Public key in JWKS format for domain verification - `domain-verification-private-key.txt` - Private key (keep secure!) \ No newline at end of file From 09bb01a3f0c740e0ff63b939f7d9d58528ef6e06 Mon Sep 17 00:00:00 2001 From: Saketh Kotamraju Date: Thu, 10 Jul 2025 17:08:37 -0700 Subject: [PATCH 8/9] updated yarn.lock to match package.json --- packages/client/package.json | 4 +- yarn.lock | 99 +++++++++++++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 50be65c..1a66528 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -78,11 +78,11 @@ "jest-websocket-mock": "^2.4.0", "nodemon": "^3.1.0", "prettier": "^3.0.0", + "secp256k1": "^5.0.0", "ts-jest": "^27.1.5", "ts-node": "^10.9.1", "tsc-alias": "^1.8.8", "tslib": "^2.6.0", - "typescript": "^5.1.6", - "secp256k1": "^5.0.0" + "typescript": "^5.1.6" } } diff --git a/yarn.lock b/yarn.lock index 0259f3b..12c9492 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2075,6 +2075,7 @@ __metadata: jest-websocket-mock: "npm:^2.4.0" nodemon: "npm:^3.1.0" prettier: "npm:^3.0.0" + secp256k1: "npm:^5.0.0" ts-jest: "npm:^27.1.5" ts-node: "npm:^10.9.1" tsc-alias: "npm:^1.8.8" @@ -3343,6 +3344,13 @@ __metadata: languageName: node linkType: hard +"bn.js@npm:^4.11.9": + version: 4.12.2 + resolution: "bn.js@npm:4.12.2" + checksum: 10c0/09a249faa416a9a1ce68b5f5ec8bbca87fe54e5dd4ef8b1cc8a4969147b80035592bddcb1e9cc814c3ba79e573503d5c5178664b722b509fb36d93620dba9b57 + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -3371,6 +3379,13 @@ __metadata: languageName: node linkType: hard +"brorand@npm:^1.1.0": + version: 1.1.0 + resolution: "brorand@npm:1.1.0" + checksum: 10c0/6f366d7c4990f82c366e3878492ba9a372a73163c09871e80d82fb4ae0d23f9f8924cb8a662330308206e6b3b76ba1d528b4601c9ef73c2166b440b2ea3b7571 + languageName: node + linkType: hard + "browser-process-hrtime@npm:^1.0.0": version: 1.0.0 resolution: "browser-process-hrtime@npm:1.0.0" @@ -4003,6 +4018,21 @@ __metadata: languageName: node linkType: hard +"elliptic@npm:^6.5.7": + version: 6.6.1 + resolution: "elliptic@npm:6.6.1" + dependencies: + bn.js: "npm:^4.11.9" + brorand: "npm:^1.1.0" + hash.js: "npm:^1.0.0" + hmac-drbg: "npm:^1.0.1" + inherits: "npm:^2.0.4" + minimalistic-assert: "npm:^1.0.1" + minimalistic-crypto-utils: "npm:^1.0.1" + checksum: 10c0/8b24ef782eec8b472053793ea1e91ae6bee41afffdfcb78a81c0a53b191e715cbe1292aa07165958a9bbe675bd0955142560b1a007ffce7d6c765bcaf951a867 + languageName: node + linkType: hard + "emittery@npm:^0.13.1": version: 0.13.1 resolution: "emittery@npm:0.13.1" @@ -5012,6 +5042,16 @@ __metadata: languageName: node linkType: hard +"hash.js@npm:^1.0.0, hash.js@npm:^1.0.3": + version: 1.1.7 + resolution: "hash.js@npm:1.1.7" + dependencies: + inherits: "npm:^2.0.3" + minimalistic-assert: "npm:^1.0.1" + checksum: 10c0/41ada59494eac5332cfc1ce6b7ebdd7b88a3864a6d6b08a3ea8ef261332ed60f37f10877e0c825aaa4bddebf164fbffa618286aeeec5296675e2671cbfa746c4 + languageName: node + linkType: hard + "hasown@npm:^2.0.0, hasown@npm:^2.0.1, hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" @@ -5021,6 +5061,17 @@ __metadata: languageName: node linkType: hard +"hmac-drbg@npm:^1.0.1": + version: 1.0.1 + resolution: "hmac-drbg@npm:1.0.1" + dependencies: + hash.js: "npm:^1.0.3" + minimalistic-assert: "npm:^1.0.0" + minimalistic-crypto-utils: "npm:^1.0.1" + checksum: 10c0/f3d9ba31b40257a573f162176ac5930109816036c59a09f901eb2ffd7e5e705c6832bedfff507957125f2086a0ab8f853c0df225642a88bf1fcaea945f20600d + languageName: node + linkType: hard + "html-encoding-sniffer@npm:^2.0.1": version: 2.0.1 resolution: "html-encoding-sniffer@npm:2.0.1" @@ -5170,7 +5221,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2": +"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:^2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 @@ -6941,6 +6992,20 @@ __metadata: languageName: node linkType: hard +"minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1": + version: 1.0.1 + resolution: "minimalistic-assert@npm:1.0.1" + checksum: 10c0/96730e5601cd31457f81a296f521eb56036e6f69133c0b18c13fe941109d53ad23a4204d946a0d638d7f3099482a0cec8c9bb6d642604612ce43ee536be3dddd + languageName: node + linkType: hard + +"minimalistic-crypto-utils@npm:^1.0.1": + version: 1.0.1 + resolution: "minimalistic-crypto-utils@npm:1.0.1" + checksum: 10c0/790ecec8c5c73973a4fbf2c663d911033e8494d5fb0960a4500634766ab05d6107d20af896ca2132e7031741f19888154d44b2408ada0852446705441383e9f8 + languageName: node + linkType: hard + "minimatch@npm:9.0.3": version: 9.0.3 resolution: "minimatch@npm:9.0.3" @@ -7117,6 +7182,26 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^5.0.0": + version: 5.1.0 + resolution: "node-addon-api@npm:5.1.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/0eb269786124ba6fad9df8007a149e03c199b3e5a3038125dfb3e747c2d5113d406a4e33f4de1ea600aa2339be1f137d55eba1a73ee34e5fff06c52a5c296d1d + languageName: node + linkType: hard + +"node-gyp-build@npm:^4.2.0": + version: 4.8.4 + resolution: "node-gyp-build@npm:4.8.4" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: 10c0/444e189907ece2081fe60e75368784f7782cfddb554b60123743dfb89509df89f1f29c03bbfa16b3a3e0be3f48799a4783f487da6203245fa5bed239ba7407e1 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 10.2.0 resolution: "node-gyp@npm:10.2.0" @@ -7948,6 +8033,18 @@ __metadata: languageName: node linkType: hard +"secp256k1@npm:^5.0.0": + version: 5.0.1 + resolution: "secp256k1@npm:5.0.1" + dependencies: + elliptic: "npm:^6.5.7" + node-addon-api: "npm:^5.0.0" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.2.0" + checksum: 10c0/ea977fcd3a21ee10439a546774d4f3f474f065a561fc2247f65cb2a64f09628732fd606c0a62316858abd7c07b41f5aa09c37773537f233590b4cf94d752dbe7 + languageName: node + linkType: hard + "semver@npm:7.x, semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4": version: 7.6.3 resolution: "semver@npm:7.6.3" From 46ecc80bffdf298fa203f56df62922c42409513b Mon Sep 17 00:00:00 2001 From: Saketh Kotamraju Date: Thu, 10 Jul 2025 17:13:07 -0700 Subject: [PATCH 9/9] removed print statements for yarn lint --- packages/client/scripts/generate-domain-keys.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/client/scripts/generate-domain-keys.ts b/packages/client/scripts/generate-domain-keys.ts index 79f64d2..afd3d51 100644 --- a/packages/client/scripts/generate-domain-keys.ts +++ b/packages/client/scripts/generate-domain-keys.ts @@ -49,8 +49,6 @@ function generateDomainVerificationKeys() { function main() { try { - console.log('šŸ”‘ Generating Coinbase domain verification keys...\n'); - const { jwks, privateKey } = generateDomainVerificationKeys(); // Try to get the project root by going up from the current directory @@ -68,18 +66,7 @@ function main() { const privateKeyPath = join(projectRoot, 'domain-verification-private-key.txt'); writeFileSync(privateKeyPath, privateKey); - console.log('āœ… Successfully generated domain verification keys!\n'); - console.log('šŸ“ Files created:'); - console.log(` • ${jwksPath} - JWKS file for your domain`); - console.log(` • ${privateKeyPath} - Private key (keep this secure!)\n`); - - console.log('🌐 Next steps:'); - console.log(' 1. Host the base-jwks.json file at: https://yourdomain.com/.well-known/base-jwks.json'); - console.log(' 2. Store the private key securely for use with the SDK'); - console.log(' 3. Use the private key with the SDK\'s generateSignature function\n'); - } catch (error) { - console.error('āŒ Error generating domain verification keys:', error); process.exit(1); } }