Auto merge of #5202 - str4d:5022-tx-v5, r=str4d

v5 transaction format

Includes a new wrapper that enables passing C++ streams across to Rust.

Closes zcash/zcash#5022.
This commit is contained in:
Homu 2021-06-12 17:59:44 +00:00
commit bad7f7eadb
15 changed files with 906 additions and 50 deletions

3
Cargo.lock generated
View File

@ -775,6 +775,7 @@ dependencies = [
"blake2b_simd",
"blake2s_simd",
"bls12_381",
"byteorder",
"ed25519-zebra",
"group",
"hyper",
@ -783,6 +784,8 @@ dependencies = [
"libc",
"metrics",
"metrics-exporter-prometheus",
"nonempty",
"orchard",
"rand_core 0.6.2",
"subtle",
"thiserror",

View File

@ -24,9 +24,12 @@ bellman = "0.10"
blake2b_simd = "0.5"
blake2s_simd = "0.5"
bls12_381 = "0.5"
byteorder = "1"
group = "0.10"
libc = "0.2"
jubjub = "0.7"
nonempty = "0.6"
orchard = "0.0"
subtle = "2.2"
rand_core = "0.6"
tracing = "0.1"

View File

@ -120,6 +120,12 @@ Files: depends/sources/utfcpp-*.tar.gz
Copyright: 2006 Nemanja Trifunovic
License: Boost-Software-License-1.0
Files: depends/*/vendored-sources/halo2/*
depends/*/vendored-sources/orchard/*
depends/*/vendored-sources/pasta_curves/*
Copyright: 2020 The Electric Coin Company
License: Bootstrap-Open-Source-Licence-1.0
Files: src/crypto/ctaes/*
Copyright: Copyright (c) 2016 Pieter Wuille
License: Expat
@ -1151,3 +1157,181 @@ License: Expat-with-advertising-clause
their institutions shall not be used in advertising or otherwise to
promote the sale, use or other dealings in this Software without
prior written authorization from the authors.
License: Bootstrap-Open-Source-Licence-1.0
=======================================================
Bootstrap Open Source Licence ("BOSL") v. 1.0
=======================================================
This Bootstrap Open Source Licence (the "License") applies to any original work
of authorship (the "Original Work") whose owner (the "Licensor") has placed the
following licensing notice adjacent to the copyright notice for the Original
Work:
.
*Licensed under the Bootstrap Open Source Licence version 1.0*
.
1. **Grant of Copyright License.** Licensor grants You a worldwide,
royalty-free, non-exclusive, sublicensable license, for the duration of the
copyright in the Original Work, to do the following:
.
a. to reproduce the Original Work in copies, either alone or as part of
a collective work;
.
b. to translate, adapt, alter, transform, modify, or arrange the
Original Work, thereby creating derivative works ("Derivative Works")
based upon the Original Work;
.
c. to distribute or communicate copies of the Original Work and
Derivative Works to the public, provided that prior to any such
distribution or communication You first place a machine-readable copy
of the Source Code of the Original Work and such Derivative Works that
You intend to distribute or communicate in an information repository
reasonably calculated to permit inexpensive and convenient access
thereto by the public (“Information Repository”) for as long as You
continue to distribute or communicate said copies, accompanied by an
irrevocable offer to license said copies to the public free of charge
under this License, said offer valid starting no later than 12 months
after You first distribute or communicate said copies;
.
d. to perform the Original Work publicly; and
.
e. to display the Original Work publicly.
.
2. **Grant of Patent License.** Licensor grants You a worldwide, royalty-free,
non-exclusive, sublicensable license, under patent claims owned or controlled
by the Licensor that are embodied in the Original Work as furnished by the
Licensor, for the duration of the patents, to make, use, sell, offer for sale,
have made, and import the Original Work and Derivative Works.
.
3. **Grant of Source Code License.** The "Source Code" for a work means the
preferred form of the work for making modifications to it and all available
documentation describing how to modify the work. Licensor agrees to provide a
machine-readable copy of the Source Code of the Original Work along with each
copy of the Original Work that Licensor distributes. Licensor reserves the
right to satisfy this obligation by placing a machine-readable copy of said
Source Code in an Information Repository for as long as Licensor continues to
distribute the Original Work.
.
4. **Exclusions From License Grant.** Neither the names of Licensor, nor the
names of any contributors to the Original Work, nor any of their trademarks or
service marks, may be used to endorse or promote products derived from this
Original Work without express prior permission of the Licensor. Except as
expressly stated herein, nothing in this License grants any license to
Licensor's trademarks, copyrights, patents, trade secrets or any other
intellectual property. No patent license is granted to make, use, sell, offer
for sale, have made, or import embodiments of any patent claims other than the
licensed claims defined in Section 2. No license is granted to the trademarks
of Licensor even if such marks are included in the Original Work. Nothing in
this License shall be interpreted to prohibit Licensor from licensing under
terms different from this License any Original Work that Licensor otherwise
would have a right to license.
.
5. **External Deployment.** The term "External Deployment" means the use,
distribution, or communication of the Original Work or Derivative Works in any
way such that the Original Work or Derivative Works may be used by anyone other
than You, whether those works are distributed or communicated to those persons
or made available as an application intended for use over a network. As an
express condition for the grants of license hereunder, You must treat any
External Deployment by You of the Original Work or a Derivative Work as a
distribution under section 1(c).
.
6. **Attribution Rights.** You must retain, in the Source Code of any
Derivative Works that You create, all copyright, patent, or trademark notices
from the Source Code of the Original Work, as well as any notices of licensing
and any descriptive text identified therein as an "Attribution Notice." You
must cause the Source Code for any Derivative Works that You create to carry a
prominent Attribution Notice reasonably calculated to inform recipients that
You have modified the Original Work.
.
7. **Warranty of Provenance and Disclaimer of Warranty.** Licensor warrants
that the copyright in and to the Original Work and the patent rights granted
herein by Licensor are owned by the Licensor or are sublicensed to You under
the terms of this License with the permission of the contributor(s) of those
copyrights and patent rights. Except as expressly stated in the immediately
preceding sentence, the Original Work is provided under this License on an "AS
IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without
limitation, the warranties of non-infringement, merchantability or fitness for
a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS
WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this
License. No license to the Original Work is granted by this License except
under this disclaimer.
.
8. **Limitation of Liability.** Under no circumstances and under no legal
theory, whether in tort (including negligence), contract, or otherwise, shall
the Licensor be liable to anyone for any indirect, special, incidental, or
consequential damages of any character arising as a result of this License or
the use of the Original Work including, without limitation, damages for loss of
goodwill, work stoppage, computer failure or malfunction, or any and all other
commercial damages or losses. This limitation of liability shall not apply to
the extent applicable law prohibits such limitation.
.
9. **Acceptance and Termination.** If, at any time, You expressly assented to
this License, that assent indicates your clear and irrevocable acceptance of
this License and all of its terms and conditions. If You distribute or
communicate copies of the Original Work or a Derivative Work, You must make a
reasonable effort under the circumstances to obtain the express assent of
recipients to the terms of this License. This License conditions your rights to
undertake the activities listed in Section 1, including your right to create
Derivative Works based upon the Original Work, and doing so without honoring
these terms and conditions is prohibited by copyright law and international
treaty. Nothing in this License is intended to affect copyright exceptions and
limitations (including 'fair use' or 'fair dealing'). This License shall
terminate immediately and You may no longer exercise any of the rights granted
to You by this License upon your failure to honor the conditions in Section
1(c).
.
10. **Termination for Patent Action.** This License shall terminate
automatically and You may no longer exercise any of the rights granted to You
by this License as of the date You commence an action, including a cross-claim
or counterclaim, against Licensor or any licensee alleging that the Original
Work infringes a patent. This termination provision shall not apply for an
action alleging patent infringement by combinations of the Original Work with
other software or hardware.
.
11. **Jurisdiction, Venue and Governing Law.** Any action or suit relating to
this License may be brought only in the courts of a jurisdiction wherein the
Licensor resides or in which Licensor conducts its primary business, and under
the laws of that jurisdiction excluding its conflict-of-law provisions. The
application of the United Nations Convention on Contracts for the International
Sale of Goods is expressly excluded. Any use of the Original Work outside the
scope of this License or after its termination shall be subject to the
requirements and penalties of copyright or patent law in the appropriate
jurisdiction. This section shall survive the termination of this License.
.
12. **Attorneys' Fees.** In any action to enforce the terms of this License or
seeking damages relating thereto, the prevailing party shall be entitled to
recover its costs and expenses, including, without limitation, reasonable
attorneys' fees and costs incurred in connection with such action, including
any appeal of such action. This section shall survive the termination of this
License.
.
13. **Miscellaneous.** If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent necessary to
make it enforceable.
.
14. **Definition of "You" in This License.** "You" throughout this License,
whether in upper or lower case, means an individual or a legal entity
exercising rights under, and complying with all of the terms of, this License.
For legal entities, "You" includes any entity that controls, is controlled by,
or is under common control with you. For purposes of this definition, "control"
means (i) the power, direct or indirect, to cause the direction or management
of such entity, whether by contract or otherwise, or (ii) ownership of fifty
percent (50%) or more of the outstanding shares, or (iii) beneficial ownership
of such entity.
.
15. **Right to Use.** You may use the Original Work in all ways not otherwise
restricted or conditioned by this License or by law, and Licensor promises not
to interfere with or be responsible for such uses by You.
.
16. **Modification of This License.** This License is Copyright © 2007 Zooko
Wilcox-O'Hearn. Permission is granted to copy, distribute, or communicate this
License without modification. Nothing in this License permits You to modify
this License as applied to the Original Work or to Derivative Works. However,
You may modify the text of this License and copy, distribute or communicate
your modified version (the "Modified License") and apply it to other original
works of authorship subject to the following conditions: (i) You may not
indicate in any way that your Modified License is the "Bootstrap Open Source
Licence" or "BOSL" and you may not use those names in the name of your Modified
License; and (ii) You must replace the notice specified in the first paragraph
above with the notice "Licensed under <insert your license name here>" or with
a notice of your own that is not confusingly similar to the notice in this
License.

View File

@ -42,7 +42,8 @@ JSON_TEST_FILES = \
test/data/merkle_witness_serialization_sapling.json \
test/data/merkle_path_sapling.json \
test/data/merkle_commitments_sapling.json \
test/data/sapling_key_components.json
test/data/sapling_key_components.json \
test/data/zip0244.json
RAW_TEST_FILES = test/data/alertTests.raw

View File

@ -20,6 +20,10 @@ static const int32_t OVERWINTER_MAX_TX_VERSION = 3;
static const int32_t SAPLING_MIN_TX_VERSION = 4;
/** The maximum allowed Sapling transaction version (network rule) */
static const int32_t SAPLING_MAX_TX_VERSION = 4;
/** The minimum allowed ZIP225 transaction version (network rule) */
static const int32_t ZIP225_MIN_TX_VERSION = 5;
/** The maximum allowed ZIP225 transaction version (network rule) */
static const int32_t ZIP225_MAX_TX_VERSION = 5;
/** The maximum allowed size for a serialized block, in bytes (network rule) */
static const unsigned int MAX_BLOCK_SIZE = 2000000;
/** The maximum allowed number of signature check operations in a block (network rule) */

View File

@ -9,6 +9,66 @@
#include "tinyformat.h"
#include "utilstrencodings.h"
SaplingBundle::SaplingBundle(
const std::vector<SpendDescription>& vShieldedSpend,
const std::vector<OutputDescription>& vShieldedOutput,
const CAmount& valueBalance,
const binding_sig_t& bindingSig)
: valueBalanceSapling(valueBalance), bindingSigSapling(bindingSig)
{
for (auto &spend : vShieldedSpend) {
vSpendsSapling.emplace_back(spend.cv, spend.nullifier, spend.rk);
if (anchorSapling.IsNull()) {
anchorSapling = spend.anchor;
} else {
assert(anchorSapling == spend.anchor);
}
vSpendProofsSapling.push_back(spend.zkproof);
vSpendAuthSigSapling.push_back(spend.spendAuthSig);
}
for (auto &output : vShieldedOutput) {
vOutputsSapling.emplace_back(
output.cv,
output.cmu,
output.ephemeralKey,
output.encCiphertext,
output.outCiphertext);
vOutputProofsSapling.push_back(output.zkproof);
}
}
std::vector<SpendDescription> SaplingBundle::GetV4ShieldedSpend()
{
std::vector<SpendDescription> vShieldedSpend;
for (int i = 0; i < vSpendsSapling.size(); i++) {
auto spend = vSpendsSapling[i];
vShieldedSpend.emplace_back(
spend.cv,
anchorSapling,
spend.nullifier,
spend.rk,
vSpendProofsSapling[i],
vSpendAuthSigSapling[i]);
}
return vShieldedSpend;
}
std::vector<OutputDescription> SaplingBundle::GetV4ShieldedOutput()
{
std::vector<OutputDescription> vShieldedOutput;
for (int i = 0; i < vOutputsSapling.size(); i++) {
auto output = vOutputsSapling[i];
vShieldedOutput.emplace_back(
output.cv,
output.cmu,
output.ephemeralKey,
output.encCiphertext,
output.outCiphertext,
vOutputProofsSapling[i]);
}
return vShieldedOutput;
}
std::string COutPoint::ToString() const
{
return strprintf("COutPoint(%s, %u)", hash.ToString().substr(0,10), n);
@ -67,8 +127,10 @@ std::string CTxOut::ToString() const
CMutableTransaction::CMutableTransaction() : nVersion(CTransaction::SPROUT_MIN_CURRENT_VERSION), fOverwintered(false), nVersionGroupId(0), nExpiryHeight(0), nLockTime(0), valueBalance(0) {}
CMutableTransaction::CMutableTransaction(const CTransaction& tx) : nVersion(tx.nVersion), fOverwintered(tx.fOverwintered), nVersionGroupId(tx.nVersionGroupId), nExpiryHeight(tx.nExpiryHeight),
nConsensusBranchId(tx.GetConsensusBranchId()),
vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime),
valueBalance(tx.valueBalance), vShieldedSpend(tx.vShieldedSpend), vShieldedOutput(tx.vShieldedOutput),
orchardBundle(tx.GetOrchardBundle()),
vJoinSplit(tx.vJoinSplit), joinSplitPubKey(tx.joinSplitPubKey), joinSplitSig(tx.joinSplitSig),
bindingSig(tx.bindingSig)
{
@ -86,14 +148,18 @@ void CTransaction::UpdateHash() const
CTransaction::CTransaction() : nVersion(CTransaction::SPROUT_MIN_CURRENT_VERSION),
fOverwintered(false), nVersionGroupId(0), nExpiryHeight(0),
nConsensusBranchId(0),
vin(), vout(), nLockTime(0),
valueBalance(0), vShieldedSpend(), vShieldedOutput(),
orchardBundle(),
vJoinSplit(), joinSplitPubKey(), joinSplitSig(),
bindingSig() { }
CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), fOverwintered(tx.fOverwintered), nVersionGroupId(tx.nVersionGroupId), nExpiryHeight(tx.nExpiryHeight),
nConsensusBranchId(tx.nConsensusBranchId),
vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime),
valueBalance(tx.valueBalance), vShieldedSpend(tx.vShieldedSpend), vShieldedOutput(tx.vShieldedOutput),
orchardBundle(tx.orchardBundle),
vJoinSplit(tx.vJoinSplit), joinSplitPubKey(tx.joinSplitPubKey), joinSplitSig(tx.joinSplitSig),
bindingSig(tx.bindingSig)
{
@ -105,8 +171,10 @@ CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion
CTransaction::CTransaction(
const CMutableTransaction &tx,
bool evilDeveloperFlag) : nVersion(tx.nVersion), fOverwintered(tx.fOverwintered), nVersionGroupId(tx.nVersionGroupId), nExpiryHeight(tx.nExpiryHeight),
nConsensusBranchId(tx.nConsensusBranchId),
vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime),
valueBalance(tx.valueBalance), vShieldedSpend(tx.vShieldedSpend), vShieldedOutput(tx.vShieldedOutput),
orchardBundle(tx.orchardBundle),
vJoinSplit(tx.vJoinSplit), joinSplitPubKey(tx.joinSplitPubKey), joinSplitSig(tx.joinSplitSig),
bindingSig(tx.bindingSig)
{
@ -115,10 +183,12 @@ CTransaction::CTransaction(
CTransaction::CTransaction(CMutableTransaction &&tx) : nVersion(tx.nVersion),
fOverwintered(tx.fOverwintered), nVersionGroupId(tx.nVersionGroupId),
nConsensusBranchId(tx.nConsensusBranchId),
vin(std::move(tx.vin)), vout(std::move(tx.vout)),
nLockTime(tx.nLockTime), nExpiryHeight(tx.nExpiryHeight),
valueBalance(tx.valueBalance),
vShieldedSpend(std::move(tx.vShieldedSpend)), vShieldedOutput(std::move(tx.vShieldedOutput)),
orchardBundle(std::move(tx.orchardBundle)),
vJoinSplit(std::move(tx.vJoinSplit)),
joinSplitPubKey(std::move(tx.joinSplitPubKey)), joinSplitSig(std::move(tx.joinSplitSig)),
bindingSig(std::move(tx.bindingSig))
@ -130,6 +200,7 @@ CTransaction& CTransaction::operator=(const CTransaction &tx) {
*const_cast<bool*>(&fOverwintered) = tx.fOverwintered;
*const_cast<int*>(&nVersion) = tx.nVersion;
*const_cast<uint32_t*>(&nVersionGroupId) = tx.nVersionGroupId;
nConsensusBranchId = tx.nConsensusBranchId;
*const_cast<std::vector<CTxIn>*>(&vin) = tx.vin;
*const_cast<std::vector<CTxOut>*>(&vout) = tx.vout;
*const_cast<unsigned int*>(&nLockTime) = tx.nLockTime;
@ -137,6 +208,7 @@ CTransaction& CTransaction::operator=(const CTransaction &tx) {
*const_cast<CAmount*>(&valueBalance) = tx.valueBalance;
*const_cast<std::vector<SpendDescription>*>(&vShieldedSpend) = tx.vShieldedSpend;
*const_cast<std::vector<OutputDescription>*>(&vShieldedOutput) = tx.vShieldedOutput;
orchardBundle = tx.orchardBundle;
*const_cast<std::vector<JSDescription>*>(&vJoinSplit) = tx.vJoinSplit;
*const_cast<Ed25519VerificationKey*>(&joinSplitPubKey) = tx.joinSplitPubKey;
*const_cast<Ed25519Signature*>(&joinSplitSig) = tx.joinSplitSig;

View File

@ -23,6 +23,7 @@
#include "zcash/Proof.hpp"
#include <rust/ed25519/types.h>
#include <rust/orchard.h>
// Overwinter transaction version group id
static constexpr uint32_t OVERWINTER_VERSION_GROUP_ID = 0x03C48270;
@ -46,6 +47,18 @@ static_assert(SAPLING_TX_VERSION >= SAPLING_MIN_TX_VERSION,
static_assert(SAPLING_TX_VERSION <= SAPLING_MAX_TX_VERSION,
"Sapling tx version must not be higher than maximum");
// ZIP225 transaction version group id
// (defined in section 7.1 of the protocol spec)
static constexpr uint32_t ZIP225_VERSION_GROUP_ID = 0x26A7270A;
static_assert(ZIP225_VERSION_GROUP_ID != 0, "version group id must be non-zero as specified in ZIP 202");
// ZIP225 transaction version
static const int32_t ZIP225_TX_VERSION = 5;
static_assert(ZIP225_TX_VERSION >= ZIP225_MIN_TX_VERSION,
"ZIP225 tx version must not be lower than minimum");
static_assert(ZIP225_TX_VERSION <= ZIP225_MAX_TX_VERSION,
"ZIP225 tx version must not be higher than maximum");
// Future transaction version group id
static constexpr uint32_t ZFUTURE_VERSION_GROUP_ID = 0xFFFFFFFF;
static_assert(ZFUTURE_VERSION_GROUP_ID != 0, "version group id must be non-zero as specified in ZIP 202");
@ -78,23 +91,54 @@ static inline size_t JOINSPLIT_SIZE(int transactionVersion) {
return transactionVersion >= SAPLING_TX_VERSION ? 1698 : 1802;
}
/**
* The storage format for Sapling Spend descriptions in v5 transactions.
*/
class SpendDescriptionV5
{
public:
uint256 cv; //!< A value commitment to the value of the input note.
uint256 nullifier; //!< The nullifier of the input note.
uint256 rk; //!< The randomized public key for spendAuthSig.
SpendDescriptionV5() { }
SpendDescriptionV5(uint256 cv, uint256 nullifier, uint256 rk)
: cv(cv), nullifier(nullifier), rk(rk) { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(cv);
READWRITE(nullifier);
READWRITE(rk);
}
};
/**
* A shielded input to a transaction. It contains data that describes a Spend transfer.
*/
class SpendDescription
class SpendDescription : public SpendDescriptionV5
{
public:
typedef std::array<unsigned char, 64> spend_auth_sig_t;
uint256 cv; //!< A value commitment to the value of the input note.
uint256 anchor; //!< A Merkle root of the Sapling note commitment tree at some block height in the past.
uint256 nullifier; //!< The nullifier of the input note.
uint256 rk; //!< The randomized public key for spendAuthSig.
libzcash::GrothProof zkproof; //!< A zero-knowledge proof using the spend circuit.
spend_auth_sig_t spendAuthSig; //!< A signature authorizing this spend.
SpendDescription() { }
SpendDescription(
uint256 cv,
uint256 anchor,
uint256 nullifier,
uint256 rk,
libzcash::GrothProof zkproof,
spend_auth_sig_t spendAuthSig)
: SpendDescriptionV5(cv, nullifier, rk), anchor(anchor), zkproof(zkproof), spendAuthSig(spendAuthSig) { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
@ -126,9 +170,9 @@ public:
};
/**
* A shielded output to a transaction. It contains data that describes an Output transfer.
* The storage format for Sapling Output descriptions in v5 transactions.
*/
class OutputDescription
class OutputDescriptionV5
{
public:
uint256 cv; //!< A value commitment to the value of the output note.
@ -136,9 +180,16 @@ public:
uint256 ephemeralKey; //!< A Jubjub public key.
libzcash::SaplingEncCiphertext encCiphertext; //!< A ciphertext component for the encrypted output note.
libzcash::SaplingOutCiphertext outCiphertext; //!< A ciphertext component for the encrypted output note.
libzcash::GrothProof zkproof; //!< A zero-knowledge proof using the output circuit.
OutputDescription() { }
OutputDescriptionV5() { }
OutputDescriptionV5(
uint256 cv,
uint256 cmu,
uint256 ephemeralKey,
libzcash::SaplingEncCiphertext encCiphertext,
libzcash::SaplingOutCiphertext outCiphertext)
: cv(cv), cmu(cmu), ephemeralKey(ephemeralKey), encCiphertext(encCiphertext), outCiphertext(outCiphertext) { }
ADD_SERIALIZE_METHODS;
@ -149,6 +200,33 @@ public:
READWRITE(ephemeralKey);
READWRITE(encCiphertext);
READWRITE(outCiphertext);
}
};
/**
* A shielded output to a transaction. It contains data that describes an Output transfer.
*/
class OutputDescription : public OutputDescriptionV5
{
public:
libzcash::GrothProof zkproof; //!< A zero-knowledge proof using the output circuit.
OutputDescription() { }
OutputDescription(
uint256 cv,
uint256 cmu,
uint256 ephemeralKey,
libzcash::SaplingEncCiphertext encCiphertext,
libzcash::SaplingOutCiphertext outCiphertext,
libzcash::GrothProof zkproof)
: OutputDescriptionV5(cv, cmu, ephemeralKey, encCiphertext, outCiphertext), zkproof(zkproof) { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
OutputDescriptionV5::SerializationOp(s, ser_action);
READWRITE(zkproof);
}
@ -170,6 +248,134 @@ public:
}
};
/**
* The Sapling component of a v5 transaction.
*/
class SaplingBundle
{
private:
typedef std::array<unsigned char, 64> binding_sig_t;
std::vector<SpendDescriptionV5> vSpendsSapling;
std::vector<OutputDescriptionV5> vOutputsSapling;
uint256 anchorSapling;
std::vector<libzcash::GrothProof> vSpendProofsSapling;
std::vector<SpendDescription::spend_auth_sig_t> vSpendAuthSigSapling;
std::vector<libzcash::GrothProof> vOutputProofsSapling;
public:
CAmount valueBalanceSapling;
binding_sig_t bindingSigSapling = {{0}};
SaplingBundle() : valueBalanceSapling(0) {}
SaplingBundle(
const std::vector<SpendDescription>& vShieldedSpend,
const std::vector<OutputDescription>& vShieldedOutput,
const CAmount& valueBalance,
const binding_sig_t& bindingSig);
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(vSpendsSapling);
READWRITE(vOutputsSapling);
bool hasSapling = !(vSpendsSapling.empty() && vOutputsSapling.empty());
if (hasSapling) {
READWRITE(valueBalanceSapling);
}
if (!vSpendsSapling.empty()) {
READWRITE(anchorSapling);
}
if (ser_action.ForRead()) {
for (auto &spend : vSpendsSapling) {
libzcash::GrothProof zkproof;
READWRITE(zkproof);
vSpendProofsSapling.push_back(zkproof);
}
for (auto &spend : vSpendsSapling) {
SpendDescription::spend_auth_sig_t spendAuthSig;
READWRITE(spendAuthSig);
vSpendAuthSigSapling.push_back(spendAuthSig);
}
for (auto &output : vOutputsSapling) {
libzcash::GrothProof zkproof;
READWRITE(zkproof);
vOutputProofsSapling.push_back(zkproof);
}
} else {
for (auto &zkproof : vSpendProofsSapling) {
READWRITE(zkproof);
}
for (auto &spendAuthSig : vSpendAuthSigSapling) {
READWRITE(spendAuthSig);
}
for (auto &zkproof : vOutputProofsSapling) {
READWRITE(zkproof);
}
}
if (hasSapling) {
READWRITE(bindingSigSapling);
}
}
std::vector<SpendDescription> GetV4ShieldedSpend();
std::vector<OutputDescription> GetV4ShieldedOutput();
};
/**
* The Orchard component of a transaction.
*/
class OrchardBundle
{
private:
/// An optional Orchard bundle (with `nullptr` corresponding to `None`).
/// Memory is allocated by Rust.
std::unique_ptr<OrchardBundlePtr, decltype(&orchard_bundle_free)> inner;
public:
OrchardBundle() : inner(nullptr, orchard_bundle_free) {}
OrchardBundle(OrchardBundle&& bundle) : inner(std::move(bundle.inner)) {}
OrchardBundle(const OrchardBundle& bundle) :
inner(orchard_bundle_clone(bundle.inner.get()), orchard_bundle_free) {}
OrchardBundle& operator=(OrchardBundle&& bundle)
{
if (this != &bundle) {
inner = std::move(bundle.inner);
}
return *this;
}
OrchardBundle& operator=(const OrchardBundle& bundle)
{
if (this != &bundle) {
inner.reset(orchard_bundle_clone(bundle.inner.get()));
}
return *this;
}
template<typename Stream>
void Serialize(Stream& s) const {
RustStream rs(s);
if (!orchard_bundle_serialize(inner.get(), &rs, RustStream<Stream>::write_callback)) {
throw std::ios_base::failure("Failed to serialize v5 Orchard bundle");
}
}
template<typename Stream>
void Unserialize(Stream& s) {
RustStream rs(s);
OrchardBundlePtr* bundle;
if (!orchard_bundle_parse(&rs, RustStream<Stream>::read_callback, &bundle)) {
throw std::ios_base::failure("Failed to parse v5 Orchard bundle");
}
inner.reset(bundle);
}
};
template <typename Stream>
class SproutProofSerializer
{
@ -501,6 +707,11 @@ struct CMutableTransaction;
class CTransaction
{
private:
/// The consensus branch ID that this transaction commits to.
/// Serialized from v5 onwards.
uint32_t nConsensusBranchId;
OrchardBundle orchardBundle;
/** Memory only. */
const uint256 hash;
void UpdateHash() const;
@ -597,6 +808,11 @@ public:
nVersionGroupId == SAPLING_VERSION_GROUP_ID &&
nVersion == SAPLING_TX_VERSION;
bool isZip225V5 =
fOverwintered &&
nVersionGroupId == ZIP225_VERSION_GROUP_ID &&
nVersion == ZIP225_TX_VERSION;
// It is not possible to make the transaction's serialized form vary on
// a per-enabled-feature basis. The approach here is that all
// serialization rules for not-yet-released features must be
@ -607,32 +823,66 @@ public:
nVersionGroupId == ZFUTURE_VERSION_GROUP_ID &&
nVersion == ZFUTURE_TX_VERSION;
if (fOverwintered && !(isOverwinterV3 || isSaplingV4 || isFuture)) {
if (fOverwintered && !(isOverwinterV3 || isSaplingV4 || isZip225V5 || isFuture)) {
throw std::ios_base::failure("Unknown transaction format");
}
READWRITE(*const_cast<std::vector<CTxIn>*>(&vin));
READWRITE(*const_cast<std::vector<CTxOut>*>(&vout));
READWRITE(*const_cast<uint32_t*>(&nLockTime));
if (isOverwinterV3 || isSaplingV4 || isFuture) {
if (isZip225V5) {
// Common Transaction Fields (plus version bytes above)
READWRITE(nConsensusBranchId);
READWRITE(*const_cast<uint32_t*>(&nLockTime));
READWRITE(*const_cast<uint32_t*>(&nExpiryHeight));
}
if (isSaplingV4 || isFuture) {
READWRITE(*const_cast<CAmount*>(&valueBalance));
READWRITE(*const_cast<std::vector<SpendDescription>*>(&vShieldedSpend));
READWRITE(*const_cast<std::vector<OutputDescription>*>(&vShieldedOutput));
}
if (nVersion >= 2) {
// These fields do not depend on fOverwintered
auto os = WithVersion(&s, static_cast<int>(header));
::SerReadWrite(os, *const_cast<std::vector<JSDescription>*>(&vJoinSplit), ser_action);
if (vJoinSplit.size() > 0) {
READWRITE(*const_cast<Ed25519VerificationKey*>(&joinSplitPubKey));
READWRITE(*const_cast<Ed25519Signature*>(&joinSplitSig));
// Transparent Transaction Fields
READWRITE(*const_cast<std::vector<CTxIn>*>(&vin));
READWRITE(*const_cast<std::vector<CTxOut>*>(&vout));
// Sapling Transaction Fields
if (ser_action.ForRead()) {
SaplingBundle saplingBundle;
READWRITE(saplingBundle);
*const_cast<std::vector<SpendDescription>*>(&vShieldedSpend) =
saplingBundle.GetV4ShieldedSpend();
*const_cast<std::vector<OutputDescription>*>(&vShieldedOutput) =
saplingBundle.GetV4ShieldedOutput();
*const_cast<CAmount*>(&valueBalance) = saplingBundle.valueBalanceSapling;
*const_cast<binding_sig_t*>(&bindingSig) = saplingBundle.bindingSigSapling;
} else {
SaplingBundle saplingBundle(
vShieldedSpend,
vShieldedOutput,
valueBalance,
bindingSig);
READWRITE(saplingBundle);
}
// Orchard Transaction Fields
READWRITE(orchardBundle);
} else {
// Legacy transaction formats
READWRITE(*const_cast<std::vector<CTxIn>*>(&vin));
READWRITE(*const_cast<std::vector<CTxOut>*>(&vout));
READWRITE(*const_cast<uint32_t*>(&nLockTime));
if (isOverwinterV3 || isSaplingV4 || isFuture) {
READWRITE(*const_cast<uint32_t*>(&nExpiryHeight));
}
if (isSaplingV4 || isFuture) {
READWRITE(*const_cast<CAmount*>(&valueBalance));
READWRITE(*const_cast<std::vector<SpendDescription>*>(&vShieldedSpend));
READWRITE(*const_cast<std::vector<OutputDescription>*>(&vShieldedOutput));
}
if (nVersion >= 2) {
// These fields do not depend on fOverwintered
auto os = WithVersion(&s, static_cast<int>(header));
::SerReadWrite(os, *const_cast<std::vector<JSDescription>*>(&vJoinSplit), ser_action);
if (vJoinSplit.size() > 0) {
READWRITE(*const_cast<Ed25519VerificationKey*>(&joinSplitPubKey));
READWRITE(*const_cast<Ed25519Signature*>(&joinSplitSig));
}
}
if ((isSaplingV4 || isFuture) && !(vShieldedSpend.empty() && vShieldedOutput.empty())) {
READWRITE(*const_cast<binding_sig_t*>(&bindingSig));
}
}
if ((isSaplingV4 || isFuture) && !(vShieldedSpend.empty() && vShieldedOutput.empty())) {
READWRITE(*const_cast<binding_sig_t*>(&bindingSig));
}
if (ser_action.ForRead())
UpdateHash();
@ -659,6 +909,14 @@ public:
return header;
}
uint32_t GetConsensusBranchId() const {
return nConsensusBranchId;
}
const OrchardBundle& GetOrchardBundle() const {
return orchardBundle;
}
/*
* Context for the two methods below:
* As at most one of vpub_new and vpub_old is non-zero in every JoinSplit,
@ -708,6 +966,9 @@ struct CMutableTransaction
bool fOverwintered;
int32_t nVersion;
uint32_t nVersionGroupId;
/// The consensus branch ID that this transaction commits to.
/// Serialized from v5 onwards.
uint32_t nConsensusBranchId;
std::vector<CTxIn> vin;
std::vector<CTxOut> vout;
uint32_t nLockTime;
@ -715,6 +976,7 @@ struct CMutableTransaction
CAmount valueBalance;
std::vector<SpendDescription> vShieldedSpend;
std::vector<OutputDescription> vShieldedOutput;
OrchardBundle orchardBundle;
std::vector<JSDescription> vJoinSplit;
Ed25519VerificationKey joinSplitPubKey;
Ed25519Signature joinSplitSig;
@ -754,35 +1016,71 @@ struct CMutableTransaction
fOverwintered &&
nVersionGroupId == SAPLING_VERSION_GROUP_ID &&
nVersion == SAPLING_TX_VERSION;
bool isZip225V5 =
fOverwintered &&
nVersionGroupId == ZIP225_VERSION_GROUP_ID &&
nVersion == ZIP225_TX_VERSION;
bool isFuture =
fOverwintered &&
nVersionGroupId == ZFUTURE_VERSION_GROUP_ID &&
nVersion == ZFUTURE_TX_VERSION;
if (fOverwintered && !(isOverwinterV3 || isSaplingV4 || isFuture)) {
if (fOverwintered && !(isOverwinterV3 || isSaplingV4 || isZip225V5 || isFuture)) {
throw std::ios_base::failure("Unknown transaction format");
}
READWRITE(vin);
READWRITE(vout);
READWRITE(nLockTime);
if (isOverwinterV3 || isSaplingV4 || isFuture) {
if (isZip225V5) {
// Common Transaction Fields (plus version bytes above)
READWRITE(nConsensusBranchId);
READWRITE(nLockTime);
READWRITE(nExpiryHeight);
}
if (isSaplingV4 || isFuture) {
READWRITE(valueBalance);
READWRITE(vShieldedSpend);
READWRITE(vShieldedOutput);
}
if (nVersion >= 2) {
auto os = WithVersion(&s, static_cast<int>(header));
::SerReadWrite(os, vJoinSplit, ser_action);
if (vJoinSplit.size() > 0) {
READWRITE(joinSplitPubKey);
READWRITE(joinSplitSig);
// Transparent Transaction Fields
READWRITE(vin);
READWRITE(vout);
// Sapling Transaction Fields
if (ser_action.ForRead()) {
SaplingBundle saplingBundle;
READWRITE(saplingBundle);
vShieldedSpend = saplingBundle.GetV4ShieldedSpend();
vShieldedOutput = saplingBundle.GetV4ShieldedOutput();
valueBalance = saplingBundle.valueBalanceSapling;
bindingSig = saplingBundle.bindingSigSapling;
} else {
SaplingBundle saplingBundle(
vShieldedSpend,
vShieldedOutput,
valueBalance,
bindingSig);
READWRITE(saplingBundle);
}
// Orchard Transaction Fields
READWRITE(orchardBundle);
} else {
// Legacy transaction formats
READWRITE(vin);
READWRITE(vout);
READWRITE(nLockTime);
if (isOverwinterV3 || isSaplingV4 || isFuture) {
READWRITE(nExpiryHeight);
}
if (isSaplingV4 || isFuture) {
READWRITE(valueBalance);
READWRITE(vShieldedSpend);
READWRITE(vShieldedOutput);
}
if (nVersion >= 2) {
auto os = WithVersion(&s, static_cast<int>(header));
::SerReadWrite(os, vJoinSplit, ser_action);
if (vJoinSplit.size() > 0) {
READWRITE(joinSplitPubKey);
READWRITE(joinSplitSig);
}
}
if ((isSaplingV4 || isFuture) && !(vShieldedSpend.empty() && vShieldedOutput.empty())) {
READWRITE(bindingSig);
}
}
if ((isSaplingV4 || isFuture) && !(vShieldedSpend.empty() && vShieldedOutput.empty())) {
READWRITE(bindingSig);
}
}

View File

@ -0,0 +1,48 @@
// Copyright (c) 2020 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#ifndef ZCASH_RUST_INCLUDE_RUST_ORCHARD_H
#define ZCASH_RUST_INCLUDE_RUST_ORCHARD_H
#include "rust/streams.h"
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
struct OrchardBundlePtr;
typedef struct OrchardBundlePtr OrchardBundlePtr;
/// Clones the given Orchard bundle.
///
/// Both bundles need to be separately freed when they go out of scope.
OrchardBundlePtr* orchard_bundle_clone(const OrchardBundlePtr* bundle);
/// Frees an Orchard bundle returned from `orchard_parse_bundle`.
void orchard_bundle_free(OrchardBundlePtr* bundle);
/// Parses an authorized Orchard bundle from the given stream.
///
/// - If no error occurs, `bundle_ret` will point to a Rust-allocated Orchard bundle.
/// - If an error occurs, `bundle_ret` will be unaltered.
bool orchard_bundle_parse(
void* stream,
read_callback_t read_cb,
OrchardBundlePtr** bundle_ret);
/// Serializes an authorized Orchard bundle to the given stream
///
/// If `bundle == nullptr`, this serializes `nActionsOrchard = 0`.
bool orchard_bundle_serialize(
const OrchardBundlePtr* bundle,
void* stream,
write_callback_t write_cb);
#ifdef __cplusplus
}
#endif
#endif // ZCASH_RUST_INCLUDE_RUST_ORCHARD_H

View File

@ -0,0 +1,23 @@
// Copyright (c) 2020 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#ifndef ZCASH_RUST_INCLUDE_RUST_STREAMS_H
#define ZCASH_RUST_INCLUDE_RUST_STREAMS_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/// The type that Rust expects for its `CppStreamReader` callback.
typedef long (*read_callback_t)(void* context, unsigned char* pch, size_t nSize);
/// The type that Rust expects for its `CppStreamWriter` callback.
typedef long (*write_callback_t)(void* context, const unsigned char* pch, size_t nSize);
#ifdef __cplusplus
}
#endif
#endif // ZCASH_RUST_INCLUDE_RUST_STREAMS_H

View File

@ -0,0 +1,67 @@
use std::ptr;
use orchard::{bundle::Authorized, Bundle};
use tracing::error;
use zcash_primitives::transaction::components::{orchard as orchard_serialization, Amount};
use crate::streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb};
#[no_mangle]
pub extern "C" fn orchard_bundle_clone(
bundle: *const Bundle<Authorized, Amount>,
) -> *mut Bundle<Authorized, Amount> {
unsafe { bundle.as_ref() }
.map(|bundle| Box::into_raw(Box::new(bundle.clone())))
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn orchard_bundle_free(bundle: *mut Bundle<Authorized, Amount>) {
if !bundle.is_null() {
drop(unsafe { Box::from_raw(bundle) });
}
}
#[no_mangle]
pub extern "C" fn orchard_bundle_parse(
stream: Option<StreamObj>,
read_cb: Option<ReadCb>,
bundle_ret: *mut *mut Bundle<Authorized, Amount>,
) -> bool {
let reader = CppStreamReader::from_raw_parts(stream, read_cb.unwrap());
match orchard_serialization::read_v5_bundle(reader) {
Ok(parsed) => {
unsafe {
*bundle_ret = if let Some(bundle) = parsed {
Box::into_raw(Box::new(bundle))
} else {
ptr::null_mut::<Bundle<Authorized, Amount>>()
};
};
true
}
Err(e) => {
error!("Failed to parse Orchard bundle: {}", e);
false
}
}
}
#[no_mangle]
pub extern "C" fn orchard_bundle_serialize(
bundle: *const Bundle<Authorized, Amount>,
stream: Option<StreamObj>,
write_cb: Option<WriteCb>,
) -> bool {
let bundle = unsafe { bundle.as_ref() };
let writer = CppStreamWriter::from_raw_parts(stream, write_cb.unwrap());
match orchard_serialization::write_v5_bundle(bundle, writer) {
Ok(()) => true,
Err(e) => {
error!("{}", e);
false
}
}
}

View File

@ -66,8 +66,11 @@ use zcash_history::{Entry as MMREntry, NodeData as MMRNodeData, Tree as MMRTree}
mod blake2b;
mod ed25519;
mod metrics_ffi;
mod streams_ffi;
mod tracing_ffi;
mod orchard_ffi;
#[cfg(test)]
mod tests;

View File

@ -0,0 +1,54 @@
use std::io;
use std::ptr::NonNull;
use libc::{c_long, c_void, size_t};
pub type StreamObj = NonNull<c_void>;
pub type ReadCb =
unsafe extern "C" fn(obj: Option<StreamObj>, pch: *mut u8, size: size_t) -> c_long;
pub type WriteCb =
unsafe extern "C" fn(obj: Option<StreamObj>, pch: *const u8, size: size_t) -> c_long;
pub struct CppStreamReader {
inner: Option<StreamObj>,
cb: ReadCb,
}
impl CppStreamReader {
pub fn from_raw_parts(inner: Option<StreamObj>, cb: ReadCb) -> Self {
CppStreamReader { inner, cb }
}
}
impl io::Read for CppStreamReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match unsafe { (self.cb)(self.inner, buf.as_mut_ptr(), buf.len()) } {
-1 => Err(io::Error::new(io::ErrorKind::Other, "C++ stream error")),
n => Ok(n as usize),
}
}
}
pub struct CppStreamWriter {
inner: Option<StreamObj>,
cb: WriteCb,
}
impl CppStreamWriter {
pub fn from_raw_parts(inner: Option<StreamObj>, cb: WriteCb) -> Self {
CppStreamWriter { inner, cb }
}
}
impl io::Write for CppStreamWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match unsafe { (self.cb)(self.inner, buf.as_ptr(), buf.len()) } {
-1 => Err(io::Error::new(io::ErrorKind::Other, "C++ stream error")),
n => Ok(n as usize),
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

View File

@ -22,6 +22,51 @@
#include <utility>
#include <vector>
/**
* Wrapper around C++ stream objects, enabling them to be passed into Rust code.
*/
template<typename Stream>
class RustStream {
Stream* stream;
public:
RustStream(Stream& stream_) : stream(&stream_) {}
static long read_callback(void* context, unsigned char* pch, size_t nSize)
{
return reinterpret_cast<RustStream*>(context)->read(
reinterpret_cast<char*>(pch), nSize);
}
static long write_callback(void* context, const unsigned char* pch, size_t nSize)
{
return reinterpret_cast<RustStream*>(context)->write(
reinterpret_cast<const char*>(pch), nSize);
}
long read(char* pch, size_t nSize)
{
try {
stream->read(pch, nSize);
return nSize;
} catch (std::ios_base::failure e) {
// TODO: log
return -1;
}
}
long write(const char* pch, size_t nSize)
{
try {
stream->write(pch, nSize);
return nSize;
} catch (std::ios_base::failure e) {
// TODO: log
return -1;
}
}
};
template<typename Stream>
class OverrideStream
{

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,7 @@
#include "test/data/tx_invalid.json.h"
#include "test/data/tx_valid.json.h"
#include "test/data/zip0244.json.h"
#include "test/test_bitcoin.h"
#include "init.h"
@ -834,4 +835,40 @@ BOOST_AUTO_TEST_CASE(test_IsStandardV2)
BOOST_CHECK(!IsStandardTx(t, reason, chainparams));
}
BOOST_AUTO_TEST_CASE(TxV5)
{
// [
// tx,
// txid,
// auth_digest,
// Option<transparent_input>,
// Option<script_code>,
// Option<amount>,
// sighash_all,
// Option<sighash_none>,
// Option<sighash_single>,
// Option<sighash_all_anyone>,
// Option<sighash_none_anyone>,
// Option<sighash_single_anyone>,
// ]
//
// The optional values are all set together.
UniValue tests = read_json(std::string(json_tests::zip0244, json_tests::zip0244 + sizeof(json_tests::zip0244)));
// Skipping over comments in zip0244.json file
for (size_t idx = 2; idx < tests.size(); idx++) {
UniValue test = tests[idx];
std::string transaction = test[0].get_str();
CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION);
CTransaction tx;
stream >> tx;
// Check that re-serializing the transaction gives the same encoding.
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << tx;
BOOST_CHECK_EQUAL(HexStr(ss.begin(), ss.end()), transaction);
}
}
BOOST_AUTO_TEST_SUITE_END()