Skip to content

Add new functions to wasm-crypto #1282

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions wasm-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ crate-type = ["cdylib"]
[dependencies]
crypto = { path = '../crypto' }
serialization = { path = "../serialization" }
common = { path = "../common" }

bip39 = { workspace = true, default-features = false, features = ["std", "zeroize"] }

# This crate is required for rand to work with wasm. See: https://docs.rs/getrandom/latest/getrandom/#webassembly-support
getrandom = { version = "0.2", features = ["js"] }
Expand Down
66 changes: 66 additions & 0 deletions wasm-crypto/js-bindings/crypto_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import {
public_key_from_private_key,
sign_message,
verify_signature,
make_default_account_pubkey,
make_receiving_address,
pubkey_to_string,
Network,
} from "../pkg/wasm_crypto.js";

export async function run_test() {
Expand Down Expand Up @@ -44,4 +48,66 @@ export async function run_test() {
}
console.log("Tested decoding bad private key successfully");
}

try {
const invalid_mnemonic = "asd asd";
make_default_account_pubkey(invalid_mnemonic, Network.Mainnet);
throw new Error("Invalid mnemonic worked somehow!");
} catch (e) {
if (!e.includes("Invalid mnemonic string")) {
throw e;
}
console.log("Tested invalid menemonic successfully");
}

try {
make_receiving_address(bad_priv_key, 0);
throw new Error("Invalid public key worked somehow!");
} catch (e) {
if (!e.includes("Invalid public key encoding")) {
throw e;
}
console.log("Tested decoding bad account public key successfully");
}

const mnemonic = "walk exile faculty near leg neutral license matrix maple invite cupboard hat opinion excess coffee leopard latin regret document core limb crew dizzy movie";
{
const account_pubkey = make_default_account_pubkey(mnemonic, Network.Mainnet);
console.log(`acc pubkey = ${account_pubkey}`);

const receiving_pubkey = make_receiving_address(account_pubkey, 0);
console.log(`receiving pubkey = ${receiving_pubkey}`);

// test bad key index
try {
make_receiving_address(account_pubkey, 1<<31);
throw new Error("Invalid key index worked somehow!");
} catch (e) {
if (!e.includes("Invalid key index, MSB bit set")) {
throw e;
}
console.log("Tested invalid key index with set MSB bit successfully");
}

const address = pubkey_to_string(receiving_pubkey, Network.Mainnet);
console.log(`address = ${address}`);
if (address != "mtc1qyqmdpxk2w42w37qsdj0e8g54ysvnlvpny3svzqx") {
throw new Error("Incorrect address generated");
}
}


{
// Test generating an address for Testnet
const account_pubkey = make_default_account_pubkey(mnemonic, Network.Testnet);
console.log(`acc pubkey = ${account_pubkey}`);

const receiving_pubkey = make_receiving_address(account_pubkey, 0);
console.log(`receiving pubkey = ${receiving_pubkey}`);
const address = pubkey_to_string(receiving_pubkey, Network.Testnet);
console.log(`address = ${address}`);
if (address != "tmt1q9dn5m4svn8sds3fcy09kpxrefnu75xekgr5wa3n") {
throw new Error("Incorrect address generated");
}
}
}
4 changes: 4 additions & 0 deletions wasm-crypto/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pub enum Error {
InvalidPublicKeyEncoding,
#[error("Invalid signature encoding")]
InvalidSignatureEncoding,
#[error("Invalid mnemonic string")]
InvalidMnemonic,
#[error("Invalid key index, MSB bit set")]
InvalidKeyIndex,
}

// This is required to make an error readable in JavaScript
Expand Down
95 changes: 94 additions & 1 deletion wasm-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,112 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crypto::key::{KeyKind, PrivateKey, PublicKey, Signature};
pub use bip39::{Language, Mnemonic};
use common::{
address::{pubkeyhash::PublicKeyHash, Address},
chain::{
config::{Builder, ChainType, BIP44_PATH},
Destination,
},
};
use crypto::key::{
extended::{ExtendedKeyKind, ExtendedPrivateKey, ExtendedPublicKey},
hdkd::{child_number::ChildNumber, derivable::Derivable, u31::U31},
KeyKind, PrivateKey, PublicKey, Signature,
};
use error::Error;
use serialization::{DecodeAll, Encode};
use wasm_bindgen::prelude::*;

pub mod error;

#[wasm_bindgen]
pub enum Network {
Mainnet,
Testnet,
Regtest,
Signet,
}

impl From<Network> for ChainType {
fn from(value: Network) -> Self {
match value {
Network::Mainnet => ChainType::Mainnet,
Network::Testnet => ChainType::Testnet,
Network::Regtest => ChainType::Regtest,
Network::Signet => ChainType::Signet,
}
}
}

#[wasm_bindgen]
pub fn make_private_key() -> Vec<u8> {
let key = PrivateKey::new_from_entropy(KeyKind::Secp256k1Schnorr);
key.0.encode()
}

#[wasm_bindgen]
pub fn make_default_account_pubkey(mnemonic: &str, network: Network) -> Result<Vec<u8>, Error> {
let mnemonic = bip39::Mnemonic::parse_in(Language::English, mnemonic)
.map_err(|_| Error::InvalidMnemonic)?;
let seed = mnemonic.to_seed("");

let root_key = ExtendedPrivateKey::new_master(&seed, ExtendedKeyKind::Secp256k1Schnorr)
.expect("Should not fail to create a master key");

let chain_config = Builder::new(network.into()).build();

let account_index = U31::ZERO;
let path = vec![
BIP44_PATH,
chain_config.bip44_coin_type(),
ChildNumber::from_hardened(account_index),
];
let account_path = path.try_into().expect("Path creation should not fail");
let account_privkey = root_key
.derive_absolute_path(&account_path)
.expect("Should not fail to derive path");

Ok(account_privkey.to_public_key().encode())
}

#[wasm_bindgen]
pub fn make_receiving_address(public_key_bytes: &[u8], key_index: u32) -> Result<Vec<u8>, Error> {
const RECEIVE_FUNDS_INDEX: ChildNumber = ChildNumber::from_normal(U31::from_u32_with_msb(0).0);

let account_pubkey = ExtendedPublicKey::decode_all(&mut &public_key_bytes[..])
.map_err(|_| Error::InvalidPublicKeyEncoding)?;

let receive_funds_pkey = account_pubkey
.derive_child(RECEIVE_FUNDS_INDEX)
.expect("Should not fail to derive key");

let public_key: PublicKey = receive_funds_pkey
.derive_child(ChildNumber::from_normal(
U31::from_u32(key_index).ok_or(Error::InvalidKeyIndex)?,
))
.expect("Should not fail to derive key")
.into_public_key();

Ok(public_key.encode())
}

#[wasm_bindgen]
pub fn pubkey_to_string(public_key_bytes: &[u8], network: Network) -> Result<String, Error> {
let public_key = PublicKey::decode_all(&mut &public_key_bytes[..])
.map_err(|_| Error::InvalidPublicKeyEncoding)?;
let chain_config = Builder::new(network.into()).build();

let public_key_hash = PublicKeyHash::from(&public_key);

Ok(
Address::new(&chain_config, &Destination::Address(public_key_hash))
.expect("Should not fail to create address")
.get()
.to_owned(),
)
}

#[wasm_bindgen]
pub fn public_key_from_private_key(private_key: &[u8]) -> Result<Vec<u8>, Error> {
let private_key = PrivateKey::decode_all(&mut &private_key[..])
Expand Down