diff --git a/transaction.py b/transaction.py new file mode 100644 index 0000000..25aa5b2 --- /dev/null +++ b/transaction.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +import struct + +from zc_utils import write_compact_size + +MAX_MONEY = 21000000 * 100000000 +TX_EXPIRY_HEIGHT_THRESHOLD = 500000000 + +OVERWINTER_VERSION_GROUP_ID = 0x03C48270 +OVERWINTER_TX_VERSION = 3 + + +# BN254 encoding of G1 elements. p[1] is big-endian. +def pack_g1(p): + return struct.pack('B', 0x02 | (1 if p[0] else 0)) + p[1] + +# BN254 encoding of G2 elements. p[1] is big-endian. +def pack_g2(p): + return struct.pack('B', 0x0a | (1 if p[0] else 0)) + p[1] + +class PHGRProof(object): + def __init__(self, rand): + self.g_A = (rand.bool(), rand.b(32)) + self.g_A_prime = (rand.bool(), rand.b(32)) + self.g_B = (rand.bool(), rand.b(64)) + self.g_B_prime = (rand.bool(), rand.b(32)) + self.g_C = (rand.bool(), rand.b(32)) + self.g_C_prime = (rand.bool(), rand.b(32)) + self.g_K = (rand.bool(), rand.b(32)) + self.g_H = (rand.bool(), rand.b(32)) + + def __bytes__(self): + return ( + pack_g1(self.g_A) + + pack_g1(self.g_A_prime) + + pack_g2(self.g_B) + + pack_g1(self.g_B_prime) + + pack_g1(self.g_C) + + pack_g1(self.g_C_prime) + + pack_g1(self.g_K) + + pack_g1(self.g_H) + ) + + +class JoinSplit(object): + def __init__(self, rand): + self.vpub_old = 0 + self.vpub_new = 0 + self.anchor = rand.b(32) + self.nullifiers = (rand.b(32), rand.b(32)) + self.commitments = (rand.b(32), rand.b(32)) + self.ephemeralKey = rand.b(32) + self.randomSeed = rand.b(32) + self.macs = (rand.b(32), rand.b(32)) + self.proof = PHGRProof(rand) + self.ciphertexts = (rand.b(601), rand.b(601)) + + def __bytes__(self): + return ( + struct.pack('= 2: + for i in range(rand.u8() % 3): + self.vJoinSplit.append(JoinSplit(rand)) + if len(self.vJoinSplit) > 0: + self.joinSplitPubKey = rand.b(32) # Potentially invalid + self.joinSplitSig = rand.b(64) # Invalid + + def header(self): + return self.nVersion | (1 << 31 if self.fOverwintered else 0) + + def __bytes__(self): + ret = b'' + ret += struct.pack('= 2: + ret += write_compact_size(len(self.vJoinSplit)) + for jsdesc in self.vJoinSplit: + ret += bytes(jsdesc) + if len(self.vJoinSplit) > 0: + ret += self.joinSplitPubKey + ret += self.joinSplitSig + + return ret diff --git a/tv_output.py b/tv_output.py index 38c182f..ce63ad8 100644 --- a/tv_output.py +++ b/tv_output.py @@ -43,7 +43,7 @@ def tv_json(filename, parts, vectors, bitcoin_flavoured): ', '.join([p[0] for p in parts]) )) print(' ' + ',\n '.join([ - json.dumps([tv_value_json(v[p[0]], bitcoin_flavoured) for p in parts]) for v in vectors + json.dumps([tv_value_json(v[p[0]], p[1].get('bitcoin_flavoured', bitcoin_flavoured)) for p in parts]) for v in vectors ])) print(']') @@ -63,6 +63,17 @@ def tv_bytes_rust(name, value, pad): pad, )) +def tv_vec_bytes_rust(name, value, pad): + print('''%s%s: vec![ + %s%s +%s],''' % ( + pad, + name, + pad, + chunk(hexlify(value)), + pad, + )) + def tv_option_bytes_rust(name, value, pad): if value: print('''%s%s: Some([ @@ -77,15 +88,44 @@ def tv_option_bytes_rust(name, value, pad): else: print('%s%s: None,' % (pad, name)) +def tv_option_vec_bytes_rust(name, value, pad): + if value: + print('''%s%s: Some(vec![ + %s%s +%s]),''' % ( + pad, + name, + pad, + chunk(hexlify(value.thing)), + pad, + )) + else: + print('%s%s: None,' % (pad, name)) + def tv_int_rust(name, value, pad): print('%s%s: %d,' % (pad, name, value)) -def tv_part_rust(name, value, indent=3): +def tv_option_int_rust(name, value, pad): + if value: + print('%s%s: Some(%d),' % (pad, name, value.thing)) + else: + print('%s%s: None,' % (pad, name)) + +def tv_part_rust(name, value, config, indent=3): + if 'rust_fmt' in config: + value = config['rust_fmt'](value) + pad = ' ' * indent - if type(value) == bytes: - tv_bytes_rust(name, value, pad) - elif isinstance(value, Some) or value is None: + if config['rust_type'] == 'Option>': + tv_option_vec_bytes_rust(name, value, pad) + elif config['rust_type'] == 'Vec': + tv_vec_bytes_rust(name, value, pad) + elif config['rust_type'].startswith('Option<['): tv_option_bytes_rust(name, value, pad) + elif type(value) == bytes: + tv_bytes_rust(name, value, pad) + elif config['rust_type'].startswith('Option<'): + tv_option_int_rust(name, value, pad) elif type(value) == int: tv_int_rust(name, value, pad) else: @@ -93,7 +133,7 @@ def tv_part_rust(name, value, indent=3): def tv_rust(filename, parts, vectors): print(' struct TestVector {') - for p in parts: print(' %s: %s,' % p) + for p in parts: print(' %s: %s,' % (p[0], p[1]['rust_type'])) print(''' }; // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/%s.py''' % ( @@ -101,13 +141,13 @@ def tv_rust(filename, parts, vectors): )) if type(vectors) == type({}): print(' let test_vector = TestVector {') - for p in parts: tv_part_rust(p[0], vectors[p[0]]) + for p in parts: tv_part_rust(p[0], vectors[p[0]], p[1]) print(' };') elif type(vectors) == type([]): print(' let test_vectors = vec![') for vector in vectors: print(' TestVector {') - for p in parts: tv_part_rust(p[0], vector[p[0]], 4) + for p in parts: tv_part_rust(p[0], vector[p[0]], p[1], 4) print(' },') print(' ];') else: @@ -124,6 +164,9 @@ def render_args(): return parser.parse_args() def render_tv(args, filename, parts, vectors): + # Convert older format + parts = [(p[0], p[1] if type(p[1]) == type({}) else {'rust_type': p[1]}) for p in parts] + if args.target == 'rust': tv_rust(filename, parts, vectors) elif args.target == 'zcash': diff --git a/tv_rand.py b/tv_rand.py new file mode 100644 index 0000000..3035286 --- /dev/null +++ b/tv_rand.py @@ -0,0 +1,28 @@ +import os +import struct + + +class Rand(object): + def __init__(self, random=os.urandom): + self._random = random + + def b(self, l): + return self._random(l) + + def v(self, l, f): + return struct.unpack(f, self.b(l))[0] + + def u8(self): + return self.v(1, 'b') + + def u32(self): + return self.v(4, ' 0 + + def a(self, vals): + return vals[self.u8() % len(vals)] diff --git a/zc_utils.py b/zc_utils.py new file mode 100644 index 0000000..06809ac --- /dev/null +++ b/zc_utils.py @@ -0,0 +1,27 @@ +import struct + +def write_compact_size(n): + if n < 253: + return struct.pack('B', n) + elif n <= 0xFFFF: + return struct.pack('B', 253) + struct.pack(' 0: + hashJoinSplits = getHashJoinSplits(tx) + + digest = blake2b( + digest_size=32, + person=b'ZcashSigHash' + struct.pack('', 'bitcoin_flavoured': False}), + ('script_code', 'Vec'), + ('transparent_input', { + 'rust_type': 'Option', + 'rust_fmt': lambda x: None if x == -1 else Some(x), + }), + ('hash_type', 'u32'), + ('amount', 'u64'), + ('consensus_branch_id', 'u32'), + ('sighash', '[u8; 32]'), + ), + test_vectors, + ) + + +if __name__ == '__main__': + main()