From 54cbe1982213b1bd9365cac8c6a276d10e2df9e3 Mon Sep 17 00:00:00 2001 From: Chris Howe Date: Tue, 12 Oct 2021 11:06:22 -0500 Subject: [PATCH 1/3] Added secp256k1 coordinator signatures. --- hermit/coordinator.py | 72 +++++++++++++++++++++++++++++- tests/fixtures/coordinator.privkey | 1 + tests/fixtures/coordinator.pubkey | 1 + tests/test_coordinator.py | 61 ++++++++++++++++++++++--- 4 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 tests/fixtures/coordinator.privkey create mode 100644 tests/fixtures/coordinator.pubkey diff --git a/hermit/coordinator.py b/hermit/coordinator.py index 48eca8d..568f1ec 100644 --- a/hermit/coordinator.py +++ b/hermit/coordinator.py @@ -8,6 +8,8 @@ from .config import get_config from .errors import InvalidCoordinatorSignature +from buidl import PrivateKey, S256Point, Signature + #: The key that holds an optional coordinator signature for a PSBT. COORDINATOR_SIGNATURE_KEY: bytes = "coordinator_sig".encode("utf8") @@ -43,6 +45,18 @@ def validate_coordinator_signature_if_necessary(original_psbt: PSBT) -> None: validate_rsa_signature(unsigned_psbt_base64_bytes, sig_bytes) +def create_secp256k1_signature(message: bytes, private_key_path: str) -> bytes: + """ Create a secp256k1 signature. + + This function is not called within usual Hermit operation. It is useful + for scripts and tests. + """ + with open(private_key_path, mode="r") as private_key_file: + private_key = PrivateKey.parse( private_key_file.read().strip() ) + + signature = private_key.sign_message(message) + return signature.der() + def create_rsa_signature(message: bytes, private_key_path: str) -> bytes: """Create an RSA signature. @@ -92,8 +106,43 @@ def validate_rsa_signature(message: bytes, signature: bytes) -> None: raise InvalidCoordinatorSignature("Coordinator signature is invalid.") +def validate_secp256k1_signature(message: bytes, signature: bytes) -> None: + """Validate a secp256k1 signature. + + Uses the public key from Hermit's configuration for verification + (see :attr:`~hermit.config.DefaultCoordinator`). + + Will raise :class:`~hermit.errors.InvalidCoordinatorSignature` if + the public key is missing or invalid or if the signature is + invalid. + + """ + public_key_text = get_config().coordinator.get("public_key") + + if public_key_text is None: + raise InvalidCoordinatorSignature( + "Coordinator signature is present but no public key is configured." + ) + + try: + public_key = S256Point.parse(bytes.fromhex(public_key_text)) + except Exception: + raise InvalidCoordinatorSignature( + "Coordinator signature is present but coordinator public key is invalid." + ) + + sig = Signature.parse(signature) + + if not public_key.verify_message(message, sig): + raise InvalidCoordinatorSignature("Coordinator signature is invalid.") + + def extract_rsa_signature_params(original_psbt: PSBT) -> Tuple[bytes, bytes]: - """Extract RSA signature parameters from a PSBT. + return extract_signature_params(original_psbt) + + +def extract_signature_params(original_psbt: PSBT) -> Tuple[bytes, bytes]: + """Extract signature parameters from a PSBT. The value of the :attr:`COORDINATOR_SIGNATURE_KEY` key within the PSBT's `extra_map` is extracted as the signature bytes. @@ -117,6 +166,9 @@ def extract_rsa_signature_params(original_psbt: PSBT) -> Tuple[bytes, bytes]: def add_rsa_signature(original_psbt: PSBT, private_key_path: str) -> PSBT: """Add a signature to a PSBT. + + This is useful for scripts and tests, but not actually ever called in + the course of regular Hermit operation """ psbt_base64 = original_psbt.serialize_base64() @@ -128,3 +180,21 @@ def add_rsa_signature(original_psbt: PSBT, private_key_path: str) -> PSBT: original_psbt.extra_map[COORDINATOR_SIGNATURE_KEY] = sig_bytes return original_psbt + + +def add_secp256k1_signature(original_psbt: PSBT, private_key_path: str) -> PSBT: + """Add a signature to a PSBT. + + This is useful for scripts and tests, but not actually ever called in + the course of regular Hermit operation + """ + + psbt_base64 = original_psbt.serialize_base64() + + sig_bytes = create_secp256k1_signature( + bytes(psbt_base64, "utf-8"), + private_key_path, + ) + + original_psbt.extra_map[COORDINATOR_SIGNATURE_KEY] = sig_bytes + return original_psbt diff --git a/tests/fixtures/coordinator.privkey b/tests/fixtures/coordinator.privkey new file mode 100644 index 0000000..6091f23 --- /dev/null +++ b/tests/fixtures/coordinator.privkey @@ -0,0 +1 @@ +L2mQE1xS3wSj2miAUhT9MGNhCsPNRzd3xvmDozsk6uuWSVGNt2oC diff --git a/tests/fixtures/coordinator.pubkey b/tests/fixtures/coordinator.pubkey new file mode 100644 index 0000000..38ae746 --- /dev/null +++ b/tests/fixtures/coordinator.pubkey @@ -0,0 +1 @@ +02acbc80af6db9f7308db032d782d51855745bd64a239496b60b239dc96d6dba12 diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index d44acb9..97e1eff 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -1,6 +1,6 @@ from unittest.mock import Mock, patch from pytest import raises -from buidl.psbt import PSBT +from buidl import PrivateKey, PSBT from hermit import InvalidCoordinatorSignature from hermit.coordinator import ( @@ -9,6 +9,9 @@ create_rsa_signature, add_rsa_signature, extract_rsa_signature_params, + create_secp256k1_signature, + add_secp256k1_signature, + validate_secp256k1_signature, COORDINATOR_SIGNATURE_KEY, ) @@ -212,10 +215,6 @@ def setup(self): self.psbt = PSBT.parse_base64(self.original_psbt_base64) self.psbt_base64 = self.psbt.serialize_base64() - self.signature = create_rsa_signature( - bytes(self.psbt_base64, "utf8"), - self.private_key_path - ) self.config = Mock() self.coordinator_config = dict(public_key=self.public_key) @@ -230,9 +229,13 @@ def test_psbt_serialization_stable(self, mock_config): def test_psbt_signature(self, mock_config): mock_config.return_value = self.config + signature = create_rsa_signature( + bytes(self.psbt_base64, "utf-8"), + self.private_key_path + ) add_rsa_signature(self.psbt, self.private_key_path) - assert self.psbt.extra_map[COORDINATOR_SIGNATURE_KEY] == self.signature + assert self.psbt.extra_map[COORDINATOR_SIGNATURE_KEY] == signature def test_validate_psbt_signature(self, mock_config): mock_config.return_value = self.config @@ -241,3 +244,49 @@ def test_validate_psbt_signature(self, mock_config): unsigned_psbt_base64_bytes, sig_bytes = extract_rsa_signature_params(self.psbt) validate_rsa_signature(unsigned_psbt_base64_bytes, sig_bytes) + + +@patch("hermit.coordinator.get_config") +class TestPSBTSignatureSecP256K1Basics(object): + def setup(self): + # Pubkey stored in hex in the fixtures folder + self.public_key = open("tests/fixtures/coordinator.pubkey", "r").read().strip() + + # Privkey stored in wif format in the fixtures folder + self.private_key_path = "tests/fixtures/coordinator.privkey" + #self.private_key = PrivateKey.parse(open(self.private_key_path, "r").read().strip()) + + self.original_psbt_base64 = open("tests/fixtures/signature_requests/2-of-2.p2sh.testnet.psbt", "r").read() + + self.psbt = PSBT.parse_base64(self.original_psbt_base64) + self.psbt_base64 = self.psbt.serialize_base64() + + self.config = Mock() + self.coordinator_config = dict(public_key=self.public_key) + self.config.coordinator = self.coordinator_config + + def test_psbt_serialization_stable(self, mock_config): + mock_config.return_value = self.config + + p2 = PSBT.parse_base64(self.psbt_base64) + assert p2.serialize_base64() == self.psbt.serialize_base64() + + def test_psbt_signature(self, mock_config): + mock_config.return_value = self.config + + signature = create_secp256k1_signature( + bytes(self.psbt_base64, "utf-8"), + self.private_key_path + ) + + add_secp256k1_signature(self.psbt, self.private_key_path) + + assert self.psbt.extra_map[COORDINATOR_SIGNATURE_KEY] == signature + + def test_validate_psbt_signature(self, mock_config): + mock_config.return_value = self.config + + add_secp256k1_signature(self.psbt, self.private_key_path) + + unsigned_psbt_base64_bytes, sig_bytes = extract_rsa_signature_params(self.psbt) + validate_secp256k1_signature(unsigned_psbt_base64_bytes, sig_bytes) From 2443b2ea67cfb390cf42154abb56f13e6f39bb50 Mon Sep 17 00:00:00 2001 From: Chris Howe Date: Tue, 12 Oct 2021 11:10:29 -0500 Subject: [PATCH 2/3] formatting --- hermit/config.py | 4 ++-- hermit/coordinator.py | 7 ++++--- tests/test_coordinator.py | 16 +++++++++------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/hermit/config.py b/hermit/config.py index f8ec937..20523b1 100644 --- a/hermit/config.py +++ b/hermit/config.py @@ -164,6 +164,6 @@ def _interpolate_commands(self) -> None: def _interpolate_paths(self) -> None: self.config["paths"] = { - key : os.path.expandvars(os.path.expanduser(value)) - for key, value in self.config['paths'].items() + key: os.path.expandvars(os.path.expanduser(value)) + for key, value in self.config["paths"].items() } diff --git a/hermit/coordinator.py b/hermit/coordinator.py index 568f1ec..e64be1c 100644 --- a/hermit/coordinator.py +++ b/hermit/coordinator.py @@ -46,17 +46,18 @@ def validate_coordinator_signature_if_necessary(original_psbt: PSBT) -> None: def create_secp256k1_signature(message: bytes, private_key_path: str) -> bytes: - """ Create a secp256k1 signature. + """Create a secp256k1 signature. This function is not called within usual Hermit operation. It is useful for scripts and tests. """ with open(private_key_path, mode="r") as private_key_file: - private_key = PrivateKey.parse( private_key_file.read().strip() ) + private_key = PrivateKey.parse(private_key_file.read().strip()) signature = private_key.sign_message(message) return signature.der() - + + def create_rsa_signature(message: bytes, private_key_path: str) -> bytes: """Create an RSA signature. diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 97e1eff..1a798a0 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -211,7 +211,9 @@ def setup(self): self.public_key = open("tests/fixtures/coordinator.pub", "r").read() self.private_key_path = "tests/fixtures/coordinator.pem" - self.original_psbt_base64 = open("tests/fixtures/signature_requests/2-of-2.p2sh.testnet.psbt", "r").read() + self.original_psbt_base64 = open( + "tests/fixtures/signature_requests/2-of-2.p2sh.testnet.psbt", "r" + ).read() self.psbt = PSBT.parse_base64(self.original_psbt_base64) self.psbt_base64 = self.psbt.serialize_base64() @@ -230,8 +232,7 @@ def test_psbt_signature(self, mock_config): mock_config.return_value = self.config signature = create_rsa_signature( - bytes(self.psbt_base64, "utf-8"), - self.private_key_path + bytes(self.psbt_base64, "utf-8"), self.private_key_path ) add_rsa_signature(self.psbt, self.private_key_path) @@ -254,9 +255,11 @@ def setup(self): # Privkey stored in wif format in the fixtures folder self.private_key_path = "tests/fixtures/coordinator.privkey" - #self.private_key = PrivateKey.parse(open(self.private_key_path, "r").read().strip()) + # self.private_key = PrivateKey.parse(open(self.private_key_path, "r").read().strip()) - self.original_psbt_base64 = open("tests/fixtures/signature_requests/2-of-2.p2sh.testnet.psbt", "r").read() + self.original_psbt_base64 = open( + "tests/fixtures/signature_requests/2-of-2.p2sh.testnet.psbt", "r" + ).read() self.psbt = PSBT.parse_base64(self.original_psbt_base64) self.psbt_base64 = self.psbt.serialize_base64() @@ -275,8 +278,7 @@ def test_psbt_signature(self, mock_config): mock_config.return_value = self.config signature = create_secp256k1_signature( - bytes(self.psbt_base64, "utf-8"), - self.private_key_path + bytes(self.psbt_base64, "utf-8"), self.private_key_path ) add_secp256k1_signature(self.psbt, self.private_key_path) From 057e75770670b132fc0452742c408c9ebc33f45b Mon Sep 17 00:00:00 2001 From: Chris Howe Date: Tue, 12 Oct 2021 16:28:34 -0500 Subject: [PATCH 3/3] Switched to secp256k1 coordinator signatures Added a script to rewrite psbts in 'buidl' normal order. Added normalized and signed versions of the 2-of-2.p2sh psbt in the fixtures. --- hermit/coordinator.py | 78 +------- requirements.txt | 1 - scripts/normalize_psbt.py | 26 +++ scripts/sign_psbt_as_coordinator.py | 21 ++- tests/fixtures/coordinator.pem | 15 -- tests/fixtures/coordinator.pub | 6 - ...testnet.normalized.coordinator_signed.psbt | 1 + tests/test_coordinator.py | 175 +++++++++--------- 8 files changed, 127 insertions(+), 196 deletions(-) create mode 100644 scripts/normalize_psbt.py delete mode 100644 tests/fixtures/coordinator.pem delete mode 100644 tests/fixtures/coordinator.pub create mode 100644 tests/fixtures/signature_requests/2-of-2.p2sh.testnet.normalized.coordinator_signed.psbt diff --git a/hermit/coordinator.py b/hermit/coordinator.py index e64be1c..9afcf07 100644 --- a/hermit/coordinator.py +++ b/hermit/coordinator.py @@ -1,9 +1,6 @@ from typing import Tuple from buidl import PSBT -from Crypto.Hash import SHA256 -from Crypto.Signature import PKCS1_v1_5 -from Crypto.PublicKey import RSA from .config import get_config from .errors import InvalidCoordinatorSignature @@ -41,8 +38,8 @@ def validate_coordinator_signature_if_necessary(original_psbt: PSBT) -> None: else: return - unsigned_psbt_base64_bytes, sig_bytes = extract_rsa_signature_params(original_psbt) - validate_rsa_signature(unsigned_psbt_base64_bytes, sig_bytes) + unsigned_psbt_base64_bytes, sig_bytes = extract_signature_params(original_psbt) + validate_secp256k1_signature(unsigned_psbt_base64_bytes, sig_bytes) def create_secp256k1_signature(message: bytes, private_key_path: str) -> bytes: @@ -58,55 +55,6 @@ def create_secp256k1_signature(message: bytes, private_key_path: str) -> bytes: return signature.der() -def create_rsa_signature(message: bytes, private_key_path: str) -> bytes: - """Create an RSA signature. - - This function is not called within usual Hermit operation. It is - useful for scripts and tests. - - """ - - with open(private_key_path, mode="r") as private_key_file: - private_key = RSA.importKey(private_key_file.read()) - - digest = SHA256.new() - digest.update(message) - signer = PKCS1_v1_5.new(private_key) - signature = signer.sign(digest) - return signature - - -def validate_rsa_signature(message: bytes, signature: bytes) -> None: - """Validate an RSA signature. - - Uses the public key from Hermit's configuration for verification - (see :attr:`~hermit.config.DefaultCoordinator`). - - Will raise :class:`~hermit.errors.InvalidCoordinatorSignature` if - the public key is missing or invalid or if the signature is - invalid. - - """ - public_key_text = get_config().coordinator.get("public_key") - if public_key_text is None: - raise InvalidCoordinatorSignature( - "Coordinator signature is present but no public key is configured." - ) - - try: - public_key = RSA.importKey(public_key_text) - except Exception: - raise InvalidCoordinatorSignature( - "Coordinator signature is present but coordinator public key is invalid." - ) - - digest = SHA256.new() - digest.update(message) - verifier = PKCS1_v1_5.new(public_key) - if not verifier.verify(digest, signature): # type: ignore - raise InvalidCoordinatorSignature("Coordinator signature is invalid.") - - def validate_secp256k1_signature(message: bytes, signature: bytes) -> None: """Validate a secp256k1 signature. @@ -138,10 +86,6 @@ def validate_secp256k1_signature(message: bytes, signature: bytes) -> None: raise InvalidCoordinatorSignature("Coordinator signature is invalid.") -def extract_rsa_signature_params(original_psbt: PSBT) -> Tuple[bytes, bytes]: - return extract_signature_params(original_psbt) - - def extract_signature_params(original_psbt: PSBT) -> Tuple[bytes, bytes]: """Extract signature parameters from a PSBT. @@ -165,24 +109,6 @@ def extract_signature_params(original_psbt: PSBT) -> Tuple[bytes, bytes]: return unsigned_psbt_base64_bytes, sig_bytes -def add_rsa_signature(original_psbt: PSBT, private_key_path: str) -> PSBT: - """Add a signature to a PSBT. - - This is useful for scripts and tests, but not actually ever called in - the course of regular Hermit operation - """ - - psbt_base64 = original_psbt.serialize_base64() - - sig_bytes = create_rsa_signature( - bytes(psbt_base64, "utf-8"), - private_key_path, - ) - - original_psbt.extra_map[COORDINATOR_SIGNATURE_KEY] = sig_bytes - return original_psbt - - def add_secp256k1_signature(original_psbt: PSBT, private_key_path: str) -> PSBT: """Add a signature to a PSBT. diff --git a/requirements.txt b/requirements.txt index d31a000..3b905c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,6 @@ pluggy==1.0.0 prompt-toolkit==2.0.7 py==1.10.0 pycodestyle==2.7.0 -pycryptodome==3.11.0 pyflakes==2.3.1 Pygments==2.10.0 pyparsing==2.4.7 diff --git a/scripts/normalize_psbt.py b/scripts/normalize_psbt.py new file mode 100644 index 0000000..e08c801 --- /dev/null +++ b/scripts/normalize_psbt.py @@ -0,0 +1,26 @@ +from sys import stdin, argv +from os.path import basename + +from buidl import PSBT + + +HELP = f"""usage: cat ... | python {basename(__file__)} + +This program reads a PSBT (in base64) over STDIN, reads it into +buidl and prints the resulting PSBT in connonical order. +""" + +if __name__ == "__main__": + + if ("--help" in argv) or ("-h" in argv) or len(argv) != 1: + print(HELP) + exit(1) + + raw_unsigned_psbt_base64 = stdin.read().strip() + if len(raw_unsigned_psbt_base64) == 0: + print("Input PSBT is required.") + exit(2) + + psbt = PSBT.parse_base64(raw_unsigned_psbt_base64) + + print(psbt.serialize_base64()) diff --git a/scripts/sign_psbt_as_coordinator.py b/scripts/sign_psbt_as_coordinator.py index 9ba5542..39ef6e3 100644 --- a/scripts/sign_psbt_as_coordinator.py +++ b/scripts/sign_psbt_as_coordinator.py @@ -4,8 +4,7 @@ from buidl import PSBT from hermit.coordinator import ( - COORDINATOR_SIGNATURE_KEY, - create_rsa_signature, + add_secp256k1_signature, ) HELP = f"""usage: cat ... | python {basename(__file__)} PRIVATE_KEY_PATH @@ -13,7 +12,7 @@ This program reads an unsigned PSBT (in base64) over STDIN, signs that PSBT as a coordinator, and prints the resulting PSBT.. -The RSA private key at PRIVATE_KEY_PATH is used for signing.""" +The secp256k1 private key at PRIVATE_KEY_PATH is used for signing.""" if __name__ == "__main__": @@ -23,15 +22,19 @@ private_key_path = argv[1] - unsigned_psbt_base64 = stdin.read().strip() - if len(unsigned_psbt_base64) == 0: + raw_unsigned_psbt_base64 = stdin.read().strip() + if len(raw_unsigned_psbt_base64) == 0: print("Input PSBT is required.") exit(2) - psbt = PSBT.parse_base64(unsigned_psbt_base64) - message = unsigned_psbt_base64.encode("utf8") - signature = create_rsa_signature(message, private_key_path) + psbt = PSBT.parse_base64(raw_unsigned_psbt_base64) + unsigned_psbt_base64 = psbt.serialize_base64() + + if unsigned_psbt_base64 != raw_unsigned_psbt_base64: + print("Input PSBT must be generated by buidl.") + exit(2) - psbt.extra_map[COORDINATOR_SIGNATURE_KEY] = signature + message = unsigned_psbt_base64.encode("utf8") + add_secp256k1_signature(psbt, private_key_path) print(psbt.serialize_base64()) diff --git a/tests/fixtures/coordinator.pem b/tests/fixtures/coordinator.pem deleted file mode 100644 index e72fdb1..0000000 --- a/tests/fixtures/coordinator.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXwIBAAKBgQDDm6LRbZLXjha+bODC7SID5SKYWfhwHNJj9fTI9wxankYzMuSM -UiSq29HBmwPkVtFzFdLuYKFnc6+1pIwXHP+Xf7PcwO4esRMkzEZUK2IR5BniarMT -Q9/sBXf7ttj6Nhp1LTQ2aZ4fjGdI9ZWj/hcJY7wY56KIRZHF+/rAzXNFfQIDAQAB -AoGBAL//3GlE7IW4aoqvxE6RBHpeRv7UEQ+6uqhzm7pHBFFOWgmXQs6ZMnSjH9ix -l7hhn2UfXtOs9cDdxPK+eOOXCyiqUhhVeTdrwYkEI9zfvUX6r5Jo/zfaJtFB9+8g -zJr3ABtV18MlESSFHYoFJ9SjZhsdqRJM7at/zta3g1Zt4tPhAkEA4qYG2qSuRVLb -spd2yuo+dc7SAYDSytYiA40x8kDv/E7sFGHDPiUvrBUC7rfVpNoPtUk3DDJ8nkP3 -Hn0HSA8KGwJBANzwiPvFQ9Rc96vDTMqagVsKq3Q8uQiQYbT2d8kBHkzqscrnrl36 -+/MIgtMPfwe5lzHzN3lxquEsi2CuutJN6EcCQQCn0oX6ubvsyviwmeS1RZOwSc9I -m6n51WrkNFWKarkImyvFv8oBJynQgtJkDq1cXrcI5kijeHK8Adlmsu+EVNaHAkEA -m2/SP6cB2Hbre/jznppipUV1aFqMJv1E8EZx8YUK5zw6hzDF2LKJ7Oqw94IwcaPd -PjQJdDRG7xIioIttPiW3YwJBAJ8AviflkzhUyHnrWoSpLqCTYWn1VAu/z5ByEVkA -W5JebxXwmWzxI4uOaionoNEGs2Zf7XhMIDU6XnAT+Sl17WY= ------END RSA PRIVATE KEY----- diff --git a/tests/fixtures/coordinator.pub b/tests/fixtures/coordinator.pub deleted file mode 100644 index de404af..0000000 --- a/tests/fixtures/coordinator.pub +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDm6LRbZLXjha+bODC7SID5SKY -WfhwHNJj9fTI9wxankYzMuSMUiSq29HBmwPkVtFzFdLuYKFnc6+1pIwXHP+Xf7Pc -wO4esRMkzEZUK2IR5BniarMTQ9/sBXf7ttj6Nhp1LTQ2aZ4fjGdI9ZWj/hcJY7wY -56KIRZHF+/rAzXNFfQIDAQAB ------END PUBLIC KEY----- \ No newline at end of file diff --git a/tests/fixtures/signature_requests/2-of-2.p2sh.testnet.normalized.coordinator_signed.psbt b/tests/fixtures/signature_requests/2-of-2.p2sh.testnet.normalized.coordinator_signed.psbt new file mode 100644 index 0000000..28cd1af --- /dev/null +++ b/tests/fixtures/signature_requests/2-of-2.p2sh.testnet.normalized.coordinator_signed.psbt @@ -0,0 +1 @@ +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 diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 1a798a0..dab10ba 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -1,14 +1,11 @@ from unittest.mock import Mock, patch from pytest import raises -from buidl import PrivateKey, PSBT +from buidl import PSBT from hermit import InvalidCoordinatorSignature from hermit.coordinator import ( validate_coordinator_signature_if_necessary, - validate_rsa_signature, - create_rsa_signature, - add_rsa_signature, - extract_rsa_signature_params, + extract_signature_params, create_secp256k1_signature, add_secp256k1_signature, validate_secp256k1_signature, @@ -44,12 +41,12 @@ def test_when_signature_absent_and_required(self, mock_get_config): assert "signature is missing" in str(e) mock_get_config.assert_called_once_with() - @patch("hermit.coordinator.extract_rsa_signature_params") - @patch("hermit.coordinator.validate_rsa_signature") + @patch("hermit.coordinator.extract_signature_params") + @patch("hermit.coordinator.validate_secp256k1_signature") def test_when_signature_valid_and_not_required( self, - mock_validate_rsa_signature, - mock_extract_rsa_signature_params, + mock_validate_secp256k1_signature, + mock_extract_signature_params, mock_get_config, ): mock_get_config.return_value = self.config @@ -57,21 +54,21 @@ def test_when_signature_valid_and_not_required( self.extra_map[COORDINATOR_SIGNATURE_KEY] = self.signature - mock_extract_rsa_signature_params.return_value = (self.message, self.signature) + mock_extract_signature_params.return_value = (self.message, self.signature) validate_coordinator_signature_if_necessary(self.psbt) mock_get_config.assert_called_once_with() - mock_extract_rsa_signature_params.assert_called_once_with(self.psbt) - mock_validate_rsa_signature.assert_called_once_with( + mock_extract_signature_params.assert_called_once_with(self.psbt) + mock_validate_secp256k1_signature.assert_called_once_with( self.message, self.signature ) - @patch("hermit.coordinator.extract_rsa_signature_params") - @patch("hermit.coordinator.validate_rsa_signature") + @patch("hermit.coordinator.extract_signature_params") + @patch("hermit.coordinator.validate_secp256k1_signature") def test_when_signature_valid_and_required( self, - mock_validate_rsa_signature, - mock_extract_rsa_signature_params, + mock_validate_secp256k1_signature, + mock_extract_signature_params, mock_get_config, ): mock_get_config.return_value = self.config @@ -79,21 +76,21 @@ def test_when_signature_valid_and_required( self.extra_map[COORDINATOR_SIGNATURE_KEY] = self.signature - mock_extract_rsa_signature_params.return_value = (self.message, self.signature) + mock_extract_signature_params.return_value = (self.message, self.signature) validate_coordinator_signature_if_necessary(self.psbt) mock_get_config.assert_called_once_with() - mock_extract_rsa_signature_params.assert_called_once_with(self.psbt) - mock_validate_rsa_signature.assert_called_once_with( + mock_extract_signature_params.assert_called_once_with(self.psbt) + mock_validate_secp256k1_signature.assert_called_once_with( self.message, self.signature ) - @patch("hermit.coordinator.extract_rsa_signature_params") - @patch("hermit.coordinator.validate_rsa_signature") + @patch("hermit.coordinator.extract_signature_params") + @patch("hermit.coordinator.validate_secp256k1_signature") def test_when_signature_invalid_and_not_required( self, - mock_validate_rsa_signature, - mock_extract_rsa_signature_params, + mock_validate_secp256k1_signature, + mock_extract_signature_params, mock_get_config, ): mock_get_config.return_value = self.config @@ -101,9 +98,9 @@ def test_when_signature_invalid_and_not_required( self.extra_map[COORDINATOR_SIGNATURE_KEY] = self.signature - mock_extract_rsa_signature_params.return_value = (self.message, self.signature) + mock_extract_signature_params.return_value = (self.message, self.signature) - mock_validate_rsa_signature.side_effect = InvalidCoordinatorSignature( + mock_validate_secp256k1_signature.side_effect = InvalidCoordinatorSignature( "Invalid signature." ) @@ -112,17 +109,17 @@ def test_when_signature_invalid_and_not_required( assert "Invalid signature" in str(e) mock_get_config.assert_called_once_with() - mock_extract_rsa_signature_params.assert_called_once_with(self.psbt) - mock_validate_rsa_signature.assert_called_once_with( + mock_extract_signature_params.assert_called_once_with(self.psbt) + mock_validate_secp256k1_signature.assert_called_once_with( self.message, self.signature ) - @patch("hermit.coordinator.extract_rsa_signature_params") - @patch("hermit.coordinator.validate_rsa_signature") + @patch("hermit.coordinator.extract_signature_params") + @patch("hermit.coordinator.validate_secp256k1_signature") def test_when_signature_invalid_and_required( self, - mock_validate_rsa_signature, - mock_extract_rsa_signature_params, + mock_validate_secp256k1_signature, + mock_extract_signature_params, mock_get_config, ): mock_get_config.return_value = self.config @@ -130,9 +127,9 @@ def test_when_signature_invalid_and_required( self.extra_map[COORDINATOR_SIGNATURE_KEY] = self.signature - mock_extract_rsa_signature_params.return_value = (self.message, self.signature) + mock_extract_signature_params.return_value = (self.message, self.signature) - mock_validate_rsa_signature.side_effect = InvalidCoordinatorSignature( + mock_validate_secp256k1_signature.side_effect = InvalidCoordinatorSignature( "Invalid signature." ) @@ -141,36 +138,36 @@ def test_when_signature_invalid_and_required( assert "Invalid signature" in str(e) mock_get_config.assert_called_once_with() - mock_extract_rsa_signature_params.assert_called_once_with(self.psbt) - mock_validate_rsa_signature.assert_called_once_with( + mock_extract_signature_params.assert_called_once_with(self.psbt) + mock_validate_secp256k1_signature.assert_called_once_with( self.message, self.signature ) @patch("hermit.coordinator.get_config") -def test_create_rsa_sigature(mock_get_config): - public_key = open("tests/fixtures/coordinator.pub", "r").read() - private_key_path = "tests/fixtures/coordinator.pem" +def test_create_secp256k1_sigature(mock_get_config): + public_key = open("tests/fixtures/coordinator.pubkey", "r").read().strip() + private_key_path = "tests/fixtures/coordinator.privkey" message = "Hello there".encode("utf8") - signature = create_rsa_signature(message, private_key_path) + signature = create_secp256k1_signature(message, private_key_path) config = Mock() coordinator_config = dict(public_key=public_key) config.coordinator = coordinator_config mock_get_config.return_value = config - validate_rsa_signature(message, signature) + validate_secp256k1_signature(message, signature) mock_get_config.assert_called_once_with() @patch("hermit.coordinator.get_config") -class TestValidateRSASignature(object): +class TestValidateSECP256K1Signature(object): def setup(self): - self.public_key = open("tests/fixtures/coordinator.pub", "r").read() - self.private_key_path = "tests/fixtures/coordinator.pem" + self.public_key = open("tests/fixtures/coordinator.pubkey", "r").read().strip() + self.private_key_path = "tests/fixtures/coordinator.privkey" self.message = "Hello there".encode("utf8") - self.signature = create_rsa_signature(self.message, self.private_key_path) + self.signature = create_secp256k1_signature(self.message, self.private_key_path) self.config = Mock() self.coordinator_config = dict(public_key=self.public_key) @@ -180,7 +177,7 @@ def test_when_no_coordinator_public_key_is_configured(self, mock_config): del self.coordinator_config["public_key"] mock_config.return_value = self.config with raises(InvalidCoordinatorSignature) as e: - validate_rsa_signature(self.message, self.signature) + validate_secp256k1_signature(self.message, self.signature) assert "no public key is configured" in str(e) mock_config.assert_called_once_with() @@ -188,63 +185,63 @@ def test_when_invalid_coordinator_public_key_is_configured(self, mock_config): self.coordinator_config["public_key"] = "foobar" mock_config.return_value = self.config with raises(InvalidCoordinatorSignature) as e: - validate_rsa_signature(self.message, self.signature) + validate_secp256k1_signature(self.message, self.signature) assert "public key is invalid" in str(e) mock_config.assert_called_once_with() def test_when_signature_is_valid(self, mock_config): mock_config.return_value = self.config - validate_rsa_signature(self.message, self.signature) + validate_secp256k1_signature(self.message, self.signature) mock_config.assert_called_once_with() def test_when_signature_is_invalid(self, mock_config): mock_config.return_value = self.config with raises(InvalidCoordinatorSignature) as e: - validate_rsa_signature(self.message + b"hello", self.signature) + validate_secp256k1_signature(self.message + b"hello", self.signature) mock_config.assert_called_once_with() assert "signature is invalid" in str(e) -@patch("hermit.coordinator.get_config") -class TestPSBTSignatureBasics(object): - def setup(self): - self.public_key = open("tests/fixtures/coordinator.pub", "r").read() - self.private_key_path = "tests/fixtures/coordinator.pem" - - self.original_psbt_base64 = open( - "tests/fixtures/signature_requests/2-of-2.p2sh.testnet.psbt", "r" - ).read() - - self.psbt = PSBT.parse_base64(self.original_psbt_base64) - self.psbt_base64 = self.psbt.serialize_base64() - - self.config = Mock() - self.coordinator_config = dict(public_key=self.public_key) - self.config.coordinator = self.coordinator_config - - def test_psbt_serialization_stable(self, mock_config): - mock_config.return_value = self.config - - p2 = PSBT.parse_base64(self.psbt_base64) - assert p2.serialize_base64() == self.psbt.serialize_base64() - - def test_psbt_signature(self, mock_config): - mock_config.return_value = self.config - - signature = create_rsa_signature( - bytes(self.psbt_base64, "utf-8"), self.private_key_path - ) - add_rsa_signature(self.psbt, self.private_key_path) - - assert self.psbt.extra_map[COORDINATOR_SIGNATURE_KEY] == signature - - def test_validate_psbt_signature(self, mock_config): - mock_config.return_value = self.config - - add_rsa_signature(self.psbt, self.private_key_path) - - unsigned_psbt_base64_bytes, sig_bytes = extract_rsa_signature_params(self.psbt) - validate_rsa_signature(unsigned_psbt_base64_bytes, sig_bytes) +# @patch("hermit.coordinator.get_config") +# class TestPSBTSignatureBasics(object): +# def setup(self): +# self.public_key = open("tests/fixtures/coordinator.pub", "r").read() +# self.private_key_path = "tests/fixtures/coordinator.pem" +# +# self.original_psbt_base64 = open( +# "tests/fixtures/signature_requests/2-of-2.p2sh.testnet.psbt", "r" +# ).read() +# +# self.psbt = PSBT.parse_base64(self.original_psbt_base64) +# self.psbt_base64 = self.psbt.serialize_base64() +# +# self.config = Mock() +# self.coordinator_config = dict(public_key=self.public_key) +# self.config.coordinator = self.coordinator_config +# +# def test_psbt_serialization_stable(self, mock_config): +# mock_config.return_value = self.config +# +# p2 = PSBT.parse_base64(self.psbt_base64) +# assert p2.serialize_base64() == self.psbt.serialize_base64() +# +# def test_psbt_signature(self, mock_config): +# mock_config.return_value = self.config +# +# signature = create_rsa_signature( +# bytes(self.psbt_base64, "utf-8"), self.private_key_path +# ) +# add_rsa_signature(self.psbt, self.private_key_path) +# +# assert self.psbt.extra_map[COORDINATOR_SIGNATURE_KEY] == signature +# +# def test_validate_psbt_signature(self, mock_config): +# mock_config.return_value = self.config +# +# add_rsa_signature(self.psbt, self.private_key_path) +# +# unsigned_psbt_base64_bytes, sig_bytes = extract_rsa_signature_params(self.psbt) +# validate_rsa_signature(unsigned_psbt_base64_bytes, sig_bytes) @patch("hermit.coordinator.get_config") @@ -290,5 +287,5 @@ def test_validate_psbt_signature(self, mock_config): add_secp256k1_signature(self.psbt, self.private_key_path) - unsigned_psbt_base64_bytes, sig_bytes = extract_rsa_signature_params(self.psbt) + unsigned_psbt_base64_bytes, sig_bytes = extract_signature_params(self.psbt) validate_secp256k1_signature(unsigned_psbt_base64_bytes, sig_bytes)