Add API docs for secp256k1_instruction and secp256k1_recover (#26065)
* Add API docs for secp256k1_instruction and secp256k1_recover * typo * Remove unused variable from secp256k1 program test * Bump solana_bpf_rust_secp256k1_recover ix count Co-authored-by: Tyera Eulberg <tyera@solana.com>
This commit is contained in:
parent
c7a997872b
commit
ebe25fdb56
|
@ -6104,6 +6104,7 @@ dependencies = [
|
|||
"ed25519-dalek",
|
||||
"ed25519-dalek-bip32",
|
||||
"generic-array 0.14.5",
|
||||
"hex",
|
||||
"hmac 0.12.1",
|
||||
"itertools",
|
||||
"js-sys",
|
||||
|
|
|
@ -2189,15 +2189,31 @@ dependencies = [
|
|||
"base64 0.12.3",
|
||||
"digest 0.9.0",
|
||||
"hmac-drbg",
|
||||
"libsecp256k1-core",
|
||||
"libsecp256k1-gen-ecmult",
|
||||
"libsecp256k1-gen-genmult",
|
||||
"libsecp256k1-core 0.2.2",
|
||||
"libsecp256k1-gen-ecmult 0.2.1",
|
||||
"libsecp256k1-gen-genmult 0.2.1",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"sha2 0.9.9",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsecp256k1"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0452aac8bab02242429380e9b2f94ea20cea2b37e2c1777a1358799bbe97f37"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"base64 0.13.0",
|
||||
"digest 0.9.0",
|
||||
"libsecp256k1-core 0.3.0",
|
||||
"libsecp256k1-gen-ecmult 0.3.0",
|
||||
"libsecp256k1-gen-genmult 0.3.0",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsecp256k1-core"
|
||||
version = "0.2.2"
|
||||
|
@ -2209,13 +2225,33 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsecp256k1-core"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
"digest 0.9.0",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsecp256k1-gen-ecmult"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3"
|
||||
dependencies = [
|
||||
"libsecp256k1-core",
|
||||
"libsecp256k1-core 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsecp256k1-gen-ecmult"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809"
|
||||
dependencies = [
|
||||
"libsecp256k1-core 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2224,7 +2260,16 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d"
|
||||
dependencies = [
|
||||
"libsecp256k1-core",
|
||||
"libsecp256k1-core 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsecp256k1-gen-genmult"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c"
|
||||
dependencies = [
|
||||
"libsecp256k1-core 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4119,7 +4164,7 @@ version = "1.12.0"
|
|||
dependencies = [
|
||||
"bincode",
|
||||
"byteorder 1.4.3",
|
||||
"libsecp256k1",
|
||||
"libsecp256k1 0.6.0",
|
||||
"log",
|
||||
"solana-measure",
|
||||
"solana-metrics",
|
||||
|
@ -4441,6 +4486,7 @@ dependencies = [
|
|||
name = "solana-bpf-rust-secp256k1-recover"
|
||||
version = "1.12.0"
|
||||
dependencies = [
|
||||
"libsecp256k1 0.7.0",
|
||||
"solana-program 1.12.0",
|
||||
]
|
||||
|
||||
|
@ -5116,7 +5162,7 @@ dependencies = [
|
|||
"itertools",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"libsecp256k1",
|
||||
"libsecp256k1 0.6.0",
|
||||
"log",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
|
@ -5158,7 +5204,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"libsecp256k1",
|
||||
"libsecp256k1 0.6.0",
|
||||
"log",
|
||||
"memoffset",
|
||||
"num-derive",
|
||||
|
@ -5388,7 +5434,7 @@ dependencies = [
|
|||
"itertools",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"libsecp256k1",
|
||||
"libsecp256k1 0.6.0",
|
||||
"log",
|
||||
"memmap2",
|
||||
"num-derive",
|
||||
|
@ -5437,7 +5483,7 @@ dependencies = [
|
|||
"itertools",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"libsecp256k1",
|
||||
"libsecp256k1 0.6.0",
|
||||
"log",
|
||||
"memmap2",
|
||||
"num-derive",
|
||||
|
|
|
@ -10,6 +10,7 @@ documentation = "https://docs.rs/solana-bpf-rust-secp256k1-recover"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
libsecp256k1 = { version = "0.7.0", default-features = false }
|
||||
solana-program = { path = "../../../../sdk/program", version = "=1.12.0" }
|
||||
|
||||
[lib]
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
//! Secp256k1Recover Syscall test
|
||||
|
||||
extern crate solana_program;
|
||||
use solana_program::{custom_heap_default, custom_panic_default, msg};
|
||||
use solana_program::{
|
||||
custom_heap_default, custom_panic_default, msg, secp256k1_recover::secp256k1_recover,
|
||||
};
|
||||
|
||||
fn test_secp256k1_recover() {
|
||||
use solana_program::secp256k1_recover::secp256k1_recover;
|
||||
|
||||
let expected: [u8; 64] = [
|
||||
0x42, 0xcd, 0x27, 0xe4, 0x0f, 0xdf, 0x7c, 0x97, 0x0a, 0xa2, 0xca, 0x0b, 0x88, 0x5b, 0x96,
|
||||
0x0f, 0x8b, 0x62, 0x8a, 0x41, 0xa1, 0x81, 0xe7, 0xe6, 0x8e, 0x03, 0xea, 0x0b, 0x84, 0x20,
|
||||
|
@ -32,11 +32,56 @@ fn test_secp256k1_recover() {
|
|||
assert_eq!(public_key.to_bytes(), expected);
|
||||
}
|
||||
|
||||
/// secp256k1_recover allows malleable signatures
|
||||
fn test_secp256k1_recover_malleability() {
|
||||
let message = b"hello world";
|
||||
let message_hash = {
|
||||
let mut hasher = solana_program::keccak::Hasher::default();
|
||||
hasher.hash(message);
|
||||
hasher.result()
|
||||
};
|
||||
|
||||
let pubkey_bytes: [u8; 64] = [
|
||||
0x9B, 0xEE, 0x7C, 0x18, 0x34, 0xE0, 0x18, 0x21, 0x7B, 0x40, 0x14, 0x9B, 0x84, 0x2E, 0xFA,
|
||||
0x80, 0x96, 0x00, 0x1A, 0x9B, 0x17, 0x88, 0x01, 0x80, 0xA8, 0x46, 0x99, 0x09, 0xE9, 0xC4,
|
||||
0x73, 0x6E, 0x39, 0x0B, 0x94, 0x00, 0x97, 0x68, 0xC2, 0x28, 0xB5, 0x55, 0xD3, 0x0C, 0x0C,
|
||||
0x42, 0x43, 0xC1, 0xEE, 0xA5, 0x0D, 0xC0, 0x48, 0x62, 0xD3, 0xAE, 0xB0, 0x3D, 0xA2, 0x20,
|
||||
0xAC, 0x11, 0x85, 0xEE,
|
||||
];
|
||||
let signature_bytes: [u8; 64] = [
|
||||
0x93, 0x92, 0xC4, 0x6C, 0x42, 0xF6, 0x31, 0x73, 0x81, 0xD4, 0xB2, 0x44, 0xE9, 0x2F, 0xFC,
|
||||
0xE3, 0xF4, 0x57, 0xDD, 0x50, 0xB3, 0xA5, 0x20, 0x26, 0x3B, 0xE7, 0xEF, 0x8A, 0xB0, 0x69,
|
||||
0xBB, 0xDE, 0x2F, 0x90, 0x12, 0x93, 0xD7, 0x3F, 0xA0, 0x29, 0x0C, 0x46, 0x4B, 0x97, 0xC5,
|
||||
0x00, 0xAD, 0xEA, 0x6A, 0x64, 0x4D, 0xC3, 0x8D, 0x25, 0x24, 0xEF, 0x97, 0x6D, 0xC6, 0xD7,
|
||||
0x1D, 0x9F, 0x5A, 0x26,
|
||||
];
|
||||
let recovery_id: u8 = 0;
|
||||
|
||||
let signature = libsecp256k1::Signature::parse_standard_slice(&signature_bytes).unwrap();
|
||||
|
||||
// Flip the S value in the signature to make a different but valid signature.
|
||||
let mut alt_signature = signature.clone();
|
||||
alt_signature.s = -alt_signature.s;
|
||||
let alt_recovery_id = libsecp256k1::RecoveryId::parse(recovery_id ^ 1).unwrap();
|
||||
|
||||
let alt_signature_bytes = alt_signature.serialize();
|
||||
let alt_recovery_id = alt_recovery_id.serialize();
|
||||
|
||||
let recovered_pubkey =
|
||||
secp256k1_recover(&message_hash.0, recovery_id, &signature_bytes[..]).unwrap();
|
||||
assert_eq!(recovered_pubkey.to_bytes(), pubkey_bytes);
|
||||
|
||||
let alt_recovered_pubkey =
|
||||
secp256k1_recover(&message_hash.0, alt_recovery_id, &alt_signature_bytes[..]).unwrap();
|
||||
assert_eq!(alt_recovered_pubkey.to_bytes(), pubkey_bytes);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn entrypoint(_input: *mut u8) -> u64 {
|
||||
msg!("secp256k1_recover");
|
||||
|
||||
test_secp256k1_recover();
|
||||
test_secp256k1_recover_malleability();
|
||||
|
||||
0
|
||||
}
|
||||
|
|
|
@ -1676,7 +1676,7 @@ fn assert_instruction_count() {
|
|||
("solana_bpf_rust_param_passing", 146),
|
||||
("solana_bpf_rust_rand", 469),
|
||||
("solana_bpf_rust_sanity", 52054),
|
||||
("solana_bpf_rust_secp256k1_recover", 25707),
|
||||
("solana_bpf_rust_secp256k1_recover", 91195),
|
||||
("solana_bpf_rust_sha", 24081),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@ js-sys = "0.3.58"
|
|||
[dev-dependencies]
|
||||
anyhow = "1.0.58"
|
||||
curve25519-dalek = "3.2.1"
|
||||
hex = "0.4.3"
|
||||
static_assertions = "1.1.0"
|
||||
tiny-bip39 = "0.8.2"
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ pub mod solana_client {
|
|||
/// programs.
|
||||
pub mod solana_sdk {
|
||||
pub use crate::{
|
||||
address_lookup_table_account, hash, instruction, message, nonce,
|
||||
address_lookup_table_account, hash, instruction, keccak, message, nonce,
|
||||
pubkey::{self, Pubkey},
|
||||
system_instruction, system_program,
|
||||
};
|
||||
|
@ -178,6 +178,7 @@ pub mod solana_sdk {
|
|||
|
||||
pub trait Signers {}
|
||||
|
||||
impl<T: Signer> Signers for [&T] {}
|
||||
impl<T: Signer> Signers for [&T; 1] {}
|
||||
impl<T: Signer> Signers for [&T; 2] {}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
//! The [secp256k1 native program][np].
|
||||
//!
|
||||
//! [np]: https://docs.solana.com/developing/runtime-facilities/programs#secp256k1-program
|
||||
//!
|
||||
//! Constructors for secp256k1 program instructions, and documentation on the
|
||||
//! program's usage can be found in [`solana_sdk::secp256k1_instruction`].
|
||||
//!
|
||||
//! [`solana_sdk::secp256k1_instruction`]: https://docs.rs/solana-sdk/latest/solana_sdk/secp256k1_instruction/index.html
|
||||
|
||||
crate::declare_id!("KeccakSecp256k11111111111111111111111111111");
|
||||
|
|
|
@ -1,4 +1,28 @@
|
|||
//! Key recovery from secp256k1 signatures.
|
||||
//! Public key recovery from [secp256k1] ECDSA signatures.
|
||||
//!
|
||||
//! [secp256k1]: https://en.bitcoin.it/wiki/Secp256k1
|
||||
//!
|
||||
//! _This module provides low-level cryptographic building blocks that must be
|
||||
//! used carefully to ensure proper security. Read this documentation and
|
||||
//! accompanying links thoroughly._
|
||||
//!
|
||||
//! The [`secp256k1_recover`] syscall allows a secp256k1 public key that has
|
||||
//! previously signed a message to be recovered from the combination of the
|
||||
//! message, the signature, and a recovery ID. The recovery ID is generated
|
||||
//! during signing.
|
||||
//!
|
||||
//! Use cases for `secp256k1_recover` include:
|
||||
//!
|
||||
//! - Implementing the Ethereum [`ecrecover`] builtin contract.
|
||||
//! - Performing secp256k1 public key recovery generally.
|
||||
//! - Verifying a single secp256k1 signature.
|
||||
//!
|
||||
//! While `secp256k1_recover` can be used to verify secp256k1 signatures, Solana
|
||||
//! also provides the [secp256k1 program][sp], which is more flexible, has lower CPU
|
||||
//! cost, and can validate many signatures at once.
|
||||
//!
|
||||
//! [sp]: crate::secp256k1_program
|
||||
//! [`ecrecover`]: https://docs.soliditylang.org/en/v0.8.14/units-and-global-variables.html?highlight=ecrecover#mathematical-and-cryptographic-functions
|
||||
|
||||
use {
|
||||
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
|
||||
|
@ -69,6 +93,309 @@ impl Secp256k1Pubkey {
|
|||
}
|
||||
}
|
||||
|
||||
/// Recover the public key from a [secp256k1] ECDSA signature and
|
||||
/// cryptographically-hashed message.
|
||||
///
|
||||
/// [secp256k1]: https://en.bitcoin.it/wiki/Secp256k1
|
||||
///
|
||||
/// This function is specifically intended for efficiently implementing
|
||||
/// Ethereum's [`ecrecover`] builtin contract, for use by Ethereum integrators.
|
||||
/// It may be useful for other purposes.
|
||||
///
|
||||
/// [`ecrecover`]: https://docs.soliditylang.org/en/v0.8.14/units-and-global-variables.html?highlight=ecrecover#mathematical-and-cryptographic-functions
|
||||
///
|
||||
/// `hash` is the 32-byte cryptographic hash (typically [`keccak`]) of an
|
||||
/// arbitrary message, signed by some public key.
|
||||
///
|
||||
/// The recovery ID is a value in the range [0, 3] that is generated during
|
||||
/// signing, and allows the recovery process to be more efficent. Note that the
|
||||
/// `recovery_id` here does not directly correspond to an Ethereum recovery ID
|
||||
/// as used in `ecrecover`. This function accepts recovery IDs in the range of
|
||||
/// [0, 3], while Ethereum's recovery IDs have a value of 27 or 28. To convert
|
||||
/// an Ethereum recovery ID to a value this function will accept subtract 27
|
||||
/// from it, checking for underflow. In practice this function will not succeed
|
||||
/// if given a recovery ID of 2 or 3, as these values represent an
|
||||
/// "overflowing" signature, and this function returns an error when parsing
|
||||
/// overflowing signatures.
|
||||
///
|
||||
/// [`keccak`]: crate::keccak
|
||||
/// [`wrapping_sub`]: https://doc.rust-lang.org/std/primitive.u8.html#method.wrapping_sub
|
||||
///
|
||||
/// On success this function returns a [`Secp256k1Pubkey`], a wrapper around a
|
||||
/// 64-byte secp256k1 public key. This public key corresponds to the secret key
|
||||
/// that previously signed the message `hash` to produce the provided
|
||||
/// `signature`.
|
||||
///
|
||||
/// While `secp256k1_recover` can be used to verify secp256k1 signatures by
|
||||
/// comparing the recovered key against an expected key, Solana also provides
|
||||
/// the [secp256k1 program][sp], which is more flexible, has lower CPU cost, and
|
||||
/// can validate many signatures at once.
|
||||
///
|
||||
/// [sp]: crate::secp256k1_program
|
||||
///
|
||||
/// The `secp256k1_recover` syscall is implemented with the [`libsecp256k1`]
|
||||
/// crate, which clients may also want to use.
|
||||
///
|
||||
/// [`libsecp256k1`]: https://docs.rs/libsecp256k1/latest/libsecp256k1
|
||||
///
|
||||
/// # Hashing messages
|
||||
///
|
||||
/// In ECDSA signing and key recovery the signed "message" is always a
|
||||
/// crytographic hash, not the original message itself. If not a cryptographic
|
||||
/// hash, then an adversary can craft signatures that recover to arbitrary
|
||||
/// public keys. This means the caller of this function generally must hash the
|
||||
/// original message themselves and not rely on another party to provide the
|
||||
/// hash.
|
||||
///
|
||||
/// Ethereum uses the [`keccak`] hash.
|
||||
///
|
||||
/// # Signature malleability
|
||||
///
|
||||
/// With the ECDSA signature algorithm it is possible for any party, given a
|
||||
/// valid signature of some message, to create a second signature that is
|
||||
/// equally valid. This is known as _signature malleability_. In many cases this
|
||||
/// is not a concern, but in cases where applications rely on signatures to have
|
||||
/// a unique representation this can be the source of bugs, potentially with
|
||||
/// security implications.
|
||||
///
|
||||
/// **The solana `secp256k1_recover` function does not prevent signature
|
||||
/// malleability**. This is in contrast to the Bitcoin secp256k1 library, which
|
||||
/// does prevent malleability by default. Solana accepts signatures with `S`
|
||||
/// values that are either in the _high order_ or in the _low order_, and it
|
||||
/// is trivial to produce one from the other.
|
||||
///
|
||||
/// To prevent signature malleability, it is common for secp256k1 signature
|
||||
/// validators to only accept signatures with low-order `S` values, and reject
|
||||
/// signatures with high-order `S` values. The following code will accomplish
|
||||
/// this:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use solana_program::program_error::ProgramError;
|
||||
/// # let signature_bytes = [
|
||||
/// # 0x83, 0x55, 0x81, 0xDF, 0xB1, 0x02, 0xA7, 0xD2,
|
||||
/// # 0x2D, 0x33, 0xA4, 0x07, 0xDD, 0x7E, 0xFA, 0x9A,
|
||||
/// # 0xE8, 0x5F, 0x42, 0x6B, 0x2A, 0x05, 0xBB, 0xFB,
|
||||
/// # 0xA1, 0xAE, 0x93, 0x84, 0x46, 0x48, 0xE3, 0x35,
|
||||
/// # 0x74, 0xE1, 0x6D, 0xB4, 0xD0, 0x2D, 0xB2, 0x0B,
|
||||
/// # 0x3C, 0x89, 0x8D, 0x0A, 0x44, 0xDF, 0x73, 0x9C,
|
||||
/// # 0x1E, 0xBF, 0x06, 0x8E, 0x8A, 0x9F, 0xA9, 0xC3,
|
||||
/// # 0xA5, 0xEA, 0x21, 0xAC, 0xED, 0x5B, 0x22, 0x13,
|
||||
/// # ];
|
||||
/// let signature = libsecp256k1::Signature::parse_standard_slice(&signature_bytes)
|
||||
/// .map_err(|_| ProgramError::InvalidArgument)?;
|
||||
///
|
||||
/// if signature.s.is_high() {
|
||||
/// return Err(ProgramError::InvalidArgument);
|
||||
/// }
|
||||
/// # Ok::<_, ProgramError>(())
|
||||
/// ```
|
||||
///
|
||||
/// This has the downside that the program must link to the [`libsecp256k1`]
|
||||
/// crate and parse the signature just for this check. Note that `libsecp256k1`
|
||||
/// version 0.7.0 or greater is required for running on the Solana BPF target.
|
||||
///
|
||||
/// [`libsecp256k1`]: https://docs.rs/libsecp256k1/latest/libsecp256k1
|
||||
///
|
||||
/// For the most accurate description of signature malleability, and its
|
||||
/// prevention in secp256k1, refer to comments in [`secp256k1.h`] in the Bitcoin
|
||||
/// Core secp256k1 library, the documentation of the [OpenZeppelin `recover`
|
||||
/// method for Solidity][ozr], and [this description of the problem on
|
||||
/// StackExchange][sxr].
|
||||
///
|
||||
/// [`secp256k1.h`]: https://github.com/bitcoin-core/secp256k1/blob/44c2452fd387f7ca604ab42d73746e7d3a44d8a2/include/secp256k1.h
|
||||
/// [ozr]: https://docs.openzeppelin.com/contracts/2.x/api/cryptography#ECDSA-recover-bytes32-bytes-
|
||||
/// [sxr]: https://bitcoin.stackexchange.com/questions/81115/if-someone-wanted-to-pretend-to-be-satoshi-by-posting-a-fake-signature-to-defrau/81116#81116
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If `hash` is not 32 bytes in length this function returns
|
||||
/// [`Secp256k1RecoverError::InvalidHash`], though see notes
|
||||
/// on BPF-specific behavior below.
|
||||
///
|
||||
/// If `recovery_id` is not in the range [0, 3] this function returns
|
||||
/// [`Secp256k1RecoverError::InvalidRecoveryId`].
|
||||
///
|
||||
/// If `signature` is not 64 bytes in length this function returns
|
||||
/// [`Secp256k1RecoverError::InvalidSignature`], though see notes
|
||||
/// on BPF-specific behavior below.
|
||||
///
|
||||
/// If `signature` represents an "overflowing" signature this function returns
|
||||
/// [`Secp256k1RecoverError::InvalidSignature`]. Overflowing signatures are
|
||||
/// non-standard and should not be encountered in practice.
|
||||
///
|
||||
/// If `signature` is otherwise invalid this function returns
|
||||
/// [`Secp256k1RecoverError::InvalidSignature`].
|
||||
///
|
||||
/// # BPF-specific behavior
|
||||
///
|
||||
/// When calling this function on-chain the caller must verify the correct
|
||||
/// lengths of `hash` and `signature` beforehand.
|
||||
///
|
||||
/// When run on-chain this function will not directly validate the lengths of
|
||||
/// `hash` and `signature`. It will assume they are the the correct lengths and
|
||||
/// pass their pointers to the runtime, which will interpret them as 32-byte and
|
||||
/// 64-byte buffers. If the provided slices are too short, the runtime will read
|
||||
/// invalid data and attempt to interpret it, most likely returning an error,
|
||||
/// though in some scenarios it may be possible to incorrectly return
|
||||
/// successfully, or the transaction will abort if the syscall reads data
|
||||
/// outside of the program's memory space. If the provided slices are too long
|
||||
/// then they may be used to "smuggle" uninterpreted data.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This example demonstrates recovering a public key and using it to very a
|
||||
/// signature with the `secp256k1_recover` syscall. It has three parts: a Solana
|
||||
/// program, an RPC client to call the program, and common definitions shared
|
||||
/// between the two.
|
||||
///
|
||||
/// Common definitions:
|
||||
///
|
||||
/// ```
|
||||
/// use borsh::{BorshDeserialize, BorshSerialize};
|
||||
///
|
||||
/// #[derive(BorshSerialize, BorshDeserialize, Debug)]
|
||||
/// pub struct DemoSecp256k1RecoverInstruction {
|
||||
/// pub message: Vec<u8>,
|
||||
/// pub signature: [u8; 64],
|
||||
/// pub recovery_id: u8,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The Solana program. Note that it uses `libsecp256k1` version 0.7.0 to parse
|
||||
/// the secp256k1 signature to prevent malleability.
|
||||
///
|
||||
/// ```no_run
|
||||
/// use solana_program::{
|
||||
/// entrypoint::ProgramResult,
|
||||
/// keccak, msg,
|
||||
/// program_error::ProgramError,
|
||||
/// secp256k1_recover::secp256k1_recover,
|
||||
/// };
|
||||
///
|
||||
/// /// The key we expect to sign secp256k1 messages,
|
||||
/// /// as serialized by `libsecp256k1::PublicKey::serialize`.
|
||||
/// const AUTHORIZED_PUBLIC_KEY: [u8; 64] = [
|
||||
/// 0x8C, 0xD6, 0x47, 0xF8, 0xA5, 0xBF, 0x59, 0xA0, 0x4F, 0x77, 0xFA, 0xFA, 0x6C, 0xA0, 0xE6, 0x4D,
|
||||
/// 0x94, 0x5B, 0x46, 0x55, 0xA6, 0x2B, 0xB0, 0x6F, 0x10, 0x4C, 0x9E, 0x2C, 0x6F, 0x42, 0x0A, 0xBE,
|
||||
/// 0x18, 0xDF, 0x0B, 0xF0, 0x87, 0x42, 0xBA, 0x88, 0xB4, 0xCF, 0x87, 0x5A, 0x35, 0x27, 0xBE, 0x0F,
|
||||
/// 0x45, 0xAE, 0xFC, 0x66, 0x9C, 0x2C, 0x6B, 0xF3, 0xEF, 0xCA, 0x5C, 0x32, 0x11, 0xF7, 0x2A, 0xC7,
|
||||
/// ];
|
||||
/// # pub struct DemoSecp256k1RecoverInstruction {
|
||||
/// # pub message: Vec<u8>,
|
||||
/// # pub signature: [u8; 64],
|
||||
/// # pub recovery_id: u8,
|
||||
/// # }
|
||||
///
|
||||
/// pub fn process_secp256k1_recover(
|
||||
/// instruction: DemoSecp256k1RecoverInstruction,
|
||||
/// ) -> ProgramResult {
|
||||
/// // The secp256k1 recovery operation accepts a cryptographically-hashed
|
||||
/// // message only. Passing it anything else is insecure and allows signatures
|
||||
/// // to be forged.
|
||||
/// //
|
||||
/// // This means that the code calling `secp256k1_recover` must perform the hash
|
||||
/// // itself, and not assume that data passed to it has been properly hashed.
|
||||
/// let message_hash = {
|
||||
/// let mut hasher = keccak::Hasher::default();
|
||||
/// hasher.hash(&instruction.message);
|
||||
/// hasher.result()
|
||||
/// };
|
||||
///
|
||||
/// // Reject high-s value signatures to prevent malleability.
|
||||
/// // Solana does not do this itself.
|
||||
/// // This may or may not be necessary depending on use case.
|
||||
/// {
|
||||
/// let signature = libsecp256k1::Signature::parse_standard_slice(&instruction.signature)
|
||||
/// .map_err(|_| ProgramError::InvalidArgument)?;
|
||||
///
|
||||
/// if signature.s.is_high() {
|
||||
/// msg!("signature with high-s value");
|
||||
/// return Err(ProgramError::InvalidArgument);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let recovered_pubkey = secp256k1_recover(
|
||||
/// &message_hash.0,
|
||||
/// instruction.recovery_id,
|
||||
/// &instruction.signature,
|
||||
/// )
|
||||
/// .map_err(|_| ProgramError::InvalidArgument)?;
|
||||
///
|
||||
/// // If we're using this function for signature verification then we
|
||||
/// // need to check the pubkey is an expected value.
|
||||
/// // Here we are checking the secp256k1 pubkey against a known authorized pubkey.
|
||||
/// if recovered_pubkey.0 != AUTHORIZED_PUBLIC_KEY {
|
||||
/// return Err(ProgramError::InvalidArgument);
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The RPC client program:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use solana_program::example_mocks::solana_client;
|
||||
/// # use solana_program::example_mocks::solana_sdk;
|
||||
/// use anyhow::Result;
|
||||
/// use solana_client::rpc_client::RpcClient;
|
||||
/// use solana_sdk::{
|
||||
/// instruction::Instruction,
|
||||
/// keccak,
|
||||
/// pubkey::Pubkey,
|
||||
/// signature::{Keypair, Signer},
|
||||
/// transaction::Transaction,
|
||||
/// };
|
||||
/// # use borsh::{BorshDeserialize, BorshSerialize};
|
||||
/// # #[derive(BorshSerialize, BorshDeserialize, Debug)]
|
||||
/// # pub struct DemoSecp256k1RecoverInstruction {
|
||||
/// # pub message: Vec<u8>,
|
||||
/// # pub signature: [u8; 64],
|
||||
/// # pub recovery_id: u8,
|
||||
/// # }
|
||||
///
|
||||
/// pub fn demo_secp256k1_recover(
|
||||
/// payer_keypair: &Keypair,
|
||||
/// secp256k1_secret_key: &libsecp256k1::SecretKey,
|
||||
/// client: &RpcClient,
|
||||
/// program_keypair: &Keypair,
|
||||
/// ) -> Result<()> {
|
||||
/// let message = b"hello world";
|
||||
/// let message_hash = {
|
||||
/// let mut hasher = keccak::Hasher::default();
|
||||
/// hasher.hash(message);
|
||||
/// hasher.result()
|
||||
/// };
|
||||
///
|
||||
/// let secp_message = libsecp256k1::Message::parse(&message_hash.0);
|
||||
/// let (signature, recovery_id) = libsecp256k1::sign(&secp_message, &secp256k1_secret_key);
|
||||
///
|
||||
/// let signature = signature.serialize();
|
||||
///
|
||||
/// let instr = DemoSecp256k1RecoverInstruction {
|
||||
/// message: message.to_vec(),
|
||||
/// signature,
|
||||
/// recovery_id: recovery_id.serialize(),
|
||||
/// };
|
||||
/// let instr = Instruction::new_with_borsh(
|
||||
/// program_keypair.pubkey(),
|
||||
/// &instr,
|
||||
/// vec![],
|
||||
/// );
|
||||
///
|
||||
/// let blockhash = client.get_latest_blockhash()?;
|
||||
/// let tx = Transaction::new_signed_with_payer(
|
||||
/// &[instr],
|
||||
/// Some(&payer_keypair.pubkey()),
|
||||
/// &[payer_keypair],
|
||||
/// blockhash,
|
||||
/// );
|
||||
///
|
||||
/// client.send_and_confirm_transaction(&tx)?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn secp256k1_recover(
|
||||
hash: &[u8],
|
||||
recovery_id: u8,
|
||||
|
|
|
@ -1,3 +1,790 @@
|
|||
//! Instructions for the [secp256k1 native program][np].
|
||||
//!
|
||||
//! [np]: https://docs.solana.com/developing/runtime-facilities/programs#secp256k1-program
|
||||
//!
|
||||
//! _This module provides low-level cryptographic building blocks that must be
|
||||
//! used carefully to ensure proper security. Read this documentation and
|
||||
//! accompanying links thoroughly._
|
||||
//!
|
||||
//! The secp26k1 native program peforms flexible verification of [secp256k1]
|
||||
//! ECDSA signatures, as used by Ethereum. It can verify up to 255 signatures on
|
||||
//! up to 255 messages, with those signatures, messages, and their public keys
|
||||
//! arbitrarily distributed across the instruction data of any instructions in
|
||||
//! the same transaction as the secp256k1 instruction.
|
||||
//!
|
||||
//! The secp256k1 native program ID is located in the [`secp256k1_program`] module.
|
||||
//!
|
||||
//! The instruction is designed for Ethereum interoperability, but may be useful
|
||||
//! for other purposes. It operates on Ethereum addresses, which are [`keccak`]
|
||||
//! hashes of secp256k1 public keys, and internally is implemented using the
|
||||
//! secp256k1 key recovery algorithm. Ethereum address can be created for
|
||||
//! secp256k1 public keys with the [`construct_eth_pubkey`] function.
|
||||
//!
|
||||
//! [`keccak`]: crate::keccak
|
||||
//!
|
||||
//! This instruction does not directly allow for key recovery as in Ethereum's
|
||||
//! [`ecrecover`] precompile. For that Solana provides the [`secp256k1_recover`]
|
||||
//! syscall.
|
||||
//!
|
||||
//! [secp256k1]: https://en.bitcoin.it/wiki/Secp256k1
|
||||
//! [`secp256k1_program`]: solana_program::secp256k1_program
|
||||
//! [`secp256k1_recover`]: solana_program::secp256k1_recover
|
||||
//! [`ecrecover`]: https://docs.soliditylang.org/en/v0.8.14/units-and-global-variables.html?highlight=ecrecover#mathematical-and-cryptographic-functions
|
||||
//!
|
||||
//! Use cases for the secp256k1 instruction include:
|
||||
//!
|
||||
//! - Verifying Ethereum transaction signatures.
|
||||
//! - Verifying Ethereum [EIP-712] signatures.
|
||||
//! - Verifying arbitrary secp256k1 signatures.
|
||||
//! - Signing a single message with multiple signatures.
|
||||
//!
|
||||
//! [EIP-712]: https://eips.ethereum.org/EIPS/eip-712
|
||||
//!
|
||||
//! The [`new_secp256k1_instruction`] function is suitable for building a
|
||||
//! secp256k1 program instruction for basic use cases were a single message must
|
||||
//! be signed by a known secret key. For other uses cases, including many
|
||||
//! Ethereum-integration use cases, construction of the secp256k1 instruction
|
||||
//! must be done manually.
|
||||
//!
|
||||
//! # How to use this program
|
||||
//!
|
||||
//! Transactions that uses the secp256k1 native program will typically include
|
||||
//! at least two instructions: one for the secp256k1 program to verify the
|
||||
//! signatures, and one for a custom program that will check that the secp256k1
|
||||
//! instruction data matches what the program expects (using
|
||||
//! [`load_instruction_at_checked`] or [`get_instruction_relative`]). The
|
||||
//! signatures, messages, and Ethereum addresses being verified may reside in the
|
||||
//! instruction data of either of these instructions, or in the instruction data
|
||||
//! of one or more additional instructions, as long as those instructions are in
|
||||
//! the same transaction.
|
||||
//!
|
||||
//! [`load_instruction_at_checked`]: crate::sysvar::instructions::load_instruction_at_checked
|
||||
//! [`get_instruction_relative`]: crate::sysvar::instructions::get_instruction_relative
|
||||
//!
|
||||
//! Correct use of this program involves multiple steps, in client code and
|
||||
//! program code:
|
||||
//!
|
||||
//! - In the client:
|
||||
//! - Sign the [`keccak`]-hashed messages with a secp256k1 ECDSA library,
|
||||
//! like the [`libsecp256k1`] crate.
|
||||
//! - Build any custom instruction data that contain signature, message, or
|
||||
//! Ethereum address data that will be used by the secp256k1 instruction.
|
||||
//! - Build the secp256k1 program instruction data, specifying the number of
|
||||
//! signatures to verify, the instruction indexes within the transaction,
|
||||
//! and offsets within those instruction's data, where the signatures,
|
||||
//! messages, and Ethereum addresses are located.
|
||||
//! - Build the custom instruction for the program that will check the results
|
||||
//! of the secp256k1 native program.
|
||||
//! - Package all instructions into a single transaction and submit them.
|
||||
//! - In the program:
|
||||
//! - Load the secp256k1 instruction data with
|
||||
//! [`load_instruction_at_checked`]. or [`get_instruction_relative`].
|
||||
//! - Check that the secp256k1 program ID is equal to
|
||||
//! [`secp256k1_program::ID`], so that the signature verification cannot be
|
||||
//! faked with a malicious program.
|
||||
//! - Check that the public keys and messages are the expected values per
|
||||
//! the program's requirements.
|
||||
//!
|
||||
//! [`secp256k1_program::ID`]: crate::secp256k1_program::ID
|
||||
//!
|
||||
//! The signature, message, or Ethereum addresses may reside in the secp256k1
|
||||
//! instruction data itself as additional data, their bytes following the bytes
|
||||
//! of the protocol required by the secp256k1 instruction to locate the
|
||||
//! signature, message, and Ethereum address data. This is the technique used by
|
||||
//! `new_secp256k1_instruction` for simple signature verification.
|
||||
//!
|
||||
//! The `solana_sdk` crate provides few APIs for building the instructions and
|
||||
//! transactions necessary for properly using the secp256k1 native program.
|
||||
//! Many steps must be done manually.
|
||||
//!
|
||||
//! The `solana_program` crate provides no APIs to assist in interpreting
|
||||
//! the the secp256k1 instruction data. It must be done manually.
|
||||
//!
|
||||
//! The secp256k1 program is implemented with the [`libsecp256k1`] crate,
|
||||
//! which clients may also want to use.
|
||||
//!
|
||||
//! [`libsecp256k1`]: https://docs.rs/libsecp256k1/latest/libsecp256k1
|
||||
//!
|
||||
//! # Layout and interpretation of the secp256k1 instruction data
|
||||
//!
|
||||
//! The secp256k1 instruction data contains:
|
||||
//!
|
||||
//! - 1 byte indicating the number of signatures to verify, 0 - 255,
|
||||
//! - A number of _signature offset_ structures that indicate where in the
|
||||
//! transaction to locate each signature, message, and Ethereum address.
|
||||
//! - 0 or more bytes of arbitrary data, which may contain signatures,
|
||||
//! messages or Ethereum addresses.
|
||||
//!
|
||||
//! The signature offset structure is defined by [`SecpSignatureOffsets`],
|
||||
//! and can be serialized to the correct format with [`bincode::serialize_into`].
|
||||
//! Note that the bincode format may not be stable,
|
||||
//! and callers should ensure they use the same version of `bincode` as the Solana SDK.
|
||||
//! This data structure is not provided to Solana programs,
|
||||
//! which are expected to interpret the signature offsets manually.
|
||||
//!
|
||||
//! [`bincode::serialize_into`]: https://docs.rs/bincode/1.3.3/bincode/fn.serialize_into.html
|
||||
//!
|
||||
//! The serialized signature offset structure has the following 11-byte layout,
|
||||
//! with data types in little-endian encoding.
|
||||
//!
|
||||
//! | index | bytes | type | description |
|
||||
//! |--------|-------|-------|-------------|
|
||||
//! | 0 | 2 | `u16` | `signature_offset` - offset to 64-byte signature plus 1-byte recovery ID. |
|
||||
//! | 2 | 1 | `u8` | `signature_offset_instruction_index` - within the transaction, the index of the transaction whose instruction data contains the signature. |
|
||||
//! | 3 | 2 | `u16` | `eth_address_offset` - offset to 20-byte Ethereum address. |
|
||||
//! | 5 | 1 | `u8` | `eth_address_instruction_index` - within the transaction, the index of the instruction whose instruction data contains the Ethereum address. |
|
||||
//! | 6 | 2 | `u16` | `message_data_offset` - Offset to start of message data. |
|
||||
//! | 8 | 2 | `u16` | `message_data_size` - Size of message data in bytes. |
|
||||
//! | 10 | 1 | `u8` | `message_instruction_index` - Within the transaction, the index of the instruction whose instruction data contains the message data. |
|
||||
//!
|
||||
//! # Signature malleability
|
||||
//!
|
||||
//! With the ECDSA signature algorithm it is possible for any party, given a
|
||||
//! valid signature of some message, to create a second signature that is
|
||||
//! equally valid. This is known as _signature malleability_. In many cases this
|
||||
//! is not a concern, but in cases where applications rely on signatures to have
|
||||
//! a unique representation this can be the source of bugs, potentially with
|
||||
//! security implications.
|
||||
//!
|
||||
//! **The solana `secp256k1_recover` function does not prevent signature
|
||||
//! malleability**. This is in contrast to the Bitcoin secp256k1 library, which
|
||||
//! does prevent malleability by default. Solana accepts signatures with `S`
|
||||
//! values that are either in the _high order_ or in the _low order_, and it
|
||||
//! is trivial to produce one from the other.
|
||||
//!
|
||||
//! For more complete documentation of the subject, and techniques to prevent
|
||||
//! malleability, see the documentation for the [`secp256k1_recover`] syscall.
|
||||
//!
|
||||
//! # Additional security considerations
|
||||
//!
|
||||
//! Most programs will want to be conservative about the layout of the secp256k1 instruction
|
||||
//! to prevent unforeseen bugs. The following checks may be desirable:
|
||||
//!
|
||||
//! - That there are exactly the expected number of signatures.
|
||||
//! - That the three indexes, `signature_offset_instruction_index`,
|
||||
//! `eth_address_instruction_index`, and `message_instruction_index` are as
|
||||
//! expected, placing the signature, message and Ethereum address in the
|
||||
//! expected instruction.
|
||||
//!
|
||||
//! Loading the secp256k1 instruction data within a program requires access to
|
||||
//! the [instructions sysvar][is], which must be passed to the program by its
|
||||
//! caller. Programs must verify the ID of this program to avoid calling an
|
||||
//! imposter program. This does not need to be done manually though, as long as
|
||||
//! it is only used through the [`load_instruction_at_checked`] or
|
||||
//! [`get_instruction_relative`] functions. Both of these functions check their
|
||||
//! sysvar argument to ensure it is the known instruction sysvar.
|
||||
//!
|
||||
//! [is]: crate::sysvar::instructions
|
||||
//!
|
||||
//! Programs should _always_ verify that the secp256k1 program ID loaded through
|
||||
//! the instructions sysvar has the same value as in the [`secp256k1_program`]
|
||||
//! module. Again this prevents imposter programs.
|
||||
//!
|
||||
//! [`secp256k1_program`]: crate::secp256k1_program
|
||||
//!
|
||||
//! # Errors
|
||||
//!
|
||||
//! The transaction will fail if any of the following are true:
|
||||
//!
|
||||
//! - Any signature was not created by the secret key corresponding to the
|
||||
//! specified public key.
|
||||
//! - Any signature is invalid.
|
||||
//! - Any signature is "overflowing", a non-standard condition.
|
||||
//! - The instruction data is empty.
|
||||
//! - The first byte of instruction data is equal to 0 (indicating no signatures),
|
||||
//! but the instruction data's length is greater than 1.
|
||||
//! - The instruction data is not long enough to hold the number of signature
|
||||
//! offsets specified in the first byte.
|
||||
//! - Any instruction indexes specified in the signature offsets are greater or
|
||||
//! equal to the number of instructions in the transaction.
|
||||
//! - Any bounds specified in the signature offsets exceed the bounds of the
|
||||
//! instruction data to which they are indexed.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Both of the following examples make use of the following module definition
|
||||
//! to parse the secp256k1 instruction data from within a Solana program.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! mod secp256k1_defs {
|
||||
//! use solana_program::program_error::ProgramError;
|
||||
//! use std::iter::Iterator;
|
||||
//!
|
||||
//! pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
|
||||
//! pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
|
||||
//! pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
|
||||
//!
|
||||
//! /// The structure encoded in the secp2256k1 instruction data.
|
||||
//! pub struct SecpSignatureOffsets {
|
||||
//! pub signature_offset: u16,
|
||||
//! pub signature_instruction_index: u8,
|
||||
//! pub eth_address_offset: u16,
|
||||
//! pub eth_address_instruction_index: u8,
|
||||
//! pub message_data_offset: u16,
|
||||
//! pub message_data_size: u16,
|
||||
//! pub message_instruction_index: u8,
|
||||
//! }
|
||||
//!
|
||||
//! pub fn iter_signature_offsets(
|
||||
//! secp256k1_instr_data: &[u8],
|
||||
//! ) -> Result<impl Iterator<Item = SecpSignatureOffsets> + '_, ProgramError> {
|
||||
//! // First element is the number of `SecpSignatureOffsets`.
|
||||
//! let num_structs = *secp256k1_instr_data
|
||||
//! .get(0)
|
||||
//! .ok_or(ProgramError::InvalidArgument)?;
|
||||
//!
|
||||
//! let all_structs_size = SIGNATURE_OFFSETS_SERIALIZED_SIZE * num_structs as usize;
|
||||
//! let all_structs_slice = secp256k1_instr_data
|
||||
//! .get(1..all_structs_size + 1)
|
||||
//! .ok_or(ProgramError::InvalidArgument)?;
|
||||
//!
|
||||
//! fn decode_u16(chunk: &[u8], index: usize) -> u16 {
|
||||
//! u16::from_le_bytes(<[u8; 2]>::try_from(&chunk[index..index + 2]).unwrap())
|
||||
//! }
|
||||
//!
|
||||
//! Ok(all_structs_slice
|
||||
//! .chunks(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
|
||||
//! .map(|chunk| SecpSignatureOffsets {
|
||||
//! signature_offset: decode_u16(chunk, 0),
|
||||
//! signature_instruction_index: chunk[2],
|
||||
//! eth_address_offset: decode_u16(chunk, 3),
|
||||
//! eth_address_instruction_index: chunk[5],
|
||||
//! message_data_offset: decode_u16(chunk, 6),
|
||||
//! message_data_size: decode_u16(chunk, 8),
|
||||
//! message_instruction_index: chunk[10],
|
||||
//! }))
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Example: Signing and verifying with `new_secp256k1_instruction`
|
||||
//!
|
||||
//! This example demonstrates the simplest way to use the secp256k1 program, by
|
||||
//! calling [`new_secp256k1_instruction`] to sign a single message and build the
|
||||
//! corresponding secp256k1 instruction.
|
||||
//!
|
||||
//! This example has two components: a Solana program, and an RPC client that
|
||||
//! sends a transaction to call it. The RPC client will sign a single message,
|
||||
//! and the Solana program will introspect the secp256k1 instruction to verify
|
||||
//! that the signer matches a known authorized public key.
|
||||
//!
|
||||
//! The Solana program. Note that it uses `libsecp256k1` version 0.7.0 to parse
|
||||
//! the secp256k1 signature to prevent malleability.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # mod secp256k1_defs {
|
||||
//! # use solana_program::program_error::ProgramError;
|
||||
//! # use std::iter::Iterator;
|
||||
//! #
|
||||
//! # pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
|
||||
//! # pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
|
||||
//! # pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
|
||||
//! #
|
||||
//! # /// The structure encoded in the secp2256k1 instruction data.
|
||||
//! # pub struct SecpSignatureOffsets {
|
||||
//! # pub signature_offset: u16,
|
||||
//! # pub signature_instruction_index: u8,
|
||||
//! # pub eth_address_offset: u16,
|
||||
//! # pub eth_address_instruction_index: u8,
|
||||
//! # pub message_data_offset: u16,
|
||||
//! # pub message_data_size: u16,
|
||||
//! # pub message_instruction_index: u8,
|
||||
//! # }
|
||||
//! #
|
||||
//! # pub fn iter_signature_offsets(
|
||||
//! # secp256k1_instr_data: &[u8],
|
||||
//! # ) -> Result<impl Iterator<Item = SecpSignatureOffsets> + '_, ProgramError> {
|
||||
//! # // First element is the number of `SecpSignatureOffsets`.
|
||||
//! # let num_structs = *secp256k1_instr_data
|
||||
//! # .get(0)
|
||||
//! # .ok_or(ProgramError::InvalidArgument)?;
|
||||
//! #
|
||||
//! # let all_structs_size = SIGNATURE_OFFSETS_SERIALIZED_SIZE * num_structs as usize;
|
||||
//! # let all_structs_slice = secp256k1_instr_data
|
||||
//! # .get(1..all_structs_size + 1)
|
||||
//! # .ok_or(ProgramError::InvalidArgument)?;
|
||||
//! #
|
||||
//! # fn decode_u16(chunk: &[u8], index: usize) -> u16 {
|
||||
//! # u16::from_le_bytes(<[u8; 2]>::try_from(&chunk[index..index + 2]).unwrap())
|
||||
//! # }
|
||||
//! #
|
||||
//! # Ok(all_structs_slice
|
||||
//! # .chunks(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
|
||||
//! # .map(|chunk| SecpSignatureOffsets {
|
||||
//! # signature_offset: decode_u16(chunk, 0),
|
||||
//! # signature_instruction_index: chunk[2],
|
||||
//! # eth_address_offset: decode_u16(chunk, 3),
|
||||
//! # eth_address_instruction_index: chunk[5],
|
||||
//! # message_data_offset: decode_u16(chunk, 6),
|
||||
//! # message_data_size: decode_u16(chunk, 8),
|
||||
//! # message_instruction_index: chunk[10],
|
||||
//! # }))
|
||||
//! # }
|
||||
//! # }
|
||||
//! use solana_program::{
|
||||
//! account_info::{next_account_info, AccountInfo},
|
||||
//! entrypoint::ProgramResult,
|
||||
//! msg,
|
||||
//! program_error::ProgramError,
|
||||
//! secp256k1_program,
|
||||
//! sysvar,
|
||||
//! };
|
||||
//!
|
||||
//! /// An Ethereum address corresponding to a secp256k1 secret key that is
|
||||
//! /// authorized to sign our messages.
|
||||
//! const AUTHORIZED_ETH_ADDRESS: [u8; 20] = [
|
||||
//! 0x18, 0x8a, 0x5c, 0xf2, 0x3b, 0x0e, 0xff, 0xe9, 0xa8, 0xe1, 0x42, 0x64, 0x5b, 0x82, 0x2f, 0x3a,
|
||||
//! 0x6b, 0x8b, 0x52, 0x35,
|
||||
//! ];
|
||||
//!
|
||||
//! /// Check the secp256k1 instruction to ensure it was signed by
|
||||
//! /// `AUTHORIZED_ETH_ADDRESS`s key.
|
||||
//! ///
|
||||
//! /// `accounts` is the slice of all accounts passed to the program
|
||||
//! /// entrypoint. The only account it should contain is the instructions sysvar.
|
||||
//! fn demo_secp256k1_verify_basic(
|
||||
//! accounts: &[AccountInfo],
|
||||
//! ) -> ProgramResult {
|
||||
//! let account_info_iter = &mut accounts.iter();
|
||||
//!
|
||||
//! // The instructions sysvar gives access to the instructions in the transaction.
|
||||
//! let instructions_sysvar_account = next_account_info(account_info_iter)?;
|
||||
//! assert!(sysvar::instructions::check_id(
|
||||
//! instructions_sysvar_account.key
|
||||
//! ));
|
||||
//!
|
||||
//! // Load the secp256k1 instruction.
|
||||
//! // `new_secp256k1_instruction` generates an instruction that must be at index 0.
|
||||
//! let secp256k1_instr =
|
||||
//! sysvar::instructions::load_instruction_at_checked(0, instructions_sysvar_account)?;
|
||||
//!
|
||||
//! // Verify it is a secp256k1 instruction.
|
||||
//! // This is security-critical - what if the transaction uses an imposter secp256k1 program?
|
||||
//! assert!(secp256k1_program::check_id(&secp256k1_instr.program_id));
|
||||
//!
|
||||
//! // There must be at least one byte. This is also verified by the runtime,
|
||||
//! // and doesn't strictly need to be checked.
|
||||
//! assert!(secp256k1_instr.data.len() > 1);
|
||||
//!
|
||||
//! let num_signatures = secp256k1_instr.data[0];
|
||||
//! // `new_secp256k1_instruction` generates an instruction that contains one signature.
|
||||
//! assert_eq!(1, num_signatures);
|
||||
//!
|
||||
//! // Load the first and only set of signature offsets.
|
||||
//! let offsets: secp256k1_defs::SecpSignatureOffsets =
|
||||
//! secp256k1_defs::iter_signature_offsets(&secp256k1_instr.data)?
|
||||
//! .next()
|
||||
//! .ok_or(ProgramError::InvalidArgument)?;
|
||||
//!
|
||||
//! // `new_secp256k1_instruction` generates an instruction that only uses instruction index 0.
|
||||
//! assert_eq!(0, offsets.signature_instruction_index);
|
||||
//! assert_eq!(0, offsets.eth_address_instruction_index);
|
||||
//! assert_eq!(0, offsets.message_instruction_index);
|
||||
//!
|
||||
//! // Reject high-s value signatures to prevent malleability.
|
||||
//! // Solana does not do this itself.
|
||||
//! // This may or may not be necessary depending on use case.
|
||||
//! {
|
||||
//! let signature = &secp256k1_instr.data[offsets.signature_offset as usize
|
||||
//! ..offsets.signature_offset as usize + secp256k1_defs::SIGNATURE_SERIALIZED_SIZE];
|
||||
//! let signature = libsecp256k1::Signature::parse_standard_slice(signature)
|
||||
//! .map_err(|_| ProgramError::InvalidArgument)?;
|
||||
//!
|
||||
//! if signature.s.is_high() {
|
||||
//! msg!("signature with high-s value");
|
||||
//! return Err(ProgramError::InvalidArgument);
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // There is likely at least one more verification step a real program needs
|
||||
//! // to do here to ensure it trusts the secp256k1 instruction, e.g.:
|
||||
//! //
|
||||
//! // - verify the tx signer is authorized
|
||||
//! // - verify the secp256k1 signer is authorized
|
||||
//!
|
||||
//! // Here we are checking the secp256k1 pubkey against a known authorized pubkey.
|
||||
//! let eth_address = &secp256k1_instr.data[offsets.eth_address_offset as usize
|
||||
//! ..offsets.eth_address_offset as usize + secp256k1_defs::HASHED_PUBKEY_SERIALIZED_SIZE];
|
||||
//!
|
||||
//! if eth_address != AUTHORIZED_ETH_ADDRESS {
|
||||
//! return Err(ProgramError::InvalidArgument);
|
||||
//! }
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! The client program:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use solana_sdk::example_mocks::solana_client;
|
||||
//! use anyhow::Result;
|
||||
//! use solana_client::rpc_client::RpcClient;
|
||||
//! use solana_sdk::{
|
||||
//! instruction::{AccountMeta, Instruction},
|
||||
//! secp256k1_instruction,
|
||||
//! signature::{Keypair, Signer},
|
||||
//! sysvar,
|
||||
//! transaction::Transaction,
|
||||
//! };
|
||||
//!
|
||||
//! fn demo_secp256k1_verify_basic(
|
||||
//! payer_keypair: &Keypair,
|
||||
//! secp256k1_secret_key: &libsecp256k1::SecretKey,
|
||||
//! client: &RpcClient,
|
||||
//! program_keypair: &Keypair,
|
||||
//! ) -> Result<()> {
|
||||
//! // Internally to `new_secp256k1_instruction` and
|
||||
//! // `secp256k_instruction::verify` (the secp256k1 program), this message is
|
||||
//! // keccak-hashed before signing.
|
||||
//! let msg = b"hello world";
|
||||
//! let secp256k1_instr = secp256k1_instruction::new_secp256k1_instruction(&secp256k1_secret_key, msg);
|
||||
//!
|
||||
//! let program_instr = Instruction::new_with_bytes(
|
||||
//! program_keypair.pubkey(),
|
||||
//! &[],
|
||||
//! vec![
|
||||
//! AccountMeta::new_readonly(sysvar::instructions::ID, false)
|
||||
//! ],
|
||||
//! );
|
||||
//!
|
||||
//! let blockhash = client.get_latest_blockhash()?;
|
||||
//! let tx = Transaction::new_signed_with_payer(
|
||||
//! &[secp256k1_instr, program_instr],
|
||||
//! Some(&payer_keypair.pubkey()),
|
||||
//! &[payer_keypair],
|
||||
//! blockhash,
|
||||
//! );
|
||||
//!
|
||||
//! client.send_and_confirm_transaction(&tx)?;
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Example: Verifying multiple signatures in one instruction
|
||||
//!
|
||||
//! This examples demonstrates manually creating a secp256k1 instruction
|
||||
//! containing many signatures, and a Solana program that parses them all. This
|
||||
//! example on its own has no practical purpose. It simply demonstrates advanced
|
||||
//! use of the secp256k1 program.
|
||||
//!
|
||||
//! Recall that the secp256k1 program will accept signatures, messages, and
|
||||
//! Ethereum addresses that reside in any instruction contained in the same
|
||||
//! transaction. In the _previous_ example, the Solana program asserted that all
|
||||
//! signatures, messages, and addresses were stored in the instruction at 0. In
|
||||
//! this next example the Solana program supports signatures, messages, and
|
||||
//! addresses stored in any instruction. For simplicity the client still only
|
||||
//! stores signatures, messages, and addresses in a single instruction, the
|
||||
//! secp256k1 instruction. The code for storing this data across multiple
|
||||
//! instructions would be complex, and may not be necessary in practice.
|
||||
//!
|
||||
//! This example has two components: a Solana program, and an RPC client that
|
||||
//! sends a transaction to call it.
|
||||
//!
|
||||
//! The Solana program:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # mod secp256k1_defs {
|
||||
//! # use solana_program::program_error::ProgramError;
|
||||
//! # use std::iter::Iterator;
|
||||
//! #
|
||||
//! # pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
|
||||
//! # pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
|
||||
//! # pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
|
||||
//! #
|
||||
//! # /// The structure encoded in the secp2256k1 instruction data.
|
||||
//! # pub struct SecpSignatureOffsets {
|
||||
//! # pub signature_offset: u16,
|
||||
//! # pub signature_instruction_index: u8,
|
||||
//! # pub eth_address_offset: u16,
|
||||
//! # pub eth_address_instruction_index: u8,
|
||||
//! # pub message_data_offset: u16,
|
||||
//! # pub message_data_size: u16,
|
||||
//! # pub message_instruction_index: u8,
|
||||
//! # }
|
||||
//! #
|
||||
//! # pub fn iter_signature_offsets(
|
||||
//! # secp256k1_instr_data: &[u8],
|
||||
//! # ) -> Result<impl Iterator<Item = SecpSignatureOffsets> + '_, ProgramError> {
|
||||
//! # // First element is the number of `SecpSignatureOffsets`.
|
||||
//! # let num_structs = *secp256k1_instr_data
|
||||
//! # .get(0)
|
||||
//! # .ok_or(ProgramError::InvalidArgument)?;
|
||||
//! #
|
||||
//! # let all_structs_size = SIGNATURE_OFFSETS_SERIALIZED_SIZE * num_structs as usize;
|
||||
//! # let all_structs_slice = secp256k1_instr_data
|
||||
//! # .get(1..all_structs_size + 1)
|
||||
//! # .ok_or(ProgramError::InvalidArgument)?;
|
||||
//! #
|
||||
//! # fn decode_u16(chunk: &[u8], index: usize) -> u16 {
|
||||
//! # u16::from_le_bytes(<[u8; 2]>::try_from(&chunk[index..index + 2]).unwrap())
|
||||
//! # }
|
||||
//! #
|
||||
//! # Ok(all_structs_slice
|
||||
//! # .chunks(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
|
||||
//! # .map(|chunk| SecpSignatureOffsets {
|
||||
//! # signature_offset: decode_u16(chunk, 0),
|
||||
//! # signature_instruction_index: chunk[2],
|
||||
//! # eth_address_offset: decode_u16(chunk, 3),
|
||||
//! # eth_address_instruction_index: chunk[5],
|
||||
//! # message_data_offset: decode_u16(chunk, 6),
|
||||
//! # message_data_size: decode_u16(chunk, 8),
|
||||
//! # message_instruction_index: chunk[10],
|
||||
//! # }))
|
||||
//! # }
|
||||
//! # }
|
||||
//! use solana_program::{
|
||||
//! account_info::{next_account_info, AccountInfo},
|
||||
//! entrypoint::ProgramResult,
|
||||
//! msg,
|
||||
//! program_error::ProgramError,
|
||||
//! secp256k1_program,
|
||||
//! sysvar,
|
||||
//! };
|
||||
//!
|
||||
//! /// A struct to hold the values specified in the `SecpSignatureOffsets` struct.
|
||||
//! struct SecpSignature {
|
||||
//! signature: [u8; secp256k1_defs::SIGNATURE_SERIALIZED_SIZE],
|
||||
//! recovery_id: u8,
|
||||
//! eth_address: [u8; secp256k1_defs::HASHED_PUBKEY_SERIALIZED_SIZE],
|
||||
//! message: Vec<u8>,
|
||||
//! }
|
||||
//!
|
||||
//! /// Load all signatures indicated in the secp256k1 instruction.
|
||||
//! ///
|
||||
//! /// This function is quite inefficient for reloading the same instructions
|
||||
//! /// repeatedly and making copies and allocations.
|
||||
//! fn load_signatures(
|
||||
//! secp256k1_instr_data: &[u8],
|
||||
//! instructions_sysvar_account: &AccountInfo,
|
||||
//! ) -> Result<Vec<SecpSignature>, ProgramError> {
|
||||
//! let mut sigs = vec![];
|
||||
//! for offsets in secp256k1_defs::iter_signature_offsets(secp256k1_instr_data)? {
|
||||
//! let signature_instr = sysvar::instructions::load_instruction_at_checked(
|
||||
//! offsets.signature_instruction_index as usize,
|
||||
//! instructions_sysvar_account,
|
||||
//! )?;
|
||||
//! let eth_address_instr = sysvar::instructions::load_instruction_at_checked(
|
||||
//! offsets.eth_address_instruction_index as usize,
|
||||
//! instructions_sysvar_account,
|
||||
//! )?;
|
||||
//! let message_instr = sysvar::instructions::load_instruction_at_checked(
|
||||
//! offsets.message_instruction_index as usize,
|
||||
//! instructions_sysvar_account,
|
||||
//! )?;
|
||||
//!
|
||||
//! // These indexes must all be valid because the runtime already verified them.
|
||||
//! let signature = &signature_instr.data[offsets.signature_offset as usize
|
||||
//! ..offsets.signature_offset as usize + secp256k1_defs::SIGNATURE_SERIALIZED_SIZE];
|
||||
//! let recovery_id = signature_instr.data
|
||||
//! [offsets.signature_offset as usize + secp256k1_defs::SIGNATURE_SERIALIZED_SIZE];
|
||||
//! let eth_address = ð_address_instr.data[offsets.eth_address_offset as usize
|
||||
//! ..offsets.eth_address_offset as usize + secp256k1_defs::HASHED_PUBKEY_SERIALIZED_SIZE];
|
||||
//! let message = &message_instr.data[offsets.message_data_offset as usize
|
||||
//! ..offsets.message_data_offset as usize + offsets.message_data_size as usize];
|
||||
//!
|
||||
//! let signature =
|
||||
//! <[u8; secp256k1_defs::SIGNATURE_SERIALIZED_SIZE]>::try_from(signature).unwrap();
|
||||
//! let eth_address =
|
||||
//! <[u8; secp256k1_defs::HASHED_PUBKEY_SERIALIZED_SIZE]>::try_from(eth_address).unwrap();
|
||||
//! let message = Vec::from(message);
|
||||
//!
|
||||
//! sigs.push(SecpSignature {
|
||||
//! signature,
|
||||
//! recovery_id,
|
||||
//! eth_address,
|
||||
//! message,
|
||||
//! })
|
||||
//! }
|
||||
//! Ok(sigs)
|
||||
//! }
|
||||
//!
|
||||
//! fn demo_secp256k1_custom_many(
|
||||
//! accounts: &[AccountInfo],
|
||||
//! ) -> ProgramResult {
|
||||
//! let account_info_iter = &mut accounts.iter();
|
||||
//!
|
||||
//! let instructions_sysvar_account = next_account_info(account_info_iter)?;
|
||||
//! assert!(sysvar::instructions::check_id(
|
||||
//! instructions_sysvar_account.key
|
||||
//! ));
|
||||
//!
|
||||
//! let secp256k1_instr =
|
||||
//! sysvar::instructions::get_instruction_relative(-1, instructions_sysvar_account)?;
|
||||
//!
|
||||
//! assert!(secp256k1_program::check_id(&secp256k1_instr.program_id));
|
||||
//!
|
||||
//! let signatures = load_signatures(&secp256k1_instr.data, instructions_sysvar_account)?;
|
||||
//! for (idx, signature_bundle) in signatures.iter().enumerate() {
|
||||
//! let signature = hex::encode(&signature_bundle.signature);
|
||||
//! let eth_address = hex::encode(&signature_bundle.eth_address);
|
||||
//! let message = hex::encode(&signature_bundle.message);
|
||||
//! msg!("sig {}: {:?}", idx, signature);
|
||||
//! msg!("recid: {}: {}", idx, signature_bundle.recovery_id);
|
||||
//! msg!("eth address {}: {}", idx, eth_address);
|
||||
//! msg!("message {}: {}", idx, message);
|
||||
//! }
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! The client program:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use solana_sdk::example_mocks::solana_client;
|
||||
//! use anyhow::Result;
|
||||
//! use solana_client::rpc_client::RpcClient;
|
||||
//! use solana_sdk::{
|
||||
//! instruction::{AccountMeta, Instruction},
|
||||
//! keccak,
|
||||
//! secp256k1_instruction::{
|
||||
//! self, SecpSignatureOffsets, HASHED_PUBKEY_SERIALIZED_SIZE,
|
||||
//! SIGNATURE_OFFSETS_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE,
|
||||
//! },
|
||||
//! signature::{Keypair, Signer},
|
||||
//! sysvar,
|
||||
//! transaction::Transaction,
|
||||
//! };
|
||||
//!
|
||||
//! /// A struct to hold the values specified in the `SecpSignatureOffsets` struct.
|
||||
//! struct SecpSignature {
|
||||
//! signature: [u8; SIGNATURE_SERIALIZED_SIZE],
|
||||
//! recovery_id: u8,
|
||||
//! eth_address: [u8; HASHED_PUBKEY_SERIALIZED_SIZE],
|
||||
//! message: Vec<u8>,
|
||||
//! }
|
||||
//!
|
||||
//! /// Create the instruction data for a secp256k1 instruction.
|
||||
//! ///
|
||||
//! /// `instruction_index` is the index the secp256k1 instruction will appear
|
||||
//! /// within the transaction. For simplicity, this function only supports packing
|
||||
//! /// the signatures into the secp256k1 instruction data, and not into any other
|
||||
//! /// instructions within the transaction.
|
||||
//! fn make_secp256k1_instruction_data(
|
||||
//! signatures: &[SecpSignature],
|
||||
//! instruction_index: u8,
|
||||
//! ) -> Result<Vec<u8>> {
|
||||
//! assert!(signatures.len() <= u8::max_value().into());
|
||||
//!
|
||||
//! // We're going to pack all the signatures into the secp256k1 instruction data.
|
||||
//! // Before our signatures though is the signature offset structures
|
||||
//! // the secp256k1 program parses to find those signatures.
|
||||
//! // This value represents the byte offset where the signatures begin.
|
||||
//! let data_start = 1 + signatures.len() * SIGNATURE_OFFSETS_SERIALIZED_SIZE;
|
||||
//!
|
||||
//! let mut signature_offsets = vec![];
|
||||
//! let mut signature_buffer = vec![];
|
||||
//!
|
||||
//! for signature_bundle in signatures {
|
||||
//! let data_start = data_start
|
||||
//! .checked_add(signature_buffer.len())
|
||||
//! .expect("overflow");
|
||||
//!
|
||||
//! let signature_offset = data_start;
|
||||
//! let eth_address_offset = data_start
|
||||
//! .checked_add(SIGNATURE_SERIALIZED_SIZE + 1)
|
||||
//! .expect("overflow");
|
||||
//! let message_data_offset = eth_address_offset
|
||||
//! .checked_add(HASHED_PUBKEY_SERIALIZED_SIZE)
|
||||
//! .expect("overflow");
|
||||
//! let message_data_size = signature_bundle.message.len();
|
||||
//!
|
||||
//! let signature_offset = u16::try_from(signature_offset)?;
|
||||
//! let eth_address_offset = u16::try_from(eth_address_offset)?;
|
||||
//! let message_data_offset = u16::try_from(message_data_offset)?;
|
||||
//! let message_data_size = u16::try_from(message_data_size)?;
|
||||
//!
|
||||
//! signature_offsets.push(SecpSignatureOffsets {
|
||||
//! signature_offset,
|
||||
//! signature_instruction_index: instruction_index,
|
||||
//! eth_address_offset,
|
||||
//! eth_address_instruction_index: instruction_index,
|
||||
//! message_data_offset,
|
||||
//! message_data_size,
|
||||
//! message_instruction_index: instruction_index,
|
||||
//! });
|
||||
//!
|
||||
//! signature_buffer.extend(signature_bundle.signature);
|
||||
//! signature_buffer.push(signature_bundle.recovery_id);
|
||||
//! signature_buffer.extend(&signature_bundle.eth_address);
|
||||
//! signature_buffer.extend(&signature_bundle.message);
|
||||
//! }
|
||||
//!
|
||||
//! let mut instr_data = vec![];
|
||||
//! instr_data.push(signatures.len() as u8);
|
||||
//!
|
||||
//! for offsets in signature_offsets {
|
||||
//! let offsets = bincode::serialize(&offsets)?;
|
||||
//! instr_data.extend(offsets);
|
||||
//! }
|
||||
//!
|
||||
//! instr_data.extend(signature_buffer);
|
||||
//!
|
||||
//! Ok(instr_data)
|
||||
//! }
|
||||
//!
|
||||
//! fn demo_secp256k1_custom_many(
|
||||
//! payer_keypair: &Keypair,
|
||||
//! client: &RpcClient,
|
||||
//! program_keypair: &Keypair,
|
||||
//! ) -> Result<()> {
|
||||
//! // Sign some messages.
|
||||
//! let mut signatures = vec![];
|
||||
//! for idx in 0..2 {
|
||||
//! let secret_key = libsecp256k1::SecretKey::random(&mut rand::thread_rng());
|
||||
//! let message = format!("hello world {}", idx).into_bytes();
|
||||
//! let message_hash = {
|
||||
//! let mut hasher = keccak::Hasher::default();
|
||||
//! hasher.hash(&message);
|
||||
//! hasher.result()
|
||||
//! };
|
||||
//! let secp_message = libsecp256k1::Message::parse(&message_hash.0);
|
||||
//! let (signature, recovery_id) = libsecp256k1::sign(&secp_message, &secret_key);
|
||||
//! let signature = signature.serialize();
|
||||
//! let recovery_id = recovery_id.serialize();
|
||||
//!
|
||||
//! let public_key = libsecp256k1::PublicKey::from_secret_key(&secret_key);
|
||||
//! let eth_address = secp256k1_instruction::construct_eth_pubkey(&public_key);
|
||||
//!
|
||||
//! signatures.push(SecpSignature {
|
||||
//! signature,
|
||||
//! recovery_id,
|
||||
//! eth_address,
|
||||
//! message,
|
||||
//! });
|
||||
//! }
|
||||
//!
|
||||
//! let secp256k1_instr_data = make_secp256k1_instruction_data(&signatures, 0)?;
|
||||
//! let secp256k1_instr = Instruction::new_with_bytes(
|
||||
//! solana_sdk::secp256k1_program::ID,
|
||||
//! &secp256k1_instr_data,
|
||||
//! vec![],
|
||||
//! );
|
||||
//!
|
||||
//! let program_instr = Instruction::new_with_bytes(
|
||||
//! program_keypair.pubkey(),
|
||||
//! &[],
|
||||
//! vec![
|
||||
//! AccountMeta::new_readonly(sysvar::instructions::ID, false)
|
||||
//! ],
|
||||
//! );
|
||||
//!
|
||||
//! let blockhash = client.get_latest_blockhash()?;
|
||||
//! let tx = Transaction::new_signed_with_payer(
|
||||
//! &[secp256k1_instr, program_instr],
|
||||
//! Some(&payer_keypair.pubkey()),
|
||||
//! &[payer_keypair],
|
||||
//! blockhash,
|
||||
//! );
|
||||
//!
|
||||
//! client.send_and_confirm_transaction(&tx)?;
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use {
|
||||
|
@ -19,17 +806,45 @@ pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
|
|||
pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
|
||||
pub const DATA_START: usize = SIGNATURE_OFFSETS_SERIALIZED_SIZE + 1;
|
||||
|
||||
/// Offsets of signature data within a secp256k1 instruction.
|
||||
///
|
||||
/// See the [module documentation][md] for a complete description.
|
||||
///
|
||||
/// [md]: self
|
||||
#[derive(Default, Serialize, Deserialize, Debug)]
|
||||
pub struct SecpSignatureOffsets {
|
||||
pub signature_offset: u16, // offset to [signature,recovery_id] of 64+1 bytes
|
||||
/// Offset to 64-byte signature plus 1-byte recovery ID.
|
||||
pub signature_offset: u16,
|
||||
/// Within the transaction, the index of the instruction whose instruction data contains the signature.
|
||||
pub signature_instruction_index: u8,
|
||||
pub eth_address_offset: u16, // offset to eth_address of 20 bytes
|
||||
/// Offset to 20-byte Ethereum address.
|
||||
pub eth_address_offset: u16,
|
||||
/// Within the transaction, the index of the instruction whose instruction data contains the address.
|
||||
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
|
||||
/// Offset to start of message data.
|
||||
pub message_data_offset: u16,
|
||||
/// Size of message data in bytes.
|
||||
pub message_data_size: u16,
|
||||
/// Within the transaction, the index of the instruction whose instruction data contains the message.
|
||||
pub message_instruction_index: u8,
|
||||
}
|
||||
|
||||
/// Sign a message and create a secp256k1 program instruction to verify the signature.
|
||||
///
|
||||
/// This function is suitable for simple uses of the secp256k1 program.
|
||||
/// More complex uses must encode the secp256k1 instruction data manually.
|
||||
/// See the [module documentation][md] for examples.
|
||||
///
|
||||
/// [md]: self
|
||||
///
|
||||
/// The instruction generated by this function must be the first instruction
|
||||
/// included in a transaction or it will not verify. The
|
||||
/// [`SecpSignatureOffsets`] structure encoded in the instruction data specify
|
||||
/// the instruction indexes as 0.
|
||||
///
|
||||
/// `message_arr` is hashed with the [`keccak`] hash function prior to signing.
|
||||
///
|
||||
/// [`keccak`]: crate::keccak
|
||||
pub fn new_secp256k1_instruction(
|
||||
priv_key: &libsecp256k1::SecretKey,
|
||||
message_arr: &[u8],
|
||||
|
@ -92,6 +907,7 @@ pub fn new_secp256k1_instruction(
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates an Ethereum address from a secp256k1 public key.
|
||||
pub fn construct_eth_pubkey(
|
||||
pubkey: &libsecp256k1::PublicKey,
|
||||
) -> [u8; HASHED_PUBKEY_SERIALIZED_SIZE] {
|
||||
|
@ -101,6 +917,19 @@ pub fn construct_eth_pubkey(
|
|||
addr
|
||||
}
|
||||
|
||||
/// Verifies the signatures specified in the secp256k1 instruction data.
|
||||
///
|
||||
/// This is same the verification routine executed by the runtime's secp256k1 native program,
|
||||
/// and is primarily of use to the runtime.
|
||||
///
|
||||
/// `data` is the secp256k1 program's instruction data. `instruction_datas` is
|
||||
/// the full slice of instruction datas for all instructions in the transaction,
|
||||
/// including the secp256k1 program's instruction data.
|
||||
///
|
||||
/// `feature_set` is the set of active Solana features. It is used to enable or
|
||||
/// disable a few minor additional checks that were activated on chain
|
||||
/// subsequent to the addition of the secp256k1 native program. For many
|
||||
/// purposes passing `Arc::new<FeatureSet::all_enabled()>` is reasonable.
|
||||
pub fn verify(
|
||||
data: &[u8],
|
||||
instruction_datas: &[&[u8]],
|
||||
|
@ -224,6 +1053,7 @@ pub mod test {
|
|||
crate::{
|
||||
feature_set,
|
||||
hash::Hash,
|
||||
keccak,
|
||||
secp256k1_instruction::{
|
||||
new_secp256k1_instruction, SecpSignatureOffsets, SIGNATURE_OFFSETS_SERIALIZED_SIZE,
|
||||
},
|
||||
|
@ -453,4 +1283,74 @@ pub mod test {
|
|||
);
|
||||
assert!(tx.verify_precompiles(&feature_set).is_err());
|
||||
}
|
||||
|
||||
// Signatures are malleable.
|
||||
#[test]
|
||||
fn test_malleability() {
|
||||
solana_logger::setup();
|
||||
|
||||
let secret_key = libsecp256k1::SecretKey::random(&mut thread_rng());
|
||||
let public_key = libsecp256k1::PublicKey::from_secret_key(&secret_key);
|
||||
let eth_address = construct_eth_pubkey(&public_key);
|
||||
|
||||
let message = b"hello";
|
||||
let message_hash = {
|
||||
let mut hasher = keccak::Hasher::default();
|
||||
hasher.hash(message);
|
||||
hasher.result()
|
||||
};
|
||||
|
||||
let secp_message = libsecp256k1::Message::parse(&message_hash.0);
|
||||
let (signature, recovery_id) = libsecp256k1::sign(&secp_message, &secret_key);
|
||||
|
||||
// Flip the S value in the signature to make a different but valid signature.
|
||||
let mut alt_signature = signature;
|
||||
alt_signature.s = -alt_signature.s;
|
||||
let alt_recovery_id = libsecp256k1::RecoveryId::parse(recovery_id.serialize() ^ 1).unwrap();
|
||||
|
||||
let mut data: Vec<u8> = vec![];
|
||||
let mut both_offsets = vec![];
|
||||
|
||||
// Verify both signatures of the same message.
|
||||
let sigs = [(signature, recovery_id), (alt_signature, alt_recovery_id)];
|
||||
for (signature, recovery_id) in sigs.iter() {
|
||||
let signature_offset = data.len();
|
||||
data.extend(signature.serialize());
|
||||
data.push(recovery_id.serialize());
|
||||
let eth_address_offset = data.len();
|
||||
data.extend(ð_address);
|
||||
let message_data_offset = data.len();
|
||||
data.extend(message);
|
||||
|
||||
let data_start = 1 + SIGNATURE_OFFSETS_SERIALIZED_SIZE * 2;
|
||||
|
||||
let offsets = SecpSignatureOffsets {
|
||||
signature_offset: (signature_offset + data_start) as u16,
|
||||
signature_instruction_index: 0,
|
||||
eth_address_offset: (eth_address_offset + data_start) as u16,
|
||||
eth_address_instruction_index: 0,
|
||||
message_data_offset: (message_data_offset + data_start) as u16,
|
||||
message_data_size: message.len() as u16,
|
||||
message_instruction_index: 0,
|
||||
};
|
||||
|
||||
both_offsets.push(offsets);
|
||||
}
|
||||
|
||||
let mut instruction_data: Vec<u8> = vec![2];
|
||||
|
||||
for offsets in both_offsets {
|
||||
let offsets = bincode::serialize(&offsets).unwrap();
|
||||
instruction_data.extend(offsets);
|
||||
}
|
||||
|
||||
instruction_data.extend(data);
|
||||
|
||||
verify(
|
||||
&instruction_data,
|
||||
&[&instruction_data],
|
||||
&Arc::new(FeatureSet::all_enabled()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue