From f317c362a8932b4dbb3f8683729dfd72e3991fef Mon Sep 17 00:00:00 2001 From: Jack May Date: Mon, 27 Jul 2020 10:45:59 -0700 Subject: [PATCH] Land program addresses on the curve (#11174) --- Cargo.lock | 1 + programs/bpf/Cargo.lock | 1 + sdk/Cargo.toml | 2 ++ sdk/benches/pubkey.rs | 13 +++++++++ sdk/src/pubkey.rs | 62 +++++++++++++++++++++++++++++++---------- 5 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 sdk/benches/pubkey.rs diff --git a/Cargo.lock b/Cargo.lock index 4fa629e601..523baf393a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3850,6 +3850,7 @@ dependencies = [ "bv", "byteorder", "chrono", + "curve25519-dalek", "ed25519-dalek", "generic-array 0.14.3", "hex", diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index a867291387..f311d36b12 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1714,6 +1714,7 @@ dependencies = [ "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)", "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)", "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)", diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index d5919be7a7..89ed8701e2 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -18,6 +18,7 @@ default = [ "assert_matches", "byteorder", "chrono", + "curve25519-dalek", "generic-array", "memmap", "rand", @@ -35,6 +36,7 @@ bs58 = "0.3.1" bv = { version = "0.11.1", features = ["serde"] } byteorder = { version = "1.3.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 } hex = "0.4.2" hmac = "0.7.0" diff --git a/sdk/benches/pubkey.rs b/sdk/benches/pubkey.rs new file mode 100644 index 0000000000..f13e709310 --- /dev/null +++ b/sdk/benches/pubkey.rs @@ -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(); + }); +} diff --git a/sdk/src/pubkey.rs b/sdk/src/pubkey.rs index 36c739ed01..039fcfd085 100644 --- a/sdk/src/pubkey.rs +++ b/sdk/src/pubkey.rs @@ -1,10 +1,5 @@ -use crate::{ - decode_error::DecodeError, - hash::{hash, hashv, Hasher}, -}; +use crate::{decode_error::DecodeError, hash::hashv}; use num_derive::{FromPrimitive, ToPrimitive}; -#[cfg(not(feature = "program"))] -use std::error; use std::{convert::TryFrom, fmt, mem, str::FromStr}; use thiserror::Error; @@ -87,11 +82,14 @@ impl Pubkey { )) } + #[cfg(not(feature = "program"))] pub fn create_program_address( seeds: &[&[u8]], program_id: &Pubkey, ) -> Result { - let mut hasher = Hasher::default(); + use std::convert::TryInto; + + let mut hasher = crate::hash::Hasher::default(); for seed in seeds.iter() { if seed.len() > MAX_SEED_LEN { return Err(PubkeyError::MaxSeedLengthExceeded); @@ -99,8 +97,20 @@ impl Pubkey { hasher.hash(seed); } 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"))] @@ -132,7 +142,7 @@ impl fmt::Display for Pubkey { } #[cfg(not(feature = "program"))] -pub fn write_pubkey_file(outfile: &str, pubkey: Pubkey) -> Result<(), Box> { +pub fn write_pubkey_file(outfile: &str, pubkey: Pubkey) -> Result<(), Box> { use std::io::Write; let printable = format!("{}", pubkey); @@ -148,7 +158,7 @@ pub fn write_pubkey_file(outfile: &str, pubkey: Pubkey) -> Result<(), Box Result> { +pub fn read_pubkey_file(infile: &str) -> Result> { let f = std::fs::File::open(infile.to_string())?; let printable: String = serde_json::from_reader(f)?; Ok(Pubkey::from_str(&printable)?) @@ -262,25 +272,25 @@ mod tests { assert!(Pubkey::create_program_address(&[max_seed], &Pubkey::new_rand()).is_ok()); assert_eq!( Pubkey::create_program_address(&[b""], &program_id), - Ok("CsdSsqp6Upkh2qajhZMBM8xT4GAyDNSmcV37g4pN8rsc" + Ok("Gv1heG5PQhTNevViduUvVBv8XmcEo6AHcoyA2wXrCsAa" .parse() .unwrap()) ); assert_eq!( Pubkey::create_program_address(&["☉".as_ref()], &program_id), - Ok("A8mYnN8Pfx7Nn6f8RoQgsPNtAGAWmmKSBCDfyDvE6sXF" + Ok("GzVDzHSMACepN7FVPhPJG3DkG2WkEWgRHWanefxvaZq6" .parse() .unwrap()) ); assert_eq!( Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id), - Ok("CawYq8Rmj4JRR992wVnGEFUjMEkmtmcFgEL4iS1qPczu" + Ok("BBstFkvRCCbzQKgdTqypWN1bZmfEmRc9d5sNGYZtEicM" .parse() .unwrap()) ); assert_eq!( Pubkey::create_program_address(&[public_key.as_ref()], &program_id), - Ok("4ak7qJacCKMAGP8xJtDkg2VYZh5QKExa71ijMDjZGQyb" + Ok("6f2a73BjEZKguMpgdmzvSz6qNtmzomCkJgoK21F1yPQK" .parse() .unwrap()) ); @@ -291,7 +301,29 @@ mod tests { } #[test] - fn test_read_write_pubkey() -> Result<(), Box> { + 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> { let filename = "test_pubkey.json"; let pubkey = Pubkey::new_rand(); write_pubkey_file(filename, pubkey)?;