Skip to content
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

feat: Rust<>JS bindings for Schnorrkel signature verification #454

Merged
merged 7 commits into from
Nov 9, 2019
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
10 changes: 10 additions & 0 deletions android/app/src/main/java/io/parity/signer/EthkeyBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@ public void substrateSign(String seed, String message, Promise promise) {
}
}

@ReactMethod
public void schnorrkelVerify(String seed, String message, String signature, Promise promise) {
try {
promise.resolve(schnorrkelVerify(seed, message, signature));
} catch (Exception e) {
promise.reject("invalid signature", "invalid signature");
}
}

private static native String ethkeyBrainwalletAddress(String seed);
private static native String ethkeyBrainwalletBIP39Address(String seed);
private static native String ethkeyBrainwalletSign(String seed, String message);
Expand All @@ -170,4 +179,5 @@ public void substrateSign(String seed, String message, Promise promise) {
private static native String ethkeyQrCodeHex(String data);
private static native String substrateBrainwalletAddress(String seed, int prefix);
private static native String substrateBrainwalletSign(String seed, String message);
private static native boolean schnorrkelVerify(String seed, String message, String signature);
}
1 change: 1 addition & 0 deletions ios/NativeSigner/EthkeyBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ + (BOOL)requiresMainQueueSetup
RCT_EXTERN_METHOD(substrateAddress:(NSString*)seed version:(NSUInteger*)version resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(substrateSign:(NSString*)seed message:(NSString*)message resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(blake2b:(NSString*)data resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(schnorrkelVerify: (NSString*)seed message:(NSString*)message signature:(NSString*)signature resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

@end
13 changes: 13 additions & 0 deletions ios/NativeSigner/EthkeyBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,17 @@ class EthkeyBridge: NSObject {
rust_string_destroy(signature_rust_str)
resolve(signature)
}

@objc func schnorrkelVerify(_ seed: String, message: String, signature: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
var error: UInt32 = 0
var seed_ptr = seed.asPtr()
var message_ptr = message.asPtr()
var signature_ptr = signature.asPtr()
let is_valid = schnorrkel_verify(&error, &seed_ptr, &message_ptr, &signature_ptr)
if error == 0 {
resolve(is_valid)
} else {
reject("Failed to verify signature.", nil, nil)
}
}
}
2 changes: 2 additions & 0 deletions rust/signer/signer.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,5 @@ struct rust_string* qrcode_hex(unsigned* error, const struct rust_string_ptr* da
struct rust_string* substrate_brainwallet_address(unsigned* error, const struct rust_string_ptr* seed, const unsigned prefix);

struct rust_string* substrate_brainwallet_sign(unsigned* error, const struct rust_string_ptr* seed, const struct rust_string_ptr* data);

struct rust_string* schnorrkel_verify(unsigned* error, const struct rust_string_ptr* seed, const struct rust_string_ptr* msg, const struct rust_string_ptr* signature);
28 changes: 24 additions & 4 deletions rust/signer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,24 @@ export! {

Some(signature.to_hex())
}

@Java_io_parity_signer_EthkeyBridge_schnorrkelVerify
fn schnorrkel_verify(suri: &str, msg: &str, signature: &str) -> Option<bool> {
let keypair = sr25519::KeyPair::from_suri(suri)?;
let message: Vec<u8> = msg.from_hex().ok()?;
let signature: Vec<u8> = signature.from_hex().ok()?;
keypair.verify_signature(&message, &signature)
}
}

#[cfg(test)]
mod tests {
use super::*;

static SURI: &str = "grant jaguar wish bench exact find voice habit tank pony state salmon";

static DERIVED_SURI: &str = "grant jaguar wish bench exact find voice habit tank pony state salmon//hard/soft/0";

#[test]
fn test_blake() {
let data = "454545454545454545454545454545454545454545454545454545454545454501\
Expand All @@ -257,21 +269,29 @@ mod tests {

#[test]
fn test_substrate_brainwallet_address() {
let suri = "grant jaguar wish bench exact find voice habit tank pony state salmon";
// Secret seed: 0xb139e4050f80172b44957ef9d1755ef5c96c296d63b8a2b50025bf477bd95224
// Public key (hex): 0x944eeb240615f4a94f673f240a256584ba178e22dd7b67503a753968e2f95761
let expected = "5FRAPSnpgmnXAnmPVv68fT6o7ntTvaZmkTED8jDttnXs9k4n";
let generated = substrate_brainwallet_address(suri, 42).unwrap();
let generated = substrate_brainwallet_address(SURI, 42).unwrap();

assert_eq!(expected, generated);
}

#[test]
fn test_substrate_brainwallet_address_suri() {
let suri = "grant jaguar wish bench exact find voice habit tank pony state salmon//hard/soft/0";
let expected = "5D4kaJXj5HVoBw2tFFsDj56BjZdPhXKxgGxZuKk4K3bKqHZ6";
let generated = substrate_brainwallet_address(suri, 42).unwrap();
let generated = substrate_brainwallet_address(DERIVED_SURI, 42).unwrap();

assert_eq!(expected, generated);
}

#[test]
fn test_substrate_sign() {
let msg: String = b"Build The Future".to_hex();
let signature = substrate_brainwallet_sign(SURI, &msg).unwrap();

let is_valid = schnorrkel_verify(SURI, &msg, &signature).unwrap();

assert!(is_valid);
}
}
10 changes: 9 additions & 1 deletion rust/signer/src/sr25519.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use codec::{Encode, Decode};
use lazy_static::lazy_static;
use regex::Regex;
use schnorrkel::{ExpansionMode, SecretKey};
use schnorrkel::{ExpansionMode, SecretKey, Signature};
use schnorrkel::derive::{ChainCode, Derivation};
use substrate_bip39::mini_secret_from_entropy;
use bip39::{Mnemonic, Language};
Expand Down Expand Up @@ -65,6 +65,14 @@ impl KeyPair {
let context = schnorrkel::signing_context(SIGNING_CTX);
self.0.sign(context.bytes(message)).to_bytes()
}

pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> Option<bool> {
let context = schnorrkel::signing_context(SIGNING_CTX);

let signature = Signature::from_bytes(signature).ok()?;

Some(self.0.verify(context.bytes(&message), &signature).is_ok())
}
}

fn derive_hard_junction(secret: &SecretKey, cc: [u8; CHAIN_CODE_LENGTH]) -> SecretKey {
Expand Down
22 changes: 21 additions & 1 deletion rust/signer/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use libc::size_t;

#[cfg(feature = "jni")]
use jni::{JNIEnv, objects::JString, sys::{jstring, jint}};
use jni::{JNIEnv, objects::JString, sys::{jboolean, jstring, jint}};
#[cfg(not(feature = "jni"))]
use std::cell::Cell;

Expand Down Expand Up @@ -102,6 +102,16 @@ impl<'a> Argument<'static> for &'a str {
}
}

#[cfg(not(feature = "jni"))]
impl Return<'static> for bool {
type Ext = u8;
type Env = Cell<u32>;

fn convert(_: &Self::Env, val: Self) -> Self::Ext {
val as u8
}
}

#[cfg(not(feature = "jni"))]
impl Argument<'static> for String {
type Ext = *const StringPtr;
Expand Down Expand Up @@ -201,6 +211,16 @@ impl<'jni> Return<'jni> for String {
}
}

#[cfg(feature = "jni")]
impl<'jni> Return<'jni> for bool {
type Ext = jboolean;
type Env = JNIEnv<'jni>;

fn convert(_: &Self::Env, val: Self) -> Self::Ext {
val as jboolean
}
}

#[cfg(feature = "jni")]
impl<'jni, Inner: Return<'jni, Env = JNIEnv<'jni>> + Default> Return<'jni> for Option<Inner> {
type Ext = Inner::Ext;
Expand Down
5 changes: 5 additions & 0 deletions src/util/native.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,8 @@ export function substrateAddress(seed, prefix) {
export function substrateSign(seed, message) {
return EthkeyBridge.substrateSign(seed, message);
}

// Verify a sr25519 signature is valid
export function schnorrkelVerify(seed, message, signature) {
return EthkeyBridge.schnorrkelVerify(seed, message, signature);
}