Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
7 August 2025 - `Feature` Enhanced End-User Security in TonConnect:
- Added `/verify` endpoint for connection verification with statuses: `ok`, `danger`, `warning`, `unknown`
- Introduced encrypted `request_source` metadata in bridge messages containing origin, IP, User-Agent, timestamp, and client ID
- Added BridgeRequestSource struct for request source tracking and verification
- Updated bridge message format to include optional `request_source` field
- Enhanced wallet workflows with connection and transaction verification processes
- Added comprehensive wallet implementation guidelines for security features
- Implemented phased rollout approach for verification status responses
- Maintained full backward compatibility with existing dApps and bridges
- Added user interface guidelines for verification marks and security warnings

7 March 2023 - `Feature` format of `DeviceInfo` of `ConnectEventSuccess` changed; Parameter `maxMessages` added to the Feature `SendTransaction`.
95 changes: 91 additions & 4 deletions bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,32 +42,119 @@ Sending message from client A to client B. Bridge returns error if ttl is too hi

```tsx
request
POST /message?client_id=<to_hex_str(A)>?to=<to_hex_str(B)>&ttl=300&topic=<sendTransaction|signData>
POST /message?client_id=<to_hex_str(A)>?to=<to_hex_str(B)>&ttl=300&topic=<sendTransaction|signData>[&no_request_source=true]

body: <base64_encoded_message>
```


The `topic` [optional] query parameter can be used by the bridge to deliver the push notification to the wallet. If the parameter is given, it must correspond to the RPC method called inside the encrypted `message`.

The `no_request_source` [optional] query parameter can be used to disable request source metadata forwarding and encryption. When set to `true`, the bridge will not include the `request_source` field in the BridgeMessage. This parameter should be set for messages from `wallet` to `dapp`, since the `dapp` side doesn't need this information.

Bridge buffers messages up to TTL (in secs), but removes them as soon as the recipient receives the message.

If the TTL exceeds the hard limit of the bridge server, it should respond with HTTP 400. Bridges should support at least 300 seconds TTL.

When the bridge receives a message `base64_encoded_message` from client `A` addressed to client `B`, it generates a message `BridgeMessage`:
## Bridge Security Verification

For enhanced security, the bridge implements verification mechanisms to help wallets confirm the true source of connection and transaction requests.

### Request Source Metadata

When the bridge receives a message from client A to client B, it collects request source data:

```go
type BridgeRequestSource struct {
Origin string `json:"origin"` // protocol + domain (e.g., "https://app.ton.org")
IP string `json:"ip"` // client IP address
Time string `json:"time"` // unixtime
UserAgent string `json:"user_agent"` // HTTP User-Agent header
}
```

### Message Processing with Verification

When the bridge receives a message `base64_encoded_message` from client `A` addressed to client `B`, it:

1. Collects request source metadata into `BridgeRequestSource` struct
2. Serializes the struct to JSON
3. Encrypts it using the recipient wallet's Curve25519 public key with:
```
naclbox.SealAnonymous(nil, data, receiverSessionPublicKey, rand.Reader)
```
4. Base64 encodes the encrypted bytes
5. Generates a message `BridgeMessage`:

```js
{
"from": <to_hex_str(A)>,
"message": <base64_encoded_message>
"message": <base64_encoded_message>,
"request_source": <base64_encoded_encrypted_request_source>
}
```

and sends it to the client B via SSE connection
and sends it to the client B via SSE connection:
```js
resB.write(BridgeMessage)
```

### Connect Verification Endpoint

Bridge provides a verification endpoint for connection requests:

```tsx
request
POST /verify

body: {
"type": "connect",
"client_id": "<client_id>",
"origin": "<protocol+domain>"
}
```

### IP Address Endpoint

Bridge provides an endpoint for wallets to obtain their current IP address for validation purposes:

```tsx
request
POST /myip
```

```tsx
response
{
"ip": "<client_ip_address>"
}
```

This endpoint returns the IP address from which the request originated, allowing wallets to compare their current IP with the IP address stored in request source metadata for security validation.

**Response statuses:**

- **Phase 1** (first 6 months): `ok`, `unknown`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s not obvious to a future reader whether we’re in Phase 1 or Phase 2 right now. Could we add something to make the current phase clear? Add dates?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can only add dates after we start the rollout.

- **Phase 2** (after 6 months): `ok`, `danger`, `warning`

```tsx
response
{
"status": "ok" | "danger" | "warning" | "unknown"
}
```

**Status meanings:**
- `ok`: Request verified and matches expected source
- `danger`: Strong indication of fraudulent activity
- `warning`: Suspicious activity or mismatched details
- `unknown`: Cannot verify (default for new or untracked origins)

**Bridge Implementation:**
- On SSE connect store connection metadata for a short period(~5 minutes): origin, IP address, client ID, timestamp
- Compare verification requests against stored data
- Implement rate limiting and abuse detection

### Heartbeat

To keep the connection, bridge server should periodically send a "heartbeat" message to the SSE channel. Client should ignore such messages.
Expand Down
103 changes: 103 additions & 0 deletions wallet-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,106 @@ Multiple network accounts can be created for one key pair. Implement this functi

We recommend wallets provide the ability to disconnect session with a specified dapp because the dapp may have an incomplete UI.

## Security Verification

### Connection Verification Implementation

1) **Implement `/verify` endpoint integration**

For HTTP Bridge connections, wallets MUST implement connection verification:

- Extract origin and client_id from connection requests
- Send POST request to `${bridgeUrl}/verify` with connection details
- Process verification status and display appropriate UI to users

2) **Verification Status Handling**

Wallets MUST handle all verification statuses appropriately:

- `ok` → Show that message is coming from the shown source, or show nothing
- `ok` + whitelisted domain → Show that message is coming from the shown source AND add source is verified
- `danger` → Show strong warning, recommend declining connection
- `warning` → Show caution message, let user decide
- `unknown` → Proceed without special indicators (default behavior)

3) **Rollout Phase Awareness**

- **Phase 1 (first 6 months)**: Only `ok` and `unknown` statuses returned
Copy link

@callmedenchick callmedenchick Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the one hand, this should be obvious, but still — why do we need to split the rollout into two phases? Why can’t we just show the statuses right away?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we need time to update sdk on the dapps side, otherwise we don't have guarantees that client connects to the bridge before opening wallet.

- **Phase 2 (after 6 months)**: Full status set including `danger` and `warning`

### Transaction Verification Implementation

1) **Request Source Metadata Processing**

Wallets MUST implement transaction source verification:

- Decrypt `request_source` field from BridgeMessage using session private key
- Parse BridgeRequestSource JSON containing origin, IP, User-Agent, timestamp, client_id
- Obtain current IP address using bridge's `POST /myip` (can be cached) endpoint for comparison
- Compare metadata against current user information
- Display warnings for any mismatches

2) **Mismatch Detection and Warnings**

Display appropriate warnings for detected mismatches:

- **Different origin**: Strong fraud warning - likely impersonation attack
- **Different IP/User-Agent**: Network change warning - could be legitimate or suspicious
- **Significant time gaps**: Message is not relevant anymore
- **Missing metadata**: Should not occure after rollout. Messages without metadata should be rejected

### User Interface Guidelines

1) **Verification Status Messages**

Wallets should be able to display following statuses:
- **Verification Mark (ok + whitelisted)**:
"✅ Verified dApp — confirmed request from a trusted source"

- **Ok**:
"No message"

- **Danger Warning**:
"⚠️ This request could not be verified and may be fraudulent. Do not proceed unless you are certain of the source."

- **Warning Message**:
"⚠️ This request's details differ from your current connection. This could be due to a network change or other unusual event. Proceed with caution."

2) **Transaction Source Display**

In transaction confirmation dialogs, wallets SHOULD display:

- Request source information (origin, timestamp)
- Any detected mismatches with stored connection data
- Clear warnings for security concerns
- Country of the request origin(from ip)
- Information about request user agent in the human readable format(e.g. Chrome 113 from macOS)

3) **User Education**

Wallets SHOULD educate users about:

- Meaning of verification marks and warnings
- How to identify legitimate vs suspicious connection patterns
- Best practices for safe dApp interaction
- When to decline suspicious requests

### Security Best Practices

1) **Metadata Storage**

- Store connection metadata securely and encrypted
- Implement proper session management and cleanup
- Limit metadata retention to necessary duration

2) **Verification Logic**

- Implement proper cryptographic verification of encrypted metadata
- Handle edge cases and error conditions gracefully

3) **User Safety**

- Default to more restrictive behavior when verification fails
- Provide clear actionable guidance to users
- Never suppress security warnings to improve user experience

66 changes: 57 additions & 9 deletions workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
1. App initiates SSE connection with bridge;
2. App passes connection info to the wallet via universal link or deeplink or QR code;
3. Wallet connects to the bridge with given parameters, and save connection info locally;
4. Wallet sends account information to the app using bridge;
5. App receives message and save connection info locally;
4. Wallet verifies connection source via `/verify` endpoint;
5. Wallet sends account information to the app using bridge;
6. App receives message and save connection info locally;

### Reconnection with http bridge
1. App reads connection info from localstorage
Expand All @@ -22,9 +23,10 @@
### Making ordinary requests and responses
1. App and wallet are in a connected state
2. App generates request and sends it to the bridge
3. Bridge forwards message to the wallet
4. Wallet generates response and sends it to the bridge
5. Bridge forwards message to the app
3. Bridge forwards message to the wallet with encrypted request source metadata
4. Wallet decrypts and verifies request source metadata
5. Wallet generates response and sends it to the bridge
6. Bridge forwards message to the app


## Details
Expand Down Expand Up @@ -67,7 +69,32 @@ App is not yet in the connected state, and may restart the whole process at any

### Wallet establishes connection

Wallet opens up a link or QR code, reads plaintext app’s **Client ID** (A from parameter “**id”**) and [InitialRequest](requests-responses.md#initiating-connection) (from parameter **“r”**).
Wallet opens up a link or QR code, reads plaintext app's **Client ID** (A from parameter "**id"**) and [InitialRequest](requests-responses.md#initiating-connection) (from parameter **"r"**).

### Connection Verification Process

**For HTTP Bridge connections:**

1. **Extract connection details**: Wallet extracts origin, client ID from the connection request
2. **Call verification endpoint**: Wallet sends POST request to `${bridgeUrl}/verify`:
```json
{
"type": "connect",
"client_id": "<client_id>",
"origin": "<protocol+domain>"
}
```
3. **Process verification response**:
- `ok` + whitelisted domain → show verification mark ✅
- `danger` → display strong warning, recommend declining
- `warning` → display caution message
- `unknown` → proceed without special indicators

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if /verify endpoint is not available or wallet gets an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be treated as danger


**User Interface Guidelines:**
- **Verification Mark (ok + whitelisted)**: "✅ Verified dApp — confirmed request from a trusted source"
- **ok**: No message for default case
- **Danger Warning**: "⚠️ This request could not be verified and may be fraudulent. Do not proceed unless you are certain of the source."
- **Warning Message**: "⚠️ This request's details differ from expected. This could be due to a network change or other unusual event. Proceed with caution."

Wallet computes the [InitialResponse](requests-responses.md#initiating-connection).

Expand All @@ -93,16 +120,37 @@ When the user performs an action in the app, it may request confirmation from th

App generates a [request](requests-responses.md#messages).

App encrypts it to the wallets key B (see below).
App encrypts it to the wallet's key B (see below).

App sends the encrypted message to B over the [Bridge](bridge.md).

App shows “pending confirmation” UI to let user know to open the wallet.
**Bridge processes message with verification**:
1. Bridge collects request source metadata (origin, IP, User-Agent, timestamp, client ID)
2. Bridge encrypts metadata using wallet's Curve25519 public key
3. Bridge includes encrypted `request_source` in the BridgeMessage

App shows "pending confirmation" UI to let user know to open the wallet.

Wallet receives the encrypted message through the Bridge.

Wallet decrypts the message and is now assured that it came from the app with ID **A.**

Wallet shows the confirmation dialog to the user, signs transaction and [replies](requests-responses.md#messages) over the bridge with user’s decision: “Ok, sent” or “User cancelled”.
### Transaction Verification Process

**Request Source Verification:**
1. **Decrypt metadata**: Wallet decrypts the `request_source` field from BridgeMessage using its private key
2. **Parse metadata**: Extract BridgeRequestSource with origin, IP, User-Agent, timestamp, client ID
3. **Compare with stored connection**: Verify metadata matches stored connection details
4. **Display warnings for mismatches**:
- Different origin → potential fraud warning
- Different IP/User-Agent → network change warning
- Significant time gaps → reject transaction

**User Interface for Transaction Verification:**
- Show request source details in transaction confirmation
- Highlight any mismatches between connection and transaction metadata
- Provide clear warnings for potential security issues

Wallet shows the confirmation dialog to the user, signs transaction and [replies](requests-responses.md#messages) over the bridge with user's decision: "Ok, sent" or "User cancelled".

App receives the encrypted message, decrypts it and closes the “pending confirmation” UI.