# Signature and Verify

This document defines the signing and verification rules for all parties interacting with the UR ecosystem. It serves as the single source of truth for signature formats and authentication mechanisms.

## Overview

The UR ecosystem uses two primary authentication methods depending on the API being accessed:

1. **Partner Authentication (Server-to-Server)**: Used for `UR-OPEN-API` and Webhooks. Authenticates the Partner backend using a registered ECDSA key pair.
2. **User Authentication (Wallet-to-Server)**: Used for `UR-API`. Authenticates individual end-users using their wallet's private key.

Both methods utilize **Ethereum Personal Sign (EIP-191)**: `"\x19Ethereum Signed Message:\n{len(messageToSign)}{messageToSign}"`

***

## Part A: Partner Authentication (UR-OPEN-API & Webhooks)

This method is used when a Partner backend calls UR APIs or when UR sends Webhooks to a Partner.

### 1. HTTP Headers

| Header            | Description                                                                                                           | Required |
| ----------------- | --------------------------------------------------------------------------------------------------------------------- | -------- |
| `X-Api-Signature` | Hexadecimal signature with `0x` prefix.                                                                               | Yes      |
| `X-Api-Deadline`  | The Unix timestamp (in seconds) indicating when the request expires. Setting this to 5 minutes from the current time. | Yes      |
| `X-Api-PublicKey` | The Ethereum address of the signer.                                                                                   | Optional |

### 2. Signing Logic

#### Partner Request (Partner -> UR)

* **Signer**: Partner's registered backend Ethereum address.
* **Message Components**:
  * `requestBody`: The exact raw JSON string in the request body.
  * `deadline`: The value sent in the `X-Api-Deadline` header, a timestamp within the next 5 minutes.
* **Message to Sign**: `messageToSign = "{requestBody} {deadline}"` (Note the single space between body and deadline).
* **Signing code example:** Refers to [this](#signature).

#### UR Response / Webhook (UR -> Partner)

* **Signer**: UR Server address.
* **Message Components**:
  * `responseBody`: The exact raw JSON string returned in the HTTP body.
* **Message to Sign**: `messageToSign = "{responseBody}"`

### 3. Verification

The receiver must:

1. Read the raw body and relevant headers.
2. Construct the `messageToSign` as defined above.
3. Use EIP-191 recovery to extract the signer's Ethereum address from the `X-Api-Signature`.
4. Verify that the recovered address matches the expected/whitelisted address.

***

## Part B: User Authentication (UR-API)

This method is used for sensitive user operations (e.g., FX, Transfers) where a direct wallet signature is required.

### 1. HTTP Headers

| Header     | Description                                                                                                                         |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `sign`     | The user's wallet signature.                                                                                                        |
| `hash`     | A Keccak256 hash of the business payload or a unique message.                                                                       |
| `deadline` | The Unix timestamp (in seconds) indicating when the request expires. We recommend setting this to 20 minutes from the current time. |
| `tokenId`  | The user's UR Token ID (URID).                                                                                                      |

### 2. Signing Logic

1. **Construct Base Message**: `baseMessage = hash + deadline` (String concatenation).
2. **Generate Intermediate Hash**: `intermediateHash = Keccak256(baseMessage)`.
3. **Construct Final Message**: `finalMessage = "I agree to access my profile. " + intermediateHash.hex()`.
4. **Sign**: The user signs the `finalMessage` using their wallet (EIP-191).

***

## Key Pair

### 1. Generate Key Pair Using Node.js (viem)

```javascript
import { toHex } from "viem";
import {
  english,
  generateMnemonic,
  mnemonicToAccount,
} from "viem/accounts";

// Generate mnemonic and account
const mnemonic = generateMnemonic(english);
const account = mnemonicToAccount(mnemonic);
const privateKeyBytes = account.getHdKey().privateKey;

console.log("Address (Public Key):", account.address);
console.log("Private Key:", privateKeyBytes ? toHex(privateKeyBytes) : "<unavailable>");
```

### 2. Key Management Recommendations

Private Key Storage: Use key management services like AWS Secrets Manager, HashiCorp Vault Public Key Registration: Synchronize the generated address (publicKey) with the UR system through secure channels

***

## Code Examples

### signature

```go
import (
    "fmt"
    "time"
    "strconv"
    "encoding/hex"
    "github.com/ethereum/go-ethereum/crypto"
)

// GenSignature generates API request signature
// Parameters:
//   privateKeyHex: private key hex string (without 0x prefix)
//   msg: business message content (typically the request body JSON string)
//   deadline: signature expiration time (Unix timestamp, seconds)
func GenSignature(privateKeyHex string, msg string, deadline int64) (string, error) {
    // 1. Build deadline string
    deadlineStr := strconv.FormatInt(deadline, 10)
    
    // For concurrent requests within the same second, you can add a very small random offset to the deadline (but still within the allowed window) to ensure signature uniqueness
    messageToSign := fmt.Sprintf("%s %s", msg, deadlineStr)
    
    // 2. Add Ethereum personal signature prefix
    prefix := fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(messageToSign))
    prefixedMessageBytes := []byte(prefix + messageToSign)
    
    // 3. Calculate message hash
    messageHash := crypto.Keccak256Hash(prefixedMessageBytes)
    
    // 4. Load private key and perform signature
    privateKey, err := crypto.HexToECDSA(privateKeyHex)
    if err != nil {
    return "", fmt.Errorf("failed to load private key: %w", err)
    }
    
    signatureBytes, err := crypto.Sign(messageHash.Bytes(), privateKey)
    if err != nil {
    return "", fmt.Errorf("failed to sign message: %w", err)
    }
    
    // 5. Return hex-encoded signature (including recovery ID)
    return "0x" + hex.EncodeToString(signatureBytes), nil
}
```

### verify

```go
// recoverAddress recovers signer's public key address from signature and original message
func recoverAddress(message, sigHex string) (common.Address, error) {
    // Decode hex signature
    sigBytes, err := hex.DecodeString(strings.TrimPrefix(sigHex, "0x"))
    if err != nil {
        return common.Address{}, fmt.Errorf("invalid hex signature: %w", err)
    }

    if len(sigBytes) != 65 {
        return common.Address{}, fmt.Errorf("signature length must be 65 bytes, got %d", len(sigBytes))
    }

    // Compatible handling of recovery ID (v): some libraries return 27/28, need to convert to 0/1
    if sigBytes[64] >= 27 {
        sigBytes[64] -= 27
    }

    // Rebuild prefixed message hash
    prefix := fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message))
    prefixedMsgBytes := []byte(prefix + message)
    hash := crypto.Keccak256Hash(prefixedMsgBytes)

    // Recover public key from signature
    pubKey, err := crypto.SigToPub(hash.Bytes(), sigBytes)
    if err != nil {
        return common.Address{}, fmt.Errorf("could not recover public key: %w", err)
    }

    return crypto.PubkeyToAddress(*pubKey), nil
}

```

### User wallet signature

```javascript
// We only allow the signature to be valid for 20 minutes max.
// Can be less than that if want more security.
const SIGNATURE_DEADLINE_IN_SECONDS = 1200;

// Alternative to Date.now()
// sometimes the device clock is out of sync and can give the wrong timestamp
const serverTimestamp = await fetch("https://api.fiat24.com/timestamp");
const now = serverTimestamp.timestamp;

const deadline = Math.round(now / 1000) + SIGNATURE_DEADLINE_IN_SECONDS;

const hash = "Hello world"; // Could be any text or payload hash

// Calculate Hash of (hash + deadline)
// Example using web3.js
const deadlineHash = web3.utils.sha3(hash + deadline); 
// Example using ethers.js
// const deadlineHash = ethers.keccak256(ethers.toUtf8Bytes(hash + deadline));

// Construct the specific message required by UR
const messageToSign = `I agree to access my profile. ${deadlineHash}`;

// Generate Signature
// Example using web3.js
const sign = await web3.eth.personal.sign(messageToSign, address);
// Example using ethers.js
// const sign = await signer.signMessage(messageToSign);

return { hash, deadline, sign };

```

***

## Important Notes

* **Raw Body**: Always use the raw HTTP body bytes. Do not re-serialize JSON, as key ordering or whitespace differences will cause signature mismatches.
* **Deadline Window**: Recommended window is 1-5 minutes. Requests with an expired deadline or a deadline too far in the future (>5 mins) will be rejected.
* **Replay Protection**: For high-concurrency environments, add a small random offset (1-5 seconds) to the `deadline` to ensure each request generates a unique signature.
* **Case Sensitivity**: Ethereum addresses should be treated as case-insensitive but are typically stored/transmitted in lowercase or checksum format.

## Environment Addresses

| Environment | UR Server Signature Address                  |
| ----------- | -------------------------------------------- |
| **Sepolia** | `0x4D2AA3f43De8f8BE746E315D291B804a4aBD3939` |
| **Mainnet** | `0xee28dEaD5F114C8405BE3be1144D59A4110B7F79` |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ur.app/developer-resources/signature-and-verify.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
