-
Notifications
You must be signed in to change notification settings - Fork 1.9k
feat: ed25519 & secp256k1 sig verification helper modules #3952
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
Changes from 12 commits
4879837
d1d65e4
ab3fcc3
e6b4e2a
05f0da4
8b39498
7fb9709
84efe87
4a5903a
35e569d
8181045
7fcb8fd
c122c3f
d5e21a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| use crate::error::ErrorCode; | ||
| use crate::prelude::*; | ||
| use crate::solana_program::instruction::Instruction; | ||
| use solana_sdk_ids::ed25519_program; | ||
|
|
||
| pub fn verify_ed25519_ix( | ||
| ix: &Instruction, | ||
| pubkey: &[u8; 32], | ||
| msg: &[u8], | ||
| sig: &[u8; 64], | ||
| ) -> Result<()> { | ||
| require_keys_eq!( | ||
| ix.program_id, | ||
| ed25519_program::id(), | ||
| ErrorCode::Ed25519InvalidProgram | ||
| ); | ||
| require_eq!(ix.accounts.len(), 0usize, ErrorCode::InstructionHasAccounts); | ||
| require!(msg.len() <= u16::MAX as usize, ErrorCode::MessageTooLong); | ||
|
|
||
| const DATA_START: usize = 16; // 2 header + 14 offset bytes | ||
| let pubkey_len = pubkey.len() as u16; | ||
| let sig_len = sig.len() as u16; | ||
| let msg_len = msg.len() as u16; | ||
|
|
||
| let sig_offset: u16 = DATA_START as u16; | ||
| let pubkey_offset: u16 = sig_offset + sig_len; | ||
| let msg_offset: u16 = pubkey_offset + pubkey_len; | ||
|
|
||
| let mut expected = Vec::with_capacity(DATA_START + sig.len() + pubkey.len() + msg.len()); | ||
|
|
||
| expected.push(1u8); // num signatures | ||
| expected.push(0u8); // padding | ||
| expected.extend_from_slice(&sig_offset.to_le_bytes()); | ||
| expected.extend_from_slice(&(u16::MAX).to_le_bytes()); | ||
| expected.extend_from_slice(&pubkey_offset.to_le_bytes()); | ||
| expected.extend_from_slice(&(u16::MAX).to_le_bytes()); | ||
| expected.extend_from_slice(&msg_offset.to_le_bytes()); | ||
| expected.extend_from_slice(&msg_len.to_le_bytes()); | ||
| expected.extend_from_slice(&(u16::MAX).to_le_bytes()); | ||
|
|
||
| expected.extend_from_slice(sig); | ||
| expected.extend_from_slice(pubkey); | ||
| expected.extend_from_slice(msg); | ||
|
|
||
| if expected != ix.data { | ||
| return Err(ErrorCode::SignatureVerificationFailed.into()); | ||
| } | ||
| Ok(()) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| use crate::prelude::*; | ||
| use crate::solana_program::instruction::Instruction; | ||
| use crate::solana_program::sysvar::instructions::load_instruction_at_checked; | ||
|
|
||
| mod ed25519; | ||
| mod secp256k1; | ||
|
|
||
| pub use ed25519::verify_ed25519_ix; | ||
| pub use secp256k1::verify_secp256k1_ix; | ||
|
|
||
| /// Load an instruction from the Instructions sysvar at the given index. | ||
| pub fn load_instruction(index: usize, ix_sysvar: &AccountInfo<'_>) -> Result<Instruction> { | ||
| let ix = load_instruction_at_checked(index, ix_sysvar) | ||
| .map_err(|_| error!(error::ErrorCode::ConstraintRaw))?; | ||
| Ok(ix) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| use crate::error::ErrorCode; | ||
| use crate::prelude::*; | ||
| use crate::solana_program::instruction::Instruction; | ||
| use solana_sdk_ids::secp256k1_program; | ||
|
|
||
| pub fn verify_secp256k1_ix( | ||
| ix: &Instruction, | ||
| eth_address: &[u8; 20], | ||
| msg: &[u8], | ||
| sig: &[u8; 64], | ||
| recovery_id: u8, | ||
| ) -> Result<()> { | ||
| require_keys_eq!( | ||
| ix.program_id, | ||
| secp256k1_program::id(), | ||
| ErrorCode::Secp256k1InvalidProgram | ||
| ); | ||
| require_eq!(ix.accounts.len(), 0usize, ErrorCode::InstructionHasAccounts); | ||
| require!(recovery_id <= 1, ErrorCode::InvalidRecoveryId); | ||
| require!(msg.len() <= u16::MAX as usize, ErrorCode::MessageTooLong); | ||
|
|
||
| const DATA_START: usize = 12; // 1 header + 11 offset bytes | ||
| let eth_len = eth_address.len() as u16; | ||
| let sig_len = sig.len() as u16; | ||
| let msg_len = msg.len() as u16; | ||
|
|
||
| let eth_offset: u16 = DATA_START as u16; | ||
| let sig_offset: u16 = eth_offset + eth_len; | ||
| let msg_offset: u16 = sig_offset + sig_len + 1; // +1 for recovery id | ||
|
|
||
| let mut expected = | ||
| Vec::with_capacity(DATA_START + eth_address.len() + sig.len() + 1 + msg.len()); | ||
|
|
||
| expected.push(1u8); // num signatures | ||
| expected.extend_from_slice(&sig_offset.to_le_bytes()); | ||
| expected.push(0u8); // sig ix idx | ||
|
||
| expected.extend_from_slice(ð_offset.to_le_bytes()); | ||
| expected.push(0u8); // eth ix idx | ||
| expected.extend_from_slice(&msg_offset.to_le_bytes()); | ||
| expected.extend_from_slice(&msg_len.to_le_bytes()); | ||
| expected.push(0u8); // msg ix idx | ||
|
|
||
| expected.extend_from_slice(eth_address); | ||
| expected.extend_from_slice(sig); | ||
| expected.push(recovery_id); | ||
| expected.extend_from_slice(msg); | ||
|
|
||
| if expected != ix.data { | ||
| return Err(ErrorCode::SignatureVerificationFailed.into()); | ||
| } | ||
| Ok(()) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| [provider] | ||
| cluster = "localnet" | ||
| wallet = "~/.config/solana/id.json" | ||
|
|
||
| [features] | ||
| seeds = false | ||
| resolution = true | ||
| skip-lint = false | ||
|
|
||
| [programs.localnet] | ||
| signature_verification_test = "9P8zSbNRQkwDrjCmqsHHcU1GTk5npaKYgKHroAkupbLG" | ||
|
|
||
| [scripts] | ||
| test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/*.ts" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
|
|
||
| [workspace] | ||
| members = ["programs/signature-verification-test"] | ||
| resolver = "2" | ||
|
|
||
| [profile.release] | ||
| overflow-checks = true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| { | ||
| "name": "signature-verification", | ||
| "version": "0.31.1", | ||
| "license": "(MIT OR Apache-2.0)", | ||
| "homepage": "https://github.com/coral-xyz/anchor#readme", | ||
| "bugs": { | ||
| "url": "https://github.com/coral-xyz/anchor/issues" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/coral-xyz/anchor.git" | ||
| }, | ||
| "engines": { | ||
| "node": ">=17" | ||
| }, | ||
| "scripts": { | ||
| "test": "anchor test" | ||
| }, | ||
| "dependencies": { | ||
| "ethers": "^5.7.2" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| [package] | ||
| name = "signature-verification-test" | ||
| version = "0.1.0" | ||
| description = "A test program for signature verification" | ||
| edition = "2021" | ||
|
|
||
| [lib] | ||
| crate-type = ["cdylib", "lib"] | ||
| name = "signature_verification_test" | ||
|
|
||
| [features] | ||
| no-entrypoint = [] | ||
| cpi = ["no-entrypoint"] | ||
| idl-build = ["anchor-lang/idl-build"] | ||
|
|
||
| [dependencies] | ||
| anchor-lang = { path = "../../../../lang" } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| [target.bpfel-unknown-unknown.dependencies.std] | ||
| features = [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| use anchor_lang::prelude::*; | ||
| use anchor_lang::signature_verification::{ | ||
| load_instruction, verify_ed25519_ix, verify_secp256k1_ix, | ||
| }; | ||
|
|
||
| declare_id!("9P8zSbNRQkwDrjCmqsHHcU1GTk5npaKYgKHroAkupbLG"); | ||
|
|
||
| #[program] | ||
| pub mod signature_verification_test { | ||
| use super::*; | ||
|
|
||
| pub fn verify_ed25519_signature( | ||
| ctx: Context<VerifyEd25519Signature>, | ||
| message: Vec<u8>, | ||
| signature: [u8; 64], | ||
| ) -> Result<()> { | ||
| let ix = load_instruction(0, &ctx.accounts.ix_sysvar)?; | ||
| verify_ed25519_ix( | ||
| &ix, | ||
| &ctx.accounts.signer.key().to_bytes(), | ||
| &message, | ||
| &signature, | ||
| )?; | ||
|
|
||
| msg!("Ed25519 signature verified successfully using custom helper!"); | ||
| Ok(()) | ||
| } | ||
|
|
||
| pub fn verify_secp( | ||
| ctx: Context<VerifySecp256k1Signature>, | ||
| message: Vec<u8>, | ||
| signature: [u8; 64], | ||
| recovery_id: u8, | ||
| eth_address: [u8; 20], | ||
| ) -> Result<()> { | ||
| let ix = load_instruction(0, &ctx.accounts.ix_sysvar)?; | ||
| verify_secp256k1_ix(&ix, ð_address, &message, &signature, recovery_id)?; | ||
|
|
||
| msg!("Secp256k1 signature verified successfully using custom helper!"); | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| #[derive(Accounts)] | ||
| pub struct VerifyEd25519Signature<'info> { | ||
| /// CHECK: Signer account | ||
| pub signer: AccountInfo<'info>, | ||
| /// CHECK: Instructions sysvar account | ||
| #[account(address = anchor_lang::solana_program::sysvar::instructions::ID)] | ||
| pub ix_sysvar: AccountInfo<'info>, | ||
| } | ||
|
|
||
| #[derive(Accounts)] | ||
| pub struct VerifySecp256k1Signature<'info> { | ||
| /// CHECK: Instructions sysvar account | ||
| #[account(address = anchor_lang::solana_program::sysvar::instructions::ID)] | ||
| pub ix_sysvar: AccountInfo<'info>, | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.