1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use solana_sdk::pubkey::Pubkey;
use solana_sdk::{
    account::KeyedAccount,
    instruction::{Instruction, InstructionError},
};

pub fn process_instruction(
    _program_id: &Pubkey,
    _keyed_accounts: &[KeyedAccount],
    _data: &[u8],
) -> Result<(), InstructionError> {
    // Should be already checked by now.
    Ok(())
}

solana_sdk::declare_program!(
    solana_sdk::secp256k1_program::ID,
    solana_keccak_secp256k1_program,
    process_instruction
);

pub fn new_secp256k1_instruction(
    priv_key: &secp256k1::SecretKey,
    message_arr: &[u8],
) -> Instruction {
    use digest::Digest;
    use solana_sdk::secp256k1::{
        construct_eth_pubkey, SecpSignatureOffsets, SIGNATURE_OFFSETS_SERIALIZED_SIZE,
        SIGNATURE_SERIALIZED_SIZE,
    };

    let secp_pubkey = secp256k1::PublicKey::from_secret_key(priv_key);
    let eth_pubkey = construct_eth_pubkey(&secp_pubkey);
    let mut hasher = sha3::Keccak256::new();
    hasher.update(&message_arr);
    let message_hash = hasher.finalize();
    let mut message_hash_arr = [0u8; 32];
    message_hash_arr.copy_from_slice(&message_hash.as_slice());
    let message = secp256k1::Message::parse(&message_hash_arr);
    let (signature, recovery_id) = secp256k1::sign(&message, priv_key);
    let signature_arr = signature.serialize();
    assert_eq!(signature_arr.len(), SIGNATURE_SERIALIZED_SIZE);

    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: solana_sdk::secp256k1_program::id(),
        accounts: vec![],
        data: instruction_data,
    }
}

#[cfg(test)]
pub mod test {
    use rand::{thread_rng, Rng};
    use solana_sdk::secp256k1::{SecpSignatureOffsets, SIGNATURE_OFFSETS_SERIALIZED_SIZE};
    use solana_sdk::{
        hash::Hash,
        signature::{Keypair, Signer},
        transaction::Transaction,
    };

    #[test]
    fn test_secp256k1() {
        solana_logger::setup();
        let offsets = SecpSignatureOffsets::default();
        assert_eq!(
            bincode::serialized_size(&offsets).unwrap() as usize,
            SIGNATURE_OFFSETS_SERIALIZED_SIZE
        );

        let secp_privkey = secp256k1::SecretKey::random(&mut thread_rng());
        let message_arr = b"hello";
        let mut secp_instruction = super::new_secp256k1_instruction(&secp_privkey, message_arr);
        let mint_keypair = Keypair::new();

        let tx = Transaction::new_signed_with_payer(
            &[secp_instruction.clone()],
            Some(&mint_keypair.pubkey()),
            &[&mint_keypair],
            Hash::default(),
        );

        assert!(tx.verify_precompiles().is_ok());

        let index = thread_rng().gen_range(0, secp_instruction.data.len());
        secp_instruction.data[index] = secp_instruction.data[index].wrapping_add(12);
        let tx = Transaction::new_signed_with_payer(
            &[secp_instruction],
            Some(&mint_keypair.pubkey()),
            &[&mint_keypair],
            Hash::default(),
        );
        assert!(tx.verify_precompiles().is_err());
    }
}