Skip to content
64 changes: 29 additions & 35 deletions barretenberg/cpp/src/barretenberg/dsl/acir_proofs/honk_contract.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,14 +366,12 @@ library TranscriptLib {
function generateTranscript(
Honk.Proof memory proof,
bytes32[] calldata publicInputs,
uint256 circuitSize,
uint256 publicInputsSize,
uint256 pubInputsOffset
uint256 vkHash,
uint256 publicInputsSize
) internal pure returns (Transcript memory t) {
Fr previousChallenge;
(t.relationParameters, previousChallenge) = generateRelationParametersChallenges(
proof, publicInputs, circuitSize, publicInputsSize, pubInputsOffset, previousChallenge
);
(t.relationParameters, previousChallenge) =
generateRelationParametersChallenges(proof, publicInputs, vkHash, publicInputsSize, previousChallenge);

(t.alphas, previousChallenge) = generateAlphaChallenges(previousChallenge, proof);

Expand Down Expand Up @@ -403,50 +401,46 @@ library TranscriptLib {
function generateRelationParametersChallenges(
Honk.Proof memory proof,
bytes32[] calldata publicInputs,
uint256 circuitSize,
uint256 vkHash,
uint256 publicInputsSize,
uint256 pubInputsOffset,
Fr previousChallenge
) internal pure returns (Honk.RelationParameters memory rp, Fr nextPreviousChallenge) {
(rp.eta, rp.etaTwo, rp.etaThree, previousChallenge) =
generateEtaChallenge(proof, publicInputs, circuitSize, publicInputsSize, pubInputsOffset);
generateEtaChallenge(proof, publicInputs, vkHash, publicInputsSize);

(rp.beta, rp.gamma, nextPreviousChallenge) = generateBetaAndGammaChallenges(previousChallenge, proof);
}

function generateEtaChallenge(
Honk.Proof memory proof,
bytes32[] calldata publicInputs,
uint256 circuitSize,
uint256 publicInputsSize,
uint256 pubInputsOffset
uint256 vkHash,
uint256 publicInputsSize
) internal pure returns (Fr eta, Fr etaTwo, Fr etaThree, Fr previousChallenge) {
bytes32[] memory round0 = new bytes32[](3 + publicInputsSize + 12);
round0[0] = bytes32(circuitSize);
round0[1] = bytes32(publicInputsSize);
round0[2] = bytes32(pubInputsOffset);
bytes32[] memory round0 = new bytes32[](1 + publicInputsSize + 12);
round0[0] = bytes32(vkHash);

for (uint256 i = 0; i < publicInputsSize - PAIRING_POINTS_SIZE; i++) {
round0[3 + i] = bytes32(publicInputs[i]);
round0[1 + i] = bytes32(publicInputs[i]);
}
for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
round0[3 + publicInputsSize - PAIRING_POINTS_SIZE + i] = FrLib.toBytes32(proof.pairingPointObject[i]);
round0[1 + publicInputsSize - PAIRING_POINTS_SIZE + i] = FrLib.toBytes32(proof.pairingPointObject[i]);
}

// Create the first challenge
// Note: w4 is added to the challenge later on
round0[3 + publicInputsSize] = bytes32(proof.w1.x_0);
round0[3 + publicInputsSize + 1] = bytes32(proof.w1.x_1);
round0[3 + publicInputsSize + 2] = bytes32(proof.w1.y_0);
round0[3 + publicInputsSize + 3] = bytes32(proof.w1.y_1);
round0[3 + publicInputsSize + 4] = bytes32(proof.w2.x_0);
round0[3 + publicInputsSize + 5] = bytes32(proof.w2.x_1);
round0[3 + publicInputsSize + 6] = bytes32(proof.w2.y_0);
round0[3 + publicInputsSize + 7] = bytes32(proof.w2.y_1);
round0[3 + publicInputsSize + 8] = bytes32(proof.w3.x_0);
round0[3 + publicInputsSize + 9] = bytes32(proof.w3.x_1);
round0[3 + publicInputsSize + 10] = bytes32(proof.w3.y_0);
round0[3 + publicInputsSize + 11] = bytes32(proof.w3.y_1);
round0[1 + publicInputsSize] = bytes32(proof.w1.x_0);
round0[1 + publicInputsSize + 1] = bytes32(proof.w1.x_1);
round0[1 + publicInputsSize + 2] = bytes32(proof.w1.y_0);
round0[1 + publicInputsSize + 3] = bytes32(proof.w1.y_1);
round0[1 + publicInputsSize + 4] = bytes32(proof.w2.x_0);
round0[1 + publicInputsSize + 5] = bytes32(proof.w2.x_1);
round0[1 + publicInputsSize + 6] = bytes32(proof.w2.y_0);
round0[1 + publicInputsSize + 7] = bytes32(proof.w2.y_1);
round0[1 + publicInputsSize + 8] = bytes32(proof.w3.x_0);
round0[1 + publicInputsSize + 9] = bytes32(proof.w3.x_1);
round0[1 + publicInputsSize + 10] = bytes32(proof.w3.y_0);
round0[1 + publicInputsSize + 11] = bytes32(proof.w3.y_1);

previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round0)));
(eta, etaTwo) = splitChallenge(previousChallenge);
Expand Down Expand Up @@ -1731,11 +1725,13 @@ abstract contract BaseHonkVerifier is IVerifier {

uint256 immutable $N;
uint256 immutable $LOG_N;
uint256 immutable $VK_HASH;
uint256 immutable $NUM_PUBLIC_INPUTS;

constructor(uint256 _N, uint256 _logN, uint256 _numPublicInputs) {
constructor(uint256 _N, uint256 _logN, uint256 _vkHash, uint256 _numPublicInputs) {
$N = _N;
$LOG_N = _logN;
$VK_HASH = _vkHash;
$NUM_PUBLIC_INPUTS = _numPublicInputs;
}

Expand Down Expand Up @@ -1764,9 +1760,7 @@ abstract contract BaseHonkVerifier is IVerifier {
}

// Generate the fiat shamir challenges for the whole protocol
Transcript memory t = TranscriptLib.generateTranscript(
p, publicInputs, vk.circuitSize, $NUM_PUBLIC_INPUTS, /*pubInputsOffset=*/ 1
);
Transcript memory t = TranscriptLib.generateTranscript(p, publicInputs, $VK_HASH, $NUM_PUBLIC_INPUTS);

// Derive public input delta
t.relationParameters.publicInputsDelta = computePublicInputDelta(
Expand Down Expand Up @@ -2170,7 +2164,7 @@ abstract contract BaseHonkVerifier is IVerifier {
}
}

contract HonkVerifier is BaseHonkVerifier(N, LOG_N, NUMBER_OF_PUBLIC_INPUTS) {
contract HonkVerifier is BaseHonkVerifier(N, LOG_N, VK_HASH, NUMBER_OF_PUBLIC_INPUTS) {
function loadVerificationKey() internal pure override returns (Honk.VerificationKey memory) {
return HonkVerificationKey.loadVerificationKey();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,14 +368,12 @@ library ZKTranscriptLib {
function generateTranscript(
Honk.ZKProof memory proof,
bytes32[] calldata publicInputs,
uint256 circuitSize,
uint256 publicInputsSize,
uint256 pubInputsOffset
uint256 vkHash,
uint256 publicInputsSize
) external pure returns (ZKTranscript memory t) {
Fr previousChallenge;
(t.relationParameters, previousChallenge) = generateRelationParametersChallenges(
proof, publicInputs, circuitSize, publicInputsSize, pubInputsOffset, previousChallenge
);
(t.relationParameters, previousChallenge) =
generateRelationParametersChallenges(proof, publicInputs, vkHash, publicInputsSize, previousChallenge);

(t.alphas, previousChallenge) = generateAlphaChallenges(previousChallenge, proof);

Expand Down Expand Up @@ -404,49 +402,46 @@ library ZKTranscriptLib {
function generateRelationParametersChallenges(
Honk.ZKProof memory proof,
bytes32[] calldata publicInputs,
uint256 circuitSize,
uint256 vkHash,
uint256 publicInputsSize,
uint256 pubInputsOffset,
Fr previousChallenge
) internal pure returns (Honk.RelationParameters memory rp, Fr nextPreviousChallenge) {
(rp.eta, rp.etaTwo, rp.etaThree, previousChallenge) =
generateEtaChallenge(proof, publicInputs, circuitSize, publicInputsSize, pubInputsOffset);
generateEtaChallenge(proof, publicInputs, vkHash, publicInputsSize);

(rp.beta, rp.gamma, nextPreviousChallenge) = generateBetaAndGammaChallenges(previousChallenge, proof);
}

function generateEtaChallenge(
Honk.ZKProof memory proof,
bytes32[] calldata publicInputs,
uint256 circuitSize,
uint256 publicInputsSize,
uint256 pubInputsOffset
uint256 vkHash,
uint256 publicInputsSize
) internal pure returns (Fr eta, Fr etaTwo, Fr etaThree, Fr previousChallenge) {
bytes32[] memory round0 = new bytes32[](3 + publicInputsSize + 12);
round0[0] = bytes32(circuitSize);
round0[1] = bytes32(publicInputsSize);
round0[2] = bytes32(pubInputsOffset);
bytes32[] memory round0 = new bytes32[](1 + publicInputsSize + 12);
round0[0] = bytes32(vkHash);

for (uint256 i = 0; i < publicInputsSize - PAIRING_POINTS_SIZE; i++) {
round0[3 + i] = bytes32(publicInputs[i]);
round0[1 + i] = bytes32(publicInputs[i]);
}
for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
round0[3 + publicInputsSize - PAIRING_POINTS_SIZE + i] = FrLib.toBytes32(proof.pairingPointObject[i]);
round0[1 + publicInputsSize - PAIRING_POINTS_SIZE + i] = FrLib.toBytes32(proof.pairingPointObject[i]);
}

// Create the first challenge
// Note: w4 is added to the challenge later on
round0[3 + publicInputsSize] = bytes32(proof.w1.x_0);
round0[3 + publicInputsSize + 1] = bytes32(proof.w1.x_1);
round0[3 + publicInputsSize + 2] = bytes32(proof.w1.y_0);
round0[3 + publicInputsSize + 3] = bytes32(proof.w1.y_1);
round0[3 + publicInputsSize + 4] = bytes32(proof.w2.x_0);
round0[3 + publicInputsSize + 5] = bytes32(proof.w2.x_1);
round0[3 + publicInputsSize + 6] = bytes32(proof.w2.y_0);
round0[3 + publicInputsSize + 7] = bytes32(proof.w2.y_1);
round0[3 + publicInputsSize + 8] = bytes32(proof.w3.x_0);
round0[3 + publicInputsSize + 9] = bytes32(proof.w3.x_1);
round0[3 + publicInputsSize + 10] = bytes32(proof.w3.y_0);
round0[3 + publicInputsSize + 11] = bytes32(proof.w3.y_1);
round0[1 + publicInputsSize] = bytes32(proof.w1.x_0);
round0[1 + publicInputsSize + 1] = bytes32(proof.w1.x_1);
round0[1 + publicInputsSize + 2] = bytes32(proof.w1.y_0);
round0[1 + publicInputsSize + 3] = bytes32(proof.w1.y_1);
round0[1 + publicInputsSize + 4] = bytes32(proof.w2.x_0);
round0[1 + publicInputsSize + 5] = bytes32(proof.w2.x_1);
round0[1 + publicInputsSize + 6] = bytes32(proof.w2.y_0);
round0[1 + publicInputsSize + 7] = bytes32(proof.w2.y_1);
round0[1 + publicInputsSize + 8] = bytes32(proof.w3.x_0);
round0[1 + publicInputsSize + 9] = bytes32(proof.w3.x_1);
round0[1 + publicInputsSize + 10] = bytes32(proof.w3.y_0);
round0[1 + publicInputsSize + 11] = bytes32(proof.w3.y_1);

previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round0)));
(eta, etaTwo) = splitChallenge(previousChallenge);
Expand Down Expand Up @@ -1793,11 +1788,13 @@ abstract contract BaseZKHonkVerifier is IVerifier {

uint256 immutable $N;
uint256 immutable $LOG_N;
uint256 immutable $VK_HASH;
uint256 immutable $NUM_PUBLIC_INPUTS;

constructor(uint256 _N, uint256 _logN, uint256 _numPublicInputs) {
constructor(uint256 _N, uint256 _logN, uint256 _vkHash, uint256 _numPublicInputs) {
$N = _N;
$LOG_N = _logN;
$VK_HASH = _vkHash;
$NUM_PUBLIC_INPUTS = _numPublicInputs;
}

Expand Down Expand Up @@ -1834,9 +1831,7 @@ abstract contract BaseZKHonkVerifier is IVerifier {
}

// Generate the fiat shamir challenges for the whole protocol
ZKTranscript memory t = ZKTranscriptLib.generateTranscript(
p, publicInputs, vk.circuitSize, $NUM_PUBLIC_INPUTS, /*pubInputsOffset=*/ 1
);
ZKTranscript memory t = ZKTranscriptLib.generateTranscript(p, publicInputs, $VK_HASH, $NUM_PUBLIC_INPUTS);

// Derive public input delta
t.relationParameters.publicInputsDelta = computePublicInputDelta(
Expand Down Expand Up @@ -2321,7 +2316,7 @@ abstract contract BaseZKHonkVerifier is IVerifier {
}
}

contract HonkVerifier is BaseZKHonkVerifier(N, LOG_N, NUMBER_OF_PUBLIC_INPUTS) {
contract HonkVerifier is BaseZKHonkVerifier(N, LOG_N, VK_HASH, NUMBER_OF_PUBLIC_INPUTS) {
function loadVerificationKey() internal pure override returns (Honk.VerificationKey memory) {
return HonkVerificationKey.loadVerificationKey();
}
Expand Down
5 changes: 3 additions & 2 deletions barretenberg/cpp/src/barretenberg/flavor/flavor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,10 @@ class NativeVerificationKey_ : public PrecomputedCommitments {
* @details Currently only used in testing.
* @return FF
*/
fr hash()
fr hash() const
{
fr vk_hash = crypto::Poseidon2<crypto::Poseidon2Bn254ScalarFieldParams>::hash(this->to_field_elements());
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1498): should hash be dependent on transcript?
fr vk_hash = Transcript::hash(this->to_field_elements());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its a little backwards that the vk hash is dependent on the transcript hm

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would prefer just overriding this in the keccak class for now

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you think so? the hash function used is defined in the transcript, so i think it makes sense

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless we pull the hash function to be defined in the flavor, then template it into the transcript + others?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because the hash of the vk or whatever object isn't fundamentally dependent on the hash we're using for Fiat-Shamir

Copy link
Member Author

@Maddiaa0 Maddiaa0 Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i get you, ill leave it as keccak

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussion with lucas: correct function is to define hash function in flavor - but is fine as it is - ISSUE

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return vk_hash;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,21 @@ TYPED_TEST(NativeVerificationKeyTests, VKHashingConsistency)
{
using Flavor = TypeParam;
using VerificationKey = typename Flavor::VerificationKey;
using Transcript = typename Flavor::Transcript;

VerificationKey vk(TestFixture::create_vk());

// First method of hashing: using to_field_elements and add_to_hash_buffer.
std::vector<fr> vk_field_elements = vk.to_field_elements();
NativeTranscript transcript;
Transcript transcript;
for (const auto& field_element : vk_field_elements) {
transcript.add_to_independent_hash_buffer("vk_element", field_element);
}
fr vkey_hash_1 = transcript.hash_independent_buffer("vk_hash");
// Second method of hashing: using hash().
fr vkey_hash_2 = vk.hash();
EXPECT_EQ(vkey_hash_1, vkey_hash_2);
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1427): Solidity verifier does not fiat shamir the full
// verification key. This will be fixed in a followup PR.
if constexpr (!IsAnyOf<Flavor, UltraKeccakFlavor, ECCVMFlavor, TranslatorFlavor>) {
if constexpr (!IsAnyOf<Flavor, ECCVMFlavor, TranslatorFlavor>) {
// Third method of hashing: using add_hash_to_transcript.
typename Flavor::Transcript transcript_2;
fr vkey_hash_3 = vk.add_hash_to_transcript("", transcript_2);
Expand Down
13 changes: 5 additions & 8 deletions barretenberg/cpp/src/barretenberg/flavor/ultra_keccak_flavor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,11 @@ class UltraKeccakFlavor : public bb::UltraFlavor {
*/
fr add_hash_to_transcript(const std::string& domain_separator, Transcript& transcript) const override
{
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1427): We need to update this function to look
// like UltraFlavor's add_hash_to_transcript. Alternatively, the VerificationKey class will go away when we
// add pairing point aggregation to the solidity verifier.
uint64_t circuit_size = 1 << this->log_circuit_size;
transcript.add_to_hash_buffer(domain_separator + "vk_log_circuit_size", circuit_size);
transcript.add_to_hash_buffer(domain_separator + "vk_num_public_inputs", this->num_public_inputs);
transcript.add_to_hash_buffer(domain_separator + "vk_pub_inputs_offset", this->pub_inputs_offset);
return 0;
// This hash contains a hash of the entire vk - including all of the elements
const fr hash = this->hash();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this->hash includes the hashes that i removed here + the hashing of the precomputed commitments


transcript.add_to_hash_buffer(domain_separator + "vk_hash", hash);
return hash;
}

// Don't statically check for object completeness.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ inline void output_vk_sol_ultra_honk(std::ostream& os,
print_u256_const(1 << key->log_circuit_size, "N");
print_u256_const(key->log_circuit_size, "LOG_N");
print_u256_const(key->num_public_inputs, "NUMBER_OF_PUBLIC_INPUTS");
print_u256_const(key->hash(), "VK_HASH");
os << ""
"library " << class_name << " {\n"
" function loadVerificationKey() internal pure returns (Honk.VerificationKey memory) {\n"
Expand Down
10 changes: 10 additions & 0 deletions barretenberg/cpp/src/barretenberg/transcript/transcript.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,16 @@ template <typename TranscriptParams> class BaseTranscript {
*/
void enable_manifest() { use_manifest = true; }

/**
* @brief Static hash method that forwards to TranscriptParams hash.
* @details This method allows hash to be called on the Transcript class directly,
* which is needed for verification key hashing.
*
* @param data Vector of field elements to hash
* @return Fr Hash result
*/
static Fr hash(const std::vector<Fr>& data) { return TranscriptParams::hash(data); }

/**
* @brief After all the prover messages have been sent, finalize the round by hashing all the data and then
* create the number of requested challenges.
Expand Down
5 changes: 1 addition & 4 deletions barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,7 @@ template <IsUltraOrMegaHonk Flavor> void OinkProver<Flavor>::execute_preamble_ro
{
PROFILE_THIS_NAME("OinkProver::execute_preamble_round");
fr vkey_hash = honk_vk->add_hash_to_transcript(domain_separator, *transcript);
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1427): Add VK FS to solidity verifier.
if constexpr (!IsAnyOf<Flavor, UltraKeccakFlavor, UltraKeccakZKFlavor>) {
vinfo("vk hash in Oink prover: ", vkey_hash);
}
vinfo("vk hash in Oink prover: ", vkey_hash);

for (size_t i = 0; i < proving_key->num_public_inputs(); ++i) {
auto public_input_i = proving_key->public_inputs[i];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,7 @@ template <IsUltraOrMegaHonk Flavor> void OinkVerifier<Flavor>::verify()
template <IsUltraOrMegaHonk Flavor> void OinkVerifier<Flavor>::execute_preamble_round()
{
FF vkey_hash = verification_key->vk->add_hash_to_transcript(domain_separator, *transcript);
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1427): Update solidity contract to generate vkey hash
// from transcript.
if constexpr (!IsAnyOf<Flavor, UltraKeccakFlavor, UltraKeccakZKFlavor>) {
vinfo("vk hash in Oink verifier: ", vkey_hash);
}
vinfo("vk hash in Oink verifier: ", vkey_hash);

for (size_t i = 0; i < verification_key->vk->num_public_inputs; ++i) {
auto public_input_i =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,7 @@ template <typename Flavor> class UltraTranscriptTests : public ::testing::Test {
size_t frs_per_evals = (Flavor::NUM_ALL_ENTITIES)*frs_per_Fr;

size_t round = 0;
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1427): Add VK FS to solidity verifier.
if constexpr (!IsAnyOf<Flavor, UltraKeccakFlavor, UltraKeccakZKFlavor>) {
manifest_expected.add_entry(round, "vk_hash", frs_per_Fr);
} else {
size_t frs_per_uint32 = bb::field_conversion::calc_num_bn254_frs<uint32_t>();
manifest_expected.add_entry(round, "vk_log_circuit_size", frs_per_uint32);
manifest_expected.add_entry(round, "vk_num_public_inputs", frs_per_uint32);
manifest_expected.add_entry(round, "vk_pub_inputs_offset", frs_per_uint32);
}
manifest_expected.add_entry(round, "vk_hash", frs_per_Fr);

manifest_expected.add_entry(round, "public_input_0", frs_per_Fr);
for (size_t i = 0; i < PAIRING_POINTS_SIZE; i++) {
Expand Down
4 changes: 2 additions & 2 deletions barretenberg/sol/scripts/copy_to_cpp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -179,15 +179,15 @@ build_verifier() {
# Add the final contract template
if [ "$is_zk" = false ]; then
cat >> "$output_file" << 'EOF'
contract HonkVerifier is BaseHonkVerifier(N, LOG_N, NUMBER_OF_PUBLIC_INPUTS) {
contract HonkVerifier is BaseHonkVerifier(N, LOG_N, VK_HASH, NUMBER_OF_PUBLIC_INPUTS) {
function loadVerificationKey() internal pure override returns (Honk.VerificationKey memory) {
return HonkVerificationKey.loadVerificationKey();
}
}
EOF
else
cat >> "$output_file" << 'EOF'
contract HonkVerifier is BaseZKHonkVerifier(N, LOG_N, NUMBER_OF_PUBLIC_INPUTS) {
contract HonkVerifier is BaseZKHonkVerifier(N, LOG_N, VK_HASH, NUMBER_OF_PUBLIC_INPUTS) {
function loadVerificationKey() internal pure override returns (Honk.VerificationKey memory) {
return HonkVerificationKey.loadVerificationKey();
}
Expand Down
Loading
Loading