jupyter | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
This is a prototyping notebook I used to design the dashboard. Feel free to ignore me.
import dash_bootstrap_components as dbc
dbc.themes.SLATE
import plotly
import numpy as np
def elliptic(p, a, b):
x = y = np.arange(p)
xx, yy = np.meshgrid(x,y)
return (np.mod(yy**2, p) - np.mod(xx**3+a*xx+b, p) == 0)*1.0
import plotly.graph_objects as go
fig = go.Figure(data=go.Heatmap(z=elliptic(37, a=0, b=7), colorscale='gray'), layout=dict(width=700, height=700))
fig
Note symmetry about
Given
We want the (modulus) line passing through
With
# taken from https://stackoverflow.com/questions/4798654/modular-multiplicative-inverse-function-in-python
# and http://anh.cs.luc.edu/331/notes/xgcd.pdf
def egcd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = egcd(b % a, a)
return (g, x - (b // a) * y, y)
def xgcd(a, b):
prevx, x = 1, 0
prevy, y = 0, 1
while b:
q = a//b
x, prevx = prevx - q*x, x
y, prevy = prevy - q*y, y
a, b = b, a % b
return a, prevx, prevy
def modinv_2(a, b):
# a = abs(a)%b # seems to be a bug because the next line allows negative assertion to pass
if a < 0:
a += b
for m in range(b):
if (a*m)%b == 1:
return m
return None
assert (modinv_2(13, 17)*13)%17 == 1
assert (modinv_2(-13, 17)*(-13))%17 == 1
# def modinv(a, m):
# """multiplicative mod inverse of a mod m"""
# # if( n < 0 ) {
# # n = n + this.k;
# # }
# if a < 0:
# a = a + m
# # g, x, y = egcd(a, m)
# g, x, y = xgcd(a, m)
# if g != 1:
# raise Exception(f'modular inverse does not exist for {a} mod {m}')
# else:
# return x % m
def modinv(a, b):
"""from Jimmy Song"""
return pow(a, b - 2, b)
assert (modinv(13, 17)*13)%17 == 1
assert (modinv(-13, 17)*-13)%17 == 1
def add_mod(P, Q, p, a, b=None):
"""add points P and Q through their intserection with curve (p,a,b) b is unused
based on https://github.com/andreacorbellini/ecc/blob/865b8f9e79ed94a9d004a9b553f4b3add55878be/interactive/ec.js#L1059
"""
if P is None:
return Q
if Q is None:
return P
x_p, y_p = P
x_q, y_q = Q
if x_p != x_q: # distinct points
print('distinct')
m = (((y_p-y_q)%p)*modinv((x_p - x_q)%p, p))%p;
x_r = ((m*m)%p - (x_p - x_q)%p)%p
y_r = ((m*((x_p-x_r)%p)%p)%p - y_p)%p
return x_r, y_r
else:
if (y_p == 0)&(y_q == 0 ):
# This may only happen if P=Q is a root of the elliptic
# curve, hence the line is vertical.
print('found root of elliptic')
return None
elif y_p == y_q:
# The points are the same, but the line is not vertical.
# print('points are the same')
m = ((((3*(x_p*x_p)%p)%p+a)%p)*modinv((2*y_p)%p, p))%p
x_r = ((m**2)%p - (2*x_p)%p)%p
y_r = ((m*((x_p - x_r)%p))%p - y_p)%p
return x_r, y_r
else:
# The points are not the same and the line is vertical.
print('found vertical')
return None
add_mod((22, 6), (22, 6), 37, 7)
P_ = (3,6)
for i in range(10):
print(i, P_)
P_ = add_mod(P_, P_, p=97, a=2)
0 (3, 6) 1 (80, 10) 2 (3, 91) 3 (80, 87) 4 (3, 6) 5 (80, 10) 6 (3, 91) 7 (80, 87) 8 (3, 6) 9 (80, 10)
def multiply_mod(P, n, p, a, b=None):
"""multiply P n times for curve (p,a,b)"""
P_ = P + ()
if n == 0:
return 0
elif n == 1:
return P_
for _ in range(1, n):
P_ = add_mod(P_, P_, p, a) + ()
return P_
multiply_mod((3, 6), 7, p=97, a=2)
e2 = dict(p=97, a=2, b=3)
for _ in range(10):
print(f"{_}: {multiply_mod((3,6), _, **e2)}")
How many points are in the subgroup generated by P?
- count all points on the curve N (see Schoof's Algorithm)
- find all divisors of N
- for every divisor of N, see if
$nP=0$
order_dict = {} # cache results
def order(p, a, b):
"""calculate the order of the field including the point at infinity"""
if (p,a,b) not in order_dict:
order_ = int(elliptic(p, a, b).sum()+1) # brute force
order_dict[(p,a,b)] = order_
else:
order_ = order_dict[(p,a,b)]
return order_
order(37, a=-1, b=3)
def divisors(n):
for i in range(1, int(n / 2) + 1):
if n % i == 0:
yield i
yield n
def subgroup_order(P, p, a, b):
N = order(p, a, b)
for _ in divisors(N):
P_ = P
for i in range(_):
P_ = add_mod(P_, P_, p=p,a=a)
if P_ == P:
break
if _ == N:
return N # already contains the point at infinity
else:
return _+1
subgroup_order((2,3), p=37, a=-1, b=3)
order(p=29, a=-1, b=1) # is prime!
subgroup_order((9,24), p=29, a=-1, b=1) # should be 37 instead of 38
order(p=29, a=-1, b=1) # is prime!
subgroup_order((9,24), p=29, a=-1, b=1) # should be 37 instead of 38
get the subgroup order to do the multiplication modulus
def multiply_mod(P, n, p, a, b=None):
"""multiply P n times for curve (p,a,b)"""
if P is None:
return None
sgo = subgroup_order(P, p=p, a=a, b=b)
# n(P) = (n mod sgo) P
n_ = n%sgo
P_ = P + ()
if n_ == 0:
return None
elif n == 1:
return P_
for _ in range(1, n_):
P_ = add_mod(P_, P_, p, a) + ()
return P_
e2 = dict(p=97, a=2, b=3)
for _ in range(10):
print(f"{_}: {multiply_mod((3,6), _, **e2)}")
fig = go.Figure(data=go.Heatmap(z=elliptic(p=29, a=-1, b=1), colorscale='gray'),
layout=dict(width=700, height=700))
fig
Checking with Jimmy Song's version:
import sys
sys.path.append('../programmingbitcoin/code-ch03/')
from ecc import Point, FieldElement
x_, y_ = FieldElement(3, 97), FieldElement(6, 97)
a_, b_ = FieldElement(2, 97), FieldElement(3, 97)
e2 = dict(p=97, a=2, b=3)
p_ = Point(x_, y_, a_, b_)
for _ in range(10):
print(_, p_)
p_ = p_+p_
(0*Point(x_, y_, a_, b_))
Checking with Jimmy Song's version:
0 (3, 6) 1 (80, 10) 2 (3, 91) 3 (80, 87) 4 (3, 6) 5 (80, 10) 6 (3, 91) 7 (80, 87) 8 (3, 6) 9 (80, 10)
I'm getting the same result as Jimmy's code!
Click to expand Jimmy's code
def __add__(self, other):
if self.a != other.a or self.b != other.b:
raise TypeError('Points {}, {} are not on the same curve'.format(self, other))
# Case 0.0: self is the point at infinity, return other
if self.x is None:
return other
# Case 0.1: other is the point at infinity, return self
if other.x is None:
return self
# Case 1: self.x == other.x, self.y != other.y
# Result is point at infinity
if self.x == other.x and self.y != other.y:
return self.__class__(None, None, self.a, self.b)
# Case 2: self.x ≠ other.x
# Formula (x3,y3)==(x1,y1)+(x2,y2)
# s=(y2-y1)/(x2-x1)
# x3=s**2-x1-x2
# y3=s*(x1-x3)-y1
if self.x != other.x:
s = (other.y - self.y) / (other.x - self.x)
x = s**2 - self.x - other.x
y = s * (self.x - x) - self.y
return self.__class__(x, y, self.a, self.b)
# Case 4: if we are tangent to the vertical line,
# we return the point at infinity
# note instead of figuring out what 0 is for each type
# we just use 0 * self.x
if self == other and self.y == 0 * self.x:
return self.__class__(None, None, self.a, self.b)
# Case 3: self == other
# Formula (x3,y3)=(x1,y1)+(x1,y1)
# s=(3*x1**2+a)/(2*y1)
# x3=s**2-2*x1
# y3=s*(x1-x3)-y1
if self == other:
s = (3 * self.x**2 + self.a) / (2 * self.y)
x = s**2 - 2 * self.x
y = s * (self.x - x) - self.y
return self.__class__(x, y, self.a, self.b)
# tag::source3[]
def __rmul__(self, coefficient):
coef = coefficient
current = self # <1>
result = self.__class__(None, None, self.a, self.b) # <2>
while coef:
if coef & 1: # <3>
result += current
current += current # <4>
coef >>= 1 # <5>
return result
# end::source3[]
Here's a paper on how secp256 generators (should be?) randomly selected
https://www.secg.org/sec2-v2.pdf
nobody knows? https://bitcoin.stackexchange.com/questions/113116/how-is-the-generator-point-g-chosen-in-the-secp256k1-curve-used-in-bitcoin
Trick is
- given N choose the subgroup order n to be a prime divisor of N
- compute cofactor h = N/n
- choose random point P -- this could be fun!
- compute G=hP
- if G != 0, then n(hP)=0 and G is a generator of the whole curve
- if G = 0, go back to (3)
import numpy as np
def is_prime(n):
prime_flag = 0
if(n > 1):
for i in range(2, int(np.sqrt(n)) + 1):
if (n % i == 0):
prime_flag = 1
break
if (prime_flag == 0):
return True
return False
def divisors(n):
for i in range(1, int(n / 2) + 1):
if n % i == 0:
yield i
yield n
def prime_divisor(n):
"""find the largest prime divisor of n"""
for _ in list(divisors(n))[::-1]:
if is_prime(_):
return _
def cofactor(order, n):
return int(order/n)
prime_divisor(42)
39/3
is_prime(39)
e1 = dict(p=29, a=-1, b=1)
order(**e1)
prime_divisor(37)
cofactor(37, 37)
N_ = order(**e1)
N_
H__ = (12, 8)
G__ = multiply_mod(H__, 37, **e1)
G__
Since
Suppose alice has public
d_a = 5
H_a = multiply_mod(H__, d_a, **e1)
d_b = 9
H_b = multiply_mod(H__, d_b, **e1)
S_b = multiply_mod(H_a, d_b, **e1) # Bob generates secret key using Alice's pub key
S_a = multiply_mod(H_b, d_a, **e1) # Alice generates secret key using Bob's pub key
assert S_a == S_b # check that Alice and Bob get the same result
S_a
- choose
$k \in [1, n]$ for subgroup order$n$ - calculate
$P=kG$ - r = P_x mod n # get x coordinate of P (unless
$r=0$ ) $s = k^{-1}(z+rd_A) modn$
from random import randrange
G_ = (9,24) # generator
n_ = subgroup_order(G_, **e1) # should be 37 for {'p': 29, 'a': -1, 'b': 1}
G_, n_
e1
d_a = 5 #alice's private key
H_a = multiply_mod(G_, d_a, **e1) #alice's pub key
H_a
import random
def get_rk(G_, n_, seed=0, k=None, **ec):
"""find point for subgroup order n, random integer k in [1,n] and elliptic curve ec"""
random.seed(seed)
r = 0
if k is None:
while r == 0:
k = randrange(n_)%n_ # random in [1,n]
P_ = multiply_mod(G_, k, **ec)
r, _ = P_
r = r%n_
else:
P_ = multiply_mod(G_, k, **ec)
r, _ = P_
r = r%n_
return P_, k
P_, k = get_rk(G_, n_, k=3, **e1)
r, _ = P_
print(r, k)
multiply_mod(G_, 3, **e1)
assert P_ == multiply_mod(G_, k, **e1)
k_inv = modinv(k, n_) # only works for n_ prime
k_inv
def get_s(k, z, r, d_a, n):
k_inv = modinv(k, n)
return (k_inv*((z%n_ + (r*d_a)%n_)%n_))%n_
z = randrange(n_)%n_ # represents hash with same bitlength as n_
s = get_s(k, z, r, d_a, n_) # s = k^-1(z+r*d_a) mod n_
assert k == get_s(s, z, r, d_a, n_) # k = s^-1(z+r*d_a) mod_n
sig_az = (r, s) # alice's signature on z message
sig_az
To verify signature:
$u_1 = s^{-1}z \text{modn}$ $u_2 = s^{-1}r \text{modn}$ $P = u_1 G + u_2 H_a$
u_1 = (modinv(s, n_)*z)%n_
u_1
u_2 = (modinv(s, n_)*r)%n_
u_2
multiply_mod(G_, u_1, **e1), multiply_mod(H_a, u_2, **e1)
a_ = multiply_mod(G_, u_1, **e1)
a_
b_ = multiply_mod(H_a, u_2, **e1)
b_
add_mod(a_, b_, **e1)
P_result = add_mod(multiply_mod(G_, u_1, **e1), multiply_mod(H_a, u_2, **e1), **e1)
P_result
if r != P_result[0]%n_:
raise AssertionError(f"signature does not match! {r} != {P_result[0]}")
Requires that you cloned Jimmy's repo into a directory next to the base of the repo
import sys
sys.path.append('../programmingbitcoin/code-ch03/')
from ecc import Point, FieldElement
Choose the generator point
n_
n_
G_ = Point(FieldElement(9, e1['p']),
FieldElement(24, e1['p']),
FieldElement(e1['a']%e1['p'], e1['p']),
FieldElement(e1['b']%e1['p'], e1['p'])
)
G_
add_mod((12, 8), (12, 8), **e1)
P_test = Point(FieldElement(12, e1['p']),
FieldElement(8, e1['p']),
FieldElement(e1['a']%e1['p'], e1['p']),
FieldElement(e1['b']%e1['p'], e1['p'])
)
P_test + P_test
P_ = (9,24)
G_test = G_
for i in range(1,15):
print(G_test, P_)
P_ = add_mod(P_, P_, **e1)
G_test = G_test + G_test
m_ = 3
m_ >>= 1
m_
Choose k
k = 3
P_ = k*G_
P_
r = P_.x.num%n_
Set Alice's priv key
d_a = 5
d_a
Compute Alice's pub key
H_a = d_a*G_
H_a
Get Alice's signature
s = get_s(k, z, r, d_a, n_)
assert k == get_s(s, z, r, d_a, n_) # check math
s
Check signature
u_1 = (modinv(s, n_)*z)%n_
u_1
u_2 = (modinv(s, n_)*r)%n_
u_2
assert u_1*G_ + u_2*H_a == P_ # signature confirmed!
import dash_bootstrap_components as dbc
from psidash.psidash import load_app
app = load_app(__name__, 'elliptic.yaml')
if __name__ == '__main__':
app.run_server(host='0.0.0.0',
port=8050,
mode='external',
extra_files=["elliptic.yaml", "elliptic/dashboard.py"],
debug=True)
from elliptic.dashboard import get_primes
for i, p in enumerate(get_primes(100)):
if p == 37:
print(i, p)
from elliptic.dashboard import elliptic, point_in_curve, subgroup_order, order
a = elliptic(37, 0, 7)
P = point_in_curve(5, 24, 37, 0, 7)
P.x.prime
import numpy as np
%load_ext autoreload
%autoreload 2
$ (3, 4) $
Adding mod inverse to the multiplication page
def extended_gcd(aa, bb):
# from https://rosettacode.org/wiki/Modular_inverse#Python
lastremainder, remainder = abs(aa), abs(bb)
x, lastx, y, lasty = 0, 1, 1, 0
while remainder:
lastremainder, (quotient, remainder) = remainder, divmod(lastremainder, remainder)
x, lastx = lastx - quotient*x, x
y, lasty = lasty - quotient*y, y
return lastremainder, lastx * (-1 if aa < 0 else 1), lasty * (-1 if bb < 0 else 1)
def modinv(a, m):
# from https://rosettacode.org/wiki/Modular_inverse#Python
g, x, y = extended_gcd(a, m)
if g != 1:
raise ValueError
return x % m
def modinv_song(a, b):
"""from Jimmy Song"""
return pow(a, b - 2, b)
p_ = 37
for _ in [-3, 5, 10, 36, 37, 39]:
try:
assert (modinv_song(_, p_)*(_))%p_ == 1
print(f"{_}^-1 mod {p_} = {modinv_song(_, p_)}")
except ValueError:
print(f'ValueError: could not calculate {_}^-1 mod {p_}')
except AssertionError:
print(f'AssertionError: could not calculate {_}^-1 mod {p_}')
with Jimmy's version, mod inverse of 37 returns 0. With the rosetta version, modinv of 37 raises a value error. I guess it makes sense not to divide by zero in the first place.
modinv_song(0, 37)
modinv_song(37, 37)
See if
Must choose a prime field!
G_ = Point(x=FieldElement(18, 37),
y=FieldElement(17, 37),
a=FieldElement(0, 37),
b=FieldElement(7, 37)
)
G_
The above point has a prime subfield of size 13
k = 3
n_ = 13
for k in [-3%n_, 3, 5, 7, 15]:
assert k*(modinv(k, n_)*G_) == G_
for k in [-3%n_, 3, 5, 7, 15]:
assert k*(modinv_song(k, n_)*G_) == G_
The above tests pass! We should be able to add the invsere to the dashboard, so long as we're taking n to be the size of the subgroup!