feat(ds-1e): python
This commit is contained in:
3
ds/25-1/1e/README.md
Normal file
3
ds/25-1/1e/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
[euclidean](https://web.archive.org/web/20230212044931/http://www-math.ucdenver.edu/~wcherowi/courses/m5410/exeucalg.html)
|
||||
[ecdsa1](https://sefiks.com/2018/02/16/elegant-signatures-with-elliptic-curve-cryptography/)
|
||||
[ecdsa2](https://learnmeabitcoin.com/technical/cryptography/elliptic-curve/ecdsa/)
|
||||
386
ds/25-1/1e/p2pkh.ipynb
Normal file
386
ds/25-1/1e/p2pkh.ipynb
Normal file
@ -0,0 +1,386 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from numba import cuda\n",
|
||||
"from numba import vectorize\n",
|
||||
"import numpy as np"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"U8_MAX = 0xFFFFFFFFFFFFFFFF\n",
|
||||
"U32_MAX = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\n",
|
||||
"\n",
|
||||
"def bignum_to_u32(num):\n",
|
||||
" res = np.empty(4, np.uint64)\n",
|
||||
" res[0] = num & U8_MAX\n",
|
||||
" res[1] = (num >> 64) & U8_MAX\n",
|
||||
" res[2] = (num >> 128) & U8_MAX\n",
|
||||
" res[3] = (num >> 192) & U8_MAX\n",
|
||||
" return res\n",
|
||||
"\n",
|
||||
"def u32_to_bignum(arr):\n",
|
||||
" return int(arr[3]) << 192 | \\\n",
|
||||
" int(arr[2]) << 128 | \\\n",
|
||||
" int(arr[1]) << 64 | \\\n",
|
||||
" int(arr[0]) \n",
|
||||
"\n",
|
||||
"Gx = bignum_to_u32(55066263022277343669578718895168534326250603453777594175500187360389116729240)\n",
|
||||
"Gy = bignum_to_u32(32670510020758816978083085130507043184471273380659243275938904335757337482424)\n",
|
||||
"p = bignum_to_u32(2**256 - 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 - 1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 67,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"@cuda.jit('void(u8[:], u8[:], u8[:])', device=True)\n",
|
||||
"def add_u32(a, b, out):\n",
|
||||
" carry = np.uint64(0)\n",
|
||||
" for i in range(4):\n",
|
||||
" ai = a[i]\n",
|
||||
" bi = b[i]\n",
|
||||
" outi = ai + bi\n",
|
||||
" out[i] = outi + carry\n",
|
||||
" carry = outi < ai or outi == U8_MAX and carry\n",
|
||||
"\n",
|
||||
"@cuda.jit('void(u8[:], u8[:], u8[:])', device=True)\n",
|
||||
"def sub_u32(a, b, out):\n",
|
||||
" borrow = np.uint64(0)\n",
|
||||
" for i in range(4):\n",
|
||||
" ai = a[i]\n",
|
||||
" bi = b[i]\n",
|
||||
" outi = ai - bi\n",
|
||||
" out[i] = outi - borrow\n",
|
||||
" borrow = ai < bi or outi == 0 and borrow\n",
|
||||
"\n",
|
||||
"@cuda.jit('void(u8[:], u8)', device=True)\n",
|
||||
"def shr_u32(out, bits):\n",
|
||||
" bits = np.uint64(bits)\n",
|
||||
" lost = np.uint64(0)\n",
|
||||
" for i in range(4):\n",
|
||||
" outi = out[3 - i]\n",
|
||||
" out[3 - i] = (outi >> bits) | lost\n",
|
||||
" lost = outi << (np.uint64(64) - bits)\n",
|
||||
" \n",
|
||||
"@cuda.jit('void(u8[:], u8[:], u8[:])', device=True)\n",
|
||||
"def mul_u32(a, b, out):\n",
|
||||
" for i in range(4):\n",
|
||||
" out[i] = 0\n",
|
||||
" \n",
|
||||
" for i in range(255, -1, -1):\n",
|
||||
" quad_pos = np.uint64(i // 64)\n",
|
||||
" bit_pos = np.uint64(i % 64)\n",
|
||||
" bit = b[quad_pos] >> bit_pos\n",
|
||||
" if bit % 2 == 1:\n",
|
||||
" add_u32(a, out, out)\n",
|
||||
" \n",
|
||||
" shr_u32(out, 1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 78,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"failed on shr 0b0 0b110000000110100101\n",
|
||||
"failed on shr 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111 0b1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111011111100100000111011\n",
|
||||
"failed on shr 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 0b111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111010100000101111100111110110011110100011011000010010\n",
|
||||
"failed on shr 0b111111111111111111111111111111111111111111111111111 0b111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110101100000010111101100110100111110011000000011010\n",
|
||||
"failed on shr 0b1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101011110\n",
|
||||
"failed on shr 0b1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100010100000001110001001100001111\n",
|
||||
"failed on shr 0b0 0b11110100110111011010100011011110011010000010011101010\n",
|
||||
"failed on shr 0b0 0b10000010101011110110111000000100100010011110111111011101111\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"/home/appuser/Miniconda3/lib/python3.6/site-packages/ipykernel_launcher.py:7: RuntimeWarning: overflow encountered in ulong_scalars\n",
|
||||
" import sys\n",
|
||||
"/home/appuser/Miniconda3/lib/python3.6/site-packages/ipykernel_launcher.py:28: RuntimeWarning: overflow encountered in ulong_scalars\n",
|
||||
"/home/appuser/Miniconda3/lib/python3.6/site-packages/ipykernel_launcher.py:17: RuntimeWarning: overflow encountered in ulong_scalars\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from random import randint\n",
|
||||
"\n",
|
||||
"@cuda.jit('void(u8[:], u8[:], u8[:])')\n",
|
||||
"def mul_u32_ker(a, b, out):\n",
|
||||
" mul_u32(a, b, out)\n",
|
||||
"\n",
|
||||
"for i in range(10):\n",
|
||||
" a = randint(0, U8_MAX)\n",
|
||||
" b = randint(0, U8_MAX)\n",
|
||||
" bit = randint(0, 256)\n",
|
||||
" a32 = bignum_to_u32(a)\n",
|
||||
" b32 = bignum_to_u32(b)\n",
|
||||
" out32 = np.empty(4, np.uint64)\n",
|
||||
" \n",
|
||||
" add_u32.py_func(a32, b32, out32)\n",
|
||||
" out = (a + b) % (U32_MAX + 1)\n",
|
||||
" if out != u32_to_bignum(out32):\n",
|
||||
" print(\"failed on add\")\n",
|
||||
" \n",
|
||||
" sub_u32.py_func(a32, b32, out32)\n",
|
||||
" out = (a - b) % (U32_MAX + 1)\n",
|
||||
" if out != u32_to_bignum(out32):\n",
|
||||
" print(\"failed on sub\")\n",
|
||||
" \n",
|
||||
" shr_u32.py_func(out32, bit)\n",
|
||||
" out >>= bit\n",
|
||||
" if out != u32_to_bignum(out32):\n",
|
||||
" print(\"failed on shr\", bin(out), bin(u32_to_bignum(out32)))\n",
|
||||
" \n",
|
||||
" #mul_u32_ker(a32, b32, out32)\n",
|
||||
" #out = (a * b) % (U32_MAX + 1)\n",
|
||||
" #if out != u32_to_bignum(out32):\n",
|
||||
" # print(\"failed on mul\", a, b, u32_to_bignum(out32))\n",
|
||||
" "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 43,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"@cuda.jit(device=True)\n",
|
||||
"def cmp_ge256(a, b):\n",
|
||||
" # return True if a >= b\n",
|
||||
" # compare from most significant word\n",
|
||||
" for i in range(3, -1, -1):\n",
|
||||
" ai = a[i]\n",
|
||||
" bi = b[i]\n",
|
||||
" if ai > bi:\n",
|
||||
" return True\n",
|
||||
" elif ai < bi:\n",
|
||||
" return False\n",
|
||||
" return True # equal\n",
|
||||
"\n",
|
||||
"@cuda.jit(device=True)\n",
|
||||
"def sub256(a, b, out):\n",
|
||||
" # assume a >= b, perform out = a - b\n",
|
||||
" borrow = 0\n",
|
||||
" for i in range(4):\n",
|
||||
" ai = a[i]\n",
|
||||
" bi = b[i]\n",
|
||||
" # compute ai - bi - borrow\n",
|
||||
" tmp = ai - bi\n",
|
||||
" borrow1 = 1 if tmp > ai else 0 # tmp < 0 -> wrapping -> tmp > ai\n",
|
||||
" tmp2 = tmp - borrow\n",
|
||||
" borrow2 = 1 if tmp2 > tmp else 0\n",
|
||||
" out[i] = tmp2\n",
|
||||
" borrow = 1 if (borrow1 or borrow2) else 0\n",
|
||||
" # if borrow != 0 then a < b (but we assumed a>=b)\n",
|
||||
"\n",
|
||||
"# 64x64 -> 128 using 32-bit split\n",
|
||||
"@cuda.jit(device=True)\n",
|
||||
"def mul64wide(x, y):\n",
|
||||
" MASK32 = (1 << 32) - 1\n",
|
||||
" x_lo = x & MASK32\n",
|
||||
" x_hi = x >> 32\n",
|
||||
" y_lo = y & MASK32\n",
|
||||
" y_hi = y >> 32\n",
|
||||
"\n",
|
||||
" p0 = x_lo * y_lo # <= 64 bits\n",
|
||||
" p1 = x_lo * y_hi # <= 64 bits\n",
|
||||
" p2 = x_hi * y_lo # <= 64 bits\n",
|
||||
" p3 = x_hi * y_hi # <= 64 bits\n",
|
||||
"\n",
|
||||
" # combine: p0 + (p1<<32) + (p2<<32) + (p3<<64)\n",
|
||||
" mid = p1 + p2\n",
|
||||
" carry_mid = 0\n",
|
||||
" # lower 64:\n",
|
||||
" low = (p0 + ((mid & MASK32) << 32)) & ((1 << 64) - 1)\n",
|
||||
" # carry from lower additions\n",
|
||||
" if (p0 + ((mid & MASK32) << 32)) >> 64:\n",
|
||||
" carry_mid = 1\n",
|
||||
" high = p3 + (mid >> 32) + carry_mid\n",
|
||||
" # low, high are 64-bit parts of 128-bit product\n",
|
||||
" return low & ((1 << 64) - 1), high & ((1 << 64) - 1)\n",
|
||||
"\n",
|
||||
"@cuda.jit(device=True)\n",
|
||||
"def mul256_full(a, b, out8):\n",
|
||||
" # out8 must be length 8 (little-endian) to hold full 512-bit product\n",
|
||||
" # Initialize\n",
|
||||
" for i in range(8):\n",
|
||||
" out8[i] = 0\n",
|
||||
"\n",
|
||||
" # schoolbook: for i in 0..3, j in 0..3\n",
|
||||
" temp = cuda.local.array(8, dtype=np.uint64) # local accumulator\n",
|
||||
" for i in range(8):\n",
|
||||
" temp[i] = 0\n",
|
||||
"\n",
|
||||
" for i in range(4):\n",
|
||||
" ai = a[i]\n",
|
||||
" for j in range(4):\n",
|
||||
" bj = b[j]\n",
|
||||
" lo, hi = mul64wide(ai, bj) # 128-bit product\n",
|
||||
" k = i + j\n",
|
||||
" # add lo to temp[k], handle carry\n",
|
||||
" s, c = add64_carry(temp[k], lo, 0)\n",
|
||||
" temp[k] = s\n",
|
||||
" # propagate carry to next word together with hi\n",
|
||||
" carry = hi + c\n",
|
||||
" t_idx = k + 1\n",
|
||||
" while carry != 0:\n",
|
||||
" s2, c2 = add64_carry(temp[t_idx], carry, 0)\n",
|
||||
" temp[t_idx] = s2\n",
|
||||
" # compute new carry (0/1) from addition\n",
|
||||
" carry = 1 if c2 else 0\n",
|
||||
" t_idx += 1\n",
|
||||
" # t_idx never exceeds 7 because i+j <= 6, plus propagation safe\n",
|
||||
" # copy to out8\n",
|
||||
" for i in range(8):\n",
|
||||
" out8[i] = temp[i]\n",
|
||||
"\n",
|
||||
"@cuda.jit(device=True)\n",
|
||||
"def mul256_lo(a, b, out4):\n",
|
||||
" # compute full product and keep lower 4 words\n",
|
||||
" out8 = cuda.local.array(8, dtype=np.uint64)\n",
|
||||
" mul256_full(a, b, out8)\n",
|
||||
" for i in range(4):\n",
|
||||
" out4[i] = out8[i]\n",
|
||||
"\n",
|
||||
"# --------------------\n",
|
||||
"# division: binary long division\n",
|
||||
"# --------------------\n",
|
||||
"@cuda.jit(device=True)\n",
|
||||
"def shl1_256_inplace(x):\n",
|
||||
" # x <<= 1 (inplace)\n",
|
||||
" carry = 0\n",
|
||||
" for i in range(4):\n",
|
||||
" new_carry = (x[i] >> 63) & 1\n",
|
||||
" x[i] = (x[i] << 1) & ((1 << 64) - 1)\n",
|
||||
" x[i] |= carry\n",
|
||||
" carry = new_carry\n",
|
||||
"\n",
|
||||
"@cuda.jit(device=True)\n",
|
||||
"def shr1_256_getbit(x):\n",
|
||||
" # shift right by 1, return LSB (bit 0) before shift\n",
|
||||
" lsb = x[0] & 1\n",
|
||||
" carry = 0\n",
|
||||
" for i in range(3, -1, -1):\n",
|
||||
" new_carry = x[i] & 1\n",
|
||||
" x[i] = (x[i] >> 1) | (carry << 63)\n",
|
||||
" carry = new_carry\n",
|
||||
" return lsb\n",
|
||||
"\n",
|
||||
"@cuda.jit(device=True)\n",
|
||||
"def get_bit256(x, idx):\n",
|
||||
" # idx: 0..255, 0 = least significant bit\n",
|
||||
" w = idx // 64\n",
|
||||
" b = idx % 64\n",
|
||||
" return (x[w] >> b) & 1\n",
|
||||
"\n",
|
||||
"@cuda.jit(device=True)\n",
|
||||
"def set_bit256(x, idx):\n",
|
||||
" w = idx // 64\n",
|
||||
" b = idx % 64\n",
|
||||
" x[w] |= (1 << b)\n",
|
||||
"\n",
|
||||
"@cuda.jit(device=True)\n",
|
||||
"def copy256(src, dst):\n",
|
||||
" for i in range(4):\n",
|
||||
" dst[i] = src[i]\n",
|
||||
"\n",
|
||||
"@cuda.jit(device=True)\n",
|
||||
"def zero256(x):\n",
|
||||
" for i in range(4):\n",
|
||||
" x[i] = 0\n",
|
||||
"\n",
|
||||
"@cuda.jit(device=True)\n",
|
||||
"def divmod256(dividend, divisor, q, r):\n",
|
||||
" # Binary long division (restoring), bit-by-bit from MSB..LSB\n",
|
||||
" # q,r are output arrays (4 words). dividend/divisor are input arrays.\n",
|
||||
" # edge cases\n",
|
||||
" zero = True\n",
|
||||
" for i in range(4):\n",
|
||||
" if divisor[i] != 0:\n",
|
||||
" zero = False\n",
|
||||
" break\n",
|
||||
" if zero:\n",
|
||||
" # division by zero — define q=0,r=dividend (user must avoid)\n",
|
||||
" for i in range(4):\n",
|
||||
" q[i] = 0\n",
|
||||
" r[i] = dividend[i]\n",
|
||||
" return\n",
|
||||
"\n",
|
||||
" zero256(q)\n",
|
||||
" zero256(r)\n",
|
||||
" # iterate bits from highest (255) down to 0\n",
|
||||
" for i in range(255, -1, -1):\n",
|
||||
" # left shift r by 1\n",
|
||||
" shl1_256_inplace(r)\n",
|
||||
" # bring in bit i of dividend\n",
|
||||
" if get_bit256(dividend, i):\n",
|
||||
" r[0] |= 1 # set lsb\n",
|
||||
" # if r >= divisor then r -= divisor and set q[i] = 1\n",
|
||||
" if cmp_ge256(r, divisor):\n",
|
||||
" # r = r - divisor\n",
|
||||
" tmp = cuda.local.array(4, dtype=np.uint64)\n",
|
||||
" sub256(r, divisor, tmp)\n",
|
||||
" for k in range(4):\n",
|
||||
" r[k] = tmp[k]\n",
|
||||
" # set q bit i\n",
|
||||
" w = i // 64\n",
|
||||
" b = i % 64\n",
|
||||
" q[w] |= (1 << b)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.6.10"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@ -1,160 +0,0 @@
|
||||
|
||||
# secp256k1_pure_python.py
|
||||
# Pure Python scalar->public (affine x,y) for secp256k1
|
||||
# No external libs required.
|
||||
|
||||
# Curve parameters for secp256k1
|
||||
P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
|
||||
A = 0 # a = 0 for secp256k1
|
||||
B = 7
|
||||
# Base point G
|
||||
Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
|
||||
Gy = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
|
||||
# Order of base point
|
||||
N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
||||
|
||||
# ---------- Field helpers ----------
|
||||
def inv_mod(x, p=P):
|
||||
"""Multiplicative inverse modulo p (Python built-in pow with -1 not supported)"""
|
||||
return pow(x, p - 2, p)
|
||||
|
||||
# ---------- Jacobian coordinates helpers ----------
|
||||
# Represent point as tuple (X, Y, Z) meaning affine (X/Z^2, Y/Z^3).
|
||||
INFINITY = (0, 1, 0) # Z == 0 means point at infinity in these helpers
|
||||
|
||||
def is_infinity(Pj):
|
||||
return Pj[2] == 0
|
||||
|
||||
def to_jacobian(x, y):
|
||||
return (x % P, y % P, 1)
|
||||
|
||||
def from_jacobian(Pj):
|
||||
X, Y, Z = Pj
|
||||
if Z == 0:
|
||||
return None # infinity
|
||||
Z_inv = inv_mod(Z)
|
||||
Z_inv2 = (Z_inv * Z_inv) % P
|
||||
Z_inv3 = (Z_inv2 * Z_inv) % P
|
||||
x = (X * Z_inv2) % P
|
||||
y = (Y * Z_inv3) % P
|
||||
return (x, y)
|
||||
|
||||
# Point doubling in Jacobian coords
|
||||
def jacobian_double(Pj):
|
||||
X1, Y1, Z1 = Pj
|
||||
if Z1 == 0 or Y1 == 0:
|
||||
return INFINITY
|
||||
# Formula for a = 0
|
||||
S = (4 * X1 * pow(Y1, 2, P)) % P
|
||||
M = (3 * pow(X1, 2, P)) % P # since a=0
|
||||
X3 = (pow(M, 2, P) - 2 * S) % P
|
||||
Y3 = (M * (S - X3) - 8 * pow(Y1, 4, P)) % P
|
||||
Z3 = (2 * Y1 * Z1) % P
|
||||
return (X3, Y3, Z3)
|
||||
|
||||
# Mixed addition: add Jacobian Pj and affine Q=(x2,y2)
|
||||
def jacobian_add_affine(Pj, Qx, Qy):
|
||||
X1, Y1, Z1 = Pj
|
||||
if Z1 == 0:
|
||||
return to_jacobian(Qx, Qy)
|
||||
if Y1 == 0 and X1 == 0 and Z1 == 0:
|
||||
return to_jacobian(Qx, Qy)
|
||||
|
||||
Z1z = (Z1 * Z1) % P
|
||||
U2 = (Qx * Z1z) % P
|
||||
S2 = (Qy * Z1z * Z1) % P # Qy * Z1^3
|
||||
|
||||
H = (U2 - X1) % P
|
||||
r = (S2 - Y1) % P
|
||||
|
||||
if H == 0:
|
||||
if r == 0:
|
||||
return jacobian_double(Pj)
|
||||
else:
|
||||
return INFINITY
|
||||
|
||||
HH = (H * H) % P
|
||||
HHH = (H * HH) % P
|
||||
V = (X1 * HH) % P
|
||||
|
||||
X3 = (r * r - HHH - 2 * V) % P
|
||||
Y3 = (r * (V - X3) - Y1 * HHH) % P
|
||||
Z3 = (Z1 * HH) % P
|
||||
return (X3, Y3, Z3)
|
||||
|
||||
# ---------- Scalar multiplication ----------
|
||||
def scalar_mult(k, Px=Gx, Py=Gy):
|
||||
"""
|
||||
Multiply base point (Px,Py) by scalar k.
|
||||
Returns affine (x,y) or None for infinity.
|
||||
k should be integer >=0.
|
||||
"""
|
||||
if k % N == 0:
|
||||
return None
|
||||
if k < 0:
|
||||
# use -P
|
||||
return scalar_mult(-k, Px, (-Py) % P)
|
||||
|
||||
# Ensure scalar reduced mod N and >0
|
||||
k = k % N
|
||||
if k == 0:
|
||||
return None
|
||||
|
||||
# Initialize result as infinity (Jacobian)
|
||||
R = INFINITY
|
||||
# Take base point as affine (we'll use mixed add)
|
||||
Px = Px % P
|
||||
Py = Py % P
|
||||
|
||||
# Left-to-right binary method
|
||||
bits = k.bit_length()
|
||||
for i in range(bits - 1, -1, -1):
|
||||
# R = 2*R
|
||||
R = jacobian_double(R)
|
||||
if (k >> i) & 1:
|
||||
# R = R + P
|
||||
R = jacobian_add_affine(R, Px, Py)
|
||||
# Convert R to affine
|
||||
return from_jacobian(R)
|
||||
|
||||
# ---------- Utility outputs ----------
|
||||
def pubkey_bytes_compressed(x, y):
|
||||
prefix = b'\x02' if (y % 2 == 0) else b'\x03'
|
||||
xb = x.to_bytes(32, 'big')
|
||||
return prefix + xb
|
||||
|
||||
def pubkey_bytes_uncompressed(x, y):
|
||||
return b'\x04' + x.to_bytes(32, 'big') + y.to_bytes(32, 'big')
|
||||
|
||||
# ---------- Public function as user asked ----------
|
||||
def priv_to_pub(priv_int):
|
||||
"""
|
||||
Input: priv_int (Python int) — private key (assumed 0 < priv < N but function reduces)
|
||||
Output: (x, y) tuple of Python ints (affine coordinates). Returns None if point at infinity (shouldn't for valid keys).
|
||||
"""
|
||||
if not isinstance(priv_int, int):
|
||||
raise TypeError("priv must be int")
|
||||
k = priv_int % N
|
||||
if k == 0:
|
||||
raise ValueError("private key is zero modulo curve order")
|
||||
pt = scalar_mult(k, Gx, Gy)
|
||||
if pt is None:
|
||||
raise RuntimeError("Result is point at infinity (shouldn't happen for valid priv)")
|
||||
return pt
|
||||
|
||||
# ---------- Example / quick test ----------
|
||||
if __name__ == "__main__":
|
||||
# Test vector: priv = 1 -> pub = G
|
||||
priv = 1
|
||||
x, y = priv_to_pub(priv)
|
||||
assert x == Gx and y == Gy
|
||||
print("priv=1 -> pub == G : OK")
|
||||
|
||||
# Test with a random private (example)
|
||||
import secrets
|
||||
priv = secrets.randbelow(N - 1) + 1
|
||||
x, y = priv_to_pub(priv)
|
||||
print("priv (hex):", hex(priv))
|
||||
print("pub x:", hex(x))
|
||||
print("pub y:", hex(y))
|
||||
print("compressed pubkey:", pubkey_bytes_compressed(x, y).hex())
|
||||
Reference in New Issue
Block a user