diff --git a/.gitignore b/.gitignore index 0bc8c0069..4be171fa4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ _star.ts benches.json target examples/generated -bindings/*/snippets +bindings/**/snippets diff --git a/Cargo.lock b/Cargo.lock index f617542a7..c0e762458 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,6 +157,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +dependencies = [ + "digest 0.10.3", +] + [[package]] name = "blake2-rfc" version = "0.2.18" @@ -188,6 +197,15 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array 0.14.5", +] + [[package]] name = "block-padding" version = "0.1.5" @@ -288,6 +306,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array 0.14.5", + "typenum", +] + [[package]] name = "crypto-mac" version = "0.7.0" @@ -388,6 +416,17 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", + "subtle 2.4.1", +] + [[package]] name = "dyn-clonable" version = "0.9.0" @@ -971,6 +1010,7 @@ name = "mod" version = "0.1.0" dependencies = [ "base58 0.2.0", + "blake2", "console_error_panic_hook", "frame-support", "getrandom 0.2.5", diff --git a/Cargo.toml b/Cargo.toml index 44fcab569..22cf6d3a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ all = ["hashers", "sr25519", "ss58"] [dependencies] base58 = { version = "0.2.0" } +blake2 = "0.10.4" console_error_panic_hook = { version = "0.1.7", optional = true } frame-support = { version = "3.0.0" } getrandom = { version = "0.2.5", features = ["js"] } diff --git a/_deps/ss58_registry.ts b/_deps/ss58_registry.ts new file mode 100644 index 000000000..7ee2b55bc --- /dev/null +++ b/_deps/ss58_registry.ts @@ -0,0 +1 @@ +export { default } from "https://esm.sh/v85/@substrate/ss58-registry@1.22.0/es2022/ss58-registry.js"; diff --git a/_tasks/build_ss58_registry_literal_type.ts b/_tasks/build_ss58_registry_literal_type.ts new file mode 100644 index 000000000..5de628f3d --- /dev/null +++ b/_tasks/build_ss58_registry_literal_type.ts @@ -0,0 +1,24 @@ +import * as path from "../_deps/path.ts"; +import ss58Registry from "../_deps/ss58_registry.ts"; + +let generated = "// @generated file from build script, do not edit"; +generated += "\nexport type Ss58Registry = "; +for (let i = 0; i < ss58Registry.length; i++) { + const current = ss58Registry[i]!; + if (i !== 0) { + generated += " | "; + } + generated += `{ + prefix: ${current.prefix}; + network: "${current.network}"; + displayName: "${current.displayName}"; + symbols: [${current.symbols.map((s) => `"${s}"`).join(", ")}]; + decimals: [${current.decimals.join(", ")}]; + standardAccount: "${current.standardAccount}"; + website: "${current.website}"; +}`; +} +generated += ";\n"; +const dest = path.join(Deno.cwd(), "bindings/ss58/registry.ts"); +console.log(`Writing "registry" file to "${dest}".`); +await Deno.writeTextFile(dest, generated); diff --git a/bindings/hashers.rs b/bindings/hashers/mod.rs similarity index 94% rename from bindings/hashers.rs rename to bindings/hashers/mod.rs index c3bc19997..c7f936c56 100644 --- a/bindings/hashers.rs +++ b/bindings/hashers/mod.rs @@ -18,6 +18,7 @@ macro_rules! make_hasher_binding { }; } +// TODO: in what cases can these error out? make_hasher_binding!(blake2_128, _blake2_128); make_hasher_binding!(blake2_256, _blake2_256); make_hasher_binding!(blake2_128Concat, ::hash); diff --git a/bindings/hashers/mod.ts b/bindings/hashers/mod.ts new file mode 100644 index 000000000..9b6183313 --- /dev/null +++ b/bindings/hashers/mod.ts @@ -0,0 +1,15 @@ +import * as M from "../../frame_metadata/mod.ts"; +import { instantiate } from "./mod.generated.js"; + +export async function Hashers(): Promise { + const instance = await instantiate(); + return { + Blake2_128: instance.blake2_128, + Blake2_128Concat: instance.blake2_128Concat, + Blake2_256: instance.blake2_256, + Identity: (x) => x, + Twox128: instance.twox128, + Twox256: instance.twox256, + Twox64Concat: instance.twox64Concat, + }; +} diff --git a/bindings/hashers/mod_bg.wasm b/bindings/hashers/mod_bg.wasm index 20a2e59fb..e06ff192f 100644 Binary files a/bindings/hashers/mod_bg.wasm and b/bindings/hashers/mod_bg.wasm differ diff --git a/bindings/mod.ts b/bindings/mod.ts index a58fd8eca..9bcc1925d 100644 --- a/bindings/mod.ts +++ b/bindings/mod.ts @@ -1,19 +1,3 @@ -import * as M from "../frame_metadata/mod.ts"; - -import * as hasherBindings from "./hashers/mod.generated.js"; - -export { instantiate as getSr25519 } from "./sr25519/mod.generated.js"; -export { instantiate as getSs58 } from "./ss58/mod.generated.js"; - -export async function getHashers(): Promise { - await hasherBindings.instantiate(); - return { - Blake2_128: hasherBindings.blake2_128, - Blake2_128Concat: hasherBindings.blake2_128Concat, - Blake2_256: hasherBindings.blake2_256, - Identity: (x) => x, - Twox128: hasherBindings.twox128, - Twox256: hasherBindings.twox256, - Twox64Concat: hasherBindings.twox64Concat, - }; -} +export { Hashers } from "./hashers/mod.ts"; +export { Sr25519 } from "./sr25519/mod.ts"; +export { Ss58 } from "./ss58/mod.ts"; diff --git a/bindings/sr25519.rs b/bindings/sr25519.rs deleted file mode 100644 index 5c1784282..000000000 --- a/bindings/sr25519.rs +++ /dev/null @@ -1,58 +0,0 @@ -use {js_sys::Uint8Array, schnorrkel as s, sp_core::sr25519::Signature, wasm_bindgen::prelude::*}; - -const SIGNING_CTX: &'static [u8] = b"capi"; - -#[wasm_bindgen] -pub struct Pair(s::Keypair); - -#[wasm_bindgen] -impl Pair { - #[wasm_bindgen(constructor)] - pub fn new(pub_key_bytes: &[u8], priv_key_bytes: &[u8]) -> Self { - let bytes = [pub_key_bytes, priv_key_bytes].concat(); - Self(s::Keypair::from_bytes(&bytes).unwrap()) - } - - #[wasm_bindgen(js_name = pubKey, getter)] - pub fn get_pub_key(&self) -> Uint8Array { - self.0.public.to_bytes()[..].into() - } - - #[wasm_bindgen(js_name = secretKey, getter)] - pub fn get_secret_key(&self) -> Uint8Array { - self.0.secret.to_bytes()[..].into() - } -} - -#[wasm_bindgen(js_name = pairFromSecretSeed)] -pub fn pair_from_secret_seed(bytes: &[u8]) -> Pair { - let mini_secret_result = s::MiniSecretKey::from_bytes(bytes); - let mini_secret = mini_secret_result.unwrap(); - let s_pair = mini_secret.expand_to_keypair(s::ExpansionMode::Ed25519); - Pair(s_pair) -} - -#[wasm_bindgen] -pub fn sign(pub_key_bytes: &[u8], secret_key_bytes: &[u8], message: &[u8]) -> Uint8Array { - let pub_key = s::PublicKey::from_bytes(pub_key_bytes).unwrap(); - let secret_key = s::SecretKey::from_bytes(secret_key_bytes).unwrap(); - let data = secret_key - .sign_simple(SIGNING_CTX, message, &pub_key) - .to_bytes() - .to_vec(); - let mut inner = [0u8; 64]; - inner.copy_from_slice(&data); - Signature(inner).0[..].into() -} - -#[wasm_bindgen] -pub fn verify(signature: &[u8], message: &[u8], pub_key: &[u8]) -> bool { - s::PublicKey::from_bytes(pub_key) - .unwrap() - .verify_simple( - SIGNING_CTX, - &message, - &s::Signature::from_bytes(signature).unwrap(), - ) - .is_ok() -} diff --git a/bindings/sr25519/mod.generated.js b/bindings/sr25519/mod.generated.js index f7e7e760a..b8f1e27a5 100644 --- a/bindings/sr25519/mod.generated.js +++ b/bindings/sr25519/mod.generated.js @@ -3,27 +3,30 @@ // deno-lint-ignore-file let wasm; -const heap = new Array(32).fill(undefined); +const cachedTextDecoder = new TextDecoder("utf-8", { + ignoreBOM: true, + fatal: true, +}); -heap.push(undefined, null, true, false); +cachedTextDecoder.decode(); -function getObject(idx) { - return heap[idx]; +let cachedUint8Memory0; +function getUint8Memory0() { + if (cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; } -let heap_next = heap.length; - -function dropObject(idx) { - if (idx < 36) return; - heap[idx] = heap_next; - heap_next = idx; +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); } -function takeObject(idx) { - const ret = getObject(idx); - dropObject(idx); - return ret; -} +const heap = new Array(32).fill(undefined); + +heap.push(undefined, null, true, false); + +let heap_next = heap.length; function addHeapObject(obj) { if (heap_next === heap.length) heap.push(heap.length + 1); @@ -34,23 +37,20 @@ function addHeapObject(obj) { return idx; } -const cachedTextDecoder = new TextDecoder("utf-8", { - ignoreBOM: true, - fatal: true, -}); - -cachedTextDecoder.decode(); +function getObject(idx) { + return heap[idx]; +} -let cachedUint8Memory0; -function getUint8Memory0() { - if (cachedUint8Memory0.byteLength === 0) { - cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); - } - return cachedUint8Memory0; +function dropObject(idx) { + if (idx < 36) return; + heap[idx] = heap_next; + heap_next = idx; } -function getStringFromWasm0(ptr, len) { - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; } let WASM_VECTOR_LEN = 0; @@ -61,17 +61,14 @@ function passArray8ToWasm0(arg, malloc) { WASM_VECTOR_LEN = arg.length; return ptr; } -/** - * @param {Uint8Array} bytes - * @returns {Pair} - */ -export function pairFromSecretSeed(bytes) { - const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.pairFromSecretSeed(ptr0, len0); - return Pair.__wrap(ret); -} +let cachedInt32Memory0; +function getInt32Memory0() { + if (cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachedInt32Memory0; +} /** * @param {Uint8Array} pub_key_bytes * @param {Uint8Array} secret_key_bytes @@ -89,23 +86,6 @@ export function sign(pub_key_bytes, secret_key_bytes, message) { return takeObject(ret); } -/** - * @param {Uint8Array} signature - * @param {Uint8Array} message - * @param {Uint8Array} pub_key - * @returns {boolean} - */ -export function verify(signature, message, pub_key) { - const ptr0 = passArray8ToWasm0(signature, wasm.__wbindgen_malloc); - const len0 = WASM_VECTOR_LEN; - const ptr1 = passArray8ToWasm0(message, wasm.__wbindgen_malloc); - const len1 = WASM_VECTOR_LEN; - const ptr2 = passArray8ToWasm0(pub_key, wasm.__wbindgen_malloc); - const len2 = WASM_VECTOR_LEN; - const ret = wasm.verify(ptr0, len0, ptr1, len1, ptr2, len2); - return ret !== 0; -} - function handleError(f, args) { try { return f.apply(this, args); @@ -118,66 +98,197 @@ function getArrayU8FromWasm0(ptr, len) { return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); } -const PairFinalization = new FinalizationRegistry((ptr) => - wasm.__wbg_pair_free(ptr) +const KeypairFinalization = new FinalizationRegistry((ptr) => + wasm.__wbg_keypair_free(ptr) ); /** */ -export class Pair { +export class Keypair { static __wrap(ptr) { - const obj = Object.create(Pair.prototype); + const obj = Object.create(Keypair.prototype); obj.ptr = ptr; - PairFinalization.register(obj, obj.ptr, obj); + KeypairFinalization.register(obj, obj.ptr, obj); return obj; } __destroy_into_raw() { const ptr = this.ptr; this.ptr = 0; - PairFinalization.unregister(this); + KeypairFinalization.unregister(this); return ptr; } free() { const ptr = this.__destroy_into_raw(); - wasm.__wbg_pair_free(ptr); + wasm.__wbg_keypair_free(ptr); } /** - * @param {Uint8Array} pub_key_bytes - * @param {Uint8Array} priv_key_bytes + * @param {Uint8Array} bytes + * @returns {Keypair} */ - constructor(pub_key_bytes, priv_key_bytes) { - const ptr0 = passArray8ToWasm0(pub_key_bytes, wasm.__wbindgen_malloc); - const len0 = WASM_VECTOR_LEN; - const ptr1 = passArray8ToWasm0(priv_key_bytes, wasm.__wbindgen_malloc); - const len1 = WASM_VECTOR_LEN; - const ret = wasm.pair_new(ptr0, len0, ptr1, len1); - return Pair.__wrap(ret); + static fromSecretSeed(bytes) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.keypair_fromSecretSeed(retptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Keypair.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {string} + */ + get publicKey() { + const ret = wasm.keypair_publicKey(this.ptr); + return takeObject(ret); + } + /** + * @returns {string} + */ + get secretKey() { + const ret = wasm.keypair_secretKey(this.ptr); + return takeObject(ret); + } +} + +const PublicKeyFinalization = new FinalizationRegistry((ptr) => + wasm.__wbg_publickey_free(ptr) +); +/** */ +export class PublicKey { + static __wrap(ptr) { + const obj = Object.create(PublicKey.prototype); + obj.ptr = ptr; + PublicKeyFinalization.register(obj, obj.ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.ptr; + this.ptr = 0; + PublicKeyFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_publickey_free(ptr); + } + /** + * @param {Uint8Array} bytes + * @returns {PublicKey} + */ + static from(bytes) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.publickey_from(retptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return PublicKey.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } } /** * @returns {Uint8Array} */ - get pubKey() { - const ret = wasm.pair_pubKey(this.ptr); + get bytes() { + const ret = wasm.publickey_bytes(this.ptr); return takeObject(ret); } /** + * @param {Uint8Array} secret_key_bytes + * @returns {Signer} + */ + signer(secret_key_bytes) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray8ToWasm0(secret_key_bytes, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.publickey_signer(retptr, this.ptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Signer.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {Uint8Array} signature + * @param {Uint8Array} message + * @returns {boolean} + */ + verify(signature, message) { + const ptr0 = passArray8ToWasm0(signature, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passArray8ToWasm0(message, wasm.__wbindgen_malloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.publickey_verify(this.ptr, ptr0, len0, ptr1, len1); + return ret !== 0; + } +} + +const SignerFinalization = new FinalizationRegistry((ptr) => + wasm.__wbg_signer_free(ptr) +); +/** */ +export class Signer { + static __wrap(ptr) { + const obj = Object.create(Signer.prototype); + obj.ptr = ptr; + SignerFinalization.register(obj, obj.ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.ptr; + this.ptr = 0; + SignerFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_signer_free(ptr); + } + /** + * @param {Uint8Array} message * @returns {Uint8Array} */ - get secretKey() { - const ret = wasm.pair_secretKey(this.ptr); + sign(message) { + const ptr0 = passArray8ToWasm0(message, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.signer_sign(this.ptr, ptr0, len0); return takeObject(ret); } } const imports = { __wbindgen_placeholder__: { + __wbindgen_error_new: function (arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, __wbindgen_object_drop_ref: function (arg0) { takeObject(arg0); }, - __wbindgen_is_undefined: function (arg0) { - const ret = getObject(arg0) === undefined; - return ret; - }, __wbindgen_is_object: function (arg0) { const val = getObject(arg0); const ret = typeof (val) === "object" && val !== null; @@ -231,6 +342,10 @@ const imports = { const ret = new Function(getStringFromWasm0(arg0, arg1)); return addHeapObject(ret); }, + __wbindgen_string_new: function (arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }, __wbindgen_object_clone_ref: function (arg0) { const ret = getObject(arg0); return addHeapObject(ret); @@ -269,6 +384,10 @@ const imports = { return addHeapObject(ret); }, arguments); }, + __wbindgen_is_undefined: function (arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }, __wbg_newwithbyteoffsetandlength_278ec7532799393a: function ( arg0, arg1, @@ -324,7 +443,7 @@ let lastLoadPromise; * loaded it will always return a reference to the same object. * @returns {Promise<{ * instance: WebAssembly.Instance; - * exports: { pairFromSecretSeed: typeof pairFromSecretSeed; sign: typeof sign; verify: typeof verify; Pair : typeof Pair } + * exports: { sign: typeof sign; Keypair : typeof Keypair ; PublicKey : typeof PublicKey ; Signer : typeof Signer } * }>} */ export function instantiateWithInstance() { @@ -340,7 +459,7 @@ export function instantiateWithInstance() { cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); instanceWithExports = { instance, - exports: { pairFromSecretSeed, sign, verify, Pair }, + exports: { sign, Keypair, PublicKey, Signer }, }; return instanceWithExports; } finally { @@ -388,5 +507,3 @@ async function instantiateModule() { throw new Error(`Unsupported protocol: ${wasm_url.protocol}`); } } - -let cachedInt32Memory0; diff --git a/bindings/sr25519/mod.rs b/bindings/sr25519/mod.rs new file mode 100644 index 000000000..4dfa9550d --- /dev/null +++ b/bindings/sr25519/mod.rs @@ -0,0 +1,113 @@ +use { + js_sys::{JsString, Uint8Array}, + schnorrkel as s, + sp_core::sr25519::Signature, + wasm_bindgen::prelude::*, +}; + +const SIGNING_CTX: &'static [u8] = b"capi"; + +const PUBLIC_KEY_INIT_ERROR: &'static str = "PublicKeyInitError"; +const INVALID_SECRET_KEY_ERROR: &'static str = "InvalidSecretKeyError"; + +#[wasm_bindgen] +pub struct PublicKey(s::PublicKey); +#[wasm_bindgen] +impl PublicKey { + #[wasm_bindgen(js_name = "from")] + pub fn from(bytes: &[u8]) -> Result { + match s::PublicKey::from_bytes(bytes) { + Ok(public_key) => Ok(Self(public_key)), + Err(_) => Err(JsError::new(PUBLIC_KEY_INIT_ERROR)), + } + } + + #[wasm_bindgen(js_name = bytes, getter)] + pub fn get_bytes(&self) -> Uint8Array { + self.0.to_bytes()[..].into() + } + + #[wasm_bindgen] + pub fn signer(&self, secret_key_bytes: &[u8]) -> Result { + match s::SecretKey::from_bytes(secret_key_bytes) { + Ok(secret) => Ok(Signer(s::Keypair { + public: self.0, + secret, + })), + Err(_) => Err(JsError::new(INVALID_SECRET_KEY_ERROR)), + } + } + + // TODO: do we want to produce an error instead of `false`? + #[wasm_bindgen] + pub fn verify(&self, signature: &[u8], message: &[u8]) -> bool { + let signature_result = self.0.verify_simple( + SIGNING_CTX, + &message, + &s::Signature::from_bytes(signature).unwrap(), + ); + match signature_result { + Ok(_) => true, + Err(_) => false, + } + } +} + +#[wasm_bindgen] +pub struct Signer(s::Keypair); +#[wasm_bindgen] +impl Signer { + #[wasm_bindgen] + pub fn sign(&self, message: &[u8]) -> Uint8Array { + let data = self + .0 + .secret + .sign_simple(SIGNING_CTX, message, &self.0.public) + .to_bytes() + .to_vec(); + let mut inner = [0u8; 64]; + inner.copy_from_slice(&data); + Signature(inner).0[..].into() + } +} + +const FAILED_TO_INIT_MINI_SECRET: &'static str = "FailedToInitMiniSecret"; + +// // TODO: delete this +#[wasm_bindgen] +pub struct Keypair(s::Keypair); +#[wasm_bindgen] +impl Keypair { + #[wasm_bindgen(js_name = fromSecretSeed)] + pub fn from_secret_seed(bytes: &[u8]) -> Result { + match s::MiniSecretKey::from_bytes(bytes) { + Ok(mini_secret) => Ok(Keypair( + mini_secret.expand_to_keypair(s::ExpansionMode::Ed25519), + )), + Err(_) => Err(JsError::new(FAILED_TO_INIT_MINI_SECRET)), + } + } + + #[wasm_bindgen(js_name = publicKey, getter)] + pub fn get_public_key(&self) -> JsString { + hex::encode(self.0.public.to_bytes()).into() + } + + #[wasm_bindgen(js_name = secretKey, getter)] + pub fn get_secret_key(&self) -> JsString { + hex::encode(self.0.secret.to_bytes()).into() + } +} + +#[wasm_bindgen] +pub fn sign(pub_key_bytes: &[u8], secret_key_bytes: &[u8], message: &[u8]) -> Uint8Array { + let pub_key = s::PublicKey::from_bytes(pub_key_bytes).unwrap(); + let secret_key = s::SecretKey::from_bytes(secret_key_bytes).unwrap(); + let data = secret_key + .sign_simple(SIGNING_CTX, message, &pub_key) + .to_bytes() + .to_vec(); + let mut inner = [0u8; 64]; + inner.copy_from_slice(&data); + Signature(inner).0[..].into() +} diff --git a/bindings/sr25519/mod.ts b/bindings/sr25519/mod.ts new file mode 100644 index 000000000..514e5bd0e --- /dev/null +++ b/bindings/sr25519/mod.ts @@ -0,0 +1,41 @@ +// TODO: brands +// TODO: narrow error types +import { instantiate } from "./mod.generated.js"; + +export interface PublicKey { + bytes: Uint8Array; + signer(secretKey: Uint8Array): Signer; + verify( + signature: Uint8Array, + message: Uint8Array, + ): boolean; +} +export type PublicKeyCtor = + & { from(bytes: Uint8Array): PublicKey } + & (new() => PublicKey); + +export interface Signer { + sign(message: Uint8Array): Uint8Array; +} +export type SignerCtor = new() => Signer; + +export interface Keypair { + publicKey: string; + secretKey: string; +} +export type KeypairCtor = + & (new() => Keypair) + & { fromSecretSeed(bytes: Uint8Array): Keypair }; + +export interface Sr25519 { + PublicKey: PublicKeyCtor; + Keypair: KeypairCtor; +} + +export async function Sr25519(): Promise { + const instance = await instantiate(); + return { + PublicKey: instance.PublicKey, + Keypair: instance.Keypair, + }; +} diff --git a/bindings/sr25519/mod_bg.wasm b/bindings/sr25519/mod_bg.wasm index 5962b2f28..e3a20ecef 100644 Binary files a/bindings/sr25519/mod_bg.wasm and b/bindings/sr25519/mod_bg.wasm differ diff --git a/bindings/ss58.rs b/bindings/ss58.rs deleted file mode 100644 index 5c2984705..000000000 --- a/bindings/ss58.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Not much here yet - -use { - base58::{FromBase58, ToBase58}, - js_sys::Uint8Array, - wasm_bindgen::prelude::*, -}; - -#[wasm_bindgen(js_name = base58Encode)] -pub fn base58_encode(data: &[u8]) -> Uint8Array { - console_error_panic_hook::set_once(); - ToBase58::to_base58(&data[..]).as_bytes().into() -} - -#[wasm_bindgen(js_name = decodeSs58Text)] // TODO: with prefix -pub fn decode_Ss58Text(addr: &str) -> Uint8Array { - console_error_panic_hook::set_once(); - let address_bytes = addr.from_base58().unwrap(); - let len = address_bytes.len(); - if len == 35 { - let hex_public_key = &address_bytes[1..33]; - hex::encode(hex_public_key).as_bytes().into() - } else { - unimplemented!() - } -} diff --git a/bindings/ss58/mod.generated.js b/bindings/ss58/mod.generated.js index f72699fcf..ad4b2d534 100644 --- a/bindings/ss58/mod.generated.js +++ b/bindings/ss58/mod.generated.js @@ -55,23 +55,6 @@ function addHeapObject(obj) { let WASM_VECTOR_LEN = 0; -function passArray8ToWasm0(arg, malloc) { - const ptr = malloc(arg.length * 1); - getUint8Memory0().set(arg, ptr / 1); - WASM_VECTOR_LEN = arg.length; - return ptr; -} -/** - * @param {Uint8Array} data - * @returns {Uint8Array} - */ -export function base58Encode(data) { - const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.base58Encode(ptr0, len0); - return takeObject(ret); -} - const cachedTextEncoder = new TextEncoder("utf-8"); const encodeString = function (arg, view) { @@ -114,20 +97,6 @@ function passStringToWasm0(arg, malloc, realloc) { WASM_VECTOR_LEN = offset; return ptr; } -/** - * @param {string} addr - * @returns {Uint8Array} - */ -export function decodeSs58Text(addr) { - const ptr0 = passStringToWasm0( - addr, - wasm.__wbindgen_malloc, - wasm.__wbindgen_realloc, - ); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.decodeSs58Text(ptr0, len0); - return takeObject(ret); -} let cachedInt32Memory0; function getInt32Memory0() { @@ -136,12 +105,82 @@ function getInt32Memory0() { } return cachedInt32Memory0; } +/** + * @param {string} text + * @returns {Array} + */ +export function decode(text) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0( + text, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len0 = WASM_VECTOR_LEN; + wasm.decode(retptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** + * @param {number} prefix + * @param {string} pub_key + * @returns {string} + */ +export function encode(prefix, pub_key) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0( + pub_key, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len0 = WASM_VECTOR_LEN; + wasm.encode(retptr, prefix, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + var ptr1 = r0; + var len1 = r1; + if (r3) { + ptr1 = 0; + len1 = 0; + throw takeObject(r2); + } + return getStringFromWasm0(ptr1, len1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(ptr1, len1); + } +} const imports = { __wbindgen_placeholder__: { __wbindgen_object_drop_ref: function (arg0) { takeObject(arg0); }, + __wbindgen_error_new: function (arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, + __wbindgen_number_new: function (arg0) { + const ret = arg0; + return addHeapObject(ret); + }, + __wbindgen_string_new: function (arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }, __wbg_new_693216e109162396: function () { const ret = new Error(); return addHeapObject(ret); @@ -164,29 +203,17 @@ const imports = { wasm.__wbindgen_free(arg0, arg1); } }, - __wbg_buffer_5e74a88a1424a2e0: function (arg0) { - const ret = getObject(arg0).buffer; + __wbg_new_16f24b0728c5e67b: function () { + const ret = new Array(); return addHeapObject(ret); }, - __wbg_newwithbyteoffsetandlength_278ec7532799393a: function ( - arg0, - arg1, - arg2, - ) { - const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0); - return addHeapObject(ret); - }, - __wbg_new_e3b800e570795b3c: function (arg0) { - const ret = new Uint8Array(getObject(arg0)); - return addHeapObject(ret); + __wbg_push_a72df856079e6930: function (arg0, arg1) { + const ret = getObject(arg0).push(getObject(arg1)); + return ret; }, __wbindgen_throw: function (arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)); }, - __wbindgen_memory: function () { - const ret = wasm.memory; - return addHeapObject(ret); - }, }, }; @@ -208,7 +235,7 @@ let lastLoadPromise; * loaded it will always return a reference to the same object. * @returns {Promise<{ * instance: WebAssembly.Instance; - * exports: { base58Encode: typeof base58Encode; decodeSs58Text: typeof decodeSs58Text } + * exports: { decode: typeof decode; encode: typeof encode } * }>} */ export function instantiateWithInstance() { @@ -224,7 +251,7 @@ export function instantiateWithInstance() { cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); instanceWithExports = { instance, - exports: { base58Encode, decodeSs58Text }, + exports: { decode, encode }, }; return instanceWithExports; } finally { diff --git a/bindings/ss58/mod.rs b/bindings/ss58/mod.rs new file mode 100644 index 000000000..1cbf2dbf7 --- /dev/null +++ b/bindings/ss58/mod.rs @@ -0,0 +1,85 @@ +// Much of the following is adapted from `https://github.com/shamilsan/ss58.org` +use { + base58::{FromBase58, ToBase58}, + blake2::{Blake2b512, Digest}, + js_sys::Array, + wasm_bindgen::prelude::*, +}; + +const BASE58_DECODING_FAILED: &'static str = "Base58DecodingFailed"; +const INVALID_LEN: &'static str = "InvalidLen"; +const INVALID_CHECKSUM: &'static str = "InvalidChecksum"; + +#[wasm_bindgen] +pub fn decode(text: &str) -> Result { + console_error_panic_hook::set_once(); + match text.from_base58() { + Ok(addr) => { + let len = addr.len(); + if !(35..=36).contains(&len) { + Err(JsError::new(INVALID_LEN)) + } else { + let checksum = &addr[len - 2..len]; + let mut hasher = Blake2b512::new(); + hasher.update(b"SS58PRE"); + hasher.update(&addr[0..len - 2]); + if checksum != &hasher.finalize()[0..2] { + return Err(JsError::new(INVALID_CHECKSUM)); + } + let key = hex::encode(&addr[len - 34..len - 2]); + let prefix_buf = &addr[0..len - 34]; + let prefix = if prefix_buf.len() == 1 { + prefix_buf[0] as u16 + } else { + let prefix_hi = ((prefix_buf[1] & 0x3F) as u16) << 8; + let prefix_lo = ((prefix_buf[0] << 2) | (prefix_buf[1] >> 6)) as u16; + prefix_hi | prefix_lo + }; + let result = Array::new(); + result.push(&JsValue::from(prefix)); + result.push(&JsValue::from(key)); + Ok(result) + } + } + Err(_) => Err(JsError::new(BASE58_DECODING_FAILED)), + } +} + +const HEX_DECODING_FAILED: &'static str = "HexDecodingFailed"; +const INVALID_PUB_KEY_LEN: &'static str = "InvalidPubKeyLen"; + +#[wasm_bindgen] +pub fn encode(prefix: u16, pub_key: &str) -> Result { + match hex::decode(pub_key) { + Err(_) => Err(JsError::new(HEX_DECODING_FAILED)), + Ok(mut raw_key) => { + if raw_key.len() != 32 { + Err(JsError::new(INVALID_PUB_KEY_LEN)) + } else { + let mut hasher = Blake2b512::new(); + hasher.update(b"SS58PRE"); + let simple_prefix: u8 = (prefix & 0x3F) as _; + let full_prefix = 0x4000 | ((prefix >> 8) & 0x3F) | ((prefix & 0xFF) << 6); + let prefix_hi: u8 = (full_prefix >> 8) as _; + let prefix_low: u8 = (full_prefix & 0xFF) as _; + if prefix == simple_prefix as u16 { + hasher.update(&[simple_prefix]); + } else { + hasher.update(&[prefix_hi]); + hasher.update(&[prefix_low]); + } + hasher.update(&raw_key); + let mut addr_bytes: Vec = Vec::with_capacity(64); + if prefix == simple_prefix as u16 { + addr_bytes.push(simple_prefix); + } else { + addr_bytes.push(prefix_hi); + addr_bytes.push(prefix_low); + } + addr_bytes.append(&mut raw_key); + addr_bytes.extend_from_slice(&hasher.finalize()[0..2]); + Ok(addr_bytes[..].to_base58()) + } + } + } +} diff --git a/bindings/ss58/mod.test.ts b/bindings/ss58/mod.test.ts new file mode 100644 index 000000000..24dd9a1ed --- /dev/null +++ b/bindings/ss58/mod.test.ts @@ -0,0 +1,20 @@ +import * as asserts from "../../_deps/asserts.ts"; +import { Ss58 } from "./mod.ts"; + +const ss58 = await Ss58(); + +Deno.test("Decode Ss58 Text", () => { + const decoded = ss58.decode("12Y8b4C9ar162cBgycxYgxxHG7cLVs8gre9Y5xeMjW3izqer"); + asserts.assertEquals(decoded, [ + 0, + "43fa61b298e82f9f207ddea327900cee26b554756c4a533f36cd875e3e7bcf06", + ]); +}); + +Deno.test("Encode Ss58 Text", () => { + const encoded = ss58.encode( + 0, + "43fa61b298e82f9f207ddea327900cee26b554756c4a533f36cd875e3e7bcf06", + ); + asserts.assertEquals(encoded, "12Y8b4C9ar162cBgycxYgxxHG7cLVs8gre9Y5xeMjW3izqer"); +}); diff --git a/bindings/ss58/mod.ts b/bindings/ss58/mod.ts new file mode 100644 index 000000000..7f46f76fc --- /dev/null +++ b/bindings/ss58/mod.ts @@ -0,0 +1,17 @@ +// TODO: brands +// TODO: narrow error typings +import { instantiate } from "./mod.generated.js"; + +// TODO: `encodeBuf` and `decodeBuf`, if useful. +export interface Ss58 { + encode(prefix: number, pubKey: string): string; + decode(text: string): [number, string]; +} + +export async function Ss58(): Promise { + const instance = await instantiate(); + return { + encode: instance.encode, + decode: instance.decode as Ss58["decode"], + }; +} diff --git a/bindings/ss58/mod_bg.wasm b/bindings/ss58/mod_bg.wasm index f4305b85b..ae908afcb 100644 Binary files a/bindings/ss58/mod_bg.wasm and b/bindings/ss58/mod_bg.wasm differ diff --git a/bindings/ss58/registry.ts b/bindings/ss58/registry.ts new file mode 100644 index 000000000..b586869a7 --- /dev/null +++ b/bindings/ss58/registry.ts @@ -0,0 +1,802 @@ +// @generated file from build script, do not edit +export type Ss58Registry = { + prefix: 0; + network: "polkadot"; + displayName: "Polkadot Relay Chain"; + symbols: ["DOT"]; + decimals: [10]; + standardAccount: "*25519"; + website: "https://polkadot.network"; +} | { + prefix: 1; + network: "BareSr25519"; + displayName: "Bare 32-bit Schnorr/Ristretto (S/R 25519) public key."; + symbols: []; + decimals: []; + standardAccount: "Sr25519"; + website: "null"; +} | { + prefix: 2; + network: "kusama"; + displayName: "Kusama Relay Chain"; + symbols: ["KSM"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://kusama.network"; +} | { + prefix: 3; + network: "BareEd25519"; + displayName: "Bare 32-bit Ed25519 public key."; + symbols: []; + decimals: []; + standardAccount: "Ed25519"; + website: "null"; +} | { + prefix: 4; + network: "katalchain"; + displayName: "Katal Chain"; + symbols: []; + decimals: []; + standardAccount: "*25519"; + website: "null"; +} | { + prefix: 5; + network: "astar"; + displayName: "Astar Network"; + symbols: ["ASTR"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://astar.network"; +} | { + prefix: 6; + network: "bifrost"; + displayName: "Bifrost"; + symbols: ["BNC"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://bifrost.finance/"; +} | { + prefix: 7; + network: "edgeware"; + displayName: "Edgeware"; + symbols: ["EDG"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://edgewa.re"; +} | { + prefix: 8; + network: "karura"; + displayName: "Karura"; + symbols: ["KAR"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://karura.network/"; +} | { + prefix: 9; + network: "reynolds"; + displayName: "Laminar Reynolds Canary"; + symbols: ["REY"]; + decimals: [18]; + standardAccount: "*25519"; + website: "http://laminar.network/"; +} | { + prefix: 10; + network: "acala"; + displayName: "Acala"; + symbols: ["ACA"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://acala.network/"; +} | { + prefix: 11; + network: "laminar"; + displayName: "Laminar"; + symbols: ["LAMI"]; + decimals: [18]; + standardAccount: "*25519"; + website: "http://laminar.network/"; +} | { + prefix: 12; + network: "polymesh"; + displayName: "Polymesh"; + symbols: ["POLYX"]; + decimals: [6]; + standardAccount: "*25519"; + website: "https://polymath.network/"; +} | { + prefix: 13; + network: "integritee"; + displayName: "Integritee"; + symbols: ["TEER"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://integritee.network"; +} | { + prefix: 14; + network: "totem"; + displayName: "Totem"; + symbols: ["TOTEM"]; + decimals: [0]; + standardAccount: "*25519"; + website: "https://totemaccounting.com"; +} | { + prefix: 15; + network: "synesthesia"; + displayName: "Synesthesia"; + symbols: ["SYN"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://synesthesia.network/"; +} | { + prefix: 16; + network: "kulupu"; + displayName: "Kulupu"; + symbols: ["KLP"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://kulupu.network/"; +} | { + prefix: 17; + network: "dark"; + displayName: "Dark Mainnet"; + symbols: []; + decimals: []; + standardAccount: "*25519"; + website: "null"; +} | { + prefix: 18; + network: "darwinia"; + displayName: "Darwinia Network"; + symbols: ["RING", "KTON"]; + decimals: [9, 9]; + standardAccount: "*25519"; + website: "https://darwinia.network/"; +} | { + prefix: 20; + network: "stafi"; + displayName: "Stafi"; + symbols: ["FIS"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://stafi.io"; +} | { + prefix: 22; + network: "dock-pos-mainnet"; + displayName: "Dock Mainnet"; + symbols: ["DCK"]; + decimals: [6]; + standardAccount: "*25519"; + website: "https://dock.io"; +} | { + prefix: 23; + network: "shift"; + displayName: "ShiftNrg"; + symbols: []; + decimals: []; + standardAccount: "*25519"; + website: "null"; +} | { + prefix: 24; + network: "zero"; + displayName: "ZERO"; + symbols: ["ZERO"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://zero.io"; +} | { + prefix: 25; + network: "zero-alphaville"; + displayName: "ZERO Alphaville"; + symbols: ["ZERO"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://zero.io"; +} | { + prefix: 26; + network: "jupiter"; + displayName: "Jupiter"; + symbols: ["jDOT"]; + decimals: [10]; + standardAccount: "*25519"; + website: "https://jupiter.patract.io"; +} | { + prefix: 27; + network: "kabocha"; + displayName: "Kabocha"; + symbols: ["KAB"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://kabocha.network"; +} | { + prefix: 28; + network: "subsocial"; + displayName: "Subsocial"; + symbols: []; + decimals: []; + standardAccount: "*25519"; + website: "null"; +} | { + prefix: 29; + network: "cord"; + displayName: "CORD Network"; + symbols: ["DHI", "WAY"]; + decimals: [12, 12]; + standardAccount: "*25519"; + website: "https://cord.network/"; +} | { + prefix: 30; + network: "phala"; + displayName: "Phala Network"; + symbols: ["PHA"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://phala.network"; +} | { + prefix: 31; + network: "litentry"; + displayName: "Litentry Network"; + symbols: ["LIT"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://litentry.com/"; +} | { + prefix: 32; + network: "robonomics"; + displayName: "Robonomics"; + symbols: ["XRT"]; + decimals: [9]; + standardAccount: "*25519"; + website: "https://robonomics.network"; +} | { + prefix: 33; + network: "datahighway"; + displayName: "DataHighway"; + symbols: []; + decimals: []; + standardAccount: "*25519"; + website: "null"; +} | { + prefix: 34; + network: "ares"; + displayName: "Ares Protocol"; + symbols: ["ARES"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://www.aresprotocol.com/"; +} | { + prefix: 35; + network: "vln"; + displayName: "Valiu Liquidity Network"; + symbols: ["USDv"]; + decimals: [15]; + standardAccount: "*25519"; + website: "https://valiu.com/"; +} | { + prefix: 36; + network: "centrifuge"; + displayName: "Centrifuge Chain"; + symbols: ["CFG"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://centrifuge.io/"; +} | { + prefix: 37; + network: "nodle"; + displayName: "Nodle Chain"; + symbols: ["NODL"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://nodle.io/"; +} | { + prefix: 38; + network: "kilt"; + displayName: "KILT Spiritnet"; + symbols: ["KILT"]; + decimals: [15]; + standardAccount: "*25519"; + website: "https://kilt.io/"; +} | { + prefix: 39; + network: "mathchain"; + displayName: "MathChain mainnet"; + symbols: ["MATH"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://mathwallet.org"; +} | { + prefix: 40; + network: "mathchain-testnet"; + displayName: "MathChain testnet"; + symbols: ["MATH"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://mathwallet.org"; +} | { + prefix: 41; + network: "poli"; + displayName: "Polimec Chain"; + symbols: []; + decimals: []; + standardAccount: "*25519"; + website: "https://polimec.io/"; +} | { + prefix: 42; + network: "substrate"; + displayName: "Substrate"; + symbols: []; + decimals: []; + standardAccount: "*25519"; + website: "https://substrate.io/"; +} | { + prefix: 43; + network: "BareSecp256k1"; + displayName: "Bare 32-bit ECDSA SECP-256k1 public key."; + symbols: []; + decimals: []; + standardAccount: "secp256k1"; + website: "null"; +} | { + prefix: 44; + network: "chainx"; + displayName: "ChainX"; + symbols: ["PCX"]; + decimals: [8]; + standardAccount: "*25519"; + website: "https://chainx.org/"; +} | { + prefix: 45; + network: "uniarts"; + displayName: "UniArts Network"; + symbols: ["UART", "UINK"]; + decimals: [12, 12]; + standardAccount: "*25519"; + website: "https://uniarts.me"; +} | { + prefix: 46; + network: "reserved46"; + displayName: "This prefix is reserved."; + symbols: []; + decimals: []; + standardAccount: "null"; + website: "null"; +} | { + prefix: 47; + network: "reserved47"; + displayName: "This prefix is reserved."; + symbols: []; + decimals: []; + standardAccount: "null"; + website: "null"; +} | { + prefix: 48; + network: "neatcoin"; + displayName: "Neatcoin Mainnet"; + symbols: ["NEAT"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://neatcoin.org"; +} | { + prefix: 49; + network: "picasso"; + displayName: "Picasso"; + symbols: ["PICA"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://picasso.composable.finance"; +} | { + prefix: 50; + network: "composable"; + displayName: "Composable"; + symbols: ["LAYR"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://composable.finance"; +} | { + prefix: 51; + network: "oak"; + displayName: "OAK Network"; + symbols: ["OAK"]; + decimals: [10]; + standardAccount: "*25519"; + website: "https://oak.tech"; +} | { + prefix: 52; + network: "KICO"; + displayName: "KICO"; + symbols: ["KICO"]; + decimals: [14]; + standardAccount: "*25519"; + website: "https://dico.io"; +} | { + prefix: 53; + network: "DICO"; + displayName: "DICO"; + symbols: ["DICO"]; + decimals: [14]; + standardAccount: "*25519"; + website: "https://dico.io"; +} | { + prefix: 54; + network: "cere"; + displayName: "Cere Network"; + symbols: ["CERE"]; + decimals: [10]; + standardAccount: "*25519"; + website: "https://cere.network"; +} | { + prefix: 55; + network: "xxnetwork"; + displayName: "xx network"; + symbols: ["XX"]; + decimals: [9]; + standardAccount: "*25519"; + website: "https://xx.network"; +} | { + prefix: 63; + network: "hydradx"; + displayName: "HydraDX"; + symbols: ["HDX"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://hydradx.io"; +} | { + prefix: 65; + network: "aventus"; + displayName: "AvN Mainnet"; + symbols: ["AVT"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://aventus.io"; +} | { + prefix: 66; + network: "crust"; + displayName: "Crust Network"; + symbols: ["CRU"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://crust.network"; +} | { + prefix: 67; + network: "genshiro"; + displayName: "Genshiro Network"; + symbols: ["GENS", "EQD", "LPT0"]; + decimals: [9, 9, 9]; + standardAccount: "*25519"; + website: "https://genshiro.equilibrium.io"; +} | { + prefix: 68; + network: "equilibrium"; + displayName: "Equilibrium Network"; + symbols: ["EQ"]; + decimals: [9]; + standardAccount: "*25519"; + website: "https://equilibrium.io"; +} | { + prefix: 69; + network: "sora"; + displayName: "SORA Network"; + symbols: ["XOR"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://sora.org"; +} | { + prefix: 73; + network: "zeitgeist"; + displayName: "Zeitgeist"; + symbols: ["ZTG"]; + decimals: [10]; + standardAccount: "*25519"; + website: "https://zeitgeist.pm"; +} | { + prefix: 77; + network: "manta"; + displayName: "Manta network"; + symbols: ["MANTA"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://manta.network"; +} | { + prefix: 78; + network: "calamari"; + displayName: "Calamari: Manta Canary Network"; + symbols: ["KMA"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://manta.network"; +} | { + prefix: 88; + network: "polkadex"; + displayName: "Polkadex Mainnet"; + symbols: ["PDEX"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://polkadex.trade"; +} | { + prefix: 89; + network: "polkadexparachain"; + displayName: "Polkadex Parachain"; + symbols: ["PDEX"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://polkadex.trade"; +} | { + prefix: 93; + network: "fragnova"; + displayName: "Fragnova Network"; + symbols: ["NOVA"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://fragnova.com"; +} | { + prefix: 98; + network: "polkasmith"; + displayName: "PolkaSmith Canary Network"; + symbols: ["PKS"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://polkafoundry.com"; +} | { + prefix: 99; + network: "polkafoundry"; + displayName: "PolkaFoundry Network"; + symbols: ["PKF"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://polkafoundry.com"; +} | { + prefix: 101; + network: "origintrail-parachain"; + displayName: "OriginTrail Parachain"; + symbols: ["OTP"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://parachain.origintrail.io/"; +} | { + prefix: 105; + network: "pontem-network"; + displayName: "Pontem Network"; + symbols: ["PONT"]; + decimals: [10]; + standardAccount: "*25519"; + website: "https://pontem.network"; +} | { + prefix: 110; + network: "heiko"; + displayName: "Heiko"; + symbols: ["HKO"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://parallel.fi/"; +} | { + prefix: 113; + network: "integritee-incognito"; + displayName: "Integritee Incognito"; + symbols: []; + decimals: []; + standardAccount: "*25519"; + website: "https://integritee.network"; +} | { + prefix: 117; + network: "tinker"; + displayName: "Tinker"; + symbols: ["TNKR"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://invarch.network"; +} | { + prefix: 128; + network: "clover"; + displayName: "Clover Finance"; + symbols: ["CLV"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://clover.finance"; +} | { + prefix: 131; + network: "litmus"; + displayName: "Litmus Network"; + symbols: ["LIT"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://litentry.com/"; +} | { + prefix: 136; + network: "altair"; + displayName: "Altair"; + symbols: ["AIR"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://centrifuge.io/"; +} | { + prefix: 172; + network: "parallel"; + displayName: "Parallel"; + symbols: ["PARA"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://parallel.fi/"; +} | { + prefix: 252; + network: "social-network"; + displayName: "Social Network"; + symbols: ["NET"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://social.network"; +} | { + prefix: 255; + network: "quartz_mainnet"; + displayName: "QUARTZ by UNIQUE"; + symbols: ["QTZ"]; + decimals: [15]; + standardAccount: "*25519"; + website: "https://unique.network"; +} | { + prefix: 268; + network: "pioneer_network"; + displayName: "Pioneer Network by Bit.Country"; + symbols: ["NEER"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://bit.country"; +} | { + prefix: 420; + network: "sora_kusama_para"; + displayName: "SORA Kusama Parachain"; + symbols: ["XOR"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://sora.org"; +} | { + prefix: 789; + network: "geek"; + displayName: "GEEK Network"; + symbols: ["GEEK"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://geek.gl"; +} | { + prefix: 1110; + network: "efinity"; + displayName: "Efinity"; + symbols: ["EFI"]; + decimals: [18]; + standardAccount: "Sr25519"; + website: "https://efinity.io/"; +} | { + prefix: 1284; + network: "moonbeam"; + displayName: "Moonbeam"; + symbols: ["GLMR"]; + decimals: [18]; + standardAccount: "secp256k1"; + website: "https://moonbeam.network"; +} | { + prefix: 1285; + network: "moonriver"; + displayName: "Moonriver"; + symbols: ["MOVR"]; + decimals: [18]; + standardAccount: "secp256k1"; + website: "https://moonbeam.network"; +} | { + prefix: 1328; + network: "ajuna"; + displayName: "Ajuna Network"; + symbols: ["AJUN"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://ajuna.io"; +} | { + prefix: 1337; + network: "bajun"; + displayName: "Bajun Network"; + symbols: ["BAJU"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://ajuna.io"; +} | { + prefix: 2007; + network: "kapex"; + displayName: "Kapex"; + symbols: ["KAPEX"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://totemaccounting.com"; +} | { + prefix: 2032; + network: "interlay"; + displayName: "Interlay"; + symbols: ["INTR"]; + decimals: [10]; + standardAccount: "*25519"; + website: "https://interlay.io/"; +} | { + prefix: 2092; + network: "kintsugi"; + displayName: "Kintsugi"; + symbols: ["KINT"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://interlay.io/"; +} | { + prefix: 2254; + network: "subspace_testnet"; + displayName: "Subspace testnet"; + symbols: ["tSSC"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://subspace.network"; +} | { + prefix: 6094; + network: "subspace"; + displayName: "Subspace"; + symbols: ["SSC"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://subspace.network"; +} | { + prefix: 7007; + network: "tidefi"; + displayName: "Tidefi"; + symbols: ["TIFI"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://tidefi.com"; +} | { + prefix: 7391; + network: "unique_mainnet"; + displayName: "Unique Network"; + symbols: ["UNQ"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://unique.network"; +} | { + prefix: 9807; + network: "dentnet"; + displayName: "DENTNet"; + symbols: ["DENTX"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://www.dentnet.io"; +} | { + prefix: 10041; + network: "basilisk"; + displayName: "Basilisk"; + symbols: ["BSX"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://bsx.fi"; +} | { + prefix: 11330; + network: "cess-testnet"; + displayName: "CESS Testnet"; + symbols: ["TCESS"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://cess.cloud"; +} | { + prefix: 11331; + network: "cess"; + displayName: "CESS"; + symbols: ["CESS"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://cess.cloud"; +} | { + prefix: 11820; + network: "contextfree"; + displayName: "Automata ContextFree"; + symbols: ["CTX"]; + decimals: [18]; + standardAccount: "*25519"; + website: "https://ata.network"; +} | { + prefix: 12191; + network: "nftmart"; + displayName: "NFTMart"; + symbols: ["NMT"]; + decimals: [12]; + standardAccount: "*25519"; + website: "https://nftmart.io"; +}; diff --git a/deno.jsonc b/deno.jsonc index 10963a1f6..69a398de3 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -31,7 +31,7 @@ "build:_wasm": "deno task run --unstable _tasks/build_wasm.ts --out", "build:wasm:hashers": "deno task build:_wasm bindings/hashers --features hashers", "build:wasm:sr25519": "deno task build:_wasm bindings/sr25519 --features sr25519", - "build:wasm:ss58": "deno task build:_wasm bindings/ss58 --features ss58", + "build:wasm:ss58": "deno task build:_wasm bindings/ss58 --features ss58 & deno task run _tasks/build_ss58_registry_literal_type.ts", "build:wasm": "deno task build:wasm:hashers && deno task build:wasm:sr25519 && deno task build:wasm:ss58", "build:npm_pkg": "deno task run _tasks/build_npm_pkg.ts", "star": "deno task run _tasks/star.ts && deno cache --no-check=remote _star.ts", diff --git a/effect/std/account/PubKey.ts b/effect/std/account/PubKey.ts index b145dec4f..0e5ccb267 100644 --- a/effect/std/account/PubKey.ts +++ b/effect/std/account/PubKey.ts @@ -1,9 +1,13 @@ -import { getSs58 } from "../../../bindings/mod.ts"; +import { Ss58 } from "../../../bindings/mod.ts"; import * as hex from "../../../util/hex.ts"; import { effector } from "../../impl/mod.ts"; export const pubKeyFromSs58 = effector.async("pubKeyFromSs58", () => async (init: string) => { // TODO: decode byte representation instead - return hex.decodeBuf((await getSs58()).decodeSs58Text(init)); + const decoded = (await Ss58()).decode(init); + if (decoded instanceof Error) { + return decoded; + } + return hex.decode(decoded[1]); }); diff --git a/effect/std/atoms/entryKey.ts b/effect/std/atoms/entryKey.ts index a6eb3f8a8..09b82c519 100644 --- a/effect/std/atoms/entryKey.ts +++ b/effect/std/atoms/entryKey.ts @@ -1,4 +1,4 @@ -import { getHashers } from "../../../bindings/mod.ts"; +import { Hashers } from "../../../bindings/mod.ts"; import * as M from "../../../frame_metadata/mod.ts"; import * as U from "../../../util/mod.ts"; import { effector } from "../../impl/mod.ts"; @@ -14,7 +14,7 @@ export const entryKey = effector.async( ) => { return U.hex.encode( M.$storageMapKey({ - hashers: await getHashers(), + hashers: await Hashers(), pallet: palletMetadata, deriveCodec, storageEntry: entryMetadata, diff --git a/effect/std/atoms/metadataDecoded.ts b/effect/std/atoms/metadataDecoded.ts index e89fd4f2c..b212832d8 100644 --- a/effect/std/atoms/metadataDecoded.ts +++ b/effect/std/atoms/metadataDecoded.ts @@ -8,8 +8,7 @@ export const metadataDecoded = effector.sync("metadataDecoded", () => (encoded: string) => { try { return M.fromPrefixedHex(encoded); - } catch (e) { - console.error(e); + } catch (_e) { return new MetadataDecodeError(); } }); diff --git a/examples/transfer.ts b/examples/transfer.ts index d46d99046..6b9b81b76 100644 --- a/examples/transfer.ts +++ b/examples/transfer.ts @@ -1,39 +1,48 @@ -import "../_deps/load_dotenv.ts"; -import { getHashers, getSr25519 } from "../bindings/mod.ts"; +import * as asserts from "../_deps/asserts.ts"; +import { Hashers, Sr25519 } from "../bindings/mod.ts"; import * as C from "../mod.ts"; -import * as hex from "../util/hex.ts"; +import * as U from "../util/mod.ts"; -const sr25519 = await getSr25519(); +const env = U.loadEnv({ + FROM_PUBLIC_KEY: U.hex.decode, + FROM_SECRET_KEY: U.hex.decode, + TO_PUBLIC_KEY: U.hex.decode, +}); -const pair = sr25519.pairFromSecretSeed( - hex.decode("2df317d6d3b060d9cef6999f592a4a4a3acfb7212a77172d8fcdf8a08f3bf120"), -); +const [client, sr25519, hashers] = await Promise.all([ + C.wsRpcClient(C.WESTEND_RPC_URL), + Sr25519(), + Hashers(), +]); -const client = await C.wsRpcClient(C.WESTEND_RPC_URL); const metadataRaw = await client.call("state_getMetadata", []); -if (metadataRaw instanceof Error) { - throw metadataRaw; -} +asserts.assert(!(metadataRaw instanceof Error)); const metadata = C.M.fromPrefixedHex(metadataRaw.result); const deriveCodec = C.M.DeriveCodec(metadata); -const dest = new C.MultiAddress( - "Id", - hex.decode("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), -); -const genesisHash = hex.decode("c5c2beaf81f8833d2ddcfe0c04b0612d16f0d08d67aa5032dde065ddf71b4ed1"); +const signer = sr25519 + .PublicKey.from(env.FROM_PUBLIC_KEY) + .signer(env.FROM_SECRET_KEY); const $extrinsic = C.M.$extrinsic({ metadata, deriveCodec, - hashers: await getHashers(), - sign: (message) => new C.Sr25519Signature(sr25519.sign(pair.pubKey, pair.secretKey, message)), + hashers, + sign: (message) => new C.Sr25519Signature(signer.sign(message)), }); +const from = new C.MultiAddress("Id", env.FROM_PUBLIC_KEY); +const dest = new C.MultiAddress("Id", env.TO_PUBLIC_KEY); + +// TODO: get this from the RPC node +const genesisHash = U.hex.decode( + "c5c2beaf81f8833d2ddcfe0c04b0612d16f0d08d67aa5032dde065ddf71b4ed1", +); + const extrinsic: C.M.Extrinsic = { protocolVersion: 4, signature: { - address: new C.MultiAddress("Address32", pair.pubKey), + address: from, extra: [ /* era */ C.immortalEra, /* nonce */ 1000, @@ -51,8 +60,6 @@ const extrinsic: C.M.Extrinsic = { args: { dest, value: 12345n }, }; -console.log({ extrinsic }); - const encoded = $extrinsic.encode(extrinsic); console.log({ encoded }); diff --git a/frame_metadata/Key.test.ts b/frame_metadata/Key.test.ts index 045514772..29127dadd 100644 --- a/frame_metadata/Key.test.ts +++ b/frame_metadata/Key.test.ts @@ -1,12 +1,12 @@ import * as asserts from "../_deps/asserts.ts"; -import { getHashers } from "../bindings/mod.ts"; +import { Hashers } from "../bindings/mod.ts"; import * as U from "../util/mod.ts"; import { $storageMapKey } from "./Key.ts"; import * as M from "./Metadata.ts"; import { accountId32, getLookupAndDeriveCodec } from "./test-util.ts"; const { lookup, deriveCodec } = await getLookupAndDeriveCodec("polkadot"); -const hashers = await getHashers(); +const hashers = await Hashers(); Deno.test("System Accounts Key", async () => { const pallet = lookup.getPalletByName("System"); diff --git a/mod.ts b/mod.ts index e36d8b429..2f2c37c65 100644 --- a/mod.ts +++ b/mod.ts @@ -1,6 +1,7 @@ export * from "./bindings/mod.ts"; export * from "./constants/mod.ts"; // TODO: update names as to prevent conflict and use `*` export on frame metadata root +export * as $ from "./_deps/scale.ts"; export * from "./effect/mod.ts"; export * as M from "./frame_metadata/mod.ts"; export * from "./primitives/mod.ts"; diff --git a/util/ErrorCtor.ts b/util/ErrorCtor.ts new file mode 100644 index 000000000..98e56334a --- /dev/null +++ b/util/ErrorCtor.ts @@ -0,0 +1,20 @@ +/** Produces an error whose name is represented within the type system */ +export function ErrorCtor(name: Name) { + return class extends Error { + override readonly name = name; + }; +} + +export function ReasonBearingErrorCtor(name: Name) { + return () => { + return class extends ErrorCtor(name) { + constructor( + readonly reason: Reason, + message?: string, + options?: ErrorOptions, + ) { + super(message, options); + } + }; + }; +} diff --git a/util/load_env.ts b/util/load_env.ts new file mode 100644 index 000000000..55aa0441f --- /dev/null +++ b/util/load_env.ts @@ -0,0 +1,14 @@ +import * as asserts from "../_deps/asserts.ts"; +import "../_deps/load_dotenv.ts"; + +export function loadEnv>( + transformers: { [Key in keyof T]: (value: string) => T[Key] }, +): T { + const result: Partial = {}; + for (const key in transformers) { + const value = Deno.env.get(key); + asserts.assert(value); + result[key] = transformers[key](value); + } + return result as T; +} diff --git a/util/mod.ts b/util/mod.ts index 41aac7da0..db22884eb 100644 --- a/util/mod.ts +++ b/util/mod.ts @@ -1,3 +1,5 @@ export * from "./branded.ts"; +export * from "./ErrorCtor.ts"; export * as hex from "./hex.ts"; +export * from "./load_env.ts"; export * from "./types.ts";