Commit ced4a798 authored by Vincent Texier's avatar Vincent Texier

issue #50 Add export/import WIF V1 file and its example (BC Broken)

SigningKey class has a new signature with seed as parameter like the parent class.
To create an instance from credentials, use from_credentials class method.
parent 41090ede
Pipeline #4343 passed with stages
in 3 minutes and 30 seconds
from .signing_key import SigningKey, ScryptParams
from .verifying_key import VerifyingKey
from .encryption_key import SecretKey, PublicKey
\ No newline at end of file
from .encryption_key import SecretKey, PublicKey
......@@ -3,7 +3,8 @@ duniter public and private keys
@author: inso
"""
from typing import Optional, Union
from re import compile, MULTILINE, search
from typing import Optional, Union, TypeVar, Type
import libnacl.sign
from pylibscrypt import scrypt
......@@ -29,11 +30,26 @@ class ScryptParams:
self.p = p
# required to type hint cls in classmethod
SigningKeyType = TypeVar('SigningKeyType', bound='SigningKey')
class SigningKey(libnacl.sign.Signer):
def __init__(self, salt: Union[str, bytes], password: Union[str, bytes],
scrypt_params: Optional[ScryptParams] = None) -> None:
def __init__(self, seed: bytes) -> None:
"""
Init pubkey property
:param str seed: Hexadecimal seed string
"""
Init a SigningKey object from credentials
super().__init__(seed)
self.pubkey = Base58Encoder.encode(self.vk)
@classmethod
def from_credentials(cls: Type[SigningKeyType], salt: Union[str, bytes], password: Union[str, bytes],
scrypt_params: Optional[ScryptParams] = None) -> SigningKeyType:
"""
Create a SigningKey object from credentials
:param salt: Secret salt passphrase credential
:param password: Secret password credential
......@@ -48,8 +64,7 @@ class SigningKey(libnacl.sign.Signer):
scrypt_params.N, scrypt_params.r, scrypt_params.p,
SEED_LENGTH)
super().__init__(seed)
self.pubkey = Base58Encoder.encode(self.vk)
return cls(seed)
def decrypt_seal(self, message: bytes) -> str:
"""
......@@ -62,3 +77,64 @@ class SigningKey(libnacl.sign.Signer):
curve25519_public_key = libnacl.crypto_sign_ed25519_pk_to_curve25519(self.vk)
curve25519_secret_key = libnacl.crypto_sign_ed25519_sk_to_curve25519(self.sk)
return libnacl.crypto_box_seal_open(message, curve25519_public_key, curve25519_secret_key).decode('utf-8')
@classmethod
def from_wif_file(cls: Type[SigningKeyType], path: str) -> SigningKeyType:
"""
Return SigningKey instance from Duniter WIF v1 file
:param path: Path to WIF file
"""
with open(path, 'r') as fh:
wif_content = fh.read()
regex = compile('Data: ([1-9A-HJ-NP-Za-km-z]+)', MULTILINE)
match = search(regex, wif_content)
if not match:
raise Exception('Error: Bad format WIF v1 file')
wif_hex = match.groups()[0]
wif_bytes = Base58Encoder.decode(wif_hex)
if len(wif_bytes) != 35:
raise Exception("Error: the size of WIF is invalid")
checksum_from_wif = wif_bytes[-2:]
fi = wif_bytes[0:1]
seed = wif_bytes[1:-2]
seed_fi = wif_bytes[0:-2]
if fi != b"\x01":
raise Exception("Error: bad WIF version")
# checksum control
checksum = libnacl.crypto_hash_sha256(libnacl.crypto_hash_sha256(seed_fi))[0:2]
if checksum_from_wif != checksum:
raise Exception("Error: bad checksum of the WIF")
return cls(seed)
def save_wif(self, path: str) -> None:
"""
Save a Wallet Import Format file (v1)
:param path: Path to file
"""
# Cesium v1
version = 1
# add version to seed
seed_fi = version.to_bytes(version, 'little') + self.seed
# calculate checksum
sha256_v1 = libnacl.crypto_hash_sha256(seed_fi)
sha256_v2 = libnacl.crypto_hash_sha256(sha256_v1)
checksum = sha256_v2[0:2]
wif_key = Base58Encoder.encode(seed_fi + checksum)
with open(path, 'w') as fh:
fh.write(
"""Type: WIF
Version: {version}
Data: {data}""".format(version=1, data=wif_key)
)
......@@ -33,7 +33,7 @@ def get_identity_document(current_block: dict, uid: str, salt: str, password: st
timestamp = BlockUID(current_block['number'], current_block['hash'])
# create keys from credentials
key = SigningKey(salt, password)
key = SigningKey.from_credentials(salt, password)
# create identity document
identity = Identity(
......
......@@ -11,7 +11,7 @@ if __name__ == '__main__':
password = getpass.getpass("Enter your password: ")
# Create key object
key = SigningKey(salt, password)
key = SigningKey.from_credentials(salt, password)
# Display your public key
print("Public key for your credentials: %s" % key.pubkey)
......@@ -21,7 +21,7 @@ if __name__ == '__main__':
password = getpass.getpass("Enter your password: ")
# Create key object
signing_key_instance = SigningKey(salt, password)
signing_key_instance = SigningKey.from_credentials(salt, password)
# open encrypted message file
with open(signed_message_path, 'rb') as file_handler:
......
......@@ -31,7 +31,7 @@ password = getpass.getpass("Enter your password: ")
pubkey = input("Enter your public key: ")
# init signer instance
signer = SigningKey(salt, password)
signer = SigningKey.from_credentials(salt, password)
# check public key
if signer.pubkey != pubkey:
......
from duniterpy.key import SigningKey
from libnacl.utils import load_key
import getpass
import os
if "XDG_CONFIG_HOME" in os.environ:
home_path = os.environ["XDG_CONFIG_HOME"]
elif "HOME" in os.environ:
home_path = os.environ["HOME"]
elif "APPDATA" in os.environ:
home_path = os.environ["APPDATA"]
else:
home_path = os.path.dirname(__file__)
# CONFIG #######################################
# WARNING : Hide this file in a safe and secure place
# If one day you forget your credentials,
# you'll have to use one of your private keys instead
PRIVATE_KEY_FILE_PATH = os.path.join(home_path, ".duniter_account_wif_v1.duniterkey")
################################################
# prompt hidden user entry
salt = getpass.getpass("Enter your passphrase (salt): ")
# prompt hidden user entry
password = getpass.getpass("Enter your password: ")
# prompt public key
pubkey = input("Enter your public key: ")
# init signer instance
signer = SigningKey.from_credentials(salt, password)
# check public key
if signer.pubkey != pubkey:
print("Bad credentials!")
exit(1)
# save private key in a file (WIF v1 format)
signer.save_wif(PRIVATE_KEY_FILE_PATH)
# document saved
print("Private key for public key %s saved in %s" % (signer.pubkey, PRIVATE_KEY_FILE_PATH))
try:
# load private keys from file
loaded_signer = SigningKey.from_wif_file(PRIVATE_KEY_FILE_PATH)
# check public key from file
print("Public key %s loaded from file %s" % (loaded_signer.pubkey, PRIVATE_KEY_FILE_PATH))
except Exception as e:
print(e)
exit(1)
exit(0)
......@@ -14,7 +14,7 @@ if __name__ == '__main__':
password = getpass.getpass("Enter your password: ")
# Create key object
key = SigningKey(salt, password)
key = SigningKey.from_credentials(salt, password)
# Display your public key
print("Public key for your credentials: %s" % key.pubkey)
......
......@@ -86,7 +86,7 @@ def get_signed_raw_revocation_document(identity: Identity, salt: str, password:
"""
revocation = Revocation(PROTOCOL_VERSION, identity.currency, identity, "")
key = SigningKey(salt, password)
key = SigningKey.from_credentials(salt, password)
revocation.sign([key])
return revocation.signed_raw()
......@@ -112,7 +112,7 @@ async def main():
pubkey = input("Enter your public key: ")
# init signer instance
signer = SigningKey(salt, password)
signer = SigningKey.from_credentials(salt, password)
# check public key
if signer.pubkey != pubkey:
......
......@@ -74,7 +74,7 @@ def get_certification_document(current_block: dict, self_cert_document: Identity
identity=self_cert_document,
timestamp=BlockUID(current_block['number'], current_block['hash']), signature="")
# sign document
key = SigningKey(salt, password)
key = SigningKey.from_credentials(salt, password)
certification.sign([key])
return certification
......
......@@ -33,7 +33,7 @@ def get_identity_document(current_block: dict, uid: str, salt: str, password: st
timestamp = BlockUID(current_block['number'], current_block['hash'])
# create keys from credentials
key = SigningKey(salt, password)
key = SigningKey.from_credentials(salt, password)
# create identity document
identity = Identity(
......@@ -69,7 +69,7 @@ def get_membership_document(membership_type: str, current_block: dict, identity:
timestamp = BlockUID(current_block['number'], current_block['hash'])
# create keys from credentials
key = SigningKey(salt, password)
key = SigningKey.from_credentials(salt, password)
# create identity document
membership = Membership(
......
......@@ -119,7 +119,7 @@ async def main():
transaction = get_transaction_document(current_block, source, pubkey_from, pubkey_to)
# create keys from credentials
key = SigningKey(salt, password)
key = SigningKey.from_credentials(salt, password)
# sign document
transaction.sign([key])
......
......@@ -6,7 +6,7 @@ import unittest
class TestVerifyingKey(unittest.TestCase):
def test_from_sign_to_verify(self):
sign_key = SigningKey("saltsalt", "passwordpassword", ScryptParams(4096, 16, 1))
sign_key = SigningKey.from_credentials("saltsalt", "passwordpassword", ScryptParams(4096, 16, 1))
verify_key = VerifyingKey(sign_key.pubkey)
self.assertEqual(verify_key.vk, sign_key.vk)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment