Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2019 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import pytest
import nkeys


@pytest.fixture(params=[
nkeys.PREFIX_BYTE_OPERATOR,
nkeys.PREFIX_BYTE_SERVER,
nkeys.PREFIX_BYTE_CLUSTER,
nkeys.PREFIX_BYTE_ACCOUNT,
nkeys.PREFIX_BYTE_USER
])
def prefix(request):
return request.param
332 changes: 150 additions & 182 deletions tests/nkeys_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,190 +12,158 @@
# limitations under the License.
#

import unittest
import sys
import pytest
import nkeys
import binascii
import base64
import os

PREFIXES = [
nkeys.PREFIX_BYTE_OPERATOR,
nkeys.PREFIX_BYTE_SERVER,
nkeys.PREFIX_BYTE_CLUSTER,
nkeys.PREFIX_BYTE_ACCOUNT,
nkeys.PREFIX_BYTE_USER
]


class NatsTestCase(unittest.TestCase):

def setUp(self):
print(
"\n=== RUN {0}.{1}".format(
self.__class__.__name__, self._testMethodName
)
)


class NkeysTest(NatsTestCase):

def test_from_seed_keypair(self):
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
kp = nkeys.from_seed(bytearray(seed.encode()))
self.assertTrue(type(kp) is nkeys.KeyPair)

def test_keypair_sign_nonce(self):
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
kp = nkeys.from_seed(bytearray(seed.encode()))
raw = kp.sign(b"PXoWU7zWAMt75FY")
sig = base64.b64encode(raw)
self.assertEqual(
sig,
b'ZaAiVDgB5CeYoXoQ7cBCmq+ZllzUnGUoDVb8C7PilWvCs8XKfUchAUhz2P4BYAF++Dg3w05CqyQFRDiGL6LrDw=='
)

def test_from_seed_keypair_bad_padding(self):
with self.assertRaises(nkeys.ErrInvalidSeed):
seed = "UAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
nkeys.from_seed(bytearray(seed.encode()))

def test_from_seed_keypair_invalid_seed(self):
with self.assertRaises(nkeys.ErrInvalidSeed):
seed = "AUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
nkeys.from_seed(bytearray(seed.encode()))

with self.assertRaises(nkeys.ErrInvalidSeed):
seed = ""
nkeys.from_seed(bytearray(seed.encode()))

def test_from_seed_keypair_valid_prefix_byte(self):
seeds = [
"SNAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU",
"SCAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU",
"SOAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU",
"SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
]

def test_from_seed_keypair():
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
kp = nkeys.from_seed(bytearray(seed.encode()))
assert type(kp) is nkeys.KeyPair


def test_keypair_sign_nonce():
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
kp = nkeys.from_seed(bytearray(seed.encode()))
raw = kp.sign(b"PXoWU7zWAMt75FY")
sig = base64.b64encode(raw)
assert sig == b'ZaAiVDgB5CeYoXoQ7cBCmq+ZllzUnGUoDVb8C7PilWvCs8XKfUchAUhz2P4BYAF++Dg3w05CqyQFRDiGL6LrDw=='


def test_from_seed_keypair_bad_padding():
with pytest.raises(nkeys.ErrInvalidSeed):
seed = "UAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
nkeys.from_seed(bytearray(seed.encode()))


def test_from_seed_keypair_invalid_seed():
with pytest.raises(nkeys.ErrInvalidSeed):
seed = "AUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
nkeys.from_seed(bytearray(seed.encode()))

with pytest.raises(nkeys.ErrInvalidSeed):
seed = ""
nkeys.from_seed(bytearray(seed.encode()))


def test_from_seed_keypair_valid_prefix_byte():
seeds = [
"SNAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU",
"SCAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU",
"SOAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU",
"SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
]
for seed in seeds:
nkeys.from_seed(bytearray(seed.encode()))


def test_from_seed_keypair_invalid_public_prefix_byte():
seeds = [
b'SBAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU',
b'SDAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU',
b'PWAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU',
b'PMAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU'
]
with pytest.raises(nkeys.ErrInvalidPrefixByte):
for seed in seeds:
nkeys.from_seed(bytearray(seed.encode()))

def test_from_seed_keypair_invalid_public_prefix_byte(self):
seeds = [
b'SBAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU',
b'SDAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU',
b'PWAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU',
b'PMAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU'
]
with self.assertRaises(nkeys.ErrInvalidPrefixByte):
for seed in seeds:
nkeys.from_seed(bytearray(seed))

def test_keypair_wipe(self):
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
kp = nkeys.from_seed(bytearray(seed.encode()))
self.assertTrue(kp._keys is not None)

kp.wipe()
with self.assertRaises(AttributeError):
kp._keys
with self.assertRaises(AttributeError):
kp._seed

def test_keypair_public_key(self):
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
encoded_seed = bytearray(seed.encode())
kp = nkeys.from_seed(encoded_seed)

self.assertEqual(None, kp._public_key)
self.assertEqual(
"UCK5N7N66OBOINFXAYC2ACJQYFSOD4VYNU6APEJTAVFZB2SVHLKGEW7L",
kp.public_key
)

# Confirm that the public key is wiped as well.
kp.wipe()
with self.assertRaises(AttributeError):
kp._public_key

def test_keypair_use_seed_to_verify_signature(self):
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
encoded_seed = bytearray(seed.encode())
kp = nkeys.from_seed(encoded_seed)
nonce = b'NcMQZSlX2lZ3Y4w'
sig = kp.sign(nonce)
self.assertTrue(kp.verify(nonce, sig))
with self.assertRaises(nkeys.ErrInvalidSignature):
kp.verify(nonce + b'asdf', sig)

def test_keypair_seed_property(self):
seed = bytearray(
b"SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
)
kp = nkeys.from_seed(seed)
self.assertEqual(
kp.seed,
bytearray(
b"SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
)
)

# Throw away the seed.
kp.wipe()

with self.assertRaises(nkeys.ErrInvalidSeed):
kp.seed

def test_keypair_public_key(self):
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
encoded_seed = bytearray(seed.encode())
kp = nkeys.from_seed(encoded_seed)

self.assertEqual(None, kp._public_key)
self.assertEqual(
b"UCK5N7N66OBOINFXAYC2ACJQYFSOD4VYNU6APEJTAVFZB2SVHLKGEW7L",
kp.public_key
)

# Confirm that the public key is wiped as well.
kp.wipe()
with self.assertRaises(AttributeError):
kp._public_key

def test_keypair_private_key(self):
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
encoded_seed = bytearray(seed.encode())
kp = nkeys.from_seed(encoded_seed)
self.assertEqual(None, kp._public_key)

priv = kp.private_key
self.assertEqual(
b"PDC2WWLK67NUTFW7ZH5A7FOPZC32VXYZYWYNQMQ6RQWP2FEEF6KDVFOW7W7PHAXEGS3QMBNABEYMCZHB6K4G2PAHSEZQKS4Q5JKTVVDCJORA",
priv
)

# Confirm that the private_key is wiped as well.
kp.wipe()
with self.assertRaises(AttributeError):
kp._keys
with self.assertRaises(AttributeError):
kp._private_key

def test_roundtrip_seed_encoding(self):
# This test is a low-tech property test in disguise,
# testing the property:
# decode . encode == identity
# Using a proper framework like hypothesis might be preferable.
num_trials = 500
raw_seeds = [os.urandom(32) for _ in range(num_trials)]
for raw_seed in raw_seeds:
for prefix in PREFIXES:
with self.subTest(rawseed=raw_seed, prefix=prefix):
encoded_seed = nkeys.encode_seed(raw_seed, prefix)
decoded_prefix, decoded_seed = nkeys.decode_seed(encoded_seed)
self.assertEqual(prefix, decoded_prefix)
self.assertEqual(raw_seed, decoded_seed)


if __name__ == '__main__':
runner = unittest.TextTestRunner(stream=sys.stdout)
unittest.main(verbosity=2, exit=False, testRunner=runner)
nkeys.from_seed(bytearray(seed))
Comment on lines +69 to +71
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pytest.raises context manager will only catch the exception from the first iteration of the loop. If the first seed raises an exception, subsequent seeds won't be tested. Each seed should be tested individually with its own pytest.raises block, or use pytest.mark.parametrize to test each seed separately.

Copilot uses AI. Check for mistakes.


def test_keypair_wipe():
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
kp = nkeys.from_seed(bytearray(seed.encode()))
assert kp._keys is not None

kp.wipe()
with pytest.raises(AttributeError):
kp._keys
with pytest.raises(AttributeError):
kp._seed


def test_keypair_public_key():
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
encoded_seed = bytearray(seed.encode())
kp = nkeys.from_seed(encoded_seed)

assert kp._public_key is None
assert kp.public_key == "UCK5N7N66OBOINFXAYC2ACJQYFSOD4VYNU6APEJTAVFZB2SVHLKGEW7L"
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion expects public_key to return a string, but based on the test_keypair_public_key_bytes function at line 133 and the nkeys implementation, public_key returns bytes. This assertion will fail. Change to: assert kp.public_key == b\"UCK5N7N66OBOINFXAYC2ACJQYFSOD4VYNU6APEJTAVFZB2SVHLKGEW7L\"

Suggested change
assert kp.public_key == "UCK5N7N66OBOINFXAYC2ACJQYFSOD4VYNU6APEJTAVFZB2SVHLKGEW7L"
assert kp.public_key == b"UCK5N7N66OBOINFXAYC2ACJQYFSOD4VYNU6APEJTAVFZB2SVHLKGEW7L"

Copilot uses AI. Check for mistakes.

# Confirm that the public key is wiped as well.
kp.wipe()
with pytest.raises(AttributeError):
kp._public_key


def test_keypair_use_seed_to_verify_signature():
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
encoded_seed = bytearray(seed.encode())
kp = nkeys.from_seed(encoded_seed)
nonce = b'NcMQZSlX2lZ3Y4w'
sig = kp.sign(nonce)
assert kp.verify(nonce, sig)
with pytest.raises(nkeys.ErrInvalidSignature):
kp.verify(nonce + b'asdf', sig)


def test_keypair_seed_property():
seed = bytearray(
b"SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
)
kp = nkeys.from_seed(seed)
assert kp.seed == bytearray(
b"SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
)

# Throw away the seed.
kp.wipe()

with pytest.raises(nkeys.ErrInvalidSeed):
kp.seed


def test_keypair_public_key_bytes():
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
encoded_seed = bytearray(seed.encode())
kp = nkeys.from_seed(encoded_seed)

assert kp._public_key is None
assert kp.public_key == b"UCK5N7N66OBOINFXAYC2ACJQYFSOD4VYNU6APEJTAVFZB2SVHLKGEW7L"

# Confirm that the public key is wiped as well.
kp.wipe()
with pytest.raises(AttributeError):
kp._public_key


def test_keypair_private_key():
seed = "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
encoded_seed = bytearray(seed.encode())
kp = nkeys.from_seed(encoded_seed)
assert kp._public_key is None

priv = kp.private_key
assert priv == b"PDC2WWLK67NUTFW7ZH5A7FOPZC32VXYZYWYNQMQ6RQWP2FEEF6KDVFOW7W7PHAXEGS3QMBNABEYMCZHB6K4G2PAHSEZQKS4Q5JKTVVDCJORA"

# Confirm that the private_key is wiped as well.
kp.wipe()
with pytest.raises(AttributeError):
kp._keys
with pytest.raises(AttributeError):
kp._private_key


def test_roundtrip_seed_encoding(prefix):
# This test is a low-tech property test in disguise,
# testing the property:
# decode . encode == identity
# Using a proper framework like hypothesis might be preferable.
num_trials = 100
raw_seeds = [os.urandom(32) for _ in range(num_trials)]
for raw_seed in raw_seeds:
encoded_seed = nkeys.encode_seed(raw_seed, prefix)
decoded_prefix, decoded_seed = nkeys.decode_seed(encoded_seed)
assert prefix == decoded_prefix
assert raw_seed == decoded_seed