175 lines
5.5 KiB
Python
Executable File
175 lines
5.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import sys; assert sys.version_info[0] >= 3, "Python 3 required."
|
|
|
|
import math
|
|
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 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
|
|
|
|
|
|
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)
|
|
|
|
test_vectors = []
|
|
for _ in range(0, 10):
|
|
has_t_addr = rand.bool()
|
|
if has_t_addr:
|
|
t_addr = b"".join([rand.b(20)])
|
|
else:
|
|
t_addr = None
|
|
|
|
has_s_addr = rand.bool()
|
|
if has_s_addr:
|
|
sapling_sk = sapling_key_components.SpendingKey(rand.b(32))
|
|
sapling_default_d = sapling_sk.default_d()
|
|
sapling_default_pk_d = sapling_sk.default_pkd()
|
|
sapling_raw_addr = b"".join([sapling_default_d[:11], bytes(sapling_default_pk_d)[:32]])
|
|
else:
|
|
sapling_raw_addr = None
|
|
|
|
has_o_addr = (not has_s_addr) or rand.bool()
|
|
if has_o_addr:
|
|
orchard_sk = orchard_key_components.SpendingKey(rand.b(32))
|
|
orchard_fvk = orchard_key_components.FullViewingKey(orchard_sk)
|
|
orchard_default_d = orchard_fvk.default_d()
|
|
orchard_default_pk_d = orchard_fvk.default_pkd()
|
|
orchard_raw_addr = b"".join([orchard_default_d[:11], bytes(orchard_default_pk_d)[:32]])
|
|
else:
|
|
orchard_raw_addr = None
|
|
|
|
is_p2pkh = rand.bool()
|
|
receivers = [
|
|
orchard_raw_addr,
|
|
sapling_raw_addr,
|
|
(is_p2pkh, t_addr)
|
|
]
|
|
ua = encode_unified(receivers)
|
|
|
|
decoded = decode_unified(ua)
|
|
assert decoded.get('orchard') == orchard_raw_addr
|
|
assert decoded.get('sapling') == sapling_raw_addr
|
|
assert decoded.get('transparent') == t_addr
|
|
|
|
test_vectors.append({
|
|
'p2pkh_bytes': t_addr if is_p2pkh else None,
|
|
'p2sh_bytes': None if is_p2pkh else t_addr,
|
|
'sapling_raw_addr': sapling_raw_addr,
|
|
'orchard_raw_addr': orchard_raw_addr,
|
|
'unified_addr': ua.encode()
|
|
})
|
|
|
|
render_tv(
|
|
args,
|
|
'unified_address',
|
|
(
|
|
('p2pkh_bytes', {
|
|
'rust_type': 'Option<[u8; 20]>',
|
|
'rust_fmt': lambda x: None if x is None else Some(x),
|
|
}),
|
|
('p2sh_bytes', {
|
|
'rust_type': 'Option<[u8; 20]>',
|
|
'rust_fmt': lambda x: None if x is None else Some(x),
|
|
}),
|
|
('sapling_raw_addr', {
|
|
'rust_type': 'Option<[u8; 43]>',
|
|
'rust_fmt': lambda x: None if x is None else Some(x),
|
|
}),
|
|
('orchard_raw_addr', {
|
|
'rust_type': 'Option<[u8; 43]>',
|
|
'rust_fmt': lambda x: None if x is None else Some(x),
|
|
}),
|
|
('unified_addr', 'Vec<u8>')
|
|
),
|
|
test_vectors,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|