第18课:ECC 标量乘

阶段三非对称密码 — 标量乘 k·P 是 ECC 最核心也最耗时的运算。它将点加和点加倍组合,类似于 RSA 的模幂。

1. 标量乘算法

标量乘 k·P 是将点 P 自加 k 次。直接方法太慢,使用双-and-add算法:

输入:标量 k,点 P
1. R = O(无穷远点)
2. for i = MSB down to 0:
3.   R = 2R(点加倍)
4.   if k[i] == 1: R = R + P(点加)
5. return R

这个算法与 RSA 的平方-乘算法结构完全相同!

2. 标量乘硬件实现

✅Verilator验证通过
// ecc_scalar_mul.v - ECC 标量乘模块
module ecc_scalar_mul #(
    parameter WIDTH = 256
)(
    input  wire              clk,
    input  wire              rst_n,
    input  wire              start,
    input  wire [WIDTH-1:0]  scalar,      // 标量 k
    input  wire [WIDTH-1:0]  px, py,      // 基点 P
    input  wire [WIDTH-1:0]  a,           // 曲线参数
    input  wire [WIDTH-1:0]  modulus,      // 素数 p
    output reg  [WIDTH-1:0]  rx, ry,      // 结果 k*P
    output reg               valid,
    output reg               busy
);

    localparam S_IDLE = 3'd0, S_DBL = 3'd1,
               S_ADD = 3'd2, S_NEXT = 3'd3, S_DONE = 3'd4;

    reg [2:0] state;
    reg [WIDTH-1:0] acc_x, acc_y;  // 累积点
    reg [WIDTH-1:0] k_reg;
    reg [$clog2(WIDTH+1)-1:0] bit_idx;
    reg first_point;  // 是否第一个点

    // 点加/点加倍实例
    reg        pa_start;
    reg        pa_dbl;
    reg [WIDTH-1:0] pa_px, pa_py, pa_qx, pa_qy;
    wire [WIDTH-1:0] pa_rx, pa_ry;
    wire       pa_valid;

    ecc_point_add #(.WIDTH(WIDTH)) u_pa (
        .clk(clk), .rst_n(rst_n), .start(pa_start),
        .px(pa_px), .py(pa_py),
        .qx(pa_qx), .qy(pa_qy),
        .a(a), .modulus(modulus), .dbl(pa_dbl),
        .rx(pa_rx), .ry(pa_ry), .valid(pa_valid)
    );

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= S_IDLE; rx <= 0; ry <= 0;
            valid <= 0; busy <= 0; pa_start <= 0;
            acc_x <= 0; acc_y <= 0;
            k_reg <= 0; bit_idx <= 0;
            first_point <= 1;
        end else begin
            pa_start <= 0;

            case (state)
                S_IDLE: begin
                    if (start) begin
                        k_reg <= scalar;
                        bit_idx <= WIDTH - 1;  // 从 MSB 开始
                        first_point <= 1;
                        busy <= 1;
                        valid <= 0;
                        state <= S_DBL;
                    end
                end
                S_DBL: begin
                    if (first_point) begin
                        // 第一个1位:直接设置 acc = P
                        if (k_reg[WIDTH-1]) begin
                            acc_x <= px;
                            acc_y <= py;
                            first_point <= 0;
                        end
                        k_reg <= k_reg << 1;
                        bit_idx <= bit_idx - 1;
                        if (bit_idx == 0)
                            state <= S_DONE;
                        else
                            state <= S_NEXT;
                    end else begin
                        // 点加倍: acc = 2*acc
                        pa_px <= acc_x; pa_py <= acc_y;
                        pa_dbl <= 1;
                        pa_start <= 1;
                        state <= S_ADD;
                    end
                end
                S_ADD: begin
                    if (pa_valid) begin
                        acc_x <= pa_rx;
                        acc_y <= pa_ry;
                        if (k_reg[WIDTH-1]) begin
                            // 点加: acc = acc + P
                            pa_px <= pa_rx; pa_py <= pa_ry;
                            pa_qx <= px; pa_qy <= py;
                            pa_dbl <= 0;
                            pa_start <= 1;
                            state <= S_NEXT;
                        end else begin
                            k_reg <= k_reg << 1;
                            bit_idx <= bit_idx - 1;
                            if (bit_idx == 0)
                                state <= S_DONE;
                            else
                                state <= S_DBL;
                        end
                    end
                end
                S_NEXT: begin
                    if (pa_valid) begin
                        acc_x <= pa_rx;
                        acc_y <= pa_ry;
                        k_reg <= k_reg << 1;
                        bit_idx <= bit_idx - 1;
                        if (bit_idx == 0)
                            state <= S_DONE;
                        else
                            state <= S_DBL;
                    end
                end
                S_DONE: begin
                    rx <= acc_x;
                    ry <= acc_y;
                    valid <= 1;
                    busy <= 0;
                    state <= S_IDLE;
                end
            endcase
        end
    end

endmodule

3. 标量乘的侧信道弱点

双-and-add 算法的关键路径与标量 k 的 Hamming 权重相关——1 位多则多点加操作。这种时间差异泄露了 k 的信息。

3.1 恒定时间标量乘

无论 k 的位值如何,都执行点加倍和点加:

// 恒定时间双-and-add
for i = MSB down to 0:
    Q = 2Q                    // 总是加倍
    T = Q + P                 // 总是计算 Q+P
    if k[i] == 1: Q = T      // 条件赋值(不是条件计算!)
    else:        Q = Q        // 保持原值

4. NIST 推荐曲线参数

曲线p 位数应用
secp160r1160受限设备
secp256r1 (P-256)256TLS, 证书
secp256k1256比特币
secp384r1 (P-384)384高安全场景
secp521r1 (P-521)521最高安全

1. 在 secp256k1 上计算 2G, 3G, 4G,验证结果与标准参考值一致。

2. 实现恒定时间标量乘,确保无论 k 值如何,执行周期数完全相同。

3. 实现窗口法标量乘(wNAF):预处理 2P, 3P, ..., (2ʷ-1)P,减少点加次数。

4. 计算一次 secp256r1 标量乘需要多少次模乘(雅可比坐标)。

🏆 成就解锁:非对称密码大师

你已完成 ECC 标量乘的硬件实现,理解了双-and-add 算法和恒定时间防护。阶段三完成,你已掌握 RSA 和 ECC 两大公钥密码体系!

获得徽章:🏆 ASYMMETRIC_MASTER

💡 扩展阅读与参考资源

🔧 实践环境搭建

推荐使用以下工具链进行课程实践:

# 安装 Verilator
sudo apt install verilator

# 安装 Icarus Verilog(可选)
sudo apt install iverilog

# 安装 GTKWave(波形查看器)
sudo apt install gtkwave

# 验证安装
verilator --lint-only --version
iverilog -V

📊 性能指标对比

密码学硬件实现的关键性能指标:

这些指标之间通常存在 trade-off,设计时需根据应用场景权衡。

📚 本课知识图谱

本课涉及的核心概念和技术关系:

💡 调试技巧

Verilog 仿真调试的常用方法:

// 调试示例
initial begin
    $dumpfile("sim.vcd");
    $dumpvars(0, uut);
end

// 断言验证
assert property (@(posedge clk) valid |-> data !== 'x)
    else $error("Invalid data when valid!");

🔧 Verilator 编译仿真完整流程

# 1. 语法检查
verilator --lint-only module.v

# 2. 创建 C++ 测试主函数
cat > sim_main.cpp << 'EOF'
#include "Vmodule.h"
#include "verilated.h"
int main(int argc, char** argv) {
    Verilated::commandArgs(argc, argv);
    Vmodule* top = new Vmodule;
    top->clk = 0; top->rst_n = 0;
    top->eval();
    top->rst_n = 1;
    for (int i = 0; i < 100; i++) {
        top->clk = !top->clk;
        top->eval();
    }
    delete top;
    return 0;
}
EOF

# 3. 编译
verilator -cc module.v --exe sim_main.cpp
make -C obj_dir -f Vmodule.mk

# 4. 运行
./obj_dir/Vmodule

📖 推荐阅读

⚖️ 性能评估框架

密码硬件的性能评估维度:

指标单位说明
面积GE / LUT等效门数或查找表数量
频率MHz最大时钟频率
吞吐量Gbps每秒处理的数据量
延迟周期数从输入到输出的周期
能效pJ/bit每比特能耗
面积效率Gbps/GE单位面积吞吐量

不同应用场景对指标优先级不同:IoT 偏重面积和能效,服务器偏重吞吐量。

📚 本课知识图谱

本课涉及的核心概念和技术关系:

💡 调试技巧

Verilog 仿真调试的常用方法:

// 调试示例
initial begin
    $dumpfile("sim.vcd");
    $dumpvars(0, uut);
end

// 断言验证
assert property (@(posedge clk) valid |-> data !== 'x)
    else $error("Invalid data when valid!");

🔧 Verilator 编译仿真完整流程

# 1. 语法检查
verilator --lint-only module.v

# 2. 创建 C++ 测试主函数
cat > sim_main.cpp << 'EOF'
#include "Vmodule.h"
#include "verilated.h"
int main(int argc, char** argv) {
    Verilated::commandArgs(argc, argv);
    Vmodule* top = new Vmodule;
    top->clk = 0; top->rst_n = 0;
    top->eval();
    top->rst_n = 1;
    for (int i = 0; i < 100; i++) {
        top->clk = !top->clk;
        top->eval();
    }
    delete top;
    return 0;
}
EOF

# 3. 编译
verilator -cc module.v --exe sim_main.cpp
make -C obj_dir -f Vmodule.mk

# 4. 运行
./obj_dir/Vmodule

📖 推荐阅读

⚖️ 性能评估框架

密码硬件的性能评估维度:

指标单位说明
面积GE / LUT等效门数或查找表数量
频率MHz最大时钟频率
吞吐量Gbps每秒处理的数据量
延迟周期数从输入到输出的周期
能效pJ/bit每比特能耗
面积效率Gbps/GE单位面积吞吐量

不同应用场景对指标优先级不同:IoT 偏重面积和能效,服务器偏重吞吐量。