Land program addresses on the curve (#11174)
This commit is contained in:
parent
aa8d6083c4
commit
f317c362a8
|
@ -3850,6 +3850,7 @@ dependencies = [
|
||||||
"bv",
|
"bv",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"curve25519-dalek",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"generic-array 0.14.3",
|
"generic-array 0.14.3",
|
||||||
"hex",
|
"hex",
|
||||||
|
|
|
@ -1714,6 +1714,7 @@ dependencies = [
|
||||||
"bv 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bv 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"curve25519-dalek 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ed25519-dalek 1.0.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ed25519-dalek 1.0.0-pre.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"generic-array 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"generic-array 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -18,6 +18,7 @@ default = [
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"curve25519-dalek",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"memmap",
|
"memmap",
|
||||||
"rand",
|
"rand",
|
||||||
|
@ -35,6 +36,7 @@ bs58 = "0.3.1"
|
||||||
bv = { version = "0.11.1", features = ["serde"] }
|
bv = { version = "0.11.1", features = ["serde"] }
|
||||||
byteorder = { version = "1.3.4", optional = true }
|
byteorder = { version = "1.3.4", optional = true }
|
||||||
chrono = { version = "0.4", optional = true }
|
chrono = { version = "0.4", optional = true }
|
||||||
|
curve25519-dalek = { version = "2.1.0", optional = true }
|
||||||
generic-array = { version = "0.14.3", default-features = false, features = ["serde", "more_lengths"], optional = true }
|
generic-array = { version = "0.14.3", default-features = false, features = ["serde", "more_lengths"], optional = true }
|
||||||
hex = "0.4.2"
|
hex = "0.4.2"
|
||||||
hmac = "0.7.0"
|
hmac = "0.7.0"
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
#![feature(test)]
|
||||||
|
|
||||||
|
extern crate test;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_pubkey_create_program_address(b: &mut Bencher) {
|
||||||
|
let program_id = Pubkey::new_rand();
|
||||||
|
b.iter(|| {
|
||||||
|
Pubkey::create_program_address(&[b"todo"], &program_id).unwrap();
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,10 +1,5 @@
|
||||||
use crate::{
|
use crate::{decode_error::DecodeError, hash::hashv};
|
||||||
decode_error::DecodeError,
|
|
||||||
hash::{hash, hashv, Hasher},
|
|
||||||
};
|
|
||||||
use num_derive::{FromPrimitive, ToPrimitive};
|
use num_derive::{FromPrimitive, ToPrimitive};
|
||||||
#[cfg(not(feature = "program"))]
|
|
||||||
use std::error;
|
|
||||||
use std::{convert::TryFrom, fmt, mem, str::FromStr};
|
use std::{convert::TryFrom, fmt, mem, str::FromStr};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -87,11 +82,14 @@ impl Pubkey {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "program"))]
|
||||||
pub fn create_program_address(
|
pub fn create_program_address(
|
||||||
seeds: &[&[u8]],
|
seeds: &[&[u8]],
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
) -> Result<Pubkey, PubkeyError> {
|
) -> Result<Pubkey, PubkeyError> {
|
||||||
let mut hasher = Hasher::default();
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
let mut hasher = crate::hash::Hasher::default();
|
||||||
for seed in seeds.iter() {
|
for seed in seeds.iter() {
|
||||||
if seed.len() > MAX_SEED_LEN {
|
if seed.len() > MAX_SEED_LEN {
|
||||||
return Err(PubkeyError::MaxSeedLengthExceeded);
|
return Err(PubkeyError::MaxSeedLengthExceeded);
|
||||||
|
@ -99,8 +97,20 @@ impl Pubkey {
|
||||||
hasher.hash(seed);
|
hasher.hash(seed);
|
||||||
}
|
}
|
||||||
hasher.hashv(&[program_id.as_ref(), "ProgramDerivedAddress".as_ref()]);
|
hasher.hashv(&[program_id.as_ref(), "ProgramDerivedAddress".as_ref()]);
|
||||||
|
let mut hashed_bits: [u8; 32] = hasher.result().as_ref().try_into().unwrap();
|
||||||
|
|
||||||
Ok(Pubkey::new(hash(hasher.result().as_ref()).as_ref()))
|
// clamp
|
||||||
|
hashed_bits[0] &= 248;
|
||||||
|
hashed_bits[31] &= 127;
|
||||||
|
hashed_bits[31] |= 64;
|
||||||
|
|
||||||
|
// point multiply
|
||||||
|
Ok(Pubkey::new(
|
||||||
|
(&curve25519_dalek::scalar::Scalar::from_bits(hashed_bits)
|
||||||
|
* &curve25519_dalek::constants::ED25519_BASEPOINT_TABLE)
|
||||||
|
.compress()
|
||||||
|
.as_bytes(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "program"))]
|
#[cfg(not(feature = "program"))]
|
||||||
|
@ -132,7 +142,7 @@ impl fmt::Display for Pubkey {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "program"))]
|
#[cfg(not(feature = "program"))]
|
||||||
pub fn write_pubkey_file(outfile: &str, pubkey: Pubkey) -> Result<(), Box<dyn error::Error>> {
|
pub fn write_pubkey_file(outfile: &str, pubkey: Pubkey) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
let printable = format!("{}", pubkey);
|
let printable = format!("{}", pubkey);
|
||||||
|
@ -148,7 +158,7 @@ pub fn write_pubkey_file(outfile: &str, pubkey: Pubkey) -> Result<(), Box<dyn er
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "program"))]
|
#[cfg(not(feature = "program"))]
|
||||||
pub fn read_pubkey_file(infile: &str) -> Result<Pubkey, Box<dyn error::Error>> {
|
pub fn read_pubkey_file(infile: &str) -> Result<Pubkey, Box<dyn std::error::Error>> {
|
||||||
let f = std::fs::File::open(infile.to_string())?;
|
let f = std::fs::File::open(infile.to_string())?;
|
||||||
let printable: String = serde_json::from_reader(f)?;
|
let printable: String = serde_json::from_reader(f)?;
|
||||||
Ok(Pubkey::from_str(&printable)?)
|
Ok(Pubkey::from_str(&printable)?)
|
||||||
|
@ -262,25 +272,25 @@ mod tests {
|
||||||
assert!(Pubkey::create_program_address(&[max_seed], &Pubkey::new_rand()).is_ok());
|
assert!(Pubkey::create_program_address(&[max_seed], &Pubkey::new_rand()).is_ok());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Pubkey::create_program_address(&[b""], &program_id),
|
Pubkey::create_program_address(&[b""], &program_id),
|
||||||
Ok("CsdSsqp6Upkh2qajhZMBM8xT4GAyDNSmcV37g4pN8rsc"
|
Ok("Gv1heG5PQhTNevViduUvVBv8XmcEo6AHcoyA2wXrCsAa"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap())
|
.unwrap())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Pubkey::create_program_address(&["☉".as_ref()], &program_id),
|
Pubkey::create_program_address(&["☉".as_ref()], &program_id),
|
||||||
Ok("A8mYnN8Pfx7Nn6f8RoQgsPNtAGAWmmKSBCDfyDvE6sXF"
|
Ok("GzVDzHSMACepN7FVPhPJG3DkG2WkEWgRHWanefxvaZq6"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap())
|
.unwrap())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id),
|
Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id),
|
||||||
Ok("CawYq8Rmj4JRR992wVnGEFUjMEkmtmcFgEL4iS1qPczu"
|
Ok("BBstFkvRCCbzQKgdTqypWN1bZmfEmRc9d5sNGYZtEicM"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap())
|
.unwrap())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Pubkey::create_program_address(&[public_key.as_ref()], &program_id),
|
Pubkey::create_program_address(&[public_key.as_ref()], &program_id),
|
||||||
Ok("4ak7qJacCKMAGP8xJtDkg2VYZh5QKExa71ijMDjZGQyb"
|
Ok("6f2a73BjEZKguMpgdmzvSz6qNtmzomCkJgoK21F1yPQK"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap())
|
.unwrap())
|
||||||
);
|
);
|
||||||
|
@ -291,7 +301,29 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_read_write_pubkey() -> Result<(), Box<dyn error::Error>> {
|
fn test_pubkey_on_curve() {
|
||||||
|
// try a bunch of random input, all the generated program addresses should land on the curve
|
||||||
|
// and should be unique
|
||||||
|
let mut addresses = vec![];
|
||||||
|
for _ in 0..1_000 {
|
||||||
|
let program_id = Pubkey::new_rand();
|
||||||
|
let bytes1 = rand::random::<[u8; 10]>();
|
||||||
|
let bytes2 = rand::random::<[u8; 32]>();
|
||||||
|
let program_address =
|
||||||
|
Pubkey::create_program_address(&[&bytes1, &bytes2], &program_id).unwrap();
|
||||||
|
let is_on_curve = curve25519_dalek::edwards::CompressedEdwardsY::from_slice(
|
||||||
|
&program_address.to_bytes(),
|
||||||
|
)
|
||||||
|
.decompress()
|
||||||
|
.is_some();
|
||||||
|
assert!(is_on_curve);
|
||||||
|
assert!(!addresses.contains(&program_address));
|
||||||
|
addresses.push(program_address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_write_pubkey() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let filename = "test_pubkey.json";
|
let filename = "test_pubkey.json";
|
||||||
let pubkey = Pubkey::new_rand();
|
let pubkey = Pubkey::new_rand();
|
||||||
write_pubkey_file(filename, pubkey)?;
|
write_pubkey_file(filename, pubkey)?;
|
||||||
|
|
Loading…
Reference in New Issue