@atproto/crypto 0.5.2 → 0.5.3
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.
- package/CHANGELOG.md +10 -0
- package/package.json +14 -9
- package/jest.config.cjs +0 -21
- package/src/const.ts +0 -7
- package/src/did.ts +0 -53
- package/src/index.ts +0 -14
- package/src/multibase.ts +0 -53
- package/src/p256/encoding.ts +0 -14
- package/src/p256/keypair.ts +0 -70
- package/src/p256/operations.ts +0 -43
- package/src/p256/plugin.ts +0 -13
- package/src/plugins.ts +0 -4
- package/src/random.ts +0 -28
- package/src/secp256k1/encoding.ts +0 -14
- package/src/secp256k1/keypair.ts +0 -70
- package/src/secp256k1/operations.ts +0 -43
- package/src/secp256k1/plugin.ts +0 -13
- package/src/sha.ts +0 -20
- package/src/types.ts +0 -32
- package/src/utils.ts +0 -23
- package/src/verify.ts +0 -34
- package/tests/did.test.ts +0 -90
- package/tests/key-compression.test.ts +0 -69
- package/tests/keypairs.test.ts +0 -71
- package/tests/random.test.ts +0 -15
- package/tests/signatures.test.ts +0 -301
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @atproto/crypto
|
|
2
2
|
|
|
3
|
+
## 0.5.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update TypeScript build to rely on references to composite internal projects
|
|
8
|
+
|
|
9
|
+
- [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Bundle only necessary files in the NPM tarball, including the `CHANGELOG.md` and `README.md` files (if present).
|
|
10
|
+
|
|
11
|
+
- [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Build with `noImplicitAny` enabled
|
|
12
|
+
|
|
3
13
|
## 0.5.2
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/crypto",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Library for cryptographic keys and signing in atproto",
|
|
6
6
|
"keywords": [
|
|
@@ -13,6 +13,18 @@
|
|
|
13
13
|
"url": "https://github.com/bluesky-social/atproto",
|
|
14
14
|
"directory": "packages/crypto"
|
|
15
15
|
},
|
|
16
|
+
"files": [
|
|
17
|
+
"./dist",
|
|
18
|
+
"./README.md",
|
|
19
|
+
"./CHANGELOG.md"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
16
28
|
"engines": {
|
|
17
29
|
"node": ">=22"
|
|
18
30
|
},
|
|
@@ -23,14 +35,7 @@
|
|
|
23
35
|
},
|
|
24
36
|
"devDependencies": {
|
|
25
37
|
"jest": "^30.0.0",
|
|
26
|
-
"@atproto/common": "^0.6.
|
|
27
|
-
},
|
|
28
|
-
"type": "module",
|
|
29
|
-
"exports": {
|
|
30
|
-
".": {
|
|
31
|
-
"types": "./dist/index.d.ts",
|
|
32
|
-
"default": "./dist/index.js"
|
|
33
|
-
}
|
|
38
|
+
"@atproto/common": "^0.6.5"
|
|
34
39
|
},
|
|
35
40
|
"scripts": {
|
|
36
41
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
package/jest.config.cjs
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/** @type {import('jest').Config} */
|
|
2
|
-
module.exports = {
|
|
3
|
-
displayName: 'Crypto',
|
|
4
|
-
transform: {
|
|
5
|
-
'^.+\\.(t|j)s$': [
|
|
6
|
-
'@swc/jest',
|
|
7
|
-
{
|
|
8
|
-
jsc: {
|
|
9
|
-
parser: { syntax: 'typescript', importAttributes: true },
|
|
10
|
-
experimental: { keepImportAttributes: true },
|
|
11
|
-
transform: {},
|
|
12
|
-
},
|
|
13
|
-
module: { type: 'es6' },
|
|
14
|
-
},
|
|
15
|
-
],
|
|
16
|
-
},
|
|
17
|
-
extensionsToTreatAsEsm: ['.ts'],
|
|
18
|
-
transformIgnorePatterns: [],
|
|
19
|
-
setupFiles: ['<rootDir>/../../test.setup.ts'],
|
|
20
|
-
moduleNameMapper: { '^(\\.\\.?\\/.+)\\.js$': ['$1.ts', '$1.js'] },
|
|
21
|
-
}
|
package/src/const.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export const P256_DID_PREFIX = new Uint8Array([0x80, 0x24])
|
|
2
|
-
export const SECP256K1_DID_PREFIX = new Uint8Array([0xe7, 0x01])
|
|
3
|
-
export const BASE58_MULTIBASE_PREFIX = 'z'
|
|
4
|
-
export const DID_KEY_PREFIX = 'did:key:'
|
|
5
|
-
|
|
6
|
-
export const P256_JWT_ALG = 'ES256'
|
|
7
|
-
export const SECP256K1_JWT_ALG = 'ES256K'
|
package/src/did.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import * as uint8arrays from 'uint8arrays'
|
|
2
|
-
import { BASE58_MULTIBASE_PREFIX, DID_KEY_PREFIX } from './const.js'
|
|
3
|
-
import { plugins } from './plugins.js'
|
|
4
|
-
import { extractMultikey, extractPrefixedBytes, hasPrefix } from './utils.js'
|
|
5
|
-
|
|
6
|
-
export type ParsedMultikey = {
|
|
7
|
-
jwtAlg: string
|
|
8
|
-
keyBytes: Uint8Array
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const parseMultikey = (multikey: string): ParsedMultikey => {
|
|
12
|
-
const prefixedBytes = extractPrefixedBytes(multikey)
|
|
13
|
-
const plugin = plugins.find((p) => hasPrefix(prefixedBytes, p.prefix))
|
|
14
|
-
if (!plugin) {
|
|
15
|
-
throw new Error('Unsupported key type')
|
|
16
|
-
}
|
|
17
|
-
const keyBytes = plugin.decompressPubkey(
|
|
18
|
-
prefixedBytes.slice(plugin.prefix.length),
|
|
19
|
-
)
|
|
20
|
-
return {
|
|
21
|
-
jwtAlg: plugin.jwtAlg,
|
|
22
|
-
keyBytes,
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const formatMultikey = (
|
|
27
|
-
jwtAlg: string,
|
|
28
|
-
keyBytes: Uint8Array,
|
|
29
|
-
): string => {
|
|
30
|
-
const plugin = plugins.find((p) => p.jwtAlg === jwtAlg)
|
|
31
|
-
if (!plugin) {
|
|
32
|
-
throw new Error('Unsupported key type')
|
|
33
|
-
}
|
|
34
|
-
const prefixedBytes = uint8arrays.concat([
|
|
35
|
-
plugin.prefix,
|
|
36
|
-
plugin.compressPubkey(keyBytes),
|
|
37
|
-
])
|
|
38
|
-
return (
|
|
39
|
-
BASE58_MULTIBASE_PREFIX + uint8arrays.toString(prefixedBytes, 'base58btc')
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const parseDidKey = (did: string): ParsedMultikey => {
|
|
44
|
-
const multikey = extractMultikey(did)
|
|
45
|
-
return parseMultikey(multikey)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function formatDidKey(
|
|
49
|
-
jwtAlg: string,
|
|
50
|
-
keyBytes: Uint8Array,
|
|
51
|
-
): `did:key:${string}` {
|
|
52
|
-
return `${DID_KEY_PREFIX}${formatMultikey(jwtAlg, keyBytes)}` as const
|
|
53
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export * from './const.js'
|
|
2
|
-
export * from './did.js'
|
|
3
|
-
export * from './multibase.js'
|
|
4
|
-
export * from './random.js'
|
|
5
|
-
export * from './sha.js'
|
|
6
|
-
export * from './types.js'
|
|
7
|
-
export * from './verify.js'
|
|
8
|
-
export * from './utils.js'
|
|
9
|
-
|
|
10
|
-
export * from './p256/keypair.js'
|
|
11
|
-
export * from './p256/plugin.js'
|
|
12
|
-
|
|
13
|
-
export * from './secp256k1/keypair.js'
|
|
14
|
-
export * from './secp256k1/plugin.js'
|
package/src/multibase.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import * as uint8arrays from 'uint8arrays'
|
|
2
|
-
import { SupportedEncodings } from 'uint8arrays/to-string'
|
|
3
|
-
|
|
4
|
-
export const multibaseToBytes = (mb: string): Uint8Array => {
|
|
5
|
-
const base = mb[0]
|
|
6
|
-
const key = mb.slice(1)
|
|
7
|
-
switch (base) {
|
|
8
|
-
case 'f':
|
|
9
|
-
return uint8arrays.fromString(key, 'base16')
|
|
10
|
-
case 'F':
|
|
11
|
-
return uint8arrays.fromString(key, 'base16upper')
|
|
12
|
-
case 'b':
|
|
13
|
-
return uint8arrays.fromString(key, 'base32')
|
|
14
|
-
case 'B':
|
|
15
|
-
return uint8arrays.fromString(key, 'base32upper')
|
|
16
|
-
case 'z':
|
|
17
|
-
return uint8arrays.fromString(key, 'base58btc')
|
|
18
|
-
case 'm':
|
|
19
|
-
return uint8arrays.fromString(key, 'base64')
|
|
20
|
-
case 'u':
|
|
21
|
-
return uint8arrays.fromString(key, 'base64url')
|
|
22
|
-
case 'U':
|
|
23
|
-
return uint8arrays.fromString(key, 'base64urlpad')
|
|
24
|
-
default:
|
|
25
|
-
throw new Error(`Unsupported multibase: :${mb}`)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const bytesToMultibase = (
|
|
30
|
-
mb: Uint8Array,
|
|
31
|
-
encoding: SupportedEncodings,
|
|
32
|
-
): string => {
|
|
33
|
-
switch (encoding) {
|
|
34
|
-
case 'base16':
|
|
35
|
-
return 'f' + uint8arrays.toString(mb, encoding)
|
|
36
|
-
case 'base16upper':
|
|
37
|
-
return 'F' + uint8arrays.toString(mb, encoding)
|
|
38
|
-
case 'base32':
|
|
39
|
-
return 'b' + uint8arrays.toString(mb, encoding)
|
|
40
|
-
case 'base32upper':
|
|
41
|
-
return 'B' + uint8arrays.toString(mb, encoding)
|
|
42
|
-
case 'base58btc':
|
|
43
|
-
return 'z' + uint8arrays.toString(mb, encoding)
|
|
44
|
-
case 'base64':
|
|
45
|
-
return 'm' + uint8arrays.toString(mb, encoding)
|
|
46
|
-
case 'base64url':
|
|
47
|
-
return 'u' + uint8arrays.toString(mb, encoding)
|
|
48
|
-
case 'base64urlpad':
|
|
49
|
-
return 'U' + uint8arrays.toString(mb, encoding)
|
|
50
|
-
default:
|
|
51
|
-
throw new Error(`Unsupported multibase: :${encoding}`)
|
|
52
|
-
}
|
|
53
|
-
}
|
package/src/p256/encoding.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { p256 } from '@noble/curves/p256'
|
|
2
|
-
|
|
3
|
-
export const compressPubkey = (pubkeyBytes: Uint8Array): Uint8Array => {
|
|
4
|
-
const point = p256.ProjectivePoint.fromHex(pubkeyBytes)
|
|
5
|
-
return point.toRawBytes(true)
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const decompressPubkey = (compressed: Uint8Array): Uint8Array => {
|
|
9
|
-
if (compressed.length !== 33) {
|
|
10
|
-
throw new Error('Expected 33 byte compress pubkey')
|
|
11
|
-
}
|
|
12
|
-
const point = p256.ProjectivePoint.fromHex(compressed)
|
|
13
|
-
return point.toRawBytes(false)
|
|
14
|
-
}
|
package/src/p256/keypair.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { p256 } from '@noble/curves/p256'
|
|
2
|
-
import { sha256 } from '@noble/hashes/sha256'
|
|
3
|
-
import {
|
|
4
|
-
fromString as ui8FromString,
|
|
5
|
-
toString as ui8ToString,
|
|
6
|
-
} from 'uint8arrays'
|
|
7
|
-
import { SupportedEncodings } from 'uint8arrays/to-string'
|
|
8
|
-
import { P256_JWT_ALG } from '../const.js'
|
|
9
|
-
import * as did from '../did.js'
|
|
10
|
-
import { Keypair } from '../types.js'
|
|
11
|
-
|
|
12
|
-
export type P256KeypairOptions = {
|
|
13
|
-
exportable: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class P256Keypair implements Keypair {
|
|
17
|
-
jwtAlg = P256_JWT_ALG
|
|
18
|
-
private publicKey: Uint8Array
|
|
19
|
-
|
|
20
|
-
constructor(
|
|
21
|
-
private privateKey: Uint8Array,
|
|
22
|
-
private exportable: boolean,
|
|
23
|
-
) {
|
|
24
|
-
this.publicKey = p256.getPublicKey(privateKey)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
static async create(
|
|
28
|
-
opts?: Partial<P256KeypairOptions>,
|
|
29
|
-
): Promise<P256Keypair> {
|
|
30
|
-
const { exportable = false } = opts || {}
|
|
31
|
-
const privKey = p256.utils.randomPrivateKey()
|
|
32
|
-
return new P256Keypair(privKey, exportable)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
static async import(
|
|
36
|
-
privKey: Uint8Array | string,
|
|
37
|
-
opts?: Partial<P256KeypairOptions>,
|
|
38
|
-
): Promise<P256Keypair> {
|
|
39
|
-
const { exportable = false } = opts || {}
|
|
40
|
-
const privKeyBytes =
|
|
41
|
-
typeof privKey === 'string' ? ui8FromString(privKey, 'hex') : privKey
|
|
42
|
-
return new P256Keypair(privKeyBytes, exportable)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
publicKeyBytes(): Uint8Array {
|
|
46
|
-
return this.publicKey
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
publicKeyStr(encoding: SupportedEncodings = 'base64pad'): string {
|
|
50
|
-
return ui8ToString(this.publicKey, encoding)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
did(): string {
|
|
54
|
-
return did.formatDidKey(this.jwtAlg, this.publicKey)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async sign(msg: Uint8Array): Promise<Uint8Array> {
|
|
58
|
-
const msgHash = await sha256(msg)
|
|
59
|
-
// return raw 64 byte sig not DER-encoded
|
|
60
|
-
const sig = await p256.sign(msgHash, this.privateKey, { lowS: true })
|
|
61
|
-
return sig.toCompactRawBytes()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async export(): Promise<Uint8Array> {
|
|
65
|
-
if (!this.exportable) {
|
|
66
|
-
throw new Error('Private key is not exportable')
|
|
67
|
-
}
|
|
68
|
-
return this.privateKey
|
|
69
|
-
}
|
|
70
|
-
}
|
package/src/p256/operations.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { p256 } from '@noble/curves/p256'
|
|
2
|
-
import { sha256 } from '@noble/hashes/sha256'
|
|
3
|
-
import { equals as ui8equals } from 'uint8arrays'
|
|
4
|
-
import { P256_DID_PREFIX } from '../const.js'
|
|
5
|
-
import { VerifyOptions } from '../types.js'
|
|
6
|
-
import { extractMultikey, extractPrefixedBytes, hasPrefix } from '../utils.js'
|
|
7
|
-
|
|
8
|
-
export const verifyDidSig = async (
|
|
9
|
-
did: string,
|
|
10
|
-
data: Uint8Array,
|
|
11
|
-
sig: Uint8Array,
|
|
12
|
-
opts?: VerifyOptions,
|
|
13
|
-
): Promise<boolean> => {
|
|
14
|
-
const prefixedBytes = extractPrefixedBytes(extractMultikey(did))
|
|
15
|
-
if (!hasPrefix(prefixedBytes, P256_DID_PREFIX)) {
|
|
16
|
-
throw new Error(`Not a P-256 did:key: ${did}`)
|
|
17
|
-
}
|
|
18
|
-
const keyBytes = prefixedBytes.slice(P256_DID_PREFIX.length)
|
|
19
|
-
return verifySig(keyBytes, data, sig, opts)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const verifySig = async (
|
|
23
|
-
publicKey: Uint8Array,
|
|
24
|
-
data: Uint8Array,
|
|
25
|
-
sig: Uint8Array,
|
|
26
|
-
opts?: VerifyOptions,
|
|
27
|
-
): Promise<boolean> => {
|
|
28
|
-
const allowMalleable = opts?.allowMalleableSig ?? false
|
|
29
|
-
const msgHash = await sha256(data)
|
|
30
|
-
return p256.verify(sig, msgHash, publicKey, {
|
|
31
|
-
format: allowMalleable ? undefined : 'compact', // prevent DER-encoded signatures
|
|
32
|
-
lowS: !allowMalleable,
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export const isCompactFormat = (sig: Uint8Array) => {
|
|
37
|
-
try {
|
|
38
|
-
const parsed = p256.Signature.fromCompact(sig)
|
|
39
|
-
return ui8equals(parsed.toCompactRawBytes(), sig)
|
|
40
|
-
} catch {
|
|
41
|
-
return false
|
|
42
|
-
}
|
|
43
|
-
}
|
package/src/p256/plugin.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { P256_DID_PREFIX, P256_JWT_ALG } from '../const.js'
|
|
2
|
-
import { DidKeyPlugin } from '../types.js'
|
|
3
|
-
import { compressPubkey, decompressPubkey } from './encoding.js'
|
|
4
|
-
import { verifyDidSig } from './operations.js'
|
|
5
|
-
|
|
6
|
-
export const p256Plugin: DidKeyPlugin = {
|
|
7
|
-
prefix: P256_DID_PREFIX,
|
|
8
|
-
jwtAlg: P256_JWT_ALG,
|
|
9
|
-
verifySignature: verifyDidSig,
|
|
10
|
-
|
|
11
|
-
compressPubkey,
|
|
12
|
-
decompressPubkey,
|
|
13
|
-
}
|
package/src/plugins.ts
DELETED
package/src/random.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import * as noble from '@noble/hashes/utils'
|
|
2
|
-
import * as uint8arrays from 'uint8arrays'
|
|
3
|
-
import { SupportedEncodings } from 'uint8arrays/to-string'
|
|
4
|
-
import { sha256 } from './sha.js'
|
|
5
|
-
|
|
6
|
-
export const randomBytes = noble.randomBytes as (
|
|
7
|
-
bytesLength?: number,
|
|
8
|
-
) => Uint8Array<ArrayBuffer>
|
|
9
|
-
|
|
10
|
-
export const randomStr = (
|
|
11
|
-
byteLength: number,
|
|
12
|
-
encoding: SupportedEncodings,
|
|
13
|
-
): string => {
|
|
14
|
-
const bytes = randomBytes(byteLength)
|
|
15
|
-
return uint8arrays.toString(bytes, encoding)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const randomIntFromSeed = async (
|
|
19
|
-
seed: string,
|
|
20
|
-
high: number,
|
|
21
|
-
low = 0,
|
|
22
|
-
): Promise<number> => {
|
|
23
|
-
const hash = await sha256(seed)
|
|
24
|
-
const number = Buffer.from(hash).readUintBE(0, 6)
|
|
25
|
-
const range = high - low
|
|
26
|
-
const normalized = number % range
|
|
27
|
-
return normalized + low
|
|
28
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { secp256k1 as k256 } from '@noble/curves/secp256k1'
|
|
2
|
-
|
|
3
|
-
export const compressPubkey = (pubkeyBytes: Uint8Array): Uint8Array => {
|
|
4
|
-
const point = k256.ProjectivePoint.fromHex(pubkeyBytes)
|
|
5
|
-
return point.toRawBytes(true)
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const decompressPubkey = (compressed: Uint8Array): Uint8Array => {
|
|
9
|
-
if (compressed.length !== 33) {
|
|
10
|
-
throw new Error('Expected 33 byte compress pubkey')
|
|
11
|
-
}
|
|
12
|
-
const point = k256.ProjectivePoint.fromHex(compressed)
|
|
13
|
-
return point.toRawBytes(false)
|
|
14
|
-
}
|
package/src/secp256k1/keypair.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { secp256k1 as k256 } from '@noble/curves/secp256k1'
|
|
2
|
-
import { sha256 } from '@noble/hashes/sha256'
|
|
3
|
-
import {
|
|
4
|
-
fromString as ui8FromString,
|
|
5
|
-
toString as ui8ToString,
|
|
6
|
-
} from 'uint8arrays'
|
|
7
|
-
import { SupportedEncodings } from 'uint8arrays/to-string'
|
|
8
|
-
import { SECP256K1_JWT_ALG } from '../const.js'
|
|
9
|
-
import * as did from '../did.js'
|
|
10
|
-
import { Keypair } from '../types.js'
|
|
11
|
-
|
|
12
|
-
export type Secp256k1KeypairOptions = {
|
|
13
|
-
exportable: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class Secp256k1Keypair implements Keypair {
|
|
17
|
-
jwtAlg = SECP256K1_JWT_ALG
|
|
18
|
-
private publicKey: Uint8Array
|
|
19
|
-
|
|
20
|
-
constructor(
|
|
21
|
-
private privateKey: Uint8Array,
|
|
22
|
-
private exportable: boolean,
|
|
23
|
-
) {
|
|
24
|
-
this.publicKey = k256.getPublicKey(privateKey)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
static async create(
|
|
28
|
-
opts?: Partial<Secp256k1KeypairOptions>,
|
|
29
|
-
): Promise<Secp256k1Keypair> {
|
|
30
|
-
const { exportable = false } = opts || {}
|
|
31
|
-
const privKey = k256.utils.randomPrivateKey()
|
|
32
|
-
return new Secp256k1Keypair(privKey, exportable)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
static async import(
|
|
36
|
-
privKey: Uint8Array | string,
|
|
37
|
-
opts?: Partial<Secp256k1KeypairOptions>,
|
|
38
|
-
): Promise<Secp256k1Keypair> {
|
|
39
|
-
const { exportable = false } = opts || {}
|
|
40
|
-
const privKeyBytes =
|
|
41
|
-
typeof privKey === 'string' ? ui8FromString(privKey, 'hex') : privKey
|
|
42
|
-
return new Secp256k1Keypair(privKeyBytes, exportable)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
publicKeyBytes(): Uint8Array {
|
|
46
|
-
return this.publicKey
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
publicKeyStr(encoding: SupportedEncodings = 'base64pad'): string {
|
|
50
|
-
return ui8ToString(this.publicKey, encoding)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
did(): string {
|
|
54
|
-
return did.formatDidKey(this.jwtAlg, this.publicKey)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async sign(msg: Uint8Array): Promise<Uint8Array> {
|
|
58
|
-
const msgHash = await sha256(msg)
|
|
59
|
-
// return raw 64 byte sig not DER-encoded
|
|
60
|
-
const sig = await k256.sign(msgHash, this.privateKey, { lowS: true })
|
|
61
|
-
return sig.toCompactRawBytes()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async export(): Promise<Uint8Array> {
|
|
65
|
-
if (!this.exportable) {
|
|
66
|
-
throw new Error('Private key is not exportable')
|
|
67
|
-
}
|
|
68
|
-
return this.privateKey
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { secp256k1 as k256 } from '@noble/curves/secp256k1'
|
|
2
|
-
import { sha256 } from '@noble/hashes/sha256'
|
|
3
|
-
import * as ui8 from 'uint8arrays'
|
|
4
|
-
import { SECP256K1_DID_PREFIX } from '../const.js'
|
|
5
|
-
import { VerifyOptions } from '../types.js'
|
|
6
|
-
import { extractMultikey, extractPrefixedBytes, hasPrefix } from '../utils.js'
|
|
7
|
-
|
|
8
|
-
export const verifyDidSig = async (
|
|
9
|
-
did: string,
|
|
10
|
-
data: Uint8Array,
|
|
11
|
-
sig: Uint8Array,
|
|
12
|
-
opts?: VerifyOptions,
|
|
13
|
-
): Promise<boolean> => {
|
|
14
|
-
const prefixedBytes = extractPrefixedBytes(extractMultikey(did))
|
|
15
|
-
if (!hasPrefix(prefixedBytes, SECP256K1_DID_PREFIX)) {
|
|
16
|
-
throw new Error(`Not a secp256k1 did:key: ${did}`)
|
|
17
|
-
}
|
|
18
|
-
const keyBytes = prefixedBytes.slice(SECP256K1_DID_PREFIX.length)
|
|
19
|
-
return verifySig(keyBytes, data, sig, opts)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const verifySig = async (
|
|
23
|
-
publicKey: Uint8Array,
|
|
24
|
-
data: Uint8Array,
|
|
25
|
-
sig: Uint8Array,
|
|
26
|
-
opts?: VerifyOptions,
|
|
27
|
-
): Promise<boolean> => {
|
|
28
|
-
const allowMalleable = opts?.allowMalleableSig ?? false
|
|
29
|
-
const msgHash = await sha256(data)
|
|
30
|
-
return k256.verify(sig, msgHash, publicKey, {
|
|
31
|
-
format: allowMalleable ? undefined : 'compact', // prevent DER-encoded signatures
|
|
32
|
-
lowS: !allowMalleable,
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export const isCompactFormat = (sig: Uint8Array) => {
|
|
37
|
-
try {
|
|
38
|
-
const parsed = k256.Signature.fromCompact(sig)
|
|
39
|
-
return ui8.equals(parsed.toCompactRawBytes(), sig)
|
|
40
|
-
} catch {
|
|
41
|
-
return false
|
|
42
|
-
}
|
|
43
|
-
}
|
package/src/secp256k1/plugin.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { SECP256K1_DID_PREFIX, SECP256K1_JWT_ALG } from '../const.js'
|
|
2
|
-
import { DidKeyPlugin } from '../types.js'
|
|
3
|
-
import { compressPubkey, decompressPubkey } from './encoding.js'
|
|
4
|
-
import { verifyDidSig } from './operations.js'
|
|
5
|
-
|
|
6
|
-
export const secp256k1Plugin: DidKeyPlugin = {
|
|
7
|
-
prefix: SECP256K1_DID_PREFIX,
|
|
8
|
-
jwtAlg: SECP256K1_JWT_ALG,
|
|
9
|
-
verifySignature: verifyDidSig,
|
|
10
|
-
|
|
11
|
-
compressPubkey,
|
|
12
|
-
decompressPubkey,
|
|
13
|
-
}
|
package/src/sha.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import * as noble from '@noble/hashes/sha256'
|
|
2
|
-
import * as uint8arrays from 'uint8arrays'
|
|
3
|
-
|
|
4
|
-
// takes either bytes of utf8 input
|
|
5
|
-
// @TODO this can be sync
|
|
6
|
-
export const sha256 = async (
|
|
7
|
-
input: Uint8Array | string,
|
|
8
|
-
): Promise<Uint8Array> => {
|
|
9
|
-
const bytes =
|
|
10
|
-
typeof input === 'string' ? uint8arrays.fromString(input, 'utf8') : input
|
|
11
|
-
return noble.sha256(bytes)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// @TODO this can be sync
|
|
15
|
-
export const sha256Hex = async (
|
|
16
|
-
input: Uint8Array | string,
|
|
17
|
-
): Promise<string> => {
|
|
18
|
-
const hash = await sha256(input)
|
|
19
|
-
return uint8arrays.toString(hash, 'hex')
|
|
20
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export interface Signer {
|
|
2
|
-
jwtAlg: string
|
|
3
|
-
sign(msg: Uint8Array): Promise<Uint8Array>
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface Didable {
|
|
7
|
-
did(): string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface Keypair extends Signer, Didable {}
|
|
11
|
-
|
|
12
|
-
export interface ExportableKeypair extends Keypair {
|
|
13
|
-
export(): Promise<Uint8Array>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type DidKeyPlugin = {
|
|
17
|
-
prefix: Uint8Array
|
|
18
|
-
jwtAlg: string
|
|
19
|
-
verifySignature: (
|
|
20
|
-
did: string,
|
|
21
|
-
msg: Uint8Array,
|
|
22
|
-
data: Uint8Array,
|
|
23
|
-
opts?: VerifyOptions,
|
|
24
|
-
) => Promise<boolean>
|
|
25
|
-
|
|
26
|
-
compressPubkey: (uncompressed: Uint8Array) => Uint8Array
|
|
27
|
-
decompressPubkey: (compressed: Uint8Array) => Uint8Array
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type VerifyOptions = {
|
|
31
|
-
allowMalleableSig?: boolean
|
|
32
|
-
}
|
package/src/utils.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import * as uint8arrays from 'uint8arrays'
|
|
2
|
-
import { BASE58_MULTIBASE_PREFIX, DID_KEY_PREFIX } from './const.js'
|
|
3
|
-
|
|
4
|
-
export const extractMultikey = (did: string): string => {
|
|
5
|
-
if (!did.startsWith(DID_KEY_PREFIX)) {
|
|
6
|
-
throw new Error(`Incorrect prefix for did:key: ${did}`)
|
|
7
|
-
}
|
|
8
|
-
return did.slice(DID_KEY_PREFIX.length)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const extractPrefixedBytes = (multikey: string): Uint8Array => {
|
|
12
|
-
if (!multikey.startsWith(BASE58_MULTIBASE_PREFIX)) {
|
|
13
|
-
throw new Error(`Incorrect prefix for multikey: ${multikey}`)
|
|
14
|
-
}
|
|
15
|
-
return uint8arrays.fromString(
|
|
16
|
-
multikey.slice(BASE58_MULTIBASE_PREFIX.length),
|
|
17
|
-
'base58btc',
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const hasPrefix = (bytes: Uint8Array, prefix: Uint8Array): boolean => {
|
|
22
|
-
return uint8arrays.equals(prefix, bytes.subarray(0, prefix.byteLength))
|
|
23
|
-
}
|
package/src/verify.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import * as uint8arrays from 'uint8arrays'
|
|
2
|
-
import { parseDidKey } from './did.js'
|
|
3
|
-
import { plugins } from './plugins.js'
|
|
4
|
-
import { VerifyOptions } from './types.js'
|
|
5
|
-
|
|
6
|
-
export const verifySignature = (
|
|
7
|
-
didKey: string,
|
|
8
|
-
data: Uint8Array,
|
|
9
|
-
sig: Uint8Array,
|
|
10
|
-
opts?: VerifyOptions & {
|
|
11
|
-
jwtAlg?: string
|
|
12
|
-
},
|
|
13
|
-
): Promise<boolean> => {
|
|
14
|
-
const parsed = parseDidKey(didKey)
|
|
15
|
-
if (opts?.jwtAlg && opts.jwtAlg !== parsed.jwtAlg) {
|
|
16
|
-
throw new Error(`Expected key alg ${opts.jwtAlg}, got ${parsed.jwtAlg}`)
|
|
17
|
-
}
|
|
18
|
-
const plugin = plugins.find((p) => p.jwtAlg === parsed.jwtAlg)
|
|
19
|
-
if (!plugin) {
|
|
20
|
-
throw new Error(`Unsupported signature alg: ${parsed.jwtAlg}`)
|
|
21
|
-
}
|
|
22
|
-
return plugin.verifySignature(didKey, data, sig, opts)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const verifySignatureUtf8 = async (
|
|
26
|
-
didKey: string,
|
|
27
|
-
data: string,
|
|
28
|
-
sig: string,
|
|
29
|
-
opts?: VerifyOptions,
|
|
30
|
-
): Promise<boolean> => {
|
|
31
|
-
const dataBytes = uint8arrays.fromString(data, 'utf8')
|
|
32
|
-
const sigBytes = uint8arrays.fromString(sig, 'base64url')
|
|
33
|
-
return verifySignature(didKey, dataBytes, sigBytes, opts)
|
|
34
|
-
}
|
package/tests/did.test.ts
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import * as uint8arrays from 'uint8arrays'
|
|
2
|
-
import * as did from '../src/did.js'
|
|
3
|
-
import { P256Keypair, Secp256k1Keypair } from '../src/index.js'
|
|
4
|
-
import { decompressPubkey as p256Decompress } from '../src/p256/encoding.js'
|
|
5
|
-
import { decompressPubkey as secp256k1Decompress } from '../src/secp256k1/encoding.js'
|
|
6
|
-
|
|
7
|
-
describe('secp256k1 did:key', () => {
|
|
8
|
-
it('derives the correct DID from the privatekey', async () => {
|
|
9
|
-
for (const vector of secpTestVectors) {
|
|
10
|
-
const keypair = await Secp256k1Keypair.import(vector.seed)
|
|
11
|
-
const did = keypair.did()
|
|
12
|
-
expect(did).toEqual(vector.id)
|
|
13
|
-
}
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('converts between bytes and did', async () => {
|
|
17
|
-
for (const vector of secpTestVectors) {
|
|
18
|
-
const keypair = await Secp256k1Keypair.import(vector.seed)
|
|
19
|
-
const didKey = did.formatDidKey('ES256K', keypair.publicKeyBytes())
|
|
20
|
-
expect(didKey).toEqual(vector.id)
|
|
21
|
-
const { jwtAlg, keyBytes } = did.parseDidKey(didKey)
|
|
22
|
-
expect(jwtAlg).toBe('ES256K')
|
|
23
|
-
expect(
|
|
24
|
-
uint8arrays.equals(
|
|
25
|
-
keyBytes,
|
|
26
|
-
secp256k1Decompress(keypair.publicKeyBytes()),
|
|
27
|
-
),
|
|
28
|
-
).toBeTruthy()
|
|
29
|
-
}
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
describe('P-256 did:key', () => {
|
|
34
|
-
it('derives the correct DID from the JWK', async () => {
|
|
35
|
-
for (const vector of p256TestVectors) {
|
|
36
|
-
const bytes = uint8arrays.fromString(vector.privateKeyBase58, 'base58btc')
|
|
37
|
-
const keypair = await P256Keypair.import(bytes)
|
|
38
|
-
const did = keypair.did()
|
|
39
|
-
expect(did).toEqual(vector.id)
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('converts between bytes and did', async () => {
|
|
44
|
-
for (const vector of p256TestVectors) {
|
|
45
|
-
const bytes = uint8arrays.fromString(vector.privateKeyBase58, 'base58btc')
|
|
46
|
-
const keypair = await P256Keypair.import(bytes)
|
|
47
|
-
const didKey = did.formatDidKey('ES256', keypair.publicKeyBytes())
|
|
48
|
-
expect(didKey).toEqual(vector.id)
|
|
49
|
-
const { jwtAlg, keyBytes } = did.parseDidKey(didKey)
|
|
50
|
-
expect(jwtAlg).toBe('ES256')
|
|
51
|
-
expect(
|
|
52
|
-
uint8arrays.equals(keyBytes, p256Decompress(keypair.publicKeyBytes())),
|
|
53
|
-
).toBeTruthy()
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
// did:key secp256k1 test vectors from W3C
|
|
59
|
-
// https://github.com/w3c-ccg/did-method-key/blob/main/test-vectors/secp256k1.json
|
|
60
|
-
const secpTestVectors = [
|
|
61
|
-
{
|
|
62
|
-
seed: '9085d2bef69286a6cbb51623c8fa258629945cd55ca705cc4e66700396894e0c',
|
|
63
|
-
id: 'did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme',
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
seed: 'f0f4df55a2b3ff13051ea814a8f24ad00f2e469af73c363ac7e9fb999a9072ed',
|
|
67
|
-
id: 'did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2',
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
seed: '6b0b91287ae3348f8c2f2552d766f30e3604867e34adc37ccbb74a8e6b893e02',
|
|
71
|
-
id: 'did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N',
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
seed: 'c0a6a7c560d37d7ba81ecee9543721ff48fea3e0fb827d42c1868226540fac15',
|
|
75
|
-
id: 'did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy',
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
seed: '175a232d440be1e0788f25488a73d9416c04b6f924bea6354bf05dd2f1a75133',
|
|
79
|
-
id: 'did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj',
|
|
80
|
-
},
|
|
81
|
-
]
|
|
82
|
-
|
|
83
|
-
// did:key p-256 test vectors from W3C
|
|
84
|
-
// https://github.com/w3c-ccg/did-method-key/blob/main/test-vectors/nist-curves.json
|
|
85
|
-
const p256TestVectors = [
|
|
86
|
-
{
|
|
87
|
-
privateKeyBase58: '9p4VRzdmhsnq869vQjVCTrRry7u4TtfRxhvBFJTGU2Cp',
|
|
88
|
-
id: 'did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb',
|
|
89
|
-
},
|
|
90
|
-
]
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import * as did from '../src/did.js'
|
|
2
|
-
import * as p256Encoding from '../src/p256/encoding.js'
|
|
3
|
-
import { P256Keypair } from '../src/p256/keypair.js'
|
|
4
|
-
import * as secpEncoding from '../src/secp256k1/encoding.js'
|
|
5
|
-
import { Secp256k1Keypair } from '../src/secp256k1/keypair.js'
|
|
6
|
-
|
|
7
|
-
describe('public key compression', () => {
|
|
8
|
-
describe('secp256k1', () => {
|
|
9
|
-
let keyBytes: Uint8Array
|
|
10
|
-
let compressed: Uint8Array
|
|
11
|
-
|
|
12
|
-
it('compresses a key to the correct length', async () => {
|
|
13
|
-
const keypair = await Secp256k1Keypair.create()
|
|
14
|
-
const parsed = did.parseDidKey(keypair.did())
|
|
15
|
-
keyBytes = parsed.keyBytes
|
|
16
|
-
compressed = secpEncoding.compressPubkey(keyBytes)
|
|
17
|
-
expect(compressed.length).toBe(33)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('decompresses a key to the original', async () => {
|
|
21
|
-
const decompressed = secpEncoding.decompressPubkey(compressed)
|
|
22
|
-
expect(decompressed.length).toBe(65)
|
|
23
|
-
expect(decompressed).toEqual(keyBytes)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('works consistently', async () => {
|
|
27
|
-
const pubkeys: Uint8Array[] = []
|
|
28
|
-
for (let i = 0; i < 100; i++) {
|
|
29
|
-
const key = await Secp256k1Keypair.create()
|
|
30
|
-
const parsed = did.parseDidKey(key.did())
|
|
31
|
-
pubkeys.push(parsed.keyBytes)
|
|
32
|
-
}
|
|
33
|
-
const compressed = pubkeys.map(secpEncoding.compressPubkey)
|
|
34
|
-
const decompressed = compressed.map(secpEncoding.decompressPubkey)
|
|
35
|
-
expect(pubkeys).toEqual(decompressed)
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
describe('P-256', () => {
|
|
40
|
-
let keyBytes: Uint8Array
|
|
41
|
-
let compressed: Uint8Array
|
|
42
|
-
|
|
43
|
-
it('compresses a key to the correct length', async () => {
|
|
44
|
-
const keypair = await P256Keypair.create()
|
|
45
|
-
const parsed = did.parseDidKey(keypair.did())
|
|
46
|
-
keyBytes = parsed.keyBytes
|
|
47
|
-
compressed = p256Encoding.compressPubkey(keyBytes)
|
|
48
|
-
expect(compressed.length).toBe(33)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('decompresses a key to the original', async () => {
|
|
52
|
-
const decompressed = p256Encoding.decompressPubkey(compressed)
|
|
53
|
-
expect(decompressed.length).toBe(65)
|
|
54
|
-
expect(decompressed).toEqual(keyBytes)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('works consistently', async () => {
|
|
58
|
-
const pubkeys: Uint8Array[] = []
|
|
59
|
-
for (let i = 0; i < 100; i++) {
|
|
60
|
-
const key = await P256Keypair.create()
|
|
61
|
-
const parsed = did.parseDidKey(key.did())
|
|
62
|
-
pubkeys.push(parsed.keyBytes)
|
|
63
|
-
}
|
|
64
|
-
const compressed = pubkeys.map(p256Encoding.compressPubkey)
|
|
65
|
-
const decompressed = compressed.map(p256Encoding.decompressPubkey)
|
|
66
|
-
expect(pubkeys).toEqual(decompressed)
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
})
|
package/tests/keypairs.test.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from '../src/index.js'
|
|
2
|
-
import { P256Keypair } from '../src/p256/keypair.js'
|
|
3
|
-
import * as p256 from '../src/p256/operations.js'
|
|
4
|
-
import { Secp256k1Keypair } from '../src/secp256k1/keypair.js'
|
|
5
|
-
import * as secp from '../src/secp256k1/operations.js'
|
|
6
|
-
|
|
7
|
-
describe('keypairs', () => {
|
|
8
|
-
describe('secp256k1', () => {
|
|
9
|
-
let keypair: Secp256k1Keypair
|
|
10
|
-
let imported: Secp256k1Keypair
|
|
11
|
-
|
|
12
|
-
it('has the same DID on import', async () => {
|
|
13
|
-
keypair = await Secp256k1Keypair.create({ exportable: true })
|
|
14
|
-
const exported = await keypair.export()
|
|
15
|
-
imported = await Secp256k1Keypair.import(exported, { exportable: true })
|
|
16
|
-
|
|
17
|
-
expect(keypair.did()).toBe(imported.did())
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('produces a valid signature', async () => {
|
|
21
|
-
const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
|
|
22
|
-
const sig = await imported.sign(data)
|
|
23
|
-
|
|
24
|
-
const validSig = await secp.verifyDidSig(keypair.did(), data, sig)
|
|
25
|
-
|
|
26
|
-
expect(validSig).toBeTruthy()
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('produces a valid signature on a typed array of a large arraybuffer', async () => {
|
|
30
|
-
const bytes = await randomBytes(8192)
|
|
31
|
-
const arrBuf = bytes.buffer
|
|
32
|
-
const sliceView = new Uint8Array(arrBuf, 1024, 1024)
|
|
33
|
-
expect(sliceView.buffer.byteLength).toBe(8192)
|
|
34
|
-
const sig = await imported.sign(sliceView)
|
|
35
|
-
const validSig = await secp.verifyDidSig(keypair.did(), sliceView, sig)
|
|
36
|
-
expect(validSig).toBeTruthy()
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
describe('P-256', () => {
|
|
41
|
-
let keypair: P256Keypair
|
|
42
|
-
let imported: P256Keypair
|
|
43
|
-
|
|
44
|
-
it('has the same DID on import', async () => {
|
|
45
|
-
keypair = await P256Keypair.create({ exportable: true })
|
|
46
|
-
const exported = await keypair.export()
|
|
47
|
-
imported = await P256Keypair.import(exported, { exportable: true })
|
|
48
|
-
|
|
49
|
-
expect(keypair.did()).toBe(imported.did())
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('produces a valid signature', async () => {
|
|
53
|
-
const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
|
|
54
|
-
const sig = await imported.sign(data)
|
|
55
|
-
|
|
56
|
-
const validSig = await p256.verifyDidSig(keypair.did(), data, sig)
|
|
57
|
-
|
|
58
|
-
expect(validSig).toBeTruthy()
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('produces a valid signature on a typed array of a large arraybuffer', async () => {
|
|
62
|
-
const bytes = await randomBytes(8192)
|
|
63
|
-
const arrBuf = bytes.buffer
|
|
64
|
-
const sliceView = new Uint8Array(arrBuf, 1024, 1024)
|
|
65
|
-
expect(sliceView.buffer.byteLength).toBe(8192)
|
|
66
|
-
const sig = await imported.sign(sliceView)
|
|
67
|
-
const validSig = await p256.verifyDidSig(keypair.did(), sliceView, sig)
|
|
68
|
-
expect(validSig).toBeTruthy()
|
|
69
|
-
})
|
|
70
|
-
})
|
|
71
|
-
})
|
package/tests/random.test.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { randomIntFromSeed } from '../src/index.js'
|
|
2
|
-
|
|
3
|
-
describe('randomIntFromSeed()', () => {
|
|
4
|
-
it('has good distribution for low bucket count.', async () => {
|
|
5
|
-
const counts: [zero: number, one: number] = [0, 0]
|
|
6
|
-
const salt = Math.random()
|
|
7
|
-
for (let i = 0; i < 10000; ++i) {
|
|
8
|
-
const int = await randomIntFromSeed(`${i}${salt}`, 2)
|
|
9
|
-
counts[int]++
|
|
10
|
-
}
|
|
11
|
-
const [zero, one] = counts
|
|
12
|
-
expect(zero + one).toEqual(10000)
|
|
13
|
-
expect(Math.max(zero, one) / Math.min(zero, one)).toBeLessThan(1.1)
|
|
14
|
-
})
|
|
15
|
-
})
|
package/tests/signatures.test.ts
DELETED
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
4
|
-
import { p256 as nobleP256 } from '@noble/curves/p256'
|
|
5
|
-
import { secp256k1 as nobleK256 } from '@noble/curves/secp256k1'
|
|
6
|
-
import * as uint8arrays from 'uint8arrays'
|
|
7
|
-
import { cborEncode } from '@atproto/common'
|
|
8
|
-
import {
|
|
9
|
-
P256_JWT_ALG,
|
|
10
|
-
SECP256K1_JWT_ALG,
|
|
11
|
-
bytesToMultibase,
|
|
12
|
-
multibaseToBytes,
|
|
13
|
-
parseDidKey,
|
|
14
|
-
sha256,
|
|
15
|
-
} from '../src/index.js'
|
|
16
|
-
import { P256Keypair } from '../src/p256/keypair.js'
|
|
17
|
-
import * as p256 from '../src/p256/operations.js'
|
|
18
|
-
import { Secp256k1Keypair } from '../src/secp256k1/keypair.js'
|
|
19
|
-
import * as secp from '../src/secp256k1/operations.js'
|
|
20
|
-
|
|
21
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
22
|
-
|
|
23
|
-
describe('signatures', () => {
|
|
24
|
-
let vectors: TestVector[]
|
|
25
|
-
|
|
26
|
-
beforeAll(() => {
|
|
27
|
-
vectors = JSON.parse(
|
|
28
|
-
fs.readFileSync(`${__dirname}/signature-fixtures.json`).toString(),
|
|
29
|
-
)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('verifies secp256k1 and P-256 test vectors', async () => {
|
|
33
|
-
for (const vector of vectors) {
|
|
34
|
-
const messageBytes = uint8arrays.fromString(
|
|
35
|
-
vector.messageBase64,
|
|
36
|
-
'base64',
|
|
37
|
-
)
|
|
38
|
-
const signatureBytes = uint8arrays.fromString(
|
|
39
|
-
vector.signatureBase64,
|
|
40
|
-
'base64',
|
|
41
|
-
)
|
|
42
|
-
const keyBytes = multibaseToBytes(vector.publicKeyMultibase)
|
|
43
|
-
const didKey = parseDidKey(vector.publicKeyDid)
|
|
44
|
-
expect(uint8arrays.equals(keyBytes, didKey.keyBytes))
|
|
45
|
-
if (vector.algorithm === P256_JWT_ALG) {
|
|
46
|
-
const verified = await p256.verifySig(
|
|
47
|
-
keyBytes,
|
|
48
|
-
messageBytes,
|
|
49
|
-
signatureBytes,
|
|
50
|
-
)
|
|
51
|
-
expect(verified).toEqual(vector.validSignature)
|
|
52
|
-
} else if (vector.algorithm === SECP256K1_JWT_ALG) {
|
|
53
|
-
const verified = await secp.verifySig(
|
|
54
|
-
keyBytes,
|
|
55
|
-
messageBytes,
|
|
56
|
-
signatureBytes,
|
|
57
|
-
)
|
|
58
|
-
expect(verified).toEqual(vector.validSignature)
|
|
59
|
-
} else {
|
|
60
|
-
throw new Error('Unsupported test vector')
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('verifies high-s signatures with explicit option', async () => {
|
|
66
|
-
const highSVectors = vectors.filter((vec) => vec.tags.includes('high-s'))
|
|
67
|
-
expect(highSVectors.length).toBeGreaterThanOrEqual(2)
|
|
68
|
-
for (const vector of highSVectors) {
|
|
69
|
-
const messageBytes = uint8arrays.fromString(
|
|
70
|
-
vector.messageBase64,
|
|
71
|
-
'base64',
|
|
72
|
-
)
|
|
73
|
-
const signatureBytes = uint8arrays.fromString(
|
|
74
|
-
vector.signatureBase64,
|
|
75
|
-
'base64',
|
|
76
|
-
)
|
|
77
|
-
const keyBytes = multibaseToBytes(vector.publicKeyMultibase)
|
|
78
|
-
const didKey = parseDidKey(vector.publicKeyDid)
|
|
79
|
-
expect(uint8arrays.equals(keyBytes, didKey.keyBytes))
|
|
80
|
-
if (vector.algorithm === P256_JWT_ALG) {
|
|
81
|
-
const verified = await p256.verifySig(
|
|
82
|
-
keyBytes,
|
|
83
|
-
messageBytes,
|
|
84
|
-
signatureBytes,
|
|
85
|
-
{ allowMalleableSig: true },
|
|
86
|
-
)
|
|
87
|
-
expect(verified).toEqual(true)
|
|
88
|
-
expect(vector.validSignature).toEqual(false) // otherwise would fail per low-s requirement
|
|
89
|
-
} else if (vector.algorithm === SECP256K1_JWT_ALG) {
|
|
90
|
-
const verified = await secp.verifySig(
|
|
91
|
-
keyBytes,
|
|
92
|
-
messageBytes,
|
|
93
|
-
signatureBytes,
|
|
94
|
-
{ allowMalleableSig: true },
|
|
95
|
-
)
|
|
96
|
-
expect(verified).toEqual(true)
|
|
97
|
-
expect(vector.validSignature).toEqual(false) // otherwise would fail per low-s requirement
|
|
98
|
-
} else {
|
|
99
|
-
throw new Error('Unsupported test vector')
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('verifies der-encoded signatures with explicit option', async () => {
|
|
105
|
-
const DERVectors = vectors.filter((vec) => vec.tags.includes('der-encoded'))
|
|
106
|
-
expect(DERVectors.length).toBeGreaterThanOrEqual(2)
|
|
107
|
-
for (const vector of DERVectors) {
|
|
108
|
-
const messageBytes = uint8arrays.fromString(
|
|
109
|
-
vector.messageBase64,
|
|
110
|
-
'base64',
|
|
111
|
-
)
|
|
112
|
-
const signatureBytes = uint8arrays.fromString(
|
|
113
|
-
vector.signatureBase64,
|
|
114
|
-
'base64',
|
|
115
|
-
)
|
|
116
|
-
const keyBytes = multibaseToBytes(vector.publicKeyMultibase)
|
|
117
|
-
const didKey = parseDidKey(vector.publicKeyDid)
|
|
118
|
-
expect(uint8arrays.equals(keyBytes, didKey.keyBytes))
|
|
119
|
-
if (vector.algorithm === P256_JWT_ALG) {
|
|
120
|
-
const verified = await p256.verifySig(
|
|
121
|
-
keyBytes,
|
|
122
|
-
messageBytes,
|
|
123
|
-
signatureBytes,
|
|
124
|
-
{ allowMalleableSig: true },
|
|
125
|
-
)
|
|
126
|
-
expect(verified).toEqual(true)
|
|
127
|
-
expect(vector.validSignature).toEqual(false) // otherwise would fail per low-s requirement
|
|
128
|
-
} else if (vector.algorithm === SECP256K1_JWT_ALG) {
|
|
129
|
-
const verified = await secp.verifySig(
|
|
130
|
-
keyBytes,
|
|
131
|
-
messageBytes,
|
|
132
|
-
signatureBytes,
|
|
133
|
-
{ allowMalleableSig: true },
|
|
134
|
-
)
|
|
135
|
-
expect(verified).toEqual(true)
|
|
136
|
-
expect(vector.validSignature).toEqual(false) // otherwise would fail per low-s requirement
|
|
137
|
-
} else {
|
|
138
|
-
throw new Error('Unsupported test vector')
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
// @ts-expect-error
|
|
145
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
146
|
-
async function generateTestVectors(): Promise<TestVector[]> {
|
|
147
|
-
const p256Key = await P256Keypair.create({ exportable: true })
|
|
148
|
-
const secpKey = await Secp256k1Keypair.create({ exportable: true })
|
|
149
|
-
const messageBytes = cborEncode({ hello: 'world' })
|
|
150
|
-
const messageBase64 = uint8arrays.toString(messageBytes, 'base64')
|
|
151
|
-
return [
|
|
152
|
-
{
|
|
153
|
-
messageBase64,
|
|
154
|
-
algorithm: P256_JWT_ALG, // "ES256" / ecdsa p-256
|
|
155
|
-
publicKeyDid: p256Key.did(),
|
|
156
|
-
publicKeyMultibase: bytesToMultibase(
|
|
157
|
-
p256Key.publicKeyBytes(),
|
|
158
|
-
'base58btc',
|
|
159
|
-
),
|
|
160
|
-
signatureBase64: uint8arrays.toString(
|
|
161
|
-
await p256Key.sign(messageBytes),
|
|
162
|
-
'base64',
|
|
163
|
-
),
|
|
164
|
-
validSignature: true,
|
|
165
|
-
tags: [],
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
messageBase64,
|
|
169
|
-
algorithm: SECP256K1_JWT_ALG, // "ES256K" / secp256k
|
|
170
|
-
publicKeyDid: secpKey.did(),
|
|
171
|
-
publicKeyMultibase: bytesToMultibase(
|
|
172
|
-
secpKey.publicKeyBytes(),
|
|
173
|
-
'base58btc',
|
|
174
|
-
),
|
|
175
|
-
signatureBase64: uint8arrays.toString(
|
|
176
|
-
await secpKey.sign(messageBytes),
|
|
177
|
-
'base64',
|
|
178
|
-
),
|
|
179
|
-
validSignature: true,
|
|
180
|
-
tags: [],
|
|
181
|
-
},
|
|
182
|
-
// these vectors test to ensure we don't allow high-s signatures
|
|
183
|
-
{
|
|
184
|
-
messageBase64,
|
|
185
|
-
algorithm: P256_JWT_ALG, // "ES256" / ecdsa p-256
|
|
186
|
-
publicKeyDid: p256Key.did(),
|
|
187
|
-
publicKeyMultibase: bytesToMultibase(
|
|
188
|
-
p256Key.publicKeyBytes(),
|
|
189
|
-
'base58btc',
|
|
190
|
-
),
|
|
191
|
-
signatureBase64: await makeHighSSig(
|
|
192
|
-
messageBytes,
|
|
193
|
-
await p256Key.export(),
|
|
194
|
-
P256_JWT_ALG,
|
|
195
|
-
),
|
|
196
|
-
validSignature: false,
|
|
197
|
-
tags: ['high-s'],
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
messageBase64,
|
|
201
|
-
algorithm: SECP256K1_JWT_ALG, // "ES256K" / secp256k
|
|
202
|
-
publicKeyDid: secpKey.did(),
|
|
203
|
-
publicKeyMultibase: bytesToMultibase(
|
|
204
|
-
secpKey.publicKeyBytes(),
|
|
205
|
-
'base58btc',
|
|
206
|
-
),
|
|
207
|
-
signatureBase64: await makeHighSSig(
|
|
208
|
-
messageBytes,
|
|
209
|
-
await secpKey.export(),
|
|
210
|
-
SECP256K1_JWT_ALG,
|
|
211
|
-
),
|
|
212
|
-
validSignature: false,
|
|
213
|
-
tags: ['high-s'],
|
|
214
|
-
},
|
|
215
|
-
// these vectors test to ensure we don't allow der-encoded signatures
|
|
216
|
-
{
|
|
217
|
-
messageBase64,
|
|
218
|
-
algorithm: P256_JWT_ALG, // "ES256" / ecdsa p-256
|
|
219
|
-
publicKeyDid: p256Key.did(),
|
|
220
|
-
publicKeyMultibase: bytesToMultibase(
|
|
221
|
-
p256Key.publicKeyBytes(),
|
|
222
|
-
'base58btc',
|
|
223
|
-
),
|
|
224
|
-
signatureBase64: await makeDerEncodedSig(
|
|
225
|
-
messageBytes,
|
|
226
|
-
await p256Key.export(),
|
|
227
|
-
P256_JWT_ALG,
|
|
228
|
-
),
|
|
229
|
-
validSignature: false,
|
|
230
|
-
tags: ['der-encoded'],
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
messageBase64,
|
|
234
|
-
algorithm: SECP256K1_JWT_ALG, // "ES256K" / secp256k
|
|
235
|
-
publicKeyDid: secpKey.did(),
|
|
236
|
-
publicKeyMultibase: bytesToMultibase(
|
|
237
|
-
secpKey.publicKeyBytes(),
|
|
238
|
-
'base58btc',
|
|
239
|
-
),
|
|
240
|
-
signatureBase64: await makeDerEncodedSig(
|
|
241
|
-
messageBytes,
|
|
242
|
-
await secpKey.export(),
|
|
243
|
-
SECP256K1_JWT_ALG,
|
|
244
|
-
),
|
|
245
|
-
validSignature: false,
|
|
246
|
-
tags: ['der-encoded'],
|
|
247
|
-
},
|
|
248
|
-
]
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async function makeHighSSig(
|
|
252
|
-
msgBytes: Uint8Array,
|
|
253
|
-
keyBytes: Uint8Array,
|
|
254
|
-
alg: string,
|
|
255
|
-
): Promise<string> {
|
|
256
|
-
const hash = await sha256(msgBytes)
|
|
257
|
-
|
|
258
|
-
let sig: string | undefined
|
|
259
|
-
do {
|
|
260
|
-
if (alg === SECP256K1_JWT_ALG) {
|
|
261
|
-
const attempt = await nobleK256.sign(hash, keyBytes, { lowS: false })
|
|
262
|
-
if (attempt.hasHighS()) {
|
|
263
|
-
sig = uint8arrays.toString(attempt.toCompactRawBytes(), 'base64')
|
|
264
|
-
}
|
|
265
|
-
} else {
|
|
266
|
-
const attempt = await nobleP256.sign(hash, keyBytes, { lowS: false })
|
|
267
|
-
if (attempt.hasHighS()) {
|
|
268
|
-
sig = uint8arrays.toString(attempt.toCompactRawBytes(), 'base64')
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
} while (sig === undefined)
|
|
272
|
-
return sig
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
async function makeDerEncodedSig(
|
|
276
|
-
msgBytes: Uint8Array,
|
|
277
|
-
keyBytes: Uint8Array,
|
|
278
|
-
alg: string,
|
|
279
|
-
): Promise<string> {
|
|
280
|
-
const hash = await sha256(msgBytes)
|
|
281
|
-
|
|
282
|
-
let sig: string
|
|
283
|
-
if (alg === SECP256K1_JWT_ALG) {
|
|
284
|
-
const attempt = await nobleK256.sign(hash, keyBytes, { lowS: true })
|
|
285
|
-
sig = uint8arrays.toString(attempt.toDERRawBytes(), 'base64')
|
|
286
|
-
} else {
|
|
287
|
-
const attempt = await nobleP256.sign(hash, keyBytes, { lowS: true })
|
|
288
|
-
sig = uint8arrays.toString(attempt.toDERRawBytes(), 'base64')
|
|
289
|
-
}
|
|
290
|
-
return sig
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
type TestVector = {
|
|
294
|
-
algorithm: string
|
|
295
|
-
publicKeyDid: string
|
|
296
|
-
publicKeyMultibase: string
|
|
297
|
-
messageBase64: string
|
|
298
|
-
signatureBase64: string
|
|
299
|
-
validSignature: boolean
|
|
300
|
-
tags: string[]
|
|
301
|
-
}
|
package/tsconfig.build.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":"7.0.0-dev.20260614.1","root":["./src/const.ts","./src/did.ts","./src/index.ts","./src/multibase.ts","./src/plugins.ts","./src/random.ts","./src/sha.ts","./src/types.ts","./src/utils.ts","./src/verify.ts","./src/p256/encoding.ts","./src/p256/keypair.ts","./src/p256/operations.ts","./src/p256/plugin.ts","./src/secp256k1/encoding.ts","./src/secp256k1/keypair.ts","./src/secp256k1/operations.ts","./src/secp256k1/plugin.ts"]}
|
package/tsconfig.json
DELETED