从压缩密钥派生 ECDSA 未压缩 public 密钥
Deriving an ECDSA uncompressed public key from a compressed one
我目前正在尝试从压缩密钥中导出比特币未压缩 ECDSA public 密钥。
根据这个link on the Bitcoin wiki,这样做是可能的...但是怎么做呢?
为您提供更多详细信息:截至目前,我已经在比特币网络上收集了压缩密钥(33 字节长)。
它们的格式如下:<1 字节长前缀><32 字节长 X>。
从那里,我想获得一个未压缩的密钥(65 字节长),其格式为:
<1 字节长前缀><32 字节长 X><32 字节长 Y>
根据这个other link on the Bitcoin wiki,应该和解方程一样简单:
Y^2 = X^3 + 7
但是,我似乎无法到达那里。我对 Y 的价值简直是遥不可及。这是我的代码(public 键的值来自 Bitcoin wiki example):
import binascii
from decimal import *
expected_uncompressed_key_hex = '0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6'
expected_y_hex = expected_uncompressed_key_hex[-64:]
expected_y_dec = int(expected_y_hex, 16)
x_hex = expected_uncompressed_key_hex[2:66]
if expected_y_dec % 2 == 0:
prefix = "02"
else:
prefix = "03"
artificial_compressed_key = prefix + x_hex
getcontext().prec = 500
test_dec = Decimal(int(x_hex, 16))
y_square_dec = test_dec**3 + 7
if prefix == "02":
y_dec = - Decimal(y_square_dec).sqrt()
else:
y_dec = Decimal(y_square_dec).sqrt()
computed_y_hex = hex(int(y_dec))
computed_uncompressed_key = "04" + x + computed_y_hex
有关信息,我的输出是:
computed_y_hex = '0X2D29684BD207BF6D809F7D0EB78E4FD61C3C6700E88AB100D1075EFA8F8FD893080F35E6C7AC2E2214F8F4D088342951'
expected_y_hex = '2CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6'
感谢您的帮助!
椭圆曲线的域不在实数域上。它在一个有限域上 modulo 一些素数。
对于 Secp256k1,素数 p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1.
因此:y^2= (x^3) + 7 (mod p)
没有直接求解方程的方法,您需要使用 Cipolla 算法:https://en.wikipedia.org/wiki/Cipolla%27s_algorithm
你需要在字段中进行计算,这主要意味着你每次计算后都要将你的数字除以p后的余数。计算这个称为取模,在 python.
中写为 % p
在这个领域求幂比单纯的乘法和减法很多次更有效。这称为模幂运算。 Python 的内置指数函数 pow(n,e,p) 可以解决这个问题。
剩下的问题是求平方根。幸运的是 secp256k1 是以特殊方式选择的 (),因此求平方根很容易:x 的平方根是 .
因此您的代码的简化版本变为:
import binascii
p_hex = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F'
p = int(p_hex, 16)
compressed_key_hex = '0250863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B2352'
x_hex = compressed_key_hex[2:66]
x = int(x_hex, 16)
prefix = compressed_key_hex[0:2]
y_square = (pow(x, 3, p) + 7) % p
y_square_square_root = pow(y_square, (p+1)/4, p)
if (prefix == "02" and y_square_square_root & 1) or (prefix == "03" and not y_square_square_root & 1):
y = (-y_square_square_root) % p
else:
y = y_square_square_root
computed_y_hex = format(y, '064x')
computed_uncompressed_key = "04" + x_hex + computed_y_hex
print computed_uncompressed_key
这里是没有任何第 3 方的示例代码 python 库:
def pow_mod(x, y, z):
"Calculate (x ** y) % z efficiently."
number = 1
while y:
if y & 1:
number = number * x % z
y >>= 1
x = x * x % z
return number
# prime p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1
p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
# bitcoin's compressed public key of private key 55255657523dd1c65a77d3cb53fcd050bf7fc2c11bb0bb6edabdbd41ea51f641
compressed_key = '0314fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267'
y_parity = int(compressed_key[:2]) - 2
x = int(compressed_key[2:], 16)
a = (pow_mod(x, 3, p) + 7) % p
y = pow_mod(a, (p+1)//4, p)
if y % 2 != y_parity:
y = -y % p
uncompressed_key = '04{:x}{:x}'.format(x, y)
print(uncompressed_key)
# should get 0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf
我知道这个问题已经得到解答,我实际上从这个答案中受益,所以谢谢。问题是我在 C# 中寻找相同的解决方案时发现了这些答案 3 次,而我并没有真正在 python 中编码 :)。所以对于任何试图解决这个问题的人来说,这是一个 C# 解决方案,玩得开心! :)(它使用 BouncyCastle 库)。
using System;
using System.Collections.Generic;
using System.Linq;
using MoreLinq;
using NBitcoin;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
namespace BitcoinPublicKeyDecompression
{
public class Program
{
public static void Main()
{
const string cPubKey = "0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352";
var uPubKey = cPubKey.ToHexByteArray().BitcoinDecompressPublicKey().ToHexString();
var expectedUPubKey = new PubKey(cPubKey).Decompress().ToString();
Console.WriteLine($"Public Key:\n\n{cPubKey}\n\nhas been {(uPubKey == expectedUPubKey ? "correctly" : "incorrectly")} decompressed to:\n\n{uPubKey}");
Console.WriteLine("\nPress any key to quit...");
Console.ReadKey();
}
}
public static class Extensions
{
public static readonly byte[] EmptyByteArray = new byte[0];
public static byte[] BitcoinDecompressPublicKey(this byte[] bPubC)
{
var ecPubKey = bPubC.BitcoinCompressedPublicKeyToECPublicKey();
return ecPubKey.ToBitcoinUncompressedPublicKey();
}
public static ECPublicKeyParameters BitcoinCompressedPublicKeyToECPublicKey(this byte[] bPubC)
{
var pubKey = bPubC.Skip(1).ToArray();
var curve = ECNamedCurveTable.GetByName("secp256k1");
var domainParams = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed());
var yParity = new BigInteger(bPubC.Take(1).ToArray()).Subtract(BigInteger.Two);
var x = new BigInteger(1, pubKey);
var p = ((FpCurve)curve.Curve).Q;
var a = x.ModPow(new BigInteger("3"), p).Add(new BigInteger("7")).Mod(p);
var y = a.ModPow(p.Add(BigInteger.One).FloorDivide(new BigInteger("4")), p);
if (!y.Mod(BigInteger.Two).Equals(yParity))
y = y.Negate().Mod(p);
var q = curve.Curve.CreatePoint(x, y);
return new ECPublicKeyParameters(q, domainParams);
}
public static byte[] ToBitcoinUncompressedPublicKey(this AsymmetricKeyParameter ecPublicKey)
{
var publicKey = ((ECPublicKeyParameters)ecPublicKey).Q;
var xs = publicKey.AffineXCoord.ToBigInteger().ToByteArrayUnsigned().PadStart(32);
var ys = publicKey.AffineYCoord.ToBigInteger().ToByteArrayUnsigned().PadStart(32);
return new byte[] { 0x04 }.ConcatMany(xs, ys).ToArray();
}
public static BigInteger FloorDivide(this BigInteger a, BigInteger b)
{
if (a.CompareTo(BigInteger.Zero) > 0 ^ b.CompareTo(BigInteger.Zero) < 0 && !a.Mod(b).Equals(BigInteger.Zero))
return a.Divide(b).Subtract(BigInteger.One);
return a.Divide(b);
}
public static byte[] ToHexByteArray(this string str)
{
byte[] bytes;
if (string.IsNullOrEmpty(str))
bytes = EmptyByteArray;
else
{
var string_length = str.Length;
var character_index = str.StartsWith("0x", StringComparison.Ordinal) ? 2 : 0;
var number_of_characters = string_length - character_index;
var add_leading_zero = false;
if (0 != number_of_characters % 2)
{
add_leading_zero = true;
number_of_characters += 1;
}
bytes = new byte[number_of_characters / 2];
var write_index = 0;
if (add_leading_zero)
{
bytes[write_index++] = CharacterToByte(str[character_index], character_index);
character_index += 1;
}
for (var read_index = character_index; read_index < str.Length; read_index += 2)
{
var upper = CharacterToByte(str[read_index], read_index, 4);
var lower = CharacterToByte(str[read_index + 1], read_index + 1);
bytes[write_index++] = (byte)(upper | lower);
}
}
return bytes;
}
public static byte CharacterToByte(char character, int index, int shift = 0)
{
var value = (byte)character;
if (0x40 < value && 0x47 > value || 0x60 < value && 0x67 > value)
{
if (0x40 != (0x40 & value))
return value;
if (0x20 == (0x20 & value))
value = (byte)((value + 0xA - 0x61) << shift);
else
value = (byte)((value + 0xA - 0x41) << shift);
}
else if (0x29 < value && 0x40 > value)
value = (byte)((value - 0x30) << shift);
else
throw new InvalidOperationException($"Character '{character}' at index '{index}' is not valid alphanumeric character.");
return value;
}
public static string ToHexString(this byte[] value, bool prefix = false)
{
var strPrex = prefix ? "0x" : "";
return strPrex + string.Concat(value.Select(b => b.ToString("x2")).ToArray());
}
public static IEnumerable<T> ConcatMany<T>(this IEnumerable<T> enumerable, params IEnumerable<T>[] enums)
{
return enumerable.Concat(enums.SelectMany(x => x));
}
}
}
结果:
我目前正在尝试从压缩密钥中导出比特币未压缩 ECDSA public 密钥。
根据这个link on the Bitcoin wiki,这样做是可能的...但是怎么做呢?
为您提供更多详细信息:截至目前,我已经在比特币网络上收集了压缩密钥(33 字节长)。
它们的格式如下:<1 字节长前缀><32 字节长 X>。 从那里,我想获得一个未压缩的密钥(65 字节长),其格式为: <1 字节长前缀><32 字节长 X><32 字节长 Y>
根据这个other link on the Bitcoin wiki,应该和解方程一样简单:
Y^2 = X^3 + 7
但是,我似乎无法到达那里。我对 Y 的价值简直是遥不可及。这是我的代码(public 键的值来自 Bitcoin wiki example):
import binascii
from decimal import *
expected_uncompressed_key_hex = '0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6'
expected_y_hex = expected_uncompressed_key_hex[-64:]
expected_y_dec = int(expected_y_hex, 16)
x_hex = expected_uncompressed_key_hex[2:66]
if expected_y_dec % 2 == 0:
prefix = "02"
else:
prefix = "03"
artificial_compressed_key = prefix + x_hex
getcontext().prec = 500
test_dec = Decimal(int(x_hex, 16))
y_square_dec = test_dec**3 + 7
if prefix == "02":
y_dec = - Decimal(y_square_dec).sqrt()
else:
y_dec = Decimal(y_square_dec).sqrt()
computed_y_hex = hex(int(y_dec))
computed_uncompressed_key = "04" + x + computed_y_hex
有关信息,我的输出是:
computed_y_hex = '0X2D29684BD207BF6D809F7D0EB78E4FD61C3C6700E88AB100D1075EFA8F8FD893080F35E6C7AC2E2214F8F4D088342951'
expected_y_hex = '2CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6'
感谢您的帮助!
椭圆曲线的域不在实数域上。它在一个有限域上 modulo 一些素数。
对于 Secp256k1,素数 p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1.
因此:y^2= (x^3) + 7 (mod p)
没有直接求解方程的方法,您需要使用 Cipolla 算法:https://en.wikipedia.org/wiki/Cipolla%27s_algorithm
你需要在字段中进行计算,这主要意味着你每次计算后都要将你的数字除以p后的余数。计算这个称为取模,在 python.
中写为% p
在这个领域求幂比单纯的乘法和减法很多次更有效。这称为模幂运算。 Python 的内置指数函数 pow(n,e,p) 可以解决这个问题。
剩下的问题是求平方根。幸运的是 secp256k1 是以特殊方式选择的 (),因此求平方根很容易:x 的平方根是 .
因此您的代码的简化版本变为:
import binascii
p_hex = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F'
p = int(p_hex, 16)
compressed_key_hex = '0250863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B2352'
x_hex = compressed_key_hex[2:66]
x = int(x_hex, 16)
prefix = compressed_key_hex[0:2]
y_square = (pow(x, 3, p) + 7) % p
y_square_square_root = pow(y_square, (p+1)/4, p)
if (prefix == "02" and y_square_square_root & 1) or (prefix == "03" and not y_square_square_root & 1):
y = (-y_square_square_root) % p
else:
y = y_square_square_root
computed_y_hex = format(y, '064x')
computed_uncompressed_key = "04" + x_hex + computed_y_hex
print computed_uncompressed_key
这里是没有任何第 3 方的示例代码 python 库:
def pow_mod(x, y, z):
"Calculate (x ** y) % z efficiently."
number = 1
while y:
if y & 1:
number = number * x % z
y >>= 1
x = x * x % z
return number
# prime p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1
p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
# bitcoin's compressed public key of private key 55255657523dd1c65a77d3cb53fcd050bf7fc2c11bb0bb6edabdbd41ea51f641
compressed_key = '0314fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267'
y_parity = int(compressed_key[:2]) - 2
x = int(compressed_key[2:], 16)
a = (pow_mod(x, 3, p) + 7) % p
y = pow_mod(a, (p+1)//4, p)
if y % 2 != y_parity:
y = -y % p
uncompressed_key = '04{:x}{:x}'.format(x, y)
print(uncompressed_key)
# should get 0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf
我知道这个问题已经得到解答,我实际上从这个答案中受益,所以谢谢。问题是我在 C# 中寻找相同的解决方案时发现了这些答案 3 次,而我并没有真正在 python 中编码 :)。所以对于任何试图解决这个问题的人来说,这是一个 C# 解决方案,玩得开心! :)(它使用 BouncyCastle 库)。
using System;
using System.Collections.Generic;
using System.Linq;
using MoreLinq;
using NBitcoin;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
namespace BitcoinPublicKeyDecompression
{
public class Program
{
public static void Main()
{
const string cPubKey = "0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352";
var uPubKey = cPubKey.ToHexByteArray().BitcoinDecompressPublicKey().ToHexString();
var expectedUPubKey = new PubKey(cPubKey).Decompress().ToString();
Console.WriteLine($"Public Key:\n\n{cPubKey}\n\nhas been {(uPubKey == expectedUPubKey ? "correctly" : "incorrectly")} decompressed to:\n\n{uPubKey}");
Console.WriteLine("\nPress any key to quit...");
Console.ReadKey();
}
}
public static class Extensions
{
public static readonly byte[] EmptyByteArray = new byte[0];
public static byte[] BitcoinDecompressPublicKey(this byte[] bPubC)
{
var ecPubKey = bPubC.BitcoinCompressedPublicKeyToECPublicKey();
return ecPubKey.ToBitcoinUncompressedPublicKey();
}
public static ECPublicKeyParameters BitcoinCompressedPublicKeyToECPublicKey(this byte[] bPubC)
{
var pubKey = bPubC.Skip(1).ToArray();
var curve = ECNamedCurveTable.GetByName("secp256k1");
var domainParams = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed());
var yParity = new BigInteger(bPubC.Take(1).ToArray()).Subtract(BigInteger.Two);
var x = new BigInteger(1, pubKey);
var p = ((FpCurve)curve.Curve).Q;
var a = x.ModPow(new BigInteger("3"), p).Add(new BigInteger("7")).Mod(p);
var y = a.ModPow(p.Add(BigInteger.One).FloorDivide(new BigInteger("4")), p);
if (!y.Mod(BigInteger.Two).Equals(yParity))
y = y.Negate().Mod(p);
var q = curve.Curve.CreatePoint(x, y);
return new ECPublicKeyParameters(q, domainParams);
}
public static byte[] ToBitcoinUncompressedPublicKey(this AsymmetricKeyParameter ecPublicKey)
{
var publicKey = ((ECPublicKeyParameters)ecPublicKey).Q;
var xs = publicKey.AffineXCoord.ToBigInteger().ToByteArrayUnsigned().PadStart(32);
var ys = publicKey.AffineYCoord.ToBigInteger().ToByteArrayUnsigned().PadStart(32);
return new byte[] { 0x04 }.ConcatMany(xs, ys).ToArray();
}
public static BigInteger FloorDivide(this BigInteger a, BigInteger b)
{
if (a.CompareTo(BigInteger.Zero) > 0 ^ b.CompareTo(BigInteger.Zero) < 0 && !a.Mod(b).Equals(BigInteger.Zero))
return a.Divide(b).Subtract(BigInteger.One);
return a.Divide(b);
}
public static byte[] ToHexByteArray(this string str)
{
byte[] bytes;
if (string.IsNullOrEmpty(str))
bytes = EmptyByteArray;
else
{
var string_length = str.Length;
var character_index = str.StartsWith("0x", StringComparison.Ordinal) ? 2 : 0;
var number_of_characters = string_length - character_index;
var add_leading_zero = false;
if (0 != number_of_characters % 2)
{
add_leading_zero = true;
number_of_characters += 1;
}
bytes = new byte[number_of_characters / 2];
var write_index = 0;
if (add_leading_zero)
{
bytes[write_index++] = CharacterToByte(str[character_index], character_index);
character_index += 1;
}
for (var read_index = character_index; read_index < str.Length; read_index += 2)
{
var upper = CharacterToByte(str[read_index], read_index, 4);
var lower = CharacterToByte(str[read_index + 1], read_index + 1);
bytes[write_index++] = (byte)(upper | lower);
}
}
return bytes;
}
public static byte CharacterToByte(char character, int index, int shift = 0)
{
var value = (byte)character;
if (0x40 < value && 0x47 > value || 0x60 < value && 0x67 > value)
{
if (0x40 != (0x40 & value))
return value;
if (0x20 == (0x20 & value))
value = (byte)((value + 0xA - 0x61) << shift);
else
value = (byte)((value + 0xA - 0x41) << shift);
}
else if (0x29 < value && 0x40 > value)
value = (byte)((value - 0x30) << shift);
else
throw new InvalidOperationException($"Character '{character}' at index '{index}' is not valid alphanumeric character.");
return value;
}
public static string ToHexString(this byte[] value, bool prefix = false)
{
var strPrex = prefix ? "0x" : "";
return strPrex + string.Concat(value.Select(b => b.ToString("x2")).ToArray());
}
public static IEnumerable<T> ConcatMany<T>(this IEnumerable<T> enumerable, params IEnumerable<T>[] enums)
{
return enumerable.Concat(enums.SelectMany(x => x));
}
}
}
结果: