Skip to content

Commit

Permalink
[Identity] Remove double encoding username signature payload (#4646)
Browse files Browse the repository at this point in the history
In order to receive a username in `pallet-identity`, users have to,
among other things, provide a signature of the desired username. Right
now, there is an [extra encoding
step](https://github.com/paritytech/polkadot-sdk/blob/4ab078d6754147ce731523292dd1882f8a7b5775/substrate/frame/identity/src/lib.rs#L1119)
when generating the payload to sign.

Encoding a `Vec` adds extra bytes related to the length, which changes
the payload. This is unnecessary and confusing as users expect the
payload to sign to be just the username bytes. This PR fixes this issue
by validating the signature directly against the username bytes.

---------

Signed-off-by: georgepisaltu <[email protected]>
  • Loading branch information
georgepisaltu committed Jun 5, 2024
1 parent e046d3b commit 49edb86
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 42 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

20 changes: 20 additions & 0 deletions prdoc/pr_4646.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: "[Identity] Remove double encoding username signature payload"

doc:
- audience: Runtime Dev
description: |
The signature payload for setting a username for an account in `pallet-identity` is now just
the raw bytes of said username (still including the suffix), removing the need to first
encode these bytes before signing.
- audience: Runtime User
description: |
The signature payload for setting a username for an account in `pallet-identity` is now just
the raw bytes of said username (still including the suffix), removing the need to first
encode these bytes before signing.

crates:
- name: pallet-identity
bump: major
2 changes: 1 addition & 1 deletion substrate/frame/identity/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pallet-identity"
version = "29.0.1"
version = "30.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
Expand Down
12 changes: 5 additions & 7 deletions substrate/frame/identity/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ use super::*;

use crate::Pallet as Identity;
use codec::Encode;
use frame_benchmarking::{
account, impl_benchmark_test_suite, v2::*, whitelisted_caller, BenchmarkError,
};
use frame_benchmarking::{account, v2::*, whitelisted_caller, BenchmarkError};
use frame_support::{
assert_ok, ensure,
traits::{EnsureOrigin, Get, OnFinalize, OnInitialize},
Expand Down Expand Up @@ -625,17 +623,17 @@ mod benchmarks {

let username = bench_username();
let bounded_username = bounded_username::<T>(username.clone(), suffix.clone());
let encoded_username = Encode::encode(&bounded_username.to_vec());

let public = sr25519_generate(0.into(), None);
let who_account: T::AccountId = MultiSigner::Sr25519(public).into_account().into();
let who_lookup = T::Lookup::unlookup(who_account.clone());

let signature =
MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap());
let signature = MultiSignature::Sr25519(
sr25519_sign(0.into(), &public, &bounded_username[..]).unwrap(),
);

// Verify signature here to avoid surprise errors at runtime
assert!(signature.verify(&encoded_username[..], &public.into()));
assert!(signature.verify(&bounded_username[..], &public.into()));

#[extrinsic_call]
_(RawOrigin::Signed(authority.clone()), who_lookup, username, Some(signature.into()));
Expand Down
7 changes: 3 additions & 4 deletions substrate/frame/identity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1116,8 +1116,7 @@ pub mod pallet {
if let Some(s) = signature {
// Account has pre-signed an authorization. Verify the signature provided and grant
// the username directly.
let encoded = Encode::encode(&bounded_username.to_vec());
Self::validate_signature(&encoded, &s, &who)?;
Self::validate_signature(&bounded_username[..], &s, &who)?;
Self::insert_username(&who, bounded_username);
} else {
// The user must accept the username, therefore, queue it.
Expand Down Expand Up @@ -1267,12 +1266,12 @@ impl<T: Config> Pallet<T> {

/// Validate a signature. Supports signatures on raw `data` or `data` wrapped in HTML `<Bytes>`.
pub fn validate_signature(
data: &Vec<u8>,
data: &[u8],
signature: &T::OffchainSignature,
signer: &T::AccountId,
) -> DispatchResult {
// Happy path, user has signed the raw data.
if signature.verify(&data[..], &signer) {
if signature.verify(data, &signer) {
return Ok(())
}
// NOTE: for security reasons modern UIs implicitly wrap the data requested to sign into
Expand Down
51 changes: 22 additions & 29 deletions substrate/frame/identity/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1061,13 +1061,13 @@ fn set_username_with_signature_without_existing_identity_should_work() {

// set up username
let (username, username_to_sign) = test_username_of(b"42".to_vec(), suffix);
let encoded_username = Encode::encode(&username_to_sign.to_vec());

// set up user and sign message
let public = sr25519_generate(0.into(), None);
let who_account: AccountIdOf<Test> = MultiSigner::Sr25519(public).into_account().into();
let signature =
MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap());
let signature = MultiSignature::Sr25519(
sr25519_sign(0.into(), &public, &username_to_sign[..]).unwrap(),
);

assert_ok!(Identity::set_username_for(
RuntimeOrigin::signed(authority),
Expand Down Expand Up @@ -1112,13 +1112,13 @@ fn set_username_with_signature_with_existing_identity_should_work() {

// set up username
let (username, username_to_sign) = test_username_of(b"42".to_vec(), suffix);
let encoded_username = Encode::encode(&username_to_sign.to_vec());

// set up user and sign message
let public = sr25519_generate(0.into(), None);
let who_account: AccountIdOf<Test> = MultiSigner::Sr25519(public).into_account().into();
let signature =
MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap());
let signature = MultiSignature::Sr25519(
sr25519_sign(0.into(), &public, &username_to_sign[..]).unwrap(),
);

// Set an identity for who. They need some balance though.
Balances::make_free_balance_be(&who_account, 1000);
Expand Down Expand Up @@ -1175,13 +1175,13 @@ fn set_username_with_bytes_signature_should_work() {
let unwrapped_username = username_to_sign.to_vec();

// Sign an unwrapped version, as in `username.suffix`.
let unwrapped_encoded = Encode::encode(&unwrapped_username);
let signature_on_unwrapped =
MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &unwrapped_encoded).unwrap());
let signature_on_unwrapped = MultiSignature::Sr25519(
sr25519_sign(0.into(), &public, &unwrapped_username[..]).unwrap(),
);

// Trivial
assert_ok!(Identity::validate_signature(
&unwrapped_encoded,
&unwrapped_username,
&signature_on_unwrapped,
&who_account
));
Expand All @@ -1193,15 +1193,15 @@ fn set_username_with_bytes_signature_should_work() {
let mut wrapped_username: Vec<u8> =
Vec::with_capacity(unwrapped_username.len() + prehtml.len() + posthtml.len());
wrapped_username.extend(prehtml);
wrapped_username.extend(unwrapped_encoded.clone());
wrapped_username.extend(&unwrapped_username);
wrapped_username.extend(posthtml);
let signature_on_wrapped =
MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &wrapped_username).unwrap());

// We want to call `validate_signature` on the *unwrapped* username, but the signature on
// the *wrapped* data.
assert_ok!(Identity::validate_signature(
&unwrapped_encoded,
&unwrapped_username,
&signature_on_wrapped,
&who_account
));
Expand Down Expand Up @@ -1420,9 +1420,8 @@ fn setting_primary_should_work() {

// set up username
let (first_username, first_to_sign) = test_username_of(b"42".to_vec(), suffix.clone());
let encoded_username = Encode::encode(&first_to_sign.to_vec());
let first_signature =
MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap());
MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &first_to_sign[..]).unwrap());

assert_ok!(Identity::set_username_for(
RuntimeOrigin::signed(authority.clone()),
Expand All @@ -1446,9 +1445,8 @@ fn setting_primary_should_work() {

// set up username
let (second_username, second_to_sign) = test_username_of(b"101".to_vec(), suffix);
let encoded_username = Encode::encode(&second_to_sign.to_vec());
let second_signature =
MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap());
MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &second_to_sign[..]).unwrap());

assert_ok!(Identity::set_username_for(
RuntimeOrigin::signed(authority),
Expand Down Expand Up @@ -1529,10 +1527,8 @@ fn must_own_primary() {
let pi_account: AccountIdOf<Test> = MultiSigner::Sr25519(pi_public).into_account().into();
let (pi_username, pi_to_sign) =
test_username_of(b"username314159".to_vec(), suffix.clone());
let encoded_pi_username = Encode::encode(&pi_to_sign.to_vec());
let pi_signature = MultiSignature::Sr25519(
sr25519_sign(0.into(), &pi_public, &encoded_pi_username).unwrap(),
);
let pi_signature =
MultiSignature::Sr25519(sr25519_sign(0.into(), &pi_public, &pi_to_sign[..]).unwrap());
assert_ok!(Identity::set_username_for(
RuntimeOrigin::signed(authority.clone()),
pi_account.clone(),
Expand All @@ -1544,10 +1540,8 @@ fn must_own_primary() {
let e_public = sr25519_generate(1.into(), None);
let e_account: AccountIdOf<Test> = MultiSigner::Sr25519(e_public).into_account().into();
let (e_username, e_to_sign) = test_username_of(b"username271828".to_vec(), suffix.clone());
let encoded_e_username = Encode::encode(&e_to_sign.to_vec());
let e_signature = MultiSignature::Sr25519(
sr25519_sign(1.into(), &e_public, &encoded_e_username).unwrap(),
);
let e_signature =
MultiSignature::Sr25519(sr25519_sign(1.into(), &e_public, &e_to_sign[..]).unwrap());
assert_ok!(Identity::set_username_for(
RuntimeOrigin::signed(authority.clone()),
e_account.clone(),
Expand Down Expand Up @@ -1652,13 +1646,13 @@ fn removing_dangling_usernames_should_work() {

// set up username
let (username, username_to_sign) = test_username_of(b"42".to_vec(), suffix.clone());
let encoded_username = Encode::encode(&username_to_sign.to_vec());

// set up user and sign message
let public = sr25519_generate(0.into(), None);
let who_account: AccountIdOf<Test> = MultiSigner::Sr25519(public).into_account().into();
let signature =
MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap());
let signature = MultiSignature::Sr25519(
sr25519_sign(0.into(), &public, &username_to_sign[..]).unwrap(),
);

// Set an identity for who. They need some balance though.
Balances::make_free_balance_be(&who_account, 1000);
Expand All @@ -1676,11 +1670,10 @@ fn removing_dangling_usernames_should_work() {

// Now they set up a second username.
let (username_two, username_two_to_sign) = test_username_of(b"43".to_vec(), suffix);
let encoded_username_two = Encode::encode(&username_two_to_sign.to_vec());

// set up user and sign message
let signature_two = MultiSignature::Sr25519(
sr25519_sign(0.into(), &public, &encoded_username_two).unwrap(),
sr25519_sign(0.into(), &public, &username_two_to_sign[..]).unwrap(),
);

assert_ok!(Identity::set_username_for(
Expand Down

0 comments on commit 49edb86

Please sign in to comment.