import struct from .orchard.pallas import ( Fp as PallasBase, Scalar as PallasScalar, ) from .orchard.sinsemilla import group_hash as pallas_group_hash from .sapling.generators import find_group_hash, SPENDING_KEY_BASE from .sapling.jubjub import ( Fq, Point, Fr as JubjubScalar, ) from .utils import leos2ip, i2leosp 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 SAPLING_VERSION_GROUP_ID = 0x892F2085 SAPLING_TX_VERSION = 4 NU5_VERSION_GROUP_ID = 0x26A7270A NU5_TX_VERSION = 5 # Sapling note magic values, copied from src/zcash/Zcash.h NOTEENCRYPTION_AUTH_BYTES = 16 ZC_NOTEPLAINTEXT_LEADING = 1 ZC_V_SIZE = 8 ZC_RHO_SIZE = 32 ZC_R_SIZE = 32 ZC_MEMO_SIZE = 512 ZC_DIVERSIFIER_SIZE = 11 ZC_JUBJUB_POINT_SIZE = 32 ZC_JUBJUB_SCALAR_SIZE = 32 ZC_NOTEPLAINTEXT_SIZE = ZC_NOTEPLAINTEXT_LEADING + ZC_V_SIZE + ZC_RHO_SIZE + ZC_R_SIZE + ZC_MEMO_SIZE ZC_SAPLING_ENCPLAINTEXT_SIZE = ZC_NOTEPLAINTEXT_LEADING + ZC_DIVERSIFIER_SIZE + ZC_V_SIZE + ZC_R_SIZE + ZC_MEMO_SIZE ZC_SAPLING_OUTPLAINTEXT_SIZE = ZC_JUBJUB_POINT_SIZE + ZC_JUBJUB_SCALAR_SIZE ZC_SAPLING_ENCCIPHERTEXT_SIZE = ZC_SAPLING_ENCPLAINTEXT_SIZE + NOTEENCRYPTION_AUTH_BYTES ZC_SAPLING_OUTCIPHERTEXT_SIZE = ZC_SAPLING_OUTPLAINTEXT_SIZE + NOTEENCRYPTION_AUTH_BYTES # 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 GrothProof(object): def __init__(self, rand): self.g_A = rand.b(48) self.g_B = rand.b(96) self.g_C = rand.b(48) def __bytes__(self): return ( self.g_A + self.g_B + self.g_C ) class RedJubjubSignature(object): def __init__(self, rand): self.R = find_group_hash(b'TVRandPt', rand.b(32)) self.S = JubjubScalar(leos2ip(rand.b(32))) def __bytes__(self): return ( bytes(self.R) + bytes(self.S) ) class RedPallasSignature(object): def __init__(self, rand): self.R = pallas_group_hash(b'TVRandPt', rand.b(32)) self.S = PallasScalar(leos2ip(rand.b(32))) def __bytes__(self): return ( bytes(self.R) + bytes(self.S) ) class SpendDescription(object): def __init__(self, rand, anchor=None): self.cv = find_group_hash(b'TVRandPt', rand.b(32)) self.anchor = Fq(leos2ip(rand.b(32))) if anchor is None else anchor self.nullifier = rand.b(32) self.rk = Point.rand(rand) self.proof = GrothProof(rand) self.spendAuthSig = rand.b(64) if anchor is None else RedJubjubSignature(rand) # Invalid def bytes_v5(self): return ( bytes(self.cv) + self.nullifier + bytes(self.rk) ) def __bytes__(self): return ( bytes(self.cv) + bytes(self.anchor) + self.nullifier + bytes(self.rk) + bytes(self.proof) + self.spendAuthSig ) class OutputDescription(object): def __init__(self, rand): self.cv = find_group_hash(b'TVRandPt', rand.b(32)) self.cmu = Fq(leos2ip(rand.b(32))) self.ephemeralKey = find_group_hash(b'TVRandPt', rand.b(32)) self.encCiphertext = rand.b(ZC_SAPLING_ENCCIPHERTEXT_SIZE) self.outCipherText = rand.b(ZC_SAPLING_OUTCIPHERTEXT_SIZE) self.proof = GrothProof(rand) def bytes_v5(self): return ( bytes(self.cv) + bytes(self.cmu) + bytes(self.ephemeralKey) + self.encCiphertext + self.outCipherText ) def __bytes__(self): return ( self.bytes_v5() + bytes(self.proof) ) class OrchardActionDescription(object): def __init__(self, rand): # We don't need to take account of whether this is a coinbase transaction, # because we're only generating random fields. self.cv = pallas_group_hash(b'TVRandPt', rand.b(32)) self.nullifier = PallasBase(leos2ip(rand.b(32))) self.rk = pallas_group_hash(b'TVRandPt', rand.b(32)) self.cmx = PallasBase(leos2ip(rand.b(32))) self.ephemeralKey = pallas_group_hash(b'TVRandPt', rand.b(32)) self.encCiphertext = rand.b(ZC_SAPLING_ENCCIPHERTEXT_SIZE) self.outCiphertext = rand.b(ZC_SAPLING_OUTCIPHERTEXT_SIZE) self.spendAuthSig = RedPallasSignature(rand) def __bytes__(self): return ( bytes(self.cv) + bytes(self.nullifier) + bytes(self.rk) + bytes(self.cmx) + bytes(self.ephemeralKey) + self.encCiphertext + self.outCiphertext ) class JoinSplit(object): def __init__(self, rand, fUseGroth = False): 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 = GrothProof(rand) if fUseGroth else PHGRProof(rand) self.ciphertexts = (rand.b(601), rand.b(601)) def __bytes__(self): return ( struct.pack('= 0 if height == 0: enc_height = b'\x00' elif height <= 16: enc_height = bytes([0x50 + height]) elif height <= 0x7F: enc_height = b'\x01' + i2leosp( 8, height) elif height <= 0x7FFF: enc_height = b'\x02' + i2leosp(16, height) elif height <= 0x7FFFFF: enc_height = b'\x03' + i2leosp(24, height) elif height <= 0x7FFFFFFF: enc_height = b'\x04' + i2leosp(32, height) else: assert height <= 0x7FFFFFFFFF enc_height = b'\x05' + i2leosp(40, height) # zcashd adds an OP_0 return Script.from_bytes(enc_height + b'\x00') def raw(self): return self._script def __bytes__(self): return write_compact_size(len(self._script)) + self._script class OutPoint(object): def __init__(self, rand=None): if rand is not None: self.txid = rand.b(32) self.n = rand.u32() @staticmethod def from_components(txid, n): outpoint = OutPoint() outpoint.txid = txid outpoint.n = n return outpoint def __bytes__(self): return self.txid + struct.pack('= SAPLING_TX_VERSION: self.valueBalance = rand.u64() % (MAX_MONEY + 1) self.vShieldedSpends = [] self.vShieldedOutputs = [] if self.nVersion >= SAPLING_TX_VERSION: for _ in range(rand.i8() % 5): self.vShieldedSpends.append(SpendDescription(rand)) for _ in range(rand.i8() % 5): self.vShieldedOutputs.append(OutputDescription(rand)) self.vJoinSplit = [] if self.nVersion >= 2: for i in range(rand.i8() % 3): self.vJoinSplit.append(JoinSplit(rand, self.fOverwintered and self.nVersion >= SAPLING_TX_VERSION)) if len(self.vJoinSplit) > 0: self.joinSplitPubKey = rand.b(32) # Potentially invalid self.joinSplitSig = rand.b(64) # Invalid if self.nVersion >= SAPLING_TX_VERSION: self.bindingSig = rand.b(64) # Invalid def version_bytes(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 if isSaplingV4 and not (len(self.vShieldedSpends) == 0 and len(self.vShieldedOutputs) == 0): ret += self.bindingSig return ret class TransactionV5(object): def __init__(self, rand, consensus_branch_id): # Decide which transaction parts will be generated. flip_coins = rand.u8() have_transparent_in = (flip_coins >> 0) % 2 have_transparent_out = (flip_coins >> 1) % 2 have_sapling = (flip_coins >> 2) % 2 have_orchard = (flip_coins >> 3) % 2 is_coinbase = (not have_transparent_in) and (flip_coins >> 4) % 2 # Common Transaction Fields self.nVersionGroupId = NU5_VERSION_GROUP_ID self.nConsensusBranchId = consensus_branch_id self.nLockTime = rand.u32() self.nExpiryHeight = rand.u32() % TX_EXPIRY_HEIGHT_THRESHOLD # Transparent Transaction Fields self.vin = [] self.vout = [] if have_transparent_in: for _ in range((rand.u8() % 3) + 1): self.vin.append(TxIn(rand)) if is_coinbase: self.vin.append(TxIn.from_components( OutPoint.from_components(b'\x00' * 32, 0xFFFFFFFF), Script.coinbase_from_height(self.nExpiryHeight), 0xFFFFFFFF)) if have_transparent_out: for _ in range((rand.u8() % 3) + 1): self.vout.append(TxOut(rand)) # Sapling Transaction Fields self.vSpendsSapling = [] self.vOutputsSapling = [] if have_sapling: self.anchorSapling = Fq(leos2ip(rand.b(32))) # We use the randomness unconditionally here to avoid unnecessary test vector changes. for _ in range(rand.u8() % 3): spend = SpendDescription(rand, self.anchorSapling) if not is_coinbase: self.vSpendsSapling.append(spend) for _ in range(rand.u8() % 3): self.vOutputsSapling.append(OutputDescription(rand)) self.valueBalanceSapling = rand.u64() % (MAX_MONEY + 1) self.bindingSigSapling = RedJubjubSignature(rand) else: # If valueBalanceSapling is not present in the serialized transaction, then # v^balanceSapling is defined to be 0. self.valueBalanceSapling = 0 # Orchard Transaction Fields self.vActionsOrchard = [] if have_orchard: for _ in range(rand.u8() % 5): self.vActionsOrchard.append(OrchardActionDescription(rand)) self.flagsOrchard = rand.u8() & 3 # Only two flag bits are currently defined. if is_coinbase: # set enableSpendsOrchard = 0 self.flagsOrchard &= 2 self.valueBalanceOrchard = rand.u64() % (MAX_MONEY + 1) self.anchorOrchard = PallasBase(leos2ip(rand.b(32))) self.proofsOrchard = rand.b(rand.u8() + 32) # Proof will always contain at least one element self.bindingSigOrchard = RedPallasSignature(rand) else: # If valueBalanceOrchard is not present in the serialized transaction, then # v^balanceOrchard is defined to be 0. self.valueBalanceOrchard = 0 assert is_coinbase == self.is_coinbase() def version_bytes(self): return NU5_TX_VERSION | (1 << 31) def is_coinbase(self): # return len(self.vin) == 1 and bytes(self.vin[0].prevout.txid) == b'\x00'*32 and self.vin[0].prevout.n == 0xFFFFFFFF # TODO: Update ZIP 225 to document endianness def __bytes__(self): ret = b'' # Common Transaction Fields ret += struct.pack(' 0 ret += write_compact_size(len(self.vSpendsSapling)) for desc in self.vSpendsSapling: ret += desc.bytes_v5() ret += write_compact_size(len(self.vOutputsSapling)) for desc in self.vOutputsSapling: ret += desc.bytes_v5() if hasSapling: ret += struct.pack(' 0: ret += bytes(self.anchorSapling) # Not explicitly gated in the protocol spec, but if the gate # were inactive then these loops would be empty by definition. for desc in self.vSpendsSapling: # vSpendProofsSapling ret += bytes(desc.proof) for desc in self.vSpendsSapling: # vSpendAuthSigsSapling ret += bytes(desc.spendAuthSig) for desc in self.vOutputsSapling: # vOutputProofsSapling ret += bytes(desc.proof) if hasSapling: ret += bytes(self.bindingSigSapling) # Orchard Transaction Fields ret += write_compact_size(len(self.vActionsOrchard)) if len(self.vActionsOrchard) > 0: # Not explicitly gated in the protocol spec, but if the gate # were inactive then these loops would be empty by definition. for desc in self.vActionsOrchard: ret += bytes(desc) # Excludes spendAuthSig ret += struct.pack('B', self.flagsOrchard) ret += struct.pack('