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 48eca8d..9afcf07 100644 --- a/hermit/coordinator.py +++ b/hermit/coordinator.py @@ -1,13 +1,12 @@ 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 +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") @@ -39,30 +38,25 @@ 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_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. +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 = RSA.importKey(private_key_file.read()) + private_key = PrivateKey.parse(private_key_file.read().strip()) - digest = SHA256.new() - digest.update(message) - signer = PKCS1_v1_5.new(private_key) - signature = signer.sign(digest) - return signature + signature = private_key.sign_message(message) + return signature.der() -def validate_rsa_signature(message: bytes, signature: bytes) -> None: - """Validate an RSA signature. +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`). @@ -73,27 +67,27 @@ def validate_rsa_signature(message: bytes, signature: bytes) -> None: """ 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) + public_key = S256Point.parse(bytes.fromhex(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 + 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. +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. @@ -115,13 +109,16 @@ def extract_rsa_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: +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_rsa_signature( + sig_bytes = create_secp256k1_signature( bytes(psbt_base64, "utf-8"), private_key_path, ) 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.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.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/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/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 d44acb9..dab10ba 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -1,14 +1,14 @@ from unittest.mock import Mock, patch from pytest import raises -from buidl.psbt import 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, COORDINATOR_SIGNATURE_KEY, ) @@ -41,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 @@ -54,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 @@ -76,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 @@ -98,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." ) @@ -109,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 @@ -127,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." ) @@ -138,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) @@ -177,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() @@ -185,37 +185,81 @@ 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): +class TestPSBTSignatureSecP256K1Basics(object): def setup(self): - self.public_key = open("tests/fixtures/coordinator.pub", "r").read() - self.private_key_path = "tests/fixtures/coordinator.pem" + # 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.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.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,14 +274,18 @@ def test_psbt_serialization_stable(self, mock_config): def test_psbt_signature(self, mock_config): mock_config.return_value = self.config - add_rsa_signature(self.psbt, self.private_key_path) + 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] == 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 - add_rsa_signature(self.psbt, self.private_key_path) + add_secp256k1_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) + unsigned_psbt_base64_bytes, sig_bytes = extract_signature_params(self.psbt) + validate_secp256k1_signature(unsigned_psbt_base64_bytes, sig_bytes)