Skip to content

Commit

Permalink
Add final pepper derivation to the pepper service and add address to …
Browse files Browse the repository at this point in the history
…the response (#13132)

* add final pepper derivation to the pepper service

* rename to PINKAS_SECRET_KEY_BASE_SEED

* clean up

* update dst

* fix seed

* add address to the response and tests to slip 10

* update example client

* fmt

* clippy

* toml sort update

* machete

* remove encrypted pepper from v1

* fix machete

* fix lint
  • Loading branch information
heliuchuan authored May 10, 2024
1 parent 5433163 commit 5fac1ad
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 49 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ dashmap = { version = "5.5.3", features = ["inline"] }
datatest-stable = "0.1.1"
debug-ignore = { version = "1.0.3", features = ["serde"] }
derivative = "2.2.0"
derivation-path = "0.2.0"
determinator = "0.12.0"
derive_more = "0.99.11"
diesel = "2.1"
Expand Down Expand Up @@ -579,6 +580,7 @@ heck = "0.4.1"
hex = { version = "0.4.3", features = ["serde"] }
hex-literal = "0.3.4"
hkdf = "0.10.0"
hmac = "0.12.0"
hostname = "0.3.1"
http = "0.2.9"
httpmock = "0.6.8"
Expand Down
14 changes: 14 additions & 0 deletions keyless/pepper/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,28 @@ rust-version = { workspace = true }

[dependencies]
anyhow = { workspace = true }
aptos-crypto = { workspace = true }
aptos-crypto-derive = { workspace = true }
aptos-dkg = { workspace = true }
aptos-types = { workspace = true }
ark-bls12-381 = { workspace = true }
ark-ec = { workspace = true }
ark-ff = { workspace = true }
ark-serialize = { workspace = true }
ark-std = { workspace = true }
bcs = { workspace = true }
blstrs = { workspace = true }
derivation-path = { workspace = true }
ed25519-dalek = { workspace = true }
hex = { workspace = true }
hmac = { workspace = true }
jsonwebtoken = { workspace = true }
once_cell = { workspace = true }
rand = { workspace = true }
regex = { workspace = true }
serde = { workspace = true }
serde-big-array = { workspace = true }
sha2_0_10_6 = { workspace = true }

[package.metadata.cargo-machete]
ignored = ["bcs"]
12 changes: 12 additions & 0 deletions keyless/pepper/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ pub struct PepperRequest {
pub epk_blinder: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uid_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub derivation_path: Option<String>,
}

/// The response to `PepperRequest`, which contains either the pepper or a processing error.
Expand All @@ -78,6 +80,16 @@ pub struct PepperResponse {
deserialize_with = "deserialize_bytes_from_hex"
)]
pub signature: Vec<u8>, // unique BLS signature
#[serde(
serialize_with = "serialize_bytes_to_hex",
deserialize_with = "deserialize_bytes_from_hex"
)]
pub pepper: Vec<u8>,
#[serde(
serialize_with = "serialize_bytes_to_hex",
deserialize_with = "deserialize_bytes_from_hex"
)]
pub address: Vec<u8>,
}

/// A pepper scheme where:
Expand Down
41 changes: 40 additions & 1 deletion keyless/pepper/common/src/vuf/bls12381_g1_bls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

use crate::vuf::VUF;
use anyhow::{anyhow, ensure};
use aptos_crypto::hash::CryptoHash;
use aptos_crypto_derive::{BCSCryptoHash, CryptoHasher};
use aptos_dkg::utils::multi_pairing;
use aptos_types::keyless::Pepper;
use ark_bls12_381::{Bls12_381, Fq12, Fr, G1Affine, G2Affine, G2Projective};
use ark_ec::{
hashing::HashToCurve, pairing::Pairing, short_weierstrass::Projective, AffineRepr, CurveGroup,
Expand All @@ -14,11 +18,46 @@ use ark_std::{
rand::{CryptoRng, RngCore},
UniformRand,
};
use blstrs::{self, Compress};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use serde_big_array::BigArray;
use std::ops::Mul;

pub struct Bls12381G1Bls {}

pub static DST: &[u8] = b"APTOS_PEPPER_SERVICE_BLS12381_VUF_DST";
pub static DST: &[u8] = b"APTOS_PEPPER_BLS12381_VUF_DST";

pub static PINKAS_DST: &[u8] = b"APTOS_PINKAS_PEPPER_DST";

pub static PINKAS_SECRET_KEY_BASE_SEED: &[u8] = b"APTOS_PINKAS_PEPPER_SECRET_KEY_BASE_SEED";

pub static PINKAS_SECRET_KEY_BASE_G2: Lazy<blstrs::G2Projective> =
Lazy::new(|| blstrs::G2Projective::hash_to_curve(PINKAS_SECRET_KEY_BASE_SEED, PINKAS_DST, b""));

#[derive(Serialize, Deserialize, CryptoHasher, BCSCryptoHash)]
pub struct PinkasPepper {
#[serde(with = "BigArray")]
pub bytes: [u8; 288],
}

impl PinkasPepper {
pub fn from_affine_bytes(input: &[u8]) -> anyhow::Result<PinkasPepper> {
let g1 = blstrs::G1Projective::from_compressed(&input[0..48].try_into()?).unwrap();
let g2 = *PINKAS_SECRET_KEY_BASE_G2;
let pairing = multi_pairing([g1].iter(), [g2].iter());
let mut output: Vec<u8> = vec![];
pairing.write_compressed(&mut output)?;
Ok(PinkasPepper {
bytes: output[0..288].try_into()?,
})
}

pub fn to_master_pepper(&self) -> Pepper {
let bytes = CryptoHash::hash(self).to_vec();
Pepper::new(bytes[0..31].try_into().unwrap())
}
}

impl Bls12381G1Bls {
fn hash_to_g1(input: &[u8]) -> G1Affine {
Expand Down
1 change: 1 addition & 0 deletions keyless/pepper/common/src/vuf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ pub trait VUF {
///
/// TODO: better name?
pub mod bls12381_g1_bls;
pub mod slip_10;
186 changes: 186 additions & 0 deletions keyless/pepper/common/src/vuf/slip_10.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright (c) Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

pub extern crate derivation_path;
pub extern crate ed25519_dalek;

use anyhow::{bail, Result};
use aptos_types::keyless::Pepper;
use core::fmt;
pub use derivation_path::{ChildIndex, DerivationPath};
pub use ed25519_dalek::{PublicKey, SecretKey};
use hmac::{Hmac, Mac};
use regex::Regex;
use sha2_0_10_6::Sha512;
use std::str::FromStr;

const PEPPER_SLIP_10_NAME: &str = "32 bytes";

/// Errors thrown while deriving secret keys
#[derive(Debug)]
pub enum Error {
Ed25519,
ExpectedHardenedIndex(ChildIndex),
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Ed25519 => f.write_str("ed25519 error"),
Self::ExpectedHardenedIndex(index) => {
f.write_fmt(format_args!("expected hardened child index: {}", index))
},
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for Error {}

/// An expanded pepper with chain code and meta data
#[derive(Debug)]
pub struct ExtendedPepper {
/// How many derivations this key is from the root (0 for root)
pub depth: u8,
/// Child index of the key used to derive from parent (`Normal(0)` for root)
pub child_index: ChildIndex,
/// 32 byte extended Pepper. Not exposed as get_pepper() should be used to get the 31 byte version
pepper: [u8; 32],
/// Chain code
pub chain_code: [u8; 32],
}

type HmacSha512 = Hmac<Sha512>;

/// A convenience wrapper for a [`core::result::Result`] with an [`Error`]
// pub type Result<T, E = Error> = core::result::Result<T, E>;

pub fn get_aptos_derivation_path(s: &str) -> Result<DerivationPath> {
let re = Regex::new(r"^m\/44'\/637'\/[0-9]+'\/[0-9]+'\/[0-9]+'?$").unwrap();
if re.is_match(s) {
println!("Valid path");
} else {
bail!(format!("Invalid derivation path: {}", s))
}
Ok(DerivationPath::from_str(s)?)
}

impl ExtendedPepper {
pub fn get_pepper(&self) -> Pepper {
let mut pepper = [0; 31];
pepper.copy_from_slice(&self.pepper[..31]);
Pepper::new(pepper[0..31].try_into().unwrap())
}

/// Create a new extended secret key from a seed
pub fn from_seed(seed: &[u8]) -> Result<Self> {
let mut mac = HmacSha512::new_from_slice(PEPPER_SLIP_10_NAME.as_ref()).unwrap();
mac.update(seed);
let bytes = mac.finalize().into_bytes();

let mut pepper = [0; 32];
pepper.copy_from_slice(&bytes[..32]);
let mut chain_code = [0; 32];
chain_code.copy_from_slice(&bytes[32..]);

Ok(Self {
depth: 0,
child_index: ChildIndex::Normal(0),
pepper,
chain_code,
})
}

/// Derive an extended secret key fom the current using a derivation path
pub fn derive<P: AsRef<[ChildIndex]>>(&self, path: &P) -> Result<Self> {
let mut path = path.as_ref().iter();
let mut next = match path.next() {
Some(index) => self.derive_child(*index)?,
None => self.clone(),
};
for index in path {
next = next.derive_child(*index)?;
}
Ok(next)
}

/// Derive a child extended secret key with an index
pub fn derive_child(&self, index: ChildIndex) -> Result<Self> {
if index.is_normal() {
bail!(format!("expected hardened child index: {}", index))
}

let mut mac = HmacSha512::new_from_slice(&self.chain_code).unwrap();
mac.update(&[0u8]);
mac.update(self.pepper.as_ref());
mac.update(index.to_bits().to_be_bytes().as_ref());
let bytes = mac.finalize().into_bytes();

let mut pepper = [0; 32];
pepper.copy_from_slice(&bytes[..32]);
let mut chain_code = [0; 32];
chain_code.copy_from_slice(&bytes[32..]);

Ok(Self {
depth: self.depth + 1,
child_index: index,
pepper,
chain_code,
})
}

#[inline]
fn clone(&self) -> Self {
Self {
depth: self.depth,
child_index: self.child_index,
pepper: self.pepper,
chain_code: self.chain_code,
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_pepper_derivation() {
let derive_path = "m/44'/637'/0'/0'/0'";
let checked_derivation_path = get_aptos_derivation_path(derive_path).unwrap();
let master_pepper = "9b543408c1a90aac54e5130e61c7fbc30994d86aea62782b477448e585d194";
let derived_pepper =
ExtendedPepper::from_seed(hex::decode(master_pepper).unwrap().as_slice())
.unwrap()
.derive(&checked_derivation_path)
.unwrap()
.get_pepper();

let pepper_hex = "06f449a8c833c95ddcbfe345541ada065667c4e2e25030f88380bac26f30844f";
let expected_pepper =
Pepper::new(hex::decode(pepper_hex).unwrap()[0..31].try_into().unwrap());
println!("expected: {:?}", hex::encode(expected_pepper.to_bytes()));
println!("actual: {:?}", hex::encode(derived_pepper.to_bytes()));
assert_eq!(expected_pepper, derived_pepper);
}

#[test]
fn test_pepper_derivation_second_account() {
let derive_path = "m/44'/637'/1'/0'/0'";
let checked_derivation_path = get_aptos_derivation_path(derive_path).unwrap();
let master_pepper = "9b543408c1a90aac54e5130e61c7fbc30994d86aea62782b477448e585d194";
let derived_pepper =
ExtendedPepper::from_seed(hex::decode(master_pepper).unwrap().as_slice())
.unwrap()
.derive(&checked_derivation_path)
.unwrap()
.get_pepper();

let pepper_hex = "81d5647372c2762fd50993f7556025211768e6ac72dbc7294ad462d447c161f2";
let expected_pepper =
Pepper::new(hex::decode(pepper_hex).unwrap()[0..31].try_into().unwrap());
println!("expected: {:?}", hex::encode(expected_pepper.to_bytes()));
println!("actual: {:?}", hex::encode(derived_pepper.to_bytes()));
assert_eq!(expected_pepper, derived_pepper);
}
}
Loading

0 comments on commit 5fac1ad

Please sign in to comment.