Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions docs/domain-verification-key-generation.md
Original file line number Diff line number Diff line change
@@ -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 Base 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 `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

From the root directory of the repo, run:

```sh
yarn generate-key-script
```

## Output
- `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

### Step 1: Generate Your Key Pair

First, generate your domain verification keys:

```sh
yarn generate-key-script
```

This will create:
- `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 `base-jwks.json` file at:
```
https://yourdomain.com/.well-known/base-jwks.json
```

This allows Base 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": "base-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 ("base-domain-verification")
- `alg`: Algorithm ("ES256K" for ECDSA with secp256k1 and SHA-256)

## Why?
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.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@
"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\"",
"validate-key-script": "yarn workspace @mobile-wallet-protocol/client validate-key-script --cwd \"$PWD\""
}
}
5 changes: 4 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
"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",
"validate-key-script": "node scripts/verify-signature.js"
},
"dependencies": {
"@noble/ciphers": "^0.5.3",
Expand Down Expand Up @@ -76,6 +78,7 @@
"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",
Expand Down
23 changes: 23 additions & 0 deletions packages/client/scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Scripts

This directory contains utility scripts for the Mobile Wallet Protocol client.

## Available Scripts

### `generate-domain-keys.ts`

Generates secp256k1 key pairs for Base domain verification.

**Usage:**
```bash
yarn generate-key-script
```

**What it does:**
- Generates a secp256k1 key pair
- Creates a `base-jwks.json` file in JWKS format
- Creates a `domain-verification-private-key.txt` file with the private key

**Output:**
- `base-jwks.json` - Public key in JWKS format for domain verification
- `domain-verification-private-key.txt` - Private key (keep secure!)
74 changes: 74 additions & 0 deletions packages/client/scripts/generate-domain-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env node

import { secp256k1 } from '@noble/curves/secp256k1';
import { randomBytes } from '@noble/hashes/utils';
import { 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 generateDomainVerificationKeys() {
// Generate a random private key
const privateKeyBytes = randomBytes(32);
const privateKey = Buffer.from(privateKeyBytes).toString('hex');

// 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: 'base-domain-verification',
alg: 'ES256K'
};

// Create the JWKS
const jwks = {
version: "1.0",
keys: [jwk]
};

return { jwks, privateKey };
}

function main() {
try {
const { jwks, privateKey } = generateDomainVerificationKeys();

// 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 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 in the project root directory
const privateKeyPath = join(projectRoot, 'domain-verification-private-key.txt');
writeFileSync(privateKeyPath, privateKey);

} catch (error) {
process.exit(1);
}
}

main();
105 changes: 105 additions & 0 deletions packages/client/scripts/verify-signature.js
Original file line number Diff line number Diff line change
@@ -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);
}
Loading