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

webauthn + refactor #23

Merged
merged 40 commits into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7b98c55
adds webauthn
Apr 20, 2022
a78327e
rename Identity to Address
Apr 21, 2022
7eccdc0
update readme
Apr 21, 2022
00f2030
change from keys to identity
Apr 25, 2022
7f19de0
adds anon identity
Apr 25, 2022
59a7c7d
rename files
Apr 25, 2022
a5f0d7b
remove optional
Apr 26, 2022
9f9c16d
getCoseKey
Apr 28, 2022
0eb8646
adds key_ops
Apr 28, 2022
b1a9e08
adds unprotected header for webauthn
Apr 29, 2022
791ed7f
fix tests. ledger.balance accepts an address
Apr 29, 2022
f34557a
firefox doesn't work with eddsa webauthn
Apr 29, 2022
9bcd0ec
CoseSign1
May 7, 2022
6440adf
change challenge and content
May 10, 2022
4350047
cbor content
May 10, 2022
9f1aaa4
unprotectedHeaders
May 10, 2022
efb52dc
cbor protected headers
May 10, 2022
24d1cfd
challenge
May 10, 2022
0e6e494
cosepublickey
May 10, 2022
0a40006
remove dep
May 10, 2022
50edc04
remove comment
May 10, 2022
e1ea49b
remove dep
May 10, 2022
00f0172
remove comment
May 10, 2022
09ff253
remove method
May 10, 2022
4012603
tests
May 11, 2022
bf05099
sign should return array buffer
May 12, 2022
6d4ad3d
updates types and getProtectedHeader
May 13, 2022
3095267
protected private key
May 13, 2022
046d77e
adds idStore
May 18, 2022
157402f
adds idstore tests. use ecdsa for webauthn because eddsa will not wo…
May 18, 2022
95b82b0
cleanup and fix tests
May 19, 2022
921aeba
adds async module
May 20, 2022
58f37df
remove comment
May 20, 2022
538864e
one minute polling
May 20, 2022
fa9e12c
increase wait time on async. fix message responses now that everythin…
May 20, 2022
6d4dabf
allow comms to authenticator through bluetooth
May 21, 2022
45e0226
fix symbol address not returned properly. fix tests
May 23, 2022
2dac5c3
adds change requests from pr
May 24, 2022
32bfcff
adds const file
May 24, 2022
2e12ca7
throw on identity verify
May 25, 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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ KeyPair.fromPem(string); // => KeyPair
Managing identities.

```ts
identity = Identity.fromPublicKey(key); // => Identity
identity = Identity.fromString(string); // => Identity
anonymous = new Identity(); // => Anonymous Identity
identity = Address.fromPublicKey(key); // => Address
identity = Address.fromString(string); // => Address
anonymous = new Address(); // => Anonymous Address

identity.toString(keys); // => "mw7aekyjtsx2hmeadrua5cpitgy7pykjkok3gyth3ggsio4zwa"
identity.toHex(keys); // => "01e736fc9624ff8ca7956189b6c1b66f55f533ed362ca48c884cd20065";
Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"crc": "^3.8.0",
"jest": "^27.4.5",
"js-sha3": "^0.8.0",
"js-sha512": "^0.8.0",
"node-forge": "^0.10.0",
"ts-jest": "^27.1.1",
"typescript": "^4.4.4",
Expand All @@ -25,6 +26,7 @@
"scripts": {
"test": "jest",
"build": "tsc",
"watch": "npm run clean && npm run build -- -w",
"clean": "rm -rf ./dist",
"postinstall": "tsc"
},
Expand All @@ -46,6 +48,7 @@
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"maxWorkers": 1,
"testMatch": [
"**/?(*.)+(spec|test).[jt]s?(x)"
],
Expand Down
2 changes: 2 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const ONE_SECOND = 1000
export const ONE_MINUTE = 60000
81 changes: 81 additions & 0 deletions src/identity/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import base32Decode from "base32-decode"
import base32Encode from "base32-encode"
import crc from "crc"

export const ANON_IDENTITY = "maa"
export class Address {
bytes: Uint8Array

constructor(bytes?: Buffer) {
this.bytes = new Uint8Array(bytes ? bytes : [0x00])
}

static anonymous(): Address {
return new Address()
}

static fromHex(hex: string): Address {
return new Address(Buffer.from(hex, "hex"))
}

static fromString(string: string): Address {
if (string === ANON_IDENTITY) {
return new Address()
}
const base32Address = string.slice(1, -2).toUpperCase()
const base32Checksum = string.slice(-2).toUpperCase()
const identity = base32Decode(base32Address, "RFC4648")
const checksum = base32Decode(base32Checksum, "RFC4648")

const check = Buffer.allocUnsafe(3)
check.writeUInt16BE(crc.crc16(Buffer.from(identity)), 0)

if (Buffer.compare(Buffer.from(checksum), check.slice(0, 1)) !== 0) {
throw new Error("Invalid Checksum")
}

return new Address(Buffer.from(identity))
}

isAnonymous(): boolean {
return Buffer.compare(this.toBuffer(), Buffer.from([0x00])) === 0
}

toBuffer(): Buffer {
return Buffer.from(this.bytes)
}

toString(): string {
if (this.isAnonymous()) {
return ANON_IDENTITY
}
const identity = this.toBuffer()
const checksum = Buffer.allocUnsafe(3)
checksum.writeUInt16BE(crc.crc16(identity), 0)

const leader = "m"
const base32Address = base32Encode(identity, "RFC4648", {
padding: false,
})
const base32Checksum = base32Encode(checksum, "RFC4648").slice(0, 2)
return (leader + base32Address + base32Checksum).toLowerCase()
}

toHex(): string {
if (this.isAnonymous()) {
return "00"
}
return Buffer.from(this.bytes).toString("hex")
}

withSubresource(id: number): Address {
let bytes = Buffer.from(this.bytes.slice(0, 29))
bytes[0] = 0x80 + ((id & 0x7f000000) >> 24)
const subresourceBytes = Buffer.from([
(id & 0x00ff0000) >> 16,
(id & 0x0000ff00) >> 8,
id & 0x000000ff,
])
return new Address(Buffer.concat([bytes, subresourceBytes]))
}
}
20 changes: 20 additions & 0 deletions src/identity/anonymous/anonymous-identity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Identity } from "../types"
import { EMPTY } from "../../message/cose"
import { Address } from "../address"

export class AnonymousIdentity extends Identity {
async sign() {
return EMPTY
}
async verify() {
return false
}

async getAddress(): Promise<Address> {
return Address.anonymous()
}

toJSON(): { dataType: string } {
return { dataType: this.constructor.name }
}
}
1 change: 1 addition & 0 deletions src/identity/anonymous/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AnonymousIdentity } from "./anonymous-identity"
47 changes: 47 additions & 0 deletions src/identity/ed25519/__tests__/ed25519-identity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Ed25519KeyPairIdentity } from "../ed25519-key-pair-identity"

describe("keys", () => {
test("getSeedWords", () => {
const seedWords = Ed25519KeyPairIdentity.getMnemonic()

expect(seedWords.split(" ")).toHaveLength(12)
})

test("fromSeedWords", async function () {
const seedWords = Ed25519KeyPairIdentity.getMnemonic()
const badWords = "abandon abandon abandon"

const alice = Ed25519KeyPairIdentity.fromMnemonic(seedWords)
const bob = Ed25519KeyPairIdentity.fromMnemonic(seedWords)

const aliceAddress = (await alice.getAddress()).toString()
const bobAddress = (await bob.getAddress()).toString()

expect(aliceAddress).toStrictEqual(bobAddress)
expect(() => {
Ed25519KeyPairIdentity.fromMnemonic(badWords)
}).toThrow()
})

test("fromPem", async function () {
const pem = `
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEICT3i6WfLx4t3UF6R8aEfczyATc/jvqvOrNga2MJfA2R
-----END PRIVATE KEY-----`
const badPem = `
-----BEGIN PRIVATE CAT-----
MEOW
-----END PRIVATE CAT-----`

const alice = Ed25519KeyPairIdentity.fromPem(pem)
const bob = Ed25519KeyPairIdentity.fromPem(pem)

const aliceAddress = (await alice.getAddress()).toString()
const bobAddress = (await bob.getAddress()).toString()

expect(aliceAddress).toStrictEqual(bobAddress)
expect(() => {
Ed25519KeyPairIdentity.fromPem(badPem)
}).toThrow()
})
})
80 changes: 80 additions & 0 deletions src/identity/ed25519/ed25519-key-pair-identity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import forge, { pki } from "node-forge"
import * as bip39 from "bip39"
import { CoseKey } from "../../message/cose"
const ed25519 = pki.ed25519
import { PublicKeyIdentity } from "../types"
import { Address } from "../address"

export class Ed25519KeyPairIdentity extends PublicKeyIdentity {
publicKey: ArrayBuffer
protected privateKey: ArrayBuffer

protected constructor(publicKey: ArrayBuffer, privateKey: ArrayBuffer) {
super()
this.publicKey = publicKey
this.privateKey = privateKey
}

static getMnemonic(): string {
return bip39.generateMnemonic()
}

async getAddress(): Promise<Address> {
const coseKey = this.getCoseKey()
return coseKey.toAddress()
}

static fromMnemonic(mnemonic: string): Ed25519KeyPairIdentity {
const sanitized = mnemonic.trim().split(/\s+/g).join(" ")
if (!bip39.validateMnemonic(sanitized)) {
throw new Error("Invalid Mnemonic")
}
const seed = bip39.mnemonicToSeedSync(sanitized).slice(0, 32)
const keys = ed25519.generateKeyPair({ seed })
return new Ed25519KeyPairIdentity(keys.publicKey, keys.privateKey)
}

static fromPem(pem: string): Ed25519KeyPairIdentity {
try {
const der = forge.pem.decode(pem)[0].body
const asn1 = forge.asn1.fromDer(der.toString())
const { privateKeyBytes } = ed25519.privateKeyFromAsn1(asn1)
const keys = ed25519.generateKeyPair({ seed: privateKeyBytes })
return new Ed25519KeyPairIdentity(keys.publicKey, keys.privateKey)
} catch (e) {
throw new Error("Invalid PEM")
}
}

async sign(data: ArrayBuffer): Promise<ArrayBuffer> {
return ed25519.sign({
message: data as Uint8Array,
privateKey: this.privateKey as Uint8Array,
})
}
async verify(m: ArrayBuffer): Promise<boolean> {
return false
}

getCoseKey(): CoseKey {
const c = new Map()
c.set(1, 1) // kty: OKP
c.set(3, -8) // alg: EdDSA
c.set(-1, 6) // crv: Ed25519
c.set(4, [2]) // key_ops: [verify]
c.set(-2, this.publicKey) // x: publicKey
return new CoseKey(c)
}

toJSON(): {
dataType: string
publicKey: ArrayBuffer
privateKey: ArrayBuffer
} {
return {
dataType: this.constructor.name,
publicKey: this.publicKey,
privateKey: this.privateKey,
}
}
}
1 change: 1 addition & 0 deletions src/identity/ed25519/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./ed25519-key-pair-identity"
Loading