Audited & minimal JS implementation of sr25519 cryptography for Polkadot.
scure — audited micro-libraries.
npm install @scure/sr25519
deno add jsr:@scure/sr25519
We support all major platforms and runtimes.
import * as sr25519 from '@scure/sr25519';
import { randomBytes } from '@noble/hashes/utils.js';
const seed = randomBytes(32);
const secretKey = sr25519.secretFromSeed(seed);
const peerSecretKey = sr25519.secretFromSeed(randomBytes(32));
const msg = new Uint8Array([1, 2, 3]);
const signature = sr25519.sign(secretKey, msg);
const publicKey = sr25519.getPublicKey(secretKey);
const peerPublicKey = sr25519.getPublicKey(peerSecretKey);
const isValid = sr25519.verify(msg, signature, publicKey);
const sharedSecret = sr25519.getSharedSecret(secretKey, peerPublicKey);
import * as sr25519 from '@scure/sr25519';
import { randomBytes } from '@noble/hashes/utils.js';
const seed = randomBytes(32);
const cc = randomBytes(32);
const parentSecret = sr25519.secretFromSeed(seed);
const parentPublic = sr25519.getPublicKey(parentSecret);
// hard
const hardSecret = sr25519.HDKD.secretHard(parentSecret, cc);
const hardPublic = sr25519.getPublicKey(hardSecret);
// soft
const softSecret = sr25519.HDKD.secretSoft(parentSecret, cc);
const softPublic = sr25519.getPublicKey(softSecret);
// public
const derivedPublic = sr25519.HDKD.publicSoft(parentPublic, cc);
import * as sr25519 from '@scure/sr25519';
import { randomBytes } from '@noble/hashes/utils.js';
const seed = randomBytes(32);
const msg = new Uint8Array([1, 2, 3]);
const secretKey = sr25519.secretFromSeed(seed);
const publicKey = sr25519.getPublicKey(secretKey);
const signature = sr25519.vrf.sign(msg, secretKey);
const isValid = sr25519.vrf.verify(msg, signature, publicKey);
fromKeypair() expects the 96-byte schnorrkel "half-Ed25519" layout:
secret.to_ed25519_bytes() || public.to_bytes().
This is the same format used by schnorrkel Keypair::to_half_ed25519_bytes().
The example below uses the same test vector shown in schnorrkel's
Keypair::from_half_ed25519_bytes() docs.
import * as sr25519 from '@scure/sr25519';
import { hexToBytes } from '@noble/hashes/utils.js';
const pair = hexToBytes(
// secret.to_ed25519_bytes()
'28b0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3eb51' +
'fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca34' +
// public.to_bytes()
'46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a'
);
const normalized = sr25519.fromKeypair(pair);
const publicKey = sr25519.getPublicKey(normalized.subarray(0, 64));
We implement only the parts of these protocols that sr25519 requires.
@polkadot/utils-crypto{publicKey, privateKey}, we always return only privateKey,
you can get publicKey via getPublicKeyThe library has been audited:
If you see anything unusual: investigate and report.
Low-level operations are done using noble-curves and noble-hashes. Consult their README for more information about constant-timeness, memory dumping and supply chain security. A few notes:
Benchmark results on Apple M4:
secretFromSeed x 493,827 ops/sec @ 2μs/op
getSharedSecret x 1,135 ops/sec @ 880μs/op
HDKD.secretHard x 54,121 ops/sec @ 18μs/op
HDKD.secretSoft x 4,108 ops/sec @ 243μs/op
HDKD.publicSoft x 4,499 ops/sec @ 222μs/op
sign x 2,475 ops/sec @ 403μs/op
verify x 955 ops/sec @ 1ms/op
vrfSign x 442 ops/sec @ 2ms/op
vrfVerify x 344 ops/sec @ 2ms/op
Comparison with wasm:
secretFromSeed wasm x 21,615 ops/sec @ 46μs/op
getSharedSecret wasm x 6,681 ops/sec @ 149μs/op
HDKD.secretHard wasm x 16,958 ops/sec @ 58μs/op
HDKD.secretSoft wasm x 16,075 ops/sec @ 62μs/op
HDKD.publicSoft wasm x 16,981 ops/sec @ 58μs/op
sign wasm x 16,559 ops/sec @ 60μs/op
verify wasm x 6,741 ops/sec @ 148μs/op
vrfSign wasm x 2,470 ops/sec @ 404μs/op
vrfVerify wasm x 2,917 ops/sec @ 342μs/op
npm install to install build dependencies like TypeScriptnpm run build to compile TypeScript codenpm run test will execute all main testsThe MIT License (MIT)
Copyright (c) 2024 Paul Miller (https://paulmillr.com)
See LICENSE file.