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

New on-chain registration flow #955

Merged
merged 35 commits into from
Aug 6, 2024
Merged

Conversation

HCastano
Copy link
Collaborator

@HCastano HCastano commented Jul 19, 2024

This PR adds a new extrinsic, Registry::register_on_chain(), which uses the new child
key derivation feature in Synedrion in order to simplify the registration process.

Since we can derive a child verification key from a parent verification key, all we need
to do for user registration is derive a user's key from the network key and store it
on-chain (alongside some program info). We don't need to reach out to the TSS to do any
cryptographic operations for us.

This PR is the first of a few that will be required to fully integrate the new
registration flow. In follow up PRs I plan to:

  • Improve the mechanism used to derive accounts
  • Remove old code related to registration (on the chain and TSS sides)
  • Consolidate our old tests to use the new extrinsic and ensure we don't lose any
    coverage

Closes #961.

HCastano added 21 commits July 19, 2024 17:01
This way we can introduce it without breaking everything just yet
This lets us use the count as the derivation path for now.
This lets us change the mapping so that instead of being indexed by verifying key we're not indexing
by the signature request account.
The notion of the signature_request_account is meant to be ephemeral, so we don't really need to
have registration know anything about this beyond the initial extrinsic.
@HCastano
Copy link
Collaborator Author

The CI is failing because Synedrion isn't able to compile to the wasm32-unknown-unknown target right now. Talking with Bogdan about this and will hopefully have it sorted soon 🤞

Copy link

github-actions bot commented Aug 1, 2024

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

return Err(Error::<T>::JumpStartNotCompleted.into());
};

// TODO (Nando): For a `CountedStorageMap` there is the possibility that the counter
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll swap this out with an issue after we go through the review process a bit in case there are discussions around this that pop up

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya also are we not moving from a counter to using the account or is there a possibility we would keep the counter?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is the possibility we keep the counter, but it would probably be in the context of something like hash(account ID) ++ counter

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #984.

@HCastano HCastano added the Bump `spec_version` A change which affects the current runtime behaviour label Aug 2, 2024
@HCastano HCastano marked this pull request as ready for review August 2, 2024 03:31
@HCastano HCastano requested review from JesseAbram and ameba23 August 2, 2024 03:31
pallets/registry/Cargo.toml Outdated Show resolved Hide resolved
Copy link
Contributor

@ameba23 ameba23 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 looks great.

Only concern i have about using a counter as input is that anybody can know what the next entropy account public key is going to be before it is made. As soon as the network jump starts everyone can know the complete set of all the entropy account public keys there will ever be.

I can't think of a reason why this is bad - but my hunch is that concatenating something from the user (eg: signature request account id and/or program modification account id) to the counter in the derivation path feels safer.

pallets/registry/src/lib.rs Show resolved Hide resolved
pallets/registry/src/lib.rs Show resolved Hide resolved
let network_verifying_key =
if let Some(key) = <JumpStartProgress<T>>::get().verifying_key {
SynedrionVerifyingKey::try_from(key.as_slice())
.expect("The network verifying key must be valid.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to leave this expect in? I guess it should be impossible to get into a state where it is invalid

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya def remove it for proper error handling

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is intentional. If this expect is hit it means there's a bug somewhere in the code (we're storing an invalid verifying key), and I would like users to log a report about it.

// down the line.
let count = RegisteredOnChain::<T>::count();
let path =
bip32::DerivationPath::from_str(&scale_info::prelude::format!("m/0/{}", count))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im guessing the m/0 bit is like a version number that we can bump if the derivation process changes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the m/.../... bit is the actual derivation path.

If you take a look at the graphic here you can see that it's sort of like a namespace. So accounts under m/0/* are all associated with each other, while those under m/1/* are distinct from m/0/* (but associated with each other).

let signature_request_account = ensure_signed(origin)?;

ensure!(
signature_request_account.encode() != NETWORK_PARENT_KEY.encode(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think we need this check anymore, as it was to stop the network parent keyshare getting clobbered by a user keyshare in the kvdb. But here the user has no keyshare to be stored.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yaaaa but this may be one of those I don't know what this could lead to but I have a bad feeling about it not being in here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the jumpstart PR where this was introduced I mentioned that allowing registration from the parent key could be cool.

While a bit weird, it would allow the network to have a registered account for use on other networks. Maybe it could allow for governance actions on Bitcoin or Ethereum for example. I'd be down to remove this check as long as its cool with both of you

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think leave it in make an issue and this is actually a good retreat topic

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #983.

let child_verifying_key = BoundedVec::try_from(
child_verifying_key.to_encoded_point(true).as_bytes().to_vec(),
)
.expect("Synedrion must have returned a valid verifying key.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree that this could never happen but i still kinda don't like having expects

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here, this would be a bug on the Synedrion side and I would like a bug report about it instead of "handling" it.

Copy link
Member

@JesseAbram JesseAbram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good in flow, won't approve till you do negative tests

pallets/registry/src/lib.rs Show resolved Hide resolved
pallets/registry/src/lib.rs Show resolved Hide resolved
let signature_request_account = ensure_signed(origin)?;

ensure!(
signature_request_account.encode() != NETWORK_PARENT_KEY.encode(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yaaaa but this may be one of those I don't know what this could lead to but I have a bad feeling about it not being in here

let network_verifying_key =
if let Some(key) = <JumpStartProgress<T>>::get().verifying_key {
SynedrionVerifyingKey::try_from(key.as_slice())
.expect("The network verifying key must be valid.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya def remove it for proper error handling

pallets/registry/src/lib.rs Show resolved Hide resolved
return Err(Error::<T>::JumpStartNotCompleted.into());
};

// TODO (Nando): For a `CountedStorageMap` there is the possibility that the counter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya also are we not moving from a counter to using the account or is there a possibility we would keep the counter?

// down the line.
let count = RegisteredOnChain::<T>::count();
let path =
bip32::DerivationPath::from_str(&scale_info::prelude::format!("m/0/{}", count))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good but what path is the parent key at and we def need to block it, if the parent key is at 0 we need to start the count from 1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use m/0/0 it still ends up with a different private key. From this bip32 website:

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true Im fine, should be a really easy test, just one assert statment in the test, probably worth it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is possible as long as some sort of derivation step exists in the extrinsic.

What do you think a test for this should look like though? Would we just check the m/0/0 path, or enumerate all the paths and check those?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just in the positive register test you can take the key you put as the parent network key derived off and assert there is not program data for that, one line no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would only check for that specific derivation path used in the test though, e.g m/0/0. Right now I don't think it's really practical to test for this, but I'll consider it further when making more changes to the actual derivation scheme

let child_verifying_key = BoundedVec::try_from(
child_verifying_key.to_encoded_point(true).as_bytes().to_vec(),
)
.expect("Synedrion must have returned a valid verifying key.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

pallets/registry/src/lib.rs Show resolved Hide resolved
pallets/registry/src/benchmarking.rs Show resolved Hide resolved
Copy link
Member

@JesseAbram JesseAbram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add negative tests

@HCastano HCastano requested a review from JesseAbram August 2, 2024 21:48
@ameba23
Copy link
Contributor

ameba23 commented Aug 5, 2024

@HCastano @JesseAbram - one thing that just occurred to me about this:

When we come to do the signing flow, i assume the signature request is going to specify the verifying key, and that to sign a message the threshold server will need to know the derivation path.

So we would need a way of looking up what count number a given verifying key has. Maybe CountedStorageMap gives us some easy way to get the count of a given key - but i can't see it. So maybe we need to store the count in RegisteredInfo.

@JesseAbram
Copy link
Member

@HCastano @JesseAbram - one thing that just occurred to me about this:

When we come to do the signing flow, i assume the signature request is going to specify the verifying key, and that to sign a message the threshold server will need to know the derivation path.

So we would need a way of looking up what count number a given verifying key has. Maybe CountedStorageMap gives us some easy way to get the count of a given key - but i can't see it. So maybe we need to store the count in RegisteredInfo.

good point, def the second, the second is magnitudes more simple

@HCastano
Copy link
Collaborator Author

HCastano commented Aug 6, 2024

@ameba23

Only concern i have about using a counter as input is that anybody can know what the next entropy account public key is going to be before it is made. As soon as the network jump starts everyone can know the complete set of all the entropy account public keys there will ever be.

I can't think of a reason why this is bad - but my hunch is that concatenating something from the user (eg: signature request account id and/or program modification account id) to the counter in the derivation path feels safer.

Yeah knowing the public key of an account isn't inherently dangerous. Maybe there are some scenarios where this could be used in some way, but I don't think it's any different from having access to a vanity generator (e.g if you want an account like 0xbeef.... We should definitely incorporate some element other than the counter though.

When we come to do the signing flow, i assume the signature request is going to specify the verifying key, and that to sign a message the threshold server will need to know the derivation path.

So we would need a way of looking up what count number a given verifying key has. Maybe CountedStorageMap gives us some easy way to get the count of a given key - but i can't see it. So maybe we need to store the count in RegisteredInfo.

You're right here, and it's something I was going to deal with in the signing flow PRs.

@HCastano HCastano merged commit 381b310 into master Aug 6, 2024
14 checks passed
@HCastano HCastano deleted the hc/new-on-chain-registration-flow branch August 6, 2024 14:57
@github-actions github-actions bot locked and limited conversation to collaborators Aug 6, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bump `spec_version` A change which affects the current runtime behaviour
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Using synedrion as a dependency of a pallet
3 participants