密码学基础算法(一)基于整数的欧几里得算法和扩展欧几里得算法

欧几里得算法

图片来源: 随便谷歌的一个图片
图片地址: https://jason-chen-1992.weebly.com/uploads/1/0/8/5/108557741/euclidean_3_orig.jpg

密码学基础系列:

  • (一) 基于整数的欧几里得算法和扩展欧几里得算法
  • (二) 扩展域 G F ( 2 n ) GF(2^n) GF(2n) 上的多项式运算
  • (三) 扩展域 G F ( 2 n ) GF(2^n) GF(2n) 上的欧几里得算法和扩展欧几里得算法


求两个整数的最大公约数,以及求整数的乘法逆元是密码学中最基本的算法。

1. 欧几里得算法求最大公约数

欧几里得算法(Euclidean Algorithm),即辗转相除算法,是求两个整数最大公约数(GCD, Greatest Common Devisor)最常用和高效的办法。

关于辗转相除的原理和证明这里不再赘述,推荐参考:

  1. 《数论概论》原书第3版,第5章,整除性与最大公因数
  2. 《密码编码学与网络安全》第七版,第2.2节,欧几里得算法
  3. 《深入浅出密码学》,第6.3.1节,欧几里得算法

以下是源码实现的递归和非递归版本。

1. 递归版本

/**
 * @description: 使用欧几里得算法(辗转相除)求最大公约数的递归版本
 * @param {int} a 整数a
 * @param {int} b 整数b
 * @return {*} 返回整数 (a, b) 的最大公约数
 */
int int_gcd(int a, int b)
{
    if (b == 0)
    {
        return a;
    }
    else
    {
        return int_gcd(b, a % b);
    }
}

2. 非递归版本

/**
 * @description: 使用欧几里得算法(辗转相除)求最大公约数的非递归版本
 * @param {int} a 整数a
 * @param {int} b 整数b
 * @return {*} 返回整数 (a, b) 的最大公约数
 */
int int_gcd(int a, int b)
{
    int t;

    while (b != 0)
    {
        t = a % b;
        a = b;
        b = t;
    }

    return a;
}

为了和后面基于多项式的欧几里得算法求最大公因式区分,这里将函数命名为int_gcd,前缀 int 表示 integer

2. 扩展欧几里得算法解线性方程 a x + b y = d = g c d ( a , b ) ax + by = d = gcd(a, b) ax+by=d=gcd(a,b)

扩展欧几里得算法(Extend Euclidean Algorithm)的最大用途就是用于计算整数的乘法逆元。

关于扩展欧几里得算法(EEA)的原理和和推导这里不再赘述,推荐参考:

  1. 《数论概论》原书第3版,第6章,线性方程与最大公因数
  2. 《密码编码学与网络安全》第七版,第2.3.6节,扩展的欧几里得算法
  3. 《深入浅出密码学》,第6.3.2节,扩展的欧几里得算法

以下的源码基于扩展欧几里得算法实现求解线性方程:
a x + b y = d = g c d ( a , b ) , 公式2.1 ax + by = d = gcd(a, b) \text{, 公式2.1} ax+by=d=gcd(a,b)公式2.1

以下是源码实现的递归和非递归版本。

1. 递归版本

/**
 * @description: 使用扩展欧几里得算法(EEA)计算等式 a x ia + b x ib = gcd(a, b) 中的系数 ia, ib 和 gcd(a, b), 递归版本, 推导和证明参考: https://blog.csdn.net/lincifer/article/details/49391175
 * @param {int} a 整数 a
 * @param {int} b 整数 b
 * @param {int} *ia 整数 a 的系数 ia
 * @param {int} *ib 整数 b 的系数 ib
 * @return {*} 返回整数 (a, b) 的最大公约数 gcd(a, b)
 */
int int_gcd_ex(int a, int b, int *ia, int *ib)
{
    int x, y;
    int q, r;

    if (b == 0)
    {
        *ia = 1;
        *ib = 0;

        return a;
    }

    r = int_gcd_ex(b, a % b, &x, &y);

    *ia = y;
    *ib = x - a / b * y;

    return r;
}

一般的书讲述扩展欧几里得算法(Extend Euclidean Algorithm)都是基于递推原理进行的。
关于递归(非递推)版本的推导和证明,请参考参考:

  • https://blog.csdn.net/lincifer/article/details/49391175
    扩展欧几里得算法的递归证明

2. 非递归版本

/**
 * @description: 使用扩展欧几里得算法: Extend Euclidean Algorithm (EEA) 计算等式 a x ia + b x ib = gcd(a, b) 中的 ia, ib 和 gcd(a, b)
 * @param {int} a 整数 a
 * @param {int} b 整数 b
 * @param {int} *ia 整数 a 的系数 ia
 * @param {int} *ib 整数 b 的系数 ib
 * @return {*} 返回整数 (a, b) 的最大公约数 gcd(a, b)
 */
int int_gcd_ex(int a, int b, int *ia, int *ib)
{
    int x, y, x0, y0, x1, y1;
    int q, r;

    /* 消除警告: "warning: ‘x’/‘y’ may be used uninitialized in this function" */
    x = y = 0;

    /* 初始化最初两项系数 */
    x0 = 1; y0 = 0;
    x1 = 0; y1 = 1;

    q = a / b;
    r = a % b;

    while (r != 0)
    {
        /* 计算当前项 x/y */
        x = x0 - q * x1;
        y = y0 - q * y1;

        /* 依次保存前两项到 x0/y0, x1/y1 */
        x0 = x1; x1 = x;
        y0 = y1; y1 = y;

        a = b;
        b = r; /* 前一次的余数 */

        q = a / b;
        r = a % b;
    }

    *ia = x;
    *ib = y;

    return b;
}

函数int_gcd_ex的返回值和int_gcd一样,都返回其参数 ab 的最大公约数,不同的是,除了返回最大公约数之外,还通过指针参数返回线性方程 2.1 中的 xy值。

二者的函数原型如下:

int int_gcd(int a, int b);
int int_gcd_ex(int a, int b, int *x, int *y);

3. 求整数的乘法逆元

在第2节的公式 2.1 中,当 g c d ( a , b ) = 1 gcd(a, b) = 1 gcd(a,b)=1 时,有:
a x + b y = 1 = g c d ( a , b ) ax + by = 1 = gcd(a, b) ax+by=1=gcd(a,b)

等式两边同时 m o d   b mod\ b mod b,有: a x   m o d   b + b y   m o d   b ≡ 1   m o d   b ax\ mod\ b + by\ mod\ b \equiv 1\ mod\ b ax mod b+by mod b1 mod b,而 b y   m o d   b = 0 by\ mod\ b = 0 by mod b=0
所以:
a x ≡ 1   m o d   b ax \equiv 1\ mod\ b ax1 mod b
因此,a 和 x 在模b 的前提下互为逆元。

第2节的函数int_gcd_ex在求整数 a 和整数 b 最大公约数的同事,可以顺带求出 a 和 b 的乘法逆元。

以下代码复用int_gcd_ex,求整数 a 基于整数 b 的乘法逆元:

/**
 * @description: 基于扩展欧几里得算法计算整数 a 关于 整数 b 的乘法逆元
 * @param {int} a 整数 a
 * @param {int} b 整数 b
 * @return {*} 返回整数 a 关于整数 b 的最小正整数乘法逆元, 乘法逆元不存在(gcd(a, b) != 1)则返回 0;
 */
int int_inv(int a, int b)
{
    int res, ia, ib;

    res = int_gcd_ex(a, b, &ia, &ib);

    /* gcd(a, b) != 1, 没有乘法逆元 */
    if (res != 1)
    {
        return 0;
    }

    /* 调整小于 0 的情况 */
    if (ia < 0)
    {
        ia += b;
    }

    return ia;
}

这里的函数名是integer inverse的简写,如果整数 a 和整数 b 的最大公约数为1(互素),则返回整数 a 关于整数 b 的最小正整数逆元,如果不能满足这个条件,则函数返回 0。

上面的代码中,在求乘法逆元时,通过扩展欧几里得算法int_gcd_ex同时得到了整数 a 和整数 b 的逆元 ia 和 ib,但整数 b 的逆元 ib 并没有被使用,所以可以在int_inv函数中将int_gcd_ex的代码展开,去掉所有整数 b 逆元计算相关代码,得到的新的代码如下:

/**
 * @description: 基于扩展欧几里得算法计算整数 a 关于 整数 b 的乘法逆元(展开 int_gcd_ex 后的优化版本)
 * @param {int} a 整数 a
 * @param {int} b 整数 b
 * @return {*} 返回整数 a 关于整数 b 的最小正整数乘法逆元, 乘法逆元不存在(gcd(a, b) != 1)则返回 0;
 */
int int_inv(int a, int b)
{
    int x, x0, x1;
    int q, r, t;

    /* 消除警告: "warning: ‘x’/‘y’ may be used uninitialized in this function" */
    x = 0;

    /* 初始化最初两项系数 */
    x0 = 1;
    x1 = 0;

    /* 临时保存 b */
    t = b;

    q = a / b;
    r = a % b;

    while (r != 0)
    {
        /* 计算当前项 x */
        x = x0 - q * x1;

        /* 依次保存前两项到 x0, x1 */
        x0 = x1; x1 = x;

        a = b;
        b = r; /* 前一次的余数 */

        q = a / b;
        r = a % b;
    }

    /* gcd(a, b) != 1, 没有乘法逆元 */
    if (b != 1)
    {
        return 0;
    }

    /* 调整小于 0 的情况 */
    if (x < 0)
    {
        x += t;
    }

    return x;
}

优化的内容主要是减少整数 b 计算乘法逆元所使用相关变量 y, y0, y1 的赋值,减少这 3 个变量所占用的空间。实际加解密中,求逆元的代码并不会非常频繁被调用,因此这个优化带来的效率提升我认为比较有限。

相比之下,我还是更喜欢调用int_gcd_ex求乘法逆元的版本,因为这个版本代码更简洁,思路更清晰。

4. 完整代码和测试

4.1 源码

  • 头文件gcd.h:
/*
 * @        file: gcd.h
 * @ description: 整数和扩展域上多项式的最大公约数(gcd), 乘法逆元(multiple reverse)的计算
 * @      author: Yongqiang Gu
 * @        blog: https://blog.csdn.net/guyongqiangx
 */
#ifndef __GCD_H__
#define __GCD_H__
#ifdef __cplusplus
extern "C"
{
#endif

/**
 * @description: 使用欧几里得算法(辗转相除)求最大公约数
 * @param {int} a 整数a
 * @param {int} b 整数b
 * @return {*} 返回整数 (a, b) 的最大公约数
 */
int int_gcd(int a, int b);

/**
 * @description: 使用扩展欧几里得算法: Extend Euclidean Algorithm (EEA) 计算等式 a x ia + b x ib = gcd(a, b) 中的 ia, ib 和 gcd(a, b)
 * @param {int} a 整数 a
 * @param {int} b 整数 b
 * @param {int} *ia 整数 a 的系数 ia
 * @param {int} *ib 整数 b 的系数 ib
 * @return {*} 返回整数 (a, b) 的最大公约数 gcd(a, b)
 */
int int_gcd_ex(int a, int b, int *ia, int *ib);

/**
 * @description: 基于扩展欧几里得算法计算整数 a 关于 整数 b 的乘法逆元
 * @param {int} a 整数 a
 * @param {int} b 整数 b
 * @return {*} 返回整数 a 关于整数 b 的最小正整数乘法逆元, 乘法逆元不存在(gcd(a, b) != 1)则返回 0;
 */
int int_inv(int a, int b);

#ifdef __cplusplus
}
#endif
#endif
  • 源码文件int_gcd.c:
/*
 * @        file: int_gcd.c
 * @ description: 整数的最大公约数(欧几里得算法 Euclidean Algorithm),和扩展欧几里得算法(Extend Euclidean Algorithm)求乘法逆元的实现
 * @      author: Yongqiang Gu
 * @        blog: https://blog.csdn.net/guyongqiangx
 */
#include "gcd.h"

///**
// * @description: 使用欧几里得算法(辗转相除)求最大公约数的递归版本
// * @param {int} a 整数a
// * @param {int} b 整数b
// * @return {*} 返回整数 (a, b) 的最大公约数
// */
//int int_gcd(int a, int b)
//{
//    if (b == 0)
//    {
//        return a;
//    }
//    else
//    {
//        return int_gcd(b, a % b);
//    }
//}


/**
 * @description: 使用欧几里得算法(辗转相除)求最大公约数的非递归版本
 * @param {int} a 整数a
 * @param {int} b 整数b
 * @return {*} 返回整数 (a, b) 的最大公约数
 */
int int_gcd(int a, int b)
{
    int t;

    while (b != 0)
    {
        t = a % b;
        a = b;
        b = t;
    }

    return a;
}

///**
// * @description: 使用扩展欧几里得算法(EEA)计算等式 a x ia + b x ib = gcd(a, b) 中的系数 ia, ib 和 gcd(a, b), 递归版本, 推导和证明参考: https://blog.csdn.net/lincifer/article/details/49391175
// * @param {int} a 整数 a
// * @param {int} b 整数 b
// * @param {int} *ia 整数 a 的系数 ia
// * @param {int} *ib 整数 b 的系数 ib
// * @return {*} 返回整数 (a, b) 的最大公约数 gcd(a, b)
// */
//int int_gcd_ex(int a, int b, int *ia, int *ib)
//{
//    int x, y;
//    int q, r;
//
//    if (b == 0)
//    {
//        *ia = 1;
//        *ib = 0;
//
//        return a;
//    }
//
//    r = int_gcd_ex(b, a % b, &x, &y);
//
//    *ia = y;
//    *ib = x - a / b * y;
//
//    return r;
//}

/**
 * @description: 使用扩展欧几里得算法(EEA) 计算等式 a x ia + b x ib = gcd(a, b) 中的系数 ia, ib 和 gcd(a, b), 非递归版本
 * @param {int} a 整数 a
 * @param {int} b 整数 b
 * @param {int} *ia 整数 a 的系数 ia
 * @param {int} *ib 整数 b 的系数 ib
 * @return {*} 返回整数 (a, b) 的最大公约数 gcd(a, b)
 */
int int_gcd_ex(int a, int b, int *ia, int *ib)
{
    int x, y, x0, y0, x1, y1;
    int q, r;

    /* 消除警告: "warning: ‘x’/‘y’ may be used uninitialized in this function" */
    x = y = 0;

    /* 初始化最初两项系数 */
    x0 = 1; y0 = 0;
    x1 = 0; y1 = 1;

    q = a / b;
    r = a % b;

    while (r != 0)
    {
        /* 计算当前项 x/y */
        x = x0 - q * x1;
        y = y0 - q * y1;

        /* 依次保存前两项到 x0/y0, x1/y1 */
        x0 = x1; x1 = x;
        y0 = y1; y1 = y;

        a = b;
        b = r; /* 前一次的余数 */

        q = a / b;
        r = a % b;
    }

    *ia = x;
    *ib = y;

    return b;
}

///**
// * @description: 基于扩展欧几里得算法计算整数 a 关于 整数 b 的乘法逆元
// * @param {int} a 整数 a
// * @param {int} b 整数 b
// * @return {*} 返回整数 a 关于整数 b 的最小正整数乘法逆元, 乘法逆元不存在(gcd(a, b) != 1)则返回 0;
// */
//int int_inv(int a, int b)
//{
//    int res, ia, ib;
//
//    res = int_gcd_ex(a, b, &ia, &ib);
//
//    /* gcd(a, b) != 1, 没有乘法逆元 */
//    if (res != 1)
//    {
//        return 0;
//    }
//
//    /* 调整小于 0 的情况 */
//    if (ia < 0)
//    {
//        ia += b;
//    }
//
//    return ia;
//}

/**
 * @description: 基于扩展欧几里得算法计算整数 a 关于 整数 b 的乘法逆元(展开 int_gcd_ex 后的优化版本)
 * @param {int} a 整数 a
 * @param {int} b 整数 b
 * @return {*} 返回整数 a 关于整数 b 的最小正整数乘法逆元, 乘法逆元不存在(gcd(a, b) != 1)则返回 0;
 */
int int_inv(int a, int b)
{
    int x, x0, x1;
    int q, r, t;

    /* 消除警告: "warning: ‘x’ may be used uninitialized in this function" */
    x = 0;

    /* 初始化最初两项系数 */
    x0 = 1;
    x1 = 0;

    /* 临时保存 b */
    t = b;

    q = a / b;
    r = a % b;

    while (r != 0)
    {
        /* 计算当前项 x */
        x = x0 - q * x1;

        /* 依次保存前两项到 x0, x1 */
        x0 = x1; x1 = x;

        a = b;
        b = r; /* 前一次的余数 */

        q = a / b;
        r = a % b;
    }

    /* gcd(a, b) != 1, 没有乘法逆元 */
    if (b != 1)
    {
        return 0;
    }

    /* 调整小于 0 的情况 */
    if (x < 0)
    {
        x += t;
    }

    return x;
}

4.2 基于gtest的单元测试

  • 测试代码

基于gtest的单元测试int_gcd_unittest.cc:

// g++ int_gcd.c int_gcd_unittest.cc -o int_gcd -Igtest/include -Lgtest/lib -lgtest_main -lgtest -lpthread
#include "gcd.h"
#include <gtest/gtest.h>

TEST(IntegerGCDTest, CompositeTest)
{
    /* gcd(60, 0) = 60, gcd(0, 20) = 20 */
    EXPECT_EQ(60, int_gcd(60,  0));
    EXPECT_EQ(20, int_gcd( 0, 20));

    EXPECT_EQ(12, int_gcd(60, 24));
    EXPECT_EQ(11, int_gcd(55, 22));
    EXPECT_EQ( 3, int_gcd(33, 18));
    EXPECT_EQ( 3, int_gcd(27, 21));
    EXPECT_EQ( 6, int_gcd(84, 30));
    EXPECT_EQ(10, int_gcd(50, 30));
    EXPECT_EQ( 7, int_gcd(973, 301));

    EXPECT_EQ(77, int_gcd(7469, 2464));
    EXPECT_EQ(34, int_gcd(24140, 16762));
    EXPECT_EQ(35, int_gcd(4655, 12075));
    EXPECT_EQ(1078, int_gcd(1160718174, 316258250));
}

TEST(IntegerGCDTest, PrimeTest)
{
    EXPECT_EQ(1, int_gcd(67, 12));
    EXPECT_EQ(1, int_gcd(29, 100));
    EXPECT_EQ(1, int_gcd(1759, 550));
    EXPECT_EQ(1, int_gcd(2689, 4001));
}

TEST(IntegerExtEuclideanTest, GCDTest)
{
    int ia, ib;

    EXPECT_EQ(3, int_gcd_ex(33, 18, &ia, &ib));
    EXPECT_EQ(3, int_gcd_ex(18, 33, &ia, &ib));

    EXPECT_EQ(1, int_gcd_ex(100, 29, &ia, &ib));
    EXPECT_EQ(1, int_gcd_ex(29, 100, &ia, &ib));

    EXPECT_EQ(10, int_gcd_ex(50, 30, &ia, &ib));

    EXPECT_EQ(1, int_gcd_ex(1759, 550, &ia, &ib));
}

TEST(IntegerExtEuclideanTest, InverseTest)
{
    int res, ia, ib;

    /* 198 x (-11) + 243 x 9 = 9 = gcd(198, 243) */
    res = int_gcd_ex(198, 243, &ia, &ib);
    EXPECT_EQ(  9, res);
    EXPECT_EQ(-11, ia);
    EXPECT_EQ(  9, ib);

    /* 1819 x 71 + 3587 x (-36) = 17 = gcd(1819, 3587) */
    res = int_gcd_ex(1819, 3587, &ia, &ib);
    EXPECT_EQ( 17, res);
    EXPECT_EQ( 71, ia);
    EXPECT_EQ(-36, ib);

    res = int_gcd_ex(100, 29, &ia, &ib);
    EXPECT_EQ(  1, res);
    EXPECT_EQ(  9, ia);
    EXPECT_EQ(-31, ib);

    res = int_gcd_ex(12, 67, &ia, &ib);
    EXPECT_EQ( 1, res);
    EXPECT_EQ(28, ia);
    EXPECT_EQ(-5, ib);

    res = int_gcd_ex(973, 301, &ia, &ib);
    EXPECT_EQ(  7, res);
    EXPECT_EQ( 13, ia);
    EXPECT_EQ(-42, ib);

    res = int_gcd_ex(1234, 4321, &ia, &ib);
    EXPECT_EQ(    1, res);
    EXPECT_EQ(-1082, ia);
    EXPECT_EQ(  309, ib);

    res = int_gcd_ex(24140, 40902, &ia, &ib);
    EXPECT_EQ(  34, res);
    EXPECT_EQ(-571, ia);
    EXPECT_EQ(337, ib);

    res = int_gcd_ex(1759, 550, &ia, &ib);
    EXPECT_EQ(   1, res);
    EXPECT_EQ(-111, ia);
    EXPECT_EQ( 355, ib);

    res = int_gcd_ex(550, 1769, &ia, &ib);
    EXPECT_EQ(   1, res);
    EXPECT_EQ( 550, ia);
    EXPECT_EQ(-171, ib);
}

TEST(IntegerInverseTest, InverseTest)
{
    /* gcd(33, 18) = 3, no inverse */
    EXPECT_EQ(0, int_inv(33, 18));
    /* gcd(30, 50) = 10, no inverse */
    EXPECT_EQ(0, int_inv(30, 50));

    /* gcd(12, 67) = 1, 12 x 28 mod 67 = 1 */
    EXPECT_EQ(28, int_inv(12, 67));

    /* gcd(1759, 550) = 1, 1759 x 439 mod 550 = 1 */
    EXPECT_EQ(439, int_inv(1759, 550));
    EXPECT_EQ(355, int_inv(550, 1759));

    EXPECT_EQ(3239, int_inv(1234, 4321));
    EXPECT_EQ(0, int_inv(24140, 40902));
    EXPECT_EQ(550, int_inv(550, 1769));

    EXPECT_EQ(69, int_inv(29, 100));
    EXPECT_EQ(9, int_inv(100, 29));
}
  • 测试结果
[==========] Running 5 tests from 3 test suites.
[----------] Global test environment set-up.
[----------] 2 tests from IntegerGCDTest
[ RUN      ] IntegerGCDTest.CompositeTest
[       OK ] IntegerGCDTest.CompositeTest (0 ms)
[ RUN      ] IntegerGCDTest.PrimeTest
[       OK ] IntegerGCDTest.PrimeTest (0 ms)
[----------] 2 tests from IntegerGCDTest (0 ms total)

[----------] 2 tests from IntegerExtEuclideanTest
[ RUN      ] IntegerExtEuclideanTest.GCDTest
[       OK ] IntegerExtEuclideanTest.GCDTest (0 ms)
[ RUN      ] IntegerExtEuclideanTest.InverseTest
[       OK ] IntegerExtEuclideanTest.InverseTest (0 ms)
[----------] 2 tests from IntegerExtEuclideanTest (0 ms total)

[----------] 1 test from IntegerInverseTest
[ RUN      ] IntegerInverseTest.InverseTest
[       OK ] IntegerInverseTest.InverseTest (0 ms)
[----------] 1 test from IntegerInverseTest (0 ms total)

[----------] Global test environment tear-down
[==========] 5 tests from 3 test suites ran. (0 ms total)
[  PASSED  ] 5 tests.

5. 其它

洛奇工作中常常会遇到自己不熟悉的问题,这些问题可能并不难,但因为不了解,找不到人帮忙而瞎折腾,往往导致浪费几天甚至更久的时间。

所以我组建了几个微信讨论群(记得微信我说加哪个群,如何加微信见后面),欢迎一起讨论:

  • 一个密码编码学讨论组,主要讨论各种加解密,签名校验等算法,请说明加密码学讨论群。
  • 一个Android OTA的讨论组,请说明加Android OTA群。
  • 一个git和repo的讨论组,请说明加git和repo群。

在工作之余,洛奇尽量写一些对大家有用的东西,如果洛奇的这篇文章让您有所收获,解决了您一直以来未能解决的问题,不妨赞赏一下洛奇,这也是对洛奇付出的最大鼓励。扫下面的二维码赞赏洛奇,金额随意:

收钱码

洛奇自己维护了一个公众号“洛奇看世界”,一个很佛系的公众号,不定期瞎逼逼。公号也提供个人联系方式,一些资源,说不定会有意外的收获,详细内容见公号提示。扫下方二维码关注公众号:

公众号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洛奇看世界

一分也是爱~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值