Skip to content

Instantly share code, notes, and snippets.

@nuke-web3
Last active May 24, 2023 02:19
Show Gist options
  • Save nuke-web3/f8a9e80e6b2f9429a7fe7c1e0173c484 to your computer and use it in GitHub Desktop.
Save nuke-web3/f8a9e80e6b2f9429a7fe7c1e0173c484 to your computer and use it in GitHub Desktop.
PBA Signature Demos
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
},
"tags": []
},
"source": [
"# Signing Demo for Web3 Engineers"
]
},
{
"cell_type": "markdown",
"metadata": {
"tags": []
},
"source": [
"## \\[Note on use\\] Reset, keeping build artifacts\n",
"\n",
"Run the below cell or commands in a shell to \"reset\" the kernel, keeping build artifacts:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"/tmp/.tmp3GYuJy\"\n"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
":clear // Clear all state, **keeping compilation cache**, use this over a kernel restart when possible. You will need to re-run the :deps to have them loaded into state. \n",
":last_compile_dir // Show where the target is for cargo for this kernel, in case you want to recover these"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Set the env var `EVCXR_TMPDIR=<the output of :last_compile_dir>` to get the REPL to use this dir.\n",
"\n",
"```sh\n",
"# Launching jupyter-lab\n",
"EVCXR_TMPDIR=<dir from :last_compile_dir> jupyter-lab\n",
"\n",
"# Using Evcxr in a shell\n",
"EVCXR_TMPDIR=<dir from :last_compile_dir> evcxr\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
},
"tags": []
},
"source": [
"## Notebook Kernel & REPL Environment Setup\n",
"\n",
"The below should be run at kernel startup before you start.\n",
"All dependencies that we need to build _before_ anything else in this notebook will work.\n",
"\n",
"Instead of rebuilding see the `Reset, keeping build artifacts` section to clear REPL state only.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
":dep sp-core = { version = \"7.0.0\", git = \"https://github.com/paritytech/substrate.git\", branch = \"polkadot-v0.9.36\" }\n",
"\n",
"// Loading & building dependencies crates here takes *a while*! \n",
"// Run this while you move on to the readings below.\n",
"// NOTE: A kernel restart removes all target artifacts!\n",
"// ONLY restart only if explicitly needed."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
},
"tags": []
},
"source": [
"## Digital Signatures\n",
"\n",
"Here we demonstrate a few parts of the [Substrate Primitives (`sp_core`)](https://paritytech.github.io/substrate/master/sp_core/index.html) library for interacting with keys and signatures."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"use sp_core::{\n",
"\tblake2_256,\n",
"\tcrypto::{Derive, Ss58Codec, Ss58AddressFormatRegistry},\n",
"\tDeriveJunction,\n",
"\thexdisplay::HexDisplay,\n",
"\tPair as _,\n",
"\tsr25519::{Pair, Public},\n",
"};"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Key Generation"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"// Generate a secret key.\n",
"let (pair, mnemonic, _) = Pair::generate_with_phrase(None);"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"hawk false thought wife devote close night pizza cabin novel across melt\""
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Reveal your Secret Seed Phrase\n",
"mnemonic"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"// Derive the public key.\n",
"let pk = pair.public();"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[38, 16, 19, 15, 113, 123, 87, 121, 148, 236, 175, 26, 71, 48, 211, 188, 209, 16, 136, 10, 144, 136, 122, 166, 43, 172, 47, 201, 201, 42, 162, 38]"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Print public key as raw bytes\n",
"pk.0"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"2610130f717b577994ecaf1a4730d3bcd110880a90887aa62bac2fc9c92aa226"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Print public key hex encoded \n",
"<HexDisplay>::from(&pk.0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Signatures"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"// Sign a message.\n",
"let msg = b\"Welcome to Polkadot Blockchain Academy!\";\n",
"let sig = pair.sign(&msg[..]);"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"Welcome to Polkadot Blockchain Academy!\""
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Message:\n",
"std::str::from_utf8(&msg[..]).unwrap()"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"c6ff39639ec55a975391e222254bac992b2d95cc5393c75444c45bdcb812af04468b549b81c1edbb9c4b06c683353dd23636e2199096553ddb40573c8cd2508d"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Signature, hex encoded:\n",
"&sig"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"// Verify the sig. `assert!` will not panic here with a valid sig\n",
"assert!(Pair::verify(&sig, &msg[..], &pk));"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"// Alter the message. `assert!` will panic on msg, here we assert it's fails.\n",
"let tampered = b\"Welcome-to-Polkadot-Blockchain-Academy!\";\n",
"assert!(!Pair::verify(&sig, &tampered[..], &pk));"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Signing & Hashes\n",
"\n",
"The signature process can be resource intensive with large data. We can use a *cryptographic hash function* of the data, which is generally a fixed small size and then sign that hash. This signature, the hashing protocol, and the original data is then used to be as valid to us as the signature on the original data without as much resource use. \n"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"// Signing a message hash example, instead of the full msg.\n",
"let long_msg =\n",
"\tb\"The term \\\"Web3\\\" (AKA Web 3.0) was coined in 2014 by Ethereum co-founder Gavin Wood, is an idea for a new iteration of the World Wide Web which incorporates concepts such as decentralization, blockchain technologies, and token-based economics.\";\n",
"let lm_hash = blake2_256(&long_msg[..]);\n",
"\n",
"let sig_lm_hash = pair.sign(&lm_hash[..]);"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"652c97cbbae2caeb52556680c55096c6a9e9aca8318461f1cb4e74019833ed32"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Hex print hash & signature\n",
"<HexDisplay>::from(&lm_hash)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"// Verify the signature from the same hash of the original message.\n",
"assert!(Pair::verify(&sig_lm_hash, blake2_256(&long_msg[..]), &pk));"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Hard Derivation"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"// Derive new key pairs from the original mnemonic using `//polkadot`.\n",
"let pair_polkadot = Pair::from_string(&format!(\"{}//polkadot\", &mnemonic), None);\n",
"let pk_polkadot = pair_polkadot.unwrap().public();"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"8a780f44284e9b2137ffa1ab94b05ba91cb903976995298e7aab04ed54219956"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Polkadot Public Key, Hex\n",
"<HexDisplay>::from(&pk_polkadot.0)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"148ZGrDNoUxCXk1SzL8SspRsxqW5Xtb7W9AxkhdhTFgQ7QEy\""
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Polkadot Public Key, SS58 for Polkadot\n",
"&pk_polkadot.to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into())"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"// Derive new key pairs from the original mnemonic using `//kusama`.\n",
"let pair_kusama = Pair::from_string(&format!(\"{}//kusama\", &mnemonic), None);\n",
"let pk_kusama = pair_kusama.unwrap().public();"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"fc760ea0f7e45b2c4e1b0624cf953b8de471811a57e73db7514e387e6c78e573"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Kusama Public Key, Hex\n",
"<HexDisplay>::from(&pk_kusama.0)"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"JHLewmVTEEzXwARwCKyFh4syXK98SFUPNQg6YDorRMP3ELx\""
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Kusama Public Key, SS58 for Polkadot\n",
"&pk_kusama.to_ss58check_with_version(Ss58AddressFormatRegistry::KusamaAccount.into())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## SS58 Tool\n",
"\n",
"https://polkadot.subscan.io/tools/ss58_transform\n",
"\n",
"This handy tool can be used to cross check a raw hex encoded pubkey, and see all SS58 variants for them."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Soft Derivation"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [],
"source": [
"// Derive a soft path on the Polkadot key.\n",
"let pair_polkadot_zero = Pair::from_string(&format!(\"{}//polkadot/0\", &mnemonic), None);\n",
"let pk0_from_secret = pair_polkadot_zero.unwrap().public();"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"123MSN6RKzp8Ha16LmajgeUKJ58YAhPV2UBhAhR5Yswjv2zg\""
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Polkadot Soft-Derived Public Key (from secret)\n",
"&pk0_from_secret.to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into())"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [],
"source": [
"// Derive a soft path only using the _public key_ of the //polkadot pair.\n",
"let pk_polkadot: Public = Public(pk_polkadot.0);\n",
"let path0 = vec![DeriveJunction::soft(0u8)];\n",
"let pk0_from_public = pk_polkadot.derive(path0.into_iter()).unwrap();"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"123MSN6RKzp8Ha16LmajgeUKJ58YAhPV2UBhAhR5Yswjv2zg\""
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Polkadot Soft-Derived Public Key (from public only)\n",
"&pk0_from_public.to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into())"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"()"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Keys are identical.\n",
"assert_eq!(pk0_from_secret, pk0_from_public)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Rust",
"language": "rust",
"name": "rust"
},
"language_info": {
"codemirror_mode": "rust",
"file_extension": ".rs",
"mimetype": "text/rust",
"name": "rust",
"pygment_lexer": "rust",
"version": ""
}
},
"nbformat": 4,
"nbformat_minor": 4
}
@nuke-web3
Copy link
Author

nuke-web3 commented Jan 14, 2023

Subkey Signature and HDKD (Hierarchical Deterministic Key Derivation) Demo

All the subkey examples also exist in a jupyter notebook for reference.
As an alternative, here are subkey examples to compliment/replace using the REPL.

Key Generation

subkey generate

Secret phrase:       desert piano add owner tuition tail melt rally height faint thunder immune
  Network ID:        substrate
  Secret seed:       0x6a0ea68072cfd0ffbabb40801570fa5e9f3a88966eaed9dedaeb0cf140b9cd8d
  Public key (hex):  0x7acdc47530002fbc50f413859093b7df90c27874aee732dca940ea4842751d58
  Account ID:        0x7acdc47530002fbc50f413859093b7df90c27874aee732dca940ea4842751d58
  Public key (SS58): 5Eqipnpt5asTm7sCFWQeJjsNJX5cYVJMid3zjKHjDUGKBJTo
  SS58 Address:      5Eqipnpt5asTm7sCFWQeJjsNJX5cYVJMid3zjKHjDUGKBJTo

Sign

echo -n 'Hello Polkadot Blockchain Academy' | subkey sign --suri 'desert piano add owner tuition tail melt rally height faint thunder immune'

Note, this changes each execution, this is one viable signature: f261d56b80e4b53c70dd2ba1de6b9384d85a8f4c6d912fd86acab3439a47992aa85ded04ac55c7525082dcbc815001cd5cc94ec1a907bbd8e3138cfc8a382683

Verify

echo -n 'Hello Polkadot Blockchain Academy' | subkey verify  '0xf261d56b80e4b53c70dd2ba1de6b9384d85a8f4c6d912fd86acab3439a47992aa85ded04ac55c7525082dcbc815001cd5cc94ec1a907bbd8e3138cfc8a382683' \
    '0x7acdc47530002fbc50f413859093b7df90c27874aee732dca940ea4842751d58'

Expect Signature verifies correctly.

Tamper with the Message

Last char in Public key (hex) - AKA URI - is changed:

echo -n 'Hello Polkadot Blockchain Academy' | subkey verify \
	'0xf261d56b80e4b53c70dd2ba1de6b9384d85a8f4c6d912fd86acab3439a47992aa85ded04ac55c7525082dcbc815001cd5cc94ec1a907bbd8e3138cfc8a382683' \
    '0x7acdc47530002fbc50f413859093b7df90c27874aee732dca940ea4842751d59'
Error: SignatureInvalid

Hard Derivation

subkey inspect 'desert piano add owner tuition tail melt rally height faint thunder immune//polkadot' --network polkadot

Secret Key URI `desert piano add owner tuition tail melt rally height faint thunder immune//polkadot` is account:
  Network ID:        polkadot
 Secret seed:       0x3d764056127d0c1b4934725cb9faecf00ed0996daa84d24a903b906f319e06bf
  Public key (hex):  0xce6ccb0af417ade10062ac9b553d506b67d16c61cd2b6ce85330bc023db7e906
  Account ID:        0xce6ccb0af417ade10062ac9b553d506b67d16c61cd2b6ce85330bc023db7e906
  Public key (SS58): 15ffBb8rhETizk36yaevSKM2MCnHyuQ8Dn3HfwQFtLMhy9io
  SS58 Address:      15ffBb8rhETizk36yaevSKM2MCnHyuQ8Dn3HfwQFtLMhy9io
subkey inspect 'desert piano add owner tuition tail melt rally height faint thunder immune//kusama' --network kusama

Secret Key URI `desert piano add owner tuition tail melt rally height faint thunder immune//kusama` is account:
  Network ID:        kusama
 Secret seed:       0xabd92064a63df86174acfd29ab3204897974f0a39f5d61efdd30099aa5f90bd9
  Public key (hex):  0xf62e5d444f89e704bb9b412adc472f990e9a9f40725ac6ff3abee1c9b7625a63
  Account ID:        0xf62e5d444f89e704bb9b412adc472f990e9a9f40725ac6ff3abee1c9b7625a63
  Public key (SS58): J9753RnTdZJct5RmFQ6gFVdKSyrEjzYwvYUBufMX33PB7az
  SS58 Address:      J9753RnTdZJct5RmFQ6gFVdKSyrEjzYwvYUBufMX33PB7az

Soft Derivation from Secret

subkey inspect 'desert piano add owner tuition tail melt rally height faint thunder immune//polkadot/0' --network polkadot

Secret Key URI `desert piano add owner tuition tail melt rally height faint thunder immune//polkadot/0` is account:
  Network ID:        polkadot
 Secret seed:       n/a
  Public key (hex):  0x4e8dfdd8a386ae37b8731dba5480d5cc65739023ea24f1a09d88be1bd9dff86b
  Account ID:        0x4e8dfdd8a386ae37b8731dba5480d5cc65739023ea24f1a09d88be1bd9dff86b
  Public key (SS58): 12mzv68gS8Zu2iEdt4Ktkt48JZSKyFSkAVjvtgYhoa42NLNa
  SS58 Address:      12mzv68gS8Zu2iEdt4Ktkt48JZSKyFSkAVjvtgYhoa42NLNa
subkey inspect 'desert piano add owner tuition tail melt rally height faint thunder immune//polkadot/1' --network polkadot

Secret Key URI `desert piano add owner tuition tail melt rally height faint thunder immune//polkadot/1` is account:
  Network ID:        polkadot
 Secret seed:       n/a
  Public key (hex):  0x2e8b3090b17b12ea63029f03d852af71570e8e526690cc271491318a45785e33
  Account ID:        0x2e8b3090b17b12ea63029f03d852af71570e8e526690cc271491318a45785e33
  Public key (SS58): 1242YwUZGBQ84btGSGdSX4swf1ibfSaCDR1sr1ejC9KQ1NbJ
  SS58 Address:      1242YwUZGBQ84btGSGdSX4swf1ibfSaCDR1sr1ejC9KQ1NbJ

Soft Derivation from Public

Note: We use addresses here because Subkey does not derive paths from a raw public key (AFAIK).

subkey inspect 12mzv68gS8Zu2iEdt4Ktkt48JZSKyFSkAVjvtgYhoa42NLNa/0

Public Key URI `12mzv68gS8Zu2iEdt4Ktkt48JZSKyFSkAVjvtgYhoa42NLNa/0` is account:
  Network ID/Version: polkadot
  Public key (hex):   0x40f22875159420aca51178d1baf2912c18dcb83737dd7bd39dc6743da326dd1c
  Account ID:         0x40f22875159420aca51178d1baf2912c18dcb83737dd7bd39dc6743da326dd1c
  Public key (SS58):  12UA12xuDnEkEsEDrR4T4Cf3S1Hyi2C7B6hJW8LTkcsZy8BX
  SS58 Address:       12UA12xuDnEkEsEDrR4T4Cf3S1Hyi2C7B6hJW8LTkcsZy8BX
subkey inspect 12mzv68gS8Zu2iEdt4Ktkt48JZSKyFSkAVjvtgYhoa42NLNa/1

Public Key URI `12mzv68gS8Zu2iEdt4Ktkt48JZSKyFSkAVjvtgYhoa42NLNa/1` is account:
  Network ID/Version: polkadot
  Public key (hex):   0xc62ec5cd7d83e1f41462d455bb47b6bad9ed5a14741a920ead8366c63746391b
  Account ID:         0xc62ec5cd7d83e1f41462d455bb47b6bad9ed5a14741a920ead8366c63746391b
  Public key (SS58):  15UrNnNSMpX49F3mWcCX7y4kMGcvnQxCabLMT3d8U5abpwr3
  SS58 Address:       15UrNnNSMpX49F3mWcCX7y4kMGcvnQxCabLMT3d8U5abpwr3

@nuke-web3
Copy link
Author

paritytech/substrate#13258 update to behavior, see the sh script as an example of use there too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment