PR #1124proposed changes to·draft-ecc-quantum-recoverability

[WIP] Support macros in ZIPs

status → Draftby daira · updated 2025-11-07zips/draft-ecc-quantum-recoverability.md @ 9e8e676cfd · upstream 4699a8d518view on GitHub ↗

Unofficial preview — the proposed text below is rendered from the pull request head and is not part of the canonical ZIP set.

Terminology

The key words "MUST", "SHOULD", and "MAY" in this document are to be interpreted as described in BCP 14 1 when, and only when, they appear in all capitals.

The term "network upgrade" in this document is to be interpreted as described in ZIP 200. 2

The character § is used when referring to sections of the Zcash Protocol Specification. 3

The terms "Mainnet" and "Testnet" are to be interpreted as described in § 3.12 ‘Mainnet and Testnet’. 4

We use the convention followed in the protocol specification that an 𝗎𝗇𝖽𝖾𝗋𝗅𝗂𝗇𝖾𝖽 variable indicates a byte sequence, and a variable suffixed with ⋆ indicates a bit-sequence encoding of an elliptic curve point.

The term "Zcash Shielded Assets" or "ZSAs" refers to the extension to the Orchard shielded protocol described in ZIPs 226 and 227 5 6.

The term "Orchard[ZSA]" in this document refers to the Orchard shielded protocol before the deployment of ZSAs, and to the OrchardZSA shielded protocol after the deployment of ZSAs.

The term "recoverable Orchard[ZSA] note" refers to an Orchard or OrchardZSA note that was created according to this proposal.

The term "Recovery Protocol" refers to a potential new shielded protocol that would allow recovery of funds held in recoverable Orchard[ZSA] notes. This ZIP describes the Recovery Protocol in outline but not in detail: many of its design decisions are intentionally left open.

Abstract

This ZIP proposes a change to the construction of Orchard[ZSA] notes starting with v6 transactions, designed to improve Zcash's long-term resilience against a significant potential threat to its security from quantum computers. It does not by itself make the protocol secure against quantum adversaries, but is intended to support a smoother transition to future versions of Zcash designed to be so.

Specifically, if it were necessary to disable the current Orchard[ZSA] shielded protocol in order to prevent a discrete-log-breaking adversary from stealing or forging funds, this change would make it possible to use a Recovery Protocol to recover existing Orchard funds. This Recovery Protocol is expected to remain secure against discrete-log-breaking and quantum adversaries.

If the Orchard[ZSA] protocol needed to be disabled for this reason, the Sapling protocol would need to be disabled as well, which would make all Sapling funds inaccessible. Sapling funds should be migrated to Orchard in order to take advantage of this change.

Motivation

If quantum computers —or any other attack that allows finding discrete logarithms on the elliptic curves used in Zcash— were to become practical, it would raise difficult issues for the Zcash community. An adversary able to compute discrete logarithms could cause arbitrary inflation or steal users' funds.

Critically, the note commitment algorithms used by the Sapling and Orchard shielded protocols are not post-quantum binding. This means that even if the proof system were upgraded to one that is believed to be secure against quantum computers, and even if the note commitment tree were to be reconstructed (from public information) to use a suitable hash function, it would still be possible for a quantum or discrete-log-breaking adversary to forge and spend notes that are not actually in the commitment tree — thus breaking the Balance property.

This ZIP proposes a small change to the way Orchard[ZSA] notes are derived. If this change is made in advance of quantum computers becoming viable, then users' Orchard[ZSA] funds could remain safe and recoverable after a post-quantum transition. This would not require any change to the Orchard or proposed OrchardZSA circuits for the time being, and would not require deciding on the particular proof system or note commitment tree hash used in the future protocol.

Recovering Orchard[ZSA] funds after the post-quantum transition would involve checking a more expensive and complicated statement in zero knowledge, but it is expected that this will be entirely practical for the intended usage of recovering funds into another shielded pool. The current privacy properties of Orchard would be retained against pre-quantum adversaries, and also against post-quantum adversaries without knowledge of the notes' addresses. 7

To reduce overall protocol complexity and analysis effort, we do not propose a similar change for Sapling. Instead, Sapling funds can be migrated to Orchard in order to make them quantum-recoverable. (Note that this analysis effort needs to include the child and internal key derivations defined in ZIP 32, which differ significantly between Sapling 8 9 and Orchard 10 11.)

This proposal is implementable at low risk, and required changes to existing libraries and wallets are small. It can be folded into other changes necessary to implement ZSAs 5 and Memo Bundles 12.

Requirements

  • Orchard[ZSA] notes constructed according to this proposal should be spendable via a Recovery Protocol (to be introduced, potentially, in a future upgrade) that is expected to be secure against discrete-log-breaking and quantum adversaries.
  • No particular choice of post-quantum proving system or commitment tree hash should be assumed for that alternate protocol.
  • The proposed scheme should be fully compatible with FROST multisignatures 13, hardware wallets, and the combination of both.
  • The proposed scheme should not require regeneration of existing non-multisignature keys or addresses.
  • The changes made to the pre-quantum protocol should not cause a significant regression in performance, applicability, or security against any given adversary class, or require significant re-analysis of that protocol's pre-quantum security.
  • The Recovery Protocol should ensure no loss of security against pre-quantum adversaries — including when FROST multisignatures and/or hardware wallets are used. (This is motivated by the fact that there is likely to be a period during which quantum attacks may be possible but very difficult.)
  • Recovery of funds from hardware wallets that support this protocol should not require exposing the pre-quantum spend authorizing key 𝖺𝗌𝗄 to theft.
  • The proposal should be compatible with Zcash Shielded Assets with minimal additional complexity 5 6.

Non-requirements

  • It is not required to address discrete-log-breaking or quantum attacks on privacy with this proposal, as long as it does not cause any regression in privacy properties.
  • It is not required to add support for the Recovery Protocol to consensus rules now.
  • It is not required for spends using the Recovery Protocol to be as efficient as spends using the Orchard, OrchardZSA, or Sapling protocols.
  • It is not anticipated that spends using the Recovery Protocol will need to be indistinguishable from non-Recovery spends.

High-level summary of changes

This subsection and the flow diagram below are non-normative.

In order to support ZSAs 5 6 and memo bundles 12, v6 transactions require in any case a new note plaintext format, with lead byte \hexint03. This gives us an opportunity to change the way that the 𝗉𝗋𝖾_𝗋𝖼𝗆 value is computed for this new format, by including all of the note fields in 𝗉𝗋𝖾_𝗋𝖼𝗆. The resulting 𝗋𝖼𝗆 is essentially a random function of the note fields — this allows us to argue that the overall commitment scheme is post-quantum binding, as long as the new derivation of 𝗋𝖼𝗆 is checked in the Recovery Protocol.

Essentially the same technique also needs to be applied to the function 𝖢𝗈𝗆𝗆𝗂𝗍𝗂𝗏𝗄 that is used to derive Orchard incoming viewing keys. In order to support existing keys, we have the Recovery Protocol check the derivations of 𝗇𝗄, 𝖺𝗄, and 𝗋𝗂𝗏𝗄 from the secret key 𝗌𝗄.

Since this derivation of (at least) 𝖺𝗄 from 𝗌𝗄 cannot be used in the case of FROST key generation, we also provide an alternative way to derive 𝗋𝗂𝗏𝗄 which is to be used in that case. This alternative derivation, using a new "quantum spending key" 𝗊𝗌𝗄 and "quantum intermediate key" 𝗊𝗄, also supports more efficient use of hardware wallets in the Recovery Protocol. It is described in sections [Usage with FROST] and [Usage with Hardware Wallets].

Flow diagram for the Orchard and OrchardZSA protocols

This diagram shows, approximately, the derivation of Orchard keys, addresses, notes, note commitments, and nullifiers. All of the flow diagrams in this ZIP omit type conversions between curve points, field elements, byte sequences, and bit sequences, and so are not sufficiently precise to be used directly as a guide for implementation.

---
title: "Key to flow diagrams"
---
graph BT
  classDef default stroke:#111111;
  classDef func fill:#d0ffb8;
  classDef cefunc fill:#a0e0a0;
  classDef sensitive fill:#ffb0b0;
  classDef semi_sensitive fill:#ffb0ff;
  classDef keybox stroke:#808080, fill:#ffffff;
  classDef spacer opacity:0;

  key([sensitive key]):::sensitive ~~~ function:::func
  value([value]) ~~~ cond{{conditional value}}
  dummyA( ) -->|existing| dummyB( )
  dummyC( ) ==>|new| dummyD( )
  dummyE( ) -.->|optional| dummyF( )

The bold lines are changes introduced by this ZIP, which all take the form of additional inputs to derivation functions or alternative derivations. The derivations shown in the box labelled "Potential recovery circuit" are, roughly speaking, those enforced by the [Proposed Recovery Protocol].

graph BT
  classDef default stroke:#111111;
  classDef func fill:#d0ffb8;
  classDef cefunc fill:#a0e0a0;
  classDef sensitive fill:#ffb0b0;
  classDef semi_sensitive fill:#ffb0ff;
  classDef circuit stroke:#000000, fill:#fffff0;
  classDef spacer opacity:0;

  PostQC:::circuit
  subgraph PostQC[<div style="margin:1.1em;font-size:20px"><b>Potential recovery circuit</b></div>]
    direction BT
    rivk_ext([rivk_ext]) --> Hrivk_int[H<sup>rivk_int</sup>]:::func
    ak([ak]) --> Hrivk_int[H<sup>rivk_int</sup>]:::func
    nk --> Hrivk_int
    rivk_ext -->|¬is_internal_rivk| rivk
    Hrivk_int -->|is_internal_rivk| rivk{{rivk}}
    rivk --> Commitivk[Commit<sup>ivk</sup>]:::func
    ak([ak]) ----> Commitivk
    nk([nk]) ----> Commitivk
    Commitivk ---> ivk([ivk])
    gd ---> ivkmul[[ivk]g<sub>d</sub>&nbsp;]:::func
    ivk --> ivkmul
    split_flag([split_flag]) --> Hpsi
    rsplit([rsplit]) -->|split_flag| rpsi{{rψ}}
    rsplit ~~~ split_flag
    rseed -->|¬split_flag| rpsi
    rpsi --> Hpsi[H<sup>ψ</sup>]:::func
    rho --> Hpsi
    leadByte([leadByte]) ==> pre_rcm
    v([v, AssetBase]) ===> pre_rcm([pre_rcm])
    pkd ====> pre_rcm
    gd ==> pre_rcm
    psi ===> pre_rcm
    rho([#8239;ρ#8239;]) --> pre_rcm
    v ~~~~ rcm
    ivkmul --> pkd([pk<sub>d</sub>])
    pre_rcm --> Hrcm[H<sup>rcm</sup>]:::func
    rseed([rseed]) --> Hrcm
    Hrcm --> rcm([rcm])
    Hpsi --> psi([#8239;ψ#8239;])
    gd --> NoteCommit:::func
    pkd --> NoteCommit
    v --> NoteCommit
    psi --> NoteCommit
    rcm --> NoteCommit
    rho --> NoteCommit
    cm --> DeriveNullifier:::func
    psi --> DeriveNullifier
    rho --> DeriveNullifier
    nk --> DeriveNullifier
    NoteCommit --> cm([cm])
    DeriveNullifier --> nf([nf])
    cm --> cmx([cm<sub><i>x</i></sub>])
  end

  d([#8239;d#8239;]) --> HashToCurve:::func ------> gd([g<sub>d</sub>])

Specification

Usage with FROST

When generating Orchard keys for FROST, 𝖺𝗄 will be derived jointly from the participants' shares of 𝖺𝗌𝗄 according to the FROST Distributed Key Generation (DKG) protocol.

This ZIP further constrains FROST key generation for Orchard as follows: participants MUST privately agree on a value 𝗌𝗄, and then use it with 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 to derive 𝗇𝗄, 𝗊𝗌𝗄, 𝗊𝗄, and (using the 𝖺𝗄 output by the DKG protocol) 𝗋𝗂𝗏𝗄_𝖾𝗑𝗍, as specified in § 4.2.3 ‘Orchard Key Components’.

graph BT
  classDef default stroke:#111111;
  classDef func fill:#d0ffb8;
  classDef cefunc fill:#a0e0a0;
  classDef sensitive fill:#ffb0b0;
  classDef semi_sensitive fill:#ffb0ff;
  classDef circuit stroke:#000000, fill:#fffff0;
  classDef spacer opacity:0;

  sk([sk]):::sensitive --> Hnk[H<sup>nk</sup>]:::func ----> nk([nk])
  sk --> Hqsk[H<sup>qsk</sup>]:::func --> qsk([qsk]):::sensitive
  qk ==> Hrivk_ext
  nk ==> Hrivk_ext
  FROST_DKG --> ak([ak]) --> FROST_Sign
  FROST_DKG ~~~ s( ):::spacer ~~~ FROST_Sign
  FROST_DKG --> ask([ask<sub>i</sub>]):::sensitive --> FROST_Sign
  ak ==> Hrivk_ext[H<sup>rivk_ext</sup>]:::func
  Hrivk_ext ==>|use_qsk| rivk_ext([rivk_ext])

  HWW1:::circuit
  subgraph HWW1[<div style="margin:1.6em;font-size:20px"><br><br><br><br><br><br><br><br><br><b>SoK<sup>qsk</sup></b></div>]
    qsk([qsk]):::sensitive ==> Hqk[H<sup>qk</sup>]:::cefunc
    Hqk ==> qk([qk]):::semi_sensitive
  end

The protocol MUST ensure that all participants obtain the same values for 𝖺𝗄, 𝗇𝗄, and 𝗋𝗂𝗏𝗄_𝖾𝗑𝗍. (Provided that the participants have followed § 4.2.3, this implies that they have the same 𝗊𝗌𝗄 and 𝗊𝗄, by collision resistance of 𝖧𝗊𝗄 and 𝖧𝗋𝗂𝗏𝗄_𝖾𝗑𝗍.)

Each participant MUST treat 𝗊𝗌𝗄 as a secret held at least as securely and reliably as 𝖺𝗌𝗄. Note that 𝗊𝗌𝗄 will not be needed to sign unless and until the Recovery Protocol is needed, but it is essential that it be retained, otherwise funds could be lost if and when the current Orchard protocol is disabled.

The Recovery Protocol will still require a RedDSA signature verifiable by the Spend validating key 𝖺𝗄, in addition to knowledge of 𝗊𝗌𝗄. Although RedDSA is not secure in the long term against a quantum or discrete-log-breaking adversary, the most likely eventuality is that attacks against it will be difficult for some years after the current Orchard protocol is disabled. During this period, checking this RedDSA signature in the Recovery Protocol will ensure that spend authorization by a t-of-n threshold of participants continues to be needed against classical adversaries. This retains the usual advantage of FROST that the parties can sign using their shares without reconstructing the Spend authorizing key 𝖺𝗌𝗄. Coalitions of fewer than t of the participants will be unable to authorize spends as long as they do not have access to a sufficiently powerful quantum computer.

Note that a quantum adversary may be able to steal the funds with only access to 𝗊𝗌𝗄, which is held by every participant. Therefore, it is RECOMMENDED that as soon as a fully post-quantum protocol that supports multisignatures is available, all funds held under FROST keys be transferred into that protocol's shielded pool.

Usage with hardware wallets

The same 𝗎𝗌𝖾_𝗊𝗌𝗄 option can help to improve the efficiency of using the Recovery Protocol with hardware wallets. If the keys 𝗋𝗂𝗏𝗄_𝖾𝗑𝗍, 𝗇𝗄, and 𝖺𝗄 are generated from 𝗌𝗄, then the circuit for the Recovery Protocol will need to prove their correct derivation using 𝖧𝗋𝗂𝗏𝗄_𝗅𝖾𝗀𝖺𝖼𝗒, 𝖧𝗇𝗄, 𝖧𝖺𝗌𝗄, and 𝖣𝖾𝗋𝗂𝗏𝖾𝖯𝗎𝖻𝗅𝗂𝖼 as shown in the 𝖲𝗈𝖪𝗌𝗄 box of the diagram below.

graph BT
  classDef default stroke:#111111;
  classDef func fill:#d0ffb8;
  classDef cefunc fill:#a0e0a0;
  classDef sensitive fill:#ffb0b0;
  classDef semi_sensitive fill:#ffb0ff;
  classDef circuit stroke:#000000, fill:#fffff0;
  classDef spacer opacity:0;

  HWW1:::circuit
  subgraph HWW1[<div style="margin:1.1em;font-size:20px"><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><b>SoK<sup>sk</sup></b></div>]
    sk --> Hrivk_legacy[H<sup>rivk_legacy</sup>]:::func ----> rivk_legacy
    DerivePublic --> ak([ak])
    sk([sk]):::sensitive --> Hnk[H<sup>nk</sup>]:::func ----> nk([nk])
    sk --> Hask[H<sup>ask</sup>]:::func --> ask([ask]):::sensitive
    ask --> DerivePublic:::func
  end

  HWW2:::circuit
  subgraph HWW2[<div style="margin:1.6em;font-size:20px"><br><br><br><br><br><br><br><br><br><b>SoK<sup>qsk</sup></b></div>]
    qsk([qsk]):::sensitive ==> Hqk[H<sup>qk</sup>]:::cefunc
    Hqk ==> qk([qk]):::semi_sensitive
  end

  sk -.-> Hqsk[H<sup>qsk</sup>]:::func -.-> qsk
  qk ==> Hrivk_ext[H<sup>rivk_ext</sup>]:::func
  nk([nk]) ==> Hrivk_ext
  ak ==> Hrivk_ext
  Hrivk_ext ==>|use_qsk| rivk_ext{{rivk_ext}}
  rivk_legacy([rivk_legacy]) -->|¬use_qsk| rivk_ext

When 𝗎𝗌𝖾_𝗊𝗌𝗄 is used, on the other hand, it is possible for the Recovery Protocol to support spend authorization using a much smaller circuit that only uses 𝖧𝗊𝗄 and a commitment scheme. For example, for some hiding and collapse binding commitment c=𝖫𝗂𝗇𝗄𝖢𝗈𝗆𝗆𝗂𝗍r(𝗊𝗄,𝗌𝗂𝗀𝗁𝖺𝗌𝗁), the hardware wallet could prove knowledge of (𝗊𝗌𝗄,r) such that c=𝖫𝗂𝗇𝗄𝖢𝗈𝗆𝗆𝗂𝗍r(𝖧𝗊𝗄(𝗊𝗌𝗄),𝗌𝗂𝗀𝗁𝖺𝗌𝗁). This circuit is relatively small and it might be feasible to do the proof in quite constrained hardware.

The host wallet would be given 𝗇𝗄, 𝖺𝗄, and 𝗊𝗄 for use in the main Recovery circuit (discussed later). The commitment c would be a public input opened to (𝗊𝗄,𝗌𝗂𝗀𝗁𝖺𝗌𝗁) in that circuit, ensuring that the hardware wallet has authorized the spend for the correct key. Because 𝖫𝗂𝗇𝗄𝖢𝗈𝗆𝗆𝗂𝗍 is hiding and the proofs are zero knowledge, no information is leaked about which 𝗊𝗄 is being used.

Alternatively, if the hardware wallet is unable to support making proofs at all, it could be updated to permit exporting 𝗊𝗌𝗄. This is less secure against a quantum adversary that is able to obtain 𝗊𝗌𝗄, but it allows the funds to be transferred to another key or protocol. A quantum adversary would have to break RedDSA in order to steal funds even if it were to obtain 𝗊𝗌𝗄, since 𝖺𝗌𝗄 would remain on the hardware wallet.

Specification Updates

This is written as a set of changes to version 2025.6.1 of the protocol specification, and to the contents of ZIPs at the time of writing in October 2025 (as proposed for the NU6.1 upgrade). It will need to be merged with other changes for v6 transactions (memo bundles 12 and ZSAs 5 6).

Changes to the Protocol Specification

Some of the suggested changes to the protocol specification are refactoring to make it easier to consistently specify the rules on allowed note plaintext lead bytes and generation of note components, without duplication between sections. It is suggested to do this refactoring first, independently of the semantic changes for quantum recoverability, and then to simplify this ZIP accordingly.

§ 3.1 ‘Payment Addresses and Keys’

Add a ∗ to the arrows leading to 𝖺𝗌𝗄 and 𝗋𝗂𝗏𝗄 in the Orchard key components diagram, with the following note:

∗ The derivations of 𝖺𝗌𝗄 and 𝗋𝗂𝗏𝗄 shown are not the only possibility. For further detail see § 4.2.3 ‘Orchard Key Components’.

§ 3.2.1 ‘Note Plaintexts and Memo Fields’

Replace the paragraph

The field 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾 indicates the version of the encoding of a Sapling or Orchard note plaintext. For Sapling it is \hexint01 before activation of the Canopy network upgrade and \hexint02 afterward, as specified in [ZIP-212]. For Orchard note plaintexts it is always \hexint02.

with

The field 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾 indicates the version of the encoding of a Sapling or Orchard note plaintext.

Let the constants 𝖢𝖺𝗇𝗈𝗉𝗒𝖠𝖼𝗍𝗂𝗏𝖺𝗍𝗂𝗈𝗇𝖧𝖾𝗂𝗀𝗁𝗍 and 𝖹𝖨𝖯𝟤𝟣𝟤𝖦𝗋𝖺𝖼𝖾𝖯𝖾𝗋𝗂𝗈𝖽 be as defined in § 5.3 ‘Constants’.

Let 𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅⦂{𝖲𝖺𝗉𝗅𝗂𝗇𝗀,𝖮𝗋𝖼𝗁𝖺𝗋𝖽} be the shielded protocol of the note.

Let 𝗁𝖾𝗂𝗀𝗁𝗍 be the block height of the block containing the transaction having the encrypted note plaintext as an output, and let 𝗍𝗑𝖵𝖾𝗋𝗌𝗂𝗈𝗇 be the transaction version number.

Define 𝖺𝗅𝗅𝗈𝗐𝖾𝖽𝖫𝖾𝖺𝖽𝖡𝗒𝗍𝖾𝗌𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅(𝗁𝖾𝗂𝗀𝗁𝗍,𝗍𝗑𝖵𝖾𝗋𝗌𝗂𝗈𝗇)= {\setof\hexint01,\caseif𝗁𝖾𝗂𝗀𝗁𝗍<𝖢𝖺𝗇𝗈𝗉𝗒𝖠𝖼𝗍𝗂𝗏𝖺𝗍𝗂𝗈𝗇𝖧𝖾𝗂𝗀𝗁𝗍\setof\hexint01,\hexint02,\caseif𝖢𝖺𝗇𝗈𝗉𝗒𝖠𝖼𝗍𝗂𝗏𝖺𝗍𝗂𝗈𝗇𝖧𝖾𝗂𝗀𝗁𝗍≤𝗁𝖾𝗂𝗀𝗁𝗍<𝖢𝖺𝗇𝗈𝗉𝗒𝖠𝖼𝗍𝗂𝗏𝖺𝗍𝗂𝗈𝗇𝖧𝖾𝗂𝗀𝗁𝗍+𝖹𝖨𝖯𝟤𝟣𝟤𝖦𝗋𝖺𝖼𝖾𝖯𝖾𝗋𝗂𝗈𝖽\setof\hexint02,\caseif𝖢𝖺𝗇𝗈𝗉𝗒𝖠𝖼𝗍𝗂𝗏𝖺𝗍𝗂𝗈𝗇𝖧𝖾𝗂𝗀𝗁𝗍+𝖹𝖨𝖯𝟤𝟣𝟤𝖦𝗋𝖺𝖼𝖾𝖯𝖾𝗋𝗂𝗈𝖽≤𝗁𝖾𝗂𝗀𝗁𝗍 and 𝗍𝗑𝖵𝖾𝗋𝗌𝗂𝗈𝗇<6\setof\hexint03,otherwise.

The 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾 of a Sapling or Orchard note MUST satisfy 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾∈𝖺𝗅𝗅𝗈𝗐𝖾𝖽𝖫𝖾𝖺𝖽𝖡𝗒𝗍𝖾𝗌𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅(𝗁𝖾𝗂𝗀𝗁𝗍,𝗍𝗑𝖵𝖾𝗋𝗌𝗂𝗈𝗇). Senders SHOULD choose the highest lead byte allowed under this condition.

Non-normative notes:

  • Since Orchard was introduced after the end of the [ZIP-212]) grace period, note plaintexts for Orchard notes MUST have 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾≥𝟶𝚡𝟶𝟸.
  • It is intentional that the definition of 𝖺𝗅𝗅𝗈𝗐𝖾𝖽𝖫𝖾𝖺𝖽𝖡𝗒𝗍𝖾𝗌 does not currently depend on 𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅. It might do so in future.

§ 4.1.2 ‘Pseudo Random Functions’

In the list of places where 𝖯𝖱𝖥𝖾𝗑𝗉𝖺𝗇𝖽 is used:

Replace

  • [NU5 onward] in § 4.2.3 ‘Orchard Key Components’, with inputs [6], [7], [8], and with first byte \hexint82 (the last of these is also specified in [ZIP-32]);
  • in the processes of sending (§ 4.7.2 ‘Sending Notes (Sapling)’ and § 4.7.3 ‘Sending Notes (Orchard)’) and of receiving (§ 4.20 ‘In-band secret distribution (Sapling and Orchard)’) notes, with inputs [4] and [5], and for Orchard [t]||ρ with t∈{5,4,9};

with

  • [NU5 onward] in § 4.2.3 ‘Orchard Key Components’, with inputs [\hexint06], [\hexint07], [\hexint08], with first byte in \setof\hexint0C,\hexint0D (also specified in {{ reference to this ZIP }}), and with first byte \hexint82 (also specified in [ZIP-32]);
  • in the processes of sending (§ 4.7.2 ‘Sending Notes (Sapling)’ and § 4.7.3 ‘Sending Notes (Orchard)’) and of receiving (§ 4.20 ‘In-band secret distribution (Sapling and Orchard)’) notes, for Sapling with inputs [\hexint04] and [\hexint05], and for Orchard with first byte in \setof\hexint05,\hexint04,\hexint09,\hexint0A,\hexint0B (\hexint0A and \hexint0B are also specified in {{ reference to this ZIP }});

Add

  • in {{ reference to this ZIP }}, with first byte in \setof\hexint0A,\hexint0B,\hexint0C,\hexint0D.

Also change the remaining decimal constants to hex for consistency.

§ 4.2.3 ‘Orchard Key Components’

Add ℓ𝗊𝗌𝗄 and ℓ𝗊𝗄 to the constants obtained from § 5.3 ‘Constants’.

Insert after the definition of 𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝖮𝗋𝖼𝗁𝖺𝗋𝖽:

Define 𝖧𝗊𝗄𝗋𝗂𝗏𝗄_𝖾𝗑𝗍(𝖺𝗄,𝗇𝗄)=𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗊𝗄𝖾𝗑𝗉𝖺𝗇𝖽([\hexint0D]||𝖨𝟤𝖫𝖤𝖮𝖲𝖯256(𝖺𝗄) ||𝖨𝟤𝖫𝖤𝖮𝖲𝖯256(𝗇𝗄))).

Replace from "From this spending key" up to and including the line "let 𝖺𝗄=𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ(𝖺𝗄ℙ)" in the algorithm with:

Under normal circumstances, the Spend authorizing key 𝖺𝗌𝗄⦂𝔽rℙ∗ is derived directly from 𝗌𝗄. However, this might not be the case for protocols that require distributed generation of shares of 𝖺𝗌𝗄, such as FROST's Distributed Key Generation 14 (see {{ reference to the [Usage with FROST] section of this ZIP }} for further discussion). To support this we define an alternative derivation method using an additional "quantum spending key", 𝗊𝗌𝗄, and "quantum intermediate key", 𝗊𝗄.

There can also be advantages to not deriving 𝖺𝗌𝗄 directly from 𝗌𝗄 for hardware wallets, in order to separate the authority to make proofs for the Recovery Protocol from the spend authorization key that is kept on the device (see {{ reference to the [Usage with hardware wallets] section of this ZIP }}). The derivation from 𝗊𝗌𝗄 can also be used in that case.

Let 𝗎𝗌𝖾_𝗊𝗌𝗄⦂𝔹 be a flag that is set to true when the derivation from 𝗊𝗌𝗄 is used, or false if 𝖺𝗌𝗄 is derived directly from 𝗌𝗄. (In cases where it is desired for the generation of key components to match versions of this specification prior to {{ fill in version }}, 𝗎𝗌𝖾_𝗊𝗌𝗄 needs to be set to false.)

Define:

  • 𝖧𝖺𝗌𝗄(𝗌𝗄)=𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗌𝗄𝖾𝗑𝗉𝖺𝗇𝖽([\hexint06]))
  • 𝖧𝗇𝗄(𝗌𝗄)=𝖳𝗈𝖡𝖺𝗌𝖾𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗌𝗄𝖾𝗑𝗉𝖺𝗇𝖽([\hexint07]))
  • 𝖧𝗋𝗂𝗏𝗄(𝗌𝗄)=𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗌𝗄𝖾𝗑𝗉𝖺𝗇𝖽([\hexint08]))
  • 𝖧𝗊𝗌𝗄(𝗌𝗄)=𝗍𝗋𝗎𝗇𝖼𝖺𝗍𝖾32(𝖯𝖱𝖥𝗌𝗄𝖾𝗑𝗉𝖺𝗇𝖽([\hexint0C]))
  • 𝖧𝗊𝗄(𝗊𝗌𝗄)=𝖡𝖫𝖠𝖪𝖤𝟤𝗌-𝟤𝟧𝟨(“𝚉𝚌𝚊𝚜𝚑_𝚚𝚔”,𝗊𝗌𝗄).

𝖺𝗌𝗄⦂𝔽rℙ∗, the Spend validating key 𝖺𝗄⦂{1..qℙ−1}, the nullifier deriving key 𝗇𝗄⦂𝔽qℙ, the optional quantum spending key 𝗊𝗌𝗄⦂𝔹𝕐[ℓ𝗊𝗌𝗄/8]∪{⊥} and quantum intermediate key 𝗊𝗄⦂𝔹𝕐[ℓ𝗊𝗄/8]∪{⊥} the 𝖢𝗈𝗆𝗆𝗂𝗍𝗂𝗏𝗄 randomness 𝗋𝗂𝗏𝗄⦂𝔽rℙ, the diversifier key 𝖽𝗄⦂𝔹𝕐[ℓ𝖽𝗄/8], the 𝖪𝖠𝖮𝗋𝖼𝗁𝖺𝗋𝖽 private key 𝗂𝗏𝗄⦂{1..qℙ−1}, the outgoing viewing key 𝗈𝗏𝗄⦂𝔹𝕐[ℓ𝗈𝗏𝗄/8], and corresponding “internal” keys MUST be derived as follows:

let 𝗇𝗄=𝖧𝗇𝗄(𝗌𝗄)
if 𝗎𝗌𝖾_𝗊𝗌𝗄:
generate 𝖺𝗌𝗄⦂𝔽rℙ∗ and corresponding 𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽 public key 𝖺𝗄ℙ⦂ℙ∗
using any suitably secure method that ensures the last bit of 𝗋𝖾𝗉𝗋ℙ(𝖺𝗄ℙ) is 0.
let 𝖺𝗄=𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ(𝖺𝗄ℙ)
let 𝗊𝗌𝗄=𝖧𝗊𝗌𝗄(𝗌𝗄)
let 𝗊𝗄=𝖧𝗊𝗄(𝗊𝗌𝗄)
let 𝗋𝗂𝗏𝗄=𝖧𝗊𝗄𝗋𝗂𝗏𝗄_𝖾𝗑𝗍(𝖺𝗄,𝗇𝗄)
else:
let mutable 𝖺𝗌𝗄←𝖧𝖺𝗌𝗄(𝗌𝗄)
let 𝖺𝗄ℙ=𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽.𝖣𝖾𝗋𝗂𝗏𝖾𝖯𝗎𝖻𝗅𝗂𝖼(𝖺𝗌𝗄)
if the last bit (that is, the y~ bit) of 𝗋𝖾𝗉𝗋ℙ(𝖺𝗄ℙ) is 1:
set 𝖺𝗌𝗄←−𝖺𝗌𝗄

let 𝖺𝗄=𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ(𝖺𝗄ℙ)
let 𝗊𝗌𝗄=𝗊𝗄=⊥ (there are no quantum spending/intermediate keys in this case)
let 𝗋𝗂𝗏𝗄=𝖧𝗋𝗂𝗏𝗄(𝗌𝗄)

Add the following notes:

  • If 𝖺𝗌𝗄, 𝖺𝗄, 𝗇𝗄, 𝗋𝗂𝗏𝗄, and 𝗋𝗂𝗏𝗄𝗂𝗇𝗍𝖾𝗋𝗇𝖺𝗅 are not generated as specified in this section, then it may not be possible to recover the resulting notes as specified in {{ reference to this ZIP }} in the event that attacks using quantum computers become practical. In addition, to recover notes it is necessary to retain, or be able to rederive the following information.
    • When 𝗎𝗌𝖾_𝗊𝗌𝗄 is 𝖿𝖺𝗅𝗌𝖾: the secret key 𝗌𝗄. This will be the case when 𝗌𝗄 is generated according to [ZIP-32], and the master seed or any secret key and chain code above 𝗌𝗄 in the derivation hierarchy is retained.
    • When 𝗎𝗌𝖾_𝗊𝗌𝗄 is 𝗍𝗋𝗎𝖾: the combination of the full viewing key (𝖺𝗄,𝗇𝗄,𝗋𝗂𝗏𝗄), the quantum spending key 𝗊𝗌𝗄, and the ability to make spend authorization signatures with 𝖺𝗌𝗄.
    When the latter option is used, see {{ reference to the [Usage with hardware wallets] section of this ZIP }} for recommendations on the storage of, and access to 𝗊𝗌𝗄 and 𝗊𝗄.

§ 4.7.2 ‘Sending Notes (Sapling)’

Replace

Let 𝖢𝖺𝗇𝗈𝗉𝗒𝖠𝖼𝗍𝗂𝗏𝖺𝗍𝗂𝗈𝗇𝖧𝖾𝗂𝗀𝗁𝗍 be as defined in § 5.3 ‘Constants’.

Let 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾 be the note plaintext lead byte. This MUST be \hexint01 if for the next block, 𝗁𝖾𝗂𝗀𝗁𝗍<𝖢𝖺𝗇𝗈𝗉𝗒𝖠𝖼𝗍𝗂𝗏𝖺𝗍𝗂𝗈𝗇𝖧𝖾𝗂𝗀𝗁𝗍, or \hexint02 if 𝗁𝖾𝗂𝗀𝗁𝗍≥𝖢𝖺𝗇𝗈𝗉𝗒𝖠𝖼𝗍𝗂𝗏𝖺𝗍𝗂𝗈𝗇𝖧𝖾𝗂𝗀𝗁𝗍.

with

Let 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾 be the note plaintext lead byte, chosen according to § 3.2.1 ‘Note Plaintexts and Memo Fields’ with 𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅=𝖲𝖺𝗉𝗅𝗂𝗇𝗀.

Define 𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖲𝖺𝗉𝗅𝗂𝗇𝗀(_,_)=𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝖲𝖺𝗉𝗅𝗂𝗇𝗀(𝖯𝖱𝖥𝗋𝗌𝖾𝖾𝖽𝖾𝗑𝗉𝖺𝗇𝖽([\hexint04]))

Define 𝖧𝗋𝗌𝖾𝖾𝖽𝖾𝗌𝗄,𝖲𝖺𝗉𝗅𝗂𝗇𝗀(_,_)=𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝖲𝖺𝗉𝗅𝗂𝗇𝗀(𝖯𝖱𝖥𝗋𝗌𝖾𝖾𝖽𝖾𝗑𝗉𝖺𝗇𝖽([\hexint05])).

(𝖧𝗋𝖼𝗆,𝖲𝖺𝗉𝗅𝗂𝗇𝗀 and 𝖧𝖾𝗌𝗄,𝖲𝖺𝗉𝗅𝗂𝗇𝗀 intentionally take arguments that are unused.)

Replace the lines deriving 𝗋𝖼𝗆 and 𝖾𝗌𝗄 with

Derive 𝗋𝖼𝗆=𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖲𝖺𝗉𝗅𝗂𝗇𝗀(⊥,⊥)

Derive 𝖾𝗌𝗄=𝖧𝗋𝗌𝖾𝖾𝖽𝖾𝗌𝗄,𝖲𝖺𝗉𝗅𝗂𝗇𝗀(⊥,⊥)

§ 4.7.3 ‘Sending Notes (Orchard)’

Replace

Let 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾 be the note plaintext lead byte, which MUST be 𝟶𝚡𝟶𝟸.

with

Let 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾 be the note plaintext lead byte, chosen according to § 3.2.1 ‘Note Plaintexts and Memo Fields’ with 𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅=𝖮𝗋𝖼𝗁𝖺𝗋𝖽.

Define 𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,(𝗀⋆𝖽,𝗉𝗄⋆𝖽,𝗏,ρ,ψ[,𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆]))= 𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗋𝗌𝖾𝖾𝖽𝖾𝗑𝗉𝖺𝗇𝖽(𝗉𝗋𝖾_𝗋𝖼𝗆))

where 𝗉𝗋𝖾_𝗋𝖼𝗆={[\hexint05]||ρ,\caseif𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾=\hexint02[\hexint0B,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾]||𝖫𝖤𝖡𝖲𝟤𝖮𝖲𝖯256(𝗀⋆𝖽)||𝖫𝖤𝖡𝖲𝟤𝖮𝖲𝖯256(𝗉𝗄⋆𝖽)[\hexint0B,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾]||𝖨𝟤𝖫𝖤𝖮𝖲𝖯64(𝗏)||ρ||𝖨𝟤𝖫𝖤𝖮𝖲𝖯256(ψ)[\hexint0B,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾][||𝖫𝖤𝖡𝖲𝟤𝖮𝖲𝖯256(𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆)],\caseif𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾=\hexint03

Define 𝖧𝗋𝗌𝖾𝖾𝖽𝖾𝗌𝗄,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(ρ)=𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗋𝗌𝖾𝖾𝖽𝖾𝗑𝗉𝖺𝗇𝖽([\hexint04]||ρ)).

Define 𝖧𝗋𝗌𝖾𝖾𝖽ψ,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(ρ,𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀)=𝖳𝗈𝖡𝖺𝗌𝖾𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗋𝗌𝖾𝖾𝖽𝖾𝗑𝗉𝖺𝗇𝖽([𝗌𝗉𝗅𝗂𝗍_𝖽𝗈𝗆𝖺𝗂𝗇]||ρ)). where 𝗌𝗉𝗅𝗂𝗍_𝖽𝗈𝗆𝖺𝗂𝗇={\hexint09if 𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀=0\hexint0Aif 𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀=1.

Insert before the derivation of 𝖾𝗌𝗄:

Let 𝗀⋆𝖽=𝗋𝖾𝗉𝗋ℙ(𝗀𝖽), 𝗉𝗄⋆𝖽=𝗋𝖾𝗉𝗋ℙ(𝗉𝗄𝖽)[, and 𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆=𝗋𝖾𝗉𝗋ℙ(𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾)].

and use these in the inputs to 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍𝖮𝗋𝖼𝗁𝖺𝗋𝖽.

Replace the lines deriving 𝖾𝗌𝗄, 𝗋𝖼𝗆, and ψ with

Derive 𝖾𝗌𝗄=𝖧𝗋𝗌𝖾𝖾𝖽𝖾𝗌𝗄,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(ρ)

Derive 𝗋𝖼𝗆=𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,(𝗀⋆𝖽,𝗉𝗄⋆𝖽,𝗏,ρ,ψ[,𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆]))

Derive ψ=𝖧𝗋𝗌𝖾𝖾𝖽ψ,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(ρ,𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀)

§ 4.8.2 ‘Dummy Notes (Sapling)’

Add

Let 𝖧𝗋𝖼𝗆,𝖲𝖺𝗉𝗅𝗂𝗇𝗀 be as defined in § 4.7.2 ‘Sending Notes (Sapling)’.

Replace the line deriving 𝗋𝖼𝗆 with

Derive 𝗋𝖼𝗆=𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖲𝖺𝗉𝗅𝗂𝗇𝗀(⊥,⊥)

Correct

A Spend description for a dummy Sapling input note is constructed as follows:

to

A Spend description for a dummy Sapling input note with note plaintext lead byte 𝟶𝚡𝟶𝟸 is constructed as follows:

§ 4.8.3 ‘Dummy Notes (Orchard)’

Insert before "The spend-related fields ...":

Let 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾 be the note plaintext lead byte, chosen according to § 3.2.1 ‘Note Plaintexts and Memo Fields’ with 𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅=𝖮𝗋𝖼𝗁𝖺𝗋𝖽.

Let 𝖧𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽 and 𝖧ψ,𝖮𝗋𝖼𝗁𝖺𝗋𝖽 be as defined in § 4.7.3 ‘Sending Notes (Orchard)’.

Replace the lines deriving 𝗋𝖼𝗆 and ψ with

Let 𝗀⋆𝖽=𝗋𝖾𝗉𝗋ℙ(𝗀𝖽), 𝗉𝗄⋆𝖽=𝗋𝖾𝗉𝗋ℙ(𝗉𝗄𝖽)[, and 𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆=𝗋𝖾𝗉𝗋ℙ(𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾)].

Derive 𝗋𝖼𝗆=𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,(𝗀⋆𝖽,𝗉𝗄⋆𝖽,𝗏,ρ,ψ[,𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆]))

Derive ψ=𝖧𝗋𝗌𝖾𝖾𝖽ψ,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(ρ,0)

and use 𝗀⋆𝖽, 𝗉𝗄⋆𝖽, and 𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆ in the inputs to 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍𝖮𝗋𝖼𝗁𝖺𝗋𝖽.

§ 4.20.2 and § 4.20.3 ‘Decryption using an Incoming/Full Viewing Key (Sapling and Orchard)’

For both § 4.20.2 and § 4.20.3, replace

Let the constants 𝖢𝖺𝗇𝗈𝗉𝗒𝖠𝖼𝗍𝗂𝗏𝖺𝗍𝗂𝗈𝗇𝖧𝖾𝗂𝗀𝗁𝗍 and 𝖹𝖨𝖯𝟤𝟣𝟤𝖦𝗋𝖺𝖼𝖾𝖯𝖾𝗋𝗂𝗈𝖽 be as defined in § 5.3 ‘Constants’.

Let 𝗁𝖾𝗂𝗀𝗁𝗍 be the block height of the block containing this transaction.

with

Let 𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅 and 𝖺𝗅𝗅𝗈𝗐𝖾𝖽𝖫𝖾𝖺𝖽𝖡𝗒𝗍𝖾𝗌 be as defined in § 3.2.1 ‘Note Plaintexts and Memo Fields’.

Let 𝖧𝗋𝖼𝗆,𝖲𝖺𝗉𝗅𝗂𝗇𝗀 and 𝖧esk,𝖲𝖺𝗉𝗅𝗂𝗇𝗀 be as defined in § 4.7.2 ‘Sending Notes (Sapling)’.

Let 𝖧𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽, 𝖧esk,𝖮𝗋𝖼𝗁𝖺𝗋𝖽, and 𝖧ψ,𝖮𝗋𝖼𝗁𝖺𝗋𝖽 be as defined in § 4.7.3 ‘Sending Notes (Orchard)’.

Let 𝗁𝖾𝗂𝗀𝗁𝗍 be the block height of the block containing this transaction, and let 𝗍𝗑𝖵𝖾𝗋𝗌𝗂𝗈𝗇 be the transaction version.

For § 4.20.3, replace

if 𝖾𝗌𝗄≥r𝔾 or 𝗉𝗄𝖽=⊥, return ⊥

with

if 𝖾𝗌𝗄≥r𝔾 or 𝗉𝗄𝖽=⊥ or (for Sapling) 𝗉𝗄𝖽∉𝕁(r)∗ (see note below), return ⊥

and in the note containing "However, this is technically redundant with the later check that returns ⊥ if 𝗉𝗄𝖽∉𝕁(r)∗", delete the word "later".

For both § 4.20.2 and § 4.20.3, replace

[Pre-Canopy] if 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾≠𝟶𝚡𝟶𝟷, return ⊥
[Pre-Canopy] let 𝗋𝖼𝗆=𝗋𝗌𝖾𝖾𝖽
[Canopy onward] if 𝗁𝖾𝗂𝗀𝗁𝗍<𝖢𝖺𝗇𝗈𝗉𝗒𝖠𝖼𝗍𝗂𝗏𝖺𝗍𝗂𝗈𝗇𝖧𝖾𝗂𝗀𝗁𝗍+𝖹𝖨𝖯𝟤𝟣𝟤𝖦𝗋𝖺𝖼𝖾𝖯𝖾𝗋𝗂𝗈𝖽 and 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾∉{𝟶𝚡𝟶𝟷,𝟶𝚡𝟶𝟸}, return ⊥
[Canopy onward] if 𝗁𝖾𝗂𝗀𝗁𝗍≥𝖢𝖺𝗇𝗈𝗉𝗒𝖠𝖼𝗍𝗂𝗏𝖺𝗍𝗂𝗈𝗇𝖧𝖾𝗂𝗀𝗁𝗍+𝖹𝖨𝖯𝟤𝟣𝟤𝖦𝗋𝖺𝖼𝖾𝖯𝖾𝗋𝗂𝗈𝖽 and 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾≠𝟶𝚡𝟶𝟸, return ⊥
for Sapling, let 𝗉𝗋𝖾_𝗋𝖼𝗆=[4] and 𝗉𝗋𝖾_𝖾𝗌𝗄=[5]
for Orchard, let ρ=𝖨𝟤𝖫𝖤𝖮𝖲𝖯256(𝗇𝖿𝗈𝗅𝖽 from the same Action description ), 𝗉𝗋𝖾_𝗋𝖼𝗆=[5]||ρ, and 𝗉𝗋𝖾_𝖾𝗌𝗄=[4]||ρ

with

if 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾∉𝖺𝗅𝗅𝗈𝗐𝖾𝖽𝖫𝖾𝖺𝖽𝖡𝗒𝗍𝖾𝗌𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅(𝗁𝖾𝗂𝗀𝗁𝗍,𝗍𝗑𝖵𝖾𝗋𝗌𝗂𝗈𝗇), return ⊥
let ρ=𝖨𝟤𝖫𝖤𝖮𝖲𝖯256(𝗇𝖿𝗈𝗅𝖽 from the same Action description )

For § 4.20.2, replace

[Canopy onward] let 𝗋𝖼𝗆={𝗋𝗌𝖾𝖾𝖽,if 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾=hexint01𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅(𝖯𝖱𝖥𝗋𝗌𝖾𝖾𝖽𝖾𝗑𝗉𝖺𝗇𝖽(𝗉𝗋𝖾_𝗋𝖼𝗆)),otherwise
let 𝗋𝖼𝗆=𝖫𝖤𝖮𝖲𝟤𝖨𝖯256(𝗋𝖼𝗆) and 𝗀𝖽=𝖣𝗂𝗏𝖾𝗋𝗌𝗂𝖿𝗒𝖧𝖺𝗌𝗁(𝖽). if 𝗋𝖼𝗆≥r𝔾 or (for Sapling) 𝗀𝖽=⊥, return ⊥
[Canopy onward] if 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾≠hexint01:
𝖾𝗌𝗄=𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅(𝖯𝖱𝖥𝗋𝗌𝖾𝖾𝖽𝖾𝗑𝗉𝖺𝗇𝖽(𝗉𝗋𝖾_𝖾𝗌𝗄))
if 𝗋𝖾𝗉𝗋𝔾(𝖪𝖠.𝖣𝖾𝗋𝗂𝗏𝖾𝖯𝗎𝖻𝗅𝗂𝖼(𝖾𝗌𝗄,𝗀𝖽))≠𝚎𝚙𝚑𝚎𝚖𝚎𝚛𝚊𝚕𝙺𝚎𝚢, return ⊥

let 𝗉𝗄𝖽=𝖪𝖠.𝖣𝖾𝗋𝗂𝗏𝖾𝖯𝗎𝖻𝗅𝗂𝖼(𝗂𝗏𝗄,𝗀𝖽)

with

let 𝗀𝖽=𝖣𝗂𝗏𝖾𝗋𝗌𝗂𝖿𝗒𝖧𝖺𝗌𝗁(𝖽); for Sapling, if 𝗀𝖽=⊥, return ⊥
[Canopy onward] if 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾≠hexint01:
let 𝖾𝗌𝗄=𝖧𝗋𝗌𝖾𝖾𝖽𝖾𝗌𝗄,𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅(ρ)
if 𝗋𝖾𝗉𝗋𝔾(𝖪𝖠.𝖣𝖾𝗋𝗂𝗏𝖾𝖯𝗎𝖻𝗅𝗂𝖼(𝖾𝗌𝗄,𝗀𝖽))≠𝚎𝚙𝚑𝚎𝚖𝚎𝚛𝚊𝚕𝙺𝚎𝚢, return ⊥
let ψ=𝖧𝗋𝗌𝖾𝖾𝖽ψ,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(ρ,0) for Orchard or ⊥ for Sapling

let 𝗉𝗄𝖽=𝖪𝖠.𝖣𝖾𝗋𝗂𝗏𝖾𝖯𝗎𝖻𝗅𝗂𝖼(𝗂𝗏𝗄,𝗀𝖽)
let 𝗀⋆𝖽=𝗋𝖾𝗉𝗋ℙ(𝗀𝖽), 𝗉𝗄⋆𝖽=𝗋𝖾𝗉𝗋ℙ(𝗉𝗄𝖽)[, and 𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆=𝗋𝖾𝗉𝗋ℙ(𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾)]
let 𝗋𝖼𝗆={𝖫𝖤𝖮𝖲𝟤𝖨𝖯256(𝗋𝗌𝖾𝖾𝖽),\caseif𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾=\hexint01𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,(𝗀⋆𝖽,𝗉𝗄⋆𝖽,𝗏,ρ,ψ[,𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆])),otherwise
if 𝗋𝖼𝗆≥r𝔾, return ⊥

(The order of operations has to be altered because the derivation of 𝗋𝖼𝗆 can depend on 𝗀𝖽 and 𝗉𝗄𝖽.)

For § 4.20.3, replace

[Canopy onward] let 𝗋𝖼𝗆={𝗋𝗌𝖾𝖾𝖽,if 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾=hexint01𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋(𝖯𝖱𝖥𝗋𝗌𝖾𝖾𝖽𝖾𝗑𝗉𝖺𝗇𝖽(𝗉𝗋𝖾_𝗋𝖼𝗆)),otherwise
let 𝗋𝖼𝗆=𝖫𝖤𝖮𝖲𝟤𝖨𝖯256(𝗋𝖼𝗆) and 𝗀𝖽=𝖣𝗂𝗏𝖾𝗋𝗌𝗂𝖿𝗒𝖧𝖺𝗌𝗁(𝖽)
if 𝗋𝖼𝗆≥r𝔾 or (for Sapling) 𝗀𝖽=⊥ or 𝗉𝗄𝖽∉𝕁(r)∗ (see note below), return ⊥

with

let 𝗀𝖽=𝖣𝗂𝗏𝖾𝗋𝗌𝗂𝖿𝗒𝖧𝖺𝗌𝗁(𝖽); for Sapling, if 𝗀𝖽=⊥, return ⊥
let 𝗀⋆𝖽=𝗋𝖾𝗉𝗋ℙ(𝗀𝖽)[ and 𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆=𝗋𝖾𝗉𝗋ℙ(𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾)]
if 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾≠𝟶𝚡𝟶𝟷, let ψ=𝖧𝗋𝗌𝖾𝖾𝖽ψ,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(ρ,0) for Orchard or ⊥ for Sapling
let 𝗋𝖼𝗆={𝖫𝖤𝖮𝖲𝟤𝖨𝖯256(𝗋𝗌𝖾𝖾𝖽),\caseif𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾=\hexint01𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,(𝗀⋆𝖽,𝗉𝗄⋆𝖽,𝗏,ρ,ψ[,𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆])),otherwise
if 𝗋𝖼𝗆≥r𝔾, return ⊥

(Note that there was previously a type error in both § 4.20.2 and § 4.20.3: 𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋 returns an integer, not a byte sequence, and so cannot be assigned to 𝗋𝖼𝗆.)

Delete "where ψ=𝖳𝗈𝖡𝖺𝗌𝖾𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗋𝗌𝖾𝖾𝖽𝖾𝗑𝗉𝖺𝗇𝖽([9]||ρ))".

§ 5.3 ‘Constants’

Add the definitions

ℓ𝗊𝗌𝗄⦂ℕ:=256
ℓ𝗊𝗄⦂ℕ:=256

Changes to ZIPs

ZIP 32

Add a ∗ to the arrows leading to 𝖺𝗌𝗄 and 𝗋𝗂𝗏𝗄 in the diagram in section ‘Orchard internal key derivation’ 11, with the following note:

∗ The derivations of 𝖺𝗌𝗄 and 𝗋𝗂𝗏𝗄 shown in the diagram are not the only possibility. For further detail see § 4.2.3 ‘Orchard Key Components’ in the protocol specification. However, if 𝖺𝗌𝗄, 𝖺𝗄, 𝗇𝗄, 𝗋𝗂𝗏𝗄, and 𝗋𝗂𝗏𝗄𝗂𝗇𝗍𝖾𝗋𝗇𝖺𝗅 are not generated as in the diagram, then it may not be possible to recover the resulting notes as specified in {{ reference to this ZIP }} in the event that attacks using quantum computers become practical.

ZIP 212

Add a note before the Abstract:

This ZIP reflects the changes made to note encryption for the Canopy upgrade. It does not include subsequent changes in {{ reference to this ZIP }}.

ZIP 226

Add the following to the section Note Structure & Commitment:

When § 4.7.3 ‘Sending Notes (Orchard)’ or § 4.8.3 ‘Dummy Notes (Orchard)’ are invoked directly or indirectly in the computation of ρ and ψ for an OrchardZSA note, 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾 MUST be set to \hexint03.

In section Split Notes, change:

where ψ𝗇𝖿 is sampled uniformly at random on 𝔽qℙ, ...

to

where ψ𝗇𝖿 is computed as 𝖧𝗋𝗌𝖾𝖾𝖽_𝗇𝖿ψ,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(ρ,1)=𝖳𝗈𝖡𝖺𝗌𝖾𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗋𝗌𝖾𝖾𝖽_𝗇𝖿𝖾𝗑𝗉𝖺𝗇𝖽([\hexint0A]||ρ)) for 𝗋𝗌𝖾𝖾𝖽_𝗇𝖿 sampled uniformly at random on 𝔹𝕐[32], ...

Rationale

Cryptographic background

This rationale is written primarily for cryptologists and protocol designers familiar with the Zcash shielded protocols. It is recommended to first read the slides of, and/or watch the following presentations:

  • Understanding Zcash Security at Zcon3 15
  • Post-Quantum Zcash at ZconVI 7

To understand the modelling of hash function and commitment security against a quantum adversary we also recommend 16 and 17.

Proposed Recovery Protocol

The details of the protocol in this section are subject to change. This description is meant to facilitate analysis of whether the specification changes to be adopted now are likely to be sufficient to securely support a Recovery Protocol.

The proposed Recovery Protocol works, roughly speaking, by enforcing the derivations given in the [Flow diagram for the Orchard and OrchardZSA protocols], and we suggest having that diagram open in another window to refer to it.

Import this definition from § 4.7.3 ‘Sending Notes (Orchard)’:

Let 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾 be the note plaintext lead byte, chosen according to § 3.2.1 ‘Note Plaintexts and Memo Fields’ with 𝗉𝗋𝗈𝗍𝗈𝖼𝗈𝗅=𝖮𝗋𝖼𝗁𝖺𝗋𝖽.

Define 𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,(𝗀⋆𝖽,𝗉𝗄⋆𝖽,𝗏,ρ,ψ[,𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆]))= 𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗋𝗌𝖾𝖾𝖽𝖾𝗑𝗉𝖺𝗇𝖽(𝗉𝗋𝖾_𝗋𝖼𝗆))

where 𝗉𝗋𝖾_𝗋𝖼𝗆={[\hexint05]||ρ,\caseif𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾=\hexint02[\hexint0B,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾]||𝖫𝖤𝖡𝖲𝟤𝖮𝖲𝖯256(𝗀⋆𝖽)||𝖫𝖤𝖡𝖲𝟤𝖮𝖲𝖯256(𝗉𝗄⋆𝖽)[\hexint0B,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾]||𝖨𝟤𝖫𝖤𝖮𝖲𝖯64(𝗏)||ρ||𝖨𝟤𝖫𝖤𝖮𝖲𝖯256(ψ)[\hexint0B,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾][||𝖫𝖤𝖡𝖲𝟤𝖮𝖲𝖯256(𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆)],\caseif𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾=\hexint03.

Define:

  • 𝖧𝗋ψψ,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(ρ,𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀)=𝖳𝗈𝖡𝖺𝗌𝖾𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗋ψ𝖾𝗑𝗉𝖺𝗇𝖽([𝗌𝗉𝗅𝗂𝗍_𝖽𝗈𝗆𝖺𝗂𝗇]||ρ))
    where 𝗌𝗉𝗅𝗂𝗍_𝖽𝗈𝗆𝖺𝗂𝗇={\hexint09,\caseif𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀=0\hexint0A,\caseif𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀=1.
  • 𝖧𝗋𝗂𝗏𝗄_𝖾𝗑𝗍𝗋𝗂𝗏𝗄_𝗂𝗇𝗍(𝖺𝗄,𝗇𝗄)=𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗋𝗂𝗏𝗄_𝖾𝗑𝗍𝖾𝗑𝗉𝖺𝗇𝖽([\hexint83]||𝖨𝟤𝖫𝖤𝖮𝖲𝖯256(𝖺𝗄) ||𝖨𝟤𝖫𝖤𝖮𝖲𝖯256(𝗇𝗄)))
  • 𝖧𝗊𝗄𝗋𝗂𝗏𝗄_𝖾𝗑𝗍(𝖺𝗄,𝗇𝗄)=𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗊𝗄𝗋𝗂𝗏𝗄_𝖾𝗑𝗍𝖾𝗑𝗉𝖺𝗇𝖽([\hexint84]||𝖨𝟤𝖫𝖤𝖮𝖲𝖯256(𝖺𝗄) ||𝖨𝟤𝖫𝖤𝖮𝖲𝖯256(𝗇𝗄)))
  • 𝒢𝖮𝗋𝖼𝗁𝖺𝗋𝖽=𝖦𝗋𝗈𝗎𝗉𝖧𝖺𝗌𝗁ℙ(“𝚣.𝚌𝚊𝚜𝚑:𝙾𝚛𝚌𝚑𝚊𝚛𝚍”,“𝙶”)

A valid instance of a Recovery statement assures that given a primary input:

(𝗋𝗍𝖮𝗋𝖼𝗁𝖺𝗋𝖽⦂{0..qℙ−1},𝗋𝗄⦂𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽.𝖯𝗎𝖻𝗅𝗂𝖼),𝗇𝖿⦂𝔽qℙ,𝖲𝗂𝗀𝖧𝖺𝗌𝗁⦂𝖬𝖾𝗌𝗌𝖺𝗀𝖾𝖧𝖺𝗌𝗁)

the prover knows an auxiliary input:

(𝗎𝗌𝖾_𝗊𝗌𝗄⦂𝔹,𝗂𝗌_𝗂𝗇𝗍𝖾𝗋𝗇𝖺𝗅_𝗋𝗂𝗏𝗄⦂𝔹,𝗉𝖺𝗍𝗁⦂{0..qℙ−1}[𝖬𝖾𝗋𝗄𝗅𝖾𝖣𝖾𝗉𝗍𝗁𝖮𝗋𝖼𝗁𝖺𝗋𝖽],𝗉𝗈𝗌⦂{0..2𝖬𝖾𝗋𝗄𝗅𝖾𝖣𝖾𝗉𝗍𝗁𝖮𝗋𝖼𝗁𝖺𝗋𝖽−1},K⦂𝔹[ℓ𝗌𝗄],𝝰⦂𝔽rℙ,𝖺𝗄ℙ⦂ℙ∗,𝗇𝗄⦂𝔽qℙ,𝗊𝗄⦂𝔹𝕐[ℓ𝗊𝗄/8],σ𝗊𝗌𝗄⦂𝖲𝗈𝖪𝗊𝗌𝗄((𝗊𝗄),𝖲𝗂𝗀𝖧𝖺𝗌𝗁),𝗋𝗂𝗏𝗄⦂𝔽rℙ,σ𝗌𝗄⦂𝖲𝗈𝖪𝗌𝗄((𝖺𝗄ℙ,𝗇𝗄,𝗋𝗂𝗏𝗄_𝖾𝗑𝗍),𝖲𝗂𝗀𝖧𝖺𝗌𝗁),𝗋𝗌𝖾𝖾𝖽⦂𝔹𝕐[32],𝗀𝖽⦂ℙ∗,𝗏⦂{0..2ℓ𝗏𝖺𝗅𝗎𝖾−1},ρ⦂𝔽qℙ)

where

𝖲𝗈𝖪𝗊𝗌𝗄.𝖲𝗍𝖺𝗍𝖾𝗆𝖾𝗇𝗍={𝗊𝗌𝗄⦂𝔹𝕐[ℓ𝗊𝗌𝗄/8]|𝗊𝗄=𝖧𝗊𝗄(𝗊𝗌𝗄)}𝖲𝗈𝖪𝗌𝗄.𝖲𝗍𝖺𝗍𝖾𝗆𝖾𝗇𝗍={𝗌𝗄⦂𝔹[ℓ𝗌𝗄]|𝖺𝗄ℙ=[𝖧𝖺𝗌𝗄(𝗌𝗄)]𝒢𝖮𝗋𝖼𝗁𝖺𝗋𝖽∧𝗇𝗄=𝖧𝗇𝗄∧𝗋𝗂𝗏𝗄_𝖾𝗑𝗍=𝖧𝗋𝗂𝗏𝗄_𝗅𝖾𝗀𝖺𝖼𝗒(𝗌𝗄)}

such that the following conditions hold:

{𝗎𝗌𝖾_𝗊𝗌𝗄⇒(𝗋𝗄=𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽.𝖱𝖺𝗇𝖽𝗈𝗆𝗂𝗓𝖾𝖯𝗎𝖻𝗅𝗂𝖼(α,𝖺𝗄ℙ)∧𝖲𝗈𝖪𝗊𝗌𝗄.𝖵𝖺𝗅𝗂𝖽𝖺𝗍𝖾((𝗊𝗄),𝖲𝗂𝗀𝖧𝖺𝗌𝗁,σ𝗊𝗌𝗄)∧𝗋𝗂𝗏𝗄_𝖾𝗑𝗍=𝖧𝗊𝗋𝗂𝗏𝗄_𝖾𝗑𝗍𝗄(𝖺𝗄,𝗇𝗄))∧not 𝗎𝗌𝖾_𝗊𝗌𝗄⇒𝖲𝗈𝖪𝗌𝗄.𝖵𝖺𝗅𝗂𝖽𝖺𝗍𝖾((𝖺𝗄ℙ,𝗇𝗄,𝗋𝗂𝗏𝗄_𝖾𝗑𝗍),𝖲𝗂𝗀𝖧𝖺𝗌𝗁,σ𝗌𝗄)∧𝗋𝗂𝗏𝗄={𝗋𝗂𝗏𝗄_𝖾𝗑𝗍if 𝗂𝗌_𝗋𝗂𝗏𝗄_𝗂𝗇𝗍𝖾𝗋𝗇𝖺𝗅=0𝖧𝗋𝗂𝗏𝗄_𝖾𝗑𝗍𝗋𝗂𝗏𝗄_𝗂𝗇𝗍(𝖺𝗄,𝗇𝗄)if 𝗂𝗌_𝗋𝗂𝗏𝗄_𝗂𝗇𝗍𝖾𝗋𝗇𝖺𝗅=1∧𝖺𝗄=𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ(𝖺𝗄ℙ)∧let 𝗂𝗏𝗄=𝖢𝗈𝗆𝗆𝗂𝗍𝗋𝗂𝗏𝗄𝗂𝗏𝗄(𝖺𝗄,𝗇𝗄)∧𝗂𝗏𝗄∉{0,⊥}∧let 𝗉𝗄𝖽=[𝗂𝗏𝗄]𝗀𝖽∧let ψ=𝖧𝗋ψψ(ρ,𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀)∧let 𝗇𝗈𝗍𝖾_𝗋𝖾𝗉𝗋=(𝗋𝖾𝗉𝗋ℙ(𝗀𝖽),𝗋𝖾𝗉𝗋ℙ(𝗉𝗄𝖽),𝗏,ρ,ψ[,𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆])∧let 𝗋𝖼𝗆=𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(\hexint03,𝗇𝗈𝗍𝖾_𝗋𝖾𝗉𝗋)∧let 𝖼𝗆=𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍𝗋𝖼𝗆𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗇𝗈𝗍𝖾_𝗋𝖾𝗉𝗋)∧𝖼𝗆≠⊥∧let 𝖼𝗆x=𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ(𝖼𝗆)∧let 𝗅𝖾𝖺𝖿=𝖬𝖾𝗋𝗄𝗅𝖾𝖢𝖱𝖧(𝖼𝗆x,ρ)∧𝗉𝖺𝗍𝗁 is a path to 𝗅𝖾𝖺𝖿 in the rehashed commitment tree∧𝗇𝖿=𝖣𝖾𝗋𝗂𝗏𝖾𝖭𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋𝗇𝗄(ρ,ψ,𝖼𝗆)}

and 𝗇𝖿 is the revealed nullifier.

TODO: finish extending this to ZSAs. We need to add ψ𝗇𝖿, and enforce 𝗋𝗌𝖾𝖾𝖽_𝗇𝖿=𝗋𝗌𝖾𝖾𝖽_𝗈𝗅𝖽 only if this is a non-split note.

(We don't need to check the derivation of 𝗀𝖽 from 𝖽.)

Cost

Note that in the "𝗎𝗌𝖾_𝗊𝗌𝗄" case, two BLAKE2b compressions are required to compute 𝗋𝗂𝗏𝗄_𝖾𝗑𝗍, and in the "not 𝗎𝗌𝖾_𝗊𝗌𝗄" cases, three BLAKE2b compressions in total are required to compute 𝖧𝖺𝗌𝗄, 𝖧𝗇𝗄, and 𝖧𝗋𝗂𝗏𝗄_𝗅𝖾𝗀𝖺𝖼𝗒. Since these cases are mutually exclusive, it is possible to multiplex the same three compression function instances. So, supporting "𝗎𝗌𝖾_𝗊𝗌𝗄" in addition to "not 𝗎𝗌𝖾_𝗊𝗌𝗄" costs very little extra.

All of the operations below need to be implemented with complete additions, even if they are incomplete in the current Orchard[ZSA] circuit.

  • 9 BLAKE2b-512 compressions:
    • 3 to compute 𝗋𝗂𝗏𝗄_𝖾𝗑𝗍
    • 2 to compute 𝖧𝗋𝗂𝗏𝗄_𝗂𝗇𝗍
    • 2 to compute 𝖧ψ
    • 2 to compute 𝖧𝗋𝖼𝗆
      • we could potentially save these two by using Poseidon to implement 𝖧𝗋𝖼𝗆, but it seems not worth it.
  • 1 use of 𝖧𝗊𝗄
  • 1 use of 𝖢𝗈𝗆𝗆𝗂𝗍𝗂𝗏𝗄 (𝖲𝗂𝗇𝗌𝖾𝗆𝗂𝗅𝗅𝖺𝖲𝗁𝗈𝗋𝗍𝖢𝗈𝗆𝗆𝗂𝗍)
  • 1 use of 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍𝖮𝗋𝖼𝗁𝖺𝗋𝖽 (𝖲𝗂𝗇𝗌𝖾𝗆𝗂𝗅𝗅𝖺𝖢𝗈𝗆𝗆𝗂𝗍)
  • 1 full-width fixed-base Pallas scalar multiplication, [𝖺𝗌𝗄]𝒢𝖮𝗋𝖼𝗁𝖺𝗋𝖽
  • 1 full-width variable-base Pallas scalar multiplication, [𝗂𝗏𝗄]𝗀𝖽
  • 1 Merkle tree path check
  • 1 additional use of 𝖬𝖾𝗋𝗄𝗅𝖾𝖢𝖱𝖧 to compute 𝗅𝖾𝖺𝖿.

The expensive parts of this are the 9 BLAKE2b compressions.

Security analysis

Let us consider the security of the Orchard protocol against a discrete-log-breaking adversary — which by definition includes quantum adversaries. Similar attacks apply to the Sapling protocol.

Suppose that the proof system has been replaced by one that is post-quantum knowledge-sound.

Repairing the note commitment Merkle tree

𝖬𝖾𝗋𝗄𝗅𝖾𝖢𝖱𝖧𝖮𝗋𝖼𝗁𝖺𝗋𝖽 is instantiated using 𝖲𝗂𝗇𝗌𝗂𝗆𝗂𝗅𝗅𝖺𝖧𝖺𝗌𝗁 18. Its collision resistance depends on the discrete log relation problem on the Pallas curve 19, and so it is not post-quantum collision-resistant or collapsing. However, because the note commitment tree is public, it is possible to re-hash all of its leaves to construct a new Merkle tree using a collapsing hash function. Suppose this has been done.

The post-quantum security of Merkle trees —considered as position-binding vector commitments which is the property required by Zcash 15— is proven under reasonable assumptions in 20 and 21.

Note: when we rehash the commitment tree, we could include both ρ and 𝖼𝗆x for each note (i.e. what is currently the leaf layer becomes 𝖬𝖾𝗋𝗄𝗅𝖾𝖢𝖱𝖧(ρ,𝖼𝗆x) where 𝖬𝖾𝗋𝗄𝗅𝖾𝖢𝖱𝖧 is pq-collision-resistant). This change might not be necessary; it just removes potential complications due to duplicate commitments for the same note.

Attacks against binding of note commitments

We still face the problem that 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍𝖮𝗋𝖼𝗁𝖺𝗋𝖽 is not binding against a discrete-log-breaking adversary: given the discrete log relations between bases, we can easily write a linear equation in the scalar field with multiple solutions of the inputs for a given commitment.

This allows an adversary to find two distinct notes corresponding to openings of 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍𝖮𝗋𝖼𝗁𝖺𝗋𝖽 on the same commitment. They create one note as an output and spend the other note — which may have a greater value, or a value in a different ZSA asset, breaking the Balance property.

Repairing note commitments

We prefer to fix this without changing 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍𝖮𝗋𝖼𝗁𝖺𝗋𝖽 itself. Instead we change how 𝗋𝖼𝗆 is computed to be a hash of 𝗋𝗌𝖾𝖾𝖽 and 𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋=(𝗀⋆𝖽,𝗉𝗄⋆𝖽,𝗏,ρ,ψ[,𝖠𝗌𝗌𝖾𝗍𝖡𝖺𝗌𝖾⋆]), as detailed in the [Specification] section. Specifically, when 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾=\hexint03 we have:

𝗋𝖼𝗆=𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)=𝖳𝗈𝖲𝖼𝖺𝗅𝖺𝗋𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝖯𝖱𝖥𝗋𝗌𝖾𝖾𝖽𝖾𝗑𝗉𝖺𝗇𝖽(𝗉𝗋𝖾_𝗋𝖼𝗆))

where 𝗉𝗋𝖾_𝗋𝖼𝗆=[\hexint0B,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾]||𝖾𝗇𝖼𝗈𝖽𝖾(𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)

Then we view the output of 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍𝗋𝖼𝗆𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋) as the point addition of a randomization term [𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)]ℛ, and some other function of 𝗋𝗌𝖾𝖾𝖽, 𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾, and 𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋.

Without loss of generality, we can write that function as [f(𝗋𝗌𝖾𝖾𝖽,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)]ℛ, by expanding each of the Sinemilla bases 𝒞j=𝒬(D) or 𝒮(j) used by 𝖧𝖺𝗌𝗁𝖳𝗈𝖲𝗂𝗇𝗌𝗂𝗆𝗂𝗅𝗅𝖺𝖯𝗈𝗂𝗇𝗍 as 𝒞j=[cj]ℛ for some cj. That is, 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍𝗋𝖼𝗆𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)=[𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)+f(𝗋𝗌𝖾𝖾𝖽,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)]ℛ.

First we informally argue security in the classical ROM. We will model 𝖧𝗋𝖼𝗆 as a random oracle independent of f with uniform output on 𝔽rℙ. This is reasonable because 𝖧𝗋𝖼𝗆 cannot depend on any of the cj, and in any case it is likely to be a conventional hash function not related to the Pallas curve.

The fact that 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍𝖮𝗋𝖼𝗁𝖺𝗋𝖽 has ψ=𝖧ψ(𝗋𝗌𝖾𝖾𝖽,ρ) as an input does not affect the analysis provided that 𝖧𝗋𝖼𝗆 and 𝖧ψ can be treated as independent. In practice both are defined in terms of 𝖯𝖱𝖥𝖾𝗑𝗉𝖺𝗇𝖽, but with strict domain separation, and so the assumption of independence is reasonable as long as BLAKE2b-512 can be modelled as a random oracle. Note that since the input to 𝖧𝗋𝖼𝗆 needs more than one BLAKE2b input block, we require that a HAIFA sponge can be modelled as a random oracle which is justified by 22.

For each 𝖧𝗋𝖼𝗆 oracle query the adversary chooses 𝗋𝗌𝖾𝖾𝖽,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋 and obtains a "random" 𝗋𝖼𝗆=𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋), such that the distribution of 𝗋𝖼𝗆+[f(𝗋𝗌𝖾𝖾𝖽,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)]ℛ is computationally indistinguishable from the uniform distribution on 𝔽rℙ. Therefore 𝖼𝗆x=𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ([𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)+f(𝗋𝗌𝖾𝖾𝖽,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)]ℛ) is computationally indistinguishable from an output of 𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ applied to uniformly distributed Pallas curve points. The number of such outputs is (𝔽rℙ+1)/2 (one for each possible x-coordinate of Pallas curve points, plus one for the zero point 𝒪ℙ which is mapped to 0).

Only one point maps to 0, as opposed to two points mapping to every other possible output of 𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ, but that has negligible effect.

The number of queries needed to find a collision therefore follows a distribution negligibly far from that expected for a collision attack on an ideal hash function mapping from the input domain to a set of size (𝔽rℙ+1)/2≈2253.

In order to adapt this argument to the quantum setting, we need to consider collapsing hash functions as defined in 16 17.

TODO: 𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ is not collapsing. Is 𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ([𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)+f(𝗋𝗌𝖾𝖾𝖽,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)]ℛ) collapsing?

By the argument in 23, the best known generic quantum attack on a hash function is simply the classical attack of 24. (In particular, the Brassard–Høyer–Tapp algorithm 25 is entirely unimplementable for a 253-bit output size: to achieve the claimed speed-up, it would require running Grover's algorithm with a quantum circuit that does random accesses to a 292.3-bit quantum memory.) Therefore, an output size of 253 bits does not exclude a hash function from being post-quantum collision-resistant. It is possible that there could be better-than-generic quantum attacks against BLAKE2b, but none have been published to our knowledge. In practice, we consider it reasonable to assume that BLAKE2b-512 has the properties needed for 𝖧𝗋𝖼𝗆 to be post-quantum collision-resistant.

TODO: discuss 26 (sponge security), 16 17 (collapse-binding property).

The above security argument means that provided we also check the uses of 𝖧𝗋𝖼𝗆 and 𝖧ψ in the post-quantum recovery circuit, Orchard note commitments can be considered binding on all of the note fields.

Note that the argument associated with Theorem 5.4.4 in the protocol specification ("A ⊥ output from 𝖲𝗂𝗇𝗌𝖾𝗆𝗂𝗅𝗅𝖺𝖧𝖺𝗌𝗁𝖳𝗈𝖯𝗈𝗂𝗇𝗍 yields a nontrivial discrete log relation." and "Since by assumption it is hard to find a nontrivial discrete logarithm relation, we can argue that it is safe to use incomplete additions when computing Sinsemilla inside a circuit.") is not applicable when it is necessary to defend against a discrete-log-breaking or quantum adversary. Therefore, the post-quantum recovery circuit will need to use complete curve additions to implement Sinsemilla.

Attacks against binding of ivk

The security of Orchard against double-spending also depends on the binding property of 𝖢𝗈𝗆𝗆𝗂𝗍𝗂𝗏𝗄. Informally, the security argument is that there is only one value of 𝗂𝗏𝗄 corresponding to a given (𝗀𝖽,𝗉𝗄𝖽) (which are committed to by the note commitment), and so this binds 𝗇𝗄 and 𝖺𝗄 to the correct values for the committed note.

If the binding property of 𝖢𝗈𝗆𝗆𝗂𝗍𝗂𝗏𝗄 fails, then so do the security arguments for the Balance and Spend Authorization properties, because we can no longer infer that 𝗇𝗄 and 𝖺𝗄 are the correct values.

Fortunately, the Orchard protocol specified 𝗋𝗂𝗏𝗄 to be derived from 𝗌𝗄.

(There is a complication in that 𝗋𝗂𝗏𝗄 is derived differently for an "internal IVK".)

We use essentially the same fix as for 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍𝖮𝗋𝖼𝗁𝖺𝗋𝖽, with 𝗋𝗂𝗏𝗄 in place of 𝗋𝖼𝗆.

There is a complication: contrary to the situation with note commitments, addresses may be used over the long term and it may not be feasible or desirable to switch to new addresses.

Informal Security Argument

The argument is that if 𝖧𝗋𝖼𝗆 and 𝖧φ are random oracles, 𝗋𝖼𝗆 is an unpredictable function of the note fields. There are two values of 𝖼𝗆 that match 𝖼𝗆x in their x-coordinate. Because the output of 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍𝖮𝗋𝖼𝗁𝖺𝗋𝖽 is of the form 𝖼𝗆=F(𝗇𝗈𝗍𝖾)+[𝗋𝖼𝗆]ℛ, for any given note we have exactly two values of 𝗋𝖼𝗆 that will pass the commitment check.

Suppose there are N legitimate notes in the tree. The adversary is trying to find (possibly using a Grover search) a note that will pass the commitment check without actually being a note in the tree. The success probability for each attempt in a classical search is 2N/rℙ where rℙ is the order of the Pallas curve, and that is negligible because N≤232≪rℙ. This is also infeasible for a quantum adversary using a Grover search.

We're not finished yet because we also have to prove that the nullifier is computed deterministically for a given note.

All of the inputs to 𝖣𝖾𝗋𝗂𝗏𝖾𝖭𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋 are things we committed to in the protocol so far except 𝗇𝗄. By the same argument used pre-quantumly, there is only one 𝗂𝗏𝗄 for a given (𝗀𝖽,𝗉𝗄𝖽). So in order to just use the existing protocol for this part, we would need to prove that there is only one 𝗇𝗄 (that is feasible to find) such that 𝖢𝗈𝗆𝗆𝗂𝗍𝗋𝗂𝗏𝗄𝗂𝗏𝗄(𝖺𝗄,𝗇𝗄)=𝗂𝗏𝗄. Unfortunately that's not true; 𝖢𝗈𝗆𝗆𝗂𝗍𝗂𝗏𝗄 is instantiated by 𝖲𝗂𝗇𝗌𝖾𝗆𝗂𝗅𝗅𝖺𝖲𝗁𝗈𝗋𝗍𝖢𝗈𝗆𝗆𝗂𝗍 which is not post-quantum binding.

There are two cases depending on 𝗎𝗌𝖾_𝗊𝗌𝗄 (the adversary can choose to attack either):

  • Case 𝗎𝗌𝖾_𝗊𝗌𝗄=0: The spender must prove knowledge of 𝗌𝗄, and that 𝖺𝗄, 𝗇𝗄, and 𝗋𝗂𝗏𝗄 are derived correctly from 𝗌𝗄. This works because the derivations use post-quantum hashes (and 𝖺𝗌𝗄→𝖺𝗄 is deterministic).

    In particular, 𝗂𝗏𝗄 is an essentially random function of 𝗌𝗄, and so we expect that an adversary has no better attack than to search for values of 𝗌𝗄 (possibly using a Grover search) to find one that reproduces a given 𝗂𝗏𝗄. Since 𝗂𝗏𝗄 must be an x-coordinate of a Pallas curve point (see the note at the end of § 4.2.3 Orchard Key Components), it can take on (rℙ−1)/2 values. So if there are T targets the success probability for each attempt in a classical search is 2T/(rℙ−1), which is negligible provided that T≪rℙ. This is also infeasible for a quantum adversary using a Grover search for reasonable values of T.

  • Case 𝗎𝗌𝖾_𝗊𝗌𝗄=0: This case is almost the same except that 𝗂𝗏𝗄 is now an essentially random function of (𝗇𝗄,𝖺𝗄,𝗊𝗄). The success probability in terms of T is also the same as for 𝗎𝗌𝖾_𝗊𝗌𝗄=0.

Security argument for Spendability

DeriveNullifier is based on a Pedersen hash. In the current Orchard protocol, the property that it is infeasible to find two distinct Orchard notes with the same nullifier (including possible nullfiers of split OrchardZSA notes), depends on the collision-resistance of that hash, which would not hold against a discrete-log-breaking adversary.

Does this mean it is possible to break Spendability for the given Recovery circuit? As it turns out, no, but the argument is somewhat involved.

TODO: this argument is too handwavy and depends on the ROM; it needs further work for a quantum adversary. Also it is incomplete because it does not consider split notes.

Since each function in the Recovery circuit is deterministic, the free variables that the adversary can control are the sources of the derivation digraph within that circuit and either 𝖲𝗈𝖪𝗌𝗄 or 𝖲𝗈𝖪𝗊𝗄. That is, the adversary can control:

  • (ρ,𝗀𝖽,𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀,𝗋𝗌𝖾𝖾𝖽,𝗋𝗌𝗉𝗅𝗂𝗍,𝗂𝗌_𝗂𝗇𝗍𝖾𝗋𝗇𝖺𝗅_𝗋𝗂𝗏𝗄,𝗎𝗌𝖾_𝗊𝗌𝗄) and
    • 𝗌𝗄, when 𝗎𝗌𝖾_𝗊𝗌𝗄=𝖿𝖺𝗅𝗌𝖾;
    • (𝗇𝗄,𝖺𝗄,𝗊𝗌𝗄), when 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾.

As we argued earlier in section [Repairing note commitments], 𝗋𝖼𝗆 binds 𝗋𝗌𝖾𝖾𝖽 and 𝗉𝗋𝖾_𝗋𝖼𝗆, and all of the other inputs to 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍 are also included in 𝗉𝗋𝖾_𝗋𝖼𝗆. Therefore, given the independence assumption between 𝖧𝗋𝖼𝗆 and f described earlier, 𝖼𝗆 binds all of the values in the Recovery circuit that the adversary can control.

We can then analyze 𝖣𝖾𝗋𝗂𝗏𝖾𝖭𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋 in essentially the same way we analyzed 𝖭𝗈𝗍𝖾𝖢𝗈𝗆𝗆𝗂𝗍.

First consider 𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀=𝖿𝖺𝗅𝗌𝖾.

Recall that 𝖣𝖾𝗋𝗂𝗏𝖾𝖭𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋 is defined in § 4.16 ‘Computing ρ values and Nullifiers’ as:

𝖣𝖾𝗋𝗂𝗏𝖾𝖭𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋𝗇𝗄(ρ,ψ,𝖼𝗆)=𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ([𝖯𝖱𝖥𝗇𝗄𝗇𝖿𝖮𝗋𝖼𝗁𝖺𝗋𝖽(ρ)+ψ)modqℙ]𝒦𝖮𝗋𝖼𝗁𝖺𝗋𝖽+𝖼𝗆)

Also recall from [Repairing note commitments] that we have

𝖼𝗆=[𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)+f(𝗋𝗌𝖾𝖾𝖽,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)]ℛ.

When 𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀=𝖿𝖺𝗅𝗌𝖾, ψ is determined by 𝗋𝗌𝖾𝖾𝖽. The nullifier corresponding to (𝗇𝗄,ρ,𝗋𝗌𝖾𝖾𝖽,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋) is then 𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ([𝖧𝗋𝗌𝖾𝖾𝖽𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽(𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)+f(𝗋𝗌𝖾𝖾𝖽,𝗅𝖾𝖺𝖽𝖡𝗒𝗍𝖾,𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋)+g(𝗇𝗄,ρ,𝗋𝗌𝖾𝖾𝖽)]ℛ)

for some g depending on the discrete logarithm of 𝒦𝖮𝗋𝖼𝗁𝖺𝗋𝖽 wrt ℛ.

The intuition is that an adversary cannot vary any of the inputs it controls without causing an unpredictable change to both 𝖼𝗆 and 𝗇𝖿. This is because every such input goes through a function that can be modelled as a random oracle on the way to deriving 𝖼𝗆 and 𝗇𝖿:

  • ρ goes through 𝖧ψ to derive ψ, and through 𝖯𝖱𝖥𝗇𝗄𝗇𝖿𝖮𝗋𝖼𝗁𝖺𝗋𝖽 which is instantiated as Poseidon to derive 𝗇𝖿;
  • 𝗀𝖽 goes through 𝖧𝗋𝖼𝗆;
  • because 𝗀𝖽↦𝗉𝗄𝖽=[𝗂𝗏𝗄]𝗀𝖽 is 1-1, any change to 𝗂𝗏𝗄 for a given 𝗀𝖽 results in a change to 𝗉𝗄𝖽, which goes through 𝖧𝗋𝖼𝗆;
  • when 𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀=𝖿𝖺𝗅𝗌𝖾, 𝗋𝗌𝖾𝖾𝖽 goes through 𝖧ψ and then 𝖧𝗋𝖼𝗆;
  • 𝗎𝗌𝖾_𝗊𝗌𝗄 only has two possible values, and therefore can't increase the adversary's advantage by more than a factor of 2 provided that both alternatives are otherwise secure;
  • when 𝗎𝗌𝖾_𝗊𝗌𝗄=𝖿𝖺𝗅𝗌𝖾:
    • 𝗌𝗄 goes through 𝖧𝗋𝗂𝗏𝗄_𝗅𝖾𝗀𝖺𝖼𝗒;
    • 𝗇𝗄 goes through 𝖧𝗇𝗄;
    • 𝖺𝗄 goes through 𝖧𝖺𝗌𝗄;
  • when 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾:
    • 𝗊𝗌𝗄 goes through 𝖧𝗊𝗄 and 𝖧𝗋𝗂𝗏𝗄_𝖾𝗑𝗍;
    • 𝗇𝗄 goes through 𝖧𝗋𝗂𝗏𝗄_𝖾𝗑𝗍;
    • 𝖺𝗄 goes through 𝖧𝗋𝗂𝗏𝗄_𝖾𝗑𝗍;

Note that while f and g are individually random oracles on their inputs, their sum modulo qℙ could potentially be subject to a meet-in-the-middle attack. This might be possible if the inputs that the adversary can control could be split so that some affect only 𝖧𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽 and f, and others affect only g. (Note that 𝖧𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽 and f have the same inputs and are each random oracles on all of their inputs.) But 𝗇𝗈𝗍𝖾𝗋𝖾𝗉𝗋 includes ρ, and we have already established that 𝗇𝗄 cannot be varied without affecting 𝗉𝗄𝖽. So with a little work we can see that such splitting is not possible.

TODO 𝗌𝗉𝗅𝗂𝗍_𝖿𝗅𝖺𝗀=𝗍𝗋𝗎𝖾.

An adversary could also attempt to cause a collision in 𝗇𝖿 by causing a collision on 𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ, but this is also not feasible if 𝖧𝗋𝖼𝗆,𝖮𝗋𝖼𝗁𝖺𝗋𝖽 can be modelled as a random oracle.

Effects of discrete-log-breaking attacks before the switch to the Recovery Protocol

TBD: explain that such attacks can break Balance and Spendability, including Spendability for transactions after switching to the Recovery Protocol.

Note that we can identify the precise set of note commitments for recoverable (lead byte \hexint03) Orchard notes, since they are the commitments for Orchard outputs of v6 transactions. However, we cannot identify the precise set of nullifiers for recoverable notes: an Orchard action in a v6 transaction could be spending either a recoverable or non-recoverable note, and their nullifier sets are indistinguishable.

On the other hand, within the recovery circuit we know that the note being spent is recoverable.

Deployment

As far as I'm aware, all existing Zcash wallets already derive (𝖺𝗄,𝗇𝗄,𝗋𝗂𝗏𝗄) from a spending key 𝗌𝗄 in the way specified for the 𝗎𝗌𝖾_𝗊𝗌𝗄=𝖿𝖺𝗅𝗌𝖾 case in § 4.2.3 Orchard Key Components.

FROST distributed key generation requires the 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 case. There is no significant existing deployment of FROST, so we can write this into ZIP 312 from the start.

The part of the protocol that is new is the different input for 𝗉𝗋𝖾_𝗋𝖼𝗆. It would have been possible to use a separate pq-binding commitment, but 𝖧𝗋𝖼𝗆 is already pq-binding and so doing it this way involves fewer components. This also allows us to avoid any security compromise and use 256-bit cryptovalues for both integrity and randomization, which would otherwise have been difficult.

It is suggested to deploy this change with v6 transactions. That is, every Orchard output of a v6-onward transaction will be a pq-recoverable note. This implies that when the pre-quantum protocol is turned off, v5 and earlier outputs will no longer be spendable. Since v6 already changes the note encryption in order to support memo bundles and ZSAs, this approach to deployment reduces the risk of the kind of difficulties that occurred with ZIP 212, where some wallets were following the old protocol after the Canopy upgrade and sending non-conformant note plaintexts.

When a compliant wallet receives an Orchard note in a v5 or earlier transaction, the associated funds are not pq-recoverable and need to be spent to v6 in order to make them so.

Note: if we prioritize spending non-pq-recoverable notes, it is conceivable that an adversary could exploit this to improve arity leakage attacks. On the other hand, adversaries can already choose note values to manipulate the note selection algorithm to some extent.

References

Footnotes

  1. Information on BCP 14 — "RFC 2119: Key words for use in RFCs to Indicate Requirement Levels" and "RFC 8174: Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words" ↩

  2. ZIP 200: Network Upgrade Mechanism ↩

  3. Zcash Protocol Specification, Version 2025.6.1 [NU6.1] or later ↩

  4. Zcash Protocol Specification, Version 2025.6.1 [NU6.1]. Section 3.12: Mainnet and Testnet ↩

  5. ZIP 226: Transfer and Burn of Zcash Shielded Assets ↩ ↩2 ↩3 ↩4 ↩5

  6. ZIP 227: Issuance of Zcash Shielded Assets ↩ ↩2 ↩3 ↩4

  7. Post-Quantum Zcash (video, slides). Presentation by Daira-Emma Hopwood at ZconVI. ↩ ↩2

  8. ZIP 32: Shielded Hierarchical Deterministic Wallets — Sapling child key derivation ↩

  9. ZIP 32: Shielded Hierarchical Deterministic Wallets — Sapling internal key derivation ↩

  10. ZIP 32: Shielded Hierarchical Deterministic Wallets — Orchard child key derivation ↩

  11. ZIP 32: Shielded Hierarchical Deterministic Wallets — Orchard internal key derivation ↩ ↩2

  12. ZIP 231: Memo Bundles ↩ ↩2 ↩3

  13. ZIP 312: FROST for Spend Authorization Multisignatures ↩

  14. ZIP 312: FROST for Spend Authorization Multisignatures — Key Generation ↩

  15. Understanding Zcash Security (video, slides). Presentation by Daira-Emma Hopwood at Zcon3. ↩ ↩2

  16. Computationally binding quantum commitments. Dominique Unruh. ↩ ↩2 ↩3

  17. Collapse-binding quantum commitments without random oracles. Dominique Unruh. ↩ ↩2 ↩3

  18. Zcash Protocol Specification, Version 2025.6.1 [NU6.1]. Section 5.4.1.9: Sinsemilla Hash Function ↩

  19. Zcash Protocol Specification, Version 2025.6.1 [NU6.1]. Section 5.4.1.9: Sinsemilla Hash Function — Security argument ↩

  20. Post-Quantum Succinct Arguments: Breaking the Quantum Rewinding Barrier. Alessandro Chiesa, Fermi Ma, Nicholas Spooner, and Mark Zhandr. ↩

  21. Quantum Rewinding for IOP-Based Succinct Arguments. Alessandro Chiesa, Marcel Dall'Agnol, Zijing Di, Ziyi Guan, and Nicholas Spooner. ↩

  22. The Sponge is Quantum Indifferentiable. Gorjan Alagic, Joseph Carolan, Christian Majenz, and Saliha Tokat. ↩

  23. Cost analysis of hash collisions: Will quantum computers make SHARCS obsolete? Daniel J. Bernstein. ↩

  24. Parallel collision search with cryptanalytic applications. Paul C. van Oorschot and Michael Wiener. ↩

  25. Quantum Algorithm for the Collision Problem. Gilles Brassard, Peter Høyer, and Alain Tapp. ↩

  26. Post-quantum security of the sponge construction. Jan Czajkowski, Leon Groot Bruinderink, Andreas Hülsing, Christian Schaffner, and Dominique Unruh. ↩

Changes

Makefile +5 −4
@@ -37,17 +37,18 @@ discard: rm -r rendered git checkout -- 'README.rst' -.Makefile.uptodate: Makefile render.sh +.Makefile.uptodate: Makefile render.sh expand_macros.py $(MAKE) clean + ./expand_macros.py --test touch .Makefile.uptodate -rendered/index.html: README.rst render.sh +rendered/index.html: README.rst render.sh expand_macros.py ./render.sh --rst $< $@ -rendered/%.html: zips/%.rst render.sh +rendered/%.html: zips/%.rst render.sh expand_macros.py ./render.sh --rst $< $@ -rendered/%.html: zips/%.md render.sh +rendered/%.html: zips/%.md render.sh expand_macros.py ./render.sh $(MARKDOWN_OPTION) $< $@ README.rst: .zipfilelist.current .draftfilelist.current makeindex.sh README.template $(wildcard zips/zip-*.rst) $(wildcard zips/zip-*.md) $(wildcard zips/draft-*.rst) $(wildcard zips/draft-*.md)
expand_macros.py +332 −0
@@ -0,0 +1,332 @@ +#!/usr/bin/env python3 + +import sys +import re +from io import StringIO +from pathlib import Path +from collections import namedtuple, deque + + +def main(args): + if args[1:] == ["--test"]: + return self_test() + + if len(args) != 3: + print("Usage: ./expand_macros.py <macrofile> <targetfile>\n" + "\n" + "Expand <targetfile> using the macros defined in <macrofile>, writing the\n" + "output on stdout. If <targetfile> is \"-\", read from stdin.\n" + "\n" + "The contents of <macrofile> should be a list of file inclusions and\n" + "macro definitions, one per line. Comments (from the rightmost '%' to the\n" + "end of the line), whitespace, and blank lines will be ignored.\n" + "\n" + "A file inclusion has the form\n" + "\n" + "\\includeonly{filename}\n" + "\n" + "and causes another macro file 'filename.tex' in the same directory as\n" + "<macrofile> to be included.\n" + "\n" + "A macro definition has the form\n" + "\n" + "\\newcommand{\\macroname}[n]{replacement}\n" + "\n" + "where n is the number of arguments. If n is zero then the '[0]' can be\n" + "omitted. The replacement text may include '#i' to substitute the i'th\n" + "argument, counting from 1.\n" + "\n" + "For example, given a macro\n" + "\n" + "\\newcommand{\\journey}[3]{from {#1} to {#3} via {#2}}\n" + "\n" + "the invocation '\\journey{Manchester}{Crewe}{London}' will expand to\n" + "'from {Manchester} to {London} via {Crewe}'. (It is usual, but not\n" + "required, to use '{}' around subsitutions to compensate for lack of\n" + "syntactic hygiene in LaTeX.)\n" + "\n" + "Note that this intentionally covers only the most commonly used subset\n" + "of LaTeX macro definition syntax.\n" + "\n" + "A very simple approximation of LaTeX's tokenization is used in order to\n" + "ensure that substitutions are not applied incorrectly in the case of\n" + "no-argument macros. Specifically, a macro named '\\foo' will match only\n" + "instances of the string '\\foo' followed by a non-letter or the end of\n" + "a line, i.e. it will match on '\\foo0' (giving the replacement text\n" + "followed by '0'), but not on '\\foonly'. As in LaTeX, a space after a\n" + "no-argument macro is consumed, i.e. '\\foo ' behaves like '\\foo'.\n" + "\n" + "Unlike LaTeX, macros with arguments will only match if each argument is\n" + "surrounded by '{}'. Nesting of '{}' within macro arguments works, i.e.\n" + "in the example above, '\\journey{{a}}{b}{c}' will expand to\n" + "'from {{a}} to {c} via {b}'. The '{' and '}' characters can be escaped\n" + "as '\\{' and '\\}'.\n" + "\n" + "A token effort (pun intended) is made to ensure that macro expansion\n" + "does not lead to tokenization inconsistent with LaTeX: if a replacement\n" + "starts with a letter that would continue a control word, then a space\n" + "is inserted before the replacement so that the tokenization of the\n" + "preceding control word is preserved. (In combination with the stricter\n" + "syntax for macro arguments, this means that certain patterns in which a\n" + "macro expansion provides arguments to another macro are not supported.)\n" + "Similarly, if a replacement ends with a control word that would be\n" + "extended by the following text, then a space is inserted after the\n" + "replacement to prevent that.\n" + "\n" + "Doubling a '\\' character can be used to escape it, i.e. an even number\n" + "of '\\' characters will be left as-is and will not be considered as the\n" + "start of a potential macro invocation. To write a literal '%' character\n" + "in a macro definition, include a comment after the definition.\n" + "\n" + "Unlike LaTeX, macro arguments cannot span lines; the expansion of each\n" + "line is independent.\n" + "\n" + "Exit code: 0 on success, 1 on failure.\n" + "\n" + "To run a self-test, use './expand_macros.py --test'. This follows the\n" + "same exit code convention.\n") + return 1 + + macroset = parse_macros_from_file(Path(args[1])) + + targetfile = args[2] + if targetfile == "-": + expand_stream(macroset, sys.stdin, "<stdin>") + else: + with open(targetfile, "r", encoding="utf-8") as targetstream: + expand_stream(macroset, targetstream, targetfile) + + + +class MacroDefinitionError(Exception): + pass + +class MacroSubsitutionError(Exception): + pass + +Macro = namedtuple("Macro", "argcount replacement") +MacroSet = namedtuple("MacroSet", "mapping regex") + +def make_macroset(macros): + regexes = deque() + for name, macro in macros.items(): + assert re.escape(name) == name + if macro.argcount == 0: + regexes.append(r"(?:(?:^|[^\\])(\\\\)*\\" + name + r" ?)") + else: + regexes.append(r"(?:(?:^|[^\\])(\\\\)*\\" + name + r"\{)") + + return MacroSet(macros, re.compile("|".join(regexes))) + +def parse_macros_from_file(filepath): + filepath = filepath.resolve() + basepath = filepath.parent + macros = {} + inclusions = set() + + def opener(includename, location): + if includename in inclusions: + raise MacroDefinitionError(f"Re-inclusion of '{includename}' at {location}") + inclusions.add(includename) + + with open(basepath / includename, "r", encoding="utf-8") as macrostream: + parse_macros_into(macros, opener, macrostream, includename) + + opener(filepath.name, "<top-level>") + return make_macroset(macros) + +def parse_macros_from_string(s, name): + macros = {} + def opener(includename, location): + raise MacroDefinitionError("Cannot include {includename} from a string at {location}") + + parse_macros_into(macros, opener, StringIO(s), name) + return make_macroset(macros) + + +# The filename pattern cannot access other directories. +INCLUDEONLY = re.compile(r"\\includeonly\{([a-zA-Z][-.a-zA-Z]*)\}") + +NEWCOMMAND_START = re.compile(r"\\newcommand\{\\([a-zA-Z]+)\}(?:\[([0-9])\])?") + +def parse_macros_into(macros, opener, macrostream, filename): + for n, line in enumerate(macrostream): + line = line.partition('%')[0].strip(' \t\n') + if line == "": continue + + include = INCLUDEONLY.match(line) + if include: + opener(include.group(1) + ".tex", f"{filename} line {n+1}") + continue + + start = NEWCOMMAND_START.match(line) + if not start: + raise MacroDefinitionError(f"Non-macro found\n At {filename} line {n+1}: <{line}>") + + name = start.group(1) + argcount = int(start.group(2) or 0) + (replacement, rest) = munch(line[start.end():]) + if replacement is None or rest != "": + raise MacroDefinitionError(f"Incomplete macro definition\n At {filename} line {n+1}: <{line}>\n Still to parse: <{rest}>") + + macros[name] = Macro(argcount, replacement) + +def munch(s): + """Parse a balanced {} group. Return (group, rest) if found, or (None, s) otherwise.""" + if s[0] != '{': + return (None, s) + + depth = 0 + unescaped = 1 + for i in range(len(s)): + c = s[i] + match c: + case '{' : depth += unescaped + case '}' : depth -= unescaped + + unescaped = (1 - unescaped) if c == '\\' else 1 + + if depth == 0: + return (s[1:i], s[i+1:]) + + return (None, s) + +def expand_stream(macroset, targetstream, filename): + for n, line in enumerate(targetstream): + print(expand_line(macroset, line.rstrip("\n"), f"{filename} line {n+1}")) + +# 0-based +SUBST = [re.compile(r"(?:^|[^\\])(\\\\)*#" + str(i)) for i in range(1, 10)] + +ENDS_IN_CONTROL_WORD = re.compile(r"(?:^|[^\\])(\\\\)*\\[a-zA-Z]+$") +ASCII_LETTER = re.compile(r"[a-zA-Z]") + +def would_extend_control_word(before, after): + return (ASCII_LETTER.match(after) is not None) and (ENDS_IN_CONTROL_WORD.search(before) is not None) + +def expand_line(macroset, line, location): + result = "" + rest = line + runaway = 0 + while found := macroset.regex.search(rest): + name = found.group().rstrip('{') + end = found.start() + len(name) + name = name.rpartition('\\')[2] + assert name is not None, f"name: {name}, rest: <{rest}>, found: {found}" + start = end - len(name) + assert start > 0, f"name: {name}, rest: <{rest}>, found: {found}" + name = name.rstrip(' ') + macro = macroset.mapping.get(name) + assert macro is not None, f"name: {name}, rest: <{rest}>, found: {found}, macroset: {macroset}" + replacement = macro.replacement + + # Append the target string up to the macro invocation including any escaped '\'s. + result += rest[:start-1] + rest = rest[end:] + + for subst in SUBST[:macro.argcount]: + (arg, rest) = munch(rest) + if arg is None: + raise MacroSubsitutionError(f"Too few arguments to macro '\\{name}'\n At {location}: <{line}>\n Still to parse: <{rest}>") + replacement = subst.sub(lambda m: m.group()[:-2] + arg, replacement) + + # If a replacement starts with a letter that would continue a control word, then a + # space is inserted before the replacement so that the tokenization of the preceding + # control word is preserved. + if would_extend_control_word(result, replacement + rest): + result += ' ' + + # If a replacement ends with a control word that would be extended by the following + # text, then a space is inserted after the replacement to prevent that. + if would_extend_control_word(result + replacement, rest): + replacement += ' ' + + rest = replacement + rest + runaway += 1 + if runaway == 100: + raise MacroSubsitutionError(f"Runaway macro expansion\n At {location}: <{line}>\n Result: <{result}>\n Still to parse: <{rest}>") + + return result + rest + +def self_test(): + successes = 0 + failures = 0 + for testname, (macrostr, expect_mapping, cases) in TESTS.items(): + try: + macroset = parse_macros_from_string(macrostr, f"test {testname}") + assert macroset.mapping == expect_mapping, f"{testname} definitions\n actual: {macroset.mapping}\n expected: {expect_mapping}" + for i, (target, expect_replacement) in enumerate(cases): + location = f"test {testname} case {i+1}" + try: + replacement = expand_line(macroset, target, location) + except MacroSubsitutionError as e: + replacement = None + if expect_replacement is not None: raise + assert replacement == expect_replacement, f"{location}\n actual: {replacement}\n expected: {expect_replacement}" + + successes += 1 + except (MacroDefinitionError, AssertionError) as e: + print(f"Failed testcase: {testname}\n {e.__class__.__name__}: {e}") + failures += 1 + + if failures > 0: + print(f"\nTests failed: {failures} failures, {successes} successes.\n") + return 1 + else: + print(f"Tests passed: no failures, {successes} successes.\n") + return 0 + + +TESTS = { + "journey": ("\\newcommand{\\journey}[3]{from {#1} to {#3} via {#2}}", + {'journey': Macro(3, 'from {#1} to {#3} via {#2}')}, [ + ("\\journey{Manchester}{Crewe}{London}", "from {Manchester} to {London} via {Crewe}"), + ("\\journey{{a}}{b}{c}", "from {{a}} to {c} via {b}"), + ("\\journey{{a}}{\\b}{\\\\c}", "from {{a}} to {\\\\c} via {\\b}"), + ("\\journey{{a}}{\\}\\{b}{c}", "from {{a}} to {c} via {\\}\\{b}"), + ("\\journey{{a}}{\\}\\\\\\{b}{\\\\c}", "from {{a}} to {\\\\c} via {\\}\\\\\\{b}"), + ("\\journey{{a}{b}{c}", None), + ]), + "noarg": (" \\newcommand{\\foo}{bar}", + {'foo': Macro(0, 'bar')}, [ + ("rhu\\foo b", "rhubarb"), + ("rhu\\foo{}b", "rhubar{}b"), + ]), + "unclosedarg": ("\t\\newcommand{\\foo}[1]{bar}", + {'foo': Macro(1, 'bar')}, [ + ("rhu\\foo{", None), + ]), + "unusedarg": ("\\newcommand{\\foo}[1]{bar} ", + {'foo': Macro(1, 'bar')}, [ + ("rhu\\foo{}b", "rhubarb"), + ]), + "recursive": ("\\newcommand{\\journey}[3]{from {#1} to {#3} via {#2}}\t\n\\newcommand{\\foo}{bar}", + {'journey': Macro(3, 'from {#1} to {#3} via {#2}'), 'foo': Macro(0, 'bar')}, [ + ("\\journey{cabbage}{rhu\\foo b}{cauliflower}", "from {cabbage} to {cauliflower} via {rhubarb}"), + ]), + "comment": ("% abc\n\n \t\\newcommand{\\foo}{bar}\t % def", + {'foo': Macro(0, 'bar')}, [ + ("rhu\\foo b", "rhubarb"), + ]), + "noextbefore": ("\\newcommand{\\foo}{o}", + {'foo': Macro(0, 'o')}, [ + ("\\fo\\foo", "\\fo o"), + ]), + "noextafter": ("\\newcommand{\\foo}{\\bar}", + {'foo': Macro(0, '\\bar')}, [ + ("rhu\\foo b", "rhu\\bar b"), + ("rhu\\\\foo b", "rhu\\\\foo b"), + ("rhu\\\\\\foo b", "rhu\\\\\\bar b"), + ]), + "escape": ("\\newcommand{\\foo}{bar}", + {'foo': Macro(0, 'bar')}, [ + ("rhu\\\\\\foo b", "rhu\\\\barb"), + ]), + "runaway": ("\\newcommand{\\foo}{\\foo}", + {'foo': Macro(0, '\\foo')}, [ + ("\\foo", None), + ]), +} + +if __name__ == '__main__': + sys.exit(main(sys.argv))
render.sh +21 −5
@@ -7,7 +7,7 @@ set -euo pipefail if ! ( ( [ "x$1" = "x--rst" ] || [ "x$1" = "x--pandoc" ] || [ "x$1" = "x--mmd" ] ) && [ $# -eq 3 ] ); then cat - <<EndOfUsage -Usage: render.sh --rst|--pandoc|--mmd <inputfile> <htmlfile> <title> +Usage: render.sh --rst|--pandoc|--mmd <inputfile> <htmlfile> --rst render reStructuredText using rst2html5 --pandoc render Markdown using pandoc @@ -29,7 +29,8 @@ if [ "x$1" = "x--rst" ]; then else filetype='.md' fi -title="$(basename -s ${filetype} ${inputfile} | sed -E 's|zip-0{0,3}|ZIP |; s|draft-|Draft |')$(grep -E '^(\.\.)?\s*Title: ' ${inputfile} |sed -E 's|.*Title||')" +basefile="$(basename -s ${filetype} ${inputfile})" +title="$(echo ${basefile} | sed -E 's|zip-0{0,3}|ZIP |; s|draft-|Draft |')$(grep -E '^(\.\.)?\s*Title: ' ${inputfile} |sed -E 's|.*Title||')" echo " ${title}" Math1='<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex.min.css" integrity="sha384-WcoG4HRXMzYzfCgiyfrySxx90XSl2rxY5mnVY5TwtWE6KLrArNKn0T/mOgNL0Mmi" crossorigin="anonymous">' @@ -40,13 +41,19 @@ Mermaid='<script defer src="https://cdn.jsdelivr.net/npm/mermaid@11.12.1/dist/me ViewAndStyle='<meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="css/style.css">' +macrofile="$(dirname ${inputfile})/${basefile}.macros.tex" + cat <( if [ "x$1" = "x--rst" ]; then # These are basic regexps so \+ is needed, not +. # We use the Unicode 💲 character to move an escaped $ out of the way, # which is much easier than trying to handle escapes within a capture. - cat "${inputfile}" \ + (if [ -f "${macrofile}" ]; then + ./expand_macros.py "${macrofile}" "${inputfile}" + else + cat "${inputfile}" + fi) \ | sed 's|[\][$]|💲|g; s|[$]\([^$]\+\)[$]\([.,:;!?)-]\)|:math:`\1\\!`\2|g; s|[$]\([^$]\+\)[$]|:math:`\1`|g; @@ -55,12 +62,21 @@ cat <( | sed "s|<script src=\"http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML\"></script>|${Math1}\n ${Math2}\n ${Math3}|; s|</head>|${ViewAndStyle}</head>|" else + delete_expandedfile=0 + if [ -f "${macrofile}" ]; then + expandedfile="$(dirname ${inputfile})/${basefile}.expanded${filetype})" + ./expand_macros.py "${macrofile}" "${inputfile}" >"${expandedfile}" + delete_expandedfile=1 + else + expandedfile="${inputfile}" + fi if [ "x$1" = "x--pandoc" ]; then # Not actually MathJax. KaTeX is compatible if we use the right headers. - pandoc --mathjax --from=markdown --to=html "${inputfile}" --output="${outputfile}.temp" + pandoc --mathjax --from=markdown --to=html "${expandedfile}" --output="${outputfile}.temp" else - multimarkdown ${inputfile} -o "${outputfile}.temp" + multimarkdown ${expandedfile} -o "${outputfile}.temp" fi + if [ ${delete_expandedfile} -eq 1 ]; then rm -f "${expandedfile}"; fi # Both pandoc and multimarkdown just output the HTML body. echo "<!DOCTYPE html>"
zips/common-macros.tex +49 −0
@@ -0,0 +1,49 @@ +% Common macros + +% To use macros from this file, write `\includeonly{common-macros}` in the +% `zip-xxxx.macros.tex` file. + +\newcommand{\bytes}[1]{\underline{#1}} +\newcommand{\zeros}[1]{[0]^{#1}} +\newcommand{\zerobytes}[1]{[\hexint{00}]^{#1}} +\newcommand{\ones}[1]{[1]^{#1}} +\newcommand{\bit}{\mathbb{B}} +\newcommand{\byte}{\mathbb{B}^{\kern-0.1em\tiny\mathbb{Y}}} +\newcommand{\Nat}{\mathbb{N}} +\newcommand{\PosInt}{\mathbb{N}^+} +\newcommand{\Int}{\mathbb{Z}} +\newcommand{\Rat}{\mathbb{Q}} +\newcommand{\GF}[1]{\mathbb{F}_{\!#1}} +\newcommand{\GFstar}[1]{\mathbb{F}^\ast_{#1}} +\newcommand{\typeexp}[2]{{#1}\vphantom{)}^{[{#2}]}} +\newcommand{\bitseq}[1]{\typeexp{\bit}{#1}} +\newcommand{\bitseqs}{\bitseq{\Nat}} +\newcommand{\byteseq}[1]{\mathbb{B}^{{\kern-0.1em\tiny\mathbb{Y}}[{#1}]}} +\newcommand{\byteseqs}{\byteseq{\Nat}} +\newcommand{\concatbits}{\mathsf{concat}_\bit} +\newcommand{\bconcat}{\,||\,} +\newcommand{\listcomp}[1]{[~{#1}~]} +\newcommand{\biglistcomp}[1]{\bigg[{#1}\bigg]} +\newcommand{\fun}[2]{{#1} \mapsto {#2}} +\newcommand{\exclusivefun}[3]{{#1} \mapsto_{\not\in\kern 0.05em{#3}} {#2}} + +\newcommand{\squash}{\!\!\!} +\newcommand{\caseif}{\squash\text{if }} +\newcommand{\caseotherwise}{\squash\text{otherwise}} + +\newcommand{\length}{\mathsf{length}} +\newcommand{\truncate}[1]{\mathsf{truncate}_{#1}} +\newcommand{\mean}{\mathsf{mean}} +\newcommand{\median}{\mathsf{median}} + +\newcommand{\hexint}[1]{\mathtt{0x#1}} +\newcommand{\ascii}[1]{\textbf{``\texttt{#1}''}} +\newcommand{\setof}[1]{\{{#1}\}} +\newcommand{\bigsetof}[1]{\big\{{#1}\big\}} +\newcommand{\powerset}[1]{\mathscr{P}\big({#1}\big)} +\newcommand{\barerange}[2]{{{#1}\,..\,{#2}}} +\newcommand{\range}[2]{\setof{\barerange{#1}{#2}}} +\newcommand{\bigrange}[2]{\bigsetof{\barerange{#1}{#2}}} +\newcommand{\rangenozero}[2]{\range{#1}{#2} \setminus \setof{0}} +\newcommand{\bigrangenozero}[2]{\bigrange{#1}{#2} \setminus \setof{0}} +\newcommand{\binaryrange}[1]{\range{0}{2^{#1}-1}}
zips/draft-ecc-quantum-recoverability.macros.tex +1 −0
@@ -0,0 +1 @@ +\includeonly{common-macros}
zips/draft-ecc-quantum-recoverability.md +52 −52
@@ -163,7 +163,7 @@ This subsection and the flow diagram below are non-normative. In order to support ZSAs [^zip-0226] [^zip-0227] and memo bundles [^zip-0231], v6 transactions require in any case a new note plaintext -format, with lead byte $\mathtt{0x03}.$ This gives us an opportunity +format, with lead byte $\hexint{03}.$ This gives us an opportunity to change the way that the $\mathsf{pre\_rcm}$ value is computed for this new format, by including all of the note fields in $\mathsf{pre\_rcm}$. The resulting $\mathsf{rcm}$ is essentially a random function of the @@ -450,10 +450,10 @@ in the Orchard key components diagram, with the following note: Replace the paragraph > The field $\mathsf{leadByte}$ indicates the version of the encoding of -> a Sapling or Orchard note plaintext. For Sapling it is $\mathtt{0x01}$ -> before activation of the Canopy network upgrade and $\mathtt{0x02}$ +> a Sapling or Orchard note plaintext. For Sapling it is $\hexint{01}$ +> before activation of the Canopy network upgrade and $\hexint{02}$ > afterward, as specified in [ZIP-212]. For Orchard note plaintexts it is -> always $\mathtt{0x02}$. +> always $\hexint{02}$. with @@ -472,10 +472,10 @@ with > > Define $\mathsf{allowedLeadBytes^{protocol}}(\mathsf{height}, \mathsf{txVersion}) =$ > $\hspace{2em} \begin{cases} -> \{ \mathtt{0x01} \},&\!\!\!\text{if } \mathsf{height} < \mathsf{CanopyActivationHeight} \\ -> \{ \mathtt{0x01}, \mathtt{0x02} \},&\!\!\!\text{if } \mathsf{CanopyActivationHeight} \leq \mathsf{height} < \mathsf{CanopyActivationHeight} + \mathsf{ZIP212GracePeriod} \\ -> \{ \mathtt{0x02} \},&\!\!\!\text{if } \mathsf{CanopyActivationHeight} + \mathsf{ZIP212GracePeriod} \leq \mathsf{height} \text{ and } \mathsf{txVersion} < 6 \\ -> \{ \mathtt{0x03} \},&\!\!\!\text{otherwise.} +> \setof{\hexint{01}},&\caseif \mathsf{height} < \mathsf{CanopyActivationHeight} \\ +> \setof{\hexint{01}, \hexint{02}},&\caseif \mathsf{CanopyActivationHeight} \leq \mathsf{height} < \mathsf{CanopyActivationHeight} + \mathsf{ZIP212GracePeriod} \\ +> \setof{\hexint{02}},&\caseif \mathsf{CanopyActivationHeight} + \mathsf{ZIP212GracePeriod} \leq \mathsf{height} \text{ and } \mathsf{txVersion} < 6 \\ +> \setof{\hexint{03}},&\!\!\!\text{otherwise.} > \end{cases}$ > > The $\mathsf{leadByte}$ of a Sapling or Orchard note MUST satisfy @@ -497,7 +497,7 @@ In the list of places where $\mathsf{PRF^{expand}}$ is used: Replace > * [**NU5** onward] in § 4.2.3 ‘Orchard Key Components’, with inputs -> $[6]$, $[7]$, $[8]$, and with first byte $\mathtt{0x82}$ (the last of +> $[6]$, $[7]$, $[8]$, and with first byte $\hexint{82}$ (the last of > these is also specified in [[ZIP-32]](https://zips.z.cash/zip-0032)); > * in the processes of sending (§ 4.7.2 ‘Sending Notes (Sapling)’ and > § 4.7.3 ‘Sending Notes (Orchard)’) and of receiving @@ -508,23 +508,23 @@ Replace with > * [**NU5** onward] in § 4.2.3 ‘Orchard Key Components’, with inputs -> $[\mathtt{0x06}]$, $[\mathtt{0x07}]$, $[\mathtt{0x08}]$, with first -> byte in $\{ \mathtt{0x0C}, \mathtt{0x0D} \}$ (also specified in -> {{ reference to this ZIP }}), and with first byte $\mathtt{0x82}$ +> $[\hexint{06}]$, $[\hexint{07}]$, $[\hexint{08}]$, with first +> byte in $\setof{\hexint{0C}, \hexint{0D}}$ (also specified in +> {{ reference to this ZIP }}), and with first byte $\hexint{82}$ > (also specified in [[ZIP-32]](https://zips.z.cash/zip-0032)); > * in the processes of sending (§ 4.7.2 ‘Sending Notes (Sapling)’ and > § 4.7.3 ‘Sending Notes (Orchard)’) and of receiving > (§ 4.20 ‘In-band secret distribution (Sapling and Orchard)’) notes, -> for Sapling with inputs $[\mathtt{0x04}]$ and $[\mathtt{0x05}]$, +> for Sapling with inputs $[\hexint{04}]$ and $[\hexint{05}]$, > and for Orchard with first byte in -> $\{ \mathtt{0x05}, \mathtt{0x04}, \mathtt{0x09}, \mathtt{0x0A}, \mathtt{0x0B} \}$ -> ($\mathtt{0x0A}$ and $\mathtt{0x0B}$ are also specified in +> $\setof{\hexint{05}, \hexint{04}, \hexint{09}, \hexint{0A}, \hexint{0B}}$ +> ($\hexint{0A}$ and $\hexint{0B}$ are also specified in > {{ reference to this ZIP }}); Add > * in {{ reference to this ZIP }}, with first byte in -> $\{ \mathtt{0x0A}, \mathtt{0x0B}, \mathtt{0x0C}, \mathtt{0x0D} \}$. +> $\setof{\hexint{0A}, \hexint{0B}, \hexint{0C}, \hexint{0D}}$. Also change the remaining decimal constants to hex for consistency. @@ -535,7 +535,7 @@ from § 5.3 ‘Constants’. Insert after the definition of $\mathsf{ToScalar^{Orchard}}$: -> Define $\mathsf{H}^{\mathsf{rivk\_ext}}_{\mathsf{qk}}(\mathsf{ak}, \mathsf{nk}) = \mathsf{ToScalar^{Orchard}}(\mathsf{PRF^{expand}_{qk}}([\mathtt{0x0D}] \,||\, \mathsf{I2LEOSP}_{256}(\mathsf{ak})$ +> Define $\mathsf{H}^{\mathsf{rivk\_ext}}_{\mathsf{qk}}(\mathsf{ak}, \mathsf{nk}) = \mathsf{ToScalar^{Orchard}}(\mathsf{PRF^{expand}_{qk}}([\hexint{0D}] \,||\, \mathsf{I2LEOSP}_{256}(\mathsf{ak})$ > $\hspace{23.9em} ||\, \mathsf{I2LEOSP}_{256}(\mathsf{nk})))$. Replace from "From this spending key" up to and including the line @@ -567,10 +567,10 @@ in the algorithm with: > needs to be set to false.) > > Define: -> * $\mathsf{H^{ask}}(\mathsf{sk}) = \mathsf{ToScalar^{Orchard}}\big(\mathsf{PRF^{expand}_{sk}}([\mathtt{0x06}])\kern-0.1em\big)$ -> * $\mathsf{H^{nk}}(\mathsf{sk}) = \mathsf{ToBase^{Orchard}}\big(\mathsf{PRF^{expand}_{sk}}([\mathtt{0x07}])\kern-0.1em\big)$ -> * $\mathsf{H^{rivk}}(\mathsf{sk}) = \mathsf{ToScalar^{Orchard}}\big(\mathsf{PRF^{expand}_{sk}}([\mathtt{0x08}])\kern-0.1em\big)$ -> * $\mathsf{H^{qsk}}(\mathsf{sk}) = \mathsf{truncate}_{32}\big(\mathsf{PRF^{expand}_{sk}}([\mathtt{0x0C}])\kern-0.1em\big)$ +> * $\mathsf{H^{ask}}(\mathsf{sk}) = \mathsf{ToScalar^{Orchard}}\big(\mathsf{PRF^{expand}_{sk}}([\hexint{06}])\kern-0.1em\big)$ +> * $\mathsf{H^{nk}}(\mathsf{sk}) = \mathsf{ToBase^{Orchard}}\big(\mathsf{PRF^{expand}_{sk}}([\hexint{07}])\kern-0.1em\big)$ +> * $\mathsf{H^{rivk}}(\mathsf{sk}) = \mathsf{ToScalar^{Orchard}}\big(\mathsf{PRF^{expand}_{sk}}([\hexint{08}])\kern-0.1em\big)$ +> * $\mathsf{H^{qsk}}(\mathsf{sk}) = \mathsf{truncate}_{32}\big(\mathsf{PRF^{expand}_{sk}}([\hexint{0C}])\kern-0.1em\big)$ > * $\mathsf{H^{qk}}(\mathsf{qsk}) = \textsf{BLAKE2s\kern0.1em-256}(\texttt{“Zcash\_qk”}, \mathsf{qsk})$. > > $\mathsf{ask} \;{\small ⦂}\; \mathbb{F}^{*}_{r_{\mathbb{P}}}$, @@ -633,8 +633,8 @@ Replace > Let $\mathsf{CanopyActivationHeight}$ be as defined in § 5.3 ‘Constants’. > > Let $\mathsf{leadByte}$ be the note plaintext lead byte. -> This MUST be $\mathsf{0x01}$ if for the next block, -> $\mathsf{height} < \mathsf{CanopyActivationHeight}$, or $\mathtt{0x02}$ +> This MUST be $\hexint{01}$ if for the next block, +> $\mathsf{height} < \mathsf{CanopyActivationHeight}$, or $\hexint{02}$ > if $\mathsf{height} \geq \mathsf{CanopyActivationHeight}$. with @@ -643,9 +643,9 @@ with > according to § 3.2.1 ‘Note Plaintexts and Memo Fields’ with > $\mathsf{protocol} = \mathsf{Sapling}$. > -> Define $\mathsf{H^{rcm,Sapling}_{rseed}}(\_, \_) = \mathsf{ToScalar^{Sapling}}\big(\mathsf{PRF^{expand}_{rseed}}([\mathtt{0x04}])\kern-0.1em\big)$ +> Define $\mathsf{H^{rcm,Sapling}_{rseed}}(\_, \_) = \mathsf{ToScalar^{Sapling}}\big(\mathsf{PRF^{expand}_{rseed}}([\hexint{04}])\kern-0.1em\big)$ > -> Define $\mathsf{H^{esk,Sapling}_{rseed}}(\_, \_) = \mathsf{ToScalar^{Sapling}}\big(\mathsf{PRF^{expand}_{rseed}}([\mathtt{0x05}])\kern-0.1em\big)$. +> Define $\mathsf{H^{esk,Sapling}_{rseed}}(\_, \_) = \mathsf{ToScalar^{Sapling}}\big(\mathsf{PRF^{expand}_{rseed}}([\hexint{05}])\kern-0.1em\big)$. > > ($\mathsf{H^{rcm,Sapling}}$ and $\mathsf{H^{esk,Sapling}}$ intentionally > take arguments that are unused.) @@ -673,18 +673,18 @@ with > $\mathsf{ToScalar^{Orchard}}\big(\mathsf{PRF^{expand}_{rseed}}(\mathsf{pre\_rcm})\kern-0.1em\big)$ > > where $\mathsf{pre\_rcm} = \begin{cases} -> [\mathtt{0x05}] \,||\, \underline{\text{ρ}},&\!\!\!\text{if } \mathsf{leadByte} = \mathtt{0x02} \\ -> [\mathtt{0x0B}, \mathsf{leadByte}] \,||\, \mathsf{LEBS2OSP}_{256}(\mathsf{g}\star_{\mathsf{d}}) \,||\, \mathsf{LEBS2OSP}_{256}(\mathsf{pk}\star_{\mathsf{d}}) \\ -> \hphantom{[\mathtt{0x0B}, \mathsf{leadByte}]} \,||\, \mathsf{I2LEOSP}_{64}(\mathsf{v}) \,||\, \underline{\text{ρ}} \,||\, \mathsf{I2LEOSP}_{256}(\text{ψ}) \\ -> \hphantom{[\mathtt{0x0B}, \mathsf{leadByte}]}\,[\,||\, \mathsf{LEBS2OSP}_{256}(\mathsf{AssetBase}\kern0.08em\star)],&\!\!\!\text{if } \mathsf{leadByte} = \mathtt{0x03} +> [\hexint{05}] \,||\, \underline{\text{ρ}},&\caseif \mathsf{leadByte} = \hexint{02} \\ +> [\hexint{0B}, \mathsf{leadByte}] \,||\, \mathsf{LEBS2OSP}_{256}(\mathsf{g}\star_{\mathsf{d}}) \,||\, \mathsf{LEBS2OSP}_{256}(\mathsf{pk}\star_{\mathsf{d}}) \\ +> \hphantom{[\hexint{0B}, \mathsf{leadByte}]} \,||\, \mathsf{I2LEOSP}_{64}(\mathsf{v}) \,||\, \underline{\text{ρ}} \,||\, \mathsf{I2LEOSP}_{256}(\text{ψ}) \\ +> \hphantom{[\hexint{0B}, \mathsf{leadByte}]}\,[\,||\, \mathsf{LEBS2OSP}_{256}(\mathsf{AssetBase}\kern0.08em\star)],&\caseif \mathsf{leadByte} = \hexint{03} > \end{cases}$ > -> Define $\mathsf{H^{esk,Orchard}_{rseed}}(\underline{\text{ρ}}) = \mathsf{ToScalar^{Orchard}}\big(\mathsf{PRF^{expand}_{rseed}}([\mathtt{0x04}] \,||\, \underline{\text{ρ}})\kern-0.1em\big)$. +> Define $\mathsf{H^{esk,Orchard}_{rseed}}(\underline{\text{ρ}}) = \mathsf{ToScalar^{Orchard}}\big(\mathsf{PRF^{expand}_{rseed}}([\hexint{04}] \,||\, \underline{\text{ρ}})\kern-0.1em\big)$. > > Define $\mathsf{H^{\text{ψ},Orchard}_{rseed}}(\underline{\text{ρ}}, \mathsf{split\_flag}) = \mathsf{ToBase^{Orchard}}\big(\mathsf{PRF^{expand}_{rseed}}([\mathsf{split\_domain}] \,||\, \underline{\text{ρ}})\kern-0.1em\big)$. > where $\mathsf{split\_domain} = \begin{cases} -> \mathtt{0x09}&\text{if } \mathsf{split\_flag} = 0 \\ -> \mathtt{0x0A}&\text{if } \mathsf{split\_flag} = 1\text{.} +> \hexint{09}&\text{if } \mathsf{split\_flag} = 0 \\ +> \hexint{0A}&\text{if } \mathsf{split\_flag} = 1\text{.} > \end{cases}$ Insert before the derivation of $\mathsf{esk}$: @@ -805,11 +805,11 @@ with For § 4.20.2, replace > $\hspace{1.0em}$ [**Canopy** onward] let $\underline{\mathsf{rcm}} = \begin{cases} -> \mathsf{rseed},&\!\!\!\text{if } \mathsf{leadByte} = \mathtt{0x01} \\ +> \mathsf{rseed},&\!\!\!\text{if } \mathsf{leadByte} = hexint{01} \\ > \mathsf{ToScalar^{protocol}}(\mathsf{PRF^{expand}_{rseed}}(\mathsf{pre\_rcm})),&\!\!\!\text{otherwise} > \end{cases}$ <br> > $\hspace{1.0em}$ let $\mathsf{rcm} = \mathsf{LEOS2IP}_{256}(\underline{\mathsf{rcm}})$ and $\mathsf{g_d} = \mathsf{DiversifyHash}(\mathsf{d})$. if $\mathsf{rcm} \geq r_{\mathbb{G}}$ or (for Sapling) $\mathsf{g_d} = \bot$, return $\bot$ <br> -> $\hspace{1.0em}$ [**Canopy** onward] if $\mathsf{leadByte} \neq \mathtt{0x01}$: <br> +> $\hspace{1.0em}$ [**Canopy** onward] if $\mathsf{leadByte} \neq hexint{01}$: <br> > $\hspace{2.5em}$ $\mathsf{esk} = \mathsf{ToScalar^{protocol}}(\mathsf{PRF^{expand}_{rseed}}(\mathsf{pre\_esk}))$ <br> > $\hspace{2.5em}$ if $\mathsf{repr}_{\mathbb{G}}(\mathsf{KA.DerivePublic}(\mathsf{esk}, \mathsf{g_d})) \neq \mathtt{ephemeralKey}$, return $\bot$ > @@ -818,15 +818,15 @@ For § 4.20.2, replace with > $\hspace{1.0em}$ let $\mathsf{g_d} = \mathsf{DiversifyHash}(\mathsf{d})$; for Sapling, if $\mathsf{g_d} = \bot$, return $\bot$ <br> -> $\hspace{1.0em}$ [**Canopy** onward] if $\mathsf{leadByte} \neq \mathtt{0x01}$: <br> +> $\hspace{1.0em}$ [**Canopy** onward] if $\mathsf{leadByte} \neq hexint{01}$: <br> > $\hspace{2.5em}$ let $\mathsf{esk} = \mathsf{H^{esk,protocol}_{rseed}}(\underline{\text{ρ}})$ <br> > $\hspace{2.5em}$ if $\mathsf{repr}_{\mathbb{G}}(\mathsf{KA.DerivePublic}(\mathsf{esk}, \mathsf{g_d})) \neq \mathtt{ephemeralKey}$, return $\bot$ <br> > $\hspace{2.5em}$ let $\text{ψ} = \mathsf{H^{\text{ψ},Orchard}_{rseed}}(\underline{\text{ρ}}, 0)$ for Orchard or $\bot$ for Sapling > > $\hspace{1.0em}$ let $\mathsf{pk_d} = \mathsf{KA.DerivePublic}(\mathsf{ivk}, \mathsf{g_d})$ <br> > $\hspace{1.0em}$ let $\mathsf{g}\star_{\mathsf{d}} = \mathsf{repr}_{\mathbb{P}}(\mathsf{g_d})$, $\mathsf{pk}\star_{\mathsf{d}} = \mathsf{repr}_{\mathbb{P}}(\mathsf{pk_d}) [$, and $\mathsf{AssetBase}\kern0.08em\star = \mathsf{repr}_{\mathbb{P}}(\mathsf{AssetBase})]$ <br> > $\hspace{1.0em}$ let $\mathsf{rcm} = \begin{cases} -> \mathsf{LEOS2IP}_{256}(\mathsf{rseed}),&\!\!\!\text{if } \mathsf{leadByte} = \mathtt{0x01} \\ +> \mathsf{LEOS2IP}_{256}(\mathsf{rseed}),&\caseif \mathsf{leadByte} = \hexint{01} \\ > \mathsf{H^{rcm,protocol}_{rseed}}(\mathsf{leadByte}, (\mathsf{g}\star_{\mathsf{d}}, \mathsf{pk}\star_{\mathsf{d}}, \mathsf{v}, \underline{\text{ρ}}, \text{ψ}[, \mathsf{AssetBase}\kern0.08em\star])),&\!\!\!\text{otherwise} > \end{cases}$ <br> > $\hspace{1.0em}$ if $\mathsf{rcm} \geq r_{\mathbb{G}}$, return $\bot$ @@ -837,7 +837,7 @@ $\mathsf{rcm}$ can depend on $\mathsf{g_d}$ and $\mathsf{pk_d}$.) For § 4.20.3, replace > $\hspace{1.0em}$ [**Canopy** onward] let $\underline{\mathsf{rcm}} = \begin{cases} -> \mathsf{rseed},&\!\!\!\text{if } \mathsf{leadByte} = \mathtt{0x01} \\ +> \mathsf{rseed},&\!\!\!\text{if } \mathsf{leadByte} = hexint{01} \\ > \mathsf{ToScalar}(\mathsf{PRF^{expand}_{rseed}}(\mathsf{pre\_rcm})),&\!\!\!\text{otherwise} > \end{cases}$ <br> > $\hspace{1.0em}$ let $\mathsf{rcm} = \mathsf{LEOS2IP}_{256}(\underline{\mathsf{rcm}})$ and $\mathsf{g_d} = \mathsf{DiversifyHash}(\mathsf{d})$ <br> @@ -849,7 +849,7 @@ with > $\hspace{1.0em}$ let $\mathsf{g}\star_{\mathsf{d}} = \mathsf{repr}_{\mathbb{P}}(\mathsf{g_d}) [$ and $\mathsf{AssetBase}\kern0.08em\star = \mathsf{repr}_{\mathbb{P}}(\mathsf{AssetBase})]$ <br> > $\hspace{1.0em}$ if $\mathsf{leadByte} \neq \mathtt{0x01}$, let $\text{ψ} = \mathsf{H^{\text{ψ},Orchard}_{rseed}}(\underline{\text{ρ}}, 0)$ for Orchard or $\bot$ for Sapling <br> > $\hspace{1.0em}$ let $\mathsf{rcm} = \begin{cases} -> \mathsf{LEOS2IP}_{256}(\mathsf{rseed}),&\!\!\!\text{if } \mathsf{leadByte} = \mathtt{0x01} \\ +> \mathsf{LEOS2IP}_{256}(\mathsf{rseed}),&\caseif \mathsf{leadByte} = \hexint{01} \\ > \mathsf{H^{rcm,protocol}_{rseed}}(\mathsf{leadByte}, (\mathsf{g}\star_{\mathsf{d}}, \mathsf{pk}\star_{\mathsf{d}}, \mathsf{v}, \underline{\text{ρ}}, \text{ψ}[, \mathsf{AssetBase}\kern0.08em\star])),&\!\!\!\text{otherwise} > \end{cases}$ <br> > $\hspace{1.0em}$ if $\mathsf{rcm} \geq r_{\mathbb{G}}$, return $\bot$ @@ -900,7 +900,7 @@ Add the following to the section [Note Structure & Commitment](https://zips.z.ca > When § 4.7.3 ‘Sending Notes (Orchard)’ or § 4.8.3 ‘Dummy Notes (Orchard)’ > are invoked directly or indirectly in the computation of $\text{ρ}$ and > $\text{ψ}$ for an OrchardZSA note, $\mathsf{leadByte}$ MUST be set to -> $\mathtt{0x03}$. +> $\hexint{03}$. In section [Split Notes](https://zips.z.cash/zip-0226#split-notes), change: @@ -910,7 +910,7 @@ In section [Split Notes](https://zips.z.cash/zip-0226#split-notes), change: to > where $\text{ψ}^{\mathsf{nf}}$ is computed as -> $\mathsf{H^{\text{ψ},Orchard}_{rseed\_nf}}(\underline{\text{ρ}}, 1) = \mathsf{ToBase^{Orchard}}\big(\mathsf{PRF^{expand}_{rseed\_nf}}([\mathtt{0x0A}] \,||\, \underline{\text{ρ}})\kern-0.1em\big)$ +> $\mathsf{H^{\text{ψ},Orchard}_{rseed\_nf}}(\underline{\text{ρ}}, 1) = \mathsf{ToBase^{Orchard}}\big(\mathsf{PRF^{expand}_{rseed\_nf}}([\hexint{0A}] \,||\, \underline{\text{ρ}})\kern-0.1em\big)$ > for $\mathsf{rseed\_nf}$ sampled uniformly at random on $\mathbb{B}^{{\kern-0.1em\tiny\mathbb{Y}}[32]}$, ... @@ -949,22 +949,22 @@ Import this definition from § 4.7.3 ‘Sending Notes (Orchard)’: > $\mathsf{ToScalar^{Orchard}}\big(\mathsf{PRF^{expand}_{rseed}}(\mathsf{pre\_rcm})\kern-0.1em\big)$ > > where $\mathsf{pre\_rcm} = \begin{cases} -> [\mathtt{0x05}] \,||\, \underline{\text{ρ}},&\!\!\!\text{if } \mathsf{leadByte} = \mathtt{0x02} \\ -> [\mathtt{0x0B}, \mathsf{leadByte}] \,||\, \mathsf{LEBS2OSP}_{256}(\mathsf{g}\star_{\mathsf{d}}) \,||\, \mathsf{LEBS2OSP}_{256}(\mathsf{pk}\star_{\mathsf{d}}) \\ -> \hphantom{[\mathtt{0x0B}, \mathsf{leadByte}]} \,||\, \mathsf{I2LEOSP}_{64}(\mathsf{v}) \,||\, \underline{\text{ρ}} \,||\, \mathsf{I2LEOSP}_{256}(\text{ψ}) \\ -> \hphantom{[\mathtt{0x0B}, \mathsf{leadByte}]}\,[\,||\, \mathsf{LEBS2OSP}_{256}(\mathsf{AssetBase}\kern0.08em\star)],&\!\!\!\text{if } \mathsf{leadByte} = \mathtt{0x03}\text{.} \\ +> [\hexint{05}] \,||\, \underline{\text{ρ}},&\caseif \mathsf{leadByte} = \hexint{02} \\ +> [\hexint{0B}, \mathsf{leadByte}] \,||\, \mathsf{LEBS2OSP}_{256}(\mathsf{g}\star_{\mathsf{d}}) \,||\, \mathsf{LEBS2OSP}_{256}(\mathsf{pk}\star_{\mathsf{d}}) \\ +> \hphantom{[\hexint{0B}, \mathsf{leadByte}]} \,||\, \mathsf{I2LEOSP}_{64}(\mathsf{v}) \,||\, \underline{\text{ρ}} \,||\, \mathsf{I2LEOSP}_{256}(\text{ψ}) \\ +> \hphantom{[\hexint{0B}, \mathsf{leadByte}]}\,[\,||\, \mathsf{LEBS2OSP}_{256}(\mathsf{AssetBase}\kern0.08em\star)],&\caseif \mathsf{leadByte} = \hexint{03}\text{.} \\ > \end{cases}$ Define: * $\mathsf{H}^{\text{ψ},\mathsf{Orchard}}_{\mathsf{r}\text{ψ}}(\underline{\text{ρ}}, \mathsf{split\_flag}) = \mathsf{ToBase^{Orchard}}(\mathsf{PRF}^{\mathsf{expand}}_{\mathsf{r}\text{ψ}}([\mathsf{split\_domain}] \,||\, \underline{\text{ρ}}))$ <br> where $\mathsf{split\_domain} = \begin{cases} - \mathtt{0x09},&\!\!\!\text{if } \mathsf{split\_flag} = 0 \\ - \mathtt{0x0A},&\!\!\!\text{if } \mathsf{split\_flag} = 1\text{.} + \hexint{09},&\caseif \mathsf{split\_flag} = 0 \\ + \hexint{0A},&\caseif \mathsf{split\_flag} = 1\text{.} \end{cases}$ -* $\mathsf{H^{rivk\_int}_{rivk\_ext}}(\mathsf{ak}, \mathsf{nk}) = \mathsf{ToScalar^{Orchard}}(\mathsf{PRF^{expand}_{rivk\_ext}}([\mathtt{0x83}] \,||\, \mathsf{I2LEOSP}_{256}(\mathsf{ak})$ +* $\mathsf{H^{rivk\_int}_{rivk\_ext}}(\mathsf{ak}, \mathsf{nk}) = \mathsf{ToScalar^{Orchard}}(\mathsf{PRF^{expand}_{rivk\_ext}}([\hexint{83}] \,||\, \mathsf{I2LEOSP}_{256}(\mathsf{ak})$ $\hspace{21.58em} ||\, \mathsf{I2LEOSP}_{256}(\mathsf{nk})))$ -* $\mathsf{H}^{\mathsf{rivk\_ext}}_{\mathsf{qk}}(\mathsf{ak}, \mathsf{nk}) = \mathsf{ToScalar^{Orchard}}(\mathsf{PRF^{expand}_{\rlap{qk}{\hphantom{rivk\_ext}}}}([\mathtt{0x84}] \,||\, \mathsf{I2LEOSP}_{256}(\mathsf{ak})$ +* $\mathsf{H}^{\mathsf{rivk\_ext}}_{\mathsf{qk}}(\mathsf{ak}, \mathsf{nk}) = \mathsf{ToScalar^{Orchard}}(\mathsf{PRF^{expand}_{\rlap{qk}{\hphantom{rivk\_ext}}}}([\hexint{84}] \,||\, \mathsf{I2LEOSP}_{256}(\mathsf{ak})$ $\hspace{21.58em} ||\, \mathsf{I2LEOSP}_{256}(\mathsf{nk})))$ * $\mathcal{G}^{\mathsf{Orchard}} = \mathsf{GroupHash}^{\mathbb{P}}(\texttt{“z.cash:Orchard”}, \texttt{“G”})$ @@ -1023,7 +1023,7 @@ $\begin{array}{l} \wedge\; \text{let } \mathsf{pk_d} = [\mathsf{ivk}]\, \mathsf{g_d} \\ \wedge\; \text{let } \text{ψ} = \mathsf{H}^{\text{ψ}}_{\mathsf{r}\text{ψ}}(\underline{\text{ρ}}, \mathsf{split\_flag}) \\ \wedge\; \text{let } \mathsf{note\_repr} = \big(\mathsf{repr}_{\mathbb{P}}(\mathsf{g_d}), \mathsf{repr}_{\mathbb{P}}(\mathsf{pk_d}), \mathsf{v}, \underline{\text{ρ}}, \text{ψ}[, \mathsf{AssetBase}\kern0.08em\star]\big) \\ -\wedge\; \text{let } \mathsf{rcm} = \mathsf{H^{rcm,Orchard}_{rseed}}\big(\mathtt{0x03}, \mathsf{note\_repr}\big) \\ +\wedge\; \text{let } \mathsf{rcm} = \mathsf{H^{rcm,Orchard}_{rseed}}\big(\hexint{03}, \mathsf{note\_repr}\big) \\ \wedge\; \text{let } \mathsf{cm} = \mathsf{NoteCommit^{Orchard}_{rcm}}(\mathsf{note\_repr}) \\ \wedge\; \mathsf{cm} \neq \bot \\ \wedge\; \text{let } \mathsf{cm}_x = \mathsf{Extract}_{\mathbb{P}}(\mathsf{cm}) \\ @@ -1121,11 +1121,11 @@ We prefer to fix this without changing $\mathsf{NoteCommit^{Orchard}}$ itself. Instead we change how $\mathsf{rcm}$ is computed to be a hash of $\mathsf{rseed}$ and $\mathsf{noterepr} = (\mathsf{g}\star_{\mathsf{d}}, \mathsf{pk}\star_{\mathsf{d}}, \mathsf{v}, \underline{\text{ρ}}, \text{ψ}[, \mathsf{AssetBase}\kern0.08em\star])$, as detailed in the [Specification] section. -Specifically, when $\mathsf{leadByte} = \mathtt{0x03}$ we have: +Specifically, when $\mathsf{leadByte} = \hexint{03}$ we have: $\mathsf{rcm} = \mathsf{H^{rcm,Orchard}_{rseed}}(\mathsf{leadByte}, \mathsf{noterepr}) = \mathsf{ToScalar^{Orchard}}(\mathsf{PRF^{expand}_{rseed}}(\mathsf{pre\_rcm}))$ -$\text{where } \mathsf{pre\_rcm} = [\mathtt{0x0B}, \mathsf{leadByte}] \,||\, \mathsf{encode}(\mathsf{noterepr})$ +$\text{where } \mathsf{pre\_rcm} = [\hexint{0B}, \mathsf{leadByte}] \,||\, \mathsf{encode}(\mathsf{noterepr})$ Then we view the output of $\mathsf{NoteCommit^{Orchard}_{rcm}}(\mathsf{noterepr})$ @@ -1405,7 +1405,7 @@ TBD: explain that such attacks can break Balance and Spendability, including Spendability for transactions after switching to the Recovery Protocol. Note that we can identify the precise set of note commitments for -recoverable (lead byte $\mathtt{0x03}$) Orchard notes, since they are +recoverable (lead byte $\hexint{03}$) Orchard notes, since they are the commitments for Orchard outputs of v6 transactions. However, we cannot identify the precise set of nullifiers for recoverable notes: an Orchard action in a v6 transaction could be
zips/zip-guide-markdown.md +29 −0
@@ -154,6 +154,35 @@ In general the conventions in the Zcash protocol specification SHOULD be followe If you find this difficult, don't worry too much about it in initial drafts; the ZIP editors will catch any inconsistencies in review. +## Macros + +$\LaTeX$-style macros are supported in ZIPs. The macros for `zips/<name>.{rst,md}` +must be defined in `zips/<name>.macros.tex`. Macros will only be processed for a +given document if this file exists. + +There is a common set of useful macro definitions in `zips/common-macros.tex`, +but they will not be used by default. To use them, write this line in +`zips/<name>.macros.tex`:: + + \includeonly{common-macros} + +The syntax of macro invocations follows LaTeX and is the same for ZIPs written in +reStructuredText and Markdown. + +For example, the following macro definition: + + \newcommand{\hello}[1]{Hello, #1!} + +causes `\hello{world}` to be replaced with `Hello, world!`. Note that it is more +typical to surround argument subsitutions with braces, e.g. `{#1}`, in the case +of macros intended for use in LaTeX. + +Run `./expand_macros.py --help` for more details, including discussion of escaping +and corner cases. + +Macros can be used anywhere in a document for which they are enabled; their use +is not restricted to embedded LaTeX. + ## Notes and warnings <div class="note"></div>
zips/zip-guide.rst +41 −0
@@ -163,6 +163,47 @@ In general the conventions in the Zcash protocol specification SHOULD be followe If you find this difficult, don't worry too much about it in initial drafts; the ZIP editors will catch any inconsistencies in review. +Macros +------ + +$\LaTeX$-style macros are supported in ZIPs. The macros for ``zips/<name>.{rst,md}`` +must be defined in a file called ``zips/<name>.macros.tex``. Macros will only be +processed for a given document if this file exists. + +There is a common set of useful macro definitions in ``zips/common-macros.tex``, +but they will not be used by default. To use them, write this line in +``zips/<name>.macros.tex``:: + + \includeonly{common-macros} + +The syntax of macro invocations follows LaTeX and is the same for ZIPs written in +reStructuredText and Markdown. + +For example, the following macro definition:: + + \newcommand{\hello}[1]{Hello, #1!} + +causes ``\hello{world}`` to be replaced with ``Hello, world!``. Note that it is more +typical to surround argument subsitutions with braces, e.g. ``{#1}``, in the case +of macros intended for use in LaTeX. + +Run ``./expand_macros.py --help`` for more details, including discussion of escaping +and corner cases. + +Macros can be used anywhere in a document for which they are enabled; their use +is not restricted to embedded LaTeX. + +then ``\hello{world}`` will be replaced with ``Hello, world!``. Note that it +is more typical to surround argument subsitutions with braces, e.g. ``{#1}``, in +the case of macros intended for use in LaTeX. + +To escape the ``\`` character, use ``\\``. + +Run ``./expand_macros.py --help`` for more details and discussion of corner cases. + +Macros can be used anywhere in a reStructuredText or Markdown file; their use +is not restricted to embedded LaTeX. + Notes and warnings ------------------