# Signature Method for X-META-INFO

本文件说明 Hezor2 接口里 `X-META-INFO` 请求头的签名方法，适用于 Node.js、Java 等非 Python 客户端。

## 1. 签名规则

`X-META-INFO` 本质是 JWT。这里是“签名”，不是“加密”。

- 算法：`EdDSA`（Ed25519）
- JWT Header：`{"alg":"EdDSA","typ":"JWT"}`
- JWT Payload：`MetaInfo` 字段 + `exp`

与 Python 实现对齐来源：`src/hezor_common/security/jwt.py`

- `algorithm="EdDSA"`
- 支持 `expires_in`（默认 3600 秒）

## 2. MetaInfo Payload 字段

字段应与 `MetaInfo` 一致（来源：`src/hezor_common/transfer/base_sdk/meta_info.py`）：

- `subject`
- `subject_code`（支持单主体如 `"yidashan"`，或多级主体如 `"yidashan/market_dept"`、`"jiangsu/jintan"`）
- `caller_id`
- `data_coverage`
- `creation_slug`
- `creation_name`
- `exp`（建议添加，秒级 Unix 时间戳）

示例：

```json
{
  "subject": "鮨大山",
  "subject_code": "wdyl_001",
  "caller_id": "user_123",
  "data_coverage": "202401-202412",
  "creation_slug": "single_store_profit_model",
  "creation_name": "单店盈利模型",
  "exp": 1760000000
}
```

## 3. Node.js 示例（EdDSA）

依赖：

```bash
npm i jsonwebtoken
```

代码示例：

```javascript
import fs from "node:fs";
import jwt from "jsonwebtoken";
import { createPrivateKey } from "node:crypto";

const privateKeyPem = fs.readFileSync("./private_key.pem", "utf8");

// 私钥若加密则传 passphrase；未加密可省略
const privateKey = createPrivateKey({
  key: privateKeyPem,
  passphrase: process.env.HEZOR2_HEADER_PK_PASSWORD || undefined,
});

const now = Math.floor(Date.now() / 1000);
const payload = {
  subject: "鮨大山",
  subject_code: "wdyl_001",
  caller_id: "user_123",
  data_coverage: "202401-202412",
  creation_slug: "single_store_profit_model",
  creation_name: "单店盈利模型",
  exp: now + 3600,
};

const metaInfoJwt = jwt.sign(payload, privateKey, {
  algorithm: "EdDSA",
});

// 请求头：X-META-INFO: metaInfoJwt
```

## 4. Java 示例（EdDSA）

推荐使用 Nimbus JOSE + JWT。

Maven 依赖：

```xml
<dependency>
  <groupId>com.nimbusds</groupId>
  <artifactId>nimbus-jose-jwt</artifactId>
  <version>9.40</version>
</dependency>
```

代码示例：

```java
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.Ed25519Signer;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.OctetKeyPair;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

import java.time.Instant;
import java.util.Date;

public class BuildMetaInfoJwt {
    public static void main(String[] args) throws Exception {
        // 建议从安全存储加载 Ed25519 私钥，并转换为 OctetKeyPair
        // 这里示例为完整 OKP JWK（包含私钥参数 d）
        String okpJwkJson = System.getenv("HEZOR2_ED25519_JWK");
        OctetKeyPair jwk = OctetKeyPair.parse(okpJwkJson);

        if (jwk.getCurve() != Curve.Ed25519) {
            throw new IllegalArgumentException("JWK must use Ed25519");
        }

        Instant now = Instant.now();
        JWTClaimsSet claims = new JWTClaimsSet.Builder()
                .claim("subject", "鮨大山")
                .claim("subject_code", "wdyl_001")
                .claim("caller_id", "user_123")
                .claim("data_coverage", "202401-202412")
                .claim("creation_slug", "single_store_profit_model")
                .claim("creation_name", "单店盈利模型")
                .expirationTime(Date.from(now.plusSeconds(3600)))
                .build();

        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.EdDSA)
                .type(JOSEObjectType.JWT)
                .build();

        SignedJWT signedJWT = new SignedJWT(header, claims);
        JWSSigner signer = new Ed25519Signer(jwk);
        signedJWT.sign(signer);

        String metaInfoJwt = signedJWT.serialize();
        System.out.println(metaInfoJwt);
    }
}
```

## 5. 常见错误检查

- 算法写错：必须是 `EdDSA`，不是 `RS256`/`HS256`
- 字段名写错：必须与 `MetaInfo` 一致
- `exp` 缺失或时间错误：容易导致服务端判定 token 过期
- 私钥格式不匹配：需使用 Ed25519 私钥
- 请求头名写错：必须是 `X-META-INFO`
