Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement rsa signer / verifier #102

Merged
merged 27 commits into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4a7bbe9
feat: unifiy principals & signers
Gozala Sep 22, 2022
8639961
fix: tests on core
Gozala Sep 22, 2022
5a0b238
fix: transport package
Gozala Sep 22, 2022
a5b30e6
chore: update client to 0.9
Gozala Sep 22, 2022
ff9c434
feat(validator): update to ucan 0.9
Gozala Sep 22, 2022
15abcb5
feat(server): upgrade to 0.9
Gozala Sep 22, 2022
28f67dd
fix(principal): invalid test
Gozala Sep 22, 2022
2edba5e
chore: update dag-ucan dep
Gozala Sep 26, 2022
b7a42d8
feat: rename caveats to nb
Gozala Sep 27, 2022
55c5427
fix(server): type checks
Gozala Sep 27, 2022
fc1d659
fix(validator): increase coverage
Gozala Sep 27, 2022
3f58a66
fix(docs): rename caveats to nb
Gozala Sep 27, 2022
2f3a606
chore: merge 'origin/main' into feat/v0.9
Gozala Sep 27, 2022
714cc80
feat: improve decoders
Gozala Sep 29, 2022
a878e04
stash: need to work on diff branch
Gozala Sep 29, 2022
0ffc431
stash: need to work on ucant 0.9
Gozala Sep 29, 2022
b30eb4a
feat: implement rsa keys
Gozala Sep 30, 2022
a1f8971
Merge remote-tracking branch 'origin/main' into feat/rsa
Gozala Sep 30, 2022
c933756
chore: remove obsolete code
Gozala Sep 30, 2022
e08ea47
fix: messy merge artifact
Gozala Sep 30, 2022
5aca463
feat: add composition operators
Gozala Sep 30, 2022
1cdec2e
chore: disable node14
Gozala Sep 30, 2022
7717f91
fix: address review comments
Gozala Oct 13, 2022
13ed09a
chore: remove format & encode functions
Gozala Oct 13, 2022
695a671
chore: add tests to ensure keys with / work
Gozala Oct 13, 2022
8b54bb4
fix: typo
Gozala Oct 13, 2022
265cb33
Apply suggestions from code review
Gozala Oct 13, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/principal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ jobs:
strategy:
matrix:
node-version:
- 14
- 16
os:
- ubuntu-latest
Expand Down
24 changes: 19 additions & 5 deletions packages/interface/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
Resource,
Signature,
Principal,
Verifier,
Signer,
Verifier as UCANVerifier,
Signer as UCANSigner,
} from '@ipld/dag-ucan'
import * as UCAN from '@ipld/dag-ucan'
import {
Expand All @@ -39,8 +39,6 @@ export * from './transport.js'
export type {
Transport,
Principal,
Verifier,
Signer,
Phantom,
Tuple,
DID,
Expand Down Expand Up @@ -388,7 +386,23 @@ export type URI<P extends Protocol = Protocol> = `${P}${string}` &
}>

export interface PrincipalParser {
parse(did: UCAN.DID): UCAN.Verifier
parse(did: UCAN.DID): Verifier
}

export interface IntoSigner {
decode: (bytes: Uint8Array) => Signer
}

export interface Signer<M extends string = string, A extends number = number>
extends UCANSigner<M, A> {
export?: () => Await<ByteView<Signer<M, A>>>
toCryptoKey?: () => Await<CryptoKey>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export?: () => Await<ByteView<Signer<M, A>>>
toCryptoKey?: () => Await<CryptoKey>
exportBytes?: () => Await<ByteView<Signer<M, A>>>
exportKey?: () => Await<CryptoKey>

}

export interface Verifier<M extends string = string, A extends number = number>
extends UCANVerifier<M, A> {
export?: () => Await<ByteView<Verifier<M, A>>>
toCryptoKey?: () => Await<CryptoKey>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export?: () => Await<ByteView<Verifier<M, A>>>
toCryptoKey?: () => Await<CryptoKey>
exportBytes?: () => Await<ByteView<Verifier<M, A>>>
exportKey?: () => Await<CryptoKey>

}

export type InferInvokedCapability<
Expand Down
10 changes: 9 additions & 1 deletion packages/principal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"@ipld/dag-ucan": "^4.0.0-beta",
"@noble/ed25519": "^1.7.0",
"@ucanto/interface": "^1.0.0",
"multiformats": "^9.8.1"
"multiformats": "^9.8.1",
"one-webcrypto": "^1.0.3"
},
"devDependencies": {
"@types/chai": "^4.3.3",
Expand All @@ -52,13 +53,20 @@
],
"ed25519": [
"dist/src/ed25519.d.ts"
],
"rsa": [
"dist/src/rsa.d.ts"
]
}
},
"exports": {
"./ed25519": {
"types": "./dist/src/ed25519.d.ts",
"import": "./src/ed25519.js"
},
"./rsa": {
"types": "./dist/src/rsa.d.ts",
"import": "./src/rsa.js"
}
},
"c8": {
Expand Down
53 changes: 38 additions & 15 deletions packages/principal/src/ed25519/signer.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import * as ED25519 from '@noble/ed25519'
import { varint } from 'multiformats'
import * as API from '@ucanto/interface'
import * as API from './type.js'
import * as Verifier from './verifier.js'
import { base64pad, base64url } from 'multiformats/bases/base64'
import * as Signature from '@ipld/dag-ucan/signature'

export const code = 0x1300
export const name = Verifier.name
export const signatureAlgorithm = Verifier.signatureAlgorithm
export const signatureCode = Verifier.signatureCode

const PRIVATE_TAG_SIZE = varint.encodingLength(code)
const PUBLIC_TAG_SIZE = varint.encodingLength(Verifier.code)
Expand All @@ -15,20 +17,16 @@ const SIZE = PRIVATE_TAG_SIZE + KEY_SIZE + PUBLIC_TAG_SIZE + KEY_SIZE

export const PUB_KEY_OFFSET = PRIVATE_TAG_SIZE + KEY_SIZE

/**
* @typedef {API.Signer<"key", typeof Signature.EdDSA> & Uint8Array & { verifier: API.Verifier<"key", typeof Signature.EdDSA> }} Signer
*/

/**
* Generates new issuer by generating underlying ED25519 keypair.
* @returns {Promise<Signer>}
* @returns {Promise<API.EdSigner>}
*/
export const generate = () => derive(ED25519.utils.randomPrivateKey())

/**
* Derives issuer from 32 byte long secret key.
* @param {Uint8Array} secret
* @returns {Promise<Signer>}
* @returns {Promise<API.EdSigner>}
*/
export const derive = async secret => {
if (secret.byteLength !== KEY_SIZE) {
Expand All @@ -51,7 +49,7 @@ export const derive = async secret => {

/**
* @param {Uint8Array} bytes
* @returns {Signer}
* @returns {API.EdSigner}
*/
export const decode = bytes => {
if (bytes.byteLength !== SIZE) {
Expand Down Expand Up @@ -80,31 +78,40 @@ export const decode = bytes => {
}

/**
* @param {Signer} signer
* @return {API.ByteView<Signer>}
* @param {API.EdSigner} signer
* @return {API.ByteView<API.EdSigner>}
*/
export const encode = signer => signer
export const encode = signer => signer.encode()

/**
* @template {string} Prefix
* @param {Signer} signer
* @param {API.EdSigner} signer
* @param {API.MultibaseEncoder<Prefix>} [encoder]
*/
export const format = (signer, encoder) => (encoder || base64pad).encode(signer)
export const format = (signer, encoder) =>
(encoder || base64pad).encode(signer.encode())

/**
* @template {string} Prefix
* @param {string} principal
* @param {API.MultibaseDecoder<Prefix>} [decoder]
* @returns {Signer}
* @returns {API.EdSigner}
*/
export const parse = (principal, decoder) =>
decode((decoder || base64pad).decode(principal))

/**
* @implements {API.Signer<'key', typeof Signature.EdDSA>}
* @implements {API.EdSigner}
*/
class Ed25519Signer extends Uint8Array {
/** @type {typeof code} */
get code() {
return code
}
get signer() {
return this
}
/** @type {API.EdVerifier} */
get verifier() {
const bytes = new Uint8Array(this.buffer, PRIVATE_TAG_SIZE + KEY_SIZE)
const verifier = Verifier.decode(bytes)
Expand Down Expand Up @@ -149,11 +156,27 @@ class Ed25519Signer extends Uint8Array {

return Signature.create(this.signatureCode, raw)
}
/**
* @template T
* @param {API.ByteView<T>} payload
* @param {API.Signature<T, typeof this.signatureCode>} signature
*/

verify(payload, signature) {
return this.verifier.verify(payload, signature)
}

get signatureAlgorithm() {
return 'EdDSA'
}
get signatureCode() {
return Signature.EdDSA
}

export() {
return this
}
encode() {
return this
}
}
Empty file.
26 changes: 26 additions & 0 deletions packages/principal/src/ed25519/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Signer, Verifier, ByteView, UCAN, Await } from '@ucanto/interface'
import * as Signature from '@ipld/dag-ucan/signature'

export * from '@ucanto/interface'

type CODE = typeof Signature.EdDSA
type ALG = 'EdDSA'

export interface EdSigner<M extends string = 'key'>
extends Signer<M, CODE>,
UCAN.Verifier<M, CODE> {
readonly signer: EdSigner<M>
readonly verifier: EdVerifier<M>

readonly code: 0x1300
encode(): ByteView<EdSigner<M>>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
encode(): ByteView<EdSigner<M>>
encode(): ByteView<EdSigner<M>>
export(): ByteView<EdSigner<M>>

}

export interface EdVerifier<M extends string = 'key'>
extends Verifier<M, CODE> {
readonly code: 0xed
readonly signatureCode: CODE
readonly signatureAlgorithm: ALG

encode: () => ByteView<EdVerifier<M>>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
encode: () => ByteView<EdVerifier<M>>
encode: () => ByteView<EdVerifier<M>>
export: () => ByteView<EdVerifier<M>>

}
37 changes: 26 additions & 11 deletions packages/principal/src/ed25519/verifier.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as DID from '@ipld/dag-ucan/did'
import * as ED25519 from '@noble/ed25519'
import { varint } from 'multiformats'
import * as API from '@ucanto/interface'
import * as API from './type.js'
import * as Signature from '@ipld/dag-ucan/signature'
import { base58btc } from 'multiformats/bases/base58'
export const code = 0xed
export const name = 'Ed25519'

export const signatureCode = Signature.EdDSA
export const name = 'Ed25519'
export const signatureAlgorithm = 'EdDSA'
const PUBLIC_TAG_SIZE = varint.encodingLength(code)
const SIZE = 32 + PUBLIC_TAG_SIZE

Expand All @@ -27,7 +28,7 @@ export const parse = did => decode(DID.parse(did))
* corresponding `Principal` that can be used to verify signatures.
*
* @param {Uint8Array} bytes
* @returns {Verifier}
* @returns {API.EdVerifier}
*/
export const decode = bytes => {
const [algorithm] = varint.decode(bytes)
Expand All @@ -40,11 +41,7 @@ export const decode = bytes => {
`Expected Uint8Array with byteLength ${SIZE}, instead got Uint8Array with byteLength ${bytes.byteLength}`
)
} else {
return new Ed25519Principal(
bytes.buffer,
bytes.byteOffset,
bytes.byteLength
)
return new Ed25519Verifier(bytes.buffer, bytes.byteOffset, bytes.byteLength)
}
}

Expand All @@ -64,10 +61,21 @@ export const format = principal => DID.format(principal)
export const encode = principal => DID.encode(principal)

/**
* @implements {API.Verifier<"key", typeof Signature.EdDSA>}
* @implements {API.Principal<"key">}
* @implements {API.EdVerifier}
*/
class Ed25519Principal extends Uint8Array {
class Ed25519Verifier extends Uint8Array {
/** @type {typeof code} */
get code() {
return code
}
/** @type {typeof signatureCode} */
get signatureCode() {
return signatureCode
}
/** @type {typeof signatureAlgorithm} */
get signatureAlgorithm() {
return signatureAlgorithm
}
/**
* Raw public key without a multiformat code.
*
Expand Down Expand Up @@ -101,4 +109,11 @@ class Ed25519Principal extends Uint8Array {
ED25519.verify(signature.raw, payload, this.publicKey)
)
}

export() {
return this
}
encode() {
return this
}
}
10 changes: 9 additions & 1 deletion packages/principal/src/lib.js
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
export * as ed25519 from './ed25519.js'
import * as ed25519 from './ed25519.js'
import * as RSA from './rsa.js'
import { create as createVerifier } from './verifier.js'
import { create as createSigner } from './signer.js'

export const Verifier = createVerifier([ed25519.Verifier, RSA.Verifier])
export const Signer = createSigner([ed25519, RSA])

export { ed25519, RSA }
35 changes: 35 additions & 0 deletions packages/principal/src/multiformat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { varint } from 'multiformats'

/**
*
* @param {number} code
* @param {Uint8Array} bytes
*/
export const tagWith = (code, bytes) => {
const offset = varint.encodingLength(code)
const multiformat = new Uint8Array(bytes.byteLength + offset)
varint.encodeTo(code, multiformat, 0)
multiformat.set(bytes, offset)

return multiformat
}

/**
* @param {number} code
* @param {Uint8Array} source
* @param {number} byteOffset
* @returns
*/
export const untagWith = (code, source, byteOffset = 0) => {
const bytes = byteOffset !== 0 ? source.subarray(byteOffset) : source
const [tag, size] = varint.decode(bytes)
if (tag !== code) {
throw new Error(
`Expected multiformat with 0x${code.toString(
16
)} tag instead got 0x${tag.toString(16)}`
)
} else {
return new Uint8Array(bytes.buffer, bytes.byteOffset + size)
}
}
Loading