์„œ๋ช…, ๊ฒ€์ฆ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์•Œ์•˜์œผ๋‹ˆ ํ•„์š”ํ•œ ํด๋ž˜์Šค๋“ค์„ ์ž‘์„ฑํ•ด๋ณด์ž.

Signature

class Signature:
 
    def __init__(self, r, s):
        self.r = r
        self.s = s
 
    def __repr__(self):
        return 'Signature({:x},{:x})'.format(self.r, self.s)
  • ์„œ๋ช…์˜ ๊ฒฝ์šฐ ์„œ๋ช…ํ•˜๋Š” ์‚ฌ๋žŒ์ด ๋„ฃ์–ด์„œ ์ฃผ๋Š” ๊ฐ’์ด๋‹ค.

Verify

  • ํƒ€์› ๊ณก์„  ์œ„์— ์žˆ๋Š” ์ ์„ ๋‚˜ํƒ€๋‚ด๋Š” S256Point์— ๊ฒ€์ฆ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.
class S256Point(Point): # ํƒ€์› ๊ณก์„  ์œ„ ์  ์ƒ์†
    ...
 
    def verify(self, z, sig):
        s_inv = pow(sig.s, N - 2, N)  # <1>
        u = z * s_inv % N  # <2>
        v = sig.r * s_inv % N  # <3>
        total = u * G + v * self  # <4>
        return total.x.num == sig.r  # <5>
  1. ํŽ˜๋ฅด๋งˆ์˜ ์†Œ๊ณต์‹์„ ํ†ตํ•ด inverse๋ฅผ 0์„ ํฌํ•จํ•œ ์ •์ˆ˜ ์ง€์ˆ˜๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.
  2. u, v๋ฅผ ๊ตฌํ•œ๋‹ค.
  3. u, v ๋‘˜๋‹ค ์œ„์ˆ˜์ธ N์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.
  4. ์ขŒ๋ณ€์„ ๊ณ„์‚ฐํ•œ๋‹ค. ()
  5. ๊ฒฐ๊ณผ๊ฐ’๊ณผ ์„ ๋น„๊ตํ•œ๋‹ค.

PrivateKey

  • ์„œ๋ช…์„ ์ƒ์„ฑํ•œ PrivateKey ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์ž.
  • ๋น„๋ฐ€ํ‚ค๋ฅผ ๋ณด๊ด€ํ•˜๋Š” ํด๋ž˜์Šค๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด์ฃผ๊ณ , ๊ฑฐ๊ธฐ์„œ ์„œ๋ช…์„ ์ƒ์„ฑํ•˜์—ฌ ๊ฒ€์ฆ์ž์—๊ฒŒ ์ „๋‹ฌํ•˜์ž.
  • ๊ฒ€์ฆ์ž๋Š” Public Key์— ํ•ด๋‹นํ•˜๋Š” ํƒ€์›์œ„์˜ ์ (S256Point)์— ์žˆ๋Š” ๋ฉ”์„œ๋“œ์ธ verify๋กœ ๊ทธ ์„œ๋ช…์„ ๋„˜๊ฒจ์ฃผ๋ฉด ๋์ด๋‹ค.
class PrivateKey:
 
    def __init__(self, secret):
        self.secret = secret
        self.point = secret * G  # ๊ณต๊ฐœํ‚ค๋ฅผ ๊ณ„์‚ฐ ํ›„ ๋ณด๊ด€ํ•œ๋‹ค.
 
    def hex(self):
        return '{:x}'.format(self.secret).zfill(64)
    
 
 
    def sign(self, z):
        k = self.deterministic_k(z)  # ๊ทธ๋ƒฅ ๋ฌด์ž‘์œ„๋กœ ๊ฒฐ์ •ํ•˜๋ฉด ์•ˆ๋œ๋‹ค. k๊ฐ€ ๋“คํ‚ค๋ฉด e๋„ ํ„ธ๋ฆฐ๋‹ค. ์„œ๋ช…๋งˆ๋‹ค k๋„ ๋‹ฌ๋ผ์•ผํ•œ๋‹ค. ์•„๋‹ˆ๋ฉด ํ„ธ๋ฆฐ๋‹ค.
        r = (k * G).x.num
        k_inv = pow(k, N - 2, N)
        s = (z + r * self.secret) * k_inv % N
        if s > N / 2: # ๊ฐ€๋ณ€์„ฑ ๋ฌธ์ œ๋กœ N/x๋ณด๋‹ค ์ž‘์€ s๊ฐ’์„ ์‚ฌ์šฉํ•œ๋‹ค.
            s = N - s
        return Signature(r, s)
 
    def deterministic_k(self, z): # ์„œ๋ช… ๋งˆ๋‹ค ๋‹ค๋ฅธ k๊ฐ’์„ ๋ช…์‹œ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ
        k = b'\x00' * 32
        v = b'\x01' * 32
        if z > N:
            z -= N
        z_bytes = z.to_bytes(32, 'big')
        secret_bytes = self.secret.to_bytes(32, 'big')
        s256 = hashlib.sha256
        k = hmac.new(k, v + b'\x00' + secret_bytes + z_bytes, s256).digest()
        v = hmac.new(k, v, s256).digest()
        k = hmac.new(k, v + b'\x01' + secret_bytes + z_bytes, s256).digest()
        v = hmac.new(k, v, s256).digest()
        while True:
            v = hmac.new(k, v, s256).digest()
            candidate = int.from_bytes(v, 'big')
            if candidate >= 1 and candidate < N:
                return candidate  # <2>
            k = hmac.new(k, v + b'\x00', s256).digest()
            v = hmac.new(k, v, s256).digest()
    
 

๊ฐ€๋ณ€์„ฑ ๋ฌธ์ œ

  • ์œ„์—์„œ s๊ฐ€ N/2๋ณด๋‹ค ์ž‘์€ s๋ฅผ ์„œ๋ช…์— ํฌํ•จํ–ˆ๋‹ค.
  • ์™œ ๊ตณ์ด ์ด๋Ÿฌ๋Š” ๊ฑธ๊นŒ?
  • ์šฐ๋ฆฌ๊ฐ€ ์„œ๋ช…์„ ๋งŒ๋“ ๋‹ค๋Š” ๊ฒƒ์€ ์–ด๋– ํ•œ ์ด์‚ฐ ๋กœ๊ทธ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ณ€์ˆ˜๋ฅผ ์ค€๋‹ค๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค.
  • ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ด์‚ฐ ๋กœ๊ทธ ๋ฌธ์ œ๋Š” ๊ทผ๋ณธ์ ์œผ๋กœ ํƒ€์› ๊ณก์„  ์œ„์— ์žˆ๊ฒŒ ๋œ๋‹ค.
  • ํƒ€์› ๊ณก์„ ์˜ ํŠน์ง•์œผ๋กœ๋Š” x์ถ• ๋Œ€์นญ์ด ์žˆ๋Š”๋ฐ, ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ™์€ x๊ฐ’์— ๋Œ€ํ•ด ๊ณก์„ ์œ„์— ์žˆ๋Š” ์ ์€ 2๊ฐœ๊ฐ€ ๋˜๊ฒŒ ๋œ๋‹ค.
  • ์ด๋Š” ์„œ๋ช…์œผ๋กœ ๊ฐ’์„ ์ƒ์„ฑํ•  ๋•Œ๋„ ๊ฐ™์€ ํŠน์„ฑ์„ ๋ˆ๋‹ค.
  • ์ฆ‰, ์— ๋Œ€ํ•ด ์ด์‚ฐ ๋กœ๊ทธ ๋ฌธ์ œ๋ฅผ ๋งŒ์กฑํ•˜๋Š” ํ•ด๋Š” , ๋กœ ๋‘๊ฐœ๋ผ๋Š” ๊ฒƒ์ด๋‹ค. ๋‘๊ฐœ์˜ ์œ ํšจํ•œ ์„œ๋ช…์ด ์žˆ๋Š” ๊ฒƒ.
  • ์ด๋ ‡๊ฒŒ 2๊ฐœ๊ฐ€ ๋˜์–ด ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ (์ด๊ฑด ์•„์ง ๋ชจ๋ฅด๊ฒ ๋‹ค.)๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด ์ž‘์€ s๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

Code

FieldElement

 
class FieldElement:
 
    def __init__(self, num, prime):
        if num >= prime or num < 0:
            error = 'Num {} not in field range 0 to {}'.format(
                num, prime - 1)
            raise ValueError(error)
        self.num = num
        self.prime = prime
 
    def __repr__(self):
        return 'FieldElement_{}({})'.format(self.prime, self.num)
 
    def __eq__(self, other):
        if other is None:
            return False
        return self.num == other.num and self.prime == other.prime
 
    def __ne__(self, other):
        # this should be the inverse of the == operator
        return not (self == other)
 
    def __add__(self, other):
        if self.prime != other.prime:
            raise TypeError('Cannot add two numbers in different Fields')
        # self.num and other.num are the actual values
        # self.prime is what we need to mod against
        num = (self.num + other.num) % self.prime
        # We return an element of the same class
        return self.__class__(num, self.prime)
 
    def __sub__(self, other):
        if self.prime != other.prime:
            raise TypeError('Cannot subtract two numbers in different Fields')
        # self.num and other.num are the actual values
        # self.prime is what we need to mod against
        num = (self.num - other.num) % self.prime
        # We return an element of the same class
        return self.__class__(num, self.prime)
 
    def __mul__(self, other):
        if self.prime != other.prime:
            raise TypeError('Cannot multiply two numbers in different Fields')
        # self.num and other.num are the actual values
        # self.prime is what we need to mod against
        num = (self.num * other.num) % self.prime
        # We return an element of the same class
        return self.__class__(num, self.prime)
 
    def __pow__(self, exponent):
        n = exponent % (self.prime - 1)
        num = pow(self.num, n, self.prime)
        return self.__class__(num, self.prime)
 
    def __truediv__(self, other):
        if self.prime != other.prime:
            raise TypeError('Cannot divide two numbers in different Fields')
        # self.num and other.num are the actual values
        # self.prime is what we need to mod against
        # use fermat's little theorem:
        # self.num**(p-1) % p == 1
        # this means:
        # 1/n == pow(n, p-2, p)
        num = (self.num * pow(other.num, self.prime - 2, self.prime)) % self.prime
        # We return an element of the same class
        return self.__class__(num, self.prime)
 
    def __rmul__(self, coefficient):
        num = (self.num * coefficient) % self.prime
        return self.__class__(num=num, prime=self.prime)

Point

class Point:
 
    def __init__(self, x, y, a, b):
        self.a = a
        self.b = b
        self.x = x
        self.y = y
        if self.x is None and self.y is None:
            return
        if self.y**2 != self.x**3 + a * x + b:
            raise ValueError('({}, {}) is not on the curve'.format(x, y))
    
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y \
            and self.a == other.a and self.b == other.b
 
    def __ne__(self, other):
        # this should be the inverse of the == operator
        return not (self == other)
 
    def __repr__(self):
        if self.x is None:
            return 'Point(infinity)'
        elif isinstance(self.x, FieldElement):
            return 'Point({},{})_{}_{} FieldElement({})'.format(
                self.x.num, self.y.num, self.a.num, self.b.num, self.x.prime)
        else:
            return 'Point({},{})_{}_{}'.format(self.x, self.y, self.a, self.b)
 
    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)
 
 
    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

Constants For Bitcoin

 
A = 0
B = 7
P = 2**256 - 2**32 - 977
N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
 

S256Field

 
class S256Field(FieldElement): # ์œ ํ•œ์ฒด ์ƒ์†
 
    def __init__(self, num, prime=None):
        super().__init__(num=num, prime=P) # ์ดˆ๊ธฐํ™”์‹œ ์ „์—ญ ๋ณ€์ˆ˜๋กœ ๋น ์ ธ์žˆ๋Š” P๋ฅผ ๋„ฃ์–ด๋ฒ„๋ฆผ
 
    def __repr__(self):
        return '{:x}'.format(self.num).zfill(64) # ์ถœ๋ ฅ์‹œ ๋นˆ์ž๋ฆฌ๋ฅผ 0์œผ๋กœ ์ฑ„์šฐ๋„๋ก ํ•จ
 
class S256Point(Point): # ํƒ€์› ๊ณก์„  ์œ„ ์  ์ƒ์†
 
    def __init__(self, x, y, a=None, b=None):
        a, b = S256Field(A), S256Field(B) # a, b๋ฅผ ์ „์—ญ๋ณ€์ˆ˜๋กœ ๋Œ€์ฒด
        if type(x) == int:
            super().__init__(x=S256Field(x), y=S256Field(y), a=a, b=b)
        else:
            super().__init__(x=x, y=y, a=a, b=b)  # ๋ฌดํ•œ ์›์ ์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๊ฒฝ์šฐ (x == None)์„ ์œ„ํ•ด ๋ถ„๊ธฐ
    
 
    def __repr__(self):
        if self.x is None:
            return 'S256Point(infinity)'
        else:
            return 'S256Point({}, {})'.format(self.x, self.y)
 
 
    def __rmul__(self, coefficient):
        coef = coefficient % N  # nG๊ฐ€ 0์ด๋‹ˆ, ์œ ํ™˜์ˆœํ™˜๊ตฐ์•ˆ์—์„œ ๋‚˜๋จธ์ง€๋กœ ๋‚˜๋ˆ„์–ด ์ฒ˜๋ฆฌํ•ด๋„ ๋ฌด๋ฐฉํ•˜๋‹ค. 
        return super().__rmul__(coef)
 
    def verify(self, z, sig):
        s_inv = pow(sig.s, N - 2, N)  # <1>
        u = z * s_inv % N  # <2>
        v = sig.r * s_inv % N  # <3>
        total = u * G + v * self  # <4>
        return total.x.num == sig.r  # <5>
    

Constants For Bitcoin Signature

 
G = S256Point(
    0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
    0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)
 

Signature

 
class Signature:
 
    def __init__(self, r, s):
        self.r = r
        self.s = s
 
    def __repr__(self):
        return 'Signature({:x},{:x})'.format(self.r, self.s)
 

PrivateKey

 
class PrivateKey:
 
    def __init__(self, secret):
        self.secret = secret
        self.point = secret * G  # <1>
 
    def hex(self):
        return '{:x}'.format(self.secret).zfill(64)
 
    def sign(self, z):
        k = self.deterministic_k(z)  # <1>
        r = (k * G).x.num
        k_inv = pow(k, N - 2, N)
        s = (z + r * self.secret) * k_inv % N
        if s > N / 2:
            s = N - s
        return Signature(r, s)
 
    def deterministic_k(self, z):
        k = b'\x00' * 32
        v = b'\x01' * 32
        if z > N:
            z -= N
        z_bytes = z.to_bytes(32, 'big')
        secret_bytes = self.secret.to_bytes(32, 'big')
        s256 = hashlib.sha256
        k = hmac.new(k, v + b'\x00' + secret_bytes + z_bytes, s256).digest()
        v = hmac.new(k, v, s256).digest()
        k = hmac.new(k, v + b'\x01' + secret_bytes + z_bytes, s256).digest()
        v = hmac.new(k, v, s256).digest()
        while True:
            v = hmac.new(k, v, s256).digest()
            candidate = int.from_bytes(v, 'big')
            if candidate >= 1 and candidate < N:
                return candidate  # <2>
            k = hmac.new(k, v + b'\x00', s256).digest()
            v = hmac.new(k, v, s256).digest()

Reference