diff --git a/packages/wasm-crypto/Cargo.lock b/packages/wasm-crypto/Cargo.lock index 4132200b..eb69d86f 100644 --- a/packages/wasm-crypto/Cargo.lock +++ b/packages/wasm-crypto/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "addr2line" version = "0.13.0" @@ -659,6 +661,7 @@ version = "0.0.0" dependencies = [ "blake2-rfc", "byteorder", + "curve25519-dalek", "ed25519-dalek", "hex-literal", "hmac", diff --git a/packages/wasm-crypto/Cargo.toml b/packages/wasm-crypto/Cargo.toml index 12475667..a7bda14e 100644 --- a/packages/wasm-crypto/Cargo.toml +++ b/packages/wasm-crypto/Cargo.toml @@ -22,6 +22,7 @@ hmac = "0.7.0" # libsecp256k1 = "0.2.2" merlin = { version = "2.0", default-features = false } pbkdf2 = { version = "0.3.0", default-features = false } +curve25519-dalek = { version = "2.1.0", default-features = false } schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"] } scrypt = { version = "0.2", default-features = false } sha2 = "0.8.1" diff --git a/packages/wasm-crypto/src/bundle.ts b/packages/wasm-crypto/src/bundle.ts index 33347ef4..9b617e7f 100644 --- a/packages/wasm-crypto/src/bundle.ts +++ b/packages/wasm-crypto/src/bundle.ts @@ -159,6 +159,17 @@ export const sr25519Verify = withWasm((wasm) => } ); +export const sr25519Agree = withWasm((wasm) => + (pubkey: Uint8Array, secret: Uint8Array): Uint8Array => { + const [ptr0, len0] = allocU8a(pubkey); + const [ptr1, len1] = allocU8a(secret); + + wasm.ext_sr_agree(8, ptr0, len0, ptr1, len1); + + return resultU8a(); + } +); + export const vrfSign = withWasm((wasm) => (secret: Uint8Array, context: Uint8Array, message: Uint8Array, extra: Uint8Array): Uint8Array => { const [ptr0, len0] = allocU8a(secret); diff --git a/packages/wasm-crypto/src/rs/sr25519.rs b/packages/wasm-crypto/src/rs/sr25519.rs index 20285a24..7b26afbd 100644 --- a/packages/wasm-crypto/src/rs/sr25519.rs +++ b/packages/wasm-crypto/src/rs/sr25519.rs @@ -7,6 +7,7 @@ // which was adpated from the initial https://github.com/paritytech/schnorrkel-js/ // forked at commit eff430ddc3090f56317c80654208b8298ef7ab3f +use curve25519_dalek::scalar::Scalar; use schnorrkel::{ ExpansionMode, Keypair, MiniSecretKey, PublicKey, SecretKey, Signature, derive::{Derivation, ChainCode, CHAIN_CODE_LENGTH}, @@ -134,6 +135,21 @@ pub fn ext_sr_verify(signature: &[u8], message: &[u8], pubkey: &[u8]) -> bool { } } +/// Key agreement between other's public key and self secret key. +/// +/// * pubkey: UIntArray with 32 element +/// * secret: UIntArray with 64 element +/// +/// * returned vector is the generated secret of 32 bytes. +#[wasm_bindgen] +pub fn ext_sr_agree(pubkey: &[u8], secret: &[u8]) -> Vec { + let secret = SecretKey::from_ed25519_bytes(secret).unwrap(); + // The first 32 bytes holds the canonical private key + let mut key = [0u8; 32]; + key.copy_from_slice(&secret.to_bytes()[0..32]); + (&Scalar::from_canonical_bytes(key).unwrap() * PublicKey::from_bytes(pubkey).unwrap().as_point()).compress().0.to_vec() +} + #[cfg(test)] pub mod tests { extern crate rand; @@ -243,4 +259,32 @@ pub mod tests { assert_eq!(public, expected); } + + #[test] + fn key_agreement() { + let self_seed = hex!("98b3d305d5a5eace562387e47e59badd4d77e3f72cabfb10a60f8a197059f0a8"); + let other_seed = hex!("9732eea001851ff862d949a1699c9971f3a26edbede2ad7922cbbe9a0701f366"); + let expected = hex!("b03a0b198c34c16f35cae933d88b16341b4cef3e84e851f20e664c6a30527f4e"); + let self_pair = ext_sr_from_seed(&self_seed); + let self_sk = &self_pair[0..SECRET_KEY_LENGTH]; + let self_pk = &self_pair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + let other_pair = ext_sr_from_seed(&other_seed); + let other_sk = &other_pair[0..SECRET_KEY_LENGTH]; + let other_pk = &other_pair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + + assert_eq!(ext_sr_agree(self_pk, other_sk), expected); + assert_eq!(ext_sr_agree(other_pk, self_sk), expected); + + let seed = generate_random_seed(); + let self_pair = ext_sr_from_seed(seed.as_slice()); + let self_sk = &self_pair[0..SECRET_KEY_LENGTH]; + let self_pk = &self_pair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + + let seed = generate_random_seed(); + let other_pair = ext_sr_from_seed(seed.as_slice()); + let other_sk = &other_pair[0..SECRET_KEY_LENGTH]; + let other_pk = &other_pair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + + assert_eq!(ext_sr_agree(self_pk, other_sk), ext_sr_agree(other_pk, self_sk)); + } } diff --git a/packages/wasm-crypto/src/types.ts b/packages/wasm-crypto/src/types.ts index 014c4118..308a19b7 100644 --- a/packages/wasm-crypto/src/types.ts +++ b/packages/wasm-crypto/src/types.ts @@ -32,6 +32,7 @@ export interface WasmCryptoInstance { ext_sr_from_seed(a: number, b: number, c: number): void; ext_sr_sign(a: number, b: number, c: number, d: number, e: number, f: number, g: number): void; ext_sr_verify(a: number, b: number, c: number, d: number, e: number, f: number): number; + ext_sr_agree(a: number, b: number, c: number, d: number, e: number): void; ext_vrf_sign(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number): void; ext_vrf_verify(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number): number; } diff --git a/packages/wasm-crypto/test/all/sr25519.cjs b/packages/wasm-crypto/test/all/sr25519.cjs index cf4ec61d..6a981235 100644 --- a/packages/wasm-crypto/test/all/sr25519.cjs +++ b/packages/wasm-crypto/test/all/sr25519.cjs @@ -120,6 +120,23 @@ function sr25519_benchmark (wasm) { } } +function sr25519_key_agreement (wasm) { + const pair1 = wasm.sr25519KeypairFromSeed(hexToU8a('0x3b44c558f9a8f3dc9690d53088558c1ba2529b677e316c6054d1852595b004af')); + const pair2 = wasm.sr25519KeypairFromSeed(hexToU8a('0x923b80f79c6981fe756272128ec236eb510ae016dd20bdccbd77a9416b7ab94e')); + const [, pk1, sk1] = extractKeys(pair1); + const [, pk2, sk2] = extractKeys(pair2); + + assert(u8aToHex(wasm.sr25519Agree(pk1, sk2)) === '0xfa7b90001b790fe42ff78b8cd86f6cf7a7c0a70b72f6b4c771b5d67536450222', 'Unmatched agreement keys'); + assert(u8aToHex(wasm.sr25519Agree(pk2, sk1)) === '0xfa7b90001b790fe42ff78b8cd86f6cf7a7c0a70b72f6b4c771b5d67536450222', 'Unmatched agreement keys'); + + for (let i = 0; i < 256; i++) { + const [, pk1, sk1] = randomPair(wasm); + const [, pk2, sk2] = randomPair(wasm); + + assert(u8aToHex(wasm.sr25519Agree(pk1, sk2)) === u8aToHex(wasm.sr25519Agree(pk2, sk1)), 'Unmatched agreement keys'); + } +} + module.exports = { sr25519_benchmark, sr25519_deriveHard, @@ -128,6 +145,7 @@ module.exports = { sr25519_deriveSoftKnown, sr25519_deriveSoftPubkey, sr25519_devFromSeed, + sr25519_key_agreement, sr25519_pairFromSeed, sr25519_signAndVerify, sr25519_sign_deterministic,