-
Notifications
You must be signed in to change notification settings - Fork 59
feat(platform)!: add contract-scoped authentication keys #4613
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
Open
PastaPastaPasta
wants to merge
4
commits into
v4.2-dev
Choose a base branch
from
feat/scoped-contract-auth-keys
base: v4.2-dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
3ca176a
feat(platform): add contract-scoped authentication keys
PastaPastaPasta cf8b57b
fix(wasm-dpp): map scoped authentication errors
PastaPastaPasta 62f9648
fix(wasm): keep scoped bounds bindings exhaustive
PastaPastaPasta 07bf81f
fix(wasm-drive-verify): serialize scoped contract bounds
PastaPastaPasta File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| # Contract-scoped authentication keys | ||
|
|
||
| Protocol version 14 adds application authentication scopes. A wallet can register | ||
| a separate key for an application while retaining the identity's master key. | ||
| Validators enforce the registered scope on every batch member. Existing identity | ||
| ownership, key purpose, security level and document rules still apply. | ||
|
|
||
| ## Registering an application key | ||
|
|
||
| The key must have AUTHENTICATION purpose and a non-MASTER security level. A HIGH | ||
| key is suitable for normal document operations. Its `contractBounds` is a new | ||
| `scoped` variant containing a versioned authentication scope: | ||
|
|
||
| - `contracts`: explicit contract IDs with optional document-type restrictions. | ||
| - `permissions`: an action bitmask shared by every listed contract. | ||
| - `expiresAt`: an optional expiry in milliseconds, checked against block time. | ||
|
|
||
| A missing/null document-type restriction authorizes all types in that contract, | ||
| including types added by later contract updates. An empty array is invalid. | ||
| Contract IDs and document-type names must be sorted and unique on the wire. There | ||
| are at most 16 contracts and 16 types per contract, and the encoded scope must not | ||
| exceed 2048 bytes. The WASM constructor sorts entries and rejects duplicates. | ||
|
|
||
| For an application that creates, updates and deletes documents and pays their | ||
| configured token fees, construct the bounds with the WASM SDK: | ||
|
|
||
| ```javascript | ||
| const P = wasm.AuthenticationPermission; | ||
| const bounds = wasm.ContractBounds.Scoped( | ||
| [ | ||
| { id: socialContractId, documentTypes: ['like', 'post'] }, | ||
| { id: profileContractId, documentTypes: ['profile'] }, | ||
| ], | ||
| P.DocumentCreate | P.DocumentReplace | P.DocumentDelete | P.DocumentTokenPayment, | ||
| BigInt(Date.now() + 24 * 60 * 60 * 1000), | ||
| ); | ||
|
|
||
| const keyToAdd = new wasm.IdentityPublicKeyInCreation({ | ||
| keyId: nextKeyId, | ||
| purpose: 'authentication', | ||
| securityLevel: 'high', | ||
| keyType: 'ecdsa_hash160', | ||
| isReadOnly: false, | ||
| data: applicationPublicKeyHash160, | ||
| signature: new Uint8Array(), | ||
| contractBounds: bounds, | ||
| }); | ||
| ``` | ||
|
|
||
| Use the normal wallet-authorized identity-update procedure to register the key. | ||
| The registration signature binds the scope as well as the public-key material. | ||
| The browser only needs the application key's private material. Registering a | ||
| scope requires its referenced contracts/types to exist and its expiry, if any, | ||
| to be in the future. No contract encryption-key opt-in or unique-key setting is | ||
| required for scoped authentication. | ||
|
|
||
| ## Permissions and token fees | ||
|
|
||
| Document create, replace, delete, ownership transfer, price updates and purchases | ||
| have separate bits. Index-only deletion uses the delete bit. Standalone token | ||
| transition kinds also have separate bits. New/unknown bits are rejected. | ||
|
|
||
| `DocumentTokenPayment` permits the actual contract-defined token cost of an | ||
| otherwise-authorized document action. It also covers fees using a token issued | ||
| by another contract. That does not authorize document writes or standalone token | ||
| operations on the issuing contract. Without the bit, a document action with a | ||
| positive token cost is rejected, even if its create/replace/delete bit is set. | ||
|
|
||
| A document-token payment permission does not implicitly authorize token transfer, | ||
| burn, mint, purchase or administration transitions. Explicitly granting one of | ||
| those bits still cannot override its normal purpose/security/ownership rules. | ||
| Contract updates can change document token fees; v0 scopes do not pin the fee | ||
| amount or currency. | ||
|
|
||
| ## Expiry, revocation and failures | ||
|
|
||
| A key is expired when executing block time is greater than or equal to its expiry. | ||
| Mempool checks use the last committed block information; a transaction may expire | ||
| between admission and execution. Disable the key through the normal identity | ||
| update to revoke it. Extending expiry or expanding permissions requires a | ||
| wallet-authorized replacement. Expired keys are not automatically deleted. | ||
|
|
||
| Scoped keys cannot execute non-batch transitions, including identity-key updates, | ||
| contract creation/updates, credit transfers/withdrawals or masternode votes. | ||
| Expired keys and non-batch use fail in identity-signature authorization. | ||
|
|
||
| Batch scope violations follow normal paid validation-failure handling. Requested | ||
| document/token operations do not execute, but Platform credit validation fees | ||
| can be charged and the first batch member's identity-contract nonce can advance, | ||
| even if that member is outside the scope. Replays follow the usual nonce rules. | ||
|
|
||
| There are no per-key budgets in scope version 0. A stolen key can exhaust credit | ||
| balances through fees and permitted token balances through allowed operations. | ||
| Expiry limits the time window, not total financial loss. | ||
|
|
||
| ## Compatibility | ||
|
|
||
| The scoped variant is appended to the existing bounds enum; old key encodings | ||
| remain unchanged. Older protocols reject scoped registration, and older clients | ||
| cannot be assumed to decode scoped keys. SDK signing performs local structural | ||
| checks, but validator checks against current state remain authoritative. | ||
|
|
||
| The native key ABI carries an encoded scope pointer/length. Native libraries, | ||
| generated headers and Swift/Kotlin consumers must be updated together. Key query, | ||
| persistence, restore and refresh paths must preserve scope metadata. It must | ||
| never be dropped or reconstructed as an unrestricted key. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
packages/rs-dpp/src/errors/consensus/basic/identity/invalid_authentication_scope_error.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| use crate::consensus::basic::BasicError; | ||
| use crate::consensus::ConsensusError; | ||
| use crate::ProtocolError; | ||
| use bincode::{Decode, Encode}; | ||
| use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; | ||
| use thiserror::Error; | ||
|
|
||
| #[derive( | ||
| Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, | ||
| )] | ||
| #[error("Invalid authentication scope: {reason}")] | ||
| #[platform_serialize(unversioned)] | ||
| pub struct InvalidAuthenticationScopeError { | ||
| reason: String, | ||
| } | ||
| impl InvalidAuthenticationScopeError { | ||
| pub fn new(reason: String) -> Self { | ||
| Self { reason } | ||
| } | ||
| pub fn reason(&self) -> &String { | ||
| &self.reason | ||
| } | ||
| } | ||
| impl From<InvalidAuthenticationScopeError> for ConsensusError { | ||
| fn from(error: InvalidAuthenticationScopeError) -> Self { | ||
| Self::BasicError(BasicError::InvalidAuthenticationScopeError(error)) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
packages/rs-dpp/src/errors/consensus/signature/scoped_key_expired_error.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| use crate::consensus::signature::SignatureError; | ||
| use crate::consensus::ConsensusError; | ||
| use crate::ProtocolError; | ||
| use bincode::{Decode, Encode}; | ||
| use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; | ||
| use thiserror::Error; | ||
|
|
||
| #[derive( | ||
| Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, | ||
| )] | ||
| #[error("Scoped key {public_key_id} has expired")] | ||
| #[platform_serialize(unversioned)] | ||
| pub struct ScopedKeyExpiredError { | ||
| public_key_id: u32, | ||
| } | ||
| impl ScopedKeyExpiredError { | ||
| pub fn new(public_key_id: u32) -> Self { | ||
| Self { public_key_id } | ||
| } | ||
| pub fn public_key_id(&self) -> &u32 { | ||
| &self.public_key_id | ||
| } | ||
| } | ||
| impl From<ScopedKeyExpiredError> for ConsensusError { | ||
| fn from(error: ScopedKeyExpiredError) -> Self { | ||
| Self::SignatureError(SignatureError::ScopedKeyExpiredError(error)) | ||
| } | ||
| } |
28 changes: 28 additions & 0 deletions
28
packages/rs-dpp/src/errors/consensus/signature/scoped_key_non_batch_error.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| use crate::consensus::signature::SignatureError; | ||
| use crate::consensus::ConsensusError; | ||
| use crate::ProtocolError; | ||
| use bincode::{Decode, Encode}; | ||
| use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; | ||
| use thiserror::Error; | ||
|
|
||
| #[derive( | ||
| Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, | ||
| )] | ||
| #[error("Scoped key {public_key_id} cannot sign a non-batch transition")] | ||
| #[platform_serialize(unversioned)] | ||
| pub struct ScopedKeyNonBatchError { | ||
| public_key_id: u32, | ||
| } | ||
| impl ScopedKeyNonBatchError { | ||
| pub fn new(public_key_id: u32) -> Self { | ||
| Self { public_key_id } | ||
| } | ||
| pub fn public_key_id(&self) -> &u32 { | ||
| &self.public_key_id | ||
| } | ||
| } | ||
| impl From<ScopedKeyNonBatchError> for ConsensusError { | ||
| fn from(error: ScopedKeyNonBatchError) -> Self { | ||
| Self::SignatureError(SignatureError::ScopedKeyNonBatchError(error)) | ||
| } | ||
| } |
28 changes: 28 additions & 0 deletions
28
packages/rs-dpp/src/errors/consensus/signature/scoped_key_out_of_scope_error.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| use crate::consensus::signature::SignatureError; | ||
| use crate::consensus::ConsensusError; | ||
| use crate::ProtocolError; | ||
| use bincode::{Decode, Encode}; | ||
| use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; | ||
| use thiserror::Error; | ||
|
|
||
| #[derive( | ||
| Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, | ||
| )] | ||
| #[error("Batch member is outside key {public_key_id} scope")] | ||
| #[platform_serialize(unversioned)] | ||
| pub struct ScopedKeyOutOfScopeError { | ||
| public_key_id: u32, | ||
| } | ||
| impl ScopedKeyOutOfScopeError { | ||
| pub fn new(public_key_id: u32) -> Self { | ||
| Self { public_key_id } | ||
| } | ||
| pub fn public_key_id(&self) -> &u32 { | ||
| &self.public_key_id | ||
| } | ||
| } | ||
| impl From<ScopedKeyOutOfScopeError> for ConsensusError { | ||
| fn from(error: ScopedKeyOutOfScopeError) -> Self { | ||
| Self::SignatureError(SignatureError::ScopedKeyOutOfScopeError(error)) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: dashpay/platform
Length of output: 50373
🤖 get_repo_knowledge executed:
get_repo_knowledge dashpay/platform /tmp/coderabbit-repo-knowledge/dashpay-platform-41d19c18/architecture /tmp/coderabbit-repo-knowledge/dashpay-platform-41d19c18/learningsLength of output: 46929
🏁 Script executed:
Repository: dashpay/platform
Length of output: 50372
🏁 Script executed:
Repository: dashpay/platform
Length of output: 50372
🏁 Script executed:
Repository: dashpay/platform
Length of output: 50372
🏁 Script executed:
Repository: dashpay/platform
Length of output: 11409
Do not mark
Identityasunversioned.Identityis not stored in Drive. Its serialization is used for transport, and the declaration documents that it includes a version.unversionedswitchesPlatformSerializeto raw bincode without the version-aware path. This can change transport bytes and prevent existing consumers from decoding them. Removeunversionedand retain only the larger limit.🤖 Prompt for AI Agents