• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

KeychainMDIP / kc / 13500561401

24 Feb 2025 02:37PM UTC coverage: 96.923%. Remained the same
13500561401

Pull #621

github

macterra
Fix for queueOperation
Pull Request #621: fix: mongodb queueOperation

1264 of 1335 branches covered (94.68%)

Branch coverage included in aggregate %.

2012 of 2045 relevant lines covered (98.39%)

392.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.11
/packages/cipher/src/cipher-node.js
1
import * as bip39 from 'bip39';
2
import * as secp from '@noble/secp256k1';
3
import { hmac } from '@noble/hashes/hmac';
4
import { sha256 } from '@noble/hashes/sha256';
5
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
6
import { managedNonce } from '@noble/ciphers/webcrypto/utils'
7
import { bytesToUtf8, utf8ToBytes } from '@noble/ciphers/utils';
8
import { base64url } from 'multiformats/bases/base64';
9
import canonicalize from 'canonicalize';
10
import HDKey from 'hdkey';
11
import { webcrypto } from 'node:crypto';
12

13
// node.js 18 and older, requires polyfilling globalThis.crypto
14
if (!globalThis.crypto) globalThis.crypto = webcrypto;
4!
15

16
// Polyfill for synchronous signatures
17
// Recommendation from https://github.com/paulmillr/noble-secp256k1/blob/main/README.md
18
secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m));
9,900✔
19

20
export default class CipherNode {
21

22
    generateMnemonic() {
23
        return bip39.generateMnemonic();
424✔
24
    }
25

26
    generateHDKey(mnemonic) {
27
        const seed = bip39.mnemonicToSeedSync(mnemonic);
436✔
28
        return HDKey.fromMasterSeed(seed);
434✔
29
    }
30

31
    generateHDKeyJSON(json) {
32
        return HDKey.fromJSON(json);
1,816✔
33
    }
34

35
    generateJwk(privateKeyBytes) {
36
        const compressedPublicKeyBytes = secp.getPublicKey(privateKeyBytes);
2,542✔
37
        const compressedPublicKeyHex = secp.etc.bytesToHex(compressedPublicKeyBytes);
2,542✔
38
        const curvePoints = secp.ProjectivePoint.fromHex(compressedPublicKeyHex);
2,542✔
39
        const uncompressedPublicKeyBytes = curvePoints.toRawBytes(false); // false = uncompressed
2,542✔
40
        // we need uncompressed public key so that it contains both the x and y values for the JWK format:
41
        // the first byte is a header that indicates whether the key is uncompressed (0x04 if uncompressed).
42
        // bytes 1 - 32 represent X
43
        // bytes 33 - 64 represent Y
44
        const d = base64url.baseEncode(privateKeyBytes);
2,542✔
45
        // skip the first byte because it's used as a header to indicate whether the key is uncompressed
46
        const x = base64url.baseEncode(uncompressedPublicKeyBytes.subarray(1, 33));
2,542✔
47
        const y = base64url.baseEncode(uncompressedPublicKeyBytes.subarray(33, 65));
2,542✔
48

49
        const publicJwk = {
2,542✔
50
            // alg: 'ES256K',
51
            kty: 'EC',
52
            crv: 'secp256k1',
53
            x,
54
            y
55
        };
56

57
        const privateJwk = { ...publicJwk, d };
2,542✔
58

59
        return { publicJwk: publicJwk, privateJwk: privateJwk };
2,542✔
60
    }
61

62
    generateRandomJwk() {
63
        const privKey = secp.utils.randomPrivateKey();
236✔
64
        return this.generateJwk(privKey);
236✔
65
    }
66

67
    convertJwkToCompressedBytes(jwk) {
68
        const xBytes = base64url.baseDecode(jwk.x);
3,124✔
69
        const yBytes = base64url.baseDecode(jwk.y);
3,124✔
70

71
        // Determine the prefix (02 for even y, 03 for odd y)
72
        const prefix = yBytes[yBytes.length - 1] % 2 === 0 ? 0x02 : 0x03;
3,124✔
73

74
        // Construct compressed key
75
        return new Uint8Array([prefix, ...xBytes]);
3,124✔
76
    }
77

78
    hashMessage(msg) {
79
        const hash = sha256(msg);
4,138✔
80
        return Buffer.from(hash).toString('hex');
4,136✔
81
    }
82

83
    hashJSON(json) {
84
        return this.hashMessage(canonicalize(json));
4,018✔
85
    }
86

87
    signHash(msgHash, privateJwk) {
88
        const privKey = base64url.baseDecode(privateJwk.d);
1,980✔
89
        const signature = secp.sign(msgHash, privKey);
1,980✔
90

91
        return signature.toCompactHex();
1,980✔
92
    }
93

94
    verifySig(msgHash, sigHex, publicJwk) {
95
        const compressedPublicKeyBytes = this.convertJwkToCompressedBytes(publicJwk);
2,032✔
96
        const signature = secp.Signature.fromCompact(sigHex);
2,032✔
97

98
        return secp.verify(signature, msgHash, compressedPublicKeyBytes);
2,030✔
99
    }
100

101
    encryptMessage(pubKey, privKey, message) {
102
        const priv = base64url.baseDecode(privKey.d);
766✔
103
        const pub = this.convertJwkToCompressedBytes(pubKey);
766✔
104
        const ss = secp.getSharedSecret(priv, pub);
766✔
105
        const key = ss.slice(0, 32);
766✔
106
        const chacha = managedNonce(xchacha20poly1305)(key); // manages nonces for you
766✔
107
        const data = utf8ToBytes(message);
766✔
108
        const ciphertext = chacha.encrypt(data);
766✔
109

110
        return base64url.baseEncode(ciphertext);
766✔
111
    }
112

113
    decryptMessage(pubKey, privKey, ciphertext) {
114
        const priv = base64url.baseDecode(privKey.d);
326✔
115
        const pub = this.convertJwkToCompressedBytes(pubKey);
326✔
116
        const ss = secp.getSharedSecret(priv, pub);
326✔
117
        const key = ss.slice(0, 32);
326✔
118
        const chacha = managedNonce(xchacha20poly1305)(key); // manages nonces for you
326✔
119
        const cipherdata = base64url.baseDecode(ciphertext);
326✔
120
        const data = chacha.decrypt(cipherdata);
324✔
121

122
        return bytesToUtf8(data);
268✔
123
    }
124
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc