From 8ce3cfb8d9cbbdd98d38cb9c9d895d22a084f701 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Thu, 6 May 2021 15:58:38 +0100 Subject: [PATCH 1/7] Add orchard_key_components.py. Signed-off-by: Daira Hopwood --- orchard_key_components.py | 144 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 orchard_key_components.py diff --git a/orchard_key_components.py b/orchard_key_components.py new file mode 100644 index 0000000..041a960 --- /dev/null +++ b/orchard_key_components.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +import sys; assert sys.version_info[0] >= 3, "Python 3 required." + +from pyblake2 import blake2b, blake2s + +from orchard_generators import SPENDING_KEY_BASE, group_hash +from orchard_pallas import Fp, Scalar, Point +from orchard_merkle_tree import MERKLE_DEPTH +from orchard_commitments import commit_ivk, note_commit +from utils import leos2bsp, leos2ip, i2leosp +from tv_output import render_args, render_tv + +# +# Utilities +# + +def to_scalar(buf): + return Scalar(leos2ip(buf)) + +def to_base(buf): + return Fp(leos2ip(buf)) + + +# +# PRFs and hashes +# + +def prf_expand(sk, t): + digest = blake2b(person=b'Zcash_ExpandSeed') + digest.update(sk) + digest.update(t) + return digest.digest() + +def diversify_hash(d): + P = group_hash(b'z.cash:Orchard-gd', d) + if P == Point.identity(): + P = group_hash(b'z.cash:Orchard-gd', b'') + return P + +# +# Key components +# + +class SpendingKey: + def __init__(self, data): + self.data = data + + self.ask = to_scalar(prf_expand(self.data, b'\x06')) + self.nk = to_base(prf_expand(self.data, b'\x07')) + self.rivk = to_scalar(prf_expand(self.data, b'\x08')) + if self.ask == Scalar.ZERO: + raise ValueError("invalid spending key") + + self.akP = SPENDING_KEY_BASE * self.ask + if bytes(self.akP)[-1] & 0x80 != 0: + self.ask = -self.ask + + self.ak = self.akP.extract() + if commit_ivk(self.rivk, self.ak, self.nk) is None: + raise ValueError("invalid spending key") + + +class FullViewingKey(object): + def __init__(self, sk): + (self.rivk, self.ak, self.nk) = (sk.rivk, sk.ak, sk.nk) + K = i2leosp(256, self.rivk.s) + R = prf_expand(K, b'\x82' + i2leosp(256, self.ak.s) + i2leosp(256, self.nk.s)) + self.dk = R[:32] + self.ovk = R[32:] + + def ivk(self): + return commit_ivk(self.rivk, self.ak, self.nk) + + def ovk(self): + return prf_expand(self.data, b'\x02')[:32] + + def default_d(self): + return i2leosp(88, 1337) + + def default_pkd(self): + return diversify_hash(self.default_d()) * self.ivk() + + +def main(): + args = render_args() + + test_vectors = [] + for i in range(0, 10): + sys.stdout.write(".") + sys.stdout.flush() + sk = SpendingKey(bytes([i] * 32)) + fvk = FullViewingKey(sk) + note_v = (2548793025584392057432895043257984320*i) % 2**64 + note_r = Scalar(8890123457840276890326754358439057438290574382905).exp(i+1) + note_rho = Fp(342358729643275392567239275209835729829*i) + note_psi = Fp(432592604358294371936572103719358723958*i) + note_cm = note_commit( + note_r, + leos2bsp(bytes(diversify_hash(fvk.default_d()))), + leos2bsp(bytes(fvk.default_pkd())), + note_v, + note_rho, + note_psi) + note_nf = b"0"*32 #note_nullifier(fvk.nk(), note_cm) + test_vectors.append({ + 'sk': sk.data, + 'ask': bytes(sk.ask), + 'ovk': fvk.ovk, + 'rivk': bytes(fvk.rivk), + 'ak': bytes(fvk.ak), + 'nk': bytes(fvk.nk), + 'ivk': bytes(fvk.ivk()), + 'default_d': fvk.default_d(), + 'default_pk_d': bytes(fvk.default_pkd()), + 'note_v': note_v, + 'note_r': bytes(note_r), + 'note_cmx': bytes(note_cm.extract()), + 'note_nf': note_nf, + }) + + render_tv( + args, + 'orchard_key_components', + ( + ('sk', '[u8; 32]'), + ('ask', '[u8; 32]'), + ('ovk', '[u8; 32]'), + ('rivk', '[u8; 32]'), + ('ak', '[u8; 32]'), + ('nk', '[u8; 32]'), + ('ivk', '[u8; 32]'), + ('default_d', '[u8; 11]'), + ('default_pk_d', '[u8; 32]'), + ('note_v', 'u64'), + ('note_r', '[u8; 32]'), + ('note_cmx', '[u8; 32]'), + ('note_nf', '[u8; 32]'), + ), + test_vectors, + ) + + +if __name__ == '__main__': + main() From 77f1299d9bdc79c48211d3845c3b042e8481bd1b Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sun, 9 May 2021 13:14:21 +0800 Subject: [PATCH 2/7] Add derive_nullifier() method --- orchard_key_components.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/orchard_key_components.py b/orchard_key_components.py index 041a960..65e2650 100644 --- a/orchard_key_components.py +++ b/orchard_key_components.py @@ -3,8 +3,9 @@ import sys; assert sys.version_info[0] >= 3, "Python 3 required." from pyblake2 import blake2b, blake2s -from orchard_generators import SPENDING_KEY_BASE, group_hash +from orchard_generators import NULLIFIER_K_BASE, SPENDING_KEY_BASE, group_hash from orchard_pallas import Fp, Scalar, Point +from orchard_poseidon_hash import poseidon_hash from orchard_merkle_tree import MERKLE_DEPTH from orchard_commitments import commit_ivk, note_commit from utils import leos2bsp, leos2ip, i2leosp @@ -25,7 +26,7 @@ def to_base(buf): # PRFs and hashes # -def prf_expand(sk, t): +def prf_expand(sk: bytes, t: bytes): digest = blake2b(person=b'Zcash_ExpandSeed') digest.update(sk) digest.update(t) @@ -37,6 +38,14 @@ def diversify_hash(d): P = group_hash(b'z.cash:Orchard-gd', b'') return P +def prf_nf_orchard(nk, rho): + return poseidon_hash(nk, rho) + +def derive_nullifier(nk, rho: Fp, psi: Fp, cm): + scalar = to_base(prf_nf_orchard(nk, rho)) + psi # addition mod p + point = NULLIFIER_K_BASE * to_scalar(scalar) + cm + return point.extract() + # # Key components # @@ -101,7 +110,7 @@ def main(): note_v, note_rho, note_psi) - note_nf = b"0"*32 #note_nullifier(fvk.nk(), note_cm) + note_nf = derive_nullifier(fvk.nk, note_rho, note_psi, note_cm) test_vectors.append({ 'sk': sk.data, 'ask': bytes(sk.ask), @@ -115,7 +124,7 @@ def main(): 'note_v': note_v, 'note_r': bytes(note_r), 'note_cmx': bytes(note_cm.extract()), - 'note_nf': note_nf, + 'note_nf': bytes(note_nf), }) render_tv( From d5a89273167e36177b65c2983a1539b3655cc60a Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sun, 9 May 2021 13:36:20 +0800 Subject: [PATCH 3/7] Remove unnecessary to_base() Co-authored-by: Daira Hopwood --- orchard_key_components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchard_key_components.py b/orchard_key_components.py index 65e2650..9ce6ba0 100644 --- a/orchard_key_components.py +++ b/orchard_key_components.py @@ -42,7 +42,7 @@ def prf_nf_orchard(nk, rho): return poseidon_hash(nk, rho) def derive_nullifier(nk, rho: Fp, psi: Fp, cm): - scalar = to_base(prf_nf_orchard(nk, rho)) + psi # addition mod p + scalar = prf_nf_orchard(nk, rho) + psi # addition mod p point = NULLIFIER_K_BASE * to_scalar(scalar) + cm return point.extract() From 04587ac9baab907c77ae1829f37be22cc683884c Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sun, 9 May 2021 13:39:27 +0800 Subject: [PATCH 4/7] Fix Scalar typecasting Co-authored-by: Daira Hopwood --- orchard_key_components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchard_key_components.py b/orchard_key_components.py index 9ce6ba0..1952f24 100644 --- a/orchard_key_components.py +++ b/orchard_key_components.py @@ -43,7 +43,7 @@ def prf_nf_orchard(nk, rho): def derive_nullifier(nk, rho: Fp, psi: Fp, cm): scalar = prf_nf_orchard(nk, rho) + psi # addition mod p - point = NULLIFIER_K_BASE * to_scalar(scalar) + cm + point = NULLIFIER_K_BASE * Scalar(scalar.s) + cm return point.extract() # From f1342994dd840853ec74eca87139543254a17162 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 11 May 2021 20:08:18 +0800 Subject: [PATCH 5/7] Case ivk as Scalar in default_pkd() --- orchard_key_components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchard_key_components.py b/orchard_key_components.py index 1952f24..1633c3d 100644 --- a/orchard_key_components.py +++ b/orchard_key_components.py @@ -87,7 +87,7 @@ class FullViewingKey(object): return i2leosp(88, 1337) def default_pkd(self): - return diversify_hash(self.default_d()) * self.ivk() + return diversify_hash(self.default_d()) * Scalar(self.ivk().s) def main(): From 646ff1577738c174dc352b4ecf5244913fa5e1b2 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 26 May 2021 13:29:59 +0800 Subject: [PATCH 6/7] Use ff1 for default diversifier --- orchard_key_components.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/orchard_key_components.py b/orchard_key_components.py index 1633c3d..c8943f4 100644 --- a/orchard_key_components.py +++ b/orchard_key_components.py @@ -1,14 +1,13 @@ #!/usr/bin/env python3 import sys; assert sys.version_info[0] >= 3, "Python 3 required." -from pyblake2 import blake2b, blake2s +from ff1 import ff1_aes256_encrypt from orchard_generators import NULLIFIER_K_BASE, SPENDING_KEY_BASE, group_hash from orchard_pallas import Fp, Scalar, Point from orchard_poseidon_hash import poseidon_hash -from orchard_merkle_tree import MERKLE_DEPTH from orchard_commitments import commit_ivk, note_commit -from utils import leos2bsp, leos2ip, i2leosp +from utils import leos2bsp, leos2ip, i2leosp, i2lebsp, lebs2osp from tv_output import render_args, render_tv # @@ -84,7 +83,8 @@ class FullViewingKey(object): return prf_expand(self.data, b'\x02')[:32] def default_d(self): - return i2leosp(88, 1337) + index = i2lebsp(88, 0) + return lebs2osp(ff1_aes256_encrypt(self.dk, b'', index)) def default_pkd(self): return diversify_hash(self.default_d()) * Scalar(self.ivk().s) From e50da335f595f3d30fbb394419debf57611e07db Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 26 May 2021 13:30:52 +0800 Subject: [PATCH 7/7] Use randomness in test and address review comments --- orchard_key_components.py | 45 +++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/orchard_key_components.py b/orchard_key_components.py index c8943f4..36c10f6 100644 --- a/orchard_key_components.py +++ b/orchard_key_components.py @@ -2,6 +2,7 @@ import sys; assert sys.version_info[0] >= 3, "Python 3 required." from ff1 import ff1_aes256_encrypt +from sapling_key_components import prf_expand from orchard_generators import NULLIFIER_K_BASE, SPENDING_KEY_BASE, group_hash from orchard_pallas import Fp, Scalar, Point @@ -25,12 +26,6 @@ def to_base(buf): # PRFs and hashes # -def prf_expand(sk: bytes, t: bytes): - digest = blake2b(person=b'Zcash_ExpandSeed') - digest.update(sk) - digest.update(t) - return digest.digest() - def diversify_hash(d): P = group_hash(b'z.cash:Orchard-gd', d) if P == Point.identity(): @@ -64,8 +59,7 @@ class SpendingKey: self.ask = -self.ask self.ak = self.akP.extract() - if commit_ivk(self.rivk, self.ak, self.nk) is None: - raise ValueError("invalid spending key") + assert commit_ivk(self.rivk, self.ak, self.nk) is not None class FullViewingKey(object): @@ -79,33 +73,42 @@ class FullViewingKey(object): def ivk(self): return commit_ivk(self.rivk, self.ak, self.nk) - def ovk(self): - return prf_expand(self.data, b'\x02')[:32] - def default_d(self): index = i2lebsp(88, 0) return lebs2osp(ff1_aes256_encrypt(self.dk, b'', index)) + def default_gd(self): + return diversify_hash(self.default_d()) + def default_pkd(self): - return diversify_hash(self.default_d()) * Scalar(self.ivk().s) + return self.default_gd() * Scalar(self.ivk().s) def main(): args = render_args() + from random import Random + from tv_rand import Rand + + 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 i in range(0, 10): - sys.stdout.write(".") - sys.stdout.flush() - sk = SpendingKey(bytes([i] * 32)) + for _ in range(0, 10): + sk = SpendingKey(rand.b(32)) fvk = FullViewingKey(sk) - note_v = (2548793025584392057432895043257984320*i) % 2**64 - note_r = Scalar(8890123457840276890326754358439057438290574382905).exp(i+1) - note_rho = Fp(342358729643275392567239275209835729829*i) - note_psi = Fp(432592604358294371936572103719358723958*i) + note_v = rand.u64() + note_r = Scalar.random(rand) + note_rho = Fp.random(rand) + note_psi = Fp.random(rand) note_cm = note_commit( note_r, - leos2bsp(bytes(diversify_hash(fvk.default_d()))), + leos2bsp(bytes(fvk.default_gd())), leos2bsp(bytes(fvk.default_pkd())), note_v, note_rho,