Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/sapling/sapling_operation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ OperationResult SaplingOperation::build()

// Build the transaction
txBuilder.SetFee(nFeeRet);
TransactionBuilderResult txResult = txBuilder.Build();
TransactionBuilderResult txResult = txBuilder.Build(true);
auto opTx = txResult.GetTx();

// Check existent tx
Expand Down Expand Up @@ -213,6 +213,16 @@ OperationResult SaplingOperation::build()
}
// Done
fee = nFeeRet;

// Clear dummy signatures/proofs and add real ones
txBuilder.ClearProofsAndSignatures();
TransactionBuilderResult txResult = txBuilder.ProveAndSign();
auto opTx = txResult.GetTx();
// Check existent tx
if (!opTx) {
return errorOut("Failed to build transaction: " + txResult.GetError());
}
finalTx = *opTx;
return OperationResult(true);
}

Expand Down
217 changes: 148 additions & 69 deletions src/sapling/transaction_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,35 @@ Optional<OutputDescription> OutputDescriptionInfo::Build(void* ctx) {
return odesc;
}

// Dummy constants used during fee-calculation loop
static OutputDescription CreateDummyOD()
{
OutputDescription dummyOD;
dummyOD.cv = UINT256_MAX;
dummyOD.cmu = UINT256_MAX;
dummyOD.ephemeralKey = UINT256_MAX;
dummyOD.encCiphertext = {{0xff}};
dummyOD.outCiphertext = {{0xff}};
dummyOD.zkproof = {{0xff}};
return dummyOD;
}
static SpendDescription CreateDummySD()
{
SpendDescription dummySD;
dummySD.cv = UINT256_MAX;
dummySD.anchor = UINT256_MAX;
dummySD.nullifier = UINT256_MAX;
dummySD.rk = UINT256_MAX;
dummySD.zkproof = {{0xff}};
dummySD.spendAuthSig = {{0xff}};
return dummySD;
}

const OutputDescription DUMMY_SHIELD_OUT = CreateDummyOD();
const SpendDescription DUMMY_SHIELD_SPEND = CreateDummySD();
const SaplingTxData::binding_sig_t DUMMY_SHIELD_BINDSIG = {{0xff}};


TransactionBuilderResult::TransactionBuilderResult(const CTransaction& tx) : maybeTx(tx) {}

TransactionBuilderResult::TransactionBuilderResult(const std::string& error) : maybeError(error) {}
Expand Down Expand Up @@ -209,66 +238,31 @@ void TransactionBuilder::SendChangeTo(CTxDestination& changeAddr)
saplingChangeAddr = nullopt;
}

TransactionBuilderResult TransactionBuilder::Build()
TransactionBuilderResult TransactionBuilder::ProveAndSign()
{
//
// Consistency checks
// Sapling spend descriptions
//
// Valid fee
if (fee < 0) {
return TransactionBuilderResult("Fee cannot be negative");
}

// Valid change
CAmount change = mtx.sapData->valueBalance - fee;
for (auto& tIn : tIns) {
change += tIn.value;
}
for (auto& tOut : mtx.vout) {
change -= tOut.nValue;
}
if (change < 0) {
return TransactionBuilderResult("Change cannot be negative");
}
if (!spends.empty() || !outputs.empty()) {

//
// Change output
//
auto ctx = librustzcash_sapling_proving_ctx_init();

if (change > 0) {
// If we get here and the change is dust, add it to the fee
CAmount dustThreshold = (spends.empty() && outputs.empty()) ? GetDustThreshold(minRelayTxFee) :
GetShieldedDustThreshold(minRelayTxFee);
if (change > dustThreshold) {
// Send change to the specified change address. If no change address
// was set, send change to the first Sapling address given as input
// (A t-address can only be used as the change address if explicitly set.)
if (saplingChangeAddr) {
AddSaplingOutput(saplingChangeAddr->first, saplingChangeAddr->second, change);
} else if (tChangeAddr) {
// tChangeAddr has already been validated.
AddTransparentOutput(*tChangeAddr, change);
} else if (!spends.empty()) {
auto fvk = spends[0].expsk.full_viewing_key();
auto note = spends[0].note;
libzcash::SaplingPaymentAddress changeAddr(note.d, note.pk_d);
AddSaplingOutput(fvk.ovk, changeAddr, change);
} else {
return TransactionBuilderResult("Could not determine change address");
// Create Sapling OutputDescriptions
for (auto output : outputs) {
// Check this out here as well to provide better logging.
if (!output.note.cmu()) {
librustzcash_sapling_proving_ctx_free(ctx);
return TransactionBuilderResult("Output is invalid");
}
} else {
// Not used after, but update for consistency
fee += change;
change = 0;
}
}

//
// Sapling spends and outputs
//
if (!spends.empty() || !outputs.empty()) {
auto odesc = output.Build(ctx);
if (!odesc) {
librustzcash_sapling_proving_ctx_free(ctx);
return TransactionBuilderResult("Failed to create output description");
}

auto ctx = librustzcash_sapling_proving_ctx_init();
mtx.sapData->vShieldedOutput.push_back(odesc.get());
}

// Create Sapling SpendDescriptions
for (auto spend : spends) {
Expand Down Expand Up @@ -307,23 +301,6 @@ TransactionBuilderResult TransactionBuilder::Build()
mtx.sapData->vShieldedSpend.push_back(sdesc);
}

// Create Sapling OutputDescriptions
for (auto output : outputs) {
// Check this out here as well to provide better logging.
if (!output.note.cmu()) {
librustzcash_sapling_proving_ctx_free(ctx);
return TransactionBuilderResult("Output is invalid");
}

auto odesc = output.Build(ctx);
if (!odesc) {
librustzcash_sapling_proving_ctx_free(ctx);
return TransactionBuilderResult("Failed to create output description");
}

mtx.sapData->vShieldedOutput.push_back(odesc.get());
}

//
// Signatures
//
Expand Down Expand Up @@ -375,3 +352,105 @@ TransactionBuilderResult TransactionBuilder::Build()

return TransactionBuilderResult(CTransaction(mtx));
}

TransactionBuilderResult TransactionBuilder::AddDummySignatures()
{
if (!spends.empty() || !outputs.empty()) {
// Add Dummy Sapling OutputDescriptions
for (unsigned int i = 0; i < outputs.size(); i++) {
mtx.sapData->vShieldedOutput.push_back(DUMMY_SHIELD_OUT);
}
// Add Dummy Sapling SpendDescriptions
for (unsigned int i = 0; i < spends.size(); i++) {
mtx.sapData->vShieldedSpend.push_back(DUMMY_SHIELD_SPEND);
}
// Add Dummy Binding sig
mtx.sapData->bindingSig = DUMMY_SHIELD_BINDSIG;
}

// Add Dummmy Transparent signatures
CTransaction txNewConst(mtx);
for (int nIn = 0; nIn < (int) mtx.vin.size(); nIn++) {
auto tIn = tIns[nIn];
SignatureData sigdata;
if (!ProduceSignature(DummySignatureCreator(keystore), tIn.scriptPubKey, sigdata, SIGVERSION_SAPLING, false)) {
return TransactionBuilderResult("Failed to sign transaction");
} else {
UpdateTransaction(mtx, nIn, sigdata);
}
}

return TransactionBuilderResult(CTransaction(mtx));
}

void TransactionBuilder::ClearProofsAndSignatures()
{
// Clear Sapling output descriptions
mtx.sapData->vShieldedOutput.clear();

// Clear Sapling spend descriptions
mtx.sapData->vShieldedSpend.clear();

// Clear Binding sig
mtx.sapData->bindingSig = {{0}};

// Clear Transparent signatures
for (CTxIn& in : mtx.vin) in.scriptSig = CScript();
}

TransactionBuilderResult TransactionBuilder::Build(bool fDummySig)
{
//
// Consistency checks
//
// Valid fee
if (fee < 0) {
return TransactionBuilderResult("Fee cannot be negative");
}

// Valid change
CAmount change = mtx.sapData->valueBalance - fee;
for (auto& tIn : tIns) {
change += tIn.value;
}
for (auto& tOut : mtx.vout) {
change -= tOut.nValue;
}
if (change < 0) {
return TransactionBuilderResult("Change cannot be negative");
}

//
// Change output
//

if (change > 0) {
// If we get here and the change is dust, add it to the fee
CAmount dustThreshold = (spends.empty() && outputs.empty()) ? GetDustThreshold(minRelayTxFee) :
GetShieldedDustThreshold(minRelayTxFee);
if (change > dustThreshold) {
// Send change to the specified change address. If no change address
// was set, send change to the first Sapling address given as input
// (A t-address can only be used as the change address if explicitly set.)
if (saplingChangeAddr) {
AddSaplingOutput(saplingChangeAddr->first, saplingChangeAddr->second, change);
} else if (tChangeAddr) {
// tChangeAddr has already been validated.
AddTransparentOutput(*tChangeAddr, change);
} else if (!spends.empty()) {
auto fvk = spends[0].expsk.full_viewing_key();
auto note = spends[0].note;
libzcash::SaplingPaymentAddress changeAddr(note.d, note.pk_d);
AddSaplingOutput(fvk.ovk, changeAddr, change);
} else {
return TransactionBuilderResult("Could not determine change address");
}
} else {
// Not used after, but update for consistency
fee += change;
change = 0;
}
}

return fDummySig ? AddDummySignatures() : ProveAndSign();
}
13 changes: 12 additions & 1 deletion src/sapling/transaction_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ struct TransparentInputInfo {
CAmount value) : scriptPubKey(scriptPubKey), value(value) {}
};

// Dummy constants used during fee-calculation loop
extern const OutputDescription DUMMY_SHIELD_OUT;
extern const SpendDescription DUMMY_SHIELD_SPEND;
extern const SaplingTxData::binding_sig_t DUMMY_SHIELD_BINDSIG;

class TransactionBuilderResult {
private:
Optional<CTransaction> maybeTx;
Expand Down Expand Up @@ -120,7 +125,13 @@ class TransactionBuilder

void SendChangeTo(CTxDestination& changeAddr);

TransactionBuilderResult Build();
TransactionBuilderResult Build(bool fDummySig = false);
// Add Sapling Spend/Output descriptions, binding sig, and transparent signatures
TransactionBuilderResult ProveAndSign();
// Add dummy Sapling Spend/Output descriptions, binding sig, and transparent signatures
TransactionBuilderResult AddDummySignatures();
// Remove Sapling Spend/Output descriptions, binding sig, and transparent signatures
void ClearProofsAndSignatures();
};

#endif /* TRANSACTION_BUILDER_H */
1 change: 1 addition & 0 deletions src/uint256.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,6 @@ arith_uint512 UintToArith512(const uint512 &);
/** constant uint256 instances */
const uint256 UINT256_ZERO = uint256();
const uint256 UINT256_ONE = uint256("0000000000000000000000000000000000000000000000000000000000000001");
const uint256 UINT256_MAX = uint256("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");

#endif // PIVX_UINT256_H
Loading