Skip to content

Commit

Permalink
Add pRPC AllowHandoverTo
Browse files Browse the repository at this point in the history
  • Loading branch information
kvinwang committed Jan 5, 2024
1 parent 33372c1 commit e9f1603
Show file tree
Hide file tree
Showing 15 changed files with 247 additions and 71 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/phactory/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ ethers = "2.0.8"

hex-literal = "0.4.1"
secp256k1 = "0.28.0"
hex = { version = "0.4", default-features = false, features = ["alloc", "serde"] }

[dev-dependencies]
insta = "1.13.0"
Expand Down
7 changes: 7 additions & 0 deletions crates/phactory/api/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ fn main() {
] {
builder = builder.field_attribute(field, "#[serde(default)]");
}
for field in [
"AllowHandoverToRequest.measurement",
"SigInfo.pubkey",
"SigInfo.signature",
] {
builder = builder.field_attribute(field, "#[serde(with=\"hex::serde\")]");
}
builder
.compile(&["pruntime_rpc.proto"], &[render_dir])
.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion crates/phactory/api/proto
Submodule proto updated 1 files
+20 −0 pruntime_rpc.proto
91 changes: 47 additions & 44 deletions crates/phactory/api/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ impl From<CodecError> for SignatureVerifyError {
}
}

#[derive(Default, Clone, Encode, Decode, Debug)]
pub enum MessageType {
Certificate { ttl: u32 },
#[default]
ContractQuery,
}

Expand Down Expand Up @@ -216,49 +218,50 @@ impl CertificateBody {
sig_type: SignatureType,
signature: &[u8],
) -> Result<AccountId32, SignatureVerifyError> {
let signer = match sig_type {
SignatureType::Ed25519 => {
recover::<sp_core::ed25519::Pair>(&self.pubkey, signature, msg)?.into()
}
SignatureType::Sr25519 => {
recover::<sp_core::sr25519::Pair>(&self.pubkey, signature, msg)?.into()
}
SignatureType::Ecdsa => sp_core::blake2_256(
recover::<sp_core::ecdsa::Pair>(&self.pubkey, signature, msg)?.as_ref(),
)
.into(),
SignatureType::Ed25519WrapBytes => {
let wrapped = wrap_bytes(msg);
recover::<sp_core::ed25519::Pair>(&self.pubkey, signature, &wrapped)?.into()
}
SignatureType::Sr25519WrapBytes => {
let wrapped = wrap_bytes(msg);
recover::<sp_core::sr25519::Pair>(&self.pubkey, signature, &wrapped)?.into()
}
SignatureType::EcdsaWrapBytes => {
let wrapped = wrap_bytes(msg);
sp_core::blake2_256(
recover::<sp_core::ecdsa::Pair>(&self.pubkey, signature, &wrapped)?.as_ref(),
)
.into()
}
SignatureType::Eip712 => {
account_id_from_evm_pubkey(eip712::recover(&self.pubkey, signature, msg, msg_type)?)
}
SignatureType::EvmEcdsa => account_id_from_evm_pubkey(recover::<sp_core::ecdsa::Pair>(
&self.pubkey,
signature,
msg,
)?),
SignatureType::EvmEcdsaWrapBytes => {
let wrapped = wrap_bytes(msg);
account_id_from_evm_pubkey(recover::<sp_core::ecdsa::Pair>(
&self.pubkey,
signature,
&wrapped,
)?)
}
};
Ok(signer)
recover_signer_account(&self.pubkey, msg, msg_type, sig_type, signature)
}
}

pub fn recover_signer_account(
pubkey: &[u8],
msg: &[u8],
msg_type: MessageType,
sig_type: SignatureType,
signature: &[u8],
) -> Result<AccountId32, SignatureVerifyError> {
use account_id_from_evm_pubkey as evm_account;
let signer = match sig_type {
SignatureType::Ed25519 => recover::<sp_core::ed25519::Pair>(pubkey, signature, msg)?.into(),
SignatureType::Sr25519 => recover::<sp_core::sr25519::Pair>(pubkey, signature, msg)?.into(),
SignatureType::Ecdsa => {
sp_core::blake2_256(recover::<sp_core::ecdsa::Pair>(pubkey, signature, msg)?.as_ref())
.into()
}
SignatureType::Ed25519WrapBytes => {
let wrapped = wrap_bytes(msg);
recover::<sp_core::ed25519::Pair>(pubkey, signature, &wrapped)?.into()
}
SignatureType::Sr25519WrapBytes => {
let wrapped = wrap_bytes(msg);
recover::<sp_core::sr25519::Pair>(pubkey, signature, &wrapped)?.into()
}
SignatureType::EcdsaWrapBytes => {
let wrapped = wrap_bytes(msg);
sp_core::blake2_256(
recover::<sp_core::ecdsa::Pair>(pubkey, signature, &wrapped)?.as_ref(),
)
.into()
}
SignatureType::Eip712 => evm_account(eip712::recover(pubkey, signature, msg, msg_type)?),
SignatureType::EvmEcdsa => {
evm_account(recover::<sp_core::ecdsa::Pair>(pubkey, signature, msg)?)
}
SignatureType::EvmEcdsaWrapBytes => {
let wrapped = wrap_bytes(msg);
evm_account(recover::<sp_core::ecdsa::Pair>(
pubkey, signature, &wrapped,
)?)
}
};
Ok(signer)
}
5 changes: 5 additions & 0 deletions crates/phactory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,10 @@ pub struct Phactory<Platform> {

#[serde(skip)]
pub(crate) cluster_state_to_apply: Option<ClusterState<'static>>,

/// The pRuntime measurement that allowed by the Council.
#[serde(skip)]
allow_handover_to: Option<Vec<u8>>,
}

#[derive(Serialize, Deserialize, Clone)]
Expand Down Expand Up @@ -310,6 +314,7 @@ impl<Platform: pal::Platform> Phactory<Platform> {
pending_effects: Vec::new(),
started_at: Instant::now(),
cluster_state_to_apply: None,
allow_handover_to: None,
}
}

Expand Down
101 changes: 77 additions & 24 deletions crates/phactory/src/prpc_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ impl<Platform: pal::Platform + Serialize + DeserializeOwned> Phactory<Platform>
),
"dispatch_block",
);

self.allow_handover_to = None;

let counters = self.runtime_state()?.storage_synchronizer.counters();
blocks.retain(|b| b.block_header.number >= counters.next_block_number);

Expand Down Expand Up @@ -381,7 +384,7 @@ impl<Platform: pal::Platform + Serialize + DeserializeOwned> Phactory<Platform>
let chain_storage = ChainStorage::from_pairs(genesis_state.into_iter());
let para_id = chain_storage.para_id();
info!(
"Genesis state loaded: root={:?}, para_id={para_id}",
"Genesis state loaded: root={:?}, para_id={para_id}, genesis_hash={genesis_block_hash:?}",
chain_storage.root()
);

Expand Down Expand Up @@ -1574,24 +1577,7 @@ impl<Platform: pal::Platform + Serialize + DeserializeOwned> PhactoryApi for Rpc
}
// 5. verify pruntime launch date, never handover to old pruntime
if !dev_mode && in_sgx {
let my_la_report = {
// target_info and reportdata not important, we just need the report metadata
let target_info =
sgx_api_lite::target_info().expect("should not fail in SGX; qed.");
sgx_api_lite::report(&target_info, &[0; 64])
.map_err(|_| from_display("Cannot read server pRuntime info"))?
};
let my_runtime_hash = {
let ias_fields = IasFields {
mr_enclave: my_la_report.body.mr_enclave.m,
mr_signer: my_la_report.body.mr_signer.m,
isv_prod_id: my_la_report.body.isv_prod_id.to_ne_bytes(),
isv_svn: my_la_report.body.isv_svn.to_ne_bytes(),
report_data: [0; 64],
confidence_level: 0,
};
ias_fields.extend_mrenclave()
};
let my_runtime_hash = my_measurement()?;
let runtime_state = phactory.runtime_state()?;
let my_runtime_timestamp = runtime_state
.chain_storage
Expand All @@ -1611,13 +1597,15 @@ impl<Platform: pal::Platform + Serialize + DeserializeOwned> PhactoryApi for Rpc
ias_fields.extend_mrenclave()
}
};
let req_runtime_timestamp = runtime_state
if let Some(req_runtime_timestamp) = runtime_state
.chain_storage
.get_pruntime_added_at(&runtime_hash)
.ok_or_else(|| from_display("Client pRuntime not allowed on chain"))?;

if my_runtime_timestamp >= req_runtime_timestamp {
return Err(from_display("No handover for old pRuntime"));
{
if my_runtime_timestamp >= req_runtime_timestamp {
return Err(from_display("No handover for old pRuntime"));
}
} else if phactory.allow_handover_to != Some(runtime_hash) {
return Err(from_display("Client pRuntime not allowed on chain"));
}
} else {
info!("Skip pRuntime timestamp check in dev mode");
Expand Down Expand Up @@ -2004,4 +1992,69 @@ impl<Platform: pal::Platform + Serialize + DeserializeOwned> PhactoryApi for Rpc
.load_cluster_state(&req.filename)
.map_err(from_debug)
}

async fn allow_handover_to(
&mut self,
request: pb::AllowHandoverToRequest,
) -> Result<(), prpc::server::Error> {
let mut phactory = self.lock_phactory(false, true)?;
let runtime_state = phactory.runtime_state()?;
let council_members = runtime_state.chain_storage.council_members();
if request.signatures.len() > council_members.len() {
return Err(from_display("Too many signatures"));
}
let genesis_hash = hex::encode(runtime_state.genesis_block_hash);
let mr_to = hex::encode(&request.measurement);
let mr_from = hex::encode(my_measurement()?);
let signed_message = format!("Allow pRuntime to handover\n from: 0x{mr_from}\n to: 0x{mr_to}\n genesis: 0x{genesis_hash}").into_bytes();
debug!("Signed message: {:?}", hex::encode(&signed_message));
let mut signers = std::collections::BTreeSet::new();
for sig in &request.signatures {
let sig_type = pb::SignatureType::from_i32(sig.signature_type)
.ok_or_else(|| from_display("Invalid signature type"))?;
let signer = crypto::recover_signer_account(
&sig.pubkey,
&signed_message,
Default::default(),
sig_type,
&sig.signature,
)
.map_err(|_| from_display("Invalid signature"))?;
if !council_members.contains(&signer) {
return Err(from_display("Not a council member"));
}
debug!("Signed by {signer:?}");
signers.insert(signer);
}
let percent = signers.len() * 100 / council_members.len();
// At least 7 of 8 members. 6/8 = 75%, 7/8 = 87.5%.
let threshold = 80;
if percent < threshold {
return Err(from_display("Not enough signatures"));
}
phactory.allow_handover_to = Some(request.measurement);
Ok(())
}
}

fn my_measurement() -> Result<Vec<u8>, RpcError> {
let my_la_report = {
// target_info and reportdata not important, we just need the report metadata
let target_info =
sgx_api_lite::target_info().or(Err(from_display("Failed to get SGX info")))?;
sgx_api_lite::report(&target_info, &[0; 64])
.or(Err(from_display("Cannot read server pRuntime info")))?
};
let mrenclave = {
let ias_fields = IasFields {
mr_enclave: my_la_report.body.mr_enclave.m,
mr_signer: my_la_report.body.mr_signer.m,
isv_prod_id: my_la_report.body.isv_prod_id.to_ne_bytes(),
isv_svn: my_la_report.body.isv_svn.to_ne_bytes(),
report_data: [0; 64],
confidence_level: 0,
};
ias_fields.extend_mrenclave()
};
Ok(mrenclave)
}
6 changes: 5 additions & 1 deletion crates/phactory/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl BlockValidator for LightValidation<chain::Runtime> {

mod storage_ext {
use crate::{chain, light_validation::utils::storage_prefix};
use chain::{pallet_computation, pallet_mq, pallet_phat, pallet_registry};
use chain::{pallet_computation, pallet_mq, pallet_phat, pallet_registry, AccountId};
use log::error;
use parity_scale_codec::{Decode, Error};
use phala_mq::{ContractClusterId, Message, MessageOrigin};
Expand Down Expand Up @@ -207,5 +207,9 @@ mod storage_ext {
) -> Option<ContractClusterId> {
self.execute_with(|| pallet_phat::ClusterByWorkers::<chain::Runtime>::get(worker))
}

pub(crate) fn council_members(&self) -> Vec<AccountId> {
self.execute_with(chain::Council::members)
}
}
}
45 changes: 45 additions & 0 deletions docs/pruntime-handover-by-council.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
PR https://github.com/Phala-Network/phala-blockchain/pull/1500

Suppose we have two version of pRuntime A and B, where A is stucked, and we want to force handover to B.

SGX MR of A: 0x10c24c0e6bf8a86634417fcd8f934e62439c62907a6f1bc726906a50b054ddf10000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e
SGX MR of B: 0xf42f7e095735702d1d3c6ac5fa3b4581d3c3673d3c5ce261a43fe782ccb3e1dc0000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e
Genisis block hash: 0x0a15d23307d533d581291ff6dedca9ca10927c7dff6f4df9e8c3bf00bc5a6ded (Can be found in prpc::get_info)

Then the steps would be:

1. Ask at least half of the council members to sign a message as below:
```
Allow pRuntime to handover
from: 0x10c24c0e6bf8a86634417fcd8f934e62439c62907a6f1bc726906a50b054ddf10000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e
to: 0xf42f7e095735702d1d3c6ac5fa3b4581d3c3673d3c5ce261a43fe782ccb3e1dc0000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e
genesis: 0x0a15d23307d533d581291ff6dedca9ca10927c7dff6f4df9e8c3bf00bc5a6ded
```
See https://files.kvin.wang:8443/signit/ for an example

2. Collect the signatures and assamble them into a rpc request like this:
```
$ cat sigs.json
{
"measurement": "f42f7e095735702d1d3c6ac5fa3b4581d3c3673d3c5ce261a43fe782ccb3e1dc0000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e",
"signatures": [
{
"signature": "fe6eeb25c088975df9bd136cc29c01a1b0bec3c4a58027efd7ca2908b233983c908a7159b81e265948a45e2c9129560f96aef24b93612f1dd4fc9aa40880ff88",
"signature_type": 4,
"pubkey": "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"
},
{
"signature": "22591a9f308e9d1a2af2ad103334cf8ab3674a2dab9e9a6372cf1e09c8671066668ed90af1c88ad7c5c280b8e5dfb043402774cf59e38d312ee107bd8aee2f8c",
"signature_type": 4,
"pubkey": "8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"
}
]
}
```
3. Load the sigs.json to pruntime A
```
$ curl -d @sigs.json localhost:8000/prpc/PhactoryAPI.AllowHandoverTo?json
```
**Note**: Don't sync any chain state to the pruntime A after this step, otherwise the handover will be rejected.
4. Run a new pruntime B instance to start the handover
`$ ./gramine-sgx pruntime --request-handover-from http://localhost:8000`
Loading

0 comments on commit e9f1603

Please sign in to comment.