椭圆曲线点压缩算法

时间:2013-06-18 14:28:53

标签: javascript cryptography elliptic-curve

我使用Javascript生成椭圆曲线,以便在加密消息传递应用中使用基于此示例代码http://www-cs-students.stanford.edu/~tjw/jsbn/ecdh.html

公钥非常大,我知道可以压缩它们,但我一直无法找到Javascript或大纲算法来执行此操作。这是一篇概述数学的文章http://nmav.gnutls.org/2012/01/do-we-need-elliptic-curve-point.html

5 个答案:

答案 0 :(得分:5)

我认为他们会对JavaScript椭圆曲线点压缩解决方案产生兴趣,WebCrypto支持过滤到浏览器中。
我将使用NIST曲线作为示例,因为这些是我在将压缩的公钥导入WebCrypto时必须处理的。

Curves and their primes
NIST P-256 (secp256r1) 2^256 - 2^224 + 2^192 + 2^96 - 1
NIST P-384 (secp384r1) 2^384 - 2^128 - 2^96 + 2^32 - 1
NIST P-521 (secp521r1) 2^521 - 1

这些素数都满足等式,p mod 4 === 3
这意味着您可以跳过有些复杂的通用目的Tonelli-Shanks algorithm,并使用简单的标识来查找平方根。

首先,点压缩的压缩部分'非常简单。记录Y的符号,然后丢弃Y的值。

/**
 * Point compress elliptic curve key
 * @param {Uint8Array} x component
 * @param {Uint8Array} y component
 * @return {Uint8Array} Compressed representation
 */
function ECPointCompress( x, y )
{
    const out = new Uint8Array( x.length + 1 );

    out[0] = 2 + ( y[ y.length-1 ] & 1 );
    out.set( x, 1 );

    return out;
}

解压缩包括查找平方根,然后根据Y奇偶校验位进行校正。 此函数依赖于JavaScript big integer library,它公开以下函数:add,sub,multiply,pow,modPow。

// Consts for P256 curve. Adjust accordingly
const two = new bigInt(2),
// 115792089210356248762697446949407573530086143415290314195533631308867097853951
prime = two.pow(256).sub( two.pow(224) ).add( two.pow(192) ).add( two.pow(96) ).sub(1),
b = new bigInt( '41058363725152142129326129780047268409114441015993725554835256314039467401291' ),
// Pre-computed value, or literal
pIdent = prime.add(1).divide(4); // 28948022302589062190674361737351893382521535853822578548883407827216774463488


/**
 * Point decompress NIST curve
 * @param {Uint8Array} Compressed representation
 * @return {Object} Explicit x & y
 */
function ECPointDecompress( comp )
{
    const signY = comp[0] - 2, // This value must be 2 or 3. 4 indicates an uncompressed key, and anything else is invalid.
    x = comp.subarray(1),
    // Import x into bigInt library
    xBig = new bigInt( x );

    // y^2 = x^3 - 3x + b
    var yBig = xBig.pow(3).sub( xBig.multiply(3) ).add( b ).modPow( pIdent, prime );

    // If the parity doesn't match it's the *other* root
    if( yBig.mod(2) !== signY )
    {
        // y = prime - y
        yBig = prime.sub( yBig );
    }

    return {
        x: x,
        y: yBig.toUint8Array()
    };
}

答案 1 :(得分:0)

椭圆曲线点的压缩由Certicom获得专利,因此一般情况下,您不应在没有许可证的情况下使用它。

更新:根据denis bider的评论,Certicom的专利于2014年到期。

答案 2 :(得分:0)

单独申请数学公式是很困难的。使用二次方程y = sqrt(x ^ 3 + ax + b)不能获得专利,如果是,则无法保护。人们当然可以从Diophantes(公元200-298)声称现有技术。关于霍尔猜想(大约1971年)的工作,关于正方形和立方体之间的最小绝对差异| y ^ 2 - x ^ 3 |很少小于x。重写它y ^ 2 - x ^ 3 = ax-b,其中x <1。 b / a并说明模块化组中的解决方案有助于减少整数中的强力搜索量。

获得专利的是有助于找出y符号的位。根据您所查看的标准,该位可用于区分(y和M-y)的最大解,或来自(y,-y)的正解。

但由于该专利已被接受,您需要寻求法律建议。

作为一名精通艺术技能的着名密码学家Dan Bernstein博士明智地指出(http://cr.yp.to/ecdh/patents.html),在米勒1986中提到了从x重新计算y的想法,这是减少椭圆形足迹的一个小问题。基于记忆的系统中的曲线点。

如果您不使用正常基准作为点坐标的表示,或者在gf(p)中使用ecc,或者如果您使用专利权利要求的律师可以帮助您评估专利是否仍适用不要用一点来重新编码y的压缩值,例如当选择随机性k时,P1(x1,y1)和计算P2(x2,y2)= [k] P1直到tr(y1)== tr(y2)消除了模糊性(有点CPU成本,但为什么不呢? )。

或者,您可以指出二次公式的分辨率显然比通信信道上保存的少量比特更昂贵,并且该专利根本没用,甚至通过建议替换6微微瓦来破坏环境传输成本为2 miliwatts的CPU成本(可接受的是,专利提交必须是新的,非平凡的和有用的)。然后你会找到一位歪歪扭扭的律师,最好是在加利福尼亚州,肯定会有一位当地法官判给你几十亿美元赔偿因环境造成的损失,因为你的编程技巧和你的钱包造成的困扰在你的宝贵申请的发布。

另外,正如另一篇文章所建议的那样,您可以寻求许可解决方案。

如果您的申请是针对美国政府的,那么您需要另一位律师来评估该专利以及您计划使用它的方式是否已经是NSA在算法“套件B”环境中购买的许可证的一部分,在这种情况下,许可证已经可以由美国人民支付。

答案 3 :(得分:0)

不幸的是,来自@Adria的上述ECPointDecompress

代码已经过时,它使用的是JavaScript big integer library的很旧的版本

我用big integer library latest version 1.6.36重写了ECPointDecompress,它可以直接使用十六进制字符串,不需要使用Uint8Array

const bigInt = require("big-integer");

// Consts for P256 curve. Adjust accordingly
const two = new bigInt(2),
// 115792089210356248762697446949407573530086143415290314195533631308867097853951
prime = two.pow(256).subtract( two.pow(224) ).add( two.pow(192) ).add( two.pow(96) ).subtract(1),
b = new bigInt( '41058363725152142129326129780047268409114441015993725554835256314039467401291' ),
// Pre-computed value, or literal
// 28948022302589062190674361737351893382521535853822578548883407827216774463488
pIdent = prime.add(1).divide(4);

function pad_with_zeroes(number, length) {
    var retval = '' + number;
    while (retval.length < length) {
        retval = '0' + retval;
    }
    return retval;
}

/**
 * Point decompress NIST curve
 * @param {string} Compressed representation in hex string
 * @return {string} Uncompressed representation in hex string
 */
function ECPointDecompress( comp ) {
    var signY = new Number(comp[1]) - 2;
    var x = new bigInt(comp.substring(2), 16);
    // y^2 = x^3 - 3x + b
    var y = x.pow(3).subtract( x.multiply(3) ).add( b ).modPow( pIdent, prime );
    // If the parity doesn't match it's the *other* root
    if( y.mod(2).toJSNumber() !== signY ) {
        // y = prime - y
        y = prime.subtract( y );
    }
    return '04' + pad_with_zeroes(x.toString(16), 64) + pad_with_zeroes(y.toString(16), 64);
}

这可用于将compressed public key (ECDSA public key)转换为65字节的未压缩公共密钥,但请注意,比特币的压缩公共密钥是使用secp256k1 curve创建的,这与我们使用的曲线不同此代码:NIST P-256(secp256r1)2 ^ 256-2 ^ 224 + 2 ^ 192 + 2 ^ 96-1

因此,以上代码无法用于转换比特币的压缩公共密钥,而是使用secp256k1 curve consts寻找比特币压缩的公共密钥的转换器,请参考https://stackoverflow.com/a/53480175/5630352

示例:

ECPointDecompress('030ce2995c738e2320a5dea2df51b99d88bc5dd38356ba72e51ecc0ca660ca4593')

返回:'040ce2995c738e2320a5dea2df51b99d88bc5dd38356ba72e51ecc0ca660ca45936215a67f6e3fa1d72f6ef46aa2b7481991427b8764ff90447c6215d8dc931773'

ECPointDecompress('0267bc6cae41a4579cda2556818bc942a38321cad961028bc74459f36ddca71e0e')

返回:'0467bc6cae41a4579cda2556818bc942a38321cad961028bc74459f36ddca71e0e7c52f0e9f82bd1b2ba81935ba125cb1030d1ade1bd0306b3579a951418b858e8'

答案 4 :(得分:0)

使用bitcoin compressed public key (ECDSA public key)常量和secp256k1 curvejavascript big integer library latest version 1.6.36转换为65字节未压缩的公共密钥的代码,它可以直接与十六进制字符串一起使用

const bigInt = require("big-integer");

function pad_with_zeroes(number, length) {
    var retval = '' + number;
    while (retval.length < length) {
        retval = '0' + retval;
    }
    return retval;
}

// Consts for secp256k1 curve. Adjust accordingly
// https://en.bitcoin.it/wiki/Secp256k1
const prime = new bigInt('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16),
pIdent = prime.add(1).divide(4);

/**
 * Point decompress secp256k1 curve
 * @param {string} Compressed representation in hex string
 * @return {string} Uncompressed representation in hex string
 */
function ECPointDecompress( comp ) {
    var signY = new Number(comp[1]) - 2;
    var x = new bigInt(comp.substring(2), 16);
    // y mod p = +-(x^3 + 7)^((p+1)/4) mod p
    var y = x.modPow(3, prime).add(7).mod(prime).modPow( pIdent, prime );
    // If the parity doesn't match it's the *other* root
    if( y.mod(2).toJSNumber() !== signY ) {
        // y = prime - y
        y = prime.subtract( y );
    }
    return '04' + pad_with_zeroes(x.toString(16), 64) + pad_with_zeroes(y.toString(16), 64);
}

带有私钥的示例:55255657523dd1c65a77d3cb53fcd050bf7fc2c11bb0bb6edabdbd41ea51f641

ECPointDecompress('0314fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267')

返回:'0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf'