Skip to content

Instantly share code, notes, and snippets.

@jgensler8
Last active September 24, 2022 22:06
Show Gist options
  • Save jgensler8/ded6fef052a028c4db4c112e80a4833c to your computer and use it in GitHub Desktop.
Save jgensler8/ded6fef052a028c4db4c112e80a4833c to your computer and use it in GitHub Desktop.
solana compute budget testing

testing signature verification in a Solana BPF program

Use Case

Verify a ed25519-dalek signature via this library: https://github.com/dalek-cryptography/ed25519-dalek

Code Copied

I copied one of the test cases: https://github.com/dalek-cryptography/ed25519-dalek/blob/master/tests/ed25519.rs#L83-L114

use ed25519_dalek::PublicKey;
use ed25519_dalek::PUBLIC_KEY_LENGTH;
use ed25519_dalek::SignatureError;
use ed25519_dalek::SIGNATURE_LENGTH;
use ed25519_dalek::Signature;
use ed25519_dalek::ed25519::signature::Signature as _;
use hex::FromHex;
use sha2::Sha512;
use ed25519_dalek::Digest;

fn verify_dalek() -> bool {
    // copied from https://github.com/dalek-cryptography/ed25519-dalek/blob/925eb9ea56192053c9eb93b9d30d1b9419eee128/tests/ed25519.rs#L85-L115
    let public_key: &[u8] = b"ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf";
    let message: &[u8] = b"616263";
    let signature: &[u8] = b"98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae4131f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406";

    let pub_bytes: Vec<u8> = FromHex::from_hex(public_key).unwrap();
    let msg_bytes: Vec<u8> = FromHex::from_hex(message).unwrap();
    let sig_bytes: Vec<u8> = FromHex::from_hex(signature).unwrap();

    sol_log_compute_units();
    let public: PublicKey = PublicKey::from_bytes(&pub_bytes[..PUBLIC_KEY_LENGTH]).unwrap();
    sol_log_compute_units();
    let sig1: Signature = Signature::from_bytes(&sig_bytes[..]).unwrap();
    sol_log_compute_units();
    let mut prehash_for_verifying: Sha512 = Sha512::default();
    sol_log_compute_units();
    prehash_for_verifying.update(&msg_bytes[..]);
    sol_log_compute_units();
    public.verify_prehashed(prehash_for_verifying, None ,&sig1).is_ok();
    sol_log_compute_units();
    true
}

hello world Cargo.toml

[dependencies]
byteorder = "1.3"
solana-program = "=1.4.8"
#curve25519-dalek = { version = "3", default-features = false, features = ["u32_backend"] }
getrandom = { version = "0.1.14", features = ["dummy"] }
hex = "^0.4"
sha2 = { version = "0.9", default-features = false }

[dependencies.ed25519-dalek]
version = "1"
default-features = false
features = ["u32_backend"]

Solana Config:

impl BpfComputeBudget {
    pub fn new() -> Self {
        BpfComputeBudget {
            max_units: 100_000_000,
            log_units: 100,
            log_64_units: 100,
            create_program_address_units: 1500,
            invoke_units: 1000,
            max_invoke_depth: 4,
            sha256_base_cost: 85,
            sha256_byte_cost: 1,
            max_call_depth: 64,
            stack_frame_size: 4_096,
            log_pubkey_units: 100,
            max_cpi_instruction_size: 1280, // IPv6 Min MTU size
        }
    }
}

Solana Output:

  Log Messages:
    Program HiqRhc6gYu1uYHGU8gybi8qz9XcHnpPEEY3KWp2mgUzc invoke [1]
    Program log: Helloworld Rust program entrypoint
    Program consumption: 99984768 units remaining
    Program consumption: 99857627 units remaining
    Program consumption: 99857447 units remaining
    Program consumption: 99856211 units remaining
    Program consumption: 99856088 units remaining
    Program consumption: 97861110 units remaining
    Program log: it is valid!

Summary

So 100,000,000 - 97,861,110 = 2,138,890

Compute limit as of 02/24/2021 = 200,000

So this program is roughly 11x more expensive than the default limit.

Alternatives

It looks like the suggested alternative is to use the secp256k1 program: https://docs.solana.com/developing/builtins/programs#secp256k1-program

I tried replacing the ed25519_dalek library with https://docs.rs/libsecp256k1/0.3.5/secp256k1/index.html but ran into a program that I believe was too big to upload.

Error: failed to send transaction: Transaction simulation failed: Blockhash not found

final cross program invocation:

use serde::{Deserialize, Serialize};
use digest::Digest;
use secp256k1::{Message, PublicKey, Signature};

pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;

#[derive(Default, Serialize, Deserialize, Debug)]
pub struct SecpSignatureOffsets {
    pub signature_offset: u16, // offset to [signature,recovery_id] of 64+1 bytes
    pub signature_instruction_index: u8,
    pub eth_address_offset: u16, // offset to eth_address of 20 bytes
    pub eth_address_instruction_index: u8,
    pub message_data_offset: u16, // offset to start of message data
    pub message_data_size: u16,   // size of message data
    pub message_instruction_index: u8,
}

pub fn construct_eth_pubkey(pubkey: &PublicKey) -> [u8; HASHED_PUBKEY_SERIALIZED_SIZE] {
    let mut addr = [0u8; HASHED_PUBKEY_SERIALIZED_SIZE];
    addr.copy_from_slice(&sha3::Keccak256::digest(&pubkey.serialize()[1..])[12..]);
    assert_eq!(addr.len(), HASHED_PUBKEY_SERIALIZED_SIZE);
    addr
}

fn new_secp256k1_instruction_pubkey(pub_key: PublicKey, message: Message, signature: Signature) -> Instruction {
    let recovery_id = [0u8; 0];

    let eth_pubkey = construct_eth_pubkey(&pub_key);
    let signature_arr = signature.serialize();
    let message_arr = message.serialize();

    let mut instruction_data = vec![];
    let data_start = 1 + SIGNATURE_OFFSETS_SERIALIZED_SIZE;
    instruction_data.resize(
        data_start + eth_pubkey.len() + signature_arr.len() + message_arr.len() + 1,
        0,
    );
    let eth_address_offset = data_start;
    instruction_data[eth_address_offset..eth_address_offset + eth_pubkey.len()]
        .copy_from_slice(&eth_pubkey);

    let signature_offset = data_start + eth_pubkey.len();
    instruction_data[signature_offset..signature_offset + signature_arr.len()]
        .copy_from_slice(&signature_arr);

    // instruction_data[signature_offset + signature_arr.len()] = recovery_id.serialize();

    let message_data_offset = signature_offset + signature_arr.len() + 1;
    instruction_data[message_data_offset..].copy_from_slice(&message_arr);

    let num_signatures = 1;
    instruction_data[0] = num_signatures;
    let offsets = SecpSignatureOffsets {
        signature_offset: signature_offset as u16,
        signature_instruction_index: 0,
        eth_address_offset: eth_address_offset as u16,
        eth_address_instruction_index: 0,
        message_data_offset: message_data_offset as u16,
        message_data_size: message_arr.len() as u16,
        message_instruction_index: 0,
    };
    let writer = std::io::Cursor::new(&mut instruction_data[1..data_start]);
    bincode::serialize_into(writer, &offsets).unwrap();

    Instruction {
        program_id: secp256k1_program::id(),
        accounts: vec![],
        data: instruction_data,
    }
}

fn cross_program_secp256k1(accounts: &[AccountInfo]) {
    sol_log_compute_units();
    let message_arr = [6u8; 32];
    let pubkey_arr = [4, 102, 10, 239, 214, 37, 120, 219, 79, 58, 84, 203, 113, 194, 62, 227, 112, 168, 142, 224, 224, 173, 245, 220, 191, 205, 109, 182, 7, 207, 132, 135, 48, 20, 135, 134, 183, 48, 79, 239, 220, 43, 207, 121, 173, 7, 0, 61, 139, 187, 222, 74, 197, 102, 206, 223, 134, 249, 168, 14, 115, 157, 89, 232, 117];
    // let bad_pubkey_arr = [40, 102, 10, 239, 214, 37, 120, 219, 79, 58, 84, 203, 113, 194, 62, 227, 112, 168, 142, 224, 224, 173, 245, 220, 191, 205, 109, 182, 7, 207, 132, 135, 48, 20, 135, 134, 183, 48, 79, 239, 220, 43, 207, 121, 173, 7, 0, 61, 139, 187, 222, 74, 197, 102, 206, 223, 134, 249, 168, 14, 115, 157, 89, 232, 117];
    // let pubkey_arr = bad_pubkey_arr;
    let signature_arr = [205, 115, 59, 14, 209, 162, 164, 174, 65, 104, 82, 199, 255, 20, 174, 19, 123, 123, 11, 57, 187, 255, 12, 177, 153, 192, 188, 115, 242, 39, 11, 77, 72, 128, 241, 126, 66, 49, 136, 66, 54, 75, 195, 174, 56, 254, 101, 216, 10, 224, 127, 239, 240, 1, 5, 87, 43, 13, 98, 195, 174, 29, 184, 66];

    sol_log_compute_units();
    let message = Message::parse(&message_arr);
    let pubkey = PublicKey::parse(&pubkey_arr).unwrap();
    let signature = Signature::parse(&signature_arr);

    sol_log_compute_units();
    let instruction = new_secp256k1_instruction_pubkey(pubkey, message, signature);
    sol_log_compute_units();

    let result = invoke(&instruction, accounts);
    match result {
        Ok(_result) => {
            info!("it worked")
        }
        Err(_err) => {
            info!("err it did not work")
        }
    }
    info!("done")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment