Skip to content
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: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Creating a Wallet took 289 ms
</h3>

```sh
yarn add react-native-quick-crypto
bun add react-native-quick-crypto react-native-nitro-modules
cd ios && pod install
```

Expand Down
Binary file modified bun.lockb
Binary file not shown.
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ PODS:
- hermes-engine (0.76.1):
- hermes-engine/Pre-built (= 0.76.1)
- hermes-engine/Pre-built (0.76.1)
- NitroModules (0.18.1):
- NitroModules (0.18.2):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -1938,7 +1938,7 @@ SPEC CHECKSUMS:
fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
hermes-engine: 46f1ffbf0297f4298862068dd4c274d4ac17a1fd
NitroModules: 55f64932b4581a7d02103bc35b84c7bd3204106b
NitroModules: 47399393665e69228b29a17f501c7b453679ccc0
OpenSSL-Universal: b60a3702c9fea8b3145549d421fdb018e53ab7b4
QuickCrypto: e68316432823f70bdae0bf1486dc5e9afdfdff4d
RCT-Folly: 84578c8756030547307e4572ab1947de1685c599
Expand Down
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"react": "18.3.1",
"react-native": "0.76.1",
"react-native-bouncy-checkbox": "4.0.1",
"react-native-nitro-modules": "0.18.1",
"react-native-nitro-modules": "0.18.2",
"react-native-quick-base64": "2.1.2",
"react-native-quick-crypto": "workspace:*",
"react-native-safe-area-context": "4.14.0",
Expand Down
5 changes: 5 additions & 0 deletions example/src/navigators/children/TestDetailsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const TestDetailsScreen = ({ route }) => {
onPress={() => setShowFailed(!showFailed)}
disableText={true}
fillColor="red"
style={styles.checkbox}
/>
<Text style={styles.showMenuLabel}>Show Failed</Text>
</View>
Expand All @@ -39,6 +40,7 @@ export const TestDetailsScreen = ({ route }) => {
onPress={() => setShowPassed(!showPassed)}
disableText={true}
fillColor={colors.green}
style={styles.checkbox}
/>
<Text style={styles.showMenuLabel}>Show Passed</Text>
</View>
Expand Down Expand Up @@ -101,4 +103,7 @@ const styles = StyleSheet.create({
scrollContent: {
paddingHorizontal: 5,
},
checkbox: {
transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }],
},
});
47 changes: 40 additions & 7 deletions example/src/tests/ed25519/ed25519_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,17 @@ types.map((type) => {
});
*/

const data1 = Buffer.from('hello world');

test(SUITE, 'sign/verify - round trip happy', async () => {
const data = Buffer.from('hello world');
const ed = new Ed('ed25519', {});
await ed.generateKeyPair();
const signature = await ed.sign(data.buffer);
const verified = await ed.verify(signature, data.buffer);
const signature = await ed.sign(data1.buffer);
const verified = await ed.verify(signature, data1.buffer);
expect(verified).to.be.true;
});

test(SUITE, 'sign/verify - round trip sad', async () => {
const data1 = Buffer.from('hello world');
const data2 = Buffer.from('goodbye cruel world');
const ed = new Ed('ed25519', {});
await ed.generateKeyPair();
Expand All @@ -66,12 +66,45 @@ test(SUITE, 'sign/verify - round trip sad', async () => {
});

test(SUITE, 'sign/verify - bad signature does not verify', async () => {
const data = Buffer.from('hello world');
const ed = new Ed('ed25519', {});
await ed.generateKeyPair();
const signature = await ed.sign(data.buffer);
const signature = await ed.sign(data1.buffer);
const signature2 = randomBytes(64).buffer;
expect(ab2str(signature2)).not.to.equal(ab2str(signature));
const verified = await ed.verify(signature2, data.buffer);
const verified = await ed.verify(signature2, data1.buffer);
expect(verified).to.be.false;
});

test(
SUITE,
'sign/verify with non-internally generated private key',
async () => {
let ed1: Ed | null = new Ed('ed25519', {});
await ed1.generateKeyPair();
const priv = ed1.getPrivateKey();
ed1 = null;

const ed2 = new Ed('ed25519', {});
const signature = await ed2.sign(data1.buffer, priv);
const verified = await ed2.verify(signature, data1.buffer, priv);
expect(verified).to.be.true;
},
);

test(
SUITE,
'sign/verify with bad non-internally generated private key',
async () => {
let ed1: Ed | null = new Ed('ed25519', {});
await ed1.generateKeyPair();
const priv = ed1.getPrivateKey();
ed1 = null;

const ed2 = new Ed('ed25519', {});
const signature = await ed2.sign(data1.buffer, priv);
const signature2 = randomBytes(64).buffer;
expect(ab2str(signature2)).not.to.equal(ab2str(signature));
const verified = await ed2.verify(signature2, data1.buffer, priv);
expect(verified).to.be.false;
},
);
59 changes: 47 additions & 12 deletions packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,28 +72,36 @@ HybridEdKeyPair::generateKeyPairSync(

std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>>
HybridEdKeyPair::sign(
const std::shared_ptr<ArrayBuffer>& message
const std::shared_ptr<ArrayBuffer>& message,
const std::optional<std::shared_ptr<ArrayBuffer>>& key
) {
// get owned NativeArrayBuffer before passing to sync function
auto nativeMessage = ToNativeArrayBuffer(message);
std::optional<std::shared_ptr<ArrayBuffer>> nativeKey = std::nullopt;
if (key.has_value()) {
nativeKey = ToNativeArrayBuffer(key.value());
}

return Promise<std::shared_ptr<ArrayBuffer>>::async([this, nativeMessage]() {
return this->signSync(nativeMessage);
return Promise<std::shared_ptr<ArrayBuffer>>::async([this, nativeMessage, nativeKey]() {
return this->signSync(nativeMessage, nativeKey);
}
);
}

std::shared_ptr<ArrayBuffer>
HybridEdKeyPair::signSync(
const std::shared_ptr<ArrayBuffer>& message
const std::shared_ptr<ArrayBuffer>& message,
const std::optional<std::shared_ptr<ArrayBuffer>>& key
) {
this->checkKeyPair();

size_t sig_len = 0;
uint8_t* sig = NULL;
EVP_MD_CTX* md_ctx = nullptr;
EVP_PKEY_CTX* pkey_ctx = nullptr;

// get key to use for signing
EVP_PKEY* pkey = this->importPrivateKey(key);

// key context
md_ctx = EVP_MD_CTX_new();
if (md_ctx == nullptr) {
Expand All @@ -107,7 +115,7 @@ HybridEdKeyPair::signSync(
throw std::runtime_error("Error creating signing context: " + this->curve);
}

if (EVP_DigestSignInit(md_ctx, &pkey_ctx, NULL, NULL, this->pkey) <= 0) {
if (EVP_DigestSignInit(md_ctx, &pkey_ctx, NULL, NULL, pkey) <= 0) {
EVP_MD_CTX_free(md_ctx);
char* err = ERR_error_string(ERR_get_error(), NULL);
throw std::runtime_error("Failed to initialize signing: " + std::string(err));
Expand Down Expand Up @@ -142,24 +150,31 @@ HybridEdKeyPair::signSync(
std::shared_ptr<Promise<bool>>
HybridEdKeyPair::verify(
const std::shared_ptr<ArrayBuffer>& signature,
const std::shared_ptr<ArrayBuffer>& message
const std::shared_ptr<ArrayBuffer>& message,
const std::optional<std::shared_ptr<ArrayBuffer>>& key
) {
// get owned NativeArrayBuffers before passing to sync function
auto nativeSignature = ToNativeArrayBuffer(signature);
auto nativeMessage = ToNativeArrayBuffer(message);
std::optional<std::shared_ptr<ArrayBuffer>> nativeKey = std::nullopt;
if (key.has_value()) {
nativeKey = ToNativeArrayBuffer(key.value());
}

return Promise<bool>::async([this, nativeSignature, nativeMessage]() {
return this->verifySync(nativeSignature, nativeMessage);
return Promise<bool>::async([this, nativeSignature, nativeMessage, nativeKey]() {
return this->verifySync(nativeSignature, nativeMessage, nativeKey);
}
);
}

bool
HybridEdKeyPair::verifySync(
const std::shared_ptr<ArrayBuffer>& signature,
const std::shared_ptr<ArrayBuffer>& message
const std::shared_ptr<ArrayBuffer>& message,
const std::optional<std::shared_ptr<ArrayBuffer>>& key
) {
this->checkKeyPair();
// get key to use for verifying
EVP_PKEY* pkey = this->importPrivateKey(key);

EVP_MD_CTX* md_ctx = nullptr;
EVP_PKEY_CTX* pkey_ctx = nullptr;
Expand All @@ -177,7 +192,7 @@ HybridEdKeyPair::verifySync(
throw std::runtime_error("Error creating verify context: " + this->curve);
}

if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, NULL, NULL, this->pkey) <= 0) {
if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, NULL, NULL, pkey) <= 0) {
EVP_MD_CTX_free(md_ctx);
char* err = ERR_error_string(ERR_get_error(), NULL);
throw std::runtime_error("Failed to initialize verify: " + std::string(err));
Expand Down Expand Up @@ -230,4 +245,24 @@ HybridEdKeyPair::setCurve(const std::string& curve) {
this->curve = curve;
}

EVP_PKEY*
HybridEdKeyPair::importPrivateKey(const std::optional<std::shared_ptr<ArrayBuffer>>& key) {
EVP_PKEY* pkey = nullptr;
if (key.has_value()) {
pkey = EVP_PKEY_new_raw_private_key(
EVP_PKEY_ED25519, // TODO: use this->curve somehow
NULL,
key.value()->data(),
32
);
if (pkey == nullptr) {
throw std::runtime_error("Failed to read private key");
}
} else {
this->checkKeyPair();
pkey = this->pkey;
}
return pkey;
}

} // namespace margelo::nitro::crypto
25 changes: 19 additions & 6 deletions packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,36 +37,49 @@ class HybridEdKeyPair : public HybridEdKeyPairSpec {
) override;

std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>>
sign(const std::shared_ptr<ArrayBuffer>& message) override;
sign(
const std::shared_ptr<ArrayBuffer>& message,
const std::optional<std::shared_ptr<ArrayBuffer>>& key
) override;

std::shared_ptr<ArrayBuffer>
signSync(const std::shared_ptr<ArrayBuffer>& message) override;
signSync(
const std::shared_ptr<ArrayBuffer>& message,
const std::optional<std::shared_ptr<ArrayBuffer>>& key
) override;

std::shared_ptr<Promise<bool>>
verify(
const std::shared_ptr<ArrayBuffer>& signature,
const std::shared_ptr<ArrayBuffer>& message
const std::shared_ptr<ArrayBuffer>& message,
const std::optional<std::shared_ptr<ArrayBuffer>>& key
) override;

bool
verifySync(
const std::shared_ptr<ArrayBuffer>& signature,
const std::shared_ptr<ArrayBuffer>& message
const std::shared_ptr<ArrayBuffer>& message,
const std::optional<std::shared_ptr<ArrayBuffer>>& key
) override;

protected:
std::shared_ptr<ArrayBuffer>
getPublicKey() override;

std::shared_ptr<ArrayBuffer> getPrivateKey();
std::shared_ptr<ArrayBuffer>
getPrivateKey() override;

void checkKeyPair();

void setCurve(const std::string& curve);
void setCurve(const std::string& curve) override;

private:
std::string curve;
EVP_PKEY* pkey = nullptr;

EVP_PKEY* importPrivateKey(
const std::optional<std::shared_ptr<ArrayBuffer>>& key
);
};

} // namespace margelo::nitro::crypto
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace margelo::nitro::crypto {
prototype.registerHybridMethod("generateKeyPair", &HybridEdKeyPairSpec::generateKeyPair);
prototype.registerHybridMethod("generateKeyPairSync", &HybridEdKeyPairSpec::generateKeyPairSync);
prototype.registerHybridMethod("getPublicKey", &HybridEdKeyPairSpec::getPublicKey);
prototype.registerHybridMethod("getPrivateKey", &HybridEdKeyPairSpec::getPrivateKey);
prototype.registerHybridMethod("sign", &HybridEdKeyPairSpec::sign);
prototype.registerHybridMethod("signSync", &HybridEdKeyPairSpec::signSync);
prototype.registerHybridMethod("verify", &HybridEdKeyPairSpec::verify);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ namespace margelo::nitro::crypto {
virtual std::shared_ptr<Promise<void>> generateKeyPair(double publicFormat, double publicType, double privateFormat, double privateType, const std::optional<std::string>& cipher, const std::optional<std::shared_ptr<ArrayBuffer>>& passphrase) = 0;
virtual void generateKeyPairSync(double publicFormat, double publicType, double privateFormat, double privateType, const std::optional<std::string>& cipher, const std::optional<std::shared_ptr<ArrayBuffer>>& passphrase) = 0;
virtual std::shared_ptr<ArrayBuffer> getPublicKey() = 0;
virtual std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> sign(const std::shared_ptr<ArrayBuffer>& message) = 0;
virtual std::shared_ptr<ArrayBuffer> signSync(const std::shared_ptr<ArrayBuffer>& message) = 0;
virtual std::shared_ptr<Promise<bool>> verify(const std::shared_ptr<ArrayBuffer>& signature, const std::shared_ptr<ArrayBuffer>& message) = 0;
virtual bool verifySync(const std::shared_ptr<ArrayBuffer>& signature, const std::shared_ptr<ArrayBuffer>& message) = 0;
virtual std::shared_ptr<ArrayBuffer> getPrivateKey() = 0;
virtual std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> sign(const std::shared_ptr<ArrayBuffer>& message, const std::optional<std::shared_ptr<ArrayBuffer>>& key) = 0;
virtual std::shared_ptr<ArrayBuffer> signSync(const std::shared_ptr<ArrayBuffer>& message, const std::optional<std::shared_ptr<ArrayBuffer>>& key) = 0;
virtual std::shared_ptr<Promise<bool>> verify(const std::shared_ptr<ArrayBuffer>& message, const std::shared_ptr<ArrayBuffer>& signature, const std::optional<std::shared_ptr<ArrayBuffer>>& key) = 0;
virtual bool verifySync(const std::shared_ptr<ArrayBuffer>& message, const std::shared_ptr<ArrayBuffer>& signature, const std::optional<std::shared_ptr<ArrayBuffer>>& key) = 0;
virtual void setCurve(const std::string& curve) = 0;

protected:
Expand Down
4 changes: 2 additions & 2 deletions packages/react-native-quick-crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"dependencies": {
"@craftzdog/react-native-buffer": "6.0.5",
"events": "3.3.0",
"react-native-nitro-modules": "0.18.1",
"react-native-nitro-modules": "0.18.2",
"react-native-quick-base64": "2.1.2",
"readable-stream": "4.5.2",
"string_decoder": "1.3.0",
Expand All @@ -88,7 +88,7 @@
"eslint": "9.9.0",
"eslint-plugin-react-native": "^4.1.0",
"jest": "29.7.0",
"nitro-codegen": "0.18.1",
"nitro-codegen": "0.18.2",
"prettier": "3.3.3",
"react-native-builder-bob": "0.33.3",
"release-it": "17.6.0",
Expand Down
34 changes: 26 additions & 8 deletions packages/react-native-quick-crypto/src/ed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,37 @@ export class Ed {
return this.native.getPublicKey();
}

async sign(message: ArrayBuffer): Promise<ArrayBuffer> {
return this.native.sign(message);
getPrivateKey(): ArrayBuffer {
return this.native.getPrivateKey();
}

signSync(message: ArrayBuffer): ArrayBuffer {
return this.native.signSync(message);
async sign(message: ArrayBuffer, key?: ArrayBuffer): Promise<ArrayBuffer> {
return key ? this.native.sign(message, key) : this.native.sign(message);
}

async verify(signature: ArrayBuffer, message: ArrayBuffer): Promise<boolean> {
return this.native.verify(signature, message);
signSync(message: ArrayBuffer, key?: ArrayBuffer): ArrayBuffer {
return key
? this.native.signSync(message, key)
: this.native.signSync(message);
}

verifySync(signature: ArrayBuffer, message: ArrayBuffer): boolean {
return this.native.verifySync(signature, message);
async verify(
message: ArrayBuffer,
signature: ArrayBuffer,
key?: ArrayBuffer,
): Promise<boolean> {
return key
? this.native.verify(message, signature, key)
: this.native.verify(message, signature);
}

verifySync(
message: ArrayBuffer,
signature: ArrayBuffer,
key?: ArrayBuffer,
): boolean {
return key
? this.native.verifySync(message, signature, key)
: this.native.verifySync(message, signature);
}
}
Loading
Loading