Merge branch 'zsa1' into zsa-note-encryption

This commit is contained in:
Paul 2022-06-15 13:05:10 +03:00
commit c6612a819c
12 changed files with 328 additions and 53 deletions

17
COPYING
View File

@ -6,14 +6,19 @@ the file ./LICENSE-BOSL for the terms of the Bootstrap Open Source Licence,
version 1.0.
Only if this Original Work is included as part of the distribution of one of the
following projects ("the Project"):
following (each, the "Project"):
- The Zcash projects published by the Electric Coin Company,
- The Zebra project published by the Zcash Foundation,
- The Zcash projects published by the Electric Coin Company;
- The Zebra project published by the Zcash Foundation;
- A project that is designed to integrate with Zcash and provides additional
functionality or utility to the Zcash network and holders of the ZEC coin; or
- A blockchain that descends from the Zcash blockchain and that is forked
within 100 blocks of the current block height of the Zcash blockchain at the
time of the code fork;
then License is granted to use this package under the BOSL as modified by the
following clarification and special exception. This exception applies only to
the Original Work when linked or combined with the Project and not to the
then License is granted to use the Original Work under the BOSL as modified by
the following clarification and special exception. This exception applies only
to the Original Work when linked or combined with the Project and not to the
Original Work when linked, combined, or included in or with any other software
or project or on a standalone basis.

19
book/src/IDENTIFIERS.json Normal file
View File

@ -0,0 +1,19 @@
{
"commit-ivk-bit-lengths": "design/circuit/commit-ivk.html#bit-length-constraints",
"commit-ivk-canonicity-ak": "design/circuit/commit-ivk.html#canonicity-ak",
"commit-ivk-canonicity-nk": "design/circuit/commit-ivk.html#canonicity-nk",
"commit-ivk-decompositions": "design/circuit/commit-ivk.html#constrain-bit-lengths",
"commit-ivk-region-layout": "design/circuit/commit-ivk.html#region-layout",
"note-commit-canonicity-g_d": "design/circuit/note-commit.html#canonicity-g_d",
"note-commit-canonicity-pk_d": "design/circuit/note-commit.html#canonicity-pk_d",
"note-commit-canonicity-psi": "design/circuit/note-commit.html#canonicity-psi",
"note-commit-canonicity-rho": "design/circuit/note-commit.html#canonicity-rho",
"note-commit-canonicity-v": "design/circuit/note-commit.html#canonicity-v",
"note-commit-canonicity-y": "design/circuit/note-commit.html#canonicity-y",
"note-commit-decomposition-b": "design/circuit/note-commit.html#decomposition-b",
"note-commit-decomposition-d": "design/circuit/note-commit.html#decomposition-d",
"note-commit-decomposition-e": "design/circuit/note-commit.html#decomposition-e",
"note-commit-decomposition-g": "design/circuit/note-commit.html#decomposition-g",
"note-commit-decomposition-h": "design/circuit/note-commit.html#decomposition-h",
"note-commit-decomposition-y": "design/circuit/note-commit.html#decomposition-y"
}

View File

@ -146,7 +146,7 @@ $\NullifierKey$).
> - Let $x' = x + t' - t$.
> - Enforce $0 \leq x' < t'$.
### $\AuthSignPublic$ with $b_1 = 1 \implies \AuthSignPublic \geq 2^{254}$
### $\AuthSignPublic$ with $b_1 = 1 \implies \AuthSignPublic \geq 2^{254}$ <a name="canonicity-ak">
In these cases, we check that $\textsf{ak}_{0..=253} < t_\mathbb{P}$:
@ -181,7 +181,7 @@ $$
\end{array}
$$
### $\NullifierKey$ with $d_1 = 1 \implies \NullifierKey \geq 2^{254}$
### $\NullifierKey$ with $d_1 = 1 \implies \NullifierKey \geq 2^{254}$ <a name="canonicity-nk">
In these cases, we check that $\textsf{nk}_{0..=253} < t_\mathbb{P}$:

View File

@ -104,7 +104,7 @@ The following helper gates are defined:
- $\ShortLookupRangeCheck{}$ is a
[short lookup range check](../decomposition.md#short-range-check).
### $b = b_0 \bconcat b_1 \bconcat b_2 \bconcat b_3$
### $b = b_0 \bconcat b_1 \bconcat b_2 \bconcat b_3$ <a name="decomposition-b">
$b$ has been constrained to be $10$ bits by the Sinsemilla hash.
#### Region layout
@ -132,7 +132,7 @@ Outside this gate, we have constrained:
- $\ShortLookupRangeCheck{b_0, 4}$
- $\ShortLookupRangeCheck{b_3, 4}$
### $d = d_0 \bconcat d_1 \bconcat d_2 \bconcat d_3$
### $d = d_0 \bconcat d_1 \bconcat d_2 \bconcat d_3$ <a name="decomposition-d">
$d$ has been constrained to be $60$ bits by the $\SinsemillaHash$.
#### Region layout
@ -161,7 +161,7 @@ Outside this gate, we have constrained:
- $d_3$ is equality-constrained to $z_{d,1}$, where the latter is the index-1 running sum
output of $\SinsemillaHash(d),$ constrained by the hash to be $50$ bits.
### $e = e_0 \bconcat e_1$
### $e = e_0 \bconcat e_1$ <a name="decomposition-e">
$e$ has been constrained to be $10$ bits by the $\SinsemillaHash$.
#### Region layout
@ -186,7 +186,7 @@ Outside this gate, we have constrained:
- $\ShortLookupRangeCheck{e_0, 6}$
- $\ShortLookupRangeCheck{e_1, 4}$
### $g = g_0 \bconcat g_1 \bconcat g_2$
### $g = g_0 \bconcat g_1 \bconcat g_2$ <a name="decomposition-g">
$g$ has been constrained to be $250$ bits by the $\SinsemillaHash$.
#### Region layout
@ -214,7 +214,7 @@ Outside this gate, we have constrained:
- $g_2$ is equality-constrained to $z_{g,1}$, where the latter is the index-1 running sum
output of $\SinsemillaHash(g),$ constrained by the hash to be 240 bits.
### $h = h_0 \bconcat h_1 \bconcat h_2$
### $h = h_0 \bconcat h_1 \bconcat h_2$ <a name="decomposition-h">
$h$ has been constrained to be $10$ bits by the $\SinsemillaHash$.
#### Region layout
@ -280,7 +280,7 @@ below are enforced if and only if the corresponding top bit is set to 1.
> - Let $x' = x + t' - t$.
> - Enforce $0 \leq x' < t'$.
### $x(\mathsf{g_d})$ with $b_1 = 1 \implies x(\mathsf{g_d}) \geq 2^{254}$
### $x(\mathsf{g_d})$ with $b_1 = 1 \implies x(\mathsf{g_d}) \geq 2^{254}$ <a name="canonicity-g_d">
Recall that $x(\mathsf{g_d}) = a + 2^{250} \cdot b_0 + 2^{254} \cdot b_1$. When the top
bit $b_1$ is set, we check that $x(\mathsf{g_d})_{0..=253} < t_\mathbb{P}$:
@ -327,7 +327,7 @@ $$
\end{array}
$$
### $x(\mathsf{pk_d})$ with $d_0 = 1 \implies x(\mathsf{pk_d}) \geq 2^{254}$
### $x(\mathsf{pk_d})$ with $d_0 = 1 \implies x(\mathsf{pk_d}) \geq 2^{254}$ <a name="canonicity-pk_d">
Recall that $x(\mathsf{pk_d}) = b_3 + 2^4 \cdot c + 2^{254} \cdot d_0$. When the top bit
$d_0$ is set, we check that $x(\mathsf{pk_d})_{0..=253} < t_\mathbb{P}$:
@ -368,7 +368,7 @@ $$
\end{array}
$$
### $\mathsf{v} = d_2 + 2^8 \cdot d_3 + 2^{58} \cdot e_0$
### $\mathsf{v} = d_2 + 2^8 \cdot d_3 + 2^{58} \cdot e_0$ <a name="canonicity-v">
#### Region layout
$$
@ -388,7 +388,7 @@ $$
\end{array}
$$
### $\rho$ with $g_0 = 1 \implies \rho \geq 2^{254}$
### $\rho$ with $g_0 = 1 \implies \rho \geq 2^{254}$ <a name="canonicity-rho">
Recall that $\rho = e_1 + 2^4 \cdot f + 2^{254} \cdot g_0$. When the top bit $g_0$ is set,
we check that $\rho_{0..=253} < t_\mathbb{P}$:
@ -429,7 +429,7 @@ $$
\end{array}
$$
### $\psi$ with $h_1 = 1 \implies \psi \geq 2^{254}$
### $\psi$ with $h_1 = 1 \implies \psi \geq 2^{254}$ <a name="canonicity-psi">
Recall that $\psi = g_1 + 2^9 \cdot g_2 + 2^{249} \cdot h_0 + 2^{254} \cdot h_1$. When the
top bit $h_1$ is set, we check that $\psi_{0..=253} < t_\mathbb{P}$:
@ -476,7 +476,7 @@ $$
\end{array}
$$
### $y$-coordinate checks
### $y$-coordinate checks <a name="decomposition-y">
Note that only the $ỹ$ LSB of the $y$-coordinates $y(\mathsf{g_d}), y(\mathsf{pk_d})$ was
input to the hash, while the other bits of the $y$-coordinate were unused. However, we
@ -523,7 +523,7 @@ $$
\end{array}
$$
### $y(\mathsf{g_d})$ with $k_3 = 1 \implies y(\mathsf{g_d}) \geq 2^{254}$
### $y(\mathsf{g_d})$ with $k_3 = 1 \implies y(\mathsf{g_d}) \geq 2^{254}$ <a name="canonicity-y">
In these cases, we check that $y(\mathsf{g_d})_{0..=253} < t_\mathbb{P}$:

View File

@ -143,10 +143,10 @@ impl plonk::Circuit<pallas::Base> for Circuit {
meta.advice_column(),
];
// Constrain v_old - v_new = magnitude * sign
// Either v_old = 0, or calculated root = anchor
// Constrain v_old = 0 or enable_spends = 1.
// Constrain v_new = 0 or enable_outputs = 1.
// Constrain v_old - v_new = magnitude * sign (https://p.z.cash/ZKS:action-cv-net-integrity?partial).
// Either v_old = 0, or calculated root = anchor (https://p.z.cash/ZKS:action-merkle-path-validity?partial).
// Constrain v_old = 0 or enable_spends = 1 (https://p.z.cash/ZKS:action-enable-spend).
// Constrain v_new = 0 or enable_outputs = 1 (https://p.z.cash/ZKS:action-enable-output).
let q_orchard = meta.selector();
meta.create_gate("Orchard circuit checks", |meta| {
let q_orchard = meta.query_selector(q_orchard);
@ -389,7 +389,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
(psi_old, rho_old, cm_old, g_d_old, ak_P, nk, v_old, v_new)
};
// Merkle path validity check.
// Merkle path validity check (https://p.z.cash/ZKS:action-merkle-path-validity?partial).
let root = {
let path = self
.path
@ -404,7 +404,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
merkle_inputs.calculate_root(layouter.namespace(|| "Merkle path"), leaf)?
};
// Value commitment integrity.
// Value commitment integrity (https://p.z.cash/ZKS:action-cv-net-integrity?partial).
let v_net_magnitude_sign = {
// Witness the magnitude and sign of v_net = v_old - v_new
let v_net_magnitude_sign = {
@ -462,7 +462,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
v_net_magnitude_sign
};
// Nullifier integrity
// Nullifier integrity (https://p.z.cash/ZKS:action-nullifier-integrity).
let nf_old = {
let nf_old = gadget::derive_nullifier(
layouter.namespace(|| "nf_old = DeriveNullifier_nk(rho_old, psi_old, cm_old)"),
@ -481,7 +481,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
nf_old
};
// Spend authority
// Spend authority (https://p.z.cash/ZKS:action-spend-authority)
{
let alpha =
ScalarFixed::new(ecc_chip.clone(), layouter.namespace(|| "alpha"), self.alpha)?;
@ -501,7 +501,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
layouter.constrain_instance(rk.inner().y().cell(), config.primary, RK_Y)?;
}
// Diversified address integrity.
// Diversified address integrity (https://p.z.cash/ZKS:action-addr-integrity?partial).
let pk_d_old = {
let ivk = {
let ak = ak_P.extract_p().inner().clone();
@ -547,7 +547,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
pk_d_old
};
// Old note commitment integrity.
// Old note commitment integrity (https://p.z.cash/ZKS:action-cm-old-integrity?partial).
{
let rcm_old = ScalarFixed::new(
ecc_chip.clone(),
@ -575,7 +575,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
derived_cm_old.constrain_equal(layouter.namespace(|| "cm_old equality"), &cm_old)?;
}
// New note commitment integrity.
// New note commitment integrity (https://p.z.cash/ZKS:action-cmx-new-integrity?partial).
{
// Witness g_d_new
let g_d_new = {

View File

@ -52,6 +52,8 @@ impl CommitIvkChip {
// - c: 240 bits,
// - d: 10 bits
//
// https://p.z.cash/orchard-0.1:commit-ivk-decompositions
// https://p.z.cash/orchard-0.1:commit-ivk-region-layout?partial
/*
The pieces are laid out in this configuration:
@ -106,6 +108,7 @@ impl CommitIvkChip {
let d_decomposition_check = d_whole - (d_0.clone() + d_1.clone() * two_pow_9);
// Check `b_1` and `d_1` are each a single-bit value.
// https://p.z.cash/orchard-0.1:commit-ivk-bit-lengths?partial
let b1_bool_check = bool_check(b_1.clone());
let d1_bool_check = bool_check(d_1.clone());
@ -126,6 +129,7 @@ impl CommitIvkChip {
// ak = a (250 bits) || b_0 (4 bits) || b_1 (1 bit)
// The `ak` canonicity checks are enforced if and only if `b_1` = 1.
// https://p.z.cash/orchard-0.1:commit-ivk-canonicity-ak?partial
let ak_canonicity_checks = {
// b_1 = 1 => b_0 = 0
let b0_canon_check = b_1.clone() * b_0;
@ -163,6 +167,7 @@ impl CommitIvkChip {
// nk = b_2 (5 bits) || c (240 bits) || d_0 (9 bits) || d_1 (1 bit)
// The `nk` canonicity checks are enforced if and only if `d_1` = 1.
// https://p.z.cash/orchard-0.1:commit-ivk-canonicity-nk?partial
let nk_canonicity_checks = {
// d_1 = 1 => d_0 = 0
let c0_canon_check = d_1.clone() * d_0;
@ -257,6 +262,8 @@ pub(in crate::circuit) mod gadgets {
//
// We start by witnessing all of the individual pieces, and range-constraining
// the short pieces b_0, b_2, and d_0.
//
// https://p.z.cash/orchard-0.1:commit-ivk-bit-lengths?partial
// `a` = bits 0..=249 of `ak`
let a = MessagePiece::from_subpieces(
@ -327,6 +334,8 @@ pub(in crate::circuit) mod gadgets {
// `ivk = ⊥` is handled internally to `CommitDomain::short_commit`: incomplete
// addition constraints allows ⊥ to occur, and then during synthesis it detects
// these edge cases and raises an error (aborting proof creation).
//
// https://p.z.cash/ZKS:action-addr-integrity?partial
let (ivk, zs) = {
let message = Message::from_pieces(
sinsemilla_chip.clone(),
@ -385,6 +394,8 @@ pub(in crate::circuit) mod gadgets {
}
/// Witnesses and decomposes the `a'` value we need to check the canonicity of `ak`.
///
/// [Specification](https://p.z.cash/orchard-0.1:commit-ivk-canonicity-ak?partial).
#[allow(clippy::type_complexity)]
fn ak_canonicity(
lookup_config: &LookupRangeCheckConfig<pallas::Base, 10>,
@ -424,6 +435,8 @@ pub(in crate::circuit) mod gadgets {
}
/// Witnesses and decomposes the `b2c'` value we need to check the canonicity of `nk`.
///
/// [Specification](https://p.z.cash/orchard-0.1:commit-ivk-canonicity-nk?partial).
#[allow(clippy::type_complexity)]
fn nk_canonicity(
lookup_config: &LookupRangeCheckConfig<pallas::Base, 10>,
@ -468,7 +481,9 @@ pub(in crate::circuit) mod gadgets {
}
impl CommitIvkConfig {
/// Assign cells for the canonicity gate.
/// Assign cells for the [canonicity gate].
///
/// [canonicity gate]: https://p.z.cash/orchard-0.1:commit-ivk-region-layout?partial
/*
The pieces are laid out in this configuration:

View File

@ -59,6 +59,8 @@ type CanonicityBounds = (
/// ------------------------------------
/// | b | b_0 | b_1 | 1 |
/// | | b_2 | b_3 | 0 |
///
/// https://p.z.cash/orchard-0.1:note-commit-decomposition-b?partial
#[derive(Clone, Debug)]
struct DecomposeB {
q_notecommit_b: Selector,
@ -207,6 +209,8 @@ impl DecomposeB {
/// ------------------------------------
/// | d | d_0 | d_1 | 1 |
/// | | d_2 | d_3 | 0 |
///
/// https://p.z.cash/orchard-0.1:note-commit-decomposition-d?partial
#[derive(Clone, Debug)]
struct DecomposeD {
q_notecommit_d: Selector,
@ -346,6 +350,8 @@ impl DecomposeD {
/// | A_6 | A_7 | A_8 | q_notecommit_e |
/// ------------------------------------
/// | e | e_0 | e_1 | 1 |
///
/// https://p.z.cash/orchard-0.1:note-commit-decomposition-e?partial
#[derive(Clone, Debug)]
struct DecomposeE {
q_notecommit_e: Selector,
@ -463,6 +469,8 @@ impl DecomposeE {
/// ------------------------------
/// | g | g_0 | 1 |
/// | g_1 | g_2 | 0 |
///
/// https://p.z.cash/orchard-0.1:note-commit-decomposition-g?partial
#[derive(Clone, Debug)]
struct DecomposeG {
q_notecommit_g: Selector,
@ -588,6 +596,8 @@ impl DecomposeG {
/// | A_6 | A_7 | A_8 | q_notecommit_h |
/// ------------------------------------
/// | h | h_0 | h_1 | 1 |
///
/// https://p.z.cash/orchard-0.1:note-commit-decomposition-h?partial
#[derive(Clone, Debug)]
struct DecomposeH {
q_notecommit_h: Selector,
@ -708,6 +718,8 @@ impl DecomposeH {
/// -----------------------------------------------------------
/// | x(g_d) | b_0 | a | z13_a | 1 |
/// | | b_1 | a_prime | z13_a_prime | 0 |
///
/// https://p.z.cash/orchard-0.1:note-commit-canonicity-g_d?partial
#[derive(Clone, Debug)]
struct GdCanonicity {
q_notecommit_g_d: Selector,
@ -823,6 +835,8 @@ impl GdCanonicity {
/// -------------------------------------------------------------------
/// | x(pk_d) | b_3 | c | z13_c | 1 |
/// | | d_0 | b3_c_prime | z14_b3_c_prime | 0 |
///
/// https://p.z.cash/orchard-0.1:note-commit-canonicity-pk_d?partial
#[derive(Clone, Debug)]
struct PkdCanonicity {
q_notecommit_pk_d: Selector,
@ -937,6 +951,8 @@ impl PkdCanonicity {
/// | A_6 | A_7 | A_8 | A_9 | q_notecommit_value |
/// ------------------------------------------------
/// | value | d_2 | d_3 | e_0 | 1 |
///
/// https://p.z.cash/orchard-0.1:note-commit-canonicity-v?partial
#[derive(Clone, Debug)]
struct ValueCanonicity {
q_notecommit_value: Selector,
@ -1013,6 +1029,8 @@ impl ValueCanonicity {
/// --------------------------------------------------------------
/// | rho | e_1 | f | z13_f | 1 |
/// | | g_0 | e1_f_prime | z14_e1_f_prime | 0 |
///
/// https://p.z.cash/orchard-0.1:note-commit-canonicity-rho?partial
#[derive(Clone, Debug)]
struct RhoCanonicity {
q_notecommit_rho: Selector,
@ -1126,6 +1144,8 @@ impl RhoCanonicity {
/// ----------------------------------------------------------------
/// | psi | g_1 | g_2 | z13_g | 1 |
/// | h_0 | h_1 | g1_g2_prime | z13_g1_g2_prime | 0 |
///
/// https://p.z.cash/orchard-0.1:note-commit-canonicity-psi?partial
#[derive(Clone, Debug)]
struct PsiCanonicity {
q_notecommit_psi: Selector,
@ -1296,6 +1316,7 @@ impl YCanonicity {
let z13_j_prime = meta.query_advice(advices[9], Rotation::next());
// Decomposition checks
// https://p.z.cash/orchard-0.1:note-commit-decomposition-y?partial
let decomposition_checks = {
// Check that k_3 is boolean
let k3_check = bool_check(k_3.clone());
@ -1316,6 +1337,7 @@ impl YCanonicity {
};
// Canonicity checks. These are enforced if and only if k_3 = 1.
// https://p.z.cash/orchard-0.1:note-commit-canonicity-y?partial
let canonicity_checks = {
iter::empty()
.chain(Some(("k_3 = 1 => k_2 = 0", k_2)))
@ -1652,6 +1674,9 @@ pub(in crate::circuit) mod gadgets {
// `cm = ⊥` is handled internally to `CommitDomain::commit`: incomplete addition
// constraints allows ⊥ to occur, and then during synthesis it detects these edge
// cases and raises an error (aborting proof creation).
//
// https://p.z.cash/ZKS:action-cm-old-integrity?partial
// https://p.z.cash/ZKS:action-cmx-new-integrity?partial
let (cm, zs) = {
let message = Message::from_pieces(
chip.clone(),
@ -1774,6 +1799,10 @@ pub(in crate::circuit) mod gadgets {
}
/// A canonicity check helper used in checking x(g_d), y(g_d), and y(pk_d).
///
/// Specifications:
/// - [`g_d` canonicity](https://p.z.cash/orchard-0.1:note-commit-canonicity-g_d?partial)
/// - [`y` canonicity](https://p.z.cash/orchard-0.1:note-commit-canonicity-y?partial)
fn canon_bitshift_130(
lookup_config: &LookupRangeCheckConfig<pallas::Base, 10>,
mut layouter: impl Layouter<pallas::Base>,
@ -1806,6 +1835,8 @@ pub(in crate::circuit) mod gadgets {
}
/// Check canonicity of `x(pk_d)` encoding.
///
/// [Specification](https://p.z.cash/orchard-0.1:note-commit-canonicity-pk_d?partial).
fn pkd_x_canonicity(
lookup_config: &LookupRangeCheckConfig<pallas::Base, 10>,
mut layouter: impl Layouter<pallas::Base>,
@ -1845,6 +1876,8 @@ pub(in crate::circuit) mod gadgets {
}
/// Check canonicity of `rho` encoding.
///
/// [Specification](https://p.z.cash/orchard-0.1:note-commit-canonicity-rho?partial).
fn rho_canonicity(
lookup_config: &LookupRangeCheckConfig<pallas::Base, 10>,
mut layouter: impl Layouter<pallas::Base>,
@ -1884,6 +1917,8 @@ pub(in crate::circuit) mod gadgets {
}
/// Check canonicity of `psi` encoding.
///
/// [Specification](https://p.z.cash/orchard-0.1:note-commit-canonicity-psi?partial).
fn psi_canonicity(
lookup_config: &LookupRangeCheckConfig<pallas::Base, 10>,
mut layouter: impl Layouter<pallas::Base>,
@ -1922,6 +1957,10 @@ pub(in crate::circuit) mod gadgets {
/// Check canonicity of y-coordinate given its LSB as a value.
/// Also, witness the LSB and return the witnessed cell.
///
/// Specifications:
/// - [`y` decomposition](https://p.z.cash/orchard-0.1:note-commit-decomposition-y?partial)
/// - [`y` canonicity](https://p.z.cash/orchard-0.1:note-commit-canonicity-y?partial)
fn y_canonicity(
lookup_config: &LookupRangeCheckConfig<pallas::Base, 10>,
y_canon: &YCanonicity,

View File

@ -18,7 +18,7 @@ use zcash_note_encryption::EphemeralKeyBytes;
use crate::{
address::Address,
primitives::redpallas::{self, SpendAuth},
primitives::redpallas::{self, SpendAuth, VerificationKey},
spec::{
commit_ivk, diversify_hash, extract_p, ka_orchard, prf_nf, to_base, to_scalar,
NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, PrfExpand,
@ -188,21 +188,104 @@ impl SpendValidatingKey {
pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
<[u8; 32]>::try_from(bytes)
.ok()
.and_then(|b| {
// Structural validity checks for ak_P:
// - The point must not be the identity
// (which for Pallas is canonically encoded as all-zeroes).
// - The sign of the y-coordinate must be positive.
if b != [0; 32] && b[31] & 0x80 == 0 {
<redpallas::VerificationKey<SpendAuth>>::try_from(b).ok()
} else {
None
}
})
.and_then(check_structural_validity)
.map(SpendValidatingKey)
}
}
/// An issuer authorizing key, used to create issuer authorization signatures.
/// This type enforces that the corresponding public point (ik^) has ỹ = 0.
///
/// $\mathsf{isk}$ as defined in
/// [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Clone, Debug)]
pub struct IssuerAuthorizingKey(redpallas::SigningKey<SpendAuth>);
impl IssuerAuthorizingKey {
/// Derives isk from sk. Internal use only, does not enforce all constraints.
fn derive_inner(sk: &SpendingKey) -> pallas::Scalar {
to_scalar(PrfExpand::ZsaIsk.expand(&sk.0))
}
}
impl From<&SpendingKey> for IssuerAuthorizingKey {
fn from(sk: &SpendingKey) -> Self {
let isk = Self::derive_inner(sk);
// IssuerSigningKey cannot be constructed such that this assertion would fail.
assert!(!bool::from(isk.is_zero()));
let ret = IssuerAuthorizingKey(isk.to_repr().try_into().unwrap());
// If the last bit of repr_P(ik) is 1, negate isk.
if (<[u8; 32]>::from(IssuerValidatingKey::from(&ret).0)[31] >> 7) == 1 {
IssuerAuthorizingKey((-isk).to_repr().try_into().unwrap())
} else {
ret
}
}
}
/// A key used to validate issuer authorization signatures.
///
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
/// Note that this is $\mathsf{ik}^\mathbb{P}$, which by construction is equivalent to
/// $\mathsf{ik}$ but stored here as a RedPallas verification key.
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug, Clone, PartialOrd, Ord)]
pub struct IssuerValidatingKey(redpallas::VerificationKey<SpendAuth>);
impl From<&IssuerAuthorizingKey> for IssuerValidatingKey {
fn from(isk: &IssuerAuthorizingKey) -> Self {
IssuerValidatingKey((&isk.0).into())
}
}
impl From<&IssuerValidatingKey> for pallas::Point {
fn from(issuer_validating_key: &IssuerValidatingKey) -> pallas::Point {
pallas::Point::from_bytes(&(&issuer_validating_key.0).into()).unwrap()
}
}
impl PartialEq for IssuerValidatingKey {
fn eq(&self, other: &Self) -> bool {
<[u8; 32]>::from(&self.0).eq(&<[u8; 32]>::from(&other.0))
}
}
impl Eq for IssuerValidatingKey {}
impl IssuerValidatingKey {
/// Converts this spend validating key to its serialized form,
/// I2LEOSP_256(ik).
pub(crate) fn to_bytes(&self) -> [u8; 32] {
// This is correct because the wrapped point must have ỹ = 0, and
// so the point repr is the same as I2LEOSP of its x-coordinate.
<[u8; 32]>::from(&self.0)
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
<[u8; 32]>::try_from(bytes)
.ok()
.and_then(check_structural_validity)
.map(IssuerValidatingKey)
}
}
/// A function to check structural validity of the validating keys for authorizing transfers and
/// issuing assets
/// Structural validity checks for ik_P:
/// - The point must not be the identity (which for Pallas is canonically encoded as all-zeroes).
/// - The sign of the y-coordinate must be positive.
fn check_structural_validity(
verification_key_bytes: [u8; 32],
) -> Option<VerificationKey<SpendAuth>> {
if verification_key_bytes != [0; 32] && verification_key_bytes[31] & 0x80 == 0 {
<redpallas::VerificationKey<SpendAuth>>::try_from(verification_key_bytes).ok()
} else {
None
}
}
/// A key used to derive [`Nullifier`]s from [`Note`]s.
///
/// $\mathsf{nk}$ as defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
@ -1022,9 +1105,15 @@ mod tests {
let ask: SpendAuthorizingKey = (&sk).into();
assert_eq!(<[u8; 32]>::from(&ask.0), tv.ask);
let isk: IssuerAuthorizingKey = (&sk).into();
assert_eq!(<[u8; 32]>::from(&isk.0), tv.isk);
let ak: SpendValidatingKey = (&ask).into();
assert_eq!(<[u8; 32]>::from(ak.0), tv.ak);
let ik: IssuerValidatingKey = (&isk).into();
assert_eq!(<[u8; 32]>::from(ik.0), tv.ik);
let nk: NullifierDerivingKey = (&sk).into();
assert_eq!(nk.0.to_repr(), tv.nk);

View File

@ -4,15 +4,18 @@ use pasta_curves::pallas;
use subtle::{Choice, ConstantTimeEq, CtOption};
use crate::constants::fixed_bases::{VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_V_BYTES};
use crate::keys::SpendValidatingKey;
use crate::keys::IssuerValidatingKey;
/// Note type identifier.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NoteType(pallas::Point);
pub const MAX_ASSET_DESCRIPTION_SIZE: usize = 512;
// the hasher used to derive the assetID
#[allow(non_snake_case)]
fn assetID_hasher(msg: Vec<u8>) -> pallas::Point {
// TODO(zsa) replace personalization
pallas::Point::hash_to_curve(VALUE_COMMITMENT_PERSONALIZATION)(&msg)
}
@ -33,10 +36,11 @@ impl NoteType {
///
/// [notetypes]: https://zips.z.cash/protocol/nu5.pdf#notetypes
#[allow(non_snake_case)]
pub(super) fn derive(ak: &SpendValidatingKey, assetDesc: &[u8; 64]) -> Self {
let mut s = vec![];
pub fn derive(ik: &IssuerValidatingKey, assetDesc: Vec<u8>) -> Self {
assert!(assetDesc.len() < MAX_ASSET_DESCRIPTION_SIZE);
s.extend(ak.to_bytes());
let mut s = vec![];
s.extend(ik.to_bytes());
s.extend(assetDesc);
NoteType(assetID_hasher(s))
@ -63,11 +67,10 @@ impl NoteType {
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
pub mod testing {
use proptest::prelude::*;
use crate::keys::{testing::arb_spending_key, FullViewingKey};
use super::NoteType;
use crate::keys::{testing::arb_spending_key, IssuerAuthorizingKey, IssuerValidatingKey};
prop_compose! {
/// Generate a uniformly distributed note type
pub fn arb_note_type()(
@ -76,8 +79,8 @@ pub mod testing {
bytes32b in prop::array::uniform32(prop::num::u8::ANY),
) -> NoteType {
let bytes64 = [bytes32a, bytes32b].concat();
let fvk = FullViewingKey::from(&sk);
NoteType::derive(&fvk.into(), &bytes64.try_into().unwrap())
let isk = IssuerAuthorizingKey::from(&sk);
NoteType::derive(&IssuerValidatingKey::from(&isk), bytes64)
}
}
}

View File

@ -179,6 +179,7 @@ impl Domain for OrchardDomain {
np[0] = if is_native { 0x02 } else { 0x03 };
np[1..12].copy_from_slice(note.recipient().diversifier().as_array());
np[12..20].copy_from_slice(&note.value().to_bytes());
// todo: add note_type
np[20..52].copy_from_slice(note.rseed().as_bytes());
if is_native {
np[52..].copy_from_slice(memo);

View File

@ -10,6 +10,7 @@ pub(crate) enum PrfExpand {
OrchardNk,
OrchardRivk,
Psi,
ZsaIsk,
OrchardZip32Child,
OrchardDkOvk,
OrchardRivkInternal,
@ -24,6 +25,7 @@ impl PrfExpand {
Self::OrchardNk => 0x07,
Self::OrchardRivk => 0x08,
Self::Psi => 0x09,
Self::ZsaIsk => 0x0a,
Self::OrchardZip32Child => 0x81,
Self::OrchardDkOvk => 0x82,
Self::OrchardRivkInternal => 0x83,

View File

@ -4,6 +4,8 @@ pub(crate) struct TestVector {
pub(crate) sk: [u8; 32],
pub(crate) ask: [u8; 32],
pub(crate) ak: [u8; 32],
pub(crate) isk: [u8; 32],
pub(crate) ik: [u8; 32],
pub(crate) nk: [u8; 32],
pub(crate) rivk: [u8; 32],
pub(crate) ivk: [u8; 32],
@ -41,6 +43,16 @@ pub(crate) fn test_vectors() -> Vec<TestVector> {
0x12, 0x8b, 0x9a, 0x14, 0x0d, 0x5e, 0x07, 0xc1, 0x51, 0x72, 0x1d, 0xc1, 0x6d, 0x25,
0xd4, 0xe2, 0x0f, 0x15,
],
isk: [
0x95, 0x4a, 0x86, 0xc7, 0xa7, 0x15, 0x53, 0xfa, 0x6c, 0x8b, 0x67, 0x58, 0x54, 0x26,
0x8e, 0xa5, 0x4c, 0x51, 0xfb, 0x17, 0xd8, 0x3d, 0x80, 0xee, 0x71, 0xd4, 0xae, 0x42,
0xa1, 0xf8, 0xc8, 0x16,
],
ik: [
0x2e, 0x4f, 0xd4, 0xa6, 0xec, 0x39, 0x94, 0x87, 0xd3, 0x78, 0xb4, 0xc7, 0x25, 0xfb,
0x9b, 0xaf, 0xbc, 0x01, 0xa5, 0xe2, 0xb7, 0xf3, 0x68, 0x2e, 0xf4, 0x53, 0x95, 0x91,
0xbc, 0xf0, 0x59, 0x02,
],
nk: [
0x9f, 0x2f, 0x82, 0x67, 0x38, 0x94, 0x5a, 0xd0, 0x1f, 0x47, 0xf7, 0x0d, 0xb0, 0xc3,
0x67, 0xc2, 0x46, 0xc2, 0x0c, 0x61, 0xff, 0x55, 0x83, 0x94, 0x8c, 0x39, 0xde, 0xa9,
@ -132,6 +144,16 @@ pub(crate) fn test_vectors() -> Vec<TestVector> {
0x2a, 0xd6, 0x43, 0x23, 0x62, 0x9c, 0xfe, 0xd1, 0xe3, 0xaa, 0x24, 0xef, 0x05, 0x2f,
0x56, 0xe4, 0x00, 0x2a,
],
isk: [
0xee, 0xf5, 0xe9, 0x1b, 0x36, 0xb8, 0x06, 0x86, 0x72, 0x3d, 0x14, 0xdc, 0xc7, 0x04,
0xad, 0x59, 0x67, 0x08, 0x0b, 0x7d, 0x6e, 0x49, 0xaf, 0x97, 0x03, 0x0e, 0x4f, 0xa0,
0xbf, 0x5a, 0xd9, 0x0b,
],
ik: [
0x2e, 0xde, 0xfb, 0x15, 0x8e, 0xa4, 0x48, 0x82, 0x57, 0x2b, 0xcd, 0xb2, 0x35, 0xca,
0x36, 0xab, 0x39, 0xc7, 0x47, 0xbb, 0x71, 0xfe, 0x0f, 0x10, 0xfa, 0xa3, 0x9b, 0xfd,
0x62, 0x0a, 0xcc, 0x04,
],
nk: [
0xa8, 0xb7, 0x3d, 0x97, 0x9b, 0x6e, 0xaa, 0xda, 0x89, 0x24, 0xbc, 0xbd, 0xc6, 0x3a,
0x9e, 0xf4, 0xe8, 0x73, 0x46, 0xf2, 0x30, 0xab, 0xa6, 0xbb, 0xe1, 0xe2, 0xb4, 0x3c,
@ -223,6 +245,16 @@ pub(crate) fn test_vectors() -> Vec<TestVector> {
0xed, 0xb4, 0x26, 0x65, 0x7b, 0x2d, 0x07, 0x40, 0x66, 0x64, 0xd8, 0x95, 0x31, 0x2e,
0xa1, 0xc3, 0xb3, 0x34,
],
isk: [
0x5b, 0x1f, 0xc4, 0x57, 0xae, 0x71, 0x38, 0x3c, 0x53, 0xf4, 0x69, 0x41, 0xb7, 0xcb,
0x4c, 0xec, 0x3d, 0xea, 0xc0, 0xc6, 0x03, 0xe2, 0xcd, 0xd0, 0xd1, 0x8d, 0x94, 0x01,
0x9e, 0x43, 0xe2, 0x07,
],
ik: [
0x4f, 0x43, 0xeb, 0x7d, 0x9e, 0x03, 0x6f, 0xa6, 0x15, 0xfd, 0x04, 0xa5, 0xef, 0x6a,
0xeb, 0x21, 0x6e, 0x06, 0x9b, 0xe9, 0x2d, 0x30, 0xe8, 0xf7, 0x16, 0x3e, 0xe3, 0x15,
0x11, 0x6f, 0x18, 0x32,
],
nk: [
0x04, 0x51, 0x4e, 0xa0, 0x48, 0xb9, 0x43, 0x63, 0xde, 0xa7, 0xcb, 0x3b, 0xe8, 0xd6,
0x25, 0x82, 0xac, 0x52, 0x92, 0x2e, 0x08, 0x65, 0xf6, 0x62, 0x74, 0x3b, 0x05, 0xea,
@ -314,6 +346,16 @@ pub(crate) fn test_vectors() -> Vec<TestVector> {
0xe7, 0x2c, 0x3b, 0x64, 0x00, 0x06, 0xff, 0x08, 0x50, 0x52, 0x80, 0xe4, 0xf0, 0x0f,
0xad, 0xf7, 0x63, 0x28,
],
isk: [
0x71, 0xd0, 0x64, 0xaa, 0xa0, 0x82, 0x63, 0xb8, 0xe4, 0xc3, 0xed, 0x70, 0x3c, 0x6f,
0x54, 0x25, 0x4a, 0x88, 0x8c, 0x36, 0xec, 0x69, 0x86, 0x62, 0xf7, 0x1f, 0xbb, 0xf4,
0x26, 0xd9, 0x09, 0x28,
],
ik: [
0x12, 0xb3, 0xab, 0xff, 0x96, 0x75, 0x20, 0x9e, 0x94, 0x54, 0x07, 0x0c, 0x14, 0xac,
0x15, 0x54, 0x65, 0xae, 0x83, 0xbe, 0x2c, 0x39, 0x46, 0x63, 0x5e, 0x38, 0x77, 0xba,
0x67, 0xdf, 0x49, 0x12,
],
nk: [
0xcf, 0x36, 0xad, 0x6a, 0x06, 0x6c, 0xd2, 0x13, 0xe1, 0xd7, 0x67, 0xab, 0x07, 0x1d,
0xc1, 0x16, 0x78, 0x85, 0xc4, 0x16, 0x8b, 0xc2, 0xe2, 0x17, 0x54, 0x48, 0x56, 0x3a,
@ -405,6 +447,16 @@ pub(crate) fn test_vectors() -> Vec<TestVector> {
0xf5, 0x6d, 0x83, 0x20, 0x09, 0xf7, 0x24, 0x2e, 0x1f, 0x7c, 0x77, 0x0a, 0x12, 0x24,
0x1d, 0xfa, 0x28, 0x07,
],
isk: [
0x44, 0x36, 0x1a, 0x7b, 0xa6, 0xa1, 0xaa, 0x17, 0x8e, 0x72, 0xaf, 0x47, 0xbd, 0xc1,
0x60, 0x40, 0xce, 0x1c, 0x54, 0xdd, 0x4b, 0x56, 0x33, 0x21, 0x55, 0xba, 0x9d, 0x04,
0x09, 0x71, 0xd0, 0x07,
],
ik: [
0x1f, 0x17, 0x2d, 0x79, 0xae, 0xdc, 0xc2, 0x06, 0x8c, 0x3a, 0x09, 0x08, 0x93, 0xe1,
0xa1, 0x75, 0xd9, 0xb5, 0x78, 0xf8, 0x91, 0xaf, 0x9a, 0xb6, 0x8d, 0x4f, 0xe1, 0xe9,
0x05, 0xa3, 0xb2, 0x11,
],
nk: [
0x51, 0xba, 0xf3, 0x33, 0xcf, 0xf1, 0xf2, 0xd0, 0xc7, 0xe3, 0xcf, 0xf4, 0xd3, 0x01,
0x29, 0x9d, 0xc1, 0xef, 0xe9, 0x83, 0x00, 0x31, 0x4a, 0x54, 0x19, 0x38, 0x02, 0x9b,
@ -496,6 +548,16 @@ pub(crate) fn test_vectors() -> Vec<TestVector> {
0xdf, 0x28, 0xbb, 0x0f, 0x10, 0x21, 0xea, 0x84, 0x3f, 0x86, 0x7f, 0x8a, 0x17, 0x0f,
0x5c, 0x33, 0x90, 0x1f,
],
isk: [
0xdc, 0x93, 0x72, 0x9f, 0x3f, 0x28, 0x30, 0xed, 0x79, 0x1c, 0x21, 0xbe, 0xbe, 0x45,
0x0f, 0xcf, 0x1f, 0x8f, 0xef, 0x49, 0x81, 0x39, 0xc7, 0x99, 0xd1, 0x63, 0x66, 0x5a,
0x8c, 0x51, 0xe5, 0x2d,
],
ik: [
0x1d, 0xb6, 0x1c, 0x29, 0x3e, 0x3a, 0x93, 0x34, 0x5d, 0x06, 0xb9, 0x0b, 0xd7, 0x1f,
0xd3, 0x21, 0x5c, 0x2c, 0x1c, 0x29, 0x53, 0x5a, 0x10, 0xde, 0x9d, 0x31, 0x40, 0xb7,
0x4d, 0xb6, 0x1d, 0x07,
],
nk: [
0x9e, 0x99, 0x7d, 0x9d, 0x26, 0x97, 0x87, 0x26, 0x8e, 0x09, 0x2a, 0x7c, 0x85, 0x41,
0x7d, 0xa5, 0x30, 0xea, 0x42, 0xfa, 0xc6, 0x68, 0xa7, 0x49, 0xaf, 0x55, 0xdf, 0xb7,
@ -587,6 +649,16 @@ pub(crate) fn test_vectors() -> Vec<TestVector> {
0x65, 0x43, 0x46, 0x2a, 0x13, 0x7f, 0xfe, 0xa3, 0x7b, 0xaf, 0x41, 0xef, 0x28, 0x6b,
0xb7, 0x32, 0xbe, 0x2c,
],
isk: [
0xf2, 0x34, 0x52, 0x32, 0xc9, 0x19, 0xc1, 0x29, 0xe0, 0x4b, 0x0c, 0x46, 0xac, 0x2c,
0xa8, 0x50, 0x65, 0xd9, 0x54, 0x85, 0xb9, 0x02, 0xab, 0x0f, 0x98, 0xf9, 0x3a, 0xee,
0x59, 0x4b, 0x5f, 0x02,
],
ik: [
0x2c, 0x83, 0xd9, 0x20, 0xe9, 0xf6, 0x6d, 0xa5, 0x04, 0x86, 0x37, 0xad, 0x9a, 0xa2,
0xcc, 0xe6, 0xe1, 0x6e, 0xf4, 0x8f, 0x86, 0x50, 0xea, 0x00, 0xd8, 0xc2, 0xd7, 0x68,
0x61, 0x8a, 0xe3, 0x36,
],
nk: [
0xfd, 0x31, 0x64, 0xc6, 0x32, 0xbe, 0xc9, 0x4c, 0xe9, 0xfb, 0x2f, 0x30, 0x22, 0x63,
0xb8, 0x84, 0xab, 0xb9, 0xc1, 0x0e, 0x55, 0xe4, 0x48, 0x64, 0x7f, 0x67, 0x98, 0x49,
@ -678,6 +750,16 @@ pub(crate) fn test_vectors() -> Vec<TestVector> {
0xb3, 0x65, 0x1f, 0xfa, 0x1c, 0x69, 0x69, 0x15, 0xac, 0x00, 0xa2, 0x5e, 0xa3, 0xac,
0x7d, 0xff, 0x99, 0x01,
],
isk: [
0x3d, 0xa9, 0x08, 0x88, 0xba, 0x18, 0x24, 0xc8, 0x16, 0x29, 0x2d, 0x7f, 0x17, 0x33,
0xac, 0x4c, 0xbe, 0x72, 0x2c, 0x6a, 0x12, 0x1c, 0xc7, 0x80, 0x17, 0x06, 0x26, 0xb7,
0x0a, 0x26, 0x95, 0x28,
],
ik: [
0x16, 0xa8, 0xff, 0x29, 0xb5, 0x17, 0xb5, 0xa8, 0xf7, 0xd0, 0x9b, 0x4e, 0x5e, 0x71,
0x3a, 0x9a, 0x78, 0x4c, 0x74, 0x04, 0xd6, 0x1e, 0x3a, 0xf5, 0x30, 0x19, 0xc3, 0x47,
0x0e, 0x90, 0x95, 0x22,
],
nk: [
0x02, 0xab, 0x99, 0x5c, 0xe9, 0x8f, 0x63, 0x02, 0x5f, 0xb6, 0x24, 0x28, 0xa0, 0xfb,
0xf5, 0x2f, 0x25, 0x22, 0xe6, 0xa2, 0x72, 0x61, 0x07, 0x8a, 0x9f, 0x4d, 0x6a, 0x36,
@ -769,6 +851,16 @@ pub(crate) fn test_vectors() -> Vec<TestVector> {
0x3b, 0x02, 0xd2, 0x5c, 0xc1, 0x0c, 0x90, 0x71, 0xfc, 0x02, 0x19, 0xe9, 0x7f, 0x93,
0x92, 0xd0, 0x67, 0x0c,
],
isk: [
0x01, 0x65, 0x33, 0x68, 0x4f, 0xb9, 0x81, 0x15, 0xa4, 0x05, 0xc9, 0xc7, 0xad, 0x47,
0x72, 0x76, 0xab, 0x7c, 0x72, 0xfd, 0x67, 0x1a, 0x27, 0xe3, 0x6c, 0x0a, 0x7a, 0xbe,
0x0a, 0x76, 0x90, 0x09,
],
ik: [
0xff, 0xd7, 0x5f, 0x6f, 0x9e, 0xf4, 0x27, 0xf3, 0x26, 0xcd, 0xbf, 0x3a, 0x98, 0xbc,
0xb5, 0x93, 0x63, 0x5a, 0x2c, 0x1a, 0xd7, 0x2b, 0x39, 0x99, 0x12, 0x61, 0xe2, 0x75,
0xa9, 0xec, 0x6f, 0x10,
],
nk: [
0x25, 0x91, 0xed, 0xf7, 0xef, 0x4c, 0xf2, 0x18, 0x4c, 0x34, 0xbe, 0x93, 0xfc, 0xf6,
0x12, 0x91, 0x50, 0x42, 0xf1, 0x5a, 0xb5, 0x08, 0x4b, 0x14, 0xe1, 0x66, 0x79, 0x5b,
@ -860,6 +952,16 @@ pub(crate) fn test_vectors() -> Vec<TestVector> {
0x8d, 0x4b, 0x02, 0x5f, 0x8c, 0xc1, 0x60, 0xe1, 0xf4, 0xe9, 0x5f, 0x0a, 0x85, 0x3e,
0xbc, 0x41, 0x6a, 0x2b,
],
isk: [
0x76, 0x08, 0x32, 0x9d, 0xfa, 0x77, 0xc4, 0x2c, 0x4f, 0xc7, 0x6a, 0xc2, 0x95, 0x94,
0xa2, 0x72, 0x83, 0x93, 0x4f, 0x5a, 0x93, 0x40, 0x71, 0xb9, 0xf8, 0xcd, 0x34, 0x4e,
0x1f, 0x98, 0x45, 0x0e,
],
ik: [
0x72, 0xa0, 0xac, 0x97, 0x8a, 0x2d, 0xa1, 0x61, 0xf4, 0x1f, 0x5b, 0x7a, 0x40, 0xbd,
0x83, 0xc0, 0x58, 0x41, 0xf8, 0x1b, 0xc5, 0x11, 0x40, 0x67, 0xb8, 0x85, 0x98, 0x7f,
0x48, 0xca, 0x52, 0x2d,
],
nk: [
0x3e, 0x88, 0xf2, 0x07, 0x1f, 0xd9, 0xa2, 0xbb, 0x26, 0xcd, 0xa2, 0xea, 0x85, 0x6a,
0xa0, 0xfb, 0x3a, 0x80, 0xa8, 0x7d, 0x2f, 0xb6, 0x13, 0x6f, 0xab, 0x85, 0xe3, 0x6c,