Skip to content

Commit 962ea5c

Browse files
committed
Merge bitcoin#28374: test: python cryptography required for BIP 324 functional tests
c534c08 [test/crypto] Add FSChaCha20Poly1305 AEAD python implementation (stratospher) c2a458f [test/crypto] Add FSChaCha20 python implementation (stratospher) c4ea5f6 [test/crypto] Add RFC 8439's ChaCha20Poly1305 AEAD (stratospher) 9fc6e03 [test/crypto] Add Poly1305 python implementation (stratospher) fec2ca6 [test/crypto] Use chacha20_block function in `data_to_num3072` (stratospher) 0cde60d [test/crypto] Add ChaCha20 python implementation (stratospher) 69d3f50 [test/crypto] Add HMAC-based Key Derivation Function (HKDF) (stratospher) 08a4a56 [test] Move test framework crypto functions to crypto/ (stratospher) Pull request description: split off from bitcoin#24748 to keep commits related to cryptography and functional test framework changes separate. This PR adds python implementation and unit tests for HKDF, ChaCha20, Poly1305, ChaCha20Poly1305 AEAD, FSChaCha20 and FSChaCha20Poly1305 AEAD. They're based on https://github.com/bitcoin/bips/blob/cc177ab7bc5abcdcdf9c956ee88afd1052053328/bip-0324/reference.py for easy review. ACKs for top commit: sipa: utACK c534c08 achow101: ACK c534c08 theStack: re-ACK c534c08 Tree-SHA512: 08a0a422d2937eadcf0edfede37e535e6bc4c2e4b192441bbf9bc26dd3f03fa3388effd22f0527c55af173933d0b50e5b2b3d36f2b62d0aca3098728ef06970e
2 parents c981771 + c534c08 commit 962ea5c

19 files changed

+568
-120
lines changed

test/functional/feature_taproot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
tweak_add_privkey,
105105
ECKey,
106106
)
107-
from test_framework import secp256k1
107+
from test_framework.crypto import secp256k1
108108
from test_framework.address import (
109109
hash160,
110110
program_to_witness,

test/functional/feature_utxo_set_hash.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
COutPoint,
1212
from_hex,
1313
)
14-
from test_framework.muhash import MuHash3072
14+
from test_framework.crypto.muhash import MuHash3072
1515
from test_framework.test_framework import BitcoinTestFramework
1616
from test_framework.util import assert_equal
1717
from test_framework.wallet import MiniWallet

test/functional/test_framework/blockfilter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Helper routines relevant for compact block filters (BIP158).
66
"""
7-
from .siphash import siphash
7+
from .crypto.siphash import siphash
88

99

1010
def bip158_basic_element_hash(script_pub_key, N, block_hash):
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2022 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
"""Test-only implementation of ChaCha20 Poly1305 AEAD Construction in RFC 8439 and FSChaCha20Poly1305 for BIP 324
7+
8+
It is designed for ease of understanding, not performance.
9+
10+
WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for
11+
anything but tests.
12+
"""
13+
14+
import unittest
15+
16+
from .chacha20 import chacha20_block, REKEY_INTERVAL
17+
from .poly1305 import Poly1305
18+
19+
20+
def pad16(x):
21+
if len(x) % 16 == 0:
22+
return b''
23+
return b'\x00' * (16 - (len(x) % 16))
24+
25+
26+
def aead_chacha20_poly1305_encrypt(key, nonce, aad, plaintext):
27+
"""Encrypt a plaintext using ChaCha20Poly1305."""
28+
ret = bytearray()
29+
msg_len = len(plaintext)
30+
for i in range((msg_len + 63) // 64):
31+
now = min(64, msg_len - 64 * i)
32+
keystream = chacha20_block(key, nonce, i + 1)
33+
for j in range(now):
34+
ret.append(plaintext[j + 64 * i] ^ keystream[j])
35+
poly1305 = Poly1305(chacha20_block(key, nonce, 0)[:32])
36+
mac_data = aad + pad16(aad)
37+
mac_data += ret + pad16(ret)
38+
mac_data += len(aad).to_bytes(8, 'little') + msg_len.to_bytes(8, 'little')
39+
ret += poly1305.tag(mac_data)
40+
return bytes(ret)
41+
42+
43+
def aead_chacha20_poly1305_decrypt(key, nonce, aad, ciphertext):
44+
"""Decrypt a ChaCha20Poly1305 ciphertext."""
45+
if len(ciphertext) < 16:
46+
return None
47+
msg_len = len(ciphertext) - 16
48+
poly1305 = Poly1305(chacha20_block(key, nonce, 0)[:32])
49+
mac_data = aad + pad16(aad)
50+
mac_data += ciphertext[:-16] + pad16(ciphertext[:-16])
51+
mac_data += len(aad).to_bytes(8, 'little') + msg_len.to_bytes(8, 'little')
52+
if ciphertext[-16:] != poly1305.tag(mac_data):
53+
return None
54+
ret = bytearray()
55+
for i in range((msg_len + 63) // 64):
56+
now = min(64, msg_len - 64 * i)
57+
keystream = chacha20_block(key, nonce, i + 1)
58+
for j in range(now):
59+
ret.append(ciphertext[j + 64 * i] ^ keystream[j])
60+
return bytes(ret)
61+
62+
63+
class FSChaCha20Poly1305:
64+
"""Rekeying wrapper AEAD around ChaCha20Poly1305."""
65+
def __init__(self, initial_key):
66+
self._key = initial_key
67+
self._packet_counter = 0
68+
69+
def _crypt(self, aad, text, is_decrypt):
70+
nonce = ((self._packet_counter % REKEY_INTERVAL).to_bytes(4, 'little') +
71+
(self._packet_counter // REKEY_INTERVAL).to_bytes(8, 'little'))
72+
if is_decrypt:
73+
ret = aead_chacha20_poly1305_decrypt(self._key, nonce, aad, text)
74+
else:
75+
ret = aead_chacha20_poly1305_encrypt(self._key, nonce, aad, text)
76+
if (self._packet_counter + 1) % REKEY_INTERVAL == 0:
77+
rekey_nonce = b"\xFF\xFF\xFF\xFF" + nonce[4:]
78+
self._key = aead_chacha20_poly1305_encrypt(self._key, rekey_nonce, b"", b"\x00" * 32)[:32]
79+
self._packet_counter += 1
80+
return ret
81+
82+
def decrypt(self, aad, ciphertext):
83+
return self._crypt(aad, ciphertext, True)
84+
85+
def encrypt(self, aad, plaintext):
86+
return self._crypt(aad, plaintext, False)
87+
88+
89+
# Test vectors from RFC8439 consisting of plaintext, aad, 32 byte key, 12 byte nonce and ciphertext
90+
AEAD_TESTS = [
91+
# RFC 8439 Example from section 2.8.2
92+
["4c616469657320616e642047656e746c656d656e206f662074686520636c6173"
93+
"73206f66202739393a204966204920636f756c64206f6666657220796f75206f"
94+
"6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73"
95+
"637265656e20776f756c642062652069742e",
96+
"50515253c0c1c2c3c4c5c6c7",
97+
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
98+
[7, 0x4746454443424140],
99+
"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6"
100+
"3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36"
101+
"92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc"
102+
"3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060"
103+
"0691"],
104+
# RFC 8439 Test vector A.5
105+
["496e7465726e65742d4472616674732061726520647261667420646f63756d65"
106+
"6e74732076616c696420666f722061206d6178696d756d206f6620736978206d"
107+
"6f6e74687320616e64206d617920626520757064617465642c207265706c6163"
108+
"65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65"
109+
"6e747320617420616e792074696d652e20497420697320696e617070726f7072"
110+
"6961746520746f2075736520496e7465726e65742d4472616674732061732072"
111+
"65666572656e6365206d6174657269616c206f7220746f206369746520746865"
112+
"6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67"
113+
"726573732e2fe2809d",
114+
"f33388860000000000004e91",
115+
"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
116+
[0, 0x0807060504030201],
117+
"64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2"
118+
"4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf"
119+
"332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855"
120+
"9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4"
121+
"b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e"
122+
"af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a"
123+
"0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10"
124+
"49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29"
125+
"a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38"],
126+
# Test vectors exercising aad and plaintext which are multiples of 16 bytes.
127+
["8d2d6a8befd9716fab35819eaac83b33269afb9f1a00fddf66095a6c0cd91951"
128+
"a6b7ad3db580be0674c3f0b55f618e34",
129+
"",
130+
"72ddc73f07101282bbbcf853b9012a9f9695fc5d36b303a97fd0845d0314e0c3",
131+
[0x3432b75f, 0xb3585537eb7f4024],
132+
"f760b8224fb2a317b1b07875092606131232a5b86ae142df5df1c846a7f6341a"
133+
"f2564483dd77f836be45e6230808ffe402a6f0a3e8be074b3d1f4ea8a7b09451"],
134+
["",
135+
"36970d8a704c065de16250c18033de5a400520ac1b5842b24551e5823a3314f3"
136+
"946285171e04a81ebfbe3566e312e74ab80e94c7dd2ff4e10de0098a58d0f503",
137+
"77adda51d6730b9ad6c995658cbd49f581b2547e7c0c08fcc24ceec797461021",
138+
[0x1f90da88, 0x75dafa3ef84471a4],
139+
"aaae5bb81e8407c94b2ae86ae0c7efbe"],
140+
]
141+
142+
FSAEAD_TESTS = [
143+
["d6a4cb04ef0f7c09c1866ed29dc24d820e75b0491032a51b4c3366f9ca35c19e"
144+
"a3047ec6be9d45f9637b63e1cf9eb4c2523a5aab7b851ebeba87199db0e839cf"
145+
"0d5c25e50168306377aedbe9089fd2463ded88b83211cf51b73b150608cc7a60"
146+
"0d0f11b9a742948482e1b109d8faf15b450aa7322e892fa2208c6691e3fecf4c"
147+
"711191b14d75a72147",
148+
"786cb9b6ebf44288974cf0",
149+
"5c9e1c3951a74fba66708bf9d2c217571684556b6a6a3573bff2847d38612654",
150+
500,
151+
"9dcebbd3281ea3dd8e9a1ef7d55a97abd6743e56ebc0c190cb2c4e14160b385e"
152+
"0bf508dddf754bd02c7c208447c131ce23e47a4a14dfaf5dd8bc601323950f75"
153+
"4e05d46e9232f83fc5120fbbef6f5347a826ec79a93820718d4ec7a2b7cfaaa4"
154+
"4b21e16d726448b62f803811aff4f6d827ed78e738ce8a507b81a8ae13131192"
155+
"8039213de18a5120dc9b7370baca878f50ff254418de3da50c"],
156+
["8349b7a2690b63d01204800c288ff1138a1d473c832c90ea8b3fc102d0bb3adc"
157+
"44261b247c7c3d6760bfbe979d061c305f46d94c0582ac3099f0bf249f8cb234",
158+
"",
159+
"3bd2093fcbcb0d034d8c569583c5425c1a53171ea299f8cc3bbf9ae3530adfce",
160+
60000,
161+
"30a6757ff8439b975363f166a0fa0e36722ab35936abd704297948f45083f4d4"
162+
"99433137ce931f7fca28a0acd3bc30f57b550acbc21cbd45bbef0739d9caf30c"
163+
"14b94829deb27f0b1923a2af704ae5d6"],
164+
]
165+
166+
167+
class TestFrameworkAEAD(unittest.TestCase):
168+
def test_aead(self):
169+
"""ChaCha20Poly1305 AEAD test vectors."""
170+
for test_vector in AEAD_TESTS:
171+
hex_plain, hex_aad, hex_key, hex_nonce, hex_cipher = test_vector
172+
plain = bytes.fromhex(hex_plain)
173+
aad = bytes.fromhex(hex_aad)
174+
key = bytes.fromhex(hex_key)
175+
nonce = hex_nonce[0].to_bytes(4, 'little') + hex_nonce[1].to_bytes(8, 'little')
176+
177+
ciphertext = aead_chacha20_poly1305_encrypt(key, nonce, aad, plain)
178+
self.assertEqual(hex_cipher, ciphertext.hex())
179+
plaintext = aead_chacha20_poly1305_decrypt(key, nonce, aad, ciphertext)
180+
self.assertEqual(plain, plaintext)
181+
182+
def test_fschacha20poly1305aead(self):
183+
"FSChaCha20Poly1305 AEAD test vectors."
184+
for test_vector in FSAEAD_TESTS:
185+
hex_plain, hex_aad, hex_key, msg_idx, hex_cipher = test_vector
186+
plain = bytes.fromhex(hex_plain)
187+
aad = bytes.fromhex(hex_aad)
188+
key = bytes.fromhex(hex_key)
189+
190+
enc_aead = FSChaCha20Poly1305(key)
191+
dec_aead = FSChaCha20Poly1305(key)
192+
193+
for _ in range(msg_idx):
194+
enc_aead.encrypt(b"", b"")
195+
ciphertext = enc_aead.encrypt(aad, plain)
196+
self.assertEqual(hex_cipher, ciphertext.hex())
197+
198+
for _ in range(msg_idx):
199+
dec_aead.decrypt(b"", bytes(16))
200+
plaintext = dec_aead.decrypt(aad, ciphertext)
201+
self.assertEqual(plain, plaintext)
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2022 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
"""Test-only implementation of ChaCha20 cipher and FSChaCha20 for BIP 324
7+
8+
It is designed for ease of understanding, not performance.
9+
10+
WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for
11+
anything but tests.
12+
"""
13+
14+
import unittest
15+
16+
CHACHA20_INDICES = (
17+
(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15),
18+
(0, 5, 10, 15), (1, 6, 11, 12), (2, 7, 8, 13), (3, 4, 9, 14)
19+
)
20+
21+
CHACHA20_CONSTANTS = (0x61707865, 0x3320646e, 0x79622d32, 0x6b206574)
22+
REKEY_INTERVAL = 224 # packets
23+
24+
25+
def rotl32(v, bits):
26+
"""Rotate the 32-bit value v left by bits bits."""
27+
bits %= 32 # Make sure the term below does not throw an exception
28+
return ((v << bits) & 0xffffffff) | (v >> (32 - bits))
29+
30+
31+
def chacha20_doubleround(s):
32+
"""Apply a ChaCha20 double round to 16-element state array s.
33+
See https://cr.yp.to/chacha/chacha-20080128.pdf and https://tools.ietf.org/html/rfc8439
34+
"""
35+
for a, b, c, d in CHACHA20_INDICES:
36+
s[a] = (s[a] + s[b]) & 0xffffffff
37+
s[d] = rotl32(s[d] ^ s[a], 16)
38+
s[c] = (s[c] + s[d]) & 0xffffffff
39+
s[b] = rotl32(s[b] ^ s[c], 12)
40+
s[a] = (s[a] + s[b]) & 0xffffffff
41+
s[d] = rotl32(s[d] ^ s[a], 8)
42+
s[c] = (s[c] + s[d]) & 0xffffffff
43+
s[b] = rotl32(s[b] ^ s[c], 7)
44+
45+
46+
def chacha20_block(key, nonce, cnt):
47+
"""Compute the 64-byte output of the ChaCha20 block function.
48+
Takes as input a 32-byte key, 12-byte nonce, and 32-bit integer counter.
49+
"""
50+
# Initial state.
51+
init = [0] * 16
52+
init[:4] = CHACHA20_CONSTANTS[:4]
53+
init[4:12] = [int.from_bytes(key[i:i+4], 'little') for i in range(0, 32, 4)]
54+
init[12] = cnt
55+
init[13:16] = [int.from_bytes(nonce[i:i+4], 'little') for i in range(0, 12, 4)]
56+
# Perform 20 rounds.
57+
state = list(init)
58+
for _ in range(10):
59+
chacha20_doubleround(state)
60+
# Add initial values back into state.
61+
for i in range(16):
62+
state[i] = (state[i] + init[i]) & 0xffffffff
63+
# Produce byte output
64+
return b''.join(state[i].to_bytes(4, 'little') for i in range(16))
65+
66+
class FSChaCha20:
67+
"""Rekeying wrapper stream cipher around ChaCha20."""
68+
def __init__(self, initial_key, rekey_interval=REKEY_INTERVAL):
69+
self._key = initial_key
70+
self._rekey_interval = rekey_interval
71+
self._block_counter = 0
72+
self._chunk_counter = 0
73+
self._keystream = b''
74+
75+
def _get_keystream_bytes(self, nbytes):
76+
while len(self._keystream) < nbytes:
77+
nonce = ((0).to_bytes(4, 'little') + (self._chunk_counter // self._rekey_interval).to_bytes(8, 'little'))
78+
self._keystream += chacha20_block(self._key, nonce, self._block_counter)
79+
self._block_counter += 1
80+
ret = self._keystream[:nbytes]
81+
self._keystream = self._keystream[nbytes:]
82+
return ret
83+
84+
def crypt(self, chunk):
85+
ks = self._get_keystream_bytes(len(chunk))
86+
ret = bytes([ks[i] ^ chunk[i] for i in range(len(chunk))])
87+
if ((self._chunk_counter + 1) % self._rekey_interval) == 0:
88+
self._key = self._get_keystream_bytes(32)
89+
self._block_counter = 0
90+
self._keystream = b''
91+
self._chunk_counter += 1
92+
return ret
93+
94+
95+
# Test vectors from RFC7539/8439 consisting of 32 byte key, 12 byte nonce, block counter
96+
# and 64 byte output after applying `chacha20_block` function
97+
CHACHA20_TESTS = [
98+
["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", [0x09000000, 0x4a000000], 1,
99+
"10f1e7e4d13b5915500fdd1fa32071c4c7d1f4c733c068030422aa9ac3d46c4e"
100+
"d2826446079faa0914c2d705d98b02a2b5129cd1de164eb9cbd083e8a2503c4e"],
101+
["0000000000000000000000000000000000000000000000000000000000000000", [0, 0], 0,
102+
"76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7"
103+
"da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"],
104+
["0000000000000000000000000000000000000000000000000000000000000000", [0, 0], 1,
105+
"9f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed"
106+
"29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f"],
107+
["0000000000000000000000000000000000000000000000000000000000000001", [0, 0], 1,
108+
"3aeb5224ecf849929b9d828db1ced4dd832025e8018b8160b82284f3c949aa5a"
109+
"8eca00bbb4a73bdad192b5c42f73f2fd4e273644c8b36125a64addeb006c13a0"],
110+
["00ff000000000000000000000000000000000000000000000000000000000000", [0, 0], 2,
111+
"72d54dfbf12ec44b362692df94137f328fea8da73990265ec1bbbea1ae9af0ca"
112+
"13b25aa26cb4a648cb9b9d1be65b2c0924a66c54d545ec1b7374f4872e99f096"],
113+
["0000000000000000000000000000000000000000000000000000000000000000", [0, 0x200000000000000], 0,
114+
"c2c64d378cd536374ae204b9ef933fcd1a8b2288b3dfa49672ab765b54ee27c7"
115+
"8a970e0e955c14f3a88e741b97c286f75f8fc299e8148362fa198a39531bed6d"],
116+
["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", [0, 0x4a000000], 1,
117+
"224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf78"
118+
"8a3b0aa372600a92b57974cded2b9334794cba40c63e34cdea212c4cf07d41b7"],
119+
["0000000000000000000000000000000000000000000000000000000000000001", [0, 0], 0,
120+
"4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41"
121+
"bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963"],
122+
["0000000000000000000000000000000000000000000000000000000000000000", [0, 1], 0,
123+
"ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32"
124+
"111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb0041b2f586b"],
125+
["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", [0, 0x0706050403020100], 0,
126+
"f798a189f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c1"
127+
"34a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a"],
128+
]
129+
130+
FSCHACHA20_TESTS = [
131+
["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
132+
"0000000000000000000000000000000000000000000000000000000000000000", 256,
133+
"a93df4ef03011f3db95f60d996e1785df5de38fc39bfcb663a47bb5561928349"],
134+
["01", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 5, "ea"],
135+
["e93fdb5c762804b9a706816aca31e35b11d2aa3080108ef46a5b1f1508819c0a",
136+
"8ec4c3ccdaea336bdeb245636970be01266509b33f3d2642504eaf412206207a", 4096,
137+
"8bfaa4eacff308fdb4a94a5ff25bd9d0c1f84b77f81239f67ff39d6e1ac280c9"],
138+
]
139+
140+
141+
class TestFrameworkChacha(unittest.TestCase):
142+
def test_chacha20(self):
143+
"""ChaCha20 test vectors."""
144+
for test_vector in CHACHA20_TESTS:
145+
hex_key, nonce, counter, hex_output = test_vector
146+
key = bytes.fromhex(hex_key)
147+
nonce_bytes = nonce[0].to_bytes(4, 'little') + nonce[1].to_bytes(8, 'little')
148+
keystream = chacha20_block(key, nonce_bytes, counter)
149+
self.assertEqual(hex_output, keystream.hex())
150+
151+
def test_fschacha20(self):
152+
"""FSChaCha20 test vectors."""
153+
for test_vector in FSCHACHA20_TESTS:
154+
hex_plaintext, hex_key, rekey_interval, hex_ciphertext_after_rotation = test_vector
155+
plaintext = bytes.fromhex(hex_plaintext)
156+
key = bytes.fromhex(hex_key)
157+
fsc20 = FSChaCha20(key, rekey_interval)
158+
for _ in range(rekey_interval):
159+
fsc20.crypt(plaintext)
160+
161+
ciphertext = fsc20.crypt(plaintext)
162+
self.assertEqual(hex_ciphertext_after_rotation, ciphertext.hex())

0 commit comments

Comments
 (0)