OSDN Git Service

add get_path_from_index
authorChengcheng Zhang <943420582@qq.com>
Thu, 28 Mar 2019 03:46:19 +0000 (11:46 +0800)
committerChengcheng Zhang <943420582@qq.com>
Thu, 28 Mar 2019 03:46:19 +0000 (11:46 +0800)
README.md
pybtm/__init__.py
pybtm/receiver.py [new file with mode: 0644]
pybtm/segwit_addr.py [new file with mode: 0644]
pybtm/utils.py
setup.py

index 6d3aa1a..4447790 100644 (file)
--- a/README.md
+++ b/README.md
@@ -15,6 +15,7 @@ pybtm
   - [2.10 Sign message](#210-sign-message)
   - [2.11 Verify signature](#211-verify-signature)
   - [2.12 Create new key](#212-create-new-key)
+  - [2.13 Create HD path](#213-create-hd-path)
 
 Python3 implementation of the Bytom protocol.
 
@@ -297,4 +298,18 @@ Return:
 'ancient young hurt bone shuffle deposit congress normal crack six boost despair'
 >>> r['seed']
 'afa3a86bbec2f40bb32833fc6324593824c4fc7821ed32eac1f762b5893e56745f66a6c6f2588b3d627680aa4e0e50efd25065097b3daa8c6a19d606838fe7d4'
-```
\ No newline at end of file
+```
+
+### 2.13 Create HD path
+
+get_path_from_index create HD path.
+
+```python
+>>> from pybtm import receiver
+>>> account_index_int = 1
+>>> address_index_int = 1
+>>> change_bool = True
+>>> receiver.get_path_from_index(account_index_int, address_index_int, change_bool)
+{'path': ['2c000000', '99000000', '01000000', '01000000', '01000000'], 'path_str': 'm/44/153/1/1/1'}
+```
+
index d66dcf9..0b5248b 100644 (file)
@@ -1,2 +1,2 @@
 name = "pybtm"
-version = "0.0.16"
\ No newline at end of file
+version = "0.0.17"
\ No newline at end of file
diff --git a/pybtm/receiver.py b/pybtm/receiver.py
new file mode 100644 (file)
index 0000000..9117236
--- /dev/null
@@ -0,0 +1,174 @@
+import hashlib
+from .key import *
+from .segwit_addr import *
+from .utils import *
+
+# get_path_from_index create xpub path from account key index and current address index
+# path: purpose(0x2c=44)/coin_type(btm:0x99)/account_index/change(1 or 0)/address_index
+# You can find more details from: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
+# You can get more test data from: https://gist.github.com/zcc0721/616eaf337673635fa5c9dd5dbb8dd114
+# Please attention:
+#   account_index_int >= 1
+#   address_index_int >= 1
+#   change_bool: true or false
+# test data 1:
+#   account_index_int: 1
+#   address_index_int: 1
+#   change_bool: true
+#   path_list: 2c000000 99000000 01000000 01000000 01000000
+# test data 2:
+#   account_index_int: 1
+#   address_index_int: 1
+#   change_bool: false
+#   path_list: 2c000000 99000000 01000000 00000000 01000000
+# test data 3:
+#   account_index_int: 3
+#   address_index_int: 1
+#   change_bool: false
+#   path_list: 2c000000 99000000 03000000 00000000 01000000
+def get_path_from_index(account_index_int, address_index_int, change_bool):
+    path_list = ['2c000000', '99000000']
+    account_index_str = (account_index_int).to_bytes(4, byteorder='little').hex()
+    path_list.append(account_index_str)
+    change_str = '0'
+    if change_bool:
+        branch_str = (1).to_bytes(4, byteorder='little').hex()
+        change_str = '1'
+    else:
+        branch_str = (0).to_bytes(4, byteorder='little').hex()
+    path_list.append(branch_str)
+    address_index_str = (address_index_int).to_bytes(4, byteorder='little').hex()
+    path_list.append(address_index_str)
+    path_str = 'm/44/153/' + str(account_index_int) + '/' + change_str + '/' + str(address_index_int)
+    return {
+        "path": path_list,
+        "path_str": path_str
+    }
+
+
+# get_control_program create control program
+# You can get more test data from: https://gist.github.com/zcc0721/afa12de04b03b9bfc49985a181ebda80
+# Please attention:
+#   account_index_int >= 1
+#   address_index_int >= 1
+#   change_bool: true or false
+# test data 1:
+#   account_index_int: 1
+#   address_index_int: 1
+#   change_bool: false
+#   xpub_str: 3c6664244d2d57168d173c4691dbf8741a67d972b2d3e1b0067eb825e2005d20c5eebd1c26ccad4de5142d7c339bf62cc1fb79a8b3e42a708cd521368dbc9286
+#   control_program_str: 0014052620b86a6d5e07311d5019dffa3864ccc8a6bd
+# test data 2:
+#   account_index_int: 1
+#   address_index_int: 1
+#   change_bool: true
+#   xpub_str: 3c6664244d2d57168d173c4691dbf8741a67d972b2d3e1b0067eb825e2005d20c5eebd1c26ccad4de5142d7c339bf62cc1fb79a8b3e42a708cd521368dbc9286
+#   control_program: 001478c3aa31753389fcde04d33d0779bdc2840f0ad4
+# test data 3:
+#   account_index_int: 1
+#   address_index_int: 17
+#   change_bool: true
+#   xpub_str: 3c6664244d2d57168d173c4691dbf8741a67d972b2d3e1b0067eb825e2005d20c5eebd1c26ccad4de5142d7c339bf62cc1fb79a8b3e42a708cd521368dbc9286
+#   control_program: 0014eefb8d0688d7960dfbd79bb3aa1bcaa3ec34415d
+# test data 4:
+#   account_index_int: 1
+#   address_index_int: 1
+#   change_bool: false
+#   xpub_str: f744493a021b65814ea149118c98aae8d1e217de29fefb7b2024ca341cd834586ee48bbcf1f4ae801ecb8c6784b044fc62a74c58c816d14537e1573c3e20ce79
+#   control_program: 001431f2b90b469e89361225aae370f73e5473b9852b
+def get_control_program(account_index_int, address_index_int, change_bool, xpub_hexstr):
+    path_list = get_path_from_index(account_index_int, address_index_int, change_bool)['path']
+    child_xpub_hexstr = get_child_xpub(xpub_hexstr, path_list)
+    child_public_key_hexstr = get_public_key(child_xpub_hexstr)
+    child_public_key_byte = bytes.fromhex(child_public_key_hexstr)
+    
+    ripemd160 = hashlib.new('ripemd160')
+    ripemd160.update(child_public_key_byte)
+    public_key_hash_hexstr = ripemd160.hexdigest()
+    control_program_hexstr = '0014' + public_key_hash_hexstr
+    return control_program_hexstr
+
+
+# get_address create address
+# You can get more test data from: https://gist.github.com/zcc0721/8f52d0a80a0a9f964e9d9d9a50e940c5
+# Please attention:
+#   network_str: mainnet/testnet/solonet
+# test data 1:
+#   control_program_str: 001431f2b90b469e89361225aae370f73e5473b9852b
+#   network_str: mainnet
+#   address_str: bm1qx8etjz6xn6ynvy394t3hpae723emnpft3nrwej
+# test data 2:
+#   control_program_str: 0014eefb8d0688d7960dfbd79bb3aa1bcaa3ec34415d
+#   network_str: mainnet
+#   address_str: bm1qamac6p5g67tqm77hnwe65x7250krgs2avl0nr6
+# test data 3:
+#   control_program_str: 0014eefb8d0688d7960dfbd79bb3aa1bcaa3ec34415d
+#   network_str: testnet
+#   address_str: tm1qamac6p5g67tqm77hnwe65x7250krgs2agfwhrt
+# test data 4:
+#   control_program_str: 0014d234314ea1533dee584417ecb922f904b8dd6c6b
+#   network_str: testnet
+#   address_str: tm1q6g6rzn4p2v77ukzyzlktjgheqjud6mrt7emxen
+# test data 5:
+#   control_program_str: 0014eefb8d0688d7960dfbd79bb3aa1bcaa3ec34415d
+#   network_str: solonet
+#   address_str: sm1qamac6p5g67tqm77hnwe65x7250krgs2adw9jr5
+# test data 6:
+#   control_program_str: 0014052620b86a6d5e07311d5019dffa3864ccc8a6bd
+#   network_str: solonet
+#   address_str: sm1qq5nzpwr2d40qwvga2qval73cvnxv3f4aa9xzh9
+def get_address(control_program_hexstr, network_str):
+    public_key_hash_hexstr = control_program_hexstr[4:]
+    if network_str == 'mainnet':
+        hrp = 'bm'
+    elif network_str == 'testnet':
+        hrp = 'tm'
+    else:
+        hrp = 'sm'
+    address_str = segwit_addr.encode(hrp, 0, bytes.fromhex(public_key_hash_hexstr))
+    return address_str
+
+
+# get_new_address create address and address qrcode
+# test data 1:
+#   xpub_str: 8fde12d7c9d6b6cbfbf344edd42f2ed86ae6270b36bab714af5fd5bb3b54adcec4265f1de85ece50f17534e42016ee9404a11fec94ddfadd4a064d27ef3f3f4c
+#   account_index_int: 1
+#   address_index_int: 1
+#   change_bool: False
+#   network_str: solonet
+#   path: m/44/153/1/0/1
+#   control_program: 00147640f3c34fe4b2b298e54e54a4692a47ce47aa5e
+#   address: sm1qweq08s60ujet9x89fe22g6f2gl8y02j7lgr5v5
+#   address_base64: /9j/4AAQSkZJRgABAQ...
+# test data 2:
+#   xpub_str: 8fde12d7c9d6b6cbfbf344edd42f2ed86ae6270b36bab714af5fd5bb3b54adcec4265f1de85ece50f17534e42016ee9404a11fec94ddfadd4a064d27ef3f3f4c
+#   account_index_int: 12
+#   address_index_int: 3
+#   change_bool: True
+#   network_str: mainnet
+#   path: m/44/153/12/1/3
+#   control_program: 001458b1477abc46ef81905d25011d36389c0788984b
+#   address: bm1qtzc5w74ugmhcryzay5q36d3cnsrc3xztzw6u4y
+#   address_base64: /9j/4AAQSkZJRgABAQA...
+# test data 3:
+#   xpub_str: 8fde12d7c9d6b6cbfbf344edd42f2ed86ae6270b36bab714af5fd5bb3b54adcec4265f1de85ece50f17534e42016ee9404a11fec94ddfadd4a064d27ef3f3f4c
+#   account_index_int: 200
+#   address_index_int: 1
+#   change_bool: True
+#   network_str: mainnet
+#   path: m/44/153/200/1/1
+#   control_program: 00144e5c8757c612c21aa2a0c55f1f8e2ab57cfdefca
+#   address: bm1qfewgw47xztpp4g4qc403lr32k470mm724cphhp
+#   address_base64: /9j/4AAQSkZJRgABAQA...
+def get_new_address(xpub_hexstr, account_index_int, address_index_int, change_bool, network_str):
+    path_str = get_path_from_index(account_index_int, address_index_int, change_bool)['path_str']
+    control_program_hexstr = get_control_program(account_index_int, address_index_int, change_bool, xpub_hexstr)
+    address_str = get_address(control_program_hexstr, network_str)
+    address_base64 = create_qrcode_base64(address_str)
+    return {
+        "path": path_str,
+        "control_program": control_program_hexstr,
+        "address": address_str,
+        "address_base64": address_base64
+    }
+
diff --git a/pybtm/segwit_addr.py b/pybtm/segwit_addr.py
new file mode 100644 (file)
index 0000000..01edb6b
--- /dev/null
@@ -0,0 +1,125 @@
+# Copyright (c) 2017 Pieter Wuille
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# Please Ref: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
+
+"""Reference implementation for Bech32 and segwit addresses."""
+
+
+CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
+
+
+def bech32_polymod(values):
+    """Internal function that computes the Bech32 checksum."""
+    generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
+    chk = 1
+    for value in values:
+        top = chk >> 25
+        chk = (chk & 0x1ffffff) << 5 ^ value
+        for i in range(5):
+            chk ^= generator[i] if ((top >> i) & 1) else 0
+    return chk
+
+
+def bech32_hrp_expand(hrp):
+    """Expand the HRP into values for checksum computation."""
+    return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
+
+
+def bech32_verify_checksum(hrp, data):
+    """Verify a checksum given HRP and converted data characters."""
+    return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1
+
+
+def bech32_create_checksum(hrp, data):
+    """Compute the checksum values given HRP and data."""
+    values = bech32_hrp_expand(hrp) + data
+    polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1
+    return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
+
+
+def bech32_encode(hrp, data):
+    """Compute a Bech32 string given HRP and data values."""
+    combined = data + bech32_create_checksum(hrp, data)
+    return hrp + '1' + ''.join([CHARSET[d] for d in combined])
+
+
+def bech32_decode(bech):
+    """Validate a Bech32 string, and determine HRP and data."""
+    if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
+            (bech.lower() != bech and bech.upper() != bech)):
+        return (None, None)
+    bech = bech.lower()
+    pos = bech.rfind('1')
+    if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
+        return (None, None)
+    if not all(x in CHARSET for x in bech[pos+1:]):
+        return (None, None)
+    hrp = bech[:pos]
+    data = [CHARSET.find(x) for x in bech[pos+1:]]
+    if not bech32_verify_checksum(hrp, data):
+        return (None, None)
+    return (hrp, data[:-6])
+
+
+def convertbits(data, frombits, tobits, pad=True):
+    """General power-of-2 base conversion."""
+    acc = 0
+    bits = 0
+    ret = []
+    maxv = (1 << tobits) - 1
+    max_acc = (1 << (frombits + tobits - 1)) - 1
+    for value in data:
+        if value < 0 or (value >> frombits):
+            return None
+        acc = ((acc << frombits) | value) & max_acc
+        bits += frombits
+        while bits >= tobits:
+            bits -= tobits
+            ret.append((acc >> bits) & maxv)
+    if pad:
+        if bits:
+            ret.append((acc << (tobits - bits)) & maxv)
+    elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
+        return None
+    return ret
+
+
+def decode(hrp, addr):
+    """Decode a segwit address."""
+    hrpgot, data = bech32_decode(addr)
+    if hrpgot != hrp:
+        return (None, None)
+    decoded = convertbits(data[1:], 5, 8, False)
+    if decoded is None or len(decoded) < 2 or len(decoded) > 40:
+        return (None, None)
+    if data[0] > 16:
+        return (None, None)
+    if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
+        return (None, None)
+    return (data[0], decoded)
+
+
+def encode(hrp, witver, witprog):
+    """Encode a segwit address."""
+    ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5))
+    if decode(hrp, ret) == (None, None):
+        return None
+    return ret
\ No newline at end of file
index 0dbce38..ae9580c 100644 (file)
@@ -17,9 +17,7 @@ def create_qrcode_base64(s):
     buffered = BytesIO()
     img.save(buffered, format="JPEG")
     base64_str = pybase64.b64encode(buffered.getvalue()).decode("utf-8")
-    return {
-        "base64": base64_str
-    }
+    return base64_str
 
 
 if six.PY3:
index c4e2c50..b2cdfbf 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
 
 setuptools.setup(
     name="pybtm",
-    version="0.0.16",
+    version="0.0.17",
     author="zcc0721",
     author_email="zcc0721@foxmail.com",
     description="Python3 implementation of the Bytom protocol.",