Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verification Fails for createVerifyLeafInstruction and createTransferInstruction #118

Open
MoizYousuf opened this issue Nov 29, 2024 · 0 comments

Comments

@MoizYousuf
Copy link

MoizYousuf commented Nov 29, 2024

I am encountering verification issues with both the createVerifyLeafInstruction function from the @solana/spl-account-compression library and the createTransferInstruction function from the @metaplex-foundation/mpl-bubblegum library.

I have implemented a local function to calculate the Merkle tree root and leaf, and the calculations match the proof provided in the tree. However, when using either createVerifyLeafInstruction or createTransferInstruction, the verification fails, indicating that the leaf or root does not match.

Steps to Reproduce

  • Implement a local function to calculate the Merkle tree root and leaf.

  • Verify that the local calculations match the provided proof tree.

  • Use createVerifyLeafInstruction to verify the proof, or createTransferInstruction to perform a transfer operation.

  • Observe that both functions return errors indicating verification failures.

What I've Done

  • Created a local function to calculate the Merkle tree root and leaf, ensuring they match the proof data.

  • Verified the proof using the local implementation, confirming its correctness.

  • Attempted to use createVerifyLeafInstruction and createTransferInstruction, both of which returned errors.

Expected Behavior

Both createVerifyLeafInstruction and createTransferInstruction should work correctly when the root and proof match the expected values.

Actual Behavior

Both functions fail, returning errors indicating mismatches in the leaf or root, even though local calculations confirm the proof is valid.

Snippets

Here’s the local function I used to calculate the Merkle tree root and verify the proof:


import { PublicKey } from "@solana/web3.js";
import bs58 from "bs58";
import { MerkleTree, MerkleTreeProof } from "../edge/utils";
import * as web3 from "@solana/web3.js";
import { fetchAssetProof, fetchDasAssets } from "../edge/utils";
import { connection } from "../tests/_base";
import { createVerifyLeafInstruction } from "@solana/spl-account-compression";
import { createTransferInstruction } from "@metaplex-foundation/mpl-bubblegum";
import { Asset } from "../edge/types";
import { GraphQLError } from "graphql";
import { Proof } from "../edge/generated";
import {
  SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
  SPL_NOOP_PROGRAM_ID,
} from "@solana/spl-account-compression";
import { BN } from "@coral-xyz/anchor";

/**
 * @param base58Str - The base58-encoded string.
 * @returns The decoded Buffer.
 */
function decodeBase58(base58Str: string): Buffer {
  return Buffer.from(bs58.decode(base58Str));
}

const secretKeyToKeyPair = (secretKey: string) => {
  return web3.Keypair.fromSecretKey(bs58.decode(secretKey));
};

const secretKeyToPublicKey = (secretKey: string) => {
  return secretKeyToKeyPair(secretKey).publicKey;
};

/**
 * Verifies the Merkle proof.
 * @param data - The Merkle tree data from Helius.
 * @returns Whether the proof is valid or not.
 */
function verifyHeliusMerkleProof(data: Proof): boolean {
  const { root, proof, node_index, leaf } = data;

  // Decode the inputs
  const decodedRoot = decodeBase58(root);
  const decodedLeaf = decodeBase58(leaf);
  const decodedProof = proof.map(decodeBase58);

  // Create the proof object
  const merkleProof: MerkleTreeProof = {
    root: decodedRoot,
    leaf: decodedLeaf,
    leafIndex: Number(node_index),
    proof: decodedProof,
  };

  // Verify the proof
  const isValid = MerkleTree.verify(merkleProof.root, merkleProof, true);

  console.log(`Is proof valid? ${isValid}`);
  return isValid;
}

/**
 * Calculate the root from the proof (if verification is not needed).
 * @param data - The Merkle tree data from Helius.
 * @returns The calculated root as a base58 string.
 */
function calculateRoot(data: Proof): string {
  const { proof, node_index, leaf } = data;

  // Decode the inputs
  const decodedLeaf = decodeBase58(leaf);
  const decodedProof = proof.map(decodeBase58);

  // Calculate the root
  const calculatedRoot = MerkleTree.hashProof({
    leaf: decodedLeaf,
    leafIndex: Number(node_index),
    proof: decodedProof,
    root: Buffer.alloc(0), // Placeholder
  });

  return bs58.encode(calculatedRoot);
}

// Example usage with Helius data
// const heliusData = {
//   root: "CDyDYWz85pz3L2VrfHACaFy5ZQeeNHWKKEs4PnsC2ZZ7",
//   proof: [
//     "DY4JYxvipo95t4vJVFw3aWTpFh23APAvV4ZpB2rTVg7M",
//     "PiHTnb2o3DyUUyzxxW3Sq5Z6KQBumyqanxXU75b5DeJ",
//     "8MEcfbEBU5VTJycRELCBRb6Vx83xLZsW3UBnJRWBzWnf",
//     "1HKxXeZGRptnBjxNzrhV65Ez9EDHRQZgGMNj635eoKm",
//     "XHQLXz9F1w4KKwnbUurjK4SDipABUy4saZBNNVfRUqr",
//     "Gj51WoA4GmabvHCgX4g2eZs1cT7TjBJLytLPp99HMWcE",
//     "5egQR2WEpNMsxVjMAW9XSdzPdZ3RBcZizyBVjQzjdZvx",
//     "H8efNp7nfRQe6CUxLm96pvePbQhUsrJjwYdPG1f35SZ1",
//     "DJtTArdE9DAbNPr5bf6Hcqe2LjKFhSUTzpvfruRPy8Xi",
//     "BxPxMzHLHNk7EHmH2XS38krtHx6UCdwVc9UHNq2EQasD",
//     "D9UBcydgqYyTMMuh34AtFJXjy5SQof9W2FF8G9JB4zhm",
//     "AAvZnTFwCsjHj12K219EW6sJp351B3yzzmUD3muj6R1S",
//     "5hPFGEZubYQriWjiB1nAu2tdLa8HbPax292qPwhbFjpH",
//     "9XsA1PzMVeycm9DqoG586vA6PKgLcPaGrtLJuYgtBefs",
//     "ESsTwGbxmfN4BMyCQ87SS3fdyfjn4vHQ62nYm9JN1iyc",
//     "FjV56x6MT13Y4Zr5pLaH4RWSZKY8hnaBqBnzP8H1C9Wf",
//     "GP2ccSDMGyEscNcR6e1SGzdPPYgtYpgxcHZ3seKXpic4",
//     "GtJ8SF7pWdb9NUdxm3QxnBnem9KzFdgVNFYwjZpVNF5s",
//     "39kHwi5HFPrKCpGg1menD5qY5nFkZeP82o3r3QV5u9SR",
//     "D5Rty4haKVQFbuqgLz8bYHBYicQdnheNzUcgickt1mry",
//   ],
//   node_index: 1310948,
//   leaf: "BBjtQRXQNF2mViUu3wQNewnQH6f7gKakiYwuXP23MmyM",
//   tree_id: "BXMBo3FKjihFk5F7Mj8UdByNrufmzhfC4Ng6qcUA5VDs",
// };

const shyftData: Proof = {
  root: "2v5UkPQwxzCkQozJxKuDpwrvMASCrCxZwVejVAfbeTdj",
  proof: [
    "DY4JYxvipo95t4vJVFw3aWTpFh23APAvV4ZpB2rTVg7M",
    "PiHTnb2o3DyUUyzxxW3Sq5Z6KQBumyqanxXU75b5DeJ",
    "8MEcfbEBU5VTJycRELCBRb6Vx83xLZsW3UBnJRWBzWnf",
    "1HKxXeZGRptnBjxNzrhV65Ez9EDHRQZgGMNj635eoKm",
    "XHQLXz9F1w4KKwnbUurjK4SDipABUy4saZBNNVfRUqr",
    "Gj51WoA4GmabvHCgX4g2eZs1cT7TjBJLytLPp99HMWcE",
    "5egQR2WEpNMsxVjMAW9XSdzPdZ3RBcZizyBVjQzjdZvx",
    "H8efNp7nfRQe6CUxLm96pvePbQhUsrJjwYdPG1f35SZ1",
    "oyLn6pYXu1aZyonjbuz6Nhf5o2wq5ihhiENguw7LzzL",
    "5okG88hBY4fRhP3nG9aENkdQP7AvPPgWyT3AAUm9dDZu",
    "CYes99Ag6kRdKVxi8qfs1Qnd4Vz73HZwMUQJoKAVtL58",
    "CU2kc4HjgbXzwUoY2aJwv3WkZ6SwWpyMK3PHsTmMuTfa",
    "ErbbNWA7AhsBiwz7gejRyRDfchUfLabKidmfP6qv7Hiw",
    "BE7NpZpnjq3i7MayEiK4CQ9vGaNhaaDetyZgbuWDbi1q",
    "88GAnAHfR4pAeWkbhozwmtbk5tGHgVdhN6AR8MXbqyvD",
    "HFphNMSTH2JgaXY87D7WBv6yNrhrcdU1hk5xkqXK1DEU",
    "8mgGPAHc3kLmMBXv3ohHNbBNPqVHt9uBaU82MvdsonLr",
    "Fw2mwmpc8h8vKcYrq9hJedt9otrzpNkMXde4jsoZLNnG",
    "4bqevQpTCzhJjDR5ZsZALQkFvHST4Xcfi3CQpCgwAKGy",
    "G6xXqFURgb15jEe4N18irqn3hakZmc2SB1MA6hTNhe4t",
  ],
  maxDepth: 20,
  node_index: 1310948n,
  leaf_index: undefined,
  leaf: "9AL3ULsY446kVzgfQ9Nu3Nf91jAkXUbg9Ar7LgEtMA2E",
  tree_id: "BXMBo3FKjihFk5F7Mj8UdByNrufmzhfC4Ng6qcUA5VDs",
  canopy_depth: 0,
};

const driverKeypair = web3.Keypair.fromSecretKey(
  bs58.decode(process.env.DRIVER_KEYPAIR)
);

async function compileAndSendTransaction(
  action: string,
  instructions: web3.TransactionInstruction[],
  payerKey: web3.PublicKey,
  signers: web3.Signer[]
) {
  const { blockhash, lastValidBlockHeight } =
    await connection.getLatestBlockhash();
  const versionedTx = new web3.VersionedTransaction(
    new web3.TransactionMessage({
      instructions,
      payerKey,
      recentBlockhash: blockhash,
    }).compileToV0Message()
  );
  versionedTx.sign(signers);

  return connection.simulateTransaction(versionedTx);
}

async function main(mintAddress: string, secretKey?: string) {
  const assetsArr = await fetchDasAssets(
    { mintList: [new web3.PublicKey(mintAddress)] },
    process.env.RPC_URL
  );

  const asset = assetsArr[0] as Asset;
  const proof = mintAddress ? await fetchAssetProof(mintAddress) : shyftData;

  if (!proof) throw new GraphQLError("Could not find asset proof");
  console.log("proof", proof);

  // Verify the proof
  verifyHeliusMerkleProof(proof as any);

  // Calculate the root from the proof
  const calculatedRoot = calculateRoot(proof);
  console.log("Calculated Root:", calculatedRoot);

  const t = await compileAndSendTransaction(
    `Verify leaf`,
    [
      createTransferInstruction(
        {
          compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
          leafDelegate: secretKey ? secretKeyToPublicKey(secretKey) : driverKeypair.publicKey,
          merkleTree: new web3.PublicKey(proof.tree_id),
          leafOwner: secretKey ? secretKeyToPublicKey(secretKey) : driverKeypair.publicKey,
          logWrapper: SPL_NOOP_PROGRAM_ID,
          newLeafOwner: secretKey ? secretKeyToPublicKey(secretKey) : driverKeypair.publicKey,
          treeAuthority: new web3.PublicKey(
            "BN1pKRWZC9xuDKoaLBqdKfvsQeGQ3fu12QuwunZMKFvY"
          ),
          anchorRemainingAccounts: proof.proof.map((x) => ({
            pubkey: new web3.PublicKey(x),
            isWritable: false,
            isSigner: false,
          })),
        },
        {
          root: Array.from(bs58.decode(proof.root)),
          index: Number(proof.leaf_index),
          creatorHash: Array.from(bs58.decode(asset.compression.creatorHash)),
          dataHash: Array.from(bs58.decode(asset.compression.dataHash)),
          nonce: new BN((proof.leaf_index || 0).toString()),
        }
      ),

      createVerifyLeafInstruction(
        {
          merkleTree: new web3.PublicKey(proof.tree_id),
          anchorRemainingAccounts: proof.proof.map((x) => ({
            pubkey: new web3.PublicKey(x),
            isWritable: false,
            isSigner: false,
          })),
        },
        {
          index: Number(proof.leaf_index),
          leaf: Array.from(bs58.decode(proof.leaf)),
          root: Array.from(bs58.decode(proof.root)),
        }
      ),
    ],
    secretKey ? secretKeyToPublicKey(secretKey) : driverKeypair.publicKey,
    secretKey ? [secretKeyToKeyPair(secretKey)] : [driverKeypair]
  );
  console.log("TTTT", t);
  console.dir(asset, { depth: null });
}

main(process.argv[2], process.argv[3]);

Questions

  1. Could there be an issue with how these functions process the proof and inputs?
  2. If I am using these functions incorrectly, could you provide guidance or examples of proper usage?

Thank you for your assistance!

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant