Add test vectors for unified viewing keys.

Fixes #47
Co-authored-by: str4d <jack@electriccoin.co>
This commit is contained in:
Kris Nuttycombe 2021-12-06 21:08:36 -07:00
parent 69a2dbb691
commit 3e77a9a158
9 changed files with 369 additions and 96 deletions

119
poetry.lock generated
View File

@ -1,3 +1,14 @@
[[package]]
name = "cffi"
version = "1.15.0"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
pycparser = "*"
[[package]]
name = "chacha20poly1305"
version = "0.0.3"
@ -10,6 +21,25 @@ python-versions = "*"
dev = ["pep8", "tox", "pypandoc"]
docs = ["Sphinx (>=1.0)", "sphinx-rtd-theme", "sphinxcontrib-programoutput"]
[[package]]
name = "cryptography"
version = "36.0.0"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
cffi = ">=1.12"
[package.extras]
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
sdist = ["setuptools_rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]]
name = "numpy"
version = "1.21.0"
@ -26,15 +56,98 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pycparser"
version = "2.21"
description = "C parser in Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "d8e6774b362633dc3d6f4b311004cd15462c394c93c9dae64afd27c6d777a4f0"
content-hash = "75e4faa5164449065d8912033077d9670d9b8e4e72b2962358701870528e0e74"
[metadata.files]
cffi = [
{file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
{file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"},
{file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"},
{file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"},
{file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"},
{file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"},
{file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"},
{file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"},
{file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"},
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"},
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"},
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"},
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"},
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"},
{file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"},
{file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"},
{file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"},
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"},
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"},
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"},
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"},
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"},
{file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"},
{file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"},
{file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"},
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"},
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"},
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"},
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"},
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"},
{file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"},
{file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"},
{file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"},
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"},
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"},
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"},
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"},
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"},
{file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"},
{file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"},
{file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"},
{file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"},
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"},
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"},
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"},
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"},
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"},
{file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"},
{file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"},
{file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"},
]
chacha20poly1305 = [
{file = "chacha20poly1305-0.0.3.tar.gz", hash = "sha256:f2f005c7cf4638ffa4ff06c02c78748068b642916795c6d16c7cc5e355e70edf"},
]
cryptography = [
{file = "cryptography-36.0.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:9511416e85e449fe1de73f7f99b21b3aa04fba4c4d335d30c486ba3756e3a2a6"},
{file = "cryptography-36.0.0-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:97199a13b772e74cdcdb03760c32109c808aff7cd49c29e9cf4b7754bb725d1d"},
{file = "cryptography-36.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:494106e9cd945c2cadfce5374fa44c94cfadf01d4566a3b13bb487d2e6c7959e"},
{file = "cryptography-36.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6fbbbb8aab4053fa018984bb0e95a16faeb051dd8cca15add2a27e267ba02b58"},
{file = "cryptography-36.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:684993ff6f67000a56454b41bdc7e015429732d65a52d06385b6e9de6181c71e"},
{file = "cryptography-36.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c702855cd3174666ef0d2d13dcc879090aa9c6c38f5578896407a7028f75b9f"},
{file = "cryptography-36.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d91bc9f535599bed58f6d2e21a2724cb0c3895bf41c6403fe881391d29096f1d"},
{file = "cryptography-36.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b17d83b3d1610e571fedac21b2eb36b816654d6f7496004d6a0d32f99d1d8120"},
{file = "cryptography-36.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8982c19bb90a4fa2aad3d635c6d71814e38b643649b4000a8419f8691f20ac44"},
{file = "cryptography-36.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:24469d9d33217ffd0ce4582dfcf2a76671af115663a95328f63c99ec7ece61a4"},
{file = "cryptography-36.0.0-cp36-abi3-win32.whl", hash = "sha256:f6a5a85beb33e57998dc605b9dbe7deaa806385fdf5c4810fb849fcd04640c81"},
{file = "cryptography-36.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:2deab5ec05d83ddcf9b0916319674d3dae88b0e7ee18f8962642d3cde0496568"},
{file = "cryptography-36.0.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2049f8b87f449fc6190350de443ee0c1dd631f2ce4fa99efad2984de81031681"},
{file = "cryptography-36.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a776bae1629c8d7198396fd93ec0265f8dd2341c553dc32b976168aaf0e6a636"},
{file = "cryptography-36.0.0-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:aa94d617a4cd4cdf4af9b5af65100c036bce22280ebb15d8b5262e8273ebc6ba"},
{file = "cryptography-36.0.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:5c49c9e8fb26a567a2b3fa0343c89f5d325447956cc2fc7231c943b29a973712"},
{file = "cryptography-36.0.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef216d13ac8d24d9cd851776662f75f8d29c9f2d05cdcc2d34a18d32463a9b0b"},
{file = "cryptography-36.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:231c4a69b11f6af79c1495a0e5a85909686ea8db946935224b7825cfb53827ed"},
{file = "cryptography-36.0.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f92556f94e476c1b616e6daec5f7ddded2c082efa7cee7f31c7aeda615906ed8"},
{file = "cryptography-36.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d73e3a96c38173e0aa5646c31bf8473bc3564837977dd480f5cbeacf1d7ef3a3"},
{file = "cryptography-36.0.0.tar.gz", hash = "sha256:52f769ecb4ef39865719aedc67b4b7eae167bafa48dbc2a26dd36fa56460507f"},
]
numpy = [
{file = "numpy-1.21.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d5caa946a9f55511e76446e170bdad1d12d6b54e17a2afe7b189112ed4412bb8"},
{file = "numpy-1.21.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ac4fd578322842dbda8d968e3962e9f22e862b6ec6e3378e7415625915e2da4d"},
@ -76,3 +189,7 @@ pyblake2 = [
{file = "pyblake2-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:c53417ee0bbe77db852d5fd1036749f03696ebc2265de359fe17418d800196c4"},
{file = "pyblake2-1.1.2.tar.gz", hash = "sha256:5ccc7eb02edb82fafb8adbb90746af71460fbc29aa0f822526fc976dff83e93f"},
]
pycparser = [
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
]

View File

@ -25,4 +25,5 @@ classifiers = [
python = "^3.7"
numpy = "1.21.0"
chacha20poly1305 = "0.0.3"
cryptography = "36.0.0"
pyblake2 = "1.1.2"

View File

@ -1,6 +0,0 @@
cffi==1.14.5
chacha20poly1305==0.0.3
cryptography==3.4.7
numpy==1.21.0
pyblake2==1.1.2
pycparser==2.20

View File

@ -133,6 +133,12 @@ class ExtendedFullViewingKey(DerivedIvk, ExtendedBase):
def nk(self):
return self._nk
def ovk(self):
return self._ovk
def dk(self):
return self._dk
def is_xsk(self):
return False
@ -164,12 +170,13 @@ class ExtendedFullViewingKey(DerivedIvk, ExtendedBase):
c_i = I_R
return self.__class__(ak_i, nk_i, ovk_i, dk_i, c_i, self.depth()+1, self.tag(), i)
def hardened(i):
assert(i < (1<<31))
return i + (1<<31)
def main():
args = render_args()
def hardened(i): return i + (1<<31)
seed = bytes(range(32))
m = ExtendedSpendingKey.master(seed)
m_1 = m.child(1)

View File

@ -1,6 +1,14 @@
import os
import struct
def randbytes_inner(rng, l):
ret = []
while len(ret) < l:
ret.append(rng.randrange(0, 256))
return bytes(ret)
def randbytes(rng):
return lambda l: randbytes_inner(rng, l)
class Rand(object):
def __init__(self, random=os.urandom):

View File

@ -2,99 +2,25 @@
import sys; assert sys.version_info[0] >= 3, "Python 3 required."
import math
from random import Random
import struct
from bech32m import bech32_encode, bech32_decode, convertbits, Encoding
from tv_output import render_args, render_tv, Some
from tv_rand import Rand
from tv_rand import Rand, randbytes
from zc_utils import write_compact_size, parse_compact_size
from f4jumble import f4jumble, f4jumble_inv
import sapling_key_components
import orchard_key_components
def tlv(typecode, value):
return b"".join([write_compact_size(typecode), write_compact_size(len(value)), value])
def padding(hrp):
assert(len(hrp) <= 16)
return bytes(hrp, "utf8") + bytes(16 - len(hrp))
def encode_unified(receivers):
orchard_receiver = b""
if receivers[0]:
orchard_receiver = tlv(0x03, receivers[0])
sapling_receiver = b""
if receivers[1]:
sapling_receiver = tlv(0x02, receivers[1])
t_receiver = b""
if receivers[2][1]:
if receivers[2][0]:
typecode = 0x00
else:
typecode = 0x01
t_receiver = tlv(typecode, receivers[2][1])
hrp = "u"
r_bytes = b"".join([orchard_receiver, sapling_receiver, t_receiver, padding(hrp)])
converted = convertbits(f4jumble(r_bytes), 8, 5)
return bech32_encode(hrp, converted, Encoding.BECH32M)
def decode_unified(addr_str):
(hrp, data, encoding) = bech32_decode(addr_str)
assert hrp == "u" and encoding == Encoding.BECH32M
decoded = f4jumble_inv(bytes(convertbits(data, 5, 8, False)))
suffix = decoded[-16:]
# check trailing padding bytes
assert suffix == padding(hrp)
rest = decoded[:-16]
result = {}
while len(rest) > 0:
(receiver_type, rest) = parse_compact_size(rest)
(receiver_len, rest) = parse_compact_size(rest)
expected_len = {0: 20, 1: 20, 2: 43, 3: 43}.get(receiver_type)
if expected_len is not None:
assert receiver_len == expected_len, "incorrect receiver length"
assert len(rest) >= receiver_len
(receiver, rest) = (rest[:receiver_len], rest[receiver_len:])
if receiver_type == 0 or receiver_type == 1:
assert not ('transparent' in result), "duplicate transparent receiver detected"
assert len(receiver) == 20
result['transparent'] = receiver
elif receiver_type == 2:
assert not ('sapling' in result), "duplicate sapling receiver detected"
assert len(receiver) == 43
result['sapling'] = receiver
elif receiver_type == 3:
assert not ('orchard' in result), "duplicate orchard receiver detected"
assert len(receiver) == 43
result['orchard'] = receiver
return result
from unified_encoding import encode_unified, decode_unified
from unified_encoding import P2PKH_ITEM, P2SH_ITEM, SAPLING_ITEM, ORCHARD_ITEM
def main():
args = render_args()
from random import Random
rng = Random(0xabad533d)
def randbytes(l):
ret = []
while len(ret) < l:
ret.append(rng.randrange(0, 256))
return bytes(ret)
rand = Rand(randbytes)
rand = Rand(randbytes(rng))
test_vectors = []
for _ in range(0, 10):
@ -125,13 +51,15 @@ def main():
is_p2pkh = rand.bool()
receivers = [
orchard_raw_addr,
sapling_raw_addr,
(is_p2pkh, t_addr)
(ORCHARD_ITEM, orchard_raw_addr),
(SAPLING_ITEM, sapling_raw_addr),
(P2PKH_ITEM, t_addr if is_p2pkh else None),
(P2SH_ITEM, None if is_p2pkh else t_addr),
]
ua = encode_unified(receivers)
ua = encode_unified(rng, receivers, "u")
decoded = decode_unified(ua)
expected_lengths = {P2PKH_ITEM: 20, P2SH_ITEM: 20, SAPLING_ITEM: 43, ORCHARD_ITEM: 43}
decoded = decode_unified(ua, "u", expected_lengths)
assert decoded.get('orchard') == orchard_raw_addr
assert decoded.get('sapling') == sapling_raw_addr
assert decoded.get('transparent') == t_addr

82
unified_encoding.py Normal file
View File

@ -0,0 +1,82 @@
import sys; assert sys.version_info[0] >= 3, "Python 3 required."
from random import Random
from zc_utils import write_compact_size, parse_compact_size
from bech32m import bech32_encode, bech32_decode, convertbits, Encoding
from f4jumble import f4jumble, f4jumble_inv
P2PKH_ITEM = 0x00
P2SH_ITEM = 0x01
SAPLING_ITEM = 0x02
ORCHARD_ITEM = 0x03
def tlv(typecode, value):
return b"".join([write_compact_size(typecode), write_compact_size(len(value)), value])
def padding(hrp):
assert(len(hrp) <= 16)
return bytes(hrp, "utf8") + bytes(16 - len(hrp))
def encode_unified(rng, items, hrp):
encoded_items = []
has_p2pkh = False
has_p2sh = False
for item in items:
if item[1]:
if item[0] == P2PKH_ITEM:
has_p2pkh = True
if item[0] == P2SH_ITEM:
has_p2sh = True
assert (not (has_p2pkh and has_p2sh))
encoded_items.append(tlv(item[0], item[1]))
items_bytes = rng.sample(encoded_items, k=len(encoded_items))
items_bytes.append(padding(hrp))
r_bytes = b"".join(items_bytes)
converted = convertbits(f4jumble(r_bytes), 8, 5)
return bech32_encode(hrp, converted, Encoding.BECH32M)
def decode_unified(encoded, expected_hrp, expected_lengths):
(hrp, data, encoding) = bech32_decode(encoded)
assert hrp == expected_hrp and encoding == Encoding.BECH32M
assert(len(data) >= 48)
decoded = f4jumble_inv(bytes(convertbits(data, 5, 8, False)))
suffix = decoded[-16:]
# check trailing padding bytes
assert suffix == padding(hrp)
rest = decoded[:-16]
result = {}
while len(rest) > 0:
(item_type, rest) = parse_compact_size(rest)
(item_len, rest) = parse_compact_size(rest)
expected_len = expected_lengths.get(item_type)
if expected_len is not None:
assert item_len == expected_len, "incorrect item length"
assert len(rest) >= item_len
(item, rest) = (rest[:item_len], rest[item_len:])
if item_type == P2PKH_ITEM or item_type == P2SH_ITEM:
assert not ('transparent' in result), "duplicate transparent item detected"
result['transparent'] = item
elif item_type == SAPLING_ITEM:
assert not ('sapling' in result), "duplicate sapling item detected"
result['sapling'] = item
elif item_type == ORCHARD_ITEM:
assert not ('orchard' in result), "duplicate orchard item detected"
result['orchard'] = item
else:
assert not ('unknown' in result), "duplicate unknown item detected"
result['unknown'] = (item_type, item)
return result

136
unified_full_viewing_keys.py Executable file
View File

@ -0,0 +1,136 @@
#!/usr/bin/env python3
import sys; assert sys.version_info[0] >= 3, "Python 3 required."
import math
from random import Random
import struct
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import PublicFormat, Encoding
from tv_output import render_args, render_tv, Some
from tv_rand import Rand, randbytes
from zc_utils import write_compact_size, parse_compact_size
from f4jumble import f4jumble, f4jumble_inv
import orchard_key_components
import sapling_key_components
import sapling_zip32
from unified_encoding import encode_unified, decode_unified
from unified_encoding import P2PKH_ITEM, P2SH_ITEM, SAPLING_ITEM, ORCHARD_ITEM
def main():
args = render_args()
rng = Random(0xabad533d)
rand = Rand(randbytes(rng))
seed = rand.b(32)
test_vectors = []
for i in range(0, 10):
has_t_key = rand.bool()
if has_t_key:
c = rand.b(32)
privkey = ec.derive_private_key(int.from_bytes(rand.b(32), 'little'), ec.SECP256K1())
pubkey = privkey.public_key()
pubkey_bytes = pubkey.public_bytes(Encoding.X962, PublicFormat.CompressedPoint)
t_key_bytes = c + pubkey_bytes
else:
t_key_bytes = None
has_s_key = rand.bool()
if has_s_key:
root_key = sapling_zip32.ExtendedSpendingKey.master(seed)
purpose_key = root_key.child(sapling_zip32.hardened(32))
coin_key = purpose_key.child(sapling_zip32.hardened(133))
account_key = coin_key.child(sapling_zip32.hardened(i))
sapling_fvk = account_key.to_extended_fvk()
sapling_fvk_bytes = b"".join([
bytes(sapling_fvk.ak()),
bytes(sapling_fvk.nk()),
sapling_fvk.ovk(),
sapling_fvk.dk()
])
else:
sapling_fvk_bytes = None
has_o_key = (not has_s_key) or rand.bool()
if has_o_key:
orchard_sk = orchard_key_components.SpendingKey(rand.b(32))
orchard_fvk = orchard_key_components.FullViewingKey(orchard_sk)
orchard_fvk_bytes = b"".join([
bytes(orchard_fvk.ak),
bytes(orchard_fvk.nk),
bytes(orchard_fvk.rivk)
])
else:
orchard_fvk_bytes = None
# include an unknown item 1/4 of the time
has_unknown_item = rand.bool() and rand.bool()
# use the range reserved for experimental typecodes for unknowns
unknown_tc = rng.randrange(0xFFFA, 0xFFFF+1)
unknown_len = rng.randrange(32, 256)
if has_unknown_item:
unknown_bytes = b"".join([rand.b(unknown_len)])
else:
unknown_bytes = None
receivers = [
(ORCHARD_ITEM, orchard_fvk_bytes),
(SAPLING_ITEM, sapling_fvk_bytes),
(P2PKH_ITEM, t_key_bytes),
(unknown_tc, unknown_bytes),
]
ufvk = encode_unified(rng, receivers, "uview")
expected_lengths = {
P2PKH_ITEM: 65,
SAPLING_ITEM: 128,
ORCHARD_ITEM: 96,
unknown_tc: unknown_len
}
decoded = decode_unified(ufvk, "uview", expected_lengths)
assert decoded.get('orchard') == orchard_fvk_bytes
assert decoded.get('sapling') == sapling_fvk_bytes
assert decoded.get('transparent') == t_key_bytes
assert decoded.get('unknown') == ((unknown_tc, unknown_bytes) if unknown_bytes else None)
test_vectors.append({
't_key_bytes': t_key_bytes,
'sapling_fvk_bytes': sapling_fvk_bytes,
'orchard_fvk_bytes': orchard_fvk_bytes,
'unknown_fvk_typecode': unknown_tc,
'unknown_fvk_bytes': unknown_bytes,
'unified_fvk': ufvk.encode(),
})
render_tv(
args,
'unified_full_viewing_keys',
(
('t_key_bytes', {
'rust_type': 'Option<[u8; 65]>',
'rust_fmt': lambda x: None if x is None else Some(x),
}),
('sapling_fvk_bytes', {
'rust_type': 'Option<[u8; 128]>',
'rust_fmt': lambda x: None if x is None else Some(x),
}),
('orchard_fvk_bytes', {
'rust_type': 'Option<[u8; 96]>',
'rust_fmt': lambda x: None if x is None else Some(x),
}),
('unknown_fvk_typecode', 'u32'),
('unknown_fvk_bytes', {
'rust_type': 'Option<Vec<u8>>',
'rust_fmt': lambda x: None if x is None else Some(x),
}),
('unified_fvk', 'Vec<u8>')
),
test_vectors,
)
if __name__ == "__main__":
main()

View File

@ -3,10 +3,10 @@ import sys; assert sys.version_info[0] >= 3, "Python 3 required."
import struct
MAX_SIZE = 0x2000000
MAX_COMPACT_SIZE = 0x2000000
def write_compact_size(n, allow_u64=False):
assert allow_u64 or n <= MAX_SIZE
assert allow_u64 or n <= MAX_COMPACT_SIZE
if n < 253:
return struct.pack('B', n)
elif n <= 0xFFFF:
@ -18,7 +18,7 @@ def write_compact_size(n, allow_u64=False):
def parse_compact_size(rest, allow_u64=False):
(n, rest) = parse_compact_u64(rest)
assert allow_u64 or n <= MAX_SIZE
assert allow_u64 or n <= MAX_COMPACT_SIZE
return (n, rest)
def parse_compact_u64(rest):