diff --git a/duniterpy/key/signing_key.py b/duniterpy/key/signing_key.py index a36cd80fe9fbfa20a7bb58a107f7a7a0e77d4817..25324e74c7396b476390a5ac9438e09640b0d81f 100644 --- a/duniterpy/key/signing_key.py +++ b/duniterpy/key/signing_key.py @@ -3,6 +3,7 @@ duniter public and private keys @author: inso, vtexier, Moul """ +import base64 import re from typing import Optional, Union, TypeVar, Type @@ -469,3 +470,30 @@ Data: {data}""".format( version=version, data=ewif_key ) ) + + @classmethod + def from_ssb_file(cls: Type[SigningKeyType], path: str) -> SigningKeyType: + """ + Return SigningKey instance from ScuttleButt .ssb/secret file + + :param path: Path to Scuttlebutt secret file + """ + with open(path, "r") as fh: + ssb_content = fh.read() + + # check data field + regex = re.compile( + '{\\s*"curve": "ed25519",\\s*"public": "(.+)\\.ed25519",\\s*"private":\\s*"(.+)\\.ed25519",\\s*"id":\\s*"@\\1.ed25519"\\s*}', + re.MULTILINE, + ) + match = re.search(regex, ssb_content) + if not match: + raise Exception("Error: Bad scuttlebutt secret file") + + # capture ssb secret key + secret = match.groups()[1] + + # extract seed from secret + seed = bytes(base64.b64decode(secret)[0:32]) + + return cls(seed) diff --git a/examples/load_scuttlebutt_file.py b/examples/load_scuttlebutt_file.py new file mode 100644 index 0000000000000000000000000000000000000000..675b205cd2742d1dc2ecf2ea57ce1fd0ea84172d --- /dev/null +++ b/examples/load_scuttlebutt_file.py @@ -0,0 +1,24 @@ +import sys + +from duniterpy.key import SigningKey + +if __name__ == "__main__": + + if len(sys.argv) < 2: + print( + """ + Usage: + python load_scuttlebutt_file.py FILEPATH + """ + ) + + # capture filepath argument + scuttlebutt_filepath = sys.argv[1] + + # create SigningKey instance from file + signing_key_instance = SigningKey.from_ssb_file( + scuttlebutt_filepath + ) # type: SigningKey + + # print pubkey + print("Public key from scuttlebutt file: {}".format(signing_key_instance.pubkey)) diff --git a/tests/key/test_signing_key.py b/tests/key/test_signing_key.py index 58802fb42667ab6a99f48c59b875ed8544537920..7f7c68843cee0cf8df61d973feb6e5e0717d5fe6 100644 --- a/tests/key/test_signing_key.py +++ b/tests/key/test_signing_key.py @@ -93,3 +93,37 @@ class TestSigningKey(unittest.TestCase): self.assertEqual(sign_key_test.sk, sign_key_load.sk) self.assertEqual(sign_key_test.pubkey, sign_key_load.pubkey) self.assertEqual(sign_key_test.vk, sign_key_load.vk) + + def test_load_ssb_file(self): + dummy_content = """ + # comments + # + # + + { + "curve": "ed25519", + "public": "dGVzdHRlc3R0ZXN0dGV0c3RldHN0dGV0c3RldGV0ZXRldHN0ZXR0c3RldHN0dGV0c3Q=.ed25519", + "private": "dGVzdHRlc3R0ZXN0dGV0c3RldHN0dGV0c3RldGV0ZXRldHN0ZXR0c3RldHN0dGV0c3Q==.ed25519", + "id": "@qJ8qVfXU2mIWG9WfKIRsd6GDscQlErzPHsxzHcyQMWQ=.ed25519" + } + + # + # comments + """ + + # create dummy .ssb/secret file + with open(TEST_FILE_PATH, "w") as fh: + fh.write(dummy_content) + # test load file + sign_key_load = SigningKey.from_credentials_file(TEST_FILE_PATH) + self.assertEqual( + sign_key_load.pubkey, "FAhCeyWq2Ni2xZS3hmYk5w95f8ELxNhUVvU5VB2LXy49" + ) + self.assertEqual( + sign_key_load.sk.hex(), + "f2f7ae68635dba3455390a74ca0811e4c06142229bb58556aaa37d5598548c9ed27f4cb2bfadbaf45b61714b896d4639ab90db035aee746611cdd342bdaa8996", + ) + self.assertEqual( + sign_key_load.vk.hex(), + "d27f4cb2bfadbaf45b61714b896d4639ab90db035aee746611cdd342bdaa8996", + )