mirror of
https://github.com/bitcoin/bips.git
synced 2026-02-23 15:38:22 +00:00
Add BIP324
This commit is contained in:
BIN
bip-0324/garbage_terminator.png
Normal file
BIN
bip-0324/garbage_terminator.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 261 KiB |
8
bip-0324/packet_encoding_test_vectors.csv
Normal file
8
bip-0324/packet_encoding_test_vectors.csv
Normal file
File diff suppressed because one or more lines are too long
575
bip-0324/reference.py
Normal file
575
bip-0324/reference.py
Normal file
@@ -0,0 +1,575 @@
|
||||
import sys
|
||||
import random
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
### BIP-340 tagged hash
|
||||
|
||||
def TaggedHash(tag, data):
|
||||
"""Compute BIP-340 tagged hash with specified tag string of data."""
|
||||
ss = hashlib.sha256(tag.encode('utf-8')).digest()
|
||||
ss += ss
|
||||
ss += data
|
||||
return hashlib.sha256(ss).digest()
|
||||
|
||||
### HKDF-SHA256
|
||||
|
||||
def hmac_sha256(key, data):
|
||||
"""Compute HMAC-SHA256 from specified byte arrays key and data."""
|
||||
return hmac.new(key, data, hashlib.sha256).digest()
|
||||
|
||||
def hkdf_sha256(length, ikm, salt, info):
|
||||
"""Derive a key using HKDF-SHA256."""
|
||||
if len(salt) == 0:
|
||||
salt = bytes([0] * 32)
|
||||
prk = hmac_sha256(salt, ikm)
|
||||
t = b""
|
||||
okm = b""
|
||||
for i in range((length + 32 - 1) // 32):
|
||||
t = hmac_sha256(prk, t + info + bytes([i + 1]))
|
||||
okm += t
|
||||
return okm[:length]
|
||||
|
||||
### secp256k1 field/group elements
|
||||
|
||||
def modinv(a, n):
|
||||
"""Compute the modular inverse of a modulo n using the extended Euclidean
|
||||
Algorithm. See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers.
|
||||
"""
|
||||
a = a % n
|
||||
if a == 0:
|
||||
return 0
|
||||
if sys.hexversion >= 0x3080000:
|
||||
# More efficient version available in Python 3.8.
|
||||
return pow(a, -1, n)
|
||||
t1, t2 = 0, 1
|
||||
r1, r2 = n, a
|
||||
while r2 != 0:
|
||||
q = r1 // r2
|
||||
t1, t2 = t2, t1 - q * t2
|
||||
r1, r2 = r2, r1 - q * r2
|
||||
if r1 > 1:
|
||||
return None
|
||||
if t1 < 0:
|
||||
t1 += n
|
||||
return t1
|
||||
|
||||
class FE:
|
||||
"""Objects of this class represent elements of the field GF(2**256 - 2**32 - 977).
|
||||
|
||||
They are represented internally in numerator / denominator form, in order to delay inversions.
|
||||
"""
|
||||
|
||||
SIZE = 2**256 - 2**32 - 977
|
||||
|
||||
def __init__(self, a=0, b=1):
|
||||
"""Initialize an FE as a/b; both a and b can be ints or field elements."""
|
||||
if isinstance(b, FE):
|
||||
if isinstance(a, FE):
|
||||
self.num = (a.num * b.den) % FE.SIZE
|
||||
self.den = (a.den * b.num) % FE.SIZE
|
||||
else:
|
||||
self.num = (a * b.den) % FE.SIZE
|
||||
self.den = a.num
|
||||
else:
|
||||
b = b % FE.SIZE
|
||||
assert b != 0
|
||||
if isinstance(a, FE):
|
||||
self.num = a.num
|
||||
self.den = (a.den * b) % FE.SIZE
|
||||
else:
|
||||
self.num = a % FE.SIZE
|
||||
self.den = b
|
||||
|
||||
def __add__(self, a):
|
||||
"""Compute the sum of two field elements (second may be int)."""
|
||||
if isinstance(a, FE):
|
||||
return FE(self.num * a.den + self.den * a.num, self.den * a.den)
|
||||
else:
|
||||
return FE(self.num + self.den * a, self.den)
|
||||
|
||||
def __radd__(self, a):
|
||||
"""Compute the sum of an integer and a field element."""
|
||||
return FE(self.num + self.den * a, self.den)
|
||||
|
||||
def __sub__(self, a):
|
||||
"""Compute the difference of two field elements (second may be int)."""
|
||||
if isinstance(a, FE):
|
||||
return FE(self.num * a.den - self.den * a.num, self.den * a.den)
|
||||
else:
|
||||
return FE(self.num - self.den * a, self.den)
|
||||
|
||||
def __rsub__(self, a):
|
||||
"""Compute the difference between an integer and a field element."""
|
||||
return FE(self.den * a - self.num, self.den)
|
||||
|
||||
def __mul__(self, a):
|
||||
"""Compute the product of two field elements (second may be int)."""
|
||||
if isinstance(a, FE):
|
||||
return FE(self.num * a.num, self.den * a.den)
|
||||
else:
|
||||
return FE(self.num * a, self.den)
|
||||
|
||||
def __rmul__(self, a):
|
||||
"""Compute the product of an integer with a field element."""
|
||||
return FE(self.num * a, self.den)
|
||||
|
||||
def __truediv__(self, a):
|
||||
"""Compute the ratio of two field elements (second may be int)."""
|
||||
return FE(self, a)
|
||||
|
||||
def __rtruediv__(self, a):
|
||||
"""Compute the ratio of an integer and a field element."""
|
||||
return FE(a, self)
|
||||
|
||||
def __pow__(self, a):
|
||||
"""Raise a field element to a (positive) integer power."""
|
||||
return FE(pow(self.num, a, FE.SIZE), pow(self.den, a, FE.SIZE))
|
||||
|
||||
def __neg__(self):
|
||||
"""Negate a field element."""
|
||||
return FE(-self.num, self.den)
|
||||
|
||||
def __int__(self):
|
||||
"""Convert a field element to an integer. The result is cached."""
|
||||
if self.den != 1:
|
||||
self.num = (self.num * modinv(self.den, FE.SIZE)) % FE.SIZE
|
||||
self.den = 1
|
||||
return self.num
|
||||
|
||||
def sqrt(self):
|
||||
"""Compute the square root of a field element.
|
||||
|
||||
Due to the fact that our modulus is of the form (p % 4) == 3, the Tonelli-Shanks
|
||||
algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) is simply
|
||||
raising the argument to the power (p + 3) / 4."""
|
||||
v = int(self)
|
||||
s = pow(v, (FE.SIZE + 1) // 4, FE.SIZE)
|
||||
if s**2 % FE.SIZE == v:
|
||||
return FE(s)
|
||||
return None
|
||||
|
||||
def is_square(self):
|
||||
"""Determine if this field element has a square root."""
|
||||
# Compute the Jacobi symbol of (self / p). Since our modulus is prime, this
|
||||
# is the same as the Legendre symbol, which determines quadratic residuosity.
|
||||
# See https://en.wikipedia.org/wiki/Jacobi_symbol for the algorithm.
|
||||
n, k, t = (self.num * self.den) % FE.SIZE, FE.SIZE, 0
|
||||
if n == 0:
|
||||
return True
|
||||
while n != 0:
|
||||
while n & 1 == 0:
|
||||
n >>= 1
|
||||
r = k & 7
|
||||
t ^= (r == 3 or r == 5)
|
||||
n, k = k, n
|
||||
t ^= (n & k & 3 == 3)
|
||||
n = n % k
|
||||
assert k == 1
|
||||
return not t
|
||||
|
||||
def __eq__(self, a):
|
||||
"""Check whether two field elements are equal (second may be an int)."""
|
||||
if isinstance(a, FE):
|
||||
return (self.num * a.den - self.den * a.num) % FE.SIZE == 0
|
||||
else:
|
||||
return (self.num - self.den * a) % FE.SIZE == 0
|
||||
|
||||
def to_bytes(self):
|
||||
"""Convert a field element to 32-byte big endian encoding."""
|
||||
return int(self).to_bytes(32, 'big')
|
||||
|
||||
@staticmethod
|
||||
def from_bytes(b):
|
||||
"""Convert a 32-byte big endian encoding of a field element to an FE."""
|
||||
v = int.from_bytes(b, 'big')
|
||||
if v >= FE.SIZE:
|
||||
return None
|
||||
return FE(v)
|
||||
|
||||
class GE:
|
||||
"""Objects of this class represent points (group elements) on the secp256k1 curve.
|
||||
|
||||
The point at infinity is represented as None."""
|
||||
|
||||
ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
||||
ORDER_HALF = ORDER // 2
|
||||
|
||||
def __init__(self, x, y):
|
||||
"""Initialize a group element with specified x and y coordinates (must be on curve)."""
|
||||
fx = FE(x)
|
||||
fy = FE(y)
|
||||
assert fy**2 == fx**3 + 7
|
||||
self.x = fx
|
||||
self.y = fy
|
||||
|
||||
def double(self):
|
||||
"""Compute the double of a point."""
|
||||
l = 3 * self.x**2 / (2 * self.y)
|
||||
x3 = l**2 - 2 * self.x
|
||||
y3 = l * (self.x - x3) - self.y
|
||||
return GE(x3, y3)
|
||||
|
||||
def __add__(self, a):
|
||||
"""Add two points, or a point and infinity, together."""
|
||||
if a is None:
|
||||
# Adding point at infinity
|
||||
return self
|
||||
if self.x != a.x:
|
||||
# Adding distinct x coordinates
|
||||
l = (a.y - self.y) / (a.x - self.x)
|
||||
x3 = l**2 - self.x - a.x
|
||||
y3 = l * (self.x - x3) - self.y
|
||||
return GE(x3, y3)
|
||||
elif self.y == a.y:
|
||||
# Adding point to itself
|
||||
return self.double()
|
||||
else:
|
||||
# Adding point to its negation
|
||||
return None
|
||||
|
||||
def __radd__(self, a):
|
||||
"""Add infinity to a point."""
|
||||
assert a is None
|
||||
return self
|
||||
|
||||
def __mul__(self, a):
|
||||
"""Multiply a point with an integer (scalar multiplication)."""
|
||||
r = None
|
||||
for i in range(a.bit_length() - 1, -1, -1):
|
||||
if r is not None:
|
||||
r = r.double()
|
||||
if (a >> i) & 1:
|
||||
r += self
|
||||
return r
|
||||
|
||||
def __rmul__(self, a):
|
||||
"""Multiply an integer with a point (scalar multiplication)."""
|
||||
return self * a
|
||||
|
||||
@staticmethod
|
||||
def lift_x(x):
|
||||
"""Take an FE, and return the point with that as X coordinate, and square Y."""
|
||||
y = (FE(x)**3 + 7).sqrt()
|
||||
if y is None:
|
||||
return None
|
||||
return GE(x, y)
|
||||
|
||||
@staticmethod
|
||||
def is_valid_x(x):
|
||||
"""Determine whether the provided field element is a valid X coordinate."""
|
||||
return (FE(x)**3 + 7).is_square()
|
||||
|
||||
SECP256K1_G = GE(
|
||||
0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
|
||||
0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)
|
||||
|
||||
### ElligatorSwift
|
||||
|
||||
# Precomputed constant square root of -3 modulo p.
|
||||
MINUS_3_SQRT = FE(-3).sqrt()
|
||||
|
||||
def xswiftec(u, t):
|
||||
"""Decode field elements (u, t) to an X coordinate on the curve."""
|
||||
if u == 0:
|
||||
u = FE(1)
|
||||
if t == 0:
|
||||
t = FE(1)
|
||||
if u**3 + t**2 + 7 == 0:
|
||||
t = 2 * t
|
||||
X = (u**3 + 7 - t**2) / (2 * t)
|
||||
Y = (X + t) / (MINUS_3_SQRT * u)
|
||||
for x in (u + 4 * Y**2, (-X / Y - u) / 2, (X / Y - u) / 2):
|
||||
if GE.is_valid_x(x):
|
||||
return x
|
||||
assert False
|
||||
|
||||
def xswiftec_inv(x, u, case):
|
||||
"""Given x and u, find t such that xswiftec(u, t) = x, or return None.
|
||||
|
||||
Case selects which of the up to 8 results to return."""
|
||||
|
||||
if case & 2 == 0:
|
||||
if GE.is_valid_x(-x - u):
|
||||
return None
|
||||
v = x if case & 1 == 0 else -x - u
|
||||
s = -(u**3 + 7) / (u**2 + u*v + v**2)
|
||||
else:
|
||||
s = x - u
|
||||
if s == 0:
|
||||
return None
|
||||
r = (-s * (4 * (u**3 + 7) + 3 * s * u**2)).sqrt()
|
||||
if r is None:
|
||||
return None
|
||||
if case & 1:
|
||||
if r == 0:
|
||||
return None
|
||||
r = -r
|
||||
v = (-u + r / s) / 2
|
||||
w = s.sqrt()
|
||||
if w is None:
|
||||
return None
|
||||
if case & 4:
|
||||
w = -w
|
||||
return w * (u * (MINUS_3_SQRT - 1) / 2 - v)
|
||||
|
||||
def xelligatorswift(x):
|
||||
"""Given a field element X on the curve, find (u, t) that encode them."""
|
||||
while True:
|
||||
u = FE(random.randrange(1, GE.ORDER))
|
||||
case = random.randrange(0, 8)
|
||||
t = xswiftec_inv(x, u, case)
|
||||
if t is not None:
|
||||
return u, t
|
||||
|
||||
def ellswift_create():
|
||||
"""Generate a (privkey, ellswift_pubkey) pair."""
|
||||
priv = random.randrange(1, GE.ORDER)
|
||||
u, t = xelligatorswift((priv * SECP256K1_G).x)
|
||||
return priv.to_bytes(32, 'big'), u.to_bytes() + t.to_bytes()
|
||||
|
||||
def ellswift_ecdh_xonly(pubkey_theirs, privkey):
|
||||
"""Compute X coordinate of shared ECDH point between elswift pubkey and privkey."""
|
||||
u = FE(int.from_bytes(pubkey_theirs[:32], 'big'))
|
||||
t = FE(int.from_bytes(pubkey_theirs[32:], 'big'))
|
||||
d = int.from_bytes(privkey, 'big')
|
||||
return (d * GE.lift_x(xswiftec(u, t))).x.to_bytes()
|
||||
|
||||
### Poly1305
|
||||
|
||||
class Poly1305:
|
||||
"""Class representing a running poly1305 computation."""
|
||||
MODULUS = 2**130 - 5
|
||||
|
||||
def __init__(self, key):
|
||||
self.r = int.from_bytes(key[:16], 'little') & 0xffffffc0ffffffc0ffffffc0fffffff
|
||||
self.s = int.from_bytes(key[16:], 'little')
|
||||
self.acc = 0
|
||||
|
||||
def add(self, msg, length=None, pad=False):
|
||||
"""Add a message of any length. Input so far must be a multiple of 16 bytes."""
|
||||
length = len(msg) if length is None else length
|
||||
for i in range((length + 15) // 16):
|
||||
chunk = msg[i * 16:i * 16 + min(16, length - i * 16)]
|
||||
val = int.from_bytes(chunk, 'little') + 256**(16 if pad else len(chunk))
|
||||
self.acc = (self.r * (self.acc + val)) % Poly1305.MODULUS
|
||||
return self
|
||||
|
||||
def tag(self):
|
||||
"""Compute the poly1305 tag."""
|
||||
return ((self.acc + self.s) & 0xffffffffffffffffffffffffffffffff).to_bytes(16, 'little')
|
||||
|
||||
### ChaCha20
|
||||
|
||||
CHACHA20_INDICES = (
|
||||
(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15),
|
||||
(0, 5, 10, 15), (1, 6, 11, 12), (2, 7, 8, 13), (3, 4, 9, 14)
|
||||
)
|
||||
|
||||
CHACHA20_CONSTANTS = (0x61707865, 0x3320646e, 0x79622d32, 0x6b206574)
|
||||
|
||||
def rotl32(v, bits):
|
||||
"""Rotate the 32-bit value v left by bits bits."""
|
||||
return ((v << bits) & 0xffffffff) | (v >> (32 - bits))
|
||||
|
||||
def chacha20_doubleround(s):
|
||||
"""Apply a ChaCha20 double round to 16-element state array s.
|
||||
|
||||
See https://cr.yp.to/chacha/chacha-20080128.pdf and https://tools.ietf.org/html/rfc8439
|
||||
"""
|
||||
for a, b, c, d in CHACHA20_INDICES:
|
||||
s[a] = (s[a] + s[b]) & 0xffffffff
|
||||
s[d] = rotl32(s[d] ^ s[a], 16)
|
||||
s[c] = (s[c] + s[d]) & 0xffffffff
|
||||
s[b] = rotl32(s[b] ^ s[c], 12)
|
||||
s[a] = (s[a] + s[b]) & 0xffffffff
|
||||
s[d] = rotl32(s[d] ^ s[a], 8)
|
||||
s[c] = (s[c] + s[d]) & 0xffffffff
|
||||
s[b] = rotl32(s[b] ^ s[c], 7)
|
||||
|
||||
def chacha20_block(key, nonce, cnt):
|
||||
"""Compute the 64-byte output of the ChaCha20 block function.
|
||||
|
||||
Takes as input a 32-byte key, 12-byte nonce, and 32-bit integer counter.
|
||||
"""
|
||||
# Initial state.
|
||||
init = [0 for _ in range(16)]
|
||||
for i in range(4):
|
||||
init[i] = CHACHA20_CONSTANTS[i]
|
||||
for i in range(8):
|
||||
init[4 + i] = int.from_bytes(key[4 * i:4 * (i+1)], 'little')
|
||||
init[12] = cnt
|
||||
for i in range(3):
|
||||
init[13 + i] = int.from_bytes(nonce[4 * i:4 * (i+1)], 'little')
|
||||
# Perform 20 rounds.
|
||||
state = [v for v in init]
|
||||
for _ in range(10):
|
||||
chacha20_doubleround(state)
|
||||
# Add initial values back into state.
|
||||
for i in range(16):
|
||||
state[i] = (state[i] + init[i]) & 0xffffffff
|
||||
# Produce byte output
|
||||
return b''.join(state[i].to_bytes(4, 'little') for i in range(16))
|
||||
|
||||
### ChaCha20Poly1305
|
||||
|
||||
def aead_chacha20_poly1305_encrypt(key, nonce, aad, plaintext):
|
||||
"""Encrypt a plaintext using ChaCha20Poly1305."""
|
||||
ret = bytearray()
|
||||
msg_len = len(plaintext)
|
||||
for i in range((msg_len + 63) // 64):
|
||||
now = min(64, msg_len - 64 * i)
|
||||
keystream = chacha20_block(key, nonce, i + 1)
|
||||
for j in range(now):
|
||||
ret.append(plaintext[j + 64 * i] ^ keystream[j])
|
||||
poly1305 = Poly1305(chacha20_block(key, nonce, 0)[:32])
|
||||
poly1305.add(aad, pad=True).add(ret, pad=True)
|
||||
poly1305.add(len(aad).to_bytes(8, 'little') + msg_len.to_bytes(8, 'little'))
|
||||
ret += poly1305.tag()
|
||||
return bytes(ret)
|
||||
|
||||
def aead_chacha20_poly1305_decrypt(key, nonce, aad, ciphertext):
|
||||
"""Decrypt a ChaCha20Poly1305 ciphertext."""
|
||||
if len(ciphertext) < 16:
|
||||
return None
|
||||
msg_len = len(ciphertext) - 16
|
||||
poly1305 = Poly1305(chacha20_block(key, nonce, 0)[:32])
|
||||
poly1305.add(aad, pad=True)
|
||||
poly1305.add(ciphertext, length=msg_len, pad=True)
|
||||
poly1305.add(len(aad).to_bytes(8, 'little') + msg_len.to_bytes(8, 'little'))
|
||||
if ciphertext[-16:] != poly1305.tag():
|
||||
return None
|
||||
ret = bytearray()
|
||||
for i in range((msg_len + 63) // 64):
|
||||
now = min(64, msg_len - 64 * i)
|
||||
keystream = chacha20_block(key, nonce, i + 1)
|
||||
for j in range(now):
|
||||
ret.append(ciphertext[j + 64 * i] ^ keystream[j])
|
||||
return bytes(ret)
|
||||
|
||||
### FSChaCha20{,Poly1305}
|
||||
|
||||
REKEY_INTERVAL = 224 # packets
|
||||
|
||||
class FSChaCha20Poly1305:
|
||||
"""Rekeying wrapper AEAD around ChaCha20Poly1305."""
|
||||
|
||||
def __init__(self, initial_key):
|
||||
self.key = initial_key
|
||||
self.packet_counter = 0
|
||||
|
||||
def crypt(self, aad, text, is_decrypt):
|
||||
nonce = ((self.packet_counter % REKEY_INTERVAL).to_bytes(4, 'little') +
|
||||
(self.packet_counter // REKEY_INTERVAL).to_bytes(8, 'little'))
|
||||
if is_decrypt:
|
||||
ret = aead_chacha20_poly1305_decrypt(self.key, nonce, aad, text)
|
||||
else:
|
||||
ret = aead_chacha20_poly1305_encrypt(self.key, nonce, aad, text)
|
||||
if (self.packet_counter + 1) % REKEY_INTERVAL == 0:
|
||||
rekey_nonce = b"\xFF\xFF\xFF\xFF" + nonce[4:]
|
||||
newkey1 = aead_chacha20_poly1305_encrypt(self.key, rekey_nonce, b"", b"\x00" * 32)[:32]
|
||||
newkey2 = chacha20_block(self.key, rekey_nonce, 1)[:32]
|
||||
assert newkey1 == newkey2
|
||||
self.key = newkey1
|
||||
self.packet_counter += 1
|
||||
return ret
|
||||
|
||||
def decrypt(self, aad, ciphertext):
|
||||
return self.crypt(aad, ciphertext, True)
|
||||
|
||||
def encrypt(self, aad, plaintext):
|
||||
return self.crypt(aad, plaintext, False)
|
||||
|
||||
|
||||
class FSChaCha20:
|
||||
"""Rekeying wrapper stream cipher around ChaCha20."""
|
||||
|
||||
def __init__(self, initial_key):
|
||||
self.key = initial_key
|
||||
self.block_counter = 0
|
||||
self.chunk_counter = 0
|
||||
self.keystream = b''
|
||||
|
||||
def get_keystream_bytes(self, nbytes):
|
||||
while len(self.keystream) < nbytes:
|
||||
nonce = ((0).to_bytes(4, 'little') +
|
||||
(self.chunk_counter // REKEY_INTERVAL).to_bytes(8, 'little'))
|
||||
self.keystream += chacha20_block(self.key, nonce, self.block_counter)
|
||||
self.block_counter += 1
|
||||
ret = self.keystream[:nbytes]
|
||||
self.keystream = self.keystream[nbytes:]
|
||||
return ret
|
||||
|
||||
def crypt(self, chunk):
|
||||
ks = self.get_keystream_bytes(len(chunk))
|
||||
ret = bytes([ks[i] ^ chunk[i] for i in range(len(chunk))])
|
||||
if ((self.chunk_counter + 1) % REKEY_INTERVAL) == 0:
|
||||
self.key = self.get_keystream_bytes(32)
|
||||
self.block_counter = 0
|
||||
self.chunk_counter += 1
|
||||
return ret
|
||||
|
||||
### Shared secret computation
|
||||
|
||||
def v2_ecdh(priv, ellswift_theirs, ellswift_ours, initiating):
|
||||
"""Compute BIP324 shared secret."""
|
||||
|
||||
ecdh_point_x32 = ellswift_ecdh_xonly(ellswift_theirs, priv)
|
||||
if initiating:
|
||||
# Initiating, place our public key encoding first.
|
||||
return TaggedHash("bip324_ellswift_xonly_ecdh",
|
||||
ellswift_ours + ellswift_theirs + ecdh_point_x32)
|
||||
else:
|
||||
# Responding, place their public key encoding first.
|
||||
return TaggedHash("bip324_ellswift_xonly_ecdh",
|
||||
ellswift_theirs + ellswift_ours + ecdh_point_x32)
|
||||
|
||||
### Key derivation
|
||||
|
||||
NETWORK_MAGIC = b'\xf9\xbe\xb4\xd9'
|
||||
|
||||
def initialize_v2_transport(ecdh_secret, initiating):
|
||||
"""Return a peer object with various BIP324 derived keys and ciphers."""
|
||||
|
||||
peer = {}
|
||||
salt = b'bitcoin_v2_shared_secret' + NETWORK_MAGIC
|
||||
for name, length in (
|
||||
('initiator_L', 32), ('initiator_P', 32), ('responder_L', 32), ('responder_P', 32),
|
||||
('garbage_terminators', 32), ('session_id', 32)):
|
||||
peer[name] = hkdf_sha256(
|
||||
salt=salt, ikm=ecdh_secret, info=name.encode('utf-8'), length=length)
|
||||
peer['initiator_garbage_terminator'] = peer['garbage_terminators'][:16]
|
||||
peer['responder_garbage_terminator'] = peer['garbage_terminators'][16:]
|
||||
del peer['garbage_terminators']
|
||||
if initiating:
|
||||
peer['send_L'] = FSChaCha20(peer['initiator_L'])
|
||||
peer['send_P'] = FSChaCha20Poly1305(peer['initiator_P'])
|
||||
peer['send_garbage_terminator'] = peer['initiator_garbage_terminator']
|
||||
peer['recv_L'] = FSChaCha20(peer['responder_L'])
|
||||
peer['recv_P'] = FSChaCha20Poly1305(peer['responder_P'])
|
||||
peer['recv_garbage_terminator'] = peer['responder_garbage_terminator']
|
||||
else:
|
||||
peer['send_L'] = FSChaCha20(peer['responder_L'])
|
||||
peer['send_P'] = FSChaCha20Poly1305(peer['responder_P'])
|
||||
peer['send_garbage_terminator'] = peer['responder_garbage_terminator']
|
||||
peer['recv_L'] = FSChaCha20(peer['initiator_L'])
|
||||
peer['recv_P'] = FSChaCha20Poly1305(peer['initiator_P'])
|
||||
peer['recv_garbage_terminator'] = peer['initiator_garbage_terminator']
|
||||
|
||||
return peer
|
||||
|
||||
### Packet encryption
|
||||
|
||||
LENGTH_FIELD_LEN = 3
|
||||
HEADER_LEN = 1
|
||||
IGNORE_BIT_POS = 7
|
||||
|
||||
def v2_enc_packet(peer, contents, aad=b'', ignore=False):
|
||||
"""Encrypt a BIP324 packet."""
|
||||
|
||||
assert len(contents) <= 2**24 - 1
|
||||
header = (ignore << IGNORE_BIT_POS).to_bytes(HEADER_LEN, 'little')
|
||||
plaintext = header + contents
|
||||
aead_ciphertext = peer['send_P'].encrypt(aad, plaintext)
|
||||
enc_plaintext_len = peer['send_L'].crypt(len(contents).to_bytes(LENGTH_FIELD_LEN, 'little'))
|
||||
return enc_plaintext_len + aead_ciphertext
|
||||
53
bip-0324/run_test_vectors.py
Normal file
53
bip-0324/run_test_vectors.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import csv
|
||||
import os
|
||||
import sys
|
||||
|
||||
import reference
|
||||
|
||||
with open(os.path.join(sys.path[0], 'packet_encoding_test_vectors.csv'), newline='') as csvfile:
|
||||
reader = csv.reader(csvfile)
|
||||
reader.__next__()
|
||||
for row in reader:
|
||||
in_idx, in_priv_ours, in_ellswift_ours, in_ellswift_theirs, in_initiating, in_content, in_multiply, in_aad, in_ignore, mid_x_ours, mid_x_shared, mid_shared_secret, mid_initiator_l, mid_initiator_p, mid_responder_l, mid_responder_p, mid_send_garbage_terminator, mid_recv_garbage_terminator, mid_session_id, out_ciphertext, out_ciphertext_endswith = row
|
||||
|
||||
assert mid_x_ours == (int.from_bytes(bytes.fromhex(in_priv_ours), 'big') * reference.SECP256K1_G).x.to_bytes().hex()
|
||||
assert mid_x_shared == reference.ellswift_ecdh_xonly(bytes.fromhex(in_ellswift_theirs), bytes.fromhex(in_priv_ours)).hex()
|
||||
assert mid_shared_secret == reference.v2_ecdh(bytes.fromhex(in_priv_ours), bytes.fromhex(in_ellswift_theirs), bytes.fromhex(in_ellswift_ours), int(in_initiating)).hex()
|
||||
|
||||
peer = reference.initialize_v2_transport(bytes.fromhex(mid_shared_secret), int(in_initiating))
|
||||
assert mid_initiator_l == peer['initiator_L'].hex()
|
||||
assert mid_initiator_p == peer['initiator_P'].hex()
|
||||
assert mid_responder_l == peer['responder_L'].hex()
|
||||
assert mid_responder_p == peer['responder_P'].hex()
|
||||
assert mid_send_garbage_terminator == peer['send_garbage_terminator'].hex()
|
||||
assert mid_recv_garbage_terminator == peer['recv_garbage_terminator'].hex()
|
||||
assert mid_session_id == peer['session_id'].hex()
|
||||
for _ in range(int(in_idx)):
|
||||
reference.v2_enc_packet(peer, b"")
|
||||
ciphertext = reference.v2_enc_packet(peer, bytes.fromhex(in_content) * int(in_multiply), bytes.fromhex(in_aad), int(in_ignore))
|
||||
if len(out_ciphertext):
|
||||
assert out_ciphertext == ciphertext.hex()
|
||||
if len(out_ciphertext_endswith):
|
||||
assert ciphertext.hex().endswith(out_ciphertext_endswith)
|
||||
|
||||
with open(os.path.join(sys.path[0], 'xswiftec_test_vectors.csv'), newline='') as csvfile:
|
||||
reader = csv.reader(csvfile)
|
||||
reader.__next__()
|
||||
for row in reader:
|
||||
u = reference.FE.from_bytes(bytes.fromhex(row[0]))
|
||||
x = reference.FE.from_bytes(bytes.fromhex(row[1]))
|
||||
for case in range(8):
|
||||
ret = reference.xswiftec_inv(x, u, case)
|
||||
if ret is None:
|
||||
assert row[2 + case] == ""
|
||||
else:
|
||||
assert row[2 + case] == ret.to_bytes().hex()
|
||||
assert reference.xswiftec(u, ret) == x
|
||||
|
||||
with open(os.path.join(sys.path[0], 'xelligatorswift_test_vectors.csv'), newline='') as csvfile:
|
||||
reader = csv.reader(csvfile)
|
||||
reader.__next__()
|
||||
for row in reader:
|
||||
ellswift = bytes.fromhex(row[0])
|
||||
x = bytes.fromhex(row[1])
|
||||
assert reference.ellswift_ecdh_xonly(ellswift, (1).to_bytes(32, 'big')) == x
|
||||
17
bip-0324/xelligatorswift_test_vectors.csv
Normal file
17
bip-0324/xelligatorswift_test_vectors.csv
Normal file
@@ -0,0 +1,17 @@
|
||||
ellswift,x
|
||||
26b25d457597a7b0463f9620f666dd10aa2c4373a505967c7c8d70922a2d6eceffffffffffffffffffffffffffffffffffffffffffffffffffffffffb2dabde1,240b740607e14d8cb767f53c9dacf5becde98abe73ffa36f096971215280dc58
|
||||
5a3e80a37915b1601c363acd1601df7ef257d5d32c664004a2ec0484a4f60628ffffffffffffffffffffffffffffffffffffffffffffffffffffffff15d5f3cd,4deaeb3cfbd2abbc9d57fdd83d825a05c45d773d96e247bda136e154769c1f8b
|
||||
6641161dc1faf1293662e9d81dc994fed6a720d6e0e1ab5702c6a866254a9076ffffffffffffffffffffffffffffffffffffffffffffffffffffffff5f44671f,32f5e32639066d09d5184e36cfca82b9f16076666edb2597bf6c8ca0f9423799
|
||||
bf5e8ffa51a9e748985800c1d3d7f1a2a6ae7435136593ca8d9637e3f87c699c76cc5805dab9b4eacefdb477f498020fd82bccdbc9c6a2d9ce10586ac85512b4,5579653da55ae6af8c92b0ab623bfede27756fdba141124c72aec43bc5b746e5
|
||||
df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119b40711a88c7039756fb8a73827eabe2c0fe5a0346ca7e0a104adc0fc764f528d,e838221abd40251a45646c40f62550e0acb8ab1ab292df7a9d4f28d72316bd3a
|
||||
f0caf11f8aea622a396127c3e7e67a6a854dccb736fcdc1270fc071592083e6da839c820778a009421bb1d1eee17cdea622828bd0d065d5b4adb6c0033570a37,bfae8740fc4926b0387803a8db03fa8b9d8b53ec30713a8227bf421b23b11571
|
||||
f1473fa4fb09147ba9d07832c92ccc0bcd651b696ff463931964066a4c849d12ffffffffffffffffffffffffffffffffffffffffffffffffffffffffd7ac238b,de26c723c76ec977f4cf79b3ba3e27800041930000ee7a74337d0e64fe164937
|
||||
fd50cee538a3798d17dde484f9d935860a88fe8dd6cd2341254ab5d558b0b67f5c5ec4b2af7c601e0f4b8d3893192292759a5c3b0a760c0589e5337bfb4e8a2d,0969798ab212485d36a0f007f744a17bffbc4fa9c3e73afcb4e7a27fb3580de9
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffff3d60a4a9252c0b6b080fa045acfcd1437f693f3be2be2ac8223ea525d492fa19ab028942,c163b493f047704ba83e241472ebb2a05f95ef47c6bf5feedd8da33504866d68
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffff4f51ebdbafa7518106309c22d325df6d2663249d158d2f36f1976269d6d4104d9198a108,37d7c5665514f85fe6e4cca488e8abdfc6bc4b3e87ff01ac08eb2504180296e9
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffff72a7e655ffffffffffffffffffffffffffffffffffffffffffffffffffffffff1dd15ad9,34818ed876cbbb6710eb832627de9eb7c468846f26bfc336303601593bbb706d
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffff92e6fb5cf32ceb01b42ea21ecdacc88a0e59dfbf72692b68d76924ba59f0a81f373d2cee,438c40e9cc47e577f56932b9bea91433acc7be309c017ff8f45a46046ed5aa9f
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffba7d0816157655bf7c7eaf74e26c4fb12043675dcce7580ea49d60317a546c3df2e14f9f,11d52804cc52a73185697681ebb8713dfe4204864fb9989b28e5a3696907710c
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffd4b84378fffffffffffffffffffffffffffffffffffffffffffffffffffffffff69a56d1,523e0758ee088690c9b95c604ef4d143e4fd3f2d1ac9084e3086750a9686f9bc
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffdac35742ffffffffffffffffffffffffffffffffffffffffffffffffffffffff99d5d507,db7f9b113af6796d460dfc12bef75f947fa1e0102686cf58de6ad5c0af752f82
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffeeee1f01fffffffffffffffffffffffffffffffffffffffffffffffffffffffff43363e8,c0c123902ec734bde1c6410a93e5d0033e0540120d5be9f555476b842fd2d245
|
||||
|
33
bip-0324/xswiftec_test_vectors.csv
Normal file
33
bip-0324/xswiftec_test_vectors.csv
Normal file
@@ -0,0 +1,33 @@
|
||||
u,x,case0_t,case1_t,case2_t,case3_t,case4_t,case5_t,case6_t,case7_t
|
||||
08da7c45cb204377e7e42249cda5713fa865116ddbb4cb5a1949b2e5b438a6ab,e087b707dabf2796b03b2fb4f976c3f2f5abb36110d00ef656432117f2c93f0a,,,,,,,,
|
||||
0a6361b3a802f55cd5ae06101c88a1e216320fe11cc0cfe1d791eed08a1200fd,a0223bc98997647daf4d520129bdb66e4937a00d1533af1fa29645fb96fb5bb5,60a3ed14bd9df0bfb89ada9372a7b5790b123a66bf130f5788237e8cd5225de4,9c4ee4629f10220fda49532d0c859a539dec5148eefc78bf48d93d2828027a9c,fc5e72f042fd1792cbf88728a374a2cc1e03e1f9ec8813fa3692e497cfa7d5e6,cb39fac005f26dc0a383ea64cb9b3b0b26767f20232cae4486f32904df4f04e3,9f5c12eb42620f404765256c8d584a86f4edc59940ecf0a877dc81722add9e4b,63b11b9d60efddf025b6acd2f37a65ac6213aeb711038740b726c2d6d7fd8193,03a18d0fbd02e86d340778d75c8b5d33e1fc1e061377ec05c96d1b6730582649,34c6053ffa0d923f5c7c159b3464c4f4d98980dfdcd351bb790cd6fa20b0f74c
|
||||
102b51b9765a56a3e899f7cf0ee38e5251f9c503b357b330a49183eb7b155604,102b51b9765a56a3e899f7cf0ee38e5251f9c503b357b330a49183eb7b155604,bdb5bd58ca96eae36147a6c55bc2bef2cee55a757ee193cb619edc8d3590f90a,bda953c1da02059350e740b83f59149628e0be50c24ac8dc6908a2225931b4a0,,,424a42a73569151c9eb8593aa43d410d311aa58a811e6c349e612371ca6f0325,4256ac3e25fdfa6caf18bf47c0a6eb69d71f41af3db5372396f75ddca6ce478f,,
|
||||
2921a11f25dadaa24aa79a548e4e81508c2e5e56af2d833d65e2bcce448ce2f5,3a70c472406b83d9f1c4398b8ecef786499bc44a3b30c34ac30f2d8a418bffa3,b9c76c21d3fabb948fa0326bf9e999068e9eed56ee4e76cb81558aa26969c56c,ef7dd84338732a0cac3a8995f3bacf9b2896582b8d3317ed508e5d9a5a3447af,,,463893de2c05446b705fcd94061666f9716112a911b189347eaa755c969636c3,108227bcc78cd5f353c5766a0c453064d769a7d472cce812af71a264a5cbb480,,
|
||||
33b67cb5385ceddad93d0ee960679041613bed34b8b4a5e6362fe7539ba2d3ce,0105c74958a165e016502eeb87835195505d89714c95272b6fa88fe6c60b33ac,,,069e1b3b155c6da989b9b6a8735bba3c5c1049dcf01fe4474772244db89cf9ca,c77b10bca540e95ee66c1f57ab6297787849a89b2b883116e700593e3c0fe66d,,,f961e4c4eaa39256764649578ca445c3a3efb6230fe01bb8b88ddbb147630265,3884ef435abf16a11993e0a8549d688787b65764d477cee918ffa6c0c3f015c2
|
||||
3a898eecdae167231275338e9a79153cbe53f7bf99943eeb72ee64e57bb58699,41ffd7362aaa7b90fe03936deeebe9afafd9c18967122d8f972db2c050d4f07b,60abf7ed2a7ffd3d2ac242a782331ea663d55ca157af994e5e964e9c79a0db40,3c3c39dc37753ab9160dfbc2e0596c3a5114784690caa1836e12036814453da3,adcd3f100de60723f127278998c591fbf081af8e0a77f2a9090bed67d8aa2aa3,,9f540812d58002c2d53dbd587dcce1599c2aa35ea85066b1a169b162865f20ef,c3c3c623c88ac546e9f2043d1fa693c5aeeb87b96f355e7c91edfc96ebbabe8c,5232c0eff219f8dc0ed8d876673a6e040f7e5071f5880d56f6f412972755d18c,
|
||||
46e04d129d7b45d054469ce34e24069a1426b3e34f1b68a3d1bff1e070aee192,c6ce9611bd908c16eba5c599e5219de2d18d82c96aafb0180b23ee315513618f,,,,,,,,
|
||||
47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254,13964717dbc998964d7c19ec3d9981fe1d4a9a80845552a98fb9352898532844,,,,,,,,
|
||||
4cab73ce2a7e6220975001c8a354143267a3c1ce8bf7692313e654481e616a93,9114cf2edd3b53dbb6581290a5cca532db38b4e9ceeacc9b0437a0e49bf97211,903b600ed648d4ddc48f0f628829c8992c88fab44b692413fb8b3d783854f9a2,2952afe39557606d08c311345788a5071413580917207c86ea7cb829cf2f2c6d,05f414320d0c4004cff10f798c3fda6c4fc335b5a2db940993b3d78147a25c18,48e2531c7e3ec99f807210d6c5330114b4f04d7345535ca5a6e6abf478bdb723,6fc49ff129b72b223b70f09d77d63766d377054bb496dbec0474c286c7ab028d,d6ad501c6aa89f92f73ceecba8775af8ebeca7f6e8df8379158347d530d0cfc2,fa0bebcdf2f3bffb300ef08673c02593b03cca4a5d246bf66c4c287db85da017,b71dace381c136607f8def293accfeeb4b0fb28cbaaca35a5919540a8742450c
|
||||
5aeca385d8b781825b07bbec7c858b7170426c88088935850bc13dd6402368a5,a5135c7a27487e7da4f84413837a748e8fbd9377f776ca7af43ec228bfdc938a,8da4f71fb2700758f623d73c24ac91747da43f2302fce16c8d438a769c63495f,6b8f345fc0a25a76455541ddbf2791ff4b943c98b16db2b6eb6cea94a6b19afb,,,725b08e04d8ff8a709dc28c3db536e8b825bc0dcfd031e9372bc7588639cb2d0,9470cba03f5da589baaabe2240d86e00b46bc3674e924d491493156a594e6134,,
|
||||
707bf0b938f307b5c222e670598b865d5e1f8a8003df82c7abbf7c9f8fa4d720,8f840f46c70cf84a3ddd198fa67479a2a1e0757ffc207d385440835f705b250f,,,eab90fb459bace62d3ce8fbd69c9f1039f0627d0e93e2f42bffd87889cb236a4,157c26578b226c66daf8edfa56f7560f1131f41d1685175e6d76cc95b4f89f10,,,1546f04ba645319d2c31704296360efc60f9d82f16c1d0bd40027876634dc58b,ea83d9a874dd939925071205a908a9f0eece0be2e97ae8a1928933694b075d1f
|
||||
766caa663e1025b9accd7ededd24fbc8193180e028eedae2f41d6bb0b1d36468,22825ee826f8b76c27220e43c79c884a8518bc20f4978cc15f83f9c48346a314,,,8fe95c178da66d1dd249ea6a4dc614a6d46d79c83cbc4beafee518090263e48a,7b044cb756eb207226db302ba05e164781c2f5161dccd72607282cb9ad86a282,,,7016a3e8725992e22db61595b239eb592b928637c343b415011ae7f5fd9c17a5,84fbb348a914df8dd924cfd45fa1e9b87e3d0ae9e23328d9f8d7d345527959ad
|
||||
78a23af8da46b1b37e8767921a2d3f528fdc8eca37cea8aea775fd2b283d3776,73d5f35d96f3ce1ef5802ead8edc10787700c593b5e0ddcc3bfb2720b9d36de3,8465ad20bd0f2b4a2d37106769af46288a109bc10b527c3b033c930c0e4b1025,1b7f03bd2c915bb736622aec85601bcabec89268c98945e19a0de4126ed62524,,,7b9a52df42f0d4b5d2c8ef989650b9d775ef643ef4ad83c4fcc36cf2f1b4ec0a,e480fc42d36ea448c99dd5137a9fe43541376d973676ba1e65f21bec9129d70b,,
|
||||
78b4be1f9eeef9da65c393e4385f67edd142709b400ca7d900bd952e0c3cf727,089329e17a58a91e71ffe6ddd851e8a352e85a29fcc289b34a3bfdeaf958fe91,,,6008d703955b38da0166bd975ad3535af3b701b2efdf653fc5e7e6eb6afff0a3,,,,9ff728fc6aa4c725fe994268a52caca50c48fe4d10209ac03a18191395000b8c,
|
||||
7a2a7c0a81d1bd595dff09b918f8ecb5b5e8493654a4f83496956ed8eb017674,85d583f57e2e42a6a200f646e707134a4a17b6c9ab5b07cb696a912614fe85bb,,,,,,,,
|
||||
913da1f8df6f8fd47593840d533ba0458cc9873996bf310460abb495b34c232a,a7803f8e02b70718443a06db502c67925640e936b3fa46dd2ed6b8f7c80fa329,67d916ba2cc154464d87ff4e0cfe3bb816b22a961831c2daf62597a8b0681e87,a4b84520f8853e5482ee7689732ed7dd7da59945d26edeee0bf5f55d3507192f,,,9826e945d33eabb9b27800b1f301c447e94dd569e7ce3d2509da68564f97dda8,5b47badf077ac1ab7d1189768cd12822825a66ba2d912111f40a0aa1caf8e300,,
|
||||
96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7,7684ab3b1a43e20a97a7b5520e5b5347841a7d95984fd76b2478a2b710f1a2ce,,,,,,,,
|
||||
99be5efb88ca2013bd8e4eb035fd42d5245468fe9afa70d8ba9c1c419a48c4e8,08ee83ae5c7af0c9b2341e595fe347537272d94f2fe9f10b9a8f913279fc6230,,,,,,,,
|
||||
9b4fb24edd6d1d8830e272398263cdbf026b97392cc35387b991dc0248a628f9,80e81d40a50b53712a8dac5f468b0903c05219544a56af70aa152ebf17887701,,,6e94af5a32ac100c5230f1e119c538742b7051934b02f3850522cff26bd32d97,e9bd309fbf041342311be3d5bab0b9d16c9f80c6640eb47e311d3178c2adc75d,,,916b50a5cd53eff3adcf0e1ee63ac78bd48fae6cb4fd0c7afadd300c942cce98,1642cf6040fbecbdcee41c2a454f462e93607f399bf14b81cee2ce863d5234d2
|
||||
9def996cb1ea87e596b6cadccca3839a352e99d9ce07e635cdb239f38ca294f8,294850a665ab014a0e75eb4b52ee66dd8a8d2b5e453074e58afacb5e019ee90a,b1a29367b95e1996f7e393fb389e7ace812d4135f6ddcdcd77467fc000dfca8c,a340aabc95b4000e3043ba6139178c450046c985fbf09676c440bc6430ddaa5b,4c4cd400d0be335dd651370c5565c2b742a298016212a8605187b3c0751a811e,d90fa208bbb5f3f6e16c5a42b419188ec1951c1eb358f04741b7b48df9e55f79,4e5d6c9846a1e669081c6c04c76185317ed2beca0922323288b9803eff2031a3,5cbf55436a4bfff1cfbc459ec6e873baffb9367a040f69893bbf439acf2251d4,b3b32bff2f41cca229aec8f3aa9a3d48bd5d67fe9ded579fae784c3e8ae57b11,26f05df7444a0c091e93a5bd4be6e7713e6ae3e14ca70fb8be484b71061a9cb6
|
||||
a2c4aed1cf757cd9a509734a267ffc7b1166b55f4c8f9c3e3550c56e743328fc,a2c4aed1cf757cd9a509734a267ffc7b1166b55f4c8f9c3e3550c56e743328fc,,,,,,,,
|
||||
a8e437abf9c0e74dc6d51eabf2d261a00e785c7e21efeac1f322b610273ba066,5a64cce4be767964e7dba23e78e30149326c539353b647e0d5d7cc361943b13b,,,6f73bdd6b748790b5f788935ca02aee3b9e560c4ba6caf47d716fbde1dd6e92c,b1ff705694188e672f58c6a05eeecc379dd1b60fd3cb9f19fcb02b1d9cab4bc5,,,908c422948b786f4a08776ca35fd511c461a9f3b459350b828e90420e2291303,4e008fa96be77198d0a7395fa11133c8622e49f02c3460e6034fd4e16354b06a
|
||||
bf60e4349cace6bce0d552e8d783428db66d0d649bd9e430a3627e2ee14ac839,409f1bcb635319431f2aad17287cbd724992f29b64261bcf5c9d81d01eb533f6,,,,,,,,
|
||||
c0ba8a33ac67f44abff5984dfbb6f56c46b880ac2b86e1f23e7fa9c402c53ae7,4767c4cab0d08133980a8e66c3f93a055c8ae62f89a92f8dcfa47607cee0bc57,4c21052f5ffccadb4f707aa1cba828ef384d7861af1690c59d638dfee9f368e7,dbcc8fe22896478161452d44688a6b138050a4d0964470c175a521dcecc5519a,,,b3defad0a0033524b08f855e3457d710c7b2879e50e96f3a629c7200160c9348,2433701dd769b87e9ebad2bb977594ec7faf5b2f69bb8f3e8a5ade22133aaa95,,
|
||||
cbe2268747c9c8072c7f9926f2288f270637dc55bb9d14d3368361d5e47d25be,0e4e25736b614910c4984843e606b1e229def08bfd672ab61e2707cde8248c6d,,,c30567184201fac8e1cb9e776d921e17d28cdb7333223abd1c8f860a16393df1,,,,3cfa98e7bdfe05371e346188926de1e82d73248cccddc542e37079f4e9c6be3e,
|
||||
ceb827ad3d3884fd4d50ae6099d6d50c09a21e72ebd309708e8b69d93df19e55,a6a0c8c94462f16f1b92502c3d5f9d1618f12ffa756227d5b19b01b9373cd940,,,,,,,,
|
||||
d57e9d4f5842134f140032eaf38b5333638e8c4b145fcf86a23d48d3e9acc0f8,2a8162b0a7bdecb0ebffcd150c74accc9c7173b4eba030795dc2b72b16533b37,349a9a592d2c56e5378ae869d646043fc09ffb8fe5fd9debd83a11274da08892,9875f58028cc991cafab9fb1183b350bc1d8d5ce5723813cc2b8434ed1a2100f,,,cb6565a6d2d3a91ac875179629b9fbc03f6004701a02621427c5eed7b25f739d,678a0a7fd73366e35054604ee7c4caf43e272a31a8dc7ec33d47bcb02e5dec20,,
|
||||
d94e7f1e9bb1f8a9b90996ba12c461b84956f0e7f230145cc594c2f80b067aa0,b4f4632803cff65c013a566748cd3386d58cd3a28f5b4721056cbe9d278a67a4,,,fad51eda7d418ee2785df9f3788ac9152576312177fc0fd83c65036750581620,749259382784be63f86cc927a5defa6aa8cecb98e38d68f6b7a7e958303c94ad,,,052ae12582be711d87a2060c877536eada89cede8803f027c39afc97afa7e60f,8b6da6c7d87b419c079336d85a210595573134671c729709485816a6cfc36782
|
||||
e545d395bb3fd971f91bf9a2b6722831df704efae6c1aa9da0989ed0970b77bb,760486143a1d512da5219d3e5febc7c5c9990d21ca7a501ed23f86c91ddee4cf,,,090892960a84c69967fe5a5d014d3ca19173e4cb72a908586fbce9d1e531a265,42a47f65d00ff2004faa98865ee8ed4f8a9a5ddc9f75042d728de335664bb546,,,f6f76d69f57b39669801a5a2feb2c35e6e8c1b348d56f7a79043162d1ace59ca,bd5b809a2ff00dffb0556779a11712b07565a223608afbd28d721cc999b446e9
|
||||
e9f86cefcfd61558fe75da7d4ea48a6c82d93191c6d49579aab49f99e543dcad,5db7371325a7bb83b030691b2d87cd9f199f43d91e302568391ac48181b7cea6,,,,,,,,
|
||||
eec4121f2a07b61aba16414812aa9afc39ab0a136360a5ace2240dc19b0464eb,0b623c5296c13218a1eb24e79d00b04bf15788f6c2f7ec100a4a16f1473124a2,,,,,,,,
|
||||
f566cc6fccc657365c0197accf3a7d6f80f85209ff666ff774f4dcbc524aa842,0a9933903339a8c9a3fe685330c582907f07adf6009990088b0b2342adb553ed,3ab8dc4ecbc0441c685436ac0d76f16393769c353be6092bd6ec4ce094106bd8,3bd189b4ef3d1baa5610f2b14cb4a2b377eb171511e6f36ef6a05a2c7c52e368,1594764c6296402aadd123675d81f3505d35f2a52c52881568eadb7b675b53f0,c64fbf71138e66de8ce0abdf3b6f51d151ca8e1037ab5b979e62b2faa15be81c,c54723b1343fbbe397abc953f2890e9c6c8963cac419f6d42913b31e6bef9057,c42e764b10c2e455a9ef0d4eb34b5d4c8814e8eaee190c91095fa5d283ad18c7,ea6b89b39d69bfd5522edc98a27e0cafa2ca0d5ad3ad77ea9715248398a4a83f,39b0408eec719921731f5420c490ae2eae3571efc854a468619d4d045ea41413
|
||||
|
Reference in New Issue
Block a user