Skip to content

Commit

Permalink
fix: fix transaction output hashing (#4483)
Browse files Browse the repository at this point in the history
Description
---
 The newly introduced `class Blake256` was used and calculation of output features consensus bytes was consolidated. Some code organization improvements were done.

_**Note:** This change is also present in #4445._

Motivation and Context
---
Recent changes in the source code base required the cucumber test environment to be updated.

How Has This Been Tested?
---
When running locally: `npm test -- --tags "@critical and not @long-running and not @broken" --profile "none"`
```
26 scenarios (26 passed)
373 steps (373 passed)
18m27.251s (executing steps: 18m17.959s)
```
  • Loading branch information
hansieodendaal authored Aug 16, 2022
1 parent a4b6d01 commit 46d65fc
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 4,154 deletions.
4 changes: 3 additions & 1 deletion integration_tests/features/support/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ const expect = require("chai").expect;
const {
waitFor,
waitForPredicate,
getTransactionOutputHash,
sleep,
withTimeout,
} = require("../../helpers/util");
const {
getTransactionOutputHash,
} = require("../../helpers/transactionOutputHashing");
const { ConnectivityStatus } = require("../../helpers/types");
const TransactionBuilder = require("../../helpers/transactionBuilder");

Expand Down
2 changes: 1 addition & 1 deletion integration_tests/helpers/hashing.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ module.exports = {
domainHashers: {
transactionKdf(label) {
return new DomainHashing(
`com.tari.base_layer.core.transactions.v0.kdf.${label}`
`com.tari.base_layer.core.transactions.kdf.v0.${label}`
);
},
},
Expand Down
52 changes: 9 additions & 43 deletions integration_tests/helpers/transactionBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
assertBufferType,
varintEncode,
} = require("../helpers/util");
const { featuresToConsensusBytes } = require("./transactionOutputHashing");
const { consensusHashers } = require("./hashing");
const { OutputType } = require("./types");

Expand Down Expand Up @@ -41,47 +42,6 @@ class TransactionBuilder {
return Buffer.from(hash).toString("hex");
}

featuresToConsensusBytes(features) {
// base_layer\core\src\transactions\transaction\output_features.rs

// TODO: Keep this number in sync with 'get_current_version()' in 'output_features_version.rs'
const OUTPUT_FEATURES_VERSION = 0x00;

const bufFromOpt = (opt, encoding = "hex") =>
opt
? Buffer.concat([
Buffer.from([0x01]),
encoding ? Buffer.from(opt, encoding) : opt,
])
: Buffer.from([0x00]);

// Add length byte to unique id - note this only works until 127 bytes (TODO: varint encoding)
let unique_id = features.unique_id
? this.toLengthEncoded(features.unique_id)
: null;

return Buffer.concat([
// version
Buffer.from([OUTPUT_FEATURES_VERSION]),
Buffer.from([parseInt(features.maturity || 0)]),
Buffer.from([features.output_type]),
bufFromOpt(features.parent_public_key, "hex"),
bufFromOpt(unique_id, false),
// TODO: SideChainFeatures
bufFromOpt(null),
// TODO: AssetOutputFeatures
bufFromOpt(null),
// TODO: MintNonFungibleFeatures
bufFromOpt(null),
// TODO: SideChainCheckpointFeatures
bufFromOpt(null),
// TODO: metadata (len is 0)
Buffer.from([0x00]),
// TODO: committee_definition (len is 0)
bufFromOpt(null),
]);
}

toLengthEncoded(buf) {
return Buffer.concat([varintEncode(buf.length), buf]);
}
Expand All @@ -97,18 +57,19 @@ class TransactionBuilder {
encryptedValue,
minimumValuePromise
) {
assertBufferType(publicNonce, 32);
assertBufferType(script);
assertBufferType(scriptOffsetPublicKey, 32);
assertBufferType(publicNonce, 32);
assertBufferType(commitment, 32);
assertBufferType(covenant);
assertBufferType(encryptedValue);

// base_layer/core/src/transactions/transaction/transaction_output.rs
let hash = consensusHashers
.transactionHasher("metadata_signature")
.chain(publicNonce)
.chain(this.toLengthEncoded(script))
.chain(this.featuresToConsensusBytes(features))
.chain(featuresToConsensusBytes(features))
.chain(scriptOffsetPublicKey)
.chain(commitment)
.chain(this.toLengthEncoded(covenant))
Expand Down Expand Up @@ -203,6 +164,7 @@ class TransactionBuilder {
sender_offset_public_key: input.output.sender_offset_public_key,
covenant: Buffer.from([]),
encrypted_value: input.output.encrypted_value,
minimum_value_promise: input.output.minimum_value_promise,
},
amount: input.amount,
privateKey: input.privateKey,
Expand Down Expand Up @@ -267,6 +229,7 @@ class TransactionBuilder {
asset: null,
mint_non_fungible: null,
sidechain_checkpoint: null,
committee_definition: null,
});
let minimumValuePromise = 0;
let meta_challenge = this.buildMetaChallenge(
Expand Down Expand Up @@ -308,6 +271,7 @@ class TransactionBuilder {
},
covenant: covenantBytes,
encrypted_value: encryptedValue,
minimum_value_promise: minimumValuePromise,
},
};
this.outputs.push(output);
Expand Down Expand Up @@ -481,6 +445,7 @@ class TransactionBuilder {
asset: null,
mint_non_fungible: null,
sidechain_checkpoint: null,
committee_definition: null,
};
let minimumValuePromise = 0;
let meta_challenge = this.buildMetaChallenge(
Expand Down Expand Up @@ -520,6 +485,7 @@ class TransactionBuilder {
},
covenant: covenantBytes,
encrypted_value: encryptedValue,
minimum_value_promise: minimumValuePromise,
},
],
kernels: [
Expand Down
92 changes: 92 additions & 0 deletions integration_tests/helpers/transactionOutputHashing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2022 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

const { Blake256 } = require("./hashing");
const {
toLittleEndian,
encodeOption,
toLengthEncoded,
assertBufferType,
} = require("./util");

const featuresToConsensusBytes = function (features) {
// base_layer\core\src\transactions\transaction\output_features.rs (fn consensus_encode)

// TODO: Keep this number in sync with 'get_current_version()' in 'output_features_version.rs'
const OUTPUT_FEATURES_VERSION = 0x00;

// Add length byte to unique id - note this only works until 127 bytes (TODO: varint encoding)
let unique_id = features.unique_id
? toLengthEncoded(features.unique_id)
: null;

return Buffer.concat([
// version
Buffer.from([OUTPUT_FEATURES_VERSION]),
// maturity
Buffer.from([parseInt(features.maturity || 0)]),
// output_type
Buffer.from([features.output_type]),
// parent_public_key
encodeOption(features.parent_public_key, "hex"),
// unique_id
encodeOption(unique_id, false),
// sidechain_features
// TODO: SideChainFeatures
encodeOption(null),
// asset
// TODO: AssetOutputFeatures
encodeOption(null),
// mint_non_fungible
// TODO: MintNonFungibleFeatures
encodeOption(null),
// sidechain_checkpoint
// TODO: SideChainCheckpointFeatures
encodeOption(null),
// metadata
// TODO: Vec<u8> (len is 0)
Buffer.from([0x00]),
// committee_definition
// TODO: CommitteeDefinitionFeatures (len is 0)
encodeOption(null),
]);
};

const getTransactionOutputHash = function (output) {
// base_layer\core\src\transactions\transaction_components\mod.rs (fn hash_output)

// TODO: Keep this number in sync with 'get_current_version()' in 'transaction_output_version.rs'
const OUTPUT_FEATURES_VERSION = 0x00;

let hasher = new Blake256();
assertBufferType(output.commitment, 32);
assertBufferType(output.script);
assertBufferType(output.covenant);
assertBufferType(output.encrypted_value, 24);
const hash = hasher
// version
.chain(Buffer.from([OUTPUT_FEATURES_VERSION]))
// features
.chain(featuresToConsensusBytes(output.features))
// commitment
.chain(output.commitment)
// script
.chain(toLengthEncoded(output.script))
// covenant
.chain(toLengthEncoded(output.covenant))
// encrypted_value
.chain(output.encrypted_value)
// minimum_value_promise
.chain(toLittleEndian(output.minimum_value_promise, 64))
.finalize();

const hashBuffer = Buffer.from(hash);
// console.log(
// "\ngetTransactionOutputHash - hash",
// hashBuffer.toString("hex"),
// "\n"
// );
return hashBuffer;
};

module.exports = { getTransactionOutputHash, featuresToConsensusBytes };
106 changes: 12 additions & 94 deletions integration_tests/helpers/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
const net = require("net");
const varint = require("varint");

const { blake2bInit, blake2bUpdate, blake2bFinal } = require("blakejs");
const { expect } = require("chai");

const NO_CONNECTION = 14;

function getRandomInt(min, max) {
Expand Down Expand Up @@ -233,103 +230,19 @@ const getFreePort = function () {
});
};

const encodeOption = function (value) {
const encodeOption = function (value, encoding = "hex") {
let buffer;
if (value) {
buffer = Buffer.concat([Buffer.from([1]), Buffer.from(value, "utf8")]);
buffer = Buffer.concat([
Buffer.from([0x01]),
encoding ? Buffer.from(value, encoding) : value,
]);
} else {
buffer = Buffer.from([0]);
buffer = Buffer.from([0x00]);
}
return buffer;
};

const getTransactionOutputHash = function (output) {
const KEY = null; // optional key
const OUTPUT_LENGTH = 32; // bytes
const context = blake2bInit(OUTPUT_LENGTH, KEY);
let encodedBytesLength = 0;
// version
const version = Buffer.from([0]);
encodedBytesLength += version.length;
blake2bUpdate(context, version);
// features
let features = Buffer.concat([
// features.version
Buffer.from([0]),
// features.maturity
Buffer.from([parseInt(output.features.maturity)]),
// features.output_type
Buffer.from([output.features.output_type]),
]);
// features.parent_public_key
features = Buffer.concat([
Buffer.from(features),
encodeOption(output.features.parent_public_key),
]);
// features.unique_id
features = Buffer.concat([
Buffer.from(features),
encodeOption(output.features.unique_id),
]);
// features.sidechain_features
features = Buffer.concat([
Buffer.from(features),
encodeOption(output.features.sidechain_features),
]); // features.asset
features = Buffer.concat([
Buffer.from(features),
encodeOption(output.features.asset),
]);
// features.mint_non_fungible
features = Buffer.concat([
Buffer.from(features),
encodeOption(output.features.mint_non_fungible),
]);
// features.sidechain_checkpoint
features = Buffer.concat([
Buffer.from(features),
encodeOption(output.features.sidechain_checkpoint),
]);
// features.metadata
features = Buffer.concat([
Buffer.from(features),
Buffer.from([output.features.metadata.length]),
Buffer.from(output.features.metadata),
]);
encodedBytesLength += features.length;
blake2bUpdate(context, features);
// commitment
encodedBytesLength += output.commitment.length;
blake2bUpdate(context, output.commitment);
// script
const script = Buffer.concat([
Buffer.from([output.script.length]),
Buffer.from(output.script),
]);
encodedBytesLength += script.length;
blake2bUpdate(context, script);
// covenant
const covenant = Buffer.concat([
Buffer.from([output.covenant.length]),
Buffer.from(output.covenant),
]);
encodedBytesLength += covenant.length;
blake2bUpdate(context, covenant);
// encrypted_value
encodedBytesLength += output.encrypted_value.length;
blake2bUpdate(context, output.encrypted_value);

expect(context.c).to.equal(encodedBytesLength);
const hash = blake2bFinal(context);
const hashBuffer = Buffer.from(hash);
// console.log(
// "\ngetTransactionOutputHash - hash",
// hashBuffer.toString("hex"),
// "\n"
// );
return hashBuffer;
};

function consoleLogTransactionDetails(txnDetails) {
console.log(
" Transaction " +
Expand Down Expand Up @@ -445,6 +358,10 @@ function varintEncode(num) {
return Buffer.from(varint.encode(num));
}

function toLengthEncoded(buf) {
return Buffer.concat([varintEncode(buf.length), buf]);
}

module.exports = {
assertBufferType,
varintEncode,
Expand All @@ -455,7 +372,6 @@ module.exports = {
littleEndianHexStringToBigEndianHexString,
// portInUse,
getFreePort,
getTransactionOutputHash,
hexSwitchEndianness,
consoleLogTransactionDetails,
tryConnect,
Expand All @@ -470,4 +386,6 @@ module.exports = {
NO_CONNECTION,
multiAddrToSocket,
findUtxoWithOutputMessage,
encodeOption,
toLengthEncoded,
};
Loading

0 comments on commit 46d65fc

Please sign in to comment.