Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various cleanups #91

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@
Code to generate test vectors for various parts of Zcash.

The generated test vectors are checked into the repository:
- `test-vectors/json/`: JSON format.
- `test-vectors/rust/`: Rust format, suitable for copying into a Rust library or

- `test-vectors/json/*.json`: JSON format.
- `test-vectors/rust/*.rs`: Rust format, suitable for copying into a Rust library or
application to use from `#[cfg(test)]` code.
- `test-vectors/zcash/`: Bitcoin-flavoured JSON format (where 256-bit values are
- `test-vectors/zcash/*.json`: Bitcoin-flavoured JSON format (where 256-bit values are
encoded as byte-reversed hex strings), for use in `zcashd` unit tests.

To generate the test vectors yourself (for example, to generate a larger set
after adjusting:

- Install [`poetry`](https://python-poetry.org/).
- `poetry install`
- `./regenerate.sh <opt_output_types>`
- `<opt_output_types>` defaults to `rust json zcash`.

To generate an individual output file:

- `poetry run SCRIPT_NAME [-t json|rust|zcash]`
- `SCRIPT_NAME` is one of the scripts listed in `pyproject.toml`.

To run unit tests, use ``poetry run test``.

## License

Licensed under either of
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@ orchard_note_encryption = "zcash_test_vectors.orchard.note_encryption:main"
orchard_poseidon = "zcash_test_vectors.orchard.poseidon:main"
orchard_poseidon_hash = "zcash_test_vectors.orchard.poseidon:hash_test_vectors"
orchard_sinsemilla = "zcash_test_vectors.orchard.sinsemilla:main"

test = "test:test"
14 changes: 12 additions & 2 deletions regenerate.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env bash

poetry install -q

tv_scripts=(
bip_0032
f4jumble
Expand Down Expand Up @@ -27,8 +29,16 @@ tv_scripts=(
zip_0244
zip_0316)

formats="${*:-rust json zcash}"
daira marked this conversation as resolved.
Show resolved Hide resolved

for generator in "${tv_scripts[@]}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we're fixing this, something that I frequently find myself doing is just regenerating a single set of test vectors, instead of regenerating them all. Now that we no longer use $2, maybe if that's present we could use that argument as the sole value in tv_scripts instead of regenerating everything?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach of using poetry run SCRIPT_NAME directly doesn't put the files in the expected place by itself.

Copy link
Contributor Author

@daira daira Sep 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The (corrected) script takes a list of formats, so there would be a conflict unless it implemented more complicated argument parsing. You can always write poetry run SCRIPT_NAME -t FORMAT >OUTPUT_FILE, although I realize that's less convenient.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. Hm. I guess I'll just have to keep commenting out bits of this script when I need to regenerate stuff.

do
echo "# $generator"
poetry run $generator -t $1 >test-vectors/$1/$generator.$2
for format in $formats
do
filetype="${format/rust/rs}"
filetype="${filetype/zcash/json}"
output_file="test-vectors/$format/$generator.$filetype"
echo $output_file
poetry run $generator -t $format >$output_file
done
done
11 changes: 11 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright (c) 2022 The Zcash developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .

import unittest

def test():
loader = unittest.TestLoader()
suite = loader.discover('.', pattern='*.py')
runner = unittest.TextTestRunner()
runner.run(suite)
4 changes: 2 additions & 2 deletions orchard_vesta.py → zcash_test_vectors/orchard/vesta.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# -*- coding: utf8 -*-
import sys; assert sys.version_info[0] >= 3, "Python 3 required."

from sapling_jubjub import FieldElement
from utils import leos2ip
from ..sapling.jubjub import FieldElement
from ..utils import leos2ip

q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001
p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001
Expand Down
130 changes: 130 additions & 0 deletions zcash_test_vectors/ripemd160.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Copyright (c) 2021 Pieter Wuille
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
"""Test-only pure Python RIPEMD160 implementation."""

import unittest

# Message schedule indexes for the left path.
ML = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13
]

# Message schedule indexes for the right path.
MR = [
5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11
]

# Rotation counts for the left path.
RL = [
11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6
]

# Rotation counts for the right path.
RR = [
8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11
]

# K constants for the left path.
KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e]

# K constants for the right path.
KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0]


def fi(x, y, z, i):
"""The f1, f2, f3, f4, and f5 functions from the specification."""
if i == 0:
return x ^ y ^ z
elif i == 1:
return (x & y) | (~x & z)
elif i == 2:
return (x | ~y) ^ z
elif i == 3:
return (x & z) | (y & ~z)
elif i == 4:
return x ^ (y | ~z)
else:
assert False


def rol(x, i):
"""Rotate the bottom 32 bits of x left by i bits."""
return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff


def compress(h0, h1, h2, h3, h4, block):
"""Compress state (h0, h1, h2, h3, h4) with block."""
# Left path variables.
al, bl, cl, dl, el = h0, h1, h2, h3, h4
# Right path variables.
ar, br, cr, dr, er = h0, h1, h2, h3, h4
# Message variables.
x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)]

# Iterate over the 80 rounds of the compression.
for j in range(80):
rnd = j >> 4
# Perform left side of the transformation.
al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el
al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl
# Perform right side of the transformation.
ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er
ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr

# Compose old state, left transform, and right transform into new state.
return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr


def ripemd160(data):
"""Compute the RIPEMD-160 hash of data."""
# Initialize state.
state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0)
# Process full 64-byte blocks in the input.
for b in range(len(data) >> 6):
state = compress(*state, data[64*b:64*(b+1)])
# Construct final blocks (with padding and size).
pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63)
fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little')
# Process final blocks.
for b in range(len(fin) >> 6):
state = compress(*state, fin[64*b:64*(b+1)])
# Produce output.
return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state)


class TestFrameworkKey(unittest.TestCase):
def test_ripemd160(self):
"""RIPEMD-160 test vectors."""
# See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
for msg, hexout in [
(b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"),
(b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"),
(b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"),
(b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"),
(b"abcdefghijklmnopqrstuvwxyz",
"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"),
(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"12a053384a9c0c88e405a06c27dcf49ada62eb2b"),
(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
"b0e20b6e3116640286ed3a87a5713079b21f5189"),
(b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"),
(b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528")
]:
self.assertEqual(ripemd160(msg).hex(), hexout)
5 changes: 2 additions & 3 deletions zcash_test_vectors/transparent/bip_0032.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from ..hd_common import ZCASH_MAIN_COINTYPE, hardened
from ..output import render_args, render_tv
from ..ripemd160 import ripemd160
from ..utils import i2beosp


Expand Down Expand Up @@ -76,9 +77,7 @@ def __bytes__(self):
return self.chaincode + self.pubkey_bytes()

def address(self):
ripemd160 = hashlib.new('ripemd160')
ripemd160.update(hashlib.sha256(self.pubkey_bytes()).digest())
return ripemd160.digest()
return ripemd160(hashlib.sha256(self.pubkey_bytes()).digest())

def child(self, i):
assert 0 <= i and i <= 0xFFFFFFFF
Expand Down