-
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)
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])
.sign(issuer_private_key)
};
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_domain(domain, 0x00)
.sign(issuer_private_key)
.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, "1_500_000_000")
.signAndSend(keyring.bob, options);
In this part we are trying to show an example how to get the domains in the verify_dispatch
function which is implemented in the plug-blockchain.
In the node-template we define DelegatedDispatchVerifier
as DummyDispatchVerifier
.
type DelegatedDispatchVerifier = DummyDispatchVerifier<Self::Doughnut, Self::AccountId>;
So we can modify the verify_dispatch function for DummyDispatchVerifier to print out the domains:
use sp_runtime::{Doughnut, DoughnutV0};
use log::{trace};
fn verify_dispatch(doughnut: &Self::Doughnut, module: &str, method: &str) -> Result<(), &'static str> {
let doughnut = DoughnutV0::try_from(doughnut.clone()).unwrap();
let domain = doughnut.get_domain("awesome_node").unwrap()[0];
trace!("awesome_node domain's value: {}", domain);
Ok(());
}
After send the transfer extrinsic to the local node. We can see the output in the console log.
We can set a permission domain specific for our awesome_node
in the DelegatedDispatchVerifier
implementation for DummyDispatchVerifier. Only doughnut contains awesome_node
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_node";
fn verify_dispatch(doughnut: &Self::Doughnut, module: &str, method: &str) -> 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(());
}
}
We can try to send same kind of transactions included doughnut with different domains. Only doughnut contains permission domain: domain: "awesome_node"
would be verified and the transaction could be processed successfully
Getting Started
PL^G Component Guides
- Attestation
- Doughnut
- Generic Assets (coming soon)
Advanced Topics
External Links