Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pRPC AllowHandoverTo #1500

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -36,6 +36,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 @@ -41,6 +41,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
+21 −1 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)
}
6 changes: 6 additions & 0 deletions crates/phactory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,11 @@ pub struct Phactory<Platform> {
#[serde(skip)]
#[serde(default = "sidevm_helper::create_sidevm_service_default")]
sidevm_spawner: sidevm::service::Spawner,

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

mod sidevm_helper {
Expand Down Expand Up @@ -409,6 +414,7 @@ impl<Platform: pal::Platform> Phactory<Platform> {
args.cores as _,
create_sidevm_outgoing_channel(weak_self),
),
allow_handover_to: None,
};
me.init(args);
me
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 @@ -263,6 +263,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 @@ -389,7 +392,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 @@ -1592,24 +1595,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 @@ -1633,13 +1619,15 @@ impl<Platform: pal::Platform + Serialize + DeserializeOwned> PhactoryApi for Rpc
collateral: _,
} => todo!(),
};
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 @@ -2046,4 +2034,69 @@ impl<Platform: pal::Platform + Serialize + DeserializeOwned> PhactoryApi for Rpc
cluster.on_idle(block_number);
Ok(())
}

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;
use chain::{pallet_computation, pallet_mq, pallet_phat, pallet_registry};
use chain::{pallet_computation, pallet_mq, pallet_phat, pallet_registry, AccountId};
use phala_mq::{ContractClusterId, Message, MessageOrigin};
use phala_trie_storage::TrieStorage;
use phala_types::messaging::TokenomicParameters;
Expand Down Expand Up @@ -178,5 +178,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
Loading