-
Notifications
You must be signed in to change notification settings - 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:
-
Alice
creates a doughnut which sets:-
Alice
as the issuer -
Bob
as the holder - Sending funds to
Charlie
as a rule in the permission domain
-
-
Alice
signs the doughnut and gives it toBob
- When
Bob
wants to send funds toCharlie
withAlice
's account,Bob
should 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
DispatchVerifier
hook 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-doughunut
Use 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