@atproto/crypto 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +3 -0
  2. package/build.js +22 -0
  3. package/dist/aes.d.ts +8 -0
  4. package/dist/const.d.ts +5 -0
  5. package/dist/did.d.ts +7 -0
  6. package/dist/index.d.ts +12 -0
  7. package/dist/index.js +3955 -0
  8. package/dist/index.js.map +7 -0
  9. package/dist/multibase.d.ts +1 -0
  10. package/dist/p256/encoding.d.ts +2 -0
  11. package/dist/p256/keypair.d.ts +19 -0
  12. package/dist/p256/operations.d.ts +4 -0
  13. package/dist/p256/plugin.d.ts +3 -0
  14. package/dist/plugins.d.ts +2 -0
  15. package/dist/random.d.ts +4 -0
  16. package/dist/secp256k1/encoding.d.ts +2 -0
  17. package/dist/secp256k1/keypair.d.ts +19 -0
  18. package/dist/secp256k1/operations.d.ts +1 -0
  19. package/dist/secp256k1/plugin.d.ts +3 -0
  20. package/dist/sha.d.ts +3 -0
  21. package/dist/verify.d.ts +1 -0
  22. package/jest.config.js +6 -0
  23. package/package.json +29 -0
  24. package/src/aes.ts +64 -0
  25. package/src/const.ts +6 -0
  26. package/src/did.ts +56 -0
  27. package/src/index.ts +15 -0
  28. package/src/multibase.ts +26 -0
  29. package/src/p256/encoding.ts +81 -0
  30. package/src/p256/keypair.ts +85 -0
  31. package/src/p256/operations.ts +64 -0
  32. package/src/p256/plugin.ts +13 -0
  33. package/src/plugins.ts +6 -0
  34. package/src/random.ts +19 -0
  35. package/src/secp256k1/encoding.ts +16 -0
  36. package/src/secp256k1/keypair.ts +65 -0
  37. package/src/secp256k1/operations.ts +16 -0
  38. package/src/secp256k1/plugin.ts +11 -0
  39. package/src/sha.ts +28 -0
  40. package/src/verify.ts +15 -0
  41. package/tests/did.test.ts +95 -0
  42. package/tests/export.test.ts +50 -0
  43. package/tests/key-compression.test.ts +69 -0
  44. package/tsconfig.build.json +4 -0
  45. package/tsconfig.build.tsbuildinfo +1 -0
  46. package/tsconfig.json +9 -0
  47. package/update-pkg.js +14 -0
@@ -0,0 +1,19 @@
1
+ import * as ucan from '@ucans/core';
2
+ export declare type EcdsaKeypairOptions = {
3
+ exportable: boolean;
4
+ };
5
+ export declare class EcdsaKeypair implements ucan.DidableKey {
6
+ jwtAlg: string;
7
+ private publicKey;
8
+ private keypair;
9
+ private exportable;
10
+ constructor(keypair: CryptoKeyPair, publicKey: Uint8Array, exportable: boolean);
11
+ static create(opts?: Partial<EcdsaKeypairOptions>): Promise<EcdsaKeypair>;
12
+ static import(jwk: JsonWebKey, opts?: Partial<EcdsaKeypairOptions>): Promise<EcdsaKeypair>;
13
+ publicKeyBytes(): Uint8Array;
14
+ publicKeyStr(encoding?: ucan.Encodings): string;
15
+ did(): string;
16
+ sign(msg: Uint8Array): Promise<Uint8Array>;
17
+ export(): Promise<JsonWebKey>;
18
+ }
19
+ export default EcdsaKeypair;
@@ -0,0 +1,4 @@
1
+ export declare const importKeypairJwk: (jwk: JsonWebKey, exportable?: boolean) => Promise<CryptoKeyPair>;
2
+ export declare const verifyDidSig: (did: string, data: Uint8Array, sig: Uint8Array) => Promise<boolean>;
3
+ export declare const verify: (publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array) => Promise<boolean>;
4
+ export declare const importEcdsaPublicKey: (keyBytes: Uint8Array) => Promise<CryptoKey>;
@@ -0,0 +1,3 @@
1
+ import { DidKeyPlugin } from '@ucans/core';
2
+ export declare const p256Plugin: DidKeyPlugin;
3
+ export default p256Plugin;
@@ -0,0 +1,2 @@
1
+ export declare const plugins: import("@ucans/core").DidKeyPlugin[];
2
+ export default plugins;
@@ -0,0 +1,4 @@
1
+ import { SupportedEncodings } from 'uint8arrays/to-string';
2
+ export declare const randomBytes: (length: number) => Uint8Array;
3
+ export declare const randomIV: () => Uint8Array;
4
+ export declare const randomStr: (byteLength: number, encoding: SupportedEncodings) => string;
@@ -0,0 +1,2 @@
1
+ export declare const compressPubkey: (pubkeyBytes: Uint8Array) => Uint8Array;
2
+ export declare const decompressPubkey: (compressed: Uint8Array) => Uint8Array;
@@ -0,0 +1,19 @@
1
+ import * as ucan from '@ucans/core';
2
+ export declare type Secp256k1KeypairOptions = {
3
+ exportable: boolean;
4
+ };
5
+ export declare class Secp256k1Keypair implements ucan.DidableKey {
6
+ private privateKey;
7
+ private exportable;
8
+ jwtAlg: string;
9
+ private publicKey;
10
+ constructor(privateKey: Uint8Array, exportable: boolean);
11
+ static create(opts?: Partial<Secp256k1KeypairOptions>): Promise<Secp256k1Keypair>;
12
+ static import(privKey: Uint8Array | string, opts?: Partial<Secp256k1KeypairOptions>): Promise<Secp256k1Keypair>;
13
+ publicKeyBytes(): Uint8Array;
14
+ publicKeyStr(encoding?: ucan.Encodings): string;
15
+ did(): string;
16
+ sign(msg: Uint8Array): Promise<Uint8Array>;
17
+ export(): Promise<Uint8Array>;
18
+ }
19
+ export default Secp256k1Keypair;
@@ -0,0 +1 @@
1
+ export declare const verifyDidSig: (did: string, data: Uint8Array, sig: Uint8Array) => Promise<boolean>;
@@ -0,0 +1,3 @@
1
+ import { DidKeyPlugin } from '@ucans/core';
2
+ export declare const secp256k1Plugin: DidKeyPlugin;
3
+ export default secp256k1Plugin;
package/dist/sha.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { Readable } from 'stream';
2
+ export declare const sha256: (input: Uint8Array | string) => Promise<Uint8Array>;
3
+ export declare const sha256Stream: (stream: Readable) => Promise<Uint8Array>;
@@ -0,0 +1 @@
1
+ export declare const verifyDidSig: (did: string, data: Uint8Array, sig: Uint8Array) => Promise<boolean>;
package/jest.config.js ADDED
@@ -0,0 +1,6 @@
1
+ const base = require('../../jest.config.base.js')
2
+
3
+ module.exports = {
4
+ ...base,
5
+ displayName: 'Crypto'
6
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@atproto/crypto",
3
+ "version": "0.0.1",
4
+ "main": "dist/index.js",
5
+ "license": "MIT",
6
+ "scripts": {
7
+ "test": "jest ",
8
+ "prettier": "prettier --check src/",
9
+ "prettier:fix": "prettier --write src/",
10
+ "lint": "eslint . --ext .ts,.tsx",
11
+ "lint:fix": "yarn lint --fix",
12
+ "verify": "run-p prettier lint",
13
+ "verify:fix": "yarn prettier:fix && yarn lint:fix",
14
+ "build": "node ./build.js",
15
+ "postbuild": "tsc --build tsconfig.build.json",
16
+ "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist",
17
+ "update-main-to-src": "node ./update-pkg.js --update-main-to-src",
18
+ "prepublish": "npm run update-main-to-dist",
19
+ "postpublish": "npm run update-main-to-src"
20
+ },
21
+ "dependencies": {
22
+ "@noble/secp256k1": "^1.7.0",
23
+ "@ucans/core": "0.11.0",
24
+ "big-integer": "^1.6.51",
25
+ "multiformats": "^9.6.4",
26
+ "one-webcrypto": "^1.0.3",
27
+ "uint8arrays": "3.0.0"
28
+ }
29
+ }
package/src/aes.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { webcrypto } from 'one-webcrypto'
2
+ import * as uint8arrays from 'uint8arrays'
3
+
4
+ import * as random from './random'
5
+
6
+ export class AesKey {
7
+ private key: CryptoKey
8
+ constructor(key: CryptoKey) {
9
+ this.key = key
10
+ }
11
+
12
+ static async create(): Promise<AesKey> {
13
+ const key = await webcrypto.subtle.generateKey(
14
+ {
15
+ name: 'AES-GCM',
16
+ length: 256,
17
+ },
18
+ true,
19
+ ['encrypt', 'decrypt'],
20
+ )
21
+ return new AesKey(key)
22
+ }
23
+
24
+ // utf8 data -> base64pad cipher
25
+ // returns base64 encrypted data with iv prepended
26
+ async encrypt(data: string): Promise<string> {
27
+ const iv = random.randomIV()
28
+ const dataBytes = uint8arrays.fromString(data, 'utf8')
29
+ const buf = await webcrypto.subtle.encrypt(
30
+ {
31
+ name: 'AES-GCM',
32
+ iv,
33
+ },
34
+ this.key,
35
+ dataBytes,
36
+ )
37
+ const encryptedBytes = new Uint8Array(buf)
38
+ const encrypted = uint8arrays.toString(
39
+ uint8arrays.concat([iv, encryptedBytes]),
40
+ 'base64pad',
41
+ )
42
+ return encrypted
43
+ }
44
+
45
+ // base64pad cipher -> utf8 data
46
+ // expects base64 encrypted data with iv prepended
47
+ async decrypt(data: string): Promise<string> {
48
+ const dataBytes = uint8arrays.fromString(data, 'base64pad')
49
+ const iv = dataBytes.slice(0, 12)
50
+ const encrypted = dataBytes.slice(12)
51
+ const buf = await webcrypto.subtle.decrypt(
52
+ {
53
+ name: 'AES-GCM',
54
+ iv,
55
+ },
56
+ this.key,
57
+ encrypted,
58
+ )
59
+ const decryptedBytes = new Uint8Array(buf)
60
+ return uint8arrays.toString(decryptedBytes, 'utf8')
61
+ }
62
+ }
63
+
64
+ export default AesKey
package/src/const.ts ADDED
@@ -0,0 +1,6 @@
1
+ export const P256_DID_PREFIX = new Uint8Array([0x80, 0x24])
2
+ export const SECP256K1_DID_PREFIX = new Uint8Array([0xe7, 0x01])
3
+ export const BASE58_DID_PREFIX = 'did:key:z' // z is the multibase prefix for base58btc byte encoding
4
+
5
+ export const P256_JWT_ALG = 'ES256'
6
+ export const SECP256K1_JWT_ALG = 'ES256K'
package/src/did.ts ADDED
@@ -0,0 +1,56 @@
1
+ import * as uint8arrays from 'uint8arrays'
2
+ import * as p256 from './p256/encoding'
3
+ import * as secp from './secp256k1/encoding'
4
+ import plugins from './plugins'
5
+ import { P256_JWT_ALG, SECP256K1_JWT_ALG } from './const'
6
+
7
+ export const DID_KEY_BASE58_PREFIX = 'did:key:z'
8
+
9
+ export type ParsedDidKey = {
10
+ jwtAlg: string
11
+ keyBytes: Uint8Array
12
+ }
13
+
14
+ export const parseDidKey = (did: string): ParsedDidKey => {
15
+ if (!did.startsWith(DID_KEY_BASE58_PREFIX)) {
16
+ throw new Error(`Incorrect prefix for did:key: ${did}`)
17
+ }
18
+ const prefixedBytes = uint8arrays.fromString(
19
+ did.slice(DID_KEY_BASE58_PREFIX.length),
20
+ 'base58btc',
21
+ )
22
+ const plugin = plugins.find((p) => hasPrefix(prefixedBytes, p.prefix))
23
+ if (!plugin) {
24
+ throw new Error('Unsupported key type')
25
+ }
26
+ let keyBytes = prefixedBytes.slice(plugin.prefix.length)
27
+ if (plugin.jwtAlg === P256_JWT_ALG) {
28
+ keyBytes = p256.decompressPubkey(keyBytes)
29
+ } else if (plugin.jwtAlg === SECP256K1_JWT_ALG) {
30
+ keyBytes = secp.decompressPubkey(keyBytes)
31
+ }
32
+ return {
33
+ jwtAlg: plugin.jwtAlg,
34
+ keyBytes,
35
+ }
36
+ }
37
+
38
+ export const formatDidKey = (jwtAlg: string, keyBytes: Uint8Array): string => {
39
+ const plugin = plugins.find((p) => p.jwtAlg === jwtAlg)
40
+ if (!plugin) {
41
+ throw new Error('Unsupported key type')
42
+ }
43
+ if (jwtAlg === P256_JWT_ALG) {
44
+ keyBytes = p256.compressPubkey(keyBytes)
45
+ } else if (jwtAlg === SECP256K1_JWT_ALG) {
46
+ keyBytes = secp.compressPubkey(keyBytes)
47
+ }
48
+ const prefixedBytes = uint8arrays.concat([plugin.prefix, keyBytes])
49
+ return (
50
+ DID_KEY_BASE58_PREFIX + uint8arrays.toString(prefixedBytes, 'base58btc')
51
+ )
52
+ }
53
+
54
+ const hasPrefix = (bytes: Uint8Array, prefix: Uint8Array): boolean => {
55
+ return uint8arrays.equals(prefix, bytes.subarray(0, prefix.byteLength))
56
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export * from './aes'
2
+ export * from './const'
3
+ export * from './did'
4
+ export * from './multibase'
5
+ export * from './random'
6
+ export * from './sha'
7
+ export * from './verify'
8
+
9
+ export * from './p256/keypair'
10
+ export * from './p256/plugin'
11
+
12
+ export * from './secp256k1/keypair'
13
+ export * from './secp256k1/plugin'
14
+
15
+ export type { DidableKey } from '@ucans/core'
@@ -0,0 +1,26 @@
1
+ import * as uint8arrays from 'uint8arrays'
2
+
3
+ export const multibaseToBytes = (mb: string): Uint8Array => {
4
+ const base = mb[0]
5
+ const key = mb.slice(1)
6
+ switch (base) {
7
+ case 'f':
8
+ return uint8arrays.fromString(key, 'base16')
9
+ case 'F':
10
+ return uint8arrays.fromString(key, 'base16upper')
11
+ case 'b':
12
+ return uint8arrays.fromString(key, 'base32')
13
+ case 'B':
14
+ return uint8arrays.fromString(key, 'base32upper')
15
+ case 'z':
16
+ return uint8arrays.fromString(key, 'base58btc')
17
+ case 'm':
18
+ return uint8arrays.fromString(key, 'base64')
19
+ case 'u':
20
+ return uint8arrays.fromString(key, 'base64url')
21
+ case 'U':
22
+ return uint8arrays.fromString(key, 'base64urlpad')
23
+ default:
24
+ throw new Error(`Unsupported multibase: :${mb}`)
25
+ }
26
+ }
@@ -0,0 +1,81 @@
1
+ import bigInt from 'big-integer'
2
+ import * as uint8arrays from 'uint8arrays'
3
+
4
+ // PUBLIC KEY COMPRESSION
5
+ // ------------------------
6
+
7
+ // Compression & Decompression algos from:
8
+ // https://stackoverflow.com/questions/48521840/biginteger-to-a-uint8array-of-bytes
9
+
10
+ // Public key compression for NIST P-256
11
+ export const compressPubkey = (pubkeyBytes: Uint8Array): Uint8Array => {
12
+ if (pubkeyBytes.length !== 65) {
13
+ throw new Error('Expected 65 byte pubkey')
14
+ } else if (pubkeyBytes[0] !== 0x04) {
15
+ throw new Error('Expected first byte to be 0x04')
16
+ }
17
+ // first byte is a prefix
18
+ const x = pubkeyBytes.slice(1, 33)
19
+ const y = pubkeyBytes.slice(33, 65)
20
+ const out = new Uint8Array(x.length + 1)
21
+
22
+ out[0] = 2 + (y[y.length - 1] & 1)
23
+ out.set(x, 1)
24
+
25
+ return out
26
+ }
27
+
28
+ // Public key decompression for NIST P-256
29
+ export const decompressPubkey = (compressed: Uint8Array): Uint8Array => {
30
+ if (compressed.length !== 33) {
31
+ throw new Error('Expected 33 byte compress pubkey')
32
+ } else if (compressed[0] !== 0x02 && compressed[0] !== 0x03) {
33
+ throw new Error('Expected first byte to be 0x02 or 0x03')
34
+ }
35
+ // Consts for P256 curve
36
+ const two = bigInt(2)
37
+ // 115792089210356248762697446949407573530086143415290314195533631308867097853951
38
+ const prime = two
39
+ .pow(256)
40
+ .subtract(two.pow(224))
41
+ .add(two.pow(192))
42
+ .add(two.pow(96))
43
+ .subtract(1)
44
+ const b = bigInt(
45
+ '41058363725152142129326129780047268409114441015993725554835256314039467401291',
46
+ )
47
+
48
+ // Pre-computed value, or literal
49
+ const pIdent = prime.add(1).divide(4) // 28948022302589062190674361737351893382521535853822578548883407827216774463488
50
+
51
+ // This value must be 2 or 3. 4 indicates an uncompressed key, and anything else is invalid.
52
+ const signY = bigInt(compressed[0] - 2)
53
+ const x = compressed.slice(1)
54
+ const xBig = bigInt(uint8arrays.toString(x, 'base10'))
55
+
56
+ // y^2 = x^3 - 3x + b
57
+ const maybeY = xBig
58
+ .pow(3)
59
+ .subtract(xBig.multiply(3))
60
+ .add(b)
61
+ .modPow(pIdent, prime)
62
+
63
+ let yBig
64
+ // If the parity matches, we found our root, otherwise it's the other root
65
+ if (maybeY.mod(2).equals(signY)) {
66
+ yBig = maybeY
67
+ } else {
68
+ // y = prime - y
69
+ yBig = prime.subtract(maybeY)
70
+ }
71
+ const y = uint8arrays.fromString(yBig.toString(10), 'base10')
72
+
73
+ // left-pad for smaller than 32 byte y
74
+ const offset = 32 - y.length
75
+ const yPadded = new Uint8Array(32)
76
+ yPadded.set(y, offset)
77
+
78
+ // concat coords & prepend P-256 prefix
79
+ const publicKey = uint8arrays.concat([[0x04], x, yPadded])
80
+ return publicKey
81
+ }
@@ -0,0 +1,85 @@
1
+ import { webcrypto } from 'one-webcrypto'
2
+ import * as uint8arrays from 'uint8arrays'
3
+
4
+ import * as ucan from '@ucans/core'
5
+
6
+ import * as did from '../did'
7
+ import * as operations from './operations'
8
+ import { P256_JWT_ALG } from '../const'
9
+
10
+ export type EcdsaKeypairOptions = {
11
+ exportable: boolean
12
+ }
13
+
14
+ export class EcdsaKeypair implements ucan.DidableKey {
15
+ jwtAlg = P256_JWT_ALG
16
+ private publicKey: Uint8Array
17
+ private keypair: CryptoKeyPair
18
+ private exportable: boolean
19
+
20
+ constructor(
21
+ keypair: CryptoKeyPair,
22
+ publicKey: Uint8Array,
23
+ exportable: boolean,
24
+ ) {
25
+ this.keypair = keypair
26
+ this.publicKey = publicKey
27
+ this.exportable = exportable
28
+ }
29
+
30
+ static async create(
31
+ opts?: Partial<EcdsaKeypairOptions>,
32
+ ): Promise<EcdsaKeypair> {
33
+ const { exportable = false } = opts || {}
34
+ const keypair = await webcrypto.subtle.generateKey(
35
+ { name: 'ECDSA', namedCurve: 'P-256' },
36
+ exportable,
37
+ ['sign', 'verify'],
38
+ )
39
+ const pubkeyBuf = await webcrypto.subtle.exportKey('raw', keypair.publicKey)
40
+ const pubkeyBytes = new Uint8Array(pubkeyBuf)
41
+ return new EcdsaKeypair(keypair, pubkeyBytes, exportable)
42
+ }
43
+
44
+ static async import(
45
+ jwk: JsonWebKey,
46
+ opts?: Partial<EcdsaKeypairOptions>,
47
+ ): Promise<EcdsaKeypair> {
48
+ const { exportable = false } = opts || {}
49
+ const keypair = await operations.importKeypairJwk(jwk, exportable)
50
+ const pubkeyBuf = await webcrypto.subtle.exportKey('raw', keypair.publicKey)
51
+ const pubkeyBytes = new Uint8Array(pubkeyBuf)
52
+ return new EcdsaKeypair(keypair, pubkeyBytes, exportable)
53
+ }
54
+
55
+ publicKeyBytes(): Uint8Array {
56
+ return this.publicKey
57
+ }
58
+
59
+ publicKeyStr(encoding: ucan.Encodings = 'base64pad'): string {
60
+ return uint8arrays.toString(this.publicKey, encoding)
61
+ }
62
+
63
+ did(): string {
64
+ return did.formatDidKey(this.jwtAlg, this.publicKey)
65
+ }
66
+
67
+ async sign(msg: Uint8Array): Promise<Uint8Array> {
68
+ const buf = await webcrypto.subtle.sign(
69
+ { name: 'ECDSA', hash: { name: 'SHA-256' } },
70
+ this.keypair.privateKey,
71
+ msg.buffer,
72
+ )
73
+ return new Uint8Array(buf)
74
+ }
75
+
76
+ async export(): Promise<JsonWebKey> {
77
+ if (!this.exportable) {
78
+ throw new Error('Private key is not exportable')
79
+ }
80
+ const jwk = await webcrypto.subtle.exportKey('jwk', this.keypair.privateKey)
81
+ return jwk
82
+ }
83
+ }
84
+
85
+ export default EcdsaKeypair
@@ -0,0 +1,64 @@
1
+ import { webcrypto } from 'one-webcrypto'
2
+ import { P256_JWT_ALG } from '../const'
3
+ import { parseDidKey } from '../did'
4
+
5
+ export const importKeypairJwk = async (
6
+ jwk: JsonWebKey,
7
+ exportable = false,
8
+ ): Promise<CryptoKeyPair> => {
9
+ const privateKey = await webcrypto.subtle.importKey(
10
+ 'jwk',
11
+ jwk,
12
+ { name: 'ECDSA', namedCurve: 'P-256' },
13
+ exportable,
14
+ ['sign'],
15
+ )
16
+ const { kty, crv, x, y } = jwk
17
+ const pubKeyJwk = { kty, crv, x, y }
18
+ const publicKey = await webcrypto.subtle.importKey(
19
+ 'jwk',
20
+ pubKeyJwk,
21
+ { name: 'ECDSA', namedCurve: 'P-256' },
22
+ true,
23
+ ['verify'],
24
+ )
25
+ return { privateKey, publicKey }
26
+ }
27
+
28
+ export const verifyDidSig = async (
29
+ did: string,
30
+ data: Uint8Array,
31
+ sig: Uint8Array,
32
+ ): Promise<boolean> => {
33
+ const { jwtAlg, keyBytes } = parseDidKey(did)
34
+ if (jwtAlg !== P256_JWT_ALG) {
35
+ throw new Error(`Not a P-256 did:key: ${did}`)
36
+ }
37
+ return verify(keyBytes, data, sig)
38
+ }
39
+
40
+ export const verify = async (
41
+ publicKey: Uint8Array,
42
+ data: Uint8Array,
43
+ sig: Uint8Array,
44
+ ): Promise<boolean> => {
45
+ const importedKey = await importEcdsaPublicKey(publicKey)
46
+ return webcrypto.subtle.verify(
47
+ { name: 'ECDSA', hash: { name: 'SHA-256' } },
48
+ importedKey,
49
+ sig,
50
+ data,
51
+ )
52
+ }
53
+
54
+ export const importEcdsaPublicKey = async (
55
+ keyBytes: Uint8Array,
56
+ ): Promise<CryptoKey> => {
57
+ return webcrypto.subtle.importKey(
58
+ 'raw',
59
+ keyBytes,
60
+ { name: 'ECDSA', namedCurve: 'P-256' },
61
+ true,
62
+ ['verify'],
63
+ )
64
+ }
@@ -0,0 +1,13 @@
1
+ import { DidKeyPlugin } from '@ucans/core'
2
+
3
+ import { P256_DID_PREFIX, P256_JWT_ALG } from '../const'
4
+
5
+ import * as operations from './operations'
6
+
7
+ export const p256Plugin: DidKeyPlugin = {
8
+ prefix: P256_DID_PREFIX,
9
+ jwtAlg: P256_JWT_ALG,
10
+ verifySignature: operations.verifyDidSig,
11
+ }
12
+
13
+ export default p256Plugin
package/src/plugins.ts ADDED
@@ -0,0 +1,6 @@
1
+ import p256Plugin from './p256/plugin'
2
+ import secp256k1Plugin from './secp256k1/plugin'
3
+
4
+ export const plugins = [p256Plugin, secp256k1Plugin]
5
+
6
+ export default plugins
package/src/random.ts ADDED
@@ -0,0 +1,19 @@
1
+ import * as uint8arrays from 'uint8arrays'
2
+ import { webcrypto } from 'one-webcrypto'
3
+ import { SupportedEncodings } from 'uint8arrays/to-string'
4
+
5
+ export const randomBytes = (length: number): Uint8Array => {
6
+ return webcrypto.getRandomValues(new Uint8Array(length))
7
+ }
8
+
9
+ export const randomIV = (): Uint8Array => {
10
+ return randomBytes(12)
11
+ }
12
+
13
+ export const randomStr = (
14
+ byteLength: number,
15
+ encoding: SupportedEncodings,
16
+ ): string => {
17
+ const bytes = randomBytes(byteLength)
18
+ return uint8arrays.toString(bytes, encoding)
19
+ }
@@ -0,0 +1,16 @@
1
+ import * as secp from '@noble/secp256k1'
2
+
3
+ export const compressPubkey = (pubkeyBytes: Uint8Array): Uint8Array => {
4
+ const hex = secp.utils.bytesToHex(pubkeyBytes)
5
+ const point = secp.Point.fromHex(hex)
6
+ return point.toRawBytes(true)
7
+ }
8
+
9
+ export const decompressPubkey = (compressed: Uint8Array): Uint8Array => {
10
+ if (compressed.length !== 33) {
11
+ throw new Error('Expected 33 byte compress pubkey')
12
+ }
13
+ const hex = secp.utils.bytesToHex(compressed)
14
+ const point = secp.Point.fromHex(hex)
15
+ return point.toRawBytes(false)
16
+ }
@@ -0,0 +1,65 @@
1
+ import * as ucan from '@ucans/core'
2
+ import * as secp from '@noble/secp256k1'
3
+ import * as uint8arrays from 'uint8arrays'
4
+ import * as did from '../did'
5
+ import { SECP256K1_JWT_ALG } from '../const'
6
+
7
+ export type Secp256k1KeypairOptions = {
8
+ exportable: boolean
9
+ }
10
+
11
+ export class Secp256k1Keypair implements ucan.DidableKey {
12
+ jwtAlg = SECP256K1_JWT_ALG
13
+ private publicKey: Uint8Array
14
+
15
+ constructor(private privateKey: Uint8Array, private exportable: boolean) {
16
+ this.publicKey = secp.getPublicKey(privateKey)
17
+ }
18
+
19
+ static async create(
20
+ opts?: Partial<Secp256k1KeypairOptions>,
21
+ ): Promise<Secp256k1Keypair> {
22
+ const { exportable = false } = opts || {}
23
+ const privKey = secp.utils.randomPrivateKey()
24
+ return new Secp256k1Keypair(privKey, exportable)
25
+ }
26
+
27
+ static async import(
28
+ privKey: Uint8Array | string,
29
+ opts?: Partial<Secp256k1KeypairOptions>,
30
+ ): Promise<Secp256k1Keypair> {
31
+ const { exportable = false } = opts || {}
32
+ const privKeyBytes =
33
+ typeof privKey === 'string'
34
+ ? uint8arrays.fromString(privKey, 'hex')
35
+ : privKey
36
+ return new Secp256k1Keypair(privKeyBytes, exportable)
37
+ }
38
+
39
+ publicKeyBytes(): Uint8Array {
40
+ return this.publicKey
41
+ }
42
+
43
+ publicKeyStr(encoding: ucan.Encodings = 'base64pad'): string {
44
+ return uint8arrays.toString(this.publicKey, encoding)
45
+ }
46
+
47
+ did(): string {
48
+ return did.formatDidKey(this.jwtAlg, this.publicKey)
49
+ }
50
+
51
+ async sign(msg: Uint8Array): Promise<Uint8Array> {
52
+ const msgHash = await secp.utils.sha256(msg)
53
+ // return raw 64 byte sig not DER-encoded
54
+ return secp.sign(msgHash, this.privateKey, { der: false })
55
+ }
56
+
57
+ async export(): Promise<Uint8Array> {
58
+ if (!this.exportable) {
59
+ throw new Error('Private key is not exportable')
60
+ }
61
+ return this.privateKey
62
+ }
63
+ }
64
+
65
+ export default Secp256k1Keypair
@@ -0,0 +1,16 @@
1
+ import * as secp from '@noble/secp256k1'
2
+ import { SECP256K1_JWT_ALG } from '../const'
3
+ import { parseDidKey } from '../did'
4
+
5
+ export const verifyDidSig = async (
6
+ did: string,
7
+ data: Uint8Array,
8
+ sig: Uint8Array,
9
+ ): Promise<boolean> => {
10
+ const { jwtAlg, keyBytes } = parseDidKey(did)
11
+ if (jwtAlg !== SECP256K1_JWT_ALG) {
12
+ throw new Error(`Not a secp256k1 did:key: ${did}`)
13
+ }
14
+ const msgHash = await secp.utils.sha256(data)
15
+ return secp.verify(sig, msgHash, keyBytes)
16
+ }