diff --git a/ff1.py b/ff1.py index 9524ea6..cf99992 100644 --- a/ff1.py +++ b/ff1.py @@ -7,7 +7,7 @@ from binascii import unhexlify, hexlify from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend -from sapling_utils import bebs2ip, i2bebsp, beos2ip, bebs2osp, cldiv +from utils import bebs2ip, i2bebsp, beos2ip, bebs2osp, cldiv # Morris Dworkin # NIST Special Publication 800-38G diff --git a/orchard_commitments.py b/orchard_commitments.py new file mode 100644 index 0000000..8fcde60 --- /dev/null +++ b/orchard_commitments.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +import sys; assert sys.version_info[0] >= 3, "Python 3 required." + +from orchard_group_hash import group_hash +from orchard_pallas import Fp, Scalar +from orchard_sinsemilla import sinsemilla_hash_to_point +from utils import i2lebsp + +# Commitment schemes used in Orchard https://zips.z.cash/protocol/nu5.pdf#concretecommit + +# https://zips.z.cash/protocol/nu5.pdf#constants +L_ORCHARD_BASE = 255 + +# https://zips.z.cash/protocol/nu5.pdf#concretehomomorphiccommit +def homomorphic_pedersen_commitment(rcv: Scalar, D, v: Scalar): + return group_hash(D, b"v") * v + group_hash(D, b"r") * rcv + +def value_commit(rcv: Scalar, v: Scalar): + return homomorphic_pedersen_commitment(rcv, b"z.cash:Orchard-cv", v) + +def rcv_trapdoor(rand): + return Scalar.random(rand) + +# https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit +def sinsemilla_commit(r: Scalar, D, M): + assert isinstance(r, Scalar) + return sinsemilla_hash_to_point(D + b"-M", M).checked_incomplete_add( + group_hash(D + b"-r", b"") * r + ) + +def sinsemilla_short_commit(r: Scalar, D, M): + return sinsemilla_commit(r, D, M).extract() + +# https://zips.z.cash/protocol/nu5.pdf#concreteorchardnotecommit +def note_commit(rcm, g_d, pk_d, v, rho, psi): + return sinsemilla_commit( + rcm, + b"z.cash:Orchard-NoteCommit", + g_d + pk_d + i2lebsp(64, v) + i2lebsp(L_ORCHARD_BASE, rho.s) + i2lebsp(L_ORCHARD_BASE, psi.s) + ) + +def rcm_trapdoor(rand): + return Scalar.random(rand) + +# https://zips.z.cash/protocol/nu5.pdf#concreteorchardnotecommit +def commit_ivk(rivk: Scalar, ak: Fp, nk: Fp): + return sinsemilla_short_commit( + rivk, + b"z.cash:Orchard-CommitIvk", + i2lebsp(L_ORCHARD_BASE, ak.s) + i2lebsp(L_ORCHARD_BASE, nk.s) + ) + +def rivk_trapdoor(rand): + return Scalar.random(rand) + +# Test consistency of ValueCommit^{Orchard} with precomputed generators +def test_value_commit(): + from random import Random + from tv_rand import Rand + from orchard_generators import VALUE_COMMITMENT_RANDOMNESS_BASE, VALUE_COMMITMENT_VALUE_BASE + + rng = Random(0xabad533d) + def randbytes(l): + ret = [] + while len(ret) < l: + ret.append(rng.randrange(0, 256)) + return bytes(ret) + rand = Rand(randbytes) + + rcv = rcv_trapdoor(rand) + v = Scalar(100000000) + + assert value_commit(rcv, v) == VALUE_COMMITMENT_RANDOMNESS_BASE * rcv + VALUE_COMMITMENT_VALUE_BASE * v + +if __name__ == '__main__': + test_value_commit() diff --git a/orchard_generators.py b/orchard_generators.py new file mode 100644 index 0000000..dff66e7 --- /dev/null +++ b/orchard_generators.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +import sys; assert sys.version_info[0] >= 3, "Python 3 required." + +from pyblake2 import blake2s + +from tv_output import render_args, render_tv +from orchard_group_hash import group_hash +from orchard_sinsemilla import sinsemilla_hash_to_point + +# https://zips.z.cash/protocol/nu5.pdf#concretespendauthsig +SPENDING_KEY_BASE = group_hash(b'z.cash:Orchard', b'G') + +# https://zips.z.cash/protocol/nu5.pdf#commitmentsandnullifiers +NULLIFIER_K_BASE = group_hash(b'z.cash:Orchard', b'K') + +# https://zips.z.cash/protocol/nu5.pdf#concretehomomorphiccommit +VALUE_COMMITMENT_VALUE_BASE = group_hash(b'z.cash:Orchard-cv', b'v') +VALUE_COMMITMENT_RANDOMNESS_BASE = group_hash(b'z.cash:Orchard-cv', b'r') + +# Used in SinsemillaCommit (https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit) +NOTE_COMMITMENT_BASE = group_hash(b'z.cash:Orchard-NoteCommit-r', b'') +NOTE_COMMITMENT_Q = group_hash(b'z.cash:SinsemillaQ', b'z.cash:Orchard-NoteCommit-M') + +# Used in SinsemillaShortCommit (https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit) +IVK_COMMITMENT_BASE = group_hash(b'z.cash:Orchard-CommitIvk-r', b'') +IVK_COMMITMENT_Q = group_hash(b'z.cash:SinsemillaQ', b'z.cash:Orchard-CommitIvk-M') + +# Used in SinsemillaHash (https://zips.z.cash/protocol/nu5.pdf#orchardmerklecrh) +MERKLE_CRH_Q = group_hash(b'z.cash:SinsemillaQ', b'z.cash:Orchard-MerkleCRH') + +def main(): + render_tv( + render_args(), + 'orchard_generators', + ( + ('skb', '[u8; 32]'), + ('nkb', '[u8; 32]'), + ('vcvb', '[u8; 32]'), + ('vcrb', '[u8; 32]'), + ('cmb', '[u8; 32]'), + ('cmq', '[u8; 32]'), + ('ivkb', '[u8; 32]'), + ('ivkq', '[u8; 32]'), + ('mcq', '[u8; 32]'), + ), + { + 'skb': bytes(SPENDING_KEY_BASE), + 'nkb': bytes(NULLIFIER_K_BASE), + 'vcvb': bytes(VALUE_COMMITMENT_VALUE_BASE), + 'vcrb': bytes(VALUE_COMMITMENT_RANDOMNESS_BASE), + 'cmb': bytes(NOTE_COMMITMENT_BASE), + 'cmq': bytes(NOTE_COMMITMENT_Q), + 'ivkb': bytes(IVK_COMMITMENT_BASE), + 'ivkq': bytes(IVK_COMMITMENT_Q), + 'mcq': bytes(MERKLE_CRH_Q), + }, + ) + + +if __name__ == '__main__': + main() diff --git a/orchard_group_hash.py b/orchard_group_hash.py index 5ef0c9e..6202f27 100644 --- a/orchard_group_hash.py +++ b/orchard_group_hash.py @@ -8,7 +8,7 @@ import orchard_iso_pallas from pyblake2 import blake2b from orchard_pallas import Fp, p, q, PALLAS_B, Point from orchard_iso_pallas import PALLAS_ISO_B, PALLAS_ISO_A -from sapling_utils import i2beosp, cldiv, beos2ip, i2leosp, lebs2ip +from utils import i2beosp, cldiv, beos2ip, i2leosp, lebs2ip from tv_output import render_args, render_tv from tv_rand import Rand @@ -16,7 +16,9 @@ from tv_rand import Rand def sxor(s1,s2): return bytes([a ^ b for a,b in zip(s1,s2)]) -def expand_message_xmd(msg, dst, len_in_bytes): +def expand_message_xmd(msg: bytes, dst: bytes, len_in_bytes: int): + assert isinstance(msg, bytes) + assert isinstance(dst, bytes) assert len(dst) <= 255 b_in_bytes = 64 # hash function output size diff --git a/orchard_map_to_curve.py b/orchard_map_to_curve.py index 4d43dd1..840ed9f 100755 --- a/orchard_map_to_curve.py +++ b/orchard_map_to_curve.py @@ -3,7 +3,7 @@ from orchard_group_hash import map_to_curve_simple_swu from orchard_iso_pallas import Point as IsoPoint from orchard_pallas import Fp -from sapling_utils import leos2ip +from utils import leos2ip from tv_output import render_args, render_tv from tv_rand import Rand diff --git a/orchard_merkle_tree.py b/orchard_merkle_tree.py index 2cd8fe0..ac3b5fb 100644 --- a/orchard_merkle_tree.py +++ b/orchard_merkle_tree.py @@ -6,7 +6,7 @@ from binascii import unhexlify from orchard_pallas import Fp from orchard_sinsemilla import sinsemilla_hash -from sapling_utils import i2lebsp, leos2bsp +from utils import i2lebsp, leos2bsp # https://zips.z.cash/protocol/nu5.pdf#constants MERKLE_DEPTH = 32 diff --git a/orchard_pallas.py b/orchard_pallas.py index 29d5ad3..0ed16e2 100644 --- a/orchard_pallas.py +++ b/orchard_pallas.py @@ -3,7 +3,7 @@ import sys; assert sys.version_info[0] >= 3, "Python 3 required." from sapling_jubjub import FieldElement -from sapling_utils import leos2ip +from utils import leos2ip p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 @@ -31,6 +31,13 @@ class Fp(FieldElement): def from_bytes(buf): return Fp(leos2ip(buf), strict=True) + def random(rand): + while True: + try: + return Fp(leos2ip(rand.b(32)), strict=True) + except ValueError: + pass + def __init__(self, s, strict=False): FieldElement.__init__(self, Fp, s, p, strict=strict) @@ -90,16 +97,29 @@ class Scalar(FieldElement): def __str__(self): return 'Scalar(%s)' % self.s -Fp.ZERO = Fp(0) -Fp.ONE = Fp(1) -Fp.MINUS_ONE = Fp(-1) + @staticmethod + def from_bytes(buf): + return Scalar(leos2ip(buf), strict=True) -assert Fp.ZERO + Fp.ZERO == Fp.ZERO -assert Fp.ZERO + Fp.ONE == Fp.ONE -assert Fp.ONE + Fp.ZERO == Fp.ONE -assert Fp.ZERO - Fp.ONE == Fp.MINUS_ONE -assert Fp.ZERO * Fp.ONE == Fp.ZERO -assert Fp.ONE * Fp.ZERO == Fp.ZERO + def random(rand): + while True: + try: + return Scalar(leos2ip(rand.b(32)), strict=True) + except ValueError: + pass + + +for F in (Fp, Scalar): + F.ZERO = F(0) + F.ONE = F(1) + F.MINUS_ONE = F(-1) + + assert F.ZERO + F.ZERO == F.ZERO + assert F.ZERO + F.ONE == F.ONE + assert F.ONE + F.ZERO == F.ONE + assert F.ZERO - F.ONE == F.MINUS_ONE + assert F.ZERO * F.ONE == F.ZERO + assert F.ONE * F.ZERO == F.ZERO # @@ -209,6 +229,7 @@ class Point(object): return self.x def __mul__(self, s): + assert isinstance(s, Scalar) s = format(s.s, '0256b') ret = self.ZERO for c in s: diff --git a/orchard_poseidon.py b/orchard_poseidon.py index c091f97..a9ed9ba 100644 --- a/orchard_poseidon.py +++ b/orchard_poseidon.py @@ -1,6 +1,6 @@ from orchard_pallas import Fp import numpy as np -from sapling_utils import leos2ip +from utils import leos2ip from tv_output import render_args, render_tv from tv_rand import Rand diff --git a/orchard_poseidon_hash.py b/orchard_poseidon_hash.py index e415632..fc238d1 100644 --- a/orchard_poseidon_hash.py +++ b/orchard_poseidon_hash.py @@ -1,6 +1,6 @@ from orchard_pallas import Fp from orchard_poseidon import perm -from sapling_utils import leos2ip +from utils import leos2ip from tv_output import render_args, render_tv from tv_rand import Rand diff --git a/orchard_sinsemilla.py b/orchard_sinsemilla.py index 2662272..920be76 100755 --- a/orchard_sinsemilla.py +++ b/orchard_sinsemilla.py @@ -6,7 +6,7 @@ import math import orchard_iso_pallas from orchard_pallas import Fp, Point -from sapling_utils import cldiv, lebs2ip, i2leosp +from utils import cldiv, lebs2ip, i2leosp from orchard_group_hash import group_hash from tv_output import render_args, render_tv from tv_rand import Rand diff --git a/sapling_generators.py b/sapling_generators.py index 3b22097..e4af823 100644 --- a/sapling_generators.py +++ b/sapling_generators.py @@ -5,7 +5,7 @@ from pyblake2 import blake2s from sapling_jubjub import Point, JUBJUB_COFACTOR from tv_output import render_args, render_tv -from sapling_utils import i2leosp +from utils import i2leosp # First 64 bytes of the BLAKE2s input during group hash. # This is chosen to be some random string that we couldn't have diff --git a/sapling_jubjub.py b/sapling_jubjub.py index db4442f..7a0a40f 100644 --- a/sapling_jubjub.py +++ b/sapling_jubjub.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import sys; assert sys.version_info[0] >= 3, "Python 3 required." -from sapling_utils import i2lebsp, leos2ip, i2leosp +from utils import i2lebsp, leos2ip, i2leosp q_j = 52435875175126190479447740508185965837690552500527637822603658699938581184513 r_j = 6554484396890773809930967563523245729705921265872317281365359162392183254199 diff --git a/sapling_key_components.py b/sapling_key_components.py index 75cce51..e23eef4 100644 --- a/sapling_key_components.py +++ b/sapling_key_components.py @@ -7,7 +7,7 @@ from sapling_generators import PROVING_KEY_BASE, SPENDING_KEY_BASE, group_hash from sapling_jubjub import Fr from sapling_merkle_tree import MERKLE_DEPTH from sapling_notes import note_commit, note_nullifier -from sapling_utils import leos2bsp, leos2ip +from utils import leos2bsp, leos2ip from tv_output import render_args, render_tv # diff --git a/sapling_merkle_tree.py b/sapling_merkle_tree.py index 6d7b7f7..7cd047b 100644 --- a/sapling_merkle_tree.py +++ b/sapling_merkle_tree.py @@ -4,7 +4,7 @@ import sys; assert sys.version_info[0] >= 3, "Python 3 required." from binascii import unhexlify from sapling_pedersen import pedersen_hash -from sapling_utils import i2lebsp, leos2bsp +from utils import i2lebsp, leos2bsp MERKLE_DEPTH = 32 diff --git a/sapling_note_encryption.py b/sapling_note_encryption.py index 736b652..d6a522d 100644 --- a/sapling_note_encryption.py +++ b/sapling_note_encryption.py @@ -10,7 +10,7 @@ from sapling_generators import VALUE_COMMITMENT_VALUE_BASE, VALUE_COMMITMENT_RAN from sapling_jubjub import Fr, JUBJUB_COFACTOR from sapling_key_components import SpendingKey, diversify_hash from sapling_notes import note_commit -from sapling_utils import leos2bsp, leos2ip +from utils import leos2bsp, leos2ip from tv_output import render_args, render_tv diff --git a/sapling_notes.py b/sapling_notes.py index aa27258..c4e0dc2 100644 --- a/sapling_notes.py +++ b/sapling_notes.py @@ -7,7 +7,7 @@ from sapling_pedersen import ( mixing_pedersen_hash, windowed_pedersen_commitment, ) -from sapling_utils import i2lebsp +from utils import i2lebsp def note_commit(rcm, g_d, pk_d, v): return windowed_pedersen_commitment(rcm, [1] * 6 + i2lebsp(64, v) + g_d + pk_d) diff --git a/sapling_pedersen.py b/sapling_pedersen.py index f782efe..649ed46 100644 --- a/sapling_pedersen.py +++ b/sapling_pedersen.py @@ -7,7 +7,7 @@ from sapling_generators import ( WINDOWED_PEDERSEN_RANDOMNESS_BASE, ) from sapling_jubjub import Fr, Point -from sapling_utils import cldiv, i2leosp +from utils import cldiv, i2leosp # diff --git a/sapling_signatures.py b/sapling_signatures.py index f774935..f2bc335 100644 --- a/sapling_signatures.py +++ b/sapling_signatures.py @@ -7,7 +7,7 @@ from pyblake2 import blake2b from sapling_generators import SPENDING_KEY_BASE from sapling_jubjub import Fr, Point, r_j from sapling_key_components import to_scalar -from sapling_utils import cldiv, leos2ip +from utils import cldiv, leos2ip from tv_output import render_args, render_tv diff --git a/sapling_zip32.py b/sapling_zip32.py index 8fcf4d7..63beb94 100644 --- a/sapling_zip32.py +++ b/sapling_zip32.py @@ -5,7 +5,7 @@ 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 sapling_utils import i2leosp, i2lebsp, lebs2osp +from utils import i2leosp, i2lebsp, lebs2osp from ff1 import ff1_aes256_encrypt from tv_output import render_args, render_tv, option, Some diff --git a/transaction.py b/transaction.py index aa3ca64..19aba90 100644 --- a/transaction.py +++ b/transaction.py @@ -3,7 +3,7 @@ import struct from sapling_generators import find_group_hash, SPENDING_KEY_BASE from sapling_jubjub import Fq, Point -from sapling_utils import leos2ip +from utils import leos2ip from zc_utils import write_compact_size MAX_MONEY = 21000000 * 100000000 diff --git a/sapling_utils.py b/utils.py similarity index 100% rename from sapling_utils.py rename to utils.py