Skip to content

Commit

Permalink
rando: refactor random generation of keys and hashes (#117)
Browse files Browse the repository at this point in the history
* Refactor random generation of keys and hashes.

* Add a unit test for rando.checkSeed.
  • Loading branch information
teknico authored Mar 16, 2020
1 parent 3921de6 commit 13b0c81
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 82 deletions.
10 changes: 3 additions & 7 deletions decred/decred/crypto/crypto.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
Copyright (c) 2019, Brian Stafford
Copyright (c) 2019, The Decred developers
Copyright (c) 2019-2020, The Decred developers
See LICENSE for details
Cryptographic functions.
Expand All @@ -21,8 +21,6 @@
from .secp256k1.curve import PrivateKey, PublicKey, curve as Curve


KEY_SIZE = 32
HASH_SIZE = 32
BLAKE256_SIZE = 32
RIPEMD160_SIZE = 20
SERIALIZED_KEY_LENGTH = 4 + 1 + 4 + 4 + 32 + 33 # 78 bytes
Expand Down Expand Up @@ -628,9 +626,7 @@ def new(seed):
Returns:
crypto.ExtendedKey: A master hierarchical deterministic key.
"""
seedLen = len(seed)
if seedLen < rando.MinSeedBytes or seedLen > rando.MaxSeedBytes:
raise AssertionError("invalid seed length %d" % seedLen)
rando.checkSeedLength(len(seed))

# First take the HMAC-SHA512 of the master key and the seed data:
# SHA512 hash is 64 bytes.
Expand Down Expand Up @@ -1137,7 +1133,7 @@ def __init__(self, pw):
pw (byte-like): A password that deterministically generates the key.
"""
super().__init__()
salt = ByteArray(rando.generateSeed(KEY_SIZE))
salt = rando.newKey()
b = lambda v: ByteArray(v).bytes()
_, hashName, iterations = defaultKDFParams()
self.key = ByteArray(
Expand Down
76 changes: 73 additions & 3 deletions decred/decred/crypto/rando.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,87 @@
"""
Copyright (c) 2019, Brian Stafford
Copyright (c) 2019, The Decred developers
Copyright (c) 2019-20, The Decred developers
See LICENSE for details
"""

import os

from decred import DecredError
from decred.util.encode import ByteArray


KEY_SIZE = 32
HASH_SIZE = 32

MinSeedBytes = 16 # 128 bits
MaxSeedBytes = 64 # 512 bits


def generateSeed(length=MaxSeedBytes):
def checkSeedLength(length):
"""
Check that seed length is correct.
Args:
length int: the seed length to be checked.
Raises:
DecredError if length is not between MinSeedBytes and MaxSeedBytes
included.
"""
if length < MinSeedBytes or length > MaxSeedBytes:
raise AssertionError("invalid seed length %d" % length)
raise DecredError(f"Invalid seed length {length}")


def generateSeed(length=MaxSeedBytes):
"""
Generate a cryptographically-strong random seed.
Returns:
bytes: a random bytes object of the given length.
Raises:
DecredError if length is not between MinSeedBytes and MaxSeedBytes
included.
"""
checkSeedLength(length)
return os.urandom(length)


def newHashRaw():
"""
Generate a random hash of HASH_SIZE length.
Returns:
bytes: a random object of HASH_SIZE length.
"""
return generateSeed(HASH_SIZE)


def newHash():
"""
Generate a wrapped random hash of HASH_SIZE length.
Returns:
ByteArray: a random object of HASH_SIZE length.
"""
return ByteArray(newHashRaw())


def newKeyRaw():
"""
Generate a random key of KEY_SIZE length.
Returns:
bytes: a random object of KEY_SIZE length.
"""
return generateSeed(KEY_SIZE)


def newKey():
"""
Generate a wrapped random key of KEY_SIZE length.
Returns:
ByteArray: a random object of KEY_SIZE length.
"""
return ByteArray(newKeyRaw())
4 changes: 2 additions & 2 deletions decred/decred/wallet/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pathlib import Path

from decred import DecredError
from decred.crypto import crypto, mnemonic, rando
from decred.crypto import mnemonic, rando
from decred.dcr import nets
from decred.dcr.dcrdata import DcrdataBlockchain
from decred.util import chains, database
Expand Down Expand Up @@ -72,7 +72,7 @@ def __init__(self, walletDir, pw, network, signals=None, allowCreate=False):
super().__init__(dbPath)
# words is only set the first time a wallet is created.
if not walletExists:
seed = rando.generateSeed(crypto.KEY_SIZE)
seed = rando.newKeyRaw()
self.initialize(seed, pw.encode(), netParams)
self.words = mnemonic.encode(seed)

Expand Down
6 changes: 3 additions & 3 deletions decred/decred/wallet/wallet.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
Copyright (c) 2019, Brian Stafford
Copyright (c) 2019, The Decred developers
Copyright (c) 2019-2020, The Decred developers
See LICENSE for details
"""

Expand Down Expand Up @@ -64,7 +64,7 @@ def initialize(self, seed, pw, netParams):
netParams (object): Network parameters.
"""
pwKey = crypto.SecretKey(pw)
cryptoKey = encode.ByteArray(rando.generateSeed(crypto.KEY_SIZE))
cryptoKey = rando.newKey()
root = crypto.ExtendedKey.new(seed)
self.masterDB[DBKeys.cryptoKey] = pwKey.encrypt(cryptoKey)
self.masterDB[DBKeys.root] = root.serialize()
Expand Down Expand Up @@ -95,7 +95,7 @@ def create(path, password, netParams):
"""
if len(password) == 0:
raise AssertionError("empty password not allowed")
seed = rando.generateSeed(crypto.KEY_SIZE)
seed = rando.newKeyRaw()
wallet = Wallet(path)
wallet.initialize(seed, password.encode(), netParams)
words = mnemonic.encode(seed)
Expand Down
6 changes: 1 addition & 5 deletions decred/tests/integration/dcr/test_dcrdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@
from decred.util.encode import ByteArray


def newHash():
return ByteArray(rando.generateSeed(32))


class TestDcrdata:
def client(self, **k):
return dcrdata.DcrdataClient("https://alpha.dcrdata.org", **k)
Expand Down Expand Up @@ -103,7 +99,7 @@ def utxosource(amt, filter):
addrString = addr.string()
keys[addrString] = privKey
pkScript = txscript.makePayToAddrScript(addrString, testnet)
txHash = newHash()
txHash = rando.newHash()
txid = reversed(txHash).hex()
utxos.append(
account.UTXO(
Expand Down
2 changes: 1 addition & 1 deletion decred/tests/unit/crypto/test_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def test_addr_script_hash(self):
self.assertEqual(addr.string(), addrStr)

def test_kdf_params(self):
salt = ByteArray(rando.generateSeed(32))
salt = rando.newHash()
digest = ByteArray(32)
kdf = crypto.KDFParams(salt, digest)
b = kdf.serialize()
Expand Down
20 changes: 20 additions & 0 deletions decred/tests/unit/crypto/test_rando.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
Copyright (c) 2020, The Decred developers
See LICENSE for details
"""

import pytest

from decred import DecredError
from decred.crypto import rando


def test_checkSeedLength():
with pytest.raises(DecredError):
rando.checkSeedLength(rando.MinSeedBytes - 1)
assert rando.checkSeedLength(rando.MinSeedBytes) is None
assert rando.checkSeedLength(rando.HASH_SIZE) is None
assert rando.checkSeedLength(rando.KEY_SIZE) is None
assert rando.checkSeedLength(rando.MaxSeedBytes) is None
with pytest.raises(DecredError):
rando.checkSeedLength(rando.MaxSeedBytes + 1)
12 changes: 6 additions & 6 deletions decred/tests/unit/dcr/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from decred import DecredError
from decred.crypto import crypto, opcode, rando
from decred.dcr import account, dcrdata, nets, txscript
from decred.dcr import account, nets, txscript
from decred.dcr.vsp import PurchaseInfo, VotingServiceProvider
from decred.dcr.wire import msgblock, msgtx
from decred.util.database import KeyValueDatabase
Expand All @@ -18,7 +18,7 @@

LOGGER_ID = "test_account"

cryptoKey = crypto.ByteArray(rando.generateSeed(32))
cryptoKey = rando.newKey()

ticketScript = ByteArray("baa914f5618dfc002becfe840da65f6a49457f41d4f21787")

Expand Down Expand Up @@ -177,7 +177,7 @@ def match(utxo):


def test_tiny_block(prepareLogger):
blockHash = rando.generateSeed(32)
blockHash = rando.newHashRaw()
height = 55
TinyBlock = account.TinyBlock
tb1 = TinyBlock(blockHash, height)
Expand Down Expand Up @@ -252,7 +252,7 @@ def test_account():
for n in range(20):
acct.nextExternalAddress()
satoshis = int(round(5 * 1e8))
txHash = ByteArray(rando.generateSeed(32))
txHash = rando.newHash()
txid = reversed(txHash).hex()
vout = 2
address = acct.nextExternalAddress()
Expand Down Expand Up @@ -314,11 +314,11 @@ def balance(cls, b):
acct.signals = Signals

# Add a txid for this first address
txid = ByteArray(rando.generateSeed(32)).hex()
txid = rando.newHash().hex()
zerothAddr = acct.externalAddresses[0]
acct.addTxid(zerothAddr, txid)
# Add a voting service provider
vspKey = ByteArray(rando.generateSeed(32)).hex()
vspKey = rando.newKey().hex()
ticketAddr = "ticketAddr"
pi = PurchaseInfo("addr", 1.0, ByteArray(b"scripthashscript"), ticketAddr, 1, 0, 2)
vsp = VotingServiceProvider("https://myvsp.com", vspKey, nets.mainnet.Name, pi)
Expand Down
Loading

0 comments on commit 13b0c81

Please sign in to comment.