-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement keccak-secp256k1 instruction
- Loading branch information
Showing
5 changed files
with
268 additions
and
9 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
[package] | ||
name = "solana-secp256k1" | ||
description = "Blockchain, Rebuilt for Scale" | ||
version = "1.4.0" | ||
documentation = "https://docs.rs/solana" | ||
homepage = "https://solana.com/" | ||
readme = "README.md" | ||
repository = "https://github.com/solana-labs/solana" | ||
authors = ["Solana Maintainers <maintainers@solana.foundation>"] | ||
license = "Apache-2.0" | ||
edition = "2018" | ||
publish = false | ||
|
||
[dependencies] | ||
bincode = "1.1.4" | ||
solana-logger = { path = "../../logger", version = "1.4.0" } | ||
solana-sdk = { path = "../../sdk", version = "1.4.0" } | ||
libsecp256k1 = "0.3.5" | ||
sha3 = "0.9.1" | ||
digest = "0.9.0" | ||
log = "" | ||
rand = "0.7.0" | ||
|
||
[dev-dependencies] | ||
solana-runtime = { path = "../../runtime", version = "1.4.0" } | ||
|
||
[lib] | ||
crate-type = ["lib"] | ||
name = "solana_secp256k1_program" | ||
|
||
[package.metadata.docs.rs] | ||
targets = ["x86_64-unknown-linux-gnu"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
extern crate secp256k1; | ||
mod secp256k1_instruction; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
use solana_sdk::pubkey::Pubkey; | ||
use solana_sdk::{account::KeyedAccount, instruction::InstructionError}; | ||
|
||
enum Secp256k1Error { | ||
InvalidSignature, | ||
InvalidPubkey, | ||
} | ||
|
||
const PUBKEY_SERIALIZED_SIZE: usize = 33; | ||
const SIGNATURE_SERIALIZED_SIZE: usize = 64; | ||
|
||
pub fn process_instruction( | ||
_program_id: &Pubkey, | ||
_keyed_accounts: &[KeyedAccount], | ||
data: &[u8], | ||
) -> Result<(), InstructionError> { | ||
use digest::Digest; | ||
use std::convert::TryInto; | ||
|
||
if data.len() < (PUBKEY_SERIALIZED_SIZE + SIGNATURE_SERIALIZED_SIZE) { | ||
return Err(InstructionError::InvalidInstructionData); | ||
} | ||
let pubkey_end = PUBKEY_SERIALIZED_SIZE; | ||
let public_key = secp256k1::PublicKey::parse_slice( | ||
&data[..pubkey_end], | ||
Some(secp256k1::PublicKeyFormat::Compressed), | ||
) | ||
.map_err(|_| InstructionError::Custom(Secp256k1Error::InvalidPubkey as u32))?; | ||
let sig = secp256k1::Signature::parse_slice( | ||
&data[pubkey_end..pubkey_end + SIGNATURE_SERIALIZED_SIZE], | ||
) | ||
.map_err(|_| InstructionError::Custom(Secp256k1Error::InvalidSignature as u32))?; | ||
let mut hasher = sha3::Keccak256::new(); | ||
hasher.update(&data[(pubkey_end + SIGNATURE_SERIALIZED_SIZE)..]); | ||
let hash = hasher.finalize(); | ||
let hash_arr: [u8; 32] = hash.as_slice().try_into().expect("Wrong length"); | ||
let message = secp256k1::Message::parse(&hash_arr); | ||
if secp256k1::verify(&message, &sig, &public_key) { | ||
Ok(()) | ||
} else { | ||
Err(InstructionError::Custom( | ||
Secp256k1Error::InvalidSignature as u32, | ||
)) | ||
} | ||
} | ||
|
||
solana_sdk::declare_program!( | ||
"KeccakSecp256k11111111111111111111111111111", | ||
solana_keccak_secp256k1_program, | ||
process_instruction | ||
); | ||
|
||
#[cfg(test)] | ||
pub mod test { | ||
use crate::secp256k1_instruction::{PUBKEY_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE}; | ||
use digest::Digest; | ||
use log::*; | ||
use rand::thread_rng; | ||
use solana_runtime::bank::Bank; | ||
use solana_sdk::instruction::Instruction; | ||
use solana_sdk::transaction::Transaction; | ||
use solana_sdk::{genesis_config::create_genesis_config, signature::Signer}; | ||
|
||
#[test] | ||
fn test_secp256k1() { | ||
solana_logger::setup(); | ||
|
||
let (genesis_config, mint_keypair) = create_genesis_config(1_000_000_000); | ||
let mut bank = Bank::new(&genesis_config); | ||
|
||
let secp_id = crate::secp256k1_instruction::id(); | ||
let mut instruction_data = vec![]; | ||
let secp_privkey = secp256k1::SecretKey::random(&mut thread_rng()); | ||
let secp_pubkey = secp256k1::PublicKey::from_secret_key(&secp_privkey); | ||
let pubkey_arr = secp_pubkey.serialize_compressed(); | ||
assert_eq!(pubkey_arr.len(), PUBKEY_SERIALIZED_SIZE); | ||
let message_arr = b"hello"; | ||
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, &secp_privkey); | ||
let signature_arr = signature.serialize(); | ||
assert_eq!(signature_arr.len(), SIGNATURE_SERIALIZED_SIZE); | ||
|
||
instruction_data.resize( | ||
pubkey_arr.len() + signature_arr.len() + message_arr.len(), | ||
0, | ||
); | ||
instruction_data[..pubkey_arr.len()].copy_from_slice(&pubkey_arr); | ||
instruction_data[pubkey_arr.len()..pubkey_arr.len() + signature_arr.len()] | ||
.copy_from_slice(&signature_arr); | ||
instruction_data[pubkey_arr.len() + signature_arr.len()..].copy_from_slice(message_arr); | ||
|
||
let rec_pubkey = secp256k1::PublicKey::parse_slice( | ||
&instruction_data[..33], | ||
Some(secp256k1::PublicKeyFormat::Compressed), | ||
) | ||
.unwrap(); | ||
info!( | ||
"recovered: {:?} from: {:?}", | ||
rec_pubkey, | ||
&instruction_data[..33] | ||
); | ||
let secp_instruction = Instruction { | ||
program_id: secp_id, | ||
accounts: vec![], | ||
data: instruction_data, | ||
}; | ||
let tx = Transaction::new_signed_with_payer( | ||
&[secp_instruction], | ||
Some(&mint_keypair.pubkey()), | ||
&[&mint_keypair], | ||
bank.last_blockhash(), | ||
); | ||
bank.add_builtin_program( | ||
"secp256k1", | ||
crate::secp256k1_instruction::id(), | ||
crate::secp256k1_instruction::process_instruction, | ||
); | ||
|
||
info!("tx: {:?}", tx); | ||
assert!(bank.process_transaction(&tx).is_ok()); | ||
} | ||
} |