-
Couldn't load subscription status.
- Fork 5
Doughnut
Doughnuts are Proofs of Delegation between two or more cryptographic keypairs. Doughnuts let us prove that one address delegates something to another address.
For example, if Alice wants to let Bob use her account, but only to send tokens to Charlie. This could be achieved using a doughnut by doing the following:
-
Alicecreates a doughnut which sets:-
Aliceas the issuer -
Bobas the holder - Sending funds to
Charlieas a rule in the permission domain
-
-
Alicesigns the doughnut and gives it toBob - When
Bobwants to send funds toCharliewithAlice's account,Bobshould attach the doughnut when signing and sending an extrinsic.
This guide covers:
- How to create a doughnut in your D'App using the Javascript SDK
- How to send an encoded doughnut from your D'App to a PL^G blockchain
- How to write a
DispatchVerifierhook on your blockchain to interpret a doughnut
The following instructions will help you use the Javascript SDK to play with doughnut.
Run npm or yarn command to install the plug-doughnut package.
npm install plug-doughnut or yarn add plug-doughunutUse the Doughnut builder pattern to create a doughnut with necessary fields. (Note: the issuer and holder should be set to the account public keys)
// Doughnut THINGs
const Doughnut = require('plug-doughnut').Doughnut;
const testingPairs = require('@polkadot/keyring/testingPairs');
const keyring = testingPairs.default({ type: 'ed25519'});
const issuer = keyring.alice.publicKey;
const issuer_private_key = keyring.alice.sign();
const holder = keyring.bob.publicKey;
const expiry = 100;
const not_before = 1;
const doughnut = Doughnut
.new(issuer, holder, expiry, not_before)
.add_payload_version(1)
.add_domain('awesome', [1, 2, 3]);
const signature = keyring.alice.sign(doughnut.payload());
doughnut.add_signature(signature);For doughnuts to be valid, they must be signed by the issuer.
You can access the fields in the doughnut with getter functions:
const issuer = doughnut.issuer;
const holder = doughnut.holder;
const expiry = doughnut.expiry;To send a doughnut, it must be encoded as a binary object. Doughnuts are encoded as u8 arrays by using doughnut.encode().
Encoded doughnut binaries can be decoded into Doughnut objects using Doughnut.decode([u8]).
const encoded_doughnut = doughnut.encode();
const doughnut = Doughnut.decode(encoded_doughnut);An encoded doughnut is a Uint8Array, for example:
[
64, 24, 64, 22, 126, 150, 15, 176, 190, 210, 156, 179, 149, 142, 84, 153, 4, 203, 61, 62,
185, 76, 45, 162, 220, 254, 188, 163, 187, 63, 39, 186, 113, 126, 12, 60, 121, 179, 67,
105, 121, 244, 39, 137, 174, 55, 85, 167, 73, 111, 50, 249, 10, 145, 141, 125, 105, 138,
38, 93, 144, 45, 224, 70, 206, 246, 116, 196, 94, 16, 0, 115, 111, 109, 101, 116, 104, 105,
110, 103, 0, 0, 0, 0, 0, 0, 0, 128, 0, 115, 111, 109, 101, 116, 104, 105, 110, 103, 69,
108, 115, 101, 0, 0, 0, 128, 0, 0, 0, 8, 185, 184, 138, 72, 86, 187, 125, 166, 109, 176,
31, 104, 162, 235, 78, 157, 166, 8, 137, 191, 33, 202, 128, 138, 165, 73, 244, 67, 247, 37,
13, 218, 44, 244, 54, 137, 179, 56, 110, 152, 170, 180, 218, 107, 177, 170, 58, 91, 62, 24,
240, 248, 244, 13, 51, 235, 3, 21, 63, 79, 192, 137, 6
]
const Doughnut = require('plug-doughnut').Doughnut;
const domain = 'awesome';
const doughnut = Doughnut
.new(issuer, holder, expiry, not_before)
.add_payload_version(1)
.add_domain('awesome', [1, 2, 3]);
const signature = keyring.alice.sign(doughnut.payload());
doughnut.add_signature(signature);
const encoded_doughnut = doughnut.encode();We will do a balances.transfer and add the encoded doughnut in the option parameter.
// Create the API and wait until ready
const provider = new WsProvider("ws://localhost:9944");
const types = PlugRuntimeTypes.default;
const api = await ApiPromise.create({ provider, types });
// Send transfer extrinsic with doughnut
const options = { doughnut: doughnut.encode() };
const txHash = await api.tx.balances
.transfer(keyring.charlie.address, 1500000000)
.signAndSend(keyring.bob, options);The following sections assume we have completed the Build a New Project with PL^G Getting Started Guide.
Doughnuts contain permission domains so that they can be used across many applications. For example, if Alice wants to enable permission for Bob to use her account in both "cennznet" and "awesome" blockchains, then Alice will need to add both permission domains to the doughnut. In our example, we will only add the permission domain for the "awesome" blockchain.
When a doughnut is sent with an extrinsic to the blockchain, it is passed to a verify_dispatch function along with:
- The name of the runtime module called (eg/
"balances") - The name of the method called (eg/
"transfer") - A list of arguments passed (eg/
[ charlie_pub_key, 1_500_000_000 ])
First, we will look at how to check the doughnut domains in the verify_dispatch function which is implemented in the plug-blockchain.
In node-template/runtime/src/lib.rs we define the DelegatedDispatchVerifier as DummyDispatchVerifier.
type DelegatedDispatchVerifier = DummyDispatchVerifier<Self::Doughnut, Self::AccountId>;For a production application, we would define our own DispatchVerifier (maybe AwesomeDispatchVerifier); but for this example, we will modify the verify_dispatch function for the DummyDispatchVerifier in frame/support/src/additional_traits.rs to print out the domain data:
use sp_runtime::{Doughnut, DoughnutV0};
use log::{trace};
fn verify_dispatch(doughnut: &Self::Doughnut, module: &str, method: &str, args: Vec::<(&str, &dyn Any)>) -> Result<(), &'static str> {
let doughnut = DoughnutV0::try_from(doughnut.clone()).unwrap();
let domain = doughnut.get_domain("awesome").unwrap()[0];
trace!("awesome domain's value: {}", domain);
Ok(());
}After sending the transfer extrinsic to the local node. We should see the following output in the console log:
awesome domain's value: 0
We can set a permission domain specific for our awesome_node in the DelegatedDispatchVerifier implementation for DummyDispatchVerifier. Only doughnuts containing the awesome domain will be verified.
impl<D: PlugDoughnutApi, A: Parameter> DelegatedDispatchVerifier for DummyDispatchVerifier<D, A> {
type Doughnut = D;
type AccountId = A;
// The doughnut permission domain it verifies
const DOMAIN: &'static str = "awesome";
fn verify_dispatch(doughnut: &Self::Doughnut, module: &str, method: &str, args: Vec::<(&str, &dyn Any)>) -> Result<(), &'static str> {
let doughnut = DoughnutV0::try_from(doughnut.clone()).unwrap();
let domain = doughnut.get_domain(Self::DOMAIN).ok_or("Doughnut does not grant permission for awesome_node domain")?;
trace!("Permission domain for awesome_node is verified, domain value: {}", domain);
Ok(());
}
}Try sending transactions with and without the awesome domain included. Only doughnuts which contain domain: "awesome" will be processed successfully.
Getting Started
PL^G Component Guides
- Attestation
- Doughnut
- Generic Assets (coming soon)
Advanced Topics
External Links
