Skip to content

Commit 73598db

Browse files
authored
feat: pass non-internal private key to sign/verify for ed25519 (#555)
1 parent b1b0148 commit 73598db

File tree

13 files changed

+162
-47
lines changed

13 files changed

+162
-47
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Creating a Wallet took 289 ms
6161
</h3>
6262

6363
```sh
64-
yarn add react-native-quick-crypto
64+
bun add react-native-quick-crypto react-native-nitro-modules
6565
cd ios && pod install
6666
```
6767

bun.lockb

0 Bytes
Binary file not shown.

example/ios/Podfile.lock

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ PODS:
77
- hermes-engine (0.76.1):
88
- hermes-engine/Pre-built (= 0.76.1)
99
- hermes-engine/Pre-built (0.76.1)
10-
- NitroModules (0.18.1):
10+
- NitroModules (0.18.2):
1111
- DoubleConversion
1212
- glog
1313
- hermes-engine
@@ -1938,7 +1938,7 @@ SPEC CHECKSUMS:
19381938
fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be
19391939
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
19401940
hermes-engine: 46f1ffbf0297f4298862068dd4c274d4ac17a1fd
1941-
NitroModules: 55f64932b4581a7d02103bc35b84c7bd3204106b
1941+
NitroModules: 47399393665e69228b29a17f501c7b453679ccc0
19421942
OpenSSL-Universal: b60a3702c9fea8b3145549d421fdb018e53ab7b4
19431943
QuickCrypto: e68316432823f70bdae0bf1486dc5e9afdfdff4d
19441944
RCT-Folly: 84578c8756030547307e4572ab1947de1685c599

example/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"react": "18.3.1",
3535
"react-native": "0.76.1",
3636
"react-native-bouncy-checkbox": "4.0.1",
37-
"react-native-nitro-modules": "0.18.1",
37+
"react-native-nitro-modules": "0.18.2",
3838
"react-native-quick-base64": "2.1.2",
3939
"react-native-quick-crypto": "workspace:*",
4040
"react-native-safe-area-context": "4.14.0",

example/src/navigators/children/TestDetailsScreen.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const TestDetailsScreen = ({ route }) => {
3030
onPress={() => setShowFailed(!showFailed)}
3131
disableText={true}
3232
fillColor="red"
33+
style={styles.checkbox}
3334
/>
3435
<Text style={styles.showMenuLabel}>Show Failed</Text>
3536
</View>
@@ -39,6 +40,7 @@ export const TestDetailsScreen = ({ route }) => {
3940
onPress={() => setShowPassed(!showPassed)}
4041
disableText={true}
4142
fillColor={colors.green}
43+
style={styles.checkbox}
4244
/>
4345
<Text style={styles.showMenuLabel}>Show Passed</Text>
4446
</View>
@@ -101,4 +103,7 @@ const styles = StyleSheet.create({
101103
scrollContent: {
102104
paddingHorizontal: 5,
103105
},
106+
checkbox: {
107+
transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }],
108+
},
104109
});

example/src/tests/ed25519/ed25519_tests.ts

+40-7
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,17 @@ types.map((type) => {
4646
});
4747
*/
4848

49+
const data1 = Buffer.from('hello world');
50+
4951
test(SUITE, 'sign/verify - round trip happy', async () => {
50-
const data = Buffer.from('hello world');
5152
const ed = new Ed('ed25519', {});
5253
await ed.generateKeyPair();
53-
const signature = await ed.sign(data.buffer);
54-
const verified = await ed.verify(signature, data.buffer);
54+
const signature = await ed.sign(data1.buffer);
55+
const verified = await ed.verify(signature, data1.buffer);
5556
expect(verified).to.be.true;
5657
});
5758

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

6868
test(SUITE, 'sign/verify - bad signature does not verify', async () => {
69-
const data = Buffer.from('hello world');
7069
const ed = new Ed('ed25519', {});
7170
await ed.generateKeyPair();
72-
const signature = await ed.sign(data.buffer);
71+
const signature = await ed.sign(data1.buffer);
7372
const signature2 = randomBytes(64).buffer;
7473
expect(ab2str(signature2)).not.to.equal(ab2str(signature));
75-
const verified = await ed.verify(signature2, data.buffer);
74+
const verified = await ed.verify(signature2, data1.buffer);
7675
expect(verified).to.be.false;
7776
});
77+
78+
test(
79+
SUITE,
80+
'sign/verify with non-internally generated private key',
81+
async () => {
82+
let ed1: Ed | null = new Ed('ed25519', {});
83+
await ed1.generateKeyPair();
84+
const priv = ed1.getPrivateKey();
85+
ed1 = null;
86+
87+
const ed2 = new Ed('ed25519', {});
88+
const signature = await ed2.sign(data1.buffer, priv);
89+
const verified = await ed2.verify(signature, data1.buffer, priv);
90+
expect(verified).to.be.true;
91+
},
92+
);
93+
94+
test(
95+
SUITE,
96+
'sign/verify with bad non-internally generated private key',
97+
async () => {
98+
let ed1: Ed | null = new Ed('ed25519', {});
99+
await ed1.generateKeyPair();
100+
const priv = ed1.getPrivateKey();
101+
ed1 = null;
102+
103+
const ed2 = new Ed('ed25519', {});
104+
const signature = await ed2.sign(data1.buffer, priv);
105+
const signature2 = randomBytes(64).buffer;
106+
expect(ab2str(signature2)).not.to.equal(ab2str(signature));
107+
const verified = await ed2.verify(signature2, data1.buffer, priv);
108+
expect(verified).to.be.false;
109+
},
110+
);

packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.cpp

+47-12
Original file line numberDiff line numberDiff line change
@@ -72,28 +72,36 @@ HybridEdKeyPair::generateKeyPairSync(
7272

7373
std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>>
7474
HybridEdKeyPair::sign(
75-
const std::shared_ptr<ArrayBuffer>& message
75+
const std::shared_ptr<ArrayBuffer>& message,
76+
const std::optional<std::shared_ptr<ArrayBuffer>>& key
7677
) {
7778
// get owned NativeArrayBuffer before passing to sync function
7879
auto nativeMessage = ToNativeArrayBuffer(message);
80+
std::optional<std::shared_ptr<ArrayBuffer>> nativeKey = std::nullopt;
81+
if (key.has_value()) {
82+
nativeKey = ToNativeArrayBuffer(key.value());
83+
}
7984

80-
return Promise<std::shared_ptr<ArrayBuffer>>::async([this, nativeMessage]() {
81-
return this->signSync(nativeMessage);
85+
return Promise<std::shared_ptr<ArrayBuffer>>::async([this, nativeMessage, nativeKey]() {
86+
return this->signSync(nativeMessage, nativeKey);
8287
}
8388
);
8489
}
8590

8691
std::shared_ptr<ArrayBuffer>
8792
HybridEdKeyPair::signSync(
88-
const std::shared_ptr<ArrayBuffer>& message
93+
const std::shared_ptr<ArrayBuffer>& message,
94+
const std::optional<std::shared_ptr<ArrayBuffer>>& key
8995
) {
90-
this->checkKeyPair();
9196

9297
size_t sig_len = 0;
9398
uint8_t* sig = NULL;
9499
EVP_MD_CTX* md_ctx = nullptr;
95100
EVP_PKEY_CTX* pkey_ctx = nullptr;
96101

102+
// get key to use for signing
103+
EVP_PKEY* pkey = this->importPrivateKey(key);
104+
97105
// key context
98106
md_ctx = EVP_MD_CTX_new();
99107
if (md_ctx == nullptr) {
@@ -107,7 +115,7 @@ HybridEdKeyPair::signSync(
107115
throw std::runtime_error("Error creating signing context: " + this->curve);
108116
}
109117

110-
if (EVP_DigestSignInit(md_ctx, &pkey_ctx, NULL, NULL, this->pkey) <= 0) {
118+
if (EVP_DigestSignInit(md_ctx, &pkey_ctx, NULL, NULL, pkey) <= 0) {
111119
EVP_MD_CTX_free(md_ctx);
112120
char* err = ERR_error_string(ERR_get_error(), NULL);
113121
throw std::runtime_error("Failed to initialize signing: " + std::string(err));
@@ -142,24 +150,31 @@ HybridEdKeyPair::signSync(
142150
std::shared_ptr<Promise<bool>>
143151
HybridEdKeyPair::verify(
144152
const std::shared_ptr<ArrayBuffer>& signature,
145-
const std::shared_ptr<ArrayBuffer>& message
153+
const std::shared_ptr<ArrayBuffer>& message,
154+
const std::optional<std::shared_ptr<ArrayBuffer>>& key
146155
) {
147156
// get owned NativeArrayBuffers before passing to sync function
148157
auto nativeSignature = ToNativeArrayBuffer(signature);
149158
auto nativeMessage = ToNativeArrayBuffer(message);
159+
std::optional<std::shared_ptr<ArrayBuffer>> nativeKey = std::nullopt;
160+
if (key.has_value()) {
161+
nativeKey = ToNativeArrayBuffer(key.value());
162+
}
150163

151-
return Promise<bool>::async([this, nativeSignature, nativeMessage]() {
152-
return this->verifySync(nativeSignature, nativeMessage);
164+
return Promise<bool>::async([this, nativeSignature, nativeMessage, nativeKey]() {
165+
return this->verifySync(nativeSignature, nativeMessage, nativeKey);
153166
}
154167
);
155168
}
156169

157170
bool
158171
HybridEdKeyPair::verifySync(
159172
const std::shared_ptr<ArrayBuffer>& signature,
160-
const std::shared_ptr<ArrayBuffer>& message
173+
const std::shared_ptr<ArrayBuffer>& message,
174+
const std::optional<std::shared_ptr<ArrayBuffer>>& key
161175
) {
162-
this->checkKeyPair();
176+
// get key to use for verifying
177+
EVP_PKEY* pkey = this->importPrivateKey(key);
163178

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

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

248+
EVP_PKEY*
249+
HybridEdKeyPair::importPrivateKey(const std::optional<std::shared_ptr<ArrayBuffer>>& key) {
250+
EVP_PKEY* pkey = nullptr;
251+
if (key.has_value()) {
252+
pkey = EVP_PKEY_new_raw_private_key(
253+
EVP_PKEY_ED25519, // TODO: use this->curve somehow
254+
NULL,
255+
key.value()->data(),
256+
32
257+
);
258+
if (pkey == nullptr) {
259+
throw std::runtime_error("Failed to read private key");
260+
}
261+
} else {
262+
this->checkKeyPair();
263+
pkey = this->pkey;
264+
}
265+
return pkey;
266+
}
267+
233268
} // namespace margelo::nitro::crypto

packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.hpp

+19-6
Original file line numberDiff line numberDiff line change
@@ -37,36 +37,49 @@ class HybridEdKeyPair : public HybridEdKeyPairSpec {
3737
) override;
3838

3939
std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>>
40-
sign(const std::shared_ptr<ArrayBuffer>& message) override;
40+
sign(
41+
const std::shared_ptr<ArrayBuffer>& message,
42+
const std::optional<std::shared_ptr<ArrayBuffer>>& key
43+
) override;
4144

4245
std::shared_ptr<ArrayBuffer>
43-
signSync(const std::shared_ptr<ArrayBuffer>& message) override;
46+
signSync(
47+
const std::shared_ptr<ArrayBuffer>& message,
48+
const std::optional<std::shared_ptr<ArrayBuffer>>& key
49+
) override;
4450

4551
std::shared_ptr<Promise<bool>>
4652
verify(
4753
const std::shared_ptr<ArrayBuffer>& signature,
48-
const std::shared_ptr<ArrayBuffer>& message
54+
const std::shared_ptr<ArrayBuffer>& message,
55+
const std::optional<std::shared_ptr<ArrayBuffer>>& key
4956
) override;
5057

5158
bool
5259
verifySync(
5360
const std::shared_ptr<ArrayBuffer>& signature,
54-
const std::shared_ptr<ArrayBuffer>& message
61+
const std::shared_ptr<ArrayBuffer>& message,
62+
const std::optional<std::shared_ptr<ArrayBuffer>>& key
5563
) override;
5664

5765
protected:
5866
std::shared_ptr<ArrayBuffer>
5967
getPublicKey() override;
6068

61-
std::shared_ptr<ArrayBuffer> getPrivateKey();
69+
std::shared_ptr<ArrayBuffer>
70+
getPrivateKey() override;
6271

6372
void checkKeyPair();
6473

65-
void setCurve(const std::string& curve);
74+
void setCurve(const std::string& curve) override;
6675

6776
private:
6877
std::string curve;
6978
EVP_PKEY* pkey = nullptr;
79+
80+
EVP_PKEY* importPrivateKey(
81+
const std::optional<std::shared_ptr<ArrayBuffer>>& key
82+
);
7083
};
7184

7285
} // namespace margelo::nitro::crypto

packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace margelo::nitro::crypto {
1717
prototype.registerHybridMethod("generateKeyPair", &HybridEdKeyPairSpec::generateKeyPair);
1818
prototype.registerHybridMethod("generateKeyPairSync", &HybridEdKeyPairSpec::generateKeyPairSync);
1919
prototype.registerHybridMethod("getPublicKey", &HybridEdKeyPairSpec::getPublicKey);
20+
prototype.registerHybridMethod("getPrivateKey", &HybridEdKeyPairSpec::getPrivateKey);
2021
prototype.registerHybridMethod("sign", &HybridEdKeyPairSpec::sign);
2122
prototype.registerHybridMethod("signSync", &HybridEdKeyPairSpec::signSync);
2223
prototype.registerHybridMethod("verify", &HybridEdKeyPairSpec::verify);

packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.hpp

+5-4
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@ namespace margelo::nitro::crypto {
5555
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;
5656
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;
5757
virtual std::shared_ptr<ArrayBuffer> getPublicKey() = 0;
58-
virtual std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> sign(const std::shared_ptr<ArrayBuffer>& message) = 0;
59-
virtual std::shared_ptr<ArrayBuffer> signSync(const std::shared_ptr<ArrayBuffer>& message) = 0;
60-
virtual std::shared_ptr<Promise<bool>> verify(const std::shared_ptr<ArrayBuffer>& signature, const std::shared_ptr<ArrayBuffer>& message) = 0;
61-
virtual bool verifySync(const std::shared_ptr<ArrayBuffer>& signature, const std::shared_ptr<ArrayBuffer>& message) = 0;
58+
virtual std::shared_ptr<ArrayBuffer> getPrivateKey() = 0;
59+
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;
60+
virtual std::shared_ptr<ArrayBuffer> signSync(const std::shared_ptr<ArrayBuffer>& message, const std::optional<std::shared_ptr<ArrayBuffer>>& key) = 0;
61+
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;
62+
virtual bool verifySync(const std::shared_ptr<ArrayBuffer>& message, const std::shared_ptr<ArrayBuffer>& signature, const std::optional<std::shared_ptr<ArrayBuffer>>& key) = 0;
6263
virtual void setCurve(const std::string& curve) = 0;
6364

6465
protected:

packages/react-native-quick-crypto/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
"dependencies": {
7171
"@craftzdog/react-native-buffer": "6.0.5",
7272
"events": "3.3.0",
73-
"react-native-nitro-modules": "0.18.1",
73+
"react-native-nitro-modules": "0.18.2",
7474
"react-native-quick-base64": "2.1.2",
7575
"readable-stream": "4.5.2",
7676
"string_decoder": "1.3.0",
@@ -88,7 +88,7 @@
8888
"eslint": "9.9.0",
8989
"eslint-plugin-react-native": "^4.1.0",
9090
"jest": "29.7.0",
91-
"nitro-codegen": "0.18.1",
91+
"nitro-codegen": "0.18.2",
9292
"prettier": "3.3.3",
9393
"react-native-builder-bob": "0.33.3",
9494
"release-it": "17.6.0",

packages/react-native-quick-crypto/src/ed.ts

+26-8
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,37 @@ export class Ed {
4040
return this.native.getPublicKey();
4141
}
4242

43-
async sign(message: ArrayBuffer): Promise<ArrayBuffer> {
44-
return this.native.sign(message);
43+
getPrivateKey(): ArrayBuffer {
44+
return this.native.getPrivateKey();
4545
}
4646

47-
signSync(message: ArrayBuffer): ArrayBuffer {
48-
return this.native.signSync(message);
47+
async sign(message: ArrayBuffer, key?: ArrayBuffer): Promise<ArrayBuffer> {
48+
return key ? this.native.sign(message, key) : this.native.sign(message);
4949
}
5050

51-
async verify(signature: ArrayBuffer, message: ArrayBuffer): Promise<boolean> {
52-
return this.native.verify(signature, message);
51+
signSync(message: ArrayBuffer, key?: ArrayBuffer): ArrayBuffer {
52+
return key
53+
? this.native.signSync(message, key)
54+
: this.native.signSync(message);
5355
}
5456

55-
verifySync(signature: ArrayBuffer, message: ArrayBuffer): boolean {
56-
return this.native.verifySync(signature, message);
57+
async verify(
58+
message: ArrayBuffer,
59+
signature: ArrayBuffer,
60+
key?: ArrayBuffer,
61+
): Promise<boolean> {
62+
return key
63+
? this.native.verify(message, signature, key)
64+
: this.native.verify(message, signature);
65+
}
66+
67+
verifySync(
68+
message: ArrayBuffer,
69+
signature: ArrayBuffer,
70+
key?: ArrayBuffer,
71+
): boolean {
72+
return key
73+
? this.native.verifySync(message, signature, key)
74+
: this.native.verifySync(message, signature);
5775
}
5876
}

0 commit comments

Comments
 (0)