diff --git a/Cargo.lock b/Cargo.lock index 9e2e8d7..5f1e085 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1234,14 +1234,13 @@ checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "client" -version = "0.0.6" +version = "0.0.7" dependencies = [ "anyhow", "bigdecimal", "clap", "client-interface", "common", - "hex", "reqwest", "sp-core 33.0.1", "sp-runtime 37.0.0", @@ -1256,11 +1255,10 @@ dependencies = [ [[package]] name = "client-interface" -version = "0.0.6" +version = "0.0.7" dependencies = [ "anyhow", "common", - "hex", "parity-scale-codec", "rand", "reqwest", @@ -1285,12 +1283,11 @@ checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "common" -version = "0.0.6" +version = "0.0.7" dependencies = [ "aws-nitro-enclaves-cose", "aws-nitro-enclaves-nsm-api", "flate2", - "hex", "openssl", "parity-scale-codec", "rand", @@ -1822,7 +1819,7 @@ dependencies = [ [[package]] name = "enclave" -version = "0.0.6" +version = "0.0.7" dependencies = [ "anyhow", "aws-nitro-enclaves-nsm-api", @@ -1844,7 +1841,7 @@ dependencies = [ [[package]] name = "enclave-interface" -version = "0.0.6" +version = "0.0.7" dependencies = [ "common", "nix 0.27.1", @@ -4615,7 +4612,7 @@ dependencies = [ [[package]] name = "service" -version = "0.0.6" +version = "0.0.7" dependencies = [ "anyhow", "aws-config", diff --git a/Cargo.toml b/Cargo.toml index 0a14d75..8b46495 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" [workspace.package] homepage = "https://projectglove.io/" repository = "https://github.com/projectglove/glove-monorepo/" -version = "0.0.6" +version = "0.0.7" [workspace.dependencies] anyhow = "1.0.86" @@ -40,7 +40,6 @@ cfg-if = "1.0.0" aws-nitro-enclaves-nsm-api = "0.4.0" aws-nitro-enclaves-cose = "0.5.2" openssl = "0.10.66" -hex = "0.4.3" sha2 = "0.10.8" flate2 = "1.0.30" aws-config = "1.5.4" diff --git a/README.md b/README.md index c20213e..91b5d4d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Enclave Image successfully created. { "Measurements": { "HashAlgorithm": "Sha384 { ... }", - "PCR0": "2c655d5ba7f35e9e5208aff0670b9bee257cd9994cb957100fd8c9b4aa693a1d1c67f430d28c4f62a5372fe96d417d29", + "PCR0": "34b6ef3d11a1bb37e6987af685803959e834805d13fada834f49dce15634926a3fa7c1fbc181665cc89de6d6dccd369d", ... } } @@ -45,7 +45,7 @@ instructions on how to audit and verify the enclave code. > [!NOTE] > The enclave measurement for the latest build is -> `2c655d5ba7f35e9e5208aff0670b9bee257cd9994cb957100fd8c9b4aa693a1d1c67f430d28c4f62a5372fe96d417d29`. +> `34b6ef3d11a1bb37e6987af685803959e834805d13fada834f49dce15634926a3fa7c1fbc181665cc89de6d6dccd369d`. # Glove mixing @@ -153,8 +153,8 @@ it points to the same network. #### `attestation_bundle` -The attestation bundle of the enclave the service is using. This is a hex-encoded string (without the `0x` prefix), -representing the [`AttestationBundle`](common/src/attestation.rs#L45) struct in +The attestation bundle of the enclave the service is using. This is a hex string representing the +[`AttestationBundle`](common/src/attestation.rs#L38) struct in [SCALE](https://docs.substrate.io/reference/scale-codec/) encoding. The attestation bundle is primarily used in Glove proofs when the enclave submits its mixed votes on-chain. It's @@ -171,8 +171,8 @@ The version of the Glove service. "proxy_account": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", "network_name": "rococo", "node_endpoint": "wss://rococo-rpc.polkadot.io", - "attestation_bundle": "6408de7737c59c238890533af25896a2c20608d8b380bb01029acb3927...", - "version": "0.0.4" + "attestation_bundle": "0x6408de7737c59c238890533af25896a2c20608d8b380bb01029acb3927...", + "version": "0.0.7" } ``` @@ -189,15 +189,15 @@ A JSON object with the following fields: #### `request` -[SCALE-encoded](https://docs.substrate.io/reference/scale-codec/) [`VoteRequest`](common/src/lib.rs#L36) struct as a -hex string (without the `0x` prefix). +[SCALE-encoded](https://docs.substrate.io/reference/scale-codec/) [`VoteRequest`](common/src/lib.rs#L57) as a +hex string. #### `signature` [SCALE-encoded](https://docs.substrate.io/reference/scale-codec/) -[`MultiSignature`](https://docs.rs/sp-runtime/latest/sp_runtime/enum.MultiSignature.html) as a hex string (without the -`0x` prefix). Signed by`VoteRequest.account`, the signature is of the `VoteRequest` in SCALE-encoded bytes, i.e. the -`request` field without the hex-encoding. +[`MultiSignature`](https://docs.rs/sp-runtime/latest/sp_runtime/enum.MultiSignature.html) as a hex string. The +signature is of the `VoteRequest` in SCALE-encoded bytes, i.e. the `request` field without the hex-encoding, signed by +`VoteRequest.account`. #### Example @@ -224,15 +224,15 @@ A JSON object with the following fields: #### `request` -[SCALE-encoded](https://docs.substrate.io/reference/scale-codec/) [`RemoveVoteRequest`](client-interface/src/lib.rs#L374) -struct as a hex string (without the `0x` prefix). +[SCALE-encoded](https://docs.substrate.io/reference/scale-codec/) [`RemoveVoteRequest`](client-interface/src/lib.rs#L416) +as a hex string. #### `signature` [SCALE-encoded](https://docs.substrate.io/reference/scale-codec/) -[`MultiSignature`](https://docs.rs/sp-runtime/latest/sp_runtime/enum.MultiSignature.html) as a hex string (without the -`0x` prefix). Signed by`RemoveVoteRequest.account`, the signature is of the `RemoveVoteRequest` in SCALE-encoded bytes, -i.e. the `request` field without the hex-encoding. +[`MultiSignature`](https://docs.rs/sp-runtime/latest/sp_runtime/enum.MultiSignature.html) as a hex string. The +signature is of the `RemoveVoteRequest` in SCALE-encoded bytes, i.e. the `request` field without the hex-encoding, +signed by `RemoveVoteRequest.account`, ### Response diff --git a/client-interface/Cargo.toml b/client-interface/Cargo.toml index b92d959..455e711 100644 --- a/client-interface/Cargo.toml +++ b/client-interface/Cargo.toml @@ -24,5 +24,4 @@ reqwest.workspace = true [dev-dependencies] serde_json.workspace = true -hex.workspace = true rand.workspace = true diff --git a/client-interface/src/lib.rs b/client-interface/src/lib.rs index d74918d..9d44773 100644 --- a/client-interface/src/lib.rs +++ b/client-interface/src/lib.rs @@ -11,7 +11,6 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use serde::{Deserialize, Serialize}; use sp_core::crypto::AccountId32; use sp_runtime::{MultiAddress, MultiSignature}; -use sp_runtime::traits::Verify; use ss58_registry::{Ss58AddressFormat, Ss58AddressFormatRegistry, Token}; use subxt::Error as SubxtError; use subxt::ext::scale_decode::DecodeAsType; @@ -23,8 +22,8 @@ use subxt_signer::SecretUri; use subxt_signer::sr25519; use tokio::sync::Mutex; +use common::{ExtrinsicLocation, verify_js_payload}; use common::attestation::AttestationBundle; -use common::ExtrinsicLocation; use metadata::proxy::events::ProxyExecuted; use metadata::referenda::storage::types::referendum_info_for::ReferendumInfoFor; use metadata::runtime_types::frame_support::traits::preimages::Bounded; @@ -408,7 +407,7 @@ pub struct SignedRemoveVoteRequest { impl SignedRemoveVoteRequest { pub fn verify(&self) -> bool { - self.signature.verify(&*self.request.encode(), &self.request.account) + verify_js_payload(&self.signature, &self.request, &self.request.account) } } @@ -428,6 +427,7 @@ mod tests { use rand::random; use serde_json::{json, Value}; use sp_core::{ed25519, Pair}; + use subxt_core::utils::to_hex; use subxt_signer::sr25519::dev; use common::attestation::{Attestation, AttestedData}; @@ -460,7 +460,7 @@ mod tests { "proxy_account": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", "network_name": "polkadot", "node_endpoint": "wss://polkadot.api.onfinality.io/public-ws", - "attestation_bundle": hex::encode(&service_info.attestation_bundle.encode()), + "attestation_bundle": to_hex(&service_info.attestation_bundle.encode()), "version": "1.0.0" }) ); diff --git a/client/Cargo.toml b/client/Cargo.toml index e93a096..1adfb5e 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -20,5 +20,4 @@ strum.workspace = true sp-core.workspace = true sp-runtime.workspace = true thiserror.workspace = true -hex.workspace = true ss58-registry.workspace = true diff --git a/client/src/main.rs b/client/src/main.rs index 369c853..df03e25 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -11,6 +11,8 @@ use sp_runtime::MultiSignature; use strum::Display; use subxt::error::DispatchError; use subxt::Error::Runtime; +use subxt_core::ext::sp_core::bytes::from_hex; +use subxt_core::utils::to_hex; use subxt_signer::sr25519::Keypair; use client::{Error, try_verify_glove_result}; @@ -234,10 +236,10 @@ async fn verify_vote( if !cmd.enclave_measurement.is_empty() { let enclave_match = cmd.enclave_measurement .iter() - .any(|str| hex::decode(str).ok() == Some(image_measurement.clone())); + .any(|str| from_hex(str).ok() == Some(image_measurement.clone())); if !enclave_match { bail!("Unknown enclave encountered in Glove proof ({})", - hex::encode(&image_measurement)) + to_hex(&image_measurement)) } println!("Vote mixed by VERIFIED Glove enclave: {:?} with {} and conviction {:?}", verified_glove_proof.result.direction, @@ -245,7 +247,7 @@ async fn verify_vote( assigned_balance.conviction); } else { println!("Vote mixed by POSSIBLE Glove enclave ({}): {:?} with {} and conviction {:?}", - hex::encode(&image_measurement), + to_hex(&image_measurement), verified_glove_proof.result.direction, network.token.amount(assigned_balance.balance), assigned_balance.conviction); @@ -255,7 +257,7 @@ async fn verify_vote( verified_glove_proof.attested_data.version, env!("CARGO_PKG_REPOSITORY")); println!(); - println!("And then verify 'PCR0' output is '{}':", hex::encode(&image_measurement)); + println!("And then verify 'PCR0' output is '{}':", to_hex(&image_measurement)); println!("./build.sh"); } @@ -289,7 +291,7 @@ fn info(service_info: ServiceInfo) -> Result { let ab = &service_info.attestation_bundle; let enclave_info = match ab.verify() { Ok(EnclaveInfo::Nitro(enclave_info)) => { - &format!("AWS Nitro Enclave ({})", hex::encode(enclave_info.image_measurement)) + &format!("AWS Nitro Enclave ({})", to_hex(enclave_info.image_measurement)) }, Err(attestation::Error::InsecureMode) => match ab.attestation { Attestation::Nitro(_) => "Debug AWS Nitro Enclave (INSECURE)", @@ -301,7 +303,7 @@ fn info(service_info: ServiceInfo) -> Result { println!("Glove proxy account: {}", service_info.proxy_account); println!("Enclave: {}", enclave_info); println!("Substrate Network: {}", service_info.network_name); - println!("Genesis hash: {}", hex::encode(ab.attested_data.genesis_hash)); + println!("Genesis hash: {}", to_hex(ab.attested_data.genesis_hash)); Ok(SuccessOutput::None) } diff --git a/common/Cargo.toml b/common/Cargo.toml index c22866a..da546b2 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -21,7 +21,6 @@ sha2.workspace = true thiserror.workspace = true flate2.workspace = true subxt.workspace = true -hex.workspace = true [dev-dependencies] serde_json.workspace = true diff --git a/common/src/lib.rs b/common/src/lib.rs index bc56aa9..2ea0119 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -28,10 +28,32 @@ pub struct SignedVoteRequest { impl SignedVoteRequest { pub fn verify(&self) -> bool { - self.signature.verify(&*self.request.encode(), &self.request.account) + verify_js_payload(&self.signature, &self.request, &self.request.account) } } +const JS_SIGNING_PREFIX: &[u8] = b""; +const JS_SIGNING_POSTFIX: &[u8] = b""; + +/// Verify a signed SCALE encoded payload which can possibly orginate from Polkadot JS. +/// +/// The `signRaw` function in Polkadot JS wraps the bytes to be signed with `` and +/// ``. So verification needs to be tried with and without this wrapping. +pub fn verify_js_payload( + signature: &MultiSignature, + payload: &E, + account: &AccountId32 +) -> bool { + let capacity = JS_SIGNING_PREFIX.len() + payload.size_hint() + JS_SIGNING_POSTFIX.len(); + let mut wrapped_bytes = Vec::with_capacity(capacity); + wrapped_bytes.extend_from_slice(JS_SIGNING_PREFIX); + payload.encode_to(&mut wrapped_bytes); + let before_postfix = wrapped_bytes.len(); + wrapped_bytes.extend_from_slice(JS_SIGNING_POSTFIX); + let encoded_payload = &wrapped_bytes[JS_SIGNING_PREFIX.len()..before_postfix]; + signature.verify(&*wrapped_bytes, account) || signature.verify(encoded_payload, account) +} + #[derive(Debug, Clone, PartialEq, Encode, Decode, MaxEncodedLen)] pub struct VoteRequest { /// The account on whose behalf the Glove proxy will vote for. @@ -152,13 +174,15 @@ pub mod serde_over_hex_scale { use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Deserializer, Serializer}; use serde::de::Error; + use sp_core::bytes::from_hex; + use subxt::utils::to_hex; pub fn serialize(value: &T, serializer: S) -> Result where T: Encode, S: Serializer { - serializer.serialize_str(&hex::encode(value.encode())) + serializer.serialize_str(&to_hex(&value.encode())) } pub fn deserialize<'de, T, D>(deserializer: D) -> Result @@ -166,7 +190,7 @@ pub mod serde_over_hex_scale { T: Decode, D: Deserializer<'de> { - let bytes = hex::decode(String::deserialize(deserializer)?).map_err(Error::custom)?; + let bytes = from_hex(&String::deserialize(deserializer)?).map_err(Error::custom)?; T::decode(&mut bytes.as_slice()).map_err(Error::custom) } } @@ -177,6 +201,7 @@ mod tests { use rand::random; use serde_json::{json, Value}; use sp_core::{Pair, sr25519}; + use sp_core::bytes::to_hex; use subxt_signer::sr25519::dev; use Conviction::{Locked3x, Locked6x}; @@ -207,8 +232,8 @@ mod tests { assert_eq!( serde_json::from_str::(&json).unwrap(), json!({ - "request": hex::encode(&signed_request.request.encode()), - "signature": hex::encode(&signed_request.signature.encode()) + "request": to_hex(&signed_request.request.encode(), false), + "signature": to_hex(&signed_request.signature.encode(), false) }) ); @@ -217,16 +242,17 @@ mod tests { } #[test] - fn signed_vote_request_json_using_polkadot_js() { + fn signed_vote_request_json_using_polkadot_js_keyring() { // Produced from test-resources/vote-request-example.mjs let json = r#" { - "request": "8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a486408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063ee502f5c1912301009c5b3607020000000000000000000002", - "signature": "01ea13f59165bc295d1e99629622dc0c13a1c7163017359178606f0e36d1d2c246c021190cf0508b0d60d292a00ba06ed396d5559fe7334c78c95bf289e84ebc81" + "request": "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a486408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063ee502f5c1912301009c5b3607020000000000000000000002", + "signature": "0x01ea13f59165bc295d1e99629622dc0c13a1c7163017359178606f0e36d1d2c246c021190cf0508b0d60d292a00ba06ed396d5559fe7334c78c95bf289e84ebc81" } "#; let signed_request = serde_json::from_str::(json).unwrap(); + println!("{:#?}", signed_request); assert!(signed_request.verify()); let request = signed_request.request; assert_eq!(request.account, dev::bob().public_key().0.into()); @@ -237,6 +263,27 @@ mod tests { assert_eq!(request.conviction, Conviction::Locked2x); } + #[test] + fn signed_vote_request_json_using_polkadot_js_signraw() { + // Produced from Glove frontend + let json = r#" +{ + "request": "0x28836d6f19d5cd8dd8b26da754c63ae337c6f938a7dc6a12e439ad8a1c69fb0d6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e6503b71b731f0000ac000bf0020000000000000000000004", + "signature": "0x017ac41d7c8de53116b37e5205ba0c20900fc04f4cf25cfa78bceb2b91e0b1fc26c08c52be1b0d73dd1856b8673d9f878df244bae898d2f22e28029ee51fe20e89" +} +"#; + let signed_request = serde_json::from_str::(json).unwrap(); + println!("{:#?}", signed_request); + assert!(signed_request.verify()); + let request = signed_request.request; + assert_eq!(request.account, AccountId32::from_str("5CyppCnQKiuY9c22yjHbDTpCqeHzAt7GXQpFAURxycWTS8My").unwrap()); + assert_eq!(request.poll_index, 217); + assert_eq!(request.genesis_hash, H256::from_str("6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e").unwrap()); + assert_eq!(request.balance, 3230000000000); + assert_eq!(request.aye, false); + assert_eq!(request.conviction, Conviction::Locked4x); + } + #[test] fn different_signer_to_vote_request_account() { let (pair1, _) = sr25519::Pair::generate(); diff --git a/common/src/nitro.rs b/common/src/nitro.rs index abd304e..23c6bde 100644 --- a/common/src/nitro.rs +++ b/common/src/nitro.rs @@ -136,6 +136,7 @@ pub enum Error { mod tests { use aws_nitro_enclaves_cose::CoseSign1; use parity_scale_codec::Decode; + use sp_core::bytes::from_hex; use super::*; @@ -145,9 +146,9 @@ mod tests { fn decode_and_verify_attestation() { let doc = Attestation::try_from(RAW_NITRO_ATTESTATION_BYTES).unwrap().verify().unwrap(); println!("{:?}", doc); - assert_eq!(doc.pcrs.get(&0).unwrap().to_vec(), hex::decode("dd1c94beae9a589b37f6601ecf73c297ff0bf41a8872f737fabf3c9a2a96eb3b1dcdabc8e33ba1f7654b528518b8b9ed").unwrap()); - assert_eq!(doc.pcrs.get(&1).unwrap().to_vec(), hex::decode("52b919754e1643f4027eeee8ec39cc4a2cb931723de0c93ce5cc8d407467dc4302e86490c01c0d755acfe10dbf657546").unwrap()); - assert_eq!(doc.pcrs.get(&2).unwrap().to_vec(), hex::decode("35a4393a77e7f60a9eb28b974b400149e36fe07791a84b3285cb16f3fdeaf7503f98ecdf6cc800d0109166d82fc7052b").unwrap()); + assert_eq!(doc.pcrs.get(&0).unwrap().to_vec(), from_hex("dd1c94beae9a589b37f6601ecf73c297ff0bf41a8872f737fabf3c9a2a96eb3b1dcdabc8e33ba1f7654b528518b8b9ed").unwrap()); + assert_eq!(doc.pcrs.get(&1).unwrap().to_vec(), from_hex("52b919754e1643f4027eeee8ec39cc4a2cb931723de0c93ce5cc8d407467dc4302e86490c01c0d755acfe10dbf657546").unwrap()); + assert_eq!(doc.pcrs.get(&2).unwrap().to_vec(), from_hex("35a4393a77e7f60a9eb28b974b400149e36fe07791a84b3285cb16f3fdeaf7503f98ecdf6cc800d0109166d82fc7052b").unwrap()); assert_eq!(doc.user_data, None); assert_eq!(doc.public_key, None); assert_eq!(doc.nonce, None);