generated from chiffre-io/template-library
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
247 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,13 @@ | ||
# @chiffre/template-library | ||
# @chiffre/crypto-sign | ||
|
||
[![NPM](https://img.shields.io/npm/v/@chiffre/template-library?color=red)](https://www.npmjs.com/package/@chiffre/template-library) | ||
[![MIT License](https://img.shields.io/github/license/chiffre-io/template-library.svg?color=blue)](https://github.com/chiffre-io/template-library/blob/next/LICENSE) | ||
[![Continuous Integration](https://github.com/chiffre-io/template-library/workflows/Continuous%20Integration/badge.svg?branch=next)](https://github.com/chiffre-io/template-library/actions) | ||
[![Coverage Status](https://coveralls.io/repos/github/chiffre-io/template-library/badge.svg?branch=next)](https://coveralls.io/github/chiffre-io/template-library?branch=next) | ||
[![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=chiffre-io/template-library)](https://dependabot.com) | ||
[![NPM](https://img.shields.io/npm/v/@chiffre/crypto-sign?color=red)](https://www.npmjs.com/package/@chiffre/crypto-sign) | ||
[![MIT License](https://img.shields.io/github/license/chiffre-io/crypto-sign.svg?color=blue)](https://github.com/chiffre-io/crypto-sign/blob/next/LICENSE) | ||
[![Continuous Integration](https://github.com/chiffre-io/crypto-sign/workflows/Continuous%20Integration/badge.svg?branch=next)](https://github.com/chiffre-io/crypto-sign/actions) | ||
[![Coverage Status](https://coveralls.io/repos/github/chiffre-io/crypto-sign/badge.svg?branch=next)](https://coveralls.io/github/chiffre-io/crypto-sign?branch=next) | ||
[![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=chiffre-io/crypto-sign)](https://dependabot.com) | ||
|
||
Template for Chiffre libraries | ||
Lightweight serialization for TweetNaCl signatures | ||
|
||
## License | ||
|
||
[MIT](https://github.com/chiffre-io/template-library/blob/next/LICENSE) - Made with ❤️ by [François Best](https://francoisbest.com). | ||
[MIT](https://github.com/chiffre-io/crypto-sign/blob/next/LICENSE) - Made with ❤️ by [François Best](https://francoisbest.com). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,109 @@ | ||
import hello from './index' | ||
import { | ||
generateKeys, | ||
importKeys, | ||
parsePublicKey, | ||
parseSecretKey, | ||
signUtf8String, | ||
publicKeyRegex, | ||
secretKeyRegex, | ||
signatureRegex, | ||
verifySignature | ||
} from './index' | ||
|
||
test('testing works', () => { | ||
expect(hello('World')).toEqual('Hello, World !') | ||
test('regex', () => { | ||
const keys = generateKeys() | ||
expect(keys.public).toMatch(publicKeyRegex) | ||
expect(keys.secret).toMatch(secretKeyRegex) | ||
expect(keys.public).not.toMatch(secretKeyRegex) | ||
expect(keys.secret).not.toMatch(publicKeyRegex) | ||
const message = signUtf8String('', keys.raw.secretKey) | ||
expect(message).toMatch(signatureRegex) | ||
}) | ||
|
||
test('generate keys', () => { | ||
const keys = generateKeys() | ||
expect(parsePublicKey(keys.public)).toEqual(keys.raw.publicKey) | ||
expect(parseSecretKey(keys.secret)).toEqual(keys.raw.secretKey) | ||
const copy = importKeys(keys.secret) | ||
expect(keys.public).toEqual(copy.public) | ||
expect(keys.secret).toEqual(copy.secret) | ||
expect(keys.public).not.toEqual(copy.secret) | ||
expect(keys.secret).not.toEqual(copy.public) | ||
expect(keys.raw.publicKey).toEqual(copy.raw.publicKey) | ||
expect(keys.raw.secretKey).toEqual(copy.raw.secretKey) | ||
expect(keys.raw.publicKey).not.toEqual(copy.raw.secretKey) | ||
expect(keys.raw.secretKey).not.toEqual(copy.raw.publicKey) | ||
}) | ||
|
||
test('codec', () => { | ||
const keys = generateKeys() | ||
const expected = 'Hello, World !' | ||
const signature = signUtf8String(expected, keys.raw.secretKey) | ||
const received = verifySignature(signature, keys.raw.publicKey) | ||
expect(received).toEqual(expected) | ||
}) | ||
|
||
test('Known test vector', () => { | ||
const secretKey = | ||
'ssk.IPwaySrr89g2ymWBrqC81qk7NCmenVN_gFmiz9gtAuTWGhwBv-mSUWbFeS9Zk00Iir8z2GM5Eue4v39FEpOiFw' | ||
const publicKey = 'spk.1hocAb_pklFmxXkvWZNNCIq_M9hjORLnuL9_RRKTohc' | ||
const message = | ||
'v1.naclsig.5InC2t-TYSFwIFOv-M2nY2zvPYD_ZVExkUqx3bxBYwJSVMvJOZqrJnrUmLuXZsmUHNO6xHX_WdbphwIih_2wD0hlbGxvLCBXb3JsZCAh==' | ||
const keys = importKeys(secretKey) | ||
expect(keys.public).toEqual(publicKey) | ||
const expected = 'Hello, World !' | ||
const received = verifySignature(message, keys.raw.publicKey) | ||
expect(received).toEqual(expected) | ||
}) | ||
|
||
test('Known test vector, no padding', () => { | ||
const secretKey = | ||
'ssk.IPwaySrr89g2ymWBrqC81qk7NCmenVN_gFmiz9gtAuTWGhwBv-mSUWbFeS9Zk00Iir8z2GM5Eue4v39FEpOiFw' | ||
const publicKey = 'spk.1hocAb_pklFmxXkvWZNNCIq_M9hjORLnuL9_RRKTohc' | ||
const message = | ||
'v1.naclsig.5InC2t-TYSFwIFOv-M2nY2zvPYD_ZVExkUqx3bxBYwJSVMvJOZqrJnrUmLuXZsmUHNO6xHX_WdbphwIih_2wD0hlbGxvLCBXb3JsZCAh' | ||
const keys = importKeys(secretKey) | ||
expect(keys.public).toEqual(publicKey) | ||
const expected = 'Hello, World !' | ||
const received = verifySignature(message, keys.raw.publicKey) | ||
expect(received).toEqual(expected) | ||
}) | ||
|
||
// Failure cases -- | ||
|
||
test('Invalid public key parsing', () => { | ||
const run = () => parsePublicKey('not a public key') | ||
expect(run).toThrowError('Invalid public key format') | ||
}) | ||
|
||
test('Invalid secret key parsing', () => { | ||
const run = () => parseSecretKey('not a secret key') | ||
expect(run).toThrowError('Invalid secret key format') | ||
}) | ||
|
||
test('Invalid secret key parsing', () => { | ||
const run = () => parseSecretKey('not a secret key') | ||
expect(run).toThrowError('Invalid secret key format') | ||
}) | ||
|
||
test('Import keys from invalid secret key', () => { | ||
const run = () => importKeys('not a secret key') | ||
expect(run).toThrowError('Invalid secret key format') | ||
}) | ||
|
||
test('Verify signature from wrong public key', () => { | ||
const publicKey = 'spk.thisisnotthecorrectpublickeyforthismessage_' | ||
const message = | ||
'v1.naclsig.5InC2t-TYSFwIFOv-M2nY2zvPYD_ZVExkUqx3bxBYwJSVMvJOZqrJnrUmLuXZsmUHNO6xHX_WdbphwIih_2wD0hlbGxvLCBXb3JsZCAh' | ||
const pk = parsePublicKey(publicKey) | ||
const run = () => verifySignature(message, pk) | ||
expect(run).toThrowError('Failed to verify signature') | ||
}) | ||
|
||
test('Verify signature with invalid format', () => { | ||
const publicKey = 'spk.1hocAb_pklFmxXkvWZNNCIq_M9hjORLnuL9_RRKTohc' | ||
const message = 'not an actual message' | ||
const pk = parsePublicKey(publicKey) | ||
const run = () => verifySignature(message, pk) | ||
expect(run).toThrowError('Invalid secret key format') | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,100 @@ | ||
export default (name: string) => `Hello, ${name} !` | ||
import nacl from 'tweetnacl' | ||
import utf8 from '@47ng/codec/dist/utf8' | ||
import b64 from '@47ng/codec/dist/b64' | ||
|
||
// -- | ||
|
||
export interface SignatureKeys { | ||
public: string | ||
secret: string | ||
raw: nacl.SignKeyPair | ||
} | ||
|
||
// -- | ||
|
||
export const publicKeyRegex = /^spk\.([a-zA-Z0-9-_]{43})$/ | ||
export const secretKeyRegex = /^ssk\.([a-zA-Z0-9-_]{86})$/ | ||
export const signatureRegex = /^v1\.naclsig\.([a-zA-Z0-9-_]{86,}={0,2})$/ | ||
|
||
// -- | ||
|
||
export function serializePublicKey(key: Uint8Array) { | ||
return `spk.${b64.encode(key).replace(/\=/g, '')}` | ||
} | ||
|
||
export function serializeSecretKey(key: Uint8Array) { | ||
return `ssk.${b64.encode(key).replace(/\=/g, '')}` | ||
} | ||
|
||
export function serializeSignature(input: Uint8Array) { | ||
return `v1.naclsig.${b64.encode(input).replace(/\=/g, '')}` | ||
} | ||
|
||
// -- | ||
|
||
export function parsePublicKey(key: string): Uint8Array { | ||
const match = key.match(publicKeyRegex) | ||
if (!match) { | ||
throw new Error('Invalid public key format') | ||
} | ||
return b64.decode(match[1]) | ||
} | ||
|
||
export function parseSecretKey(key: string): Uint8Array { | ||
const match = key.match(secretKeyRegex) | ||
if (!match) { | ||
throw new Error('Invalid secret key format') | ||
} | ||
return b64.decode(match[1]) | ||
} | ||
|
||
export function parseSignature(sig: string): Uint8Array { | ||
const match = sig.match(signatureRegex) | ||
if (!match) { | ||
throw new Error('Invalid secret key format') | ||
} | ||
return b64.decode(match[1]) | ||
} | ||
|
||
// -- | ||
|
||
export function generateKeys(): Readonly<SignatureKeys> { | ||
const keyPair = nacl.sign.keyPair() | ||
return { | ||
public: serializePublicKey(keyPair.publicKey), | ||
secret: serializeSecretKey(keyPair.secretKey), | ||
raw: keyPair | ||
} | ||
} | ||
|
||
export function importKeys(secretKey: string): Readonly<SignatureKeys> { | ||
const secretKeyBuffer = parseSecretKey(secretKey) | ||
const keyPair = nacl.sign.keyPair.fromSecretKey(secretKeyBuffer) | ||
return { | ||
public: serializePublicKey(keyPair.publicKey), | ||
secret: serializeSecretKey(keyPair.secretKey), | ||
raw: keyPair | ||
} | ||
} | ||
|
||
// -- | ||
|
||
export function signUtf8String(input: string, secretKey: Uint8Array) { | ||
return signBuffer(utf8.encode(input), secretKey) | ||
} | ||
|
||
export function signBuffer(input: Uint8Array, secretKey: Uint8Array) { | ||
const sig = nacl.sign(input, secretKey) | ||
return serializeSignature(sig) | ||
} | ||
|
||
// -- | ||
|
||
export function verifySignature(input: string, publicKey: Uint8Array) { | ||
const sig = parseSignature(input) | ||
const msg = nacl.sign.open(sig, publicKey) | ||
if (msg === null) { | ||
throw new Error('Failed to verify signature') | ||
} | ||
return utf8.decode(msg) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters