Browser extension signing example (#1067)

* routing and signing example

* cliipy fix

* submitting extrinsics

* change order of lines

* Skip call variants if there aren't any (#980)

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>

* Tidy up some metadata accessing (#978)

* Reduce some repetition when obtaining metadata pallets/runtime_traits

* make them pub

* fix docs and clippy

* Bump tokio from 1.28.1 to 1.28.2 (#984)

Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.28.1 to 1.28.2.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.28.1...tokio-1.28.2)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump regex from 1.8.2 to 1.8.3 (#986)

Bumps [regex](https://github.com/rust-lang/regex) from 1.8.2 to 1.8.3.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.8.2...1.8.3)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump quote from 1.0.27 to 1.0.28 (#983)

Bumps [quote](https://github.com/dtolnay/quote) from 1.0.27 to 1.0.28.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.27...1.0.28)

---
updated-dependencies:
- dependency-name: quote
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump proc-macro2 from 1.0.58 to 1.0.59 (#985)

Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.58 to 1.0.59.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.58...1.0.59)

---
updated-dependencies:
- dependency-name: proc-macro2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* restrict sign_with_address_and_signature interface (#988)

* changing js bridge

* dryrunresult ok

* submitting extrinsic working

* tiny up code and ui

* formatting

* remove todos

* support tip and mortality

* Prevent bug when reusing type ids in hashing (#1075)

* practice TDD

* implement a hashmap 2-phases approach

* use nicer types

* add test for cache filling

* adjust test

---------

Co-authored-by: James Wilson <james@jsdw.me>

* small adjustment

* Merge branch 'master' into tadeo-hepperle-browser-extension-signing-example

* fix lock file

* tell users how to add Alice account to run signing example

* adjust to PR comments

* fmt

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: James Wilson <james@jsdw.me>
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Tadeo Hepperle
2023-08-02 14:56:41 +02:00
committed by GitHub
parent 2176ec9fa7
commit dc0aeac3d6
14 changed files with 1134 additions and 424 deletions
+112 -1
View File
@@ -1,10 +1,19 @@
use anyhow::anyhow;
use futures::StreamExt;
use js_sys::Promise;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::fmt::Write;
use subxt::ext::codec::Encode;
use subxt::tx::PartialExtrinsic;
use subxt::{self, OnlineClient, PolkadotConfig};
use subxt::utils::AccountId32;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use yew::{AttrValue, Callback};
#[subxt::subxt(runtime_metadata_path = "../../artifacts/polkadot_metadata_small.scale")]
mod polkadot {}
pub mod polkadot {}
pub(crate) async fn fetch_constant_block_length() -> Result<String, subxt::Error> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
@@ -71,3 +80,105 @@ pub(crate) async fn subscribe_to_finalized_blocks(
}
Ok(())
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = getAccounts)]
pub fn js_get_accounts() -> Promise;
#[wasm_bindgen(js_name = signPayload)]
pub fn js_sign_payload(payload: String, source: String, address: String) -> Promise;
}
/// DTO to communicate with JavaScript
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Account {
/// account name
pub name: String,
/// name of the browser extension
pub source: String,
/// the signature type, e.g. "sr25519" or "ed25519"
pub ty: String,
/// ss58 formatted address as string. Can be converted into AccountId32 via it's FromStr implementation.
pub address: String,
}
pub async fn get_accounts() -> Result<Vec<Account>, anyhow::Error> {
let result = JsFuture::from(js_get_accounts())
.await
.map_err(|js_err| anyhow!("{js_err:?}"))?;
let accounts_str = result
.as_string()
.ok_or(anyhow!("Error converting JsValue into String"))?;
let accounts: Vec<Account> = serde_json::from_str(&accounts_str)?;
Ok(accounts)
}
fn to_hex(bytes: impl AsRef<[u8]>) -> String {
format!("0x{}", hex::encode(bytes.as_ref()))
}
fn encode_to_hex<E: Encode>(input: &E) -> String {
format!("0x{}", hex::encode(input.encode()))
}
/// this is used because numeric types (e.g. u32) are encoded as little-endian via scale (e.g. 9430 -> d6240000)
/// while we need a big-endian representation for the json (e.g. 9430 -> 000024d6).
fn encode_to_hex_reverse<E: Encode>(input: &E) -> String {
let mut bytes = input.encode();
bytes.reverse();
format!("0x{}", hex::encode(bytes))
}
/// communicates with JavaScript to obtain a signature for the `partial_extrinsic` via a browser extension (e.g. polkadot-js or Talisman)
///
/// Some parameters are hard-coded here and not taken from the partial_extrinsic itself (mortality_checkpoint, era, tip).
pub async fn extension_signature_for_partial_extrinsic(
partial_extrinsic: &PartialExtrinsic<PolkadotConfig, OnlineClient<PolkadotConfig>>,
api: &OnlineClient<PolkadotConfig>,
account_id: &AccountId32,
account_source: String,
account_address: String,
) -> Result<Vec<u8>, anyhow::Error> {
let spec_version = encode_to_hex_reverse(&api.runtime_version().spec_version);
let transaction_version = encode_to_hex_reverse(&api.runtime_version().transaction_version);
let mortality_checkpoint = encode_to_hex(&api.genesis_hash());
let era = encode_to_hex(&subxt::config::extrinsic_params::Era::Immortal);
let genesis_hash = encode_to_hex(&api.genesis_hash());
let method = to_hex(partial_extrinsic.call_data());
let nonce = api.tx().account_nonce(account_id).await?;
let nonce = encode_to_hex_reverse(&nonce);
let signed_extensions: Vec<String> = api
.metadata()
.extrinsic()
.signed_extensions()
.iter()
.map(|e| e.identifier().to_string())
.collect();
let tip = encode_to_hex(&subxt::config::polkadot::PlainTip::new(0));
let payload = json!({
"specVersion": spec_version,
"transactionVersion": transaction_version,
"address": account_address,
"blockHash": mortality_checkpoint,
"blockNumber": "0x00000000",
"era": era,
"genesisHash": genesis_hash,
"method": method,
"nonce": nonce,
"signedExtensions": signed_extensions,
"tip": tip,
"version": 4,
});
let payload = payload.to_string();
let result = JsFuture::from(js_sign_payload(payload, account_source, account_address))
.await
.map_err(|js_err| anyhow!("{js_err:?}"))?;
let signature = result
.as_string()
.ok_or(anyhow!("Error converting JsValue into String"))?;
let signature = hex::decode(&signature[2..])?;
Ok(signature)
}