Skip to content

Commit e9f1603

Browse files
committed
Add pRPC AllowHandoverTo
1 parent 33372c1 commit e9f1603

File tree

15 files changed

+247
-71
lines changed

15 files changed

+247
-71
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/phactory/api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ ethers = "2.0.8"
3939

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

4344
[dev-dependencies]
4445
insta = "1.13.0"

crates/phactory/api/build.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ fn main() {
3333
] {
3434
builder = builder.field_attribute(field, "#[serde(default)]");
3535
}
36+
for field in [
37+
"AllowHandoverToRequest.measurement",
38+
"SigInfo.pubkey",
39+
"SigInfo.signature",
40+
] {
41+
builder = builder.field_attribute(field, "#[serde(with=\"hex::serde\")]");
42+
}
3643
builder
3744
.compile(&["pruntime_rpc.proto"], &[render_dir])
3845
.unwrap();

crates/phactory/api/proto

crates/phactory/api/src/crypto.rs

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ impl From<CodecError> for SignatureVerifyError {
6161
}
6262
}
6363

64+
#[derive(Default, Clone, Encode, Decode, Debug)]
6465
pub enum MessageType {
6566
Certificate { ttl: u32 },
67+
#[default]
6668
ContractQuery,
6769
}
6870

@@ -216,49 +218,50 @@ impl CertificateBody {
216218
sig_type: SignatureType,
217219
signature: &[u8],
218220
) -> Result<AccountId32, SignatureVerifyError> {
219-
let signer = match sig_type {
220-
SignatureType::Ed25519 => {
221-
recover::<sp_core::ed25519::Pair>(&self.pubkey, signature, msg)?.into()
222-
}
223-
SignatureType::Sr25519 => {
224-
recover::<sp_core::sr25519::Pair>(&self.pubkey, signature, msg)?.into()
225-
}
226-
SignatureType::Ecdsa => sp_core::blake2_256(
227-
recover::<sp_core::ecdsa::Pair>(&self.pubkey, signature, msg)?.as_ref(),
228-
)
229-
.into(),
230-
SignatureType::Ed25519WrapBytes => {
231-
let wrapped = wrap_bytes(msg);
232-
recover::<sp_core::ed25519::Pair>(&self.pubkey, signature, &wrapped)?.into()
233-
}
234-
SignatureType::Sr25519WrapBytes => {
235-
let wrapped = wrap_bytes(msg);
236-
recover::<sp_core::sr25519::Pair>(&self.pubkey, signature, &wrapped)?.into()
237-
}
238-
SignatureType::EcdsaWrapBytes => {
239-
let wrapped = wrap_bytes(msg);
240-
sp_core::blake2_256(
241-
recover::<sp_core::ecdsa::Pair>(&self.pubkey, signature, &wrapped)?.as_ref(),
242-
)
243-
.into()
244-
}
245-
SignatureType::Eip712 => {
246-
account_id_from_evm_pubkey(eip712::recover(&self.pubkey, signature, msg, msg_type)?)
247-
}
248-
SignatureType::EvmEcdsa => account_id_from_evm_pubkey(recover::<sp_core::ecdsa::Pair>(
249-
&self.pubkey,
250-
signature,
251-
msg,
252-
)?),
253-
SignatureType::EvmEcdsaWrapBytes => {
254-
let wrapped = wrap_bytes(msg);
255-
account_id_from_evm_pubkey(recover::<sp_core::ecdsa::Pair>(
256-
&self.pubkey,
257-
signature,
258-
&wrapped,
259-
)?)
260-
}
261-
};
262-
Ok(signer)
221+
recover_signer_account(&self.pubkey, msg, msg_type, sig_type, signature)
263222
}
264223
}
224+
225+
pub fn recover_signer_account(
226+
pubkey: &[u8],
227+
msg: &[u8],
228+
msg_type: MessageType,
229+
sig_type: SignatureType,
230+
signature: &[u8],
231+
) -> Result<AccountId32, SignatureVerifyError> {
232+
use account_id_from_evm_pubkey as evm_account;
233+
let signer = match sig_type {
234+
SignatureType::Ed25519 => recover::<sp_core::ed25519::Pair>(pubkey, signature, msg)?.into(),
235+
SignatureType::Sr25519 => recover::<sp_core::sr25519::Pair>(pubkey, signature, msg)?.into(),
236+
SignatureType::Ecdsa => {
237+
sp_core::blake2_256(recover::<sp_core::ecdsa::Pair>(pubkey, signature, msg)?.as_ref())
238+
.into()
239+
}
240+
SignatureType::Ed25519WrapBytes => {
241+
let wrapped = wrap_bytes(msg);
242+
recover::<sp_core::ed25519::Pair>(pubkey, signature, &wrapped)?.into()
243+
}
244+
SignatureType::Sr25519WrapBytes => {
245+
let wrapped = wrap_bytes(msg);
246+
recover::<sp_core::sr25519::Pair>(pubkey, signature, &wrapped)?.into()
247+
}
248+
SignatureType::EcdsaWrapBytes => {
249+
let wrapped = wrap_bytes(msg);
250+
sp_core::blake2_256(
251+
recover::<sp_core::ecdsa::Pair>(pubkey, signature, &wrapped)?.as_ref(),
252+
)
253+
.into()
254+
}
255+
SignatureType::Eip712 => evm_account(eip712::recover(pubkey, signature, msg, msg_type)?),
256+
SignatureType::EvmEcdsa => {
257+
evm_account(recover::<sp_core::ecdsa::Pair>(pubkey, signature, msg)?)
258+
}
259+
SignatureType::EvmEcdsaWrapBytes => {
260+
let wrapped = wrap_bytes(msg);
261+
evm_account(recover::<sp_core::ecdsa::Pair>(
262+
pubkey, signature, &wrapped,
263+
)?)
264+
}
265+
};
266+
Ok(signer)
267+
}

crates/phactory/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,10 @@ pub struct Phactory<Platform> {
270270

271271
#[serde(skip)]
272272
pub(crate) cluster_state_to_apply: Option<ClusterState<'static>>,
273+
274+
/// The pRuntime measurement that allowed by the Council.
275+
#[serde(skip)]
276+
allow_handover_to: Option<Vec<u8>>,
273277
}
274278

275279
#[derive(Serialize, Deserialize, Clone)]
@@ -310,6 +314,7 @@ impl<Platform: pal::Platform> Phactory<Platform> {
310314
pending_effects: Vec::new(),
311315
started_at: Instant::now(),
312316
cluster_state_to_apply: None,
317+
allow_handover_to: None,
313318
}
314319
}
315320

crates/phactory/src/prpc_service.rs

Lines changed: 77 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,9 @@ impl<Platform: pal::Platform + Serialize + DeserializeOwned> Phactory<Platform>
258258
),
259259
"dispatch_block",
260260
);
261+
262+
self.allow_handover_to = None;
263+
261264
let counters = self.runtime_state()?.storage_synchronizer.counters();
262265
blocks.retain(|b| b.block_header.number >= counters.next_block_number);
263266

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

@@ -1574,24 +1577,7 @@ impl<Platform: pal::Platform + Serialize + DeserializeOwned> PhactoryApi for Rpc
15741577
}
15751578
// 5. verify pruntime launch date, never handover to old pruntime
15761579
if !dev_mode && in_sgx {
1577-
let my_la_report = {
1578-
// target_info and reportdata not important, we just need the report metadata
1579-
let target_info =
1580-
sgx_api_lite::target_info().expect("should not fail in SGX; qed.");
1581-
sgx_api_lite::report(&target_info, &[0; 64])
1582-
.map_err(|_| from_display("Cannot read server pRuntime info"))?
1583-
};
1584-
let my_runtime_hash = {
1585-
let ias_fields = IasFields {
1586-
mr_enclave: my_la_report.body.mr_enclave.m,
1587-
mr_signer: my_la_report.body.mr_signer.m,
1588-
isv_prod_id: my_la_report.body.isv_prod_id.to_ne_bytes(),
1589-
isv_svn: my_la_report.body.isv_svn.to_ne_bytes(),
1590-
report_data: [0; 64],
1591-
confidence_level: 0,
1592-
};
1593-
ias_fields.extend_mrenclave()
1594-
};
1580+
let my_runtime_hash = my_measurement()?;
15951581
let runtime_state = phactory.runtime_state()?;
15961582
let my_runtime_timestamp = runtime_state
15971583
.chain_storage
@@ -1611,13 +1597,15 @@ impl<Platform: pal::Platform + Serialize + DeserializeOwned> PhactoryApi for Rpc
16111597
ias_fields.extend_mrenclave()
16121598
}
16131599
};
1614-
let req_runtime_timestamp = runtime_state
1600+
if let Some(req_runtime_timestamp) = runtime_state
16151601
.chain_storage
16161602
.get_pruntime_added_at(&runtime_hash)
1617-
.ok_or_else(|| from_display("Client pRuntime not allowed on chain"))?;
1618-
1619-
if my_runtime_timestamp >= req_runtime_timestamp {
1620-
return Err(from_display("No handover for old pRuntime"));
1603+
{
1604+
if my_runtime_timestamp >= req_runtime_timestamp {
1605+
return Err(from_display("No handover for old pRuntime"));
1606+
}
1607+
} else if phactory.allow_handover_to != Some(runtime_hash) {
1608+
return Err(from_display("Client pRuntime not allowed on chain"));
16211609
}
16221610
} else {
16231611
info!("Skip pRuntime timestamp check in dev mode");
@@ -2004,4 +1992,69 @@ impl<Platform: pal::Platform + Serialize + DeserializeOwned> PhactoryApi for Rpc
20041992
.load_cluster_state(&req.filename)
20051993
.map_err(from_debug)
20061994
}
1995+
1996+
async fn allow_handover_to(
1997+
&mut self,
1998+
request: pb::AllowHandoverToRequest,
1999+
) -> Result<(), prpc::server::Error> {
2000+
let mut phactory = self.lock_phactory(false, true)?;
2001+
let runtime_state = phactory.runtime_state()?;
2002+
let council_members = runtime_state.chain_storage.council_members();
2003+
if request.signatures.len() > council_members.len() {
2004+
return Err(from_display("Too many signatures"));
2005+
}
2006+
let genesis_hash = hex::encode(runtime_state.genesis_block_hash);
2007+
let mr_to = hex::encode(&request.measurement);
2008+
let mr_from = hex::encode(my_measurement()?);
2009+
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();
2010+
debug!("Signed message: {:?}", hex::encode(&signed_message));
2011+
let mut signers = std::collections::BTreeSet::new();
2012+
for sig in &request.signatures {
2013+
let sig_type = pb::SignatureType::from_i32(sig.signature_type)
2014+
.ok_or_else(|| from_display("Invalid signature type"))?;
2015+
let signer = crypto::recover_signer_account(
2016+
&sig.pubkey,
2017+
&signed_message,
2018+
Default::default(),
2019+
sig_type,
2020+
&sig.signature,
2021+
)
2022+
.map_err(|_| from_display("Invalid signature"))?;
2023+
if !council_members.contains(&signer) {
2024+
return Err(from_display("Not a council member"));
2025+
}
2026+
debug!("Signed by {signer:?}");
2027+
signers.insert(signer);
2028+
}
2029+
let percent = signers.len() * 100 / council_members.len();
2030+
// At least 7 of 8 members. 6/8 = 75%, 7/8 = 87.5%.
2031+
let threshold = 80;
2032+
if percent < threshold {
2033+
return Err(from_display("Not enough signatures"));
2034+
}
2035+
phactory.allow_handover_to = Some(request.measurement);
2036+
Ok(())
2037+
}
2038+
}
2039+
2040+
fn my_measurement() -> Result<Vec<u8>, RpcError> {
2041+
let my_la_report = {
2042+
// target_info and reportdata not important, we just need the report metadata
2043+
let target_info =
2044+
sgx_api_lite::target_info().or(Err(from_display("Failed to get SGX info")))?;
2045+
sgx_api_lite::report(&target_info, &[0; 64])
2046+
.or(Err(from_display("Cannot read server pRuntime info")))?
2047+
};
2048+
let mrenclave = {
2049+
let ias_fields = IasFields {
2050+
mr_enclave: my_la_report.body.mr_enclave.m,
2051+
mr_signer: my_la_report.body.mr_signer.m,
2052+
isv_prod_id: my_la_report.body.isv_prod_id.to_ne_bytes(),
2053+
isv_svn: my_la_report.body.isv_svn.to_ne_bytes(),
2054+
report_data: [0; 64],
2055+
confidence_level: 0,
2056+
};
2057+
ias_fields.extend_mrenclave()
2058+
};
2059+
Ok(mrenclave)
20072060
}

crates/phactory/src/storage.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ impl BlockValidator for LightValidation<chain::Runtime> {
3636

3737
mod storage_ext {
3838
use crate::{chain, light_validation::utils::storage_prefix};
39-
use chain::{pallet_computation, pallet_mq, pallet_phat, pallet_registry};
39+
use chain::{pallet_computation, pallet_mq, pallet_phat, pallet_registry, AccountId};
4040
use log::error;
4141
use parity_scale_codec::{Decode, Error};
4242
use phala_mq::{ContractClusterId, Message, MessageOrigin};
@@ -207,5 +207,9 @@ mod storage_ext {
207207
) -> Option<ContractClusterId> {
208208
self.execute_with(|| pallet_phat::ClusterByWorkers::<chain::Runtime>::get(worker))
209209
}
210+
211+
pub(crate) fn council_members(&self) -> Vec<AccountId> {
212+
self.execute_with(chain::Council::members)
213+
}
210214
}
211215
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
PR https://github.com/Phala-Network/phala-blockchain/pull/1500
2+
3+
Suppose we have two version of pRuntime A and B, where A is stucked, and we want to force handover to B.
4+
5+
SGX MR of A: 0x10c24c0e6bf8a86634417fcd8f934e62439c62907a6f1bc726906a50b054ddf10000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e
6+
SGX MR of B: 0xf42f7e095735702d1d3c6ac5fa3b4581d3c3673d3c5ce261a43fe782ccb3e1dc0000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e
7+
Genisis block hash: 0x0a15d23307d533d581291ff6dedca9ca10927c7dff6f4df9e8c3bf00bc5a6ded (Can be found in prpc::get_info)
8+
9+
Then the steps would be:
10+
11+
1. Ask at least half of the council members to sign a message as below:
12+
```
13+
Allow pRuntime to handover
14+
from: 0x10c24c0e6bf8a86634417fcd8f934e62439c62907a6f1bc726906a50b054ddf10000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e
15+
to: 0xf42f7e095735702d1d3c6ac5fa3b4581d3c3673d3c5ce261a43fe782ccb3e1dc0000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e
16+
genesis: 0x0a15d23307d533d581291ff6dedca9ca10927c7dff6f4df9e8c3bf00bc5a6ded
17+
```
18+
See https://files.kvin.wang:8443/signit/ for an example
19+
20+
2. Collect the signatures and assamble them into a rpc request like this:
21+
```
22+
$ cat sigs.json
23+
{
24+
"measurement": "f42f7e095735702d1d3c6ac5fa3b4581d3c3673d3c5ce261a43fe782ccb3e1dc0000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e",
25+
"signatures": [
26+
{
27+
"signature": "fe6eeb25c088975df9bd136cc29c01a1b0bec3c4a58027efd7ca2908b233983c908a7159b81e265948a45e2c9129560f96aef24b93612f1dd4fc9aa40880ff88",
28+
"signature_type": 4,
29+
"pubkey": "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"
30+
},
31+
{
32+
"signature": "22591a9f308e9d1a2af2ad103334cf8ab3674a2dab9e9a6372cf1e09c8671066668ed90af1c88ad7c5c280b8e5dfb043402774cf59e38d312ee107bd8aee2f8c",
33+
"signature_type": 4,
34+
"pubkey": "8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"
35+
}
36+
]
37+
}
38+
```
39+
3. Load the sigs.json to pruntime A
40+
```
41+
$ curl -d @sigs.json localhost:8000/prpc/PhactoryAPI.AllowHandoverTo?json
42+
```
43+
**Note**: Don't sync any chain state to the pruntime A after this step, otherwise the handover will be rejected.
44+
4. Run a new pruntime B instance to start the handover
45+
`$ ./gramine-sgx pruntime --request-handover-from http://localhost:8000`

0 commit comments

Comments
 (0)