zcash-test-vectors/sapling_zip32.py

225 lines
6.7 KiB
Python

#!/usr/bin/env python3
import sys; assert sys.version_info[0] >= 3, "Python 3 required."
from pyblake2 import blake2b
from sapling_key_components import to_scalar, prf_expand, diversify_hash, DerivedAkNk, DerivedIvk
from sapling_generators import SPENDING_KEY_BASE, PROVING_KEY_BASE
from utils import i2leosp, i2lebsp, lebs2osp
from ff1 import ff1_aes256_encrypt
from tv_output import render_args, render_tv, option, Some
def encode_xsk_parts(ask, nsk, ovk, dk):
# bytes = i2leosp_256 for Fr
return bytes(ask) + bytes(nsk) + ovk + dk
def encode_xfvk_parts(ak, nk, ovk, dk):
return bytes(ak) + bytes(nk) + ovk + dk
class ExtendedBase(object):
def ovk(self):
return self._ovk
def dk(self):
return self._dk
def c(self):
return self._c
def depth(self):
return self._depth
def parent_tag(self):
return self._parent_tag
def i(self):
return self._i
def diversifier(self, j):
d = lebs2osp(ff1_aes256_encrypt(self.dk(), b'', i2lebsp(88, j)))
return d if diversify_hash(d) else None
def fingerprint(self):
FVK = bytes(self.ak()) + bytes(self.nk()) + self.ovk()
return blake2b(person=b'ZcashSaplingFVFP', digest_size=32, data=FVK).digest()
def tag(self):
return self.fingerprint()[:4]
class ExtendedSpendingKey(DerivedAkNk, DerivedIvk, ExtendedBase):
def __init__(self, ask, nsk, ovk, dk, c, depth=0, parent_tag=i2leosp(32, 0), i=0):
self._ask = ask
self._nsk = nsk
self._ovk = ovk
self._dk = dk
self._c = c
self._depth = depth
self._parent_tag = parent_tag
self._i = i
@classmethod
def master(cls, S):
I = blake2b(person=b'ZcashIP32Sapling', data=S).digest()
I_L = I[:32]
I_R = I[32:]
sk_m = I_L
ask_m = to_scalar(prf_expand(sk_m, b'\x00'))
nsk_m = to_scalar(prf_expand(sk_m, b'\x01'))
ovk_m = prf_expand(sk_m, b'\x02')[:32]
dk_m = prf_expand(sk_m, b'\x10')[:32]
c_m = I_R
return cls(ask_m, nsk_m, ovk_m, dk_m, c_m)
def ask(self):
return self._ask
def nsk(self):
return self._nsk
def is_xsk(self):
return True
def __bytes__(self):
return (i2leosp(8, self.depth()) +
self.parent_tag() +
i2leosp(32, self.i()) +
self.c() +
encode_xsk_parts(self.ask(), self.nsk(), self.ovk(), self.dk()))
def to_extended_fvk(self):
return ExtendedFullViewingKey(self.ak(), self.nk(), self.ovk(), self.dk(), self.c(),
self.depth(), self.parent_tag(), self.i())
def child(self, i):
if i >= 1<<31: # child is a hardened key
prefix = b'\x11' + encode_xsk_parts(self.ask(), self.nsk(), self.ovk(), self.dk())
else:
prefix = b'\x12' + encode_xfvk_parts(self.ak(), self.nk(), self.ovk(), self.dk())
I = prf_expand(self.c(), prefix + i2leosp(32, i))
I_L = I[:32]
I_R = I[32:]
I_ask = to_scalar(prf_expand(I_L, b'\x13'))
I_nsk = to_scalar(prf_expand(I_L, b'\x14'))
ask_i = I_ask + self.ask()
nsk_i = I_nsk + self.nsk()
ovk_i = prf_expand(I_L, b'\x15' + self.ovk())[:32]
dk_i = prf_expand(I_L, b'\x16' + self.dk())[:32]
c_i = I_R
return self.__class__(ask_i, nsk_i, ovk_i, dk_i, c_i, self.depth()+1, self.tag(), i)
class ExtendedFullViewingKey(DerivedIvk, ExtendedBase):
def __init__(self, ak, nk, ovk, dk, c, depth=0, parent_tag=i2leosp(32, 0), i=0):
self._ak = ak
self._nk = nk
self._ovk = ovk
self._dk = dk
self._c = c
self._depth = depth
self._parent_tag = parent_tag
self._i = i
@classmethod
def master(cls, S):
return ExtendedSpendingKey.master(S).to_extended_fvk()
def ak(self):
return self._ak
def nk(self):
return self._nk
def is_xsk(self):
return False
def __bytes__(self):
return (i2leosp(8, self.depth()) +
self.parent_tag() +
i2leosp(32, self.i()) +
self.c() +
encode_xfvk_parts(self.ak(), self.nk(), self.ovk(), self.dk()))
def to_extended_fvk(self):
return self
def child(self, i):
if i >= 1<<31:
raise ValueError("can't derive a child hardened key from an extended full viewing key")
else:
prefix = b'\x12' + encode_xfvk_parts(self.ak(), self.nk(), self.ovk(), self.dk())
I = prf_expand(self.c(), prefix + i2leosp(32, i))
I_L = I[:32]
I_R = I[32:]
I_ask = to_scalar(prf_expand(I_L, b'\x13'))
I_nsk = to_scalar(prf_expand(I_L, b'\x14'))
ak_i = SPENDING_KEY_BASE * I_ask + self.ak()
nk_i = PROVING_KEY_BASE * I_nsk + self.nk()
ovk_i = prf_expand(I_L, b'\x15' + self.ovk())[:32]
dk_i = prf_expand(I_L, b'\x16' + self.dk())[:32]
c_i = I_R
return self.__class__(ak_i, nk_i, ovk_i, dk_i, c_i, self.depth()+1, self.tag(), i)
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)
m_1_2h = m_1.child(hardened(2))
m_1_2hv = m_1_2h.to_extended_fvk()
m_1_2hv_3 = m_1_2hv.child(3)
test_vectors = [
{'ask' : Some(bytes(k.ask())) if k.is_xsk() else None,
'nsk' : Some(bytes(k.nsk())) if k.is_xsk() else None,
'ovk' : k.ovk(),
'dk' : k.dk(),
'c' : k.c(),
'ak' : bytes(k.ak()),
'nk' : bytes(k.nk()),
'ivk' : bytes(k.ivk()),
'xsk' : Some(bytes(k)) if k.is_xsk() else None,
'xfvk': bytes(k.to_extended_fvk()),
'fp' : k.fingerprint(),
'd0' : option(k.diversifier(0)),
'd1' : option(k.diversifier(1)),
'd2' : option(k.diversifier(2)),
'dmax': option(k.diversifier((1<<88)-1)),
}
for k in (m, m_1, m_1_2h, m_1_2hv, m_1_2hv_3)
]
render_tv(
args,
'sapling_zip32',
(
('ask', 'Option<[u8; 32]>'),
('nsk', 'Option<[u8; 32]>'),
('ovk', '[u8; 32]'),
('dk', '[u8; 32]'),
('c', '[u8; 32]'),
('ak', '[u8; 32]'),
('nk', '[u8; 32]'),
('ivk', '[u8; 32]'),
('xsk', 'Option<[u8; 169]>'),
('xfvk','[u8; 169]'),
('fp', '[u8; 32]'),
('d0', 'Option<[u8; 11]>'),
('d1', 'Option<[u8; 11]>'),
('d2', 'Option<[u8; 11]>'),
('dmax','Option<[u8; 11]>'),
),
test_vectors,
)
if __name__ == '__main__':
main()