From ff8eb510c7a5a27c8e576bd86747fffa103fec79 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 2 Nov 2024 04:57:07 +0000 Subject: [PATCH 1/2] Extract ZIP 32 hardened-only key derivation from Orchard --- zcash_test_vectors/orchard/key_components.py | 21 +++++-------- zcash_test_vectors/zip_0032.py | 33 ++++++++++++++++++++ 2 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 zcash_test_vectors/zip_0032.py diff --git a/zcash_test_vectors/orchard/key_components.py b/zcash_test_vectors/orchard/key_components.py index 3a7f864..3d934d3 100755 --- a/zcash_test_vectors/orchard/key_components.py +++ b/zcash_test_vectors/orchard/key_components.py @@ -1,10 +1,9 @@ #!/usr/bin/env python3 import sys; assert sys.version_info[0] >= 3, "Python 3 required." -from hashlib import blake2b - from ..ff1 import ff1_aes256_encrypt from ..sapling.key_components import prf_expand +from ..zip_0032 import CKDh, HardenedOnlyContext, MKGh from .generators import NULLIFIER_K_BASE, SPENDING_KEY_BASE, group_hash from .pallas import Fp, Scalar, Point @@ -55,26 +54,20 @@ def __init__(self, data): class ExtendedSpendingKey(SpendingKey): + Orchard = HardenedOnlyContext(b'ZcashIP32Orchard', b'\x81') + def __init__(self, chaincode, data): SpendingKey.__init__(self, data) self.chaincode = chaincode @classmethod def master(cls, S): - digest = blake2b(person=b'ZcashIP32Orchard') - digest.update(S) - I = digest.digest() - I_L = I[:32] - I_R = I[32:] - return cls(I_R, I_L) + (sk, chaincode) = MKGh(cls.Orchard, S) + return cls(chaincode, sk) def child(self, i): - assert 0x80000000 <= i and i <= 0xFFFFFFFF - - I = prf_expand(self.chaincode, b'\x81' + self.data + i2leosp(32, i)) - I_L = I[:32] - I_R = I[32:] - return self.__class__(I_R, I_L) + (sk_i, c_i) = CKDh(self.Orchard, self.data, self.chaincode, i) + return self.__class__(c_i, sk_i) class FullViewingKey(object): diff --git a/zcash_test_vectors/zip_0032.py b/zcash_test_vectors/zip_0032.py new file mode 100644 index 0000000..3466fb0 --- /dev/null +++ b/zcash_test_vectors/zip_0032.py @@ -0,0 +1,33 @@ +from hashlib import blake2b + +from .sapling.key_components import prf_expand +from .utils import i2leosp + +class HardenedOnlyContext(object): + def __init__(self, MKGDomain, CKDDomain): + assert type(MKGDomain) == bytes + assert len(MKGDomain) == 16 + assert type(CKDDomain) == bytes + assert len(CKDDomain) == 1 + + self.MKGDomain = MKGDomain + self.CKDDomain = CKDDomain + +def MKGh(Context, IKM): + assert type(Context) == HardenedOnlyContext + + digest = blake2b(person=Context.MKGDomain) + digest.update(IKM) + I = digest.digest() + I_L = I[:32] + I_R = I[32:] + return (I_L, I_R) + +def CKDh(Context, sk_par, c_par, i): + assert type(Context) == HardenedOnlyContext + assert 0x80000000 <= i and i <= 0xFFFFFFFF + + I = prf_expand(c_par, Context.CKDDomain + sk_par + i2leosp(32, i)) + I_L = I[:32] + I_R = I[32:] + return (I_L, I_R) From f80ca40016423e1e5432ff8e043da9154e84190b Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 2 Nov 2024 05:15:28 +0000 Subject: [PATCH 2/2] Add test vectors for ZIP 32 arbitrary key derivation --- pyproject.toml | 1 + regenerate.sh | 1 + test-vectors/json/zip_0032_arbitrary.json | 8 +++ test-vectors/rust/zip_0032_arbitrary.rs | 40 +++++++++++++++ test-vectors/zcash/zip_0032_arbitrary.json | 8 +++ zcash_test_vectors/zip_0032.py | 59 ++++++++++++++++++++++ 6 files changed, 117 insertions(+) create mode 100644 test-vectors/json/zip_0032_arbitrary.json create mode 100644 test-vectors/rust/zip_0032_arbitrary.rs create mode 100644 test-vectors/zcash/zip_0032_arbitrary.json diff --git a/pyproject.toml b/pyproject.toml index bc787b5..a95114b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ f4jumble_long = "zcash_test_vectors.f4jumble:long_test_vectors" unified_address = "zcash_test_vectors.unified_address:main" unified_full_viewing_keys = "zcash_test_vectors.unified_full_viewing_keys:main" unified_incoming_viewing_keys = "zcash_test_vectors.unified_incoming_viewing_keys:main" +zip_0032_arbitrary = "zcash_test_vectors.zip_0032:arbitrary_key_derivation_tvs" zip_0143 = "zcash_test_vectors.zip_0143:main" zip_0243 = "zcash_test_vectors.zip_0243:main" zip_0244 = "zcash_test_vectors.zip_0244:main" diff --git a/regenerate.sh b/regenerate.sh index 0695b70..d9d0466 100755 --- a/regenerate.sh +++ b/regenerate.sh @@ -45,6 +45,7 @@ case "$2" in unified_address unified_full_viewing_keys unified_incoming_viewing_keys + zip_0032_arbitrary zip_0143 zip_0243 zip_0244 diff --git a/test-vectors/json/zip_0032_arbitrary.json b/test-vectors/json/zip_0032_arbitrary.json new file mode 100644 index 0000000..a6578df --- /dev/null +++ b/test-vectors/json/zip_0032_arbitrary.json @@ -0,0 +1,8 @@ +[ + ["From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/zip_0032_arbitrary.py"], + ["sk, c"], + ["e9da8806409dc3c3ebd1fc2a71c879c13dd7aa93ede803bf1a83414b9d3b158a", "65a748f2905f7a8aab9f3d02f1b26c3d65c82994ce59a086d4c651d8a81cec51"], + ["e8409aaa832cc2378f2badeb77150562153742fee876dcf4783a6ccd119da66a", "cc084922a0ead2da5338bd82200a1946bc8585b8d9ee416df6a09a71ab0e5b58"], + ["464f90a364cff805fee93a85b72f4894ce4e1358dcdc1e61a3d430301c60910e", "f9d2544a5528ae6bd9f036f42f9f05d83dff507aeb2a8141af11d9f167e221ae"], + ["fc4b6e93b0e42f7a762ca0c6522ccd1045cab506b372452af7306c87389ab62c", "e89bf2ed73f5e0887542e36793fac82c508ab5d99198578227b241fbac198429"] +] diff --git a/test-vectors/rust/zip_0032_arbitrary.rs b/test-vectors/rust/zip_0032_arbitrary.rs new file mode 100644 index 0000000..790424c --- /dev/null +++ b/test-vectors/rust/zip_0032_arbitrary.rs @@ -0,0 +1,40 @@ + struct TestVector { + sk: [u8; 32], + c: [u8; 32], + }; + + // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/zip_0032_arbitrary.py + let test_vectors = vec![ + TestVector { + sk: [ + 0xe9, 0xda, 0x88, 0x06, 0x40, 0x9d, 0xc3, 0xc3, 0xeb, 0xd1, 0xfc, 0x2a, 0x71, 0xc8, 0x79, 0xc1, 0x3d, 0xd7, 0xaa, 0x93, 0xed, 0xe8, 0x03, 0xbf, 0x1a, 0x83, 0x41, 0x4b, 0x9d, 0x3b, 0x15, 0x8a + ], + c: [ + 0x65, 0xa7, 0x48, 0xf2, 0x90, 0x5f, 0x7a, 0x8a, 0xab, 0x9f, 0x3d, 0x02, 0xf1, 0xb2, 0x6c, 0x3d, 0x65, 0xc8, 0x29, 0x94, 0xce, 0x59, 0xa0, 0x86, 0xd4, 0xc6, 0x51, 0xd8, 0xa8, 0x1c, 0xec, 0x51 + ], + }, + TestVector { + sk: [ + 0xe8, 0x40, 0x9a, 0xaa, 0x83, 0x2c, 0xc2, 0x37, 0x8f, 0x2b, 0xad, 0xeb, 0x77, 0x15, 0x05, 0x62, 0x15, 0x37, 0x42, 0xfe, 0xe8, 0x76, 0xdc, 0xf4, 0x78, 0x3a, 0x6c, 0xcd, 0x11, 0x9d, 0xa6, 0x6a + ], + c: [ + 0xcc, 0x08, 0x49, 0x22, 0xa0, 0xea, 0xd2, 0xda, 0x53, 0x38, 0xbd, 0x82, 0x20, 0x0a, 0x19, 0x46, 0xbc, 0x85, 0x85, 0xb8, 0xd9, 0xee, 0x41, 0x6d, 0xf6, 0xa0, 0x9a, 0x71, 0xab, 0x0e, 0x5b, 0x58 + ], + }, + TestVector { + sk: [ + 0x46, 0x4f, 0x90, 0xa3, 0x64, 0xcf, 0xf8, 0x05, 0xfe, 0xe9, 0x3a, 0x85, 0xb7, 0x2f, 0x48, 0x94, 0xce, 0x4e, 0x13, 0x58, 0xdc, 0xdc, 0x1e, 0x61, 0xa3, 0xd4, 0x30, 0x30, 0x1c, 0x60, 0x91, 0x0e + ], + c: [ + 0xf9, 0xd2, 0x54, 0x4a, 0x55, 0x28, 0xae, 0x6b, 0xd9, 0xf0, 0x36, 0xf4, 0x2f, 0x9f, 0x05, 0xd8, 0x3d, 0xff, 0x50, 0x7a, 0xeb, 0x2a, 0x81, 0x41, 0xaf, 0x11, 0xd9, 0xf1, 0x67, 0xe2, 0x21, 0xae + ], + }, + TestVector { + sk: [ + 0xfc, 0x4b, 0x6e, 0x93, 0xb0, 0xe4, 0x2f, 0x7a, 0x76, 0x2c, 0xa0, 0xc6, 0x52, 0x2c, 0xcd, 0x10, 0x45, 0xca, 0xb5, 0x06, 0xb3, 0x72, 0x45, 0x2a, 0xf7, 0x30, 0x6c, 0x87, 0x38, 0x9a, 0xb6, 0x2c + ], + c: [ + 0xe8, 0x9b, 0xf2, 0xed, 0x73, 0xf5, 0xe0, 0x88, 0x75, 0x42, 0xe3, 0x67, 0x93, 0xfa, 0xc8, 0x2c, 0x50, 0x8a, 0xb5, 0xd9, 0x91, 0x98, 0x57, 0x82, 0x27, 0xb2, 0x41, 0xfb, 0xac, 0x19, 0x84, 0x29 + ], + }, + ]; diff --git a/test-vectors/zcash/zip_0032_arbitrary.json b/test-vectors/zcash/zip_0032_arbitrary.json new file mode 100644 index 0000000..dd1c38e --- /dev/null +++ b/test-vectors/zcash/zip_0032_arbitrary.json @@ -0,0 +1,8 @@ +[ + ["From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/zip_0032_arbitrary.py"], + ["sk, c"], + ["8a153b9d4b41831abf03e8ed93aad73dc179c8712afcd1ebc3c39d400688dae9", "51ec1ca8d851c6d486a059ce9429c8653d6cb2f1023d9fab8a7a5f90f248a765"], + ["6aa69d11cd6c3a78f4dc76e8fe42371562051577ebad2b8f37c22c83aa9a40e8", "585b0eab719aa0f66d41eed9b88585bc46190a2082bd3853dad2eaa0224908cc"], + ["0e91601c3030d4a3611edcdc58134ece94482fb7853ae9fe05f8cf64a3904f46", "ae21e267f1d911af41812aeb7a50ff3dd8059f2ff436f0d96bae28554a54d2f9"], + ["2cb69a38876c30f72a4572b306b5ca4510cd2c52c6a02c767a2fe4b0936e4bfc", "298419acfb41b22782579891d9b58a502cc8fa9367e3427588e0f573edf29be8"] +] diff --git a/zcash_test_vectors/zip_0032.py b/zcash_test_vectors/zip_0032.py index 3466fb0..1f6d250 100644 --- a/zcash_test_vectors/zip_0032.py +++ b/zcash_test_vectors/zip_0032.py @@ -3,6 +3,9 @@ from .sapling.key_components import prf_expand from .utils import i2leosp +from .hd_common import hardened +from .output import render_args, render_tv + class HardenedOnlyContext(object): def __init__(self, MKGDomain, CKDDomain): assert type(MKGDomain) == bytes @@ -31,3 +34,59 @@ def CKDh(Context, sk_par, c_par, i): I_L = I[:32] I_R = I[32:] return (I_L, I_R) + +class ArbitraryKey(object): + Arbitrary = HardenedOnlyContext(b'ZcashArbitraryKD', b'\xAB') + + def __init__(self, sk, chaincode): + self.sk = sk + self.chaincode = chaincode + + @classmethod + def master(cls, ContextString, S): + length_ContextString = len(ContextString) + length_S = len(S) + + assert length_ContextString <= 252 + assert 32 <= length_S <= 252 + + (sk, chaincode) = MKGh( + cls.Arbitrary, + bytes([length_ContextString]) + ContextString + bytes([length_S]) + S, + ) + return cls(sk, chaincode) + + def child(self, i): + (sk_i, c_i) = CKDh(self.Arbitrary, self.sk, self.chaincode, i) + return self.__class__(sk_i, c_i) + + +def arbitrary_key_derivation_tvs(): + args = render_args() + + context_string = b'Zcash test vectors' + seed = bytes(range(32)) + m = ArbitraryKey.master(context_string, seed) + m_1h = m.child(hardened(1)) + m_1h_2h = m_1h.child(hardened(2)) + m_1h_2h_3h = m_1h_2h.child(hardened(3)) + + keys = [m, m_1h, m_1h_2h, m_1h_2h_3h] + + test_vectors = [ + { + 'sk' : k.sk, + 'c' : k.chaincode + } + for k in keys + ] + + render_tv( + args, + 'zip_0032_arbitrary', + ( + ('sk', '[u8; 32]'), + ('c', '[u8; 32]'), + ), + test_vectors, + )