From 7b98c55db7923aaef6ed7eb969b0c2cccb1085d3 Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 19 Apr 2022 18:18:08 -0700 Subject: [PATCH 01/40] adds webauthn --- .../ed25519/ed25519-key-pair-user-identity.ts | 24 +++++ src/identity/ed25519/index.ts | 1 + src/identity/identity.ts | 89 ++++++++++++++++++ src/identity/index.ts | 93 +------------------ src/identity/types.ts | 17 ++++ src/identity/webauthn/index.ts | 1 + .../webauthn/webauthn-user-identity.ts | 91 ++++++++++++++++++ src/network/index.ts | 2 +- 8 files changed, 228 insertions(+), 90 deletions(-) create mode 100644 src/identity/ed25519/ed25519-key-pair-user-identity.ts create mode 100644 src/identity/ed25519/index.ts create mode 100644 src/identity/identity.ts create mode 100644 src/identity/types.ts create mode 100644 src/identity/webauthn/index.ts create mode 100644 src/identity/webauthn/webauthn-user-identity.ts diff --git a/src/identity/ed25519/ed25519-key-pair-user-identity.ts b/src/identity/ed25519/ed25519-key-pair-user-identity.ts new file mode 100644 index 0000000..6a0d1c1 --- /dev/null +++ b/src/identity/ed25519/ed25519-key-pair-user-identity.ts @@ -0,0 +1,24 @@ +import { pki } from "node-forge" +const ed25519 = pki.ed25519 +import { KeyPairUserIdentity } from "../types" + +export class Ed25519KeyPairUserIdentity extends KeyPairUserIdentity { + publicKey: ArrayBuffer + privateKey: ArrayBuffer + constructor(publicKey: ArrayBuffer, privateKey: ArrayBuffer) { + super() + this.publicKey = publicKey + this.privateKey = privateKey + } + async sign(data: ArrayBuffer): Promise { + return Buffer.from( + ed25519.sign({ + message: new Uint8Array(data), + privateKey: new Uint8Array(this.privateKey), + }), + ) + } + async verify(m: ArrayBuffer): Promise { + return false + } +} diff --git a/src/identity/ed25519/index.ts b/src/identity/ed25519/index.ts new file mode 100644 index 0000000..08e6be7 --- /dev/null +++ b/src/identity/ed25519/index.ts @@ -0,0 +1 @@ +export * from "./ed25519-key-pair-user-identity" diff --git a/src/identity/identity.ts b/src/identity/identity.ts new file mode 100644 index 0000000..4dde075 --- /dev/null +++ b/src/identity/identity.ts @@ -0,0 +1,89 @@ +import base32Decode from "base32-decode" +import base32Encode from "base32-encode" +import crc from "crc" + +import { Key } from "../keys" +import { CoseKey } from "../message/cose" + +export const ANON_IDENTITY = "maa" +export class Identity { + bytes: Uint8Array + + constructor(bytes?: Buffer) { + this.bytes = new Uint8Array(bytes ? bytes : [0x00]) + } + + static anonymous(): Identity { + return new Identity() + } + + static fromHex(hex: string): Identity { + return new Identity(Buffer.from(hex, "hex")) + } + + static fromPublicKey(key: Key): Identity { + const coseKey = new CoseKey(key) + return coseKey.toIdentity() + } + + static fromString(string: string): Identity { + if (string === ANON_IDENTITY) { + return new Identity() + } + const base32Identity = string.slice(1, -2).toUpperCase() + const base32Checksum = string.slice(-2).toUpperCase() + const identity = base32Decode(base32Identity, "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 Identity(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 base32Identity = base32Encode(identity, "RFC4648", { + padding: false, + }) + const base32Checksum = base32Encode(checksum, "RFC4648").slice(0, 2) + return (leader + base32Identity + base32Checksum).toLowerCase() + } + + toHex(): string { + if (this.isAnonymous()) { + return "00" + } + return Buffer.from(this.bytes).toString("hex") + } + + withSubresource(id: number): Identity { + 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 Identity(Buffer.concat([bytes, subresourceBytes])) + } +} diff --git a/src/identity/index.ts b/src/identity/index.ts index 787632b..1c23401 100644 --- a/src/identity/index.ts +++ b/src/identity/index.ts @@ -1,89 +1,4 @@ -import base32Decode from "base32-decode"; -import base32Encode from "base32-encode"; -import crc from "crc"; - -import { Key } from "../keys"; -import { CoseKey } from "../message/cose"; - -export const ANON_IDENTITY = "maa" -export class Identity { - bytes: Uint8Array; - - constructor(bytes?: Buffer) { - this.bytes = new Uint8Array(bytes ? bytes : [0x00]); - } - - static anonymous(): Identity { - return new Identity(); - } - - static fromHex(hex: string): Identity { - return new Identity(Buffer.from(hex, "hex")); - } - - static fromPublicKey(key: Key): Identity { - const coseKey = new CoseKey(key); - return coseKey.toIdentity(); - } - - static fromString(string: string): Identity { - if (string === ANON_IDENTITY) { - return new Identity() - } - const base32Identity = string.slice(1, -2).toUpperCase(); - const base32Checksum = string.slice(-2).toUpperCase(); - const identity = base32Decode(base32Identity, "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 Identity(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 base32Identity = base32Encode(identity, "RFC4648", { - padding: false, - }); - const base32Checksum = base32Encode(checksum, "RFC4648").slice(0, 2); - return (leader + base32Identity + base32Checksum).toLowerCase(); - } - - toHex(): string { - if (this.isAnonymous()) { - return "00"; - } - return Buffer.from(this.bytes).toString("hex"); - } - - withSubresource(id: number): Identity { - 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 Identity(Buffer.concat([bytes, subresourceBytes])); - } -} +export * from "./identity" +export * from "./webauthn" +export * from "./ed25519" +export * from "./types" diff --git a/src/identity/types.ts b/src/identity/types.ts new file mode 100644 index 0000000..e1ff64b --- /dev/null +++ b/src/identity/types.ts @@ -0,0 +1,17 @@ +export interface Signer { + sign(data: ArrayBuffer): Promise +} + +export interface Verifier { + verify(data: ArrayBuffer): Promise +} + +export abstract class UserIdentity implements Signer, Verifier { + abstract publicKey: ArrayBuffer + abstract sign(data: ArrayBuffer): Promise + abstract verify(data: ArrayBuffer): Promise +} + +export abstract class KeyPairUserIdentity extends UserIdentity { + abstract privateKey: ArrayBuffer +} diff --git a/src/identity/webauthn/index.ts b/src/identity/webauthn/index.ts new file mode 100644 index 0000000..5ae12a8 --- /dev/null +++ b/src/identity/webauthn/index.ts @@ -0,0 +1 @@ +export * from "./webauthn-user-identity" diff --git a/src/identity/webauthn/webauthn-user-identity.ts b/src/identity/webauthn/webauthn-user-identity.ts new file mode 100644 index 0000000..2f2b901 --- /dev/null +++ b/src/identity/webauthn/webauthn-user-identity.ts @@ -0,0 +1,91 @@ +import cbor from "cbor" +import { UserIdentity } from "../types" + +const CHALLENGE_BUFFER = new TextEncoder().encode("lifted") + +export class WebAuthnUserIdentity extends UserIdentity { + publicKey: ArrayBuffer + rawId: ArrayBuffer + + constructor(publicKey: ArrayBuffer, rawId: ArrayBuffer) { + super() + this.publicKey = publicKey + this.rawId = rawId + } + + static async create() { + const publicKeyCredential = await createPublicKeyCredential() + const attestationResponse = + publicKeyCredential.response as AuthenticatorResponse & + AuthenticatorAttestationResponse + + const attestationObj = cbor.decodeFirstSync( + attestationResponse.attestationObject, + ) + const publicKeyBytes = getPublicKeyBytesFromAuthData( + attestationObj.authData, + ) + + return new WebAuthnUserIdentity(publicKeyBytes, publicKeyCredential.rawId) + } + + async sign(data: ArrayBuffer): Promise { + const publicKey: PublicKeyCredentialRequestOptions = { + challenge: data, + allowCredentials: [ + { + id: this.rawId, + type: "public-key", + }, + ], + } + let credential = (await window.navigator.credentials.get({ + publicKey, + })) as PublicKeyCredential + + return (credential.response as AuthenticatorAssertionResponse).signature + } + + async verify(m: ArrayBuffer): Promise { + return false + } +} + +async function createPublicKeyCredential() { + const publicKey: PublicKeyCredentialCreationOptions = { + challenge: CHALLENGE_BUFFER, + + rp: { + name: "lifted", + }, + + user: { + id: window.crypto.getRandomValues(new Uint8Array(32)), + name: "Lifted", + displayName: "Lifted", + }, + + attestation: "direct", + + pubKeyCredParams: [ + { + type: "public-key", + alg: -7, + }, + ], + } + + return (await navigator.credentials.create({ + publicKey, + })) as PublicKeyCredential +} + +function getPublicKeyBytesFromAuthData(authData: ArrayBuffer): ArrayBuffer { + const dataView = new DataView(new ArrayBuffer(2)) + const idLenBytes = authData.slice(53, 55) + // @ts-ignore + idLenBytes.forEach((value, index) => dataView.setUint8(index, value)) + const credentialIdLength = dataView.getUint16(0) + const publicKeyBytes = authData.slice(55 + credentialIdLength) + return publicKeyBytes +} diff --git a/src/network/index.ts b/src/network/index.ts index e5d7800..946203c 100644 --- a/src/network/index.ts +++ b/src/network/index.ts @@ -1,2 +1,2 @@ -export { Network } from "./network" +export * from "./network" export * from "./modules" From a78327e650960fae0787eab88927892d25301492 Mon Sep 17 00:00:00 2001 From: Tommy Date: Thu, 21 Apr 2022 15:46:14 -0700 Subject: [PATCH 02/40] rename Identity to Address --- src/identity/{identity.ts => address.ts} | 32 +++---- .../ed25519/ed25519-key-pair-user-identity.ts | 4 +- src/identity/identity.test.ts | 94 +++++++++---------- src/identity/index.ts | 2 +- src/identity/types.ts | 4 +- .../webauthn/webauthn-user-identity.ts | 6 +- src/message/cose.ts | 8 +- src/message/index.ts | 7 +- src/network/modules/ledger/__tests__/data.ts | 36 +++---- src/network/modules/ledger/ledger.ts | 24 ++--- src/network/network.test.ts | 8 +- src/network/network.ts | 4 +- 12 files changed, 114 insertions(+), 115 deletions(-) rename src/identity/{identity.ts => address.ts} (69%) diff --git a/src/identity/identity.ts b/src/identity/address.ts similarity index 69% rename from src/identity/identity.ts rename to src/identity/address.ts index 4dde075..82bc3d1 100644 --- a/src/identity/identity.ts +++ b/src/identity/address.ts @@ -6,33 +6,33 @@ import { Key } from "../keys" import { CoseKey } from "../message/cose" export const ANON_IDENTITY = "maa" -export class Identity { +export class Address { bytes: Uint8Array constructor(bytes?: Buffer) { this.bytes = new Uint8Array(bytes ? bytes : [0x00]) } - static anonymous(): Identity { - return new Identity() + static anonymous(): Address { + return new Address() } - static fromHex(hex: string): Identity { - return new Identity(Buffer.from(hex, "hex")) + static fromHex(hex: string): Address { + return new Address(Buffer.from(hex, "hex")) } - static fromPublicKey(key: Key): Identity { + static fromPublicKey(key: Key): Address { const coseKey = new CoseKey(key) - return coseKey.toIdentity() + return coseKey.toAddress() } - static fromString(string: string): Identity { + static fromString(string: string): Address { if (string === ANON_IDENTITY) { - return new Identity() + return new Address() } - const base32Identity = string.slice(1, -2).toUpperCase() + const base32Address = string.slice(1, -2).toUpperCase() const base32Checksum = string.slice(-2).toUpperCase() - const identity = base32Decode(base32Identity, "RFC4648") + const identity = base32Decode(base32Address, "RFC4648") const checksum = base32Decode(base32Checksum, "RFC4648") const check = Buffer.allocUnsafe(3) @@ -42,7 +42,7 @@ export class Identity { throw new Error("Invalid Checksum") } - return new Identity(Buffer.from(identity)) + return new Address(Buffer.from(identity)) } isAnonymous(): boolean { @@ -62,11 +62,11 @@ export class Identity { checksum.writeUInt16BE(crc.crc16(identity), 0) const leader = "m" - const base32Identity = base32Encode(identity, "RFC4648", { + const base32Address = base32Encode(identity, "RFC4648", { padding: false, }) const base32Checksum = base32Encode(checksum, "RFC4648").slice(0, 2) - return (leader + base32Identity + base32Checksum).toLowerCase() + return (leader + base32Address + base32Checksum).toLowerCase() } toHex(): string { @@ -76,7 +76,7 @@ export class Identity { return Buffer.from(this.bytes).toString("hex") } - withSubresource(id: number): Identity { + withSubresource(id: number): Address { let bytes = Buffer.from(this.bytes.slice(0, 29)) bytes[0] = 0x80 + ((id & 0x7f000000) >> 24) const subresourceBytes = Buffer.from([ @@ -84,6 +84,6 @@ export class Identity { (id & 0x0000ff00) >> 8, id & 0x000000ff, ]) - return new Identity(Buffer.concat([bytes, subresourceBytes])) + return new Address(Buffer.concat([bytes, subresourceBytes])) } } diff --git a/src/identity/ed25519/ed25519-key-pair-user-identity.ts b/src/identity/ed25519/ed25519-key-pair-user-identity.ts index 6a0d1c1..72f9b18 100644 --- a/src/identity/ed25519/ed25519-key-pair-user-identity.ts +++ b/src/identity/ed25519/ed25519-key-pair-user-identity.ts @@ -1,8 +1,8 @@ import { pki } from "node-forge" const ed25519 = pki.ed25519 -import { KeyPairUserIdentity } from "../types" +import { KeyPairIdentity } from "../types" -export class Ed25519KeyPairUserIdentity extends KeyPairUserIdentity { +export class Ed25519KeyPairIdentity extends KeyPairIdentity { publicKey: ArrayBuffer privateKey: ArrayBuffer constructor(publicKey: ArrayBuffer, privateKey: ArrayBuffer) { diff --git a/src/identity/identity.test.ts b/src/identity/identity.test.ts index 6e8ed05..2196b24 100644 --- a/src/identity/identity.test.ts +++ b/src/identity/identity.test.ts @@ -1,4 +1,4 @@ -import { Identity } from "../identity"; +import { Address } from "../identity" describe("identity", () => { function id(seed: number) { @@ -10,67 +10,67 @@ describe("identity", () => { 0, 0, 0, 0, 0, 0, 0, 0, seed >> 24, seed >> 16, seed >> 8, seed & 0xff, ]); - return new Identity(Buffer.from(bytes)); + return new Address(Buffer.from(bytes)) } test("can read anonymous", () => { - const anon = new Identity(); - const anonStr = anon.toString(); + const anon = new Address() + const anonStr = anon.toString() - expect(anon.isAnonymous()).toBe(true); - expect(anon).toStrictEqual(Identity.fromString(anonStr)); - }); + expect(anon.isAnonymous()).toBe(true) + expect(anon).toStrictEqual(Address.fromString(anonStr)) + }) test("byte array conversion", () => { - const anon = new Identity(); - const alice = id(1); - const bob = id(2); + const anon = new Address() + const alice = id(1) + const bob = id(2) - expect(anon.toString()).not.toStrictEqual(alice.toString()); - expect(alice.toString()).not.toStrictEqual(bob.toString()); + expect(anon.toString()).not.toStrictEqual(alice.toString()) + expect(alice.toString()).not.toStrictEqual(bob.toString()) - expect(anon.toBuffer()).not.toStrictEqual(alice.toBuffer()); - expect(alice.toBuffer()).not.toStrictEqual(bob.toBuffer()); + expect(anon.toBuffer()).not.toStrictEqual(alice.toBuffer()) + expect(alice.toBuffer()).not.toStrictEqual(bob.toBuffer()) - expect(Identity.fromString(anon.toString())).toStrictEqual(anon); - expect(Identity.fromString(alice.toString())).toStrictEqual(alice); - expect(Identity.fromString(bob.toString())).toStrictEqual(bob); - }); + expect(Address.fromString(anon.toString())).toStrictEqual(anon) + expect(Address.fromString(alice.toString())).toStrictEqual(alice) + expect(Address.fromString(bob.toString())).toStrictEqual(bob) + }) test("textual format 1", () => { - const alice = Identity.fromString( - "mahek5lid7ek7ckhq7j77nfwgk3vkspnyppm2u467ne5mwiqys" - ); - const bob = Identity.fromHex( - "01c8aead03f915f128f0fa7ff696c656eaa93db87bd9aa73df693acb22" - ); + const alice = Address.fromString( + "mahek5lid7ek7ckhq7j77nfwgk3vkspnyppm2u467ne5mwiqys", + ) + const bob = Address.fromHex( + "01c8aead03f915f128f0fa7ff696c656eaa93db87bd9aa73df693acb22", + ) - expect(alice).toStrictEqual(bob); - }); + expect(alice).toStrictEqual(bob) + }) test("textual format 2", () => { - const alice = Identity.fromString( - "mqbfbahksdwaqeenayy2gxke32hgb7aq4ao4wt745lsfs6wiaaaaqnz" - ); - const bob = Identity.fromHex( - "804a101d521d810211a0c6346ba89bd1cc1f821c03b969ff9d5c8b2f59000001" - ); + const alice = Address.fromString( + "mqbfbahksdwaqeenayy2gxke32hgb7aq4ao4wt745lsfs6wiaaaaqnz", + ) + const bob = Address.fromHex( + "804a101d521d810211a0c6346ba89bd1cc1f821c03b969ff9d5c8b2f59000001", + ) - expect(alice).toStrictEqual(bob); - }); + expect(alice).toStrictEqual(bob) + }) test("subresource 1", () => { - const alice = Identity.fromString( - "mahek5lid7ek7ckhq7j77nfwgk3vkspnyppm2u467ne5mwiqys" - ).withSubresource(1); - const bob = Identity.fromHex( - "80c8aead03f915f128f0fa7ff696c656eaa93db87bd9aa73df693acb22000001" - ); - const charlie = Identity.fromHex( - "80c8aead03f915f128f0fa7ff696c656eaa93db87bd9aa73df693acb22000002" - ); + const alice = Address.fromString( + "mahek5lid7ek7ckhq7j77nfwgk3vkspnyppm2u467ne5mwiqys", + ).withSubresource(1) + const bob = Address.fromHex( + "80c8aead03f915f128f0fa7ff696c656eaa93db87bd9aa73df693acb22000001", + ) + const charlie = Address.fromHex( + "80c8aead03f915f128f0fa7ff696c656eaa93db87bd9aa73df693acb22000002", + ) - expect(alice).toStrictEqual(bob); - expect(bob.withSubresource(2)).toStrictEqual(charlie); - }); -}); + expect(alice).toStrictEqual(bob) + expect(bob.withSubresource(2)).toStrictEqual(charlie) + }) +}) diff --git a/src/identity/index.ts b/src/identity/index.ts index 1c23401..f4a40b9 100644 --- a/src/identity/index.ts +++ b/src/identity/index.ts @@ -1,4 +1,4 @@ -export * from "./identity" +export * from "./address" export * from "./webauthn" export * from "./ed25519" export * from "./types" diff --git a/src/identity/types.ts b/src/identity/types.ts index e1ff64b..a2c1277 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -6,12 +6,12 @@ export interface Verifier { verify(data: ArrayBuffer): Promise } -export abstract class UserIdentity implements Signer, Verifier { +export abstract class Identity implements Signer, Verifier { abstract publicKey: ArrayBuffer abstract sign(data: ArrayBuffer): Promise abstract verify(data: ArrayBuffer): Promise } -export abstract class KeyPairUserIdentity extends UserIdentity { +export abstract class KeyPairIdentity extends Identity { abstract privateKey: ArrayBuffer } diff --git a/src/identity/webauthn/webauthn-user-identity.ts b/src/identity/webauthn/webauthn-user-identity.ts index 2f2b901..8a02902 100644 --- a/src/identity/webauthn/webauthn-user-identity.ts +++ b/src/identity/webauthn/webauthn-user-identity.ts @@ -1,9 +1,9 @@ import cbor from "cbor" -import { UserIdentity } from "../types" +import { Identity } from "../types" const CHALLENGE_BUFFER = new TextEncoder().encode("lifted") -export class WebAuthnUserIdentity extends UserIdentity { +export class WebAuthnIdentity extends Identity { publicKey: ArrayBuffer rawId: ArrayBuffer @@ -26,7 +26,7 @@ export class WebAuthnUserIdentity extends UserIdentity { attestationObj.authData, ) - return new WebAuthnUserIdentity(publicKeyBytes, publicKeyCredential.rawId) + return new WebAuthnIdentity(publicKeyBytes, publicKeyCredential.rawId) } async sign(data: ArrayBuffer): Promise { diff --git a/src/message/cose.ts b/src/message/cose.ts index ddf680b..1d2c26c 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -3,7 +3,7 @@ import { pki } from "node-forge"; import { sha3_224 } from "js-sha3"; const ed25519 = pki.ed25519; -import { Identity } from "../identity"; +import { Address } from "../identity"; import { Key, KeyPair } from "../keys"; import { Message } from "../message"; import { CborData, CborMap, tag } from "./cbor"; @@ -33,7 +33,7 @@ export class CoseMessage { static fromCborData(data: CborData): CoseMessage { const decoders = { tags: { - 10000: (value: Uint8Array) => new Identity(Buffer.from(value)), + 10000: (value: Uint8Array) => new Address(Buffer.from(value)), 1: (value: number) => tag(1, value), }, }; @@ -168,7 +168,7 @@ export class CoseKey { return cbor.encodeCanonical([this.key]); } - toIdentity(): Identity { - return new Identity(this.keyId); + toAddress(): Address { + return new Address(this.keyId) } } diff --git a/src/message/index.ts b/src/message/index.ts index d3ce69d..cb703eb 100644 --- a/src/message/index.ts +++ b/src/message/index.ts @@ -1,6 +1,5 @@ import cbor from "cbor"; - -import { Identity } from "../identity"; +import { Address } from "../identity" import { CborData, CborMap, tag } from "./cbor"; import { CoseMessage } from "./cose"; import { ManyError, SerializedManyError } from "./error"; @@ -8,8 +7,8 @@ import { KeyPair } from "../keys"; interface MessageContent { version?: number; - from?: Identity; - to?: Identity; + from?: Address + to?: Address method: string; data?: any; timestamp?: number; diff --git a/src/network/modules/ledger/__tests__/data.ts b/src/network/modules/ledger/__tests__/data.ts index 9599670..ee3dbe4 100644 --- a/src/network/modules/ledger/__tests__/data.ts +++ b/src/network/modules/ledger/__tests__/data.ts @@ -1,16 +1,16 @@ -import { Identity } from "../../../../identity" +import { Address } from "../../../../identity" import cbor from "cbor" import { tag } from "../../../../message/cbor" import { Message } from "../../../../message" const identityStr1 = "mqbfbahksdwaqeenayy2gxke32hgb7aq4ao4wt745lsfs6wiaaaaqnz" -const Identity1 = Identity.fromString(identityStr1).toBuffer() +const Address1 = Address.fromString(identityStr1).toBuffer() const identityStr2 = "maffbahksdwaqeenayy2gxke32hgb7aq4ao4wt745lsfs6wijp" -const Identity2 = Identity.fromString(identityStr2).toBuffer() +const Address2 = Address.fromString(identityStr2).toBuffer() -export const mockSymbolIdentity = [tag(10000, Identity1), "abc"] +export const mockSymbolAddress = [tag(10000, Address1), "abc"] -export const mockSymbolIdentity2 = [tag(10000, Identity2), "cba"] +export const mockSymbolAddress2 = [tag(10000, Address2), "cba"] export const expectedSymbolsMap = { symbols: new Map([ @@ -27,7 +27,7 @@ export const mockLedgerInfoResponseContent = new Map([ [ 4, // @ts-ignore - new Map([mockSymbolIdentity, mockSymbolIdentity2]), + new Map([mockSymbolAddress, mockSymbolAddress2]), ], ]), ), @@ -38,8 +38,8 @@ export const mockLedgerInfoResponseMessage = new Message( mockLedgerInfoResponseContent, ) -export const mockSymbolBalance = [tag(10000, Identity1), 1000000] -export const mockSymbolBalance2 = [tag(10000, Identity2), 5000000] +export const mockSymbolBalance = [tag(10000, Address1), 1000000] +export const mockSymbolBalance2 = [tag(10000, Address2), 5000000] export const mockLedgerBalanceResponseContent = new Map([ [ @@ -65,8 +65,8 @@ export const expectedBalancesMap = { ]), } -const txnSymbolIdentity1 = "oafw3bxrqe2jdcidvjlonloqcczvytrxr3fl4naybmign3uy6e" -const txnSymbolIdentity2 = "oafxombm6axwsrcvymht5ss3chlpbks7sp7dvl2v7chnuzkyfj" +const txnSymbolAddress1 = "oafw3bxrqe2jdcidvjlonloqcczvytrxr3fl4naybmign3uy6e" +const txnSymbolAddress2 = "oafxombm6axwsrcvymht5ss3chlpbks7sp7dvl2v7chnuzkyfj" const txnTime1 = new Date() const txnTime2 = new Date() txnTime2.setMinutes(txnTime1.getMinutes() + 1) @@ -88,9 +88,9 @@ export const mockLedgerListResponseContent = new Map([ 2, [ 0, - tag(10000, Identity1), - tag(10000, Identity2), - txnSymbolIdentity1, + tag(10000, Address1), + tag(10000, Address2), + txnSymbolAddress1, 1, ], ], @@ -103,9 +103,9 @@ export const mockLedgerListResponseContent = new Map([ 2, [ 0, - tag(10000, Identity1), - tag(10000, Identity2), - txnSymbolIdentity2, + tag(10000, Address1), + tag(10000, Address2), + txnSymbolAddress2, 2, ], ], @@ -126,7 +126,7 @@ export const expectedListResponse = { type: "send", from: identityStr1, to: identityStr2, - symbolIdentity: txnSymbolIdentity1, + symbolAddress: txnSymbolAddress1, amount: BigInt(1), }, { @@ -135,7 +135,7 @@ export const expectedListResponse = { type: "send", from: identityStr1, to: identityStr2, - symbolIdentity: txnSymbolIdentity2, + symbolAddress: txnSymbolAddress2, amount: BigInt(2), }, ], diff --git a/src/network/modules/ledger/ledger.ts b/src/network/modules/ledger/ledger.ts index 621f6c8..0c24481 100644 --- a/src/network/modules/ledger/ledger.ts +++ b/src/network/modules/ledger/ledger.ts @@ -1,10 +1,10 @@ import cbor from "cbor" -import { Identity } from "../../../identity" +import { Address } from "../../../identity" import { Message } from "../../../message" import type { NetworkModule } from "../types" export interface LedgerInfo { - symbols: Map, string> + symbols: Map, string> } export enum OrderType { @@ -42,7 +42,7 @@ export interface Transaction { time: Date type: TransactionType amount: bigint - symbolIdentity: string + symbolAddress: string symbol?: string from?: string to?: string @@ -76,7 +76,7 @@ interface Ledger extends NetworkModule { balance: (symbols?: string[]) => Promise mint: () => Promise burn: () => Promise - send: (to: Identity, amount: bigint, symbol: string) => Promise + send: (to: Address, amount: bigint, symbol: string) => Promise transactions: () => Promise<{ count: bigint }> list: (opts?: ListArgs) => Promise } @@ -103,7 +103,7 @@ export const Ledger: Ledger = { throw new Error("Not implemented") }, - async send(to: Identity, amount: bigint, symbol: string): Promise { + async send(to: Address, amount: bigint, symbol: string): Promise { return await this.call( "ledger.send", new Map([ @@ -145,9 +145,9 @@ export function getLedgerInfo(message: Message): LedgerInfo { const symbols = decodedContent.get(4) for (const symbol of symbols) { - const identity = new Identity(Buffer.from(symbol[0].value)).toString() + const address = new Address(Buffer.from(symbol[0].value)).toString() const symbolName = symbol[1] - result.symbols.set(identity, symbolName) + result.symbols.set(address, symbolName) } } } @@ -166,9 +166,9 @@ export function getBalance(message: Message): Balances { const symbolsToBalancesMap = messageContent.get(0) if (!(symbolsToBalancesMap instanceof Map)) return result for (const balanceEntry of symbolsToBalancesMap) { - const symbolIdentityStr = new Identity(balanceEntry[0].value).toString() + const symbolAddress = new Address(balanceEntry[0].value).toString() const balance = balanceEntry[1] - result.balances.set(symbolIdentityStr, balance) + result.balances.set(symbolAddress, balance) } } } @@ -209,15 +209,15 @@ function makeSendTransactionData(t: Map) { const time = t.get(1) const from = transactionData[1] as { value: Uint8Array } const to = transactionData[2] as { value: Uint8Array } - const fromAddress = new Identity(from.value as Buffer).toString() - const toAddress = new Identity(to.value as Buffer).toString() + const fromAddress = new Address(from.value as Buffer).toString() + const toAddress = new Address(to.value as Buffer).toString() return { id, time, type: TransactionType.send, from: fromAddress, to: toAddress, - symbolIdentity: transactionData[3], + symbolAddress: transactionData[3], amount: BigInt(transactionData[4] as number), } } diff --git a/src/network/network.test.ts b/src/network/network.test.ts index f202420..c58335b 100644 --- a/src/network/network.test.ts +++ b/src/network/network.test.ts @@ -5,8 +5,8 @@ import { tag } from "../message/cbor"; import { sha3_224 } from "js-sha3"; import { expectedSymbolsMap, - mockSymbolIdentity, - mockSymbolIdentity2, + mockSymbolAddress, + mockSymbolAddress2, } from "./modules/ledger/__tests__/data" import { Ledger } from "./modules"; @@ -71,10 +71,10 @@ describe("network", () => { 4, cbor.encode( // @ts-ignore - new Map([[4, new Map([mockSymbolIdentity, mockSymbolIdentity2])]]) + new Map([[4, new Map([mockSymbolAddress, mockSymbolAddress2])]]), ), ], - ]); + ]) const mockCoseResponse = cbor.encodeCanonical( tag(18, [ cbor.encodeCanonical(protectedHeader), diff --git a/src/network/network.ts b/src/network/network.ts index 546b423..d9a7b7f 100644 --- a/src/network/network.ts +++ b/src/network/network.ts @@ -1,4 +1,4 @@ -import { Identity } from "../identity" +import { Address } from "../identity" import { KeyPair } from "../keys" import { Message } from "../message" import { CborData } from "../message/cbor" @@ -41,7 +41,7 @@ export class Network { const req = Message.fromObject({ method, from: this.keys?.publicKey - ? Identity.fromPublicKey(this.keys.publicKey) + ? Address.fromPublicKey(this.keys.publicKey) : undefined, data, }) From 7eccdc0c3775b8c044813bbafd05bff5de15bb95 Mon Sep 17 00:00:00 2001 From: Tommy Date: Thu, 21 Apr 2022 15:46:56 -0700 Subject: [PATCH 03/40] update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e98bc56..2e0b37f 100644 --- a/README.md +++ b/README.md @@ -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"; From 00f2030de253e1de2bc4a6ff71a073bc260db978 Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 25 Apr 2022 13:33:59 -0700 Subject: [PATCH 04/40] change from keys to identity --- .../ed25519/ed25519-key-pair-user-identity.ts | 19 ++++-- src/identity/index.ts | 1 + src/identity/types.ts | 1 + .../webauthn/webauthn-user-identity.ts | 58 ++++++++++++++----- src/keys/index.ts | 2 +- src/message/cose.test.ts | 38 ++++++------ src/message/cose.ts | 40 ++++++------- src/message/index.ts | 11 ++-- src/message/message.test.ts | 12 ++-- src/network/network.ts | 14 ++--- 10 files changed, 116 insertions(+), 80 deletions(-) diff --git a/src/identity/ed25519/ed25519-key-pair-user-identity.ts b/src/identity/ed25519/ed25519-key-pair-user-identity.ts index 72f9b18..6232ae6 100644 --- a/src/identity/ed25519/ed25519-key-pair-user-identity.ts +++ b/src/identity/ed25519/ed25519-key-pair-user-identity.ts @@ -5,20 +5,27 @@ import { KeyPairIdentity } from "../types" export class Ed25519KeyPairIdentity extends KeyPairIdentity { publicKey: ArrayBuffer privateKey: ArrayBuffer + constructor(publicKey: ArrayBuffer, privateKey: ArrayBuffer) { super() this.publicKey = publicKey this.privateKey = privateKey } + async sign(data: ArrayBuffer): Promise { - return Buffer.from( - ed25519.sign({ - message: new Uint8Array(data), - privateKey: new Uint8Array(this.privateKey), - }), - ) + return ed25519.sign({ + message: data as Uint8Array, + privateKey: this.privateKey as Uint8Array, + }) } async verify(m: ArrayBuffer): Promise { return false } + + toJson() { + return { + publicKey: this.publicKey, + privateKey: this.privateKey, + } + } } diff --git a/src/identity/index.ts b/src/identity/index.ts index f4a40b9..ffeb473 100644 --- a/src/identity/index.ts +++ b/src/identity/index.ts @@ -1,4 +1,5 @@ export * from "./address" export * from "./webauthn" +export * from "./anonymous" export * from "./ed25519" export * from "./types" diff --git a/src/identity/types.ts b/src/identity/types.ts index a2c1277..bbb131f 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -8,6 +8,7 @@ export interface Verifier { export abstract class Identity implements Signer, Verifier { abstract publicKey: ArrayBuffer + abstract toJson(): unknown abstract sign(data: ArrayBuffer): Promise abstract verify(data: ArrayBuffer): Promise } diff --git a/src/identity/webauthn/webauthn-user-identity.ts b/src/identity/webauthn/webauthn-user-identity.ts index 8a02902..fde95b7 100644 --- a/src/identity/webauthn/webauthn-user-identity.ts +++ b/src/identity/webauthn/webauthn-user-identity.ts @@ -13,7 +13,7 @@ export class WebAuthnIdentity extends Identity { this.rawId = rawId } - static async create() { + static async create(): Promise { const publicKeyCredential = await createPublicKeyCredential() const attestationResponse = publicKeyCredential.response as AuthenticatorResponse & @@ -30,25 +30,42 @@ export class WebAuthnIdentity extends Identity { } async sign(data: ArrayBuffer): Promise { - const publicKey: PublicKeyCredentialRequestOptions = { - challenge: data, - allowCredentials: [ - { - id: this.rawId, - type: "public-key", - }, - ], - } - let credential = (await window.navigator.credentials.get({ - publicKey, - })) as PublicKeyCredential - - return (credential.response as AuthenticatorAssertionResponse).signature + const credential = await WebAuthnIdentity.getCredential(this.rawId, data) + const signature = (credential.response as AuthenticatorAssertionResponse) + .signature + return new Uint8Array(signature) } async verify(m: ArrayBuffer): Promise { return false } + + static async getCredential( + credentialId: ArrayBuffer, + challenge?: Uint8Array | ArrayBuffer, + ): Promise { + let credential = (await window.navigator.credentials.get({ + publicKey: { + challenge: challenge ?? CHALLENGE_BUFFER, + timeout: 10000, + allowCredentials: [ + { + transports: ["nfc", "usb"], + id: credentialId, + type: "public-key", + }, + ], + }, + })) as PublicKeyCredential + return credential + } + + toJson(): { rawId: string; publicKey: ArrayBuffer } { + return { + rawId: Buffer.from(this.rawId).toString("base64"), + publicKey: this.publicKey, + } + } } async function createPublicKeyCredential() { @@ -67,10 +84,19 @@ async function createPublicKeyCredential() { attestation: "direct", + authenticatorSelection: { + authenticatorAttachment: "cross-platform", + }, + pubKeyCredParams: [ { + /* + EdDSA -8 + ES256 -7 ECDSA w/ SHA-256 + */ type: "public-key", - alg: -7, + alg: -8, + // alg: -7, }, ], } diff --git a/src/keys/index.ts b/src/keys/index.ts index 0b61926..eb32473 100644 --- a/src/keys/index.ts +++ b/src/keys/index.ts @@ -3,7 +3,7 @@ import * as bip39 from "bip39"; const ed25519 = forge.pki.ed25519; -export type Key = Uint8Array; +export type Key = ArrayBuffer interface KeyPairParams { privateKey: Key; diff --git a/src/message/cose.test.ts b/src/message/cose.test.ts index dd6674a..3fc5d15 100644 --- a/src/message/cose.test.ts +++ b/src/message/cose.test.ts @@ -1,31 +1,33 @@ import { Message } from "../message"; import { CoseMessage, CoseKey } from "./cose"; import { KeyPair } from "../keys"; +import { Ed25519KeyPairIdentity } from "../identity" describe("CoseMessage", () => { - test("can make anonymous request", () => { - const anonymousMessage = Message.fromObject({ method: "info" }); - const coseMessage = CoseMessage.fromMessage(anonymousMessage); + test("can make anonymous request", async () => { + const anonymousMessage = Message.fromObject({ method: "info" }) + const coseMessage = await CoseMessage.fromMessage(anonymousMessage) - expect(coseMessage.signature).toHaveLength(0); - }); + expect(coseMessage.signature).toHaveLength(0) + }) - test("can make signed request", () => { - const keys = KeyPair.fromMnemonic(KeyPair.getMnemonic()); - const signedMessage = Message.fromObject({ method: "info" }); - const coseMessage = CoseMessage.fromMessage(signedMessage, keys); + test("can make signed request", async () => { + const keys = KeyPair.fromMnemonic(KeyPair.getMnemonic()) + const identity = new Ed25519KeyPairIdentity(keys.publicKey, keys.privateKey) + const signedMessage = Message.fromObject({ method: "info" }) + const coseMessage = await CoseMessage.fromMessage(signedMessage, identity) - expect(coseMessage.signature).not.toHaveLength(0); - }); + expect(coseMessage.signature).not.toHaveLength(0) + }) - test("can serialize/deserialize CBOR", () => { - const request = Message.fromObject({ method: "info" }); - const coseMessage = CoseMessage.fromMessage(request); - const serialized = coseMessage.toCborData(); - const deserialized = CoseMessage.fromCborData(serialized); + test("can serialize/deserialize CBOR", async () => { + const request = Message.fromObject({ method: "info" }) + const coseMessage = await CoseMessage.fromMessage(request) + const serialized = coseMessage.toCborData() + const deserialized = CoseMessage.fromCborData(serialized) - expect(deserialized.content).toStrictEqual(coseMessage.content); - }); + expect(deserialized.content).toStrictEqual(coseMessage.content) + }) }); describe("CoseKey", () => { diff --git a/src/message/cose.ts b/src/message/cose.ts index 1d2c26c..e4c4d72 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -1,28 +1,24 @@ import cbor from "cbor"; -import { pki } from "node-forge"; import { sha3_224 } from "js-sha3"; -const ed25519 = pki.ed25519; - -import { Address } from "../identity"; -import { Key, KeyPair } from "../keys"; +import { Address, Identity } from "../identity" +import { Key } from "../keys" import { Message } from "../message"; import { CborData, CborMap, tag } from "./cbor"; - -const ANONYMOUS = Buffer.from([0x00]); +export const ANONYMOUS = Buffer.from([0x00]) const EMPTY = Buffer.alloc(0); export class CoseMessage { protectedHeader: CborMap; unprotectedHeader: CborMap; content: CborMap; - signature: Buffer; + signature: ArrayBuffer constructor( protectedHeader: CborMap, unprotectedHeader: CborMap, content: CborMap, - signature: Buffer + signature: ArrayBuffer, ) { this.protectedHeader = protectedHeader; this.unprotectedHeader = unprotectedHeader; @@ -51,14 +47,18 @@ export class CoseMessage { ); } - static fromMessage(message: Message, keys?: KeyPair): CoseMessage { + static async fromMessage( + message: Message, + identity?: Identity, + ): Promise { const protectedHeader = this.getProtectedHeader( - keys ? keys.publicKey : ANONYMOUS - ); - const unprotectedHeader = this.getUnprotectedHeader(); - const content = message.content; - const signature = keys - ? this.getSignature(protectedHeader, content, keys.privateKey) + identity ? identity?.publicKey : ANONYMOUS, + ) + const unprotectedHeader = this.getUnprotectedHeader() + const content = message.content + + const signature = identity + ? await this.getSignature(protectedHeader, content, identity) : EMPTY; return new CoseMessage( protectedHeader, @@ -81,15 +81,15 @@ export class CoseMessage { return new Map(); } - private static getSignature( + private static async getSignature( protectedHeader: CborMap, content: CborMap, - privateKey: Key - ): Buffer { + identity: Identity, + ): Promise { const p = cbor.encodeCanonical(protectedHeader); const payload = cbor.encode(tag(10001, content)); const message = cbor.encodeCanonical(["Signature1", p, EMPTY, payload]); - return Buffer.from(ed25519.sign({ message, privateKey })); + return await identity.sign(message) } private replacer(key: string, value: any) { diff --git a/src/message/index.ts b/src/message/index.ts index cb703eb..29c3d7e 100644 --- a/src/message/index.ts +++ b/src/message/index.ts @@ -1,9 +1,8 @@ import cbor from "cbor"; -import { Address } from "../identity" +import { Address, Identity } from "../identity" import { CborData, CborMap, tag } from "./cbor"; import { CoseMessage } from "./cose"; import { ManyError, SerializedManyError } from "./error"; -import { KeyPair } from "../keys"; interface MessageContent { version?: number; @@ -84,12 +83,12 @@ export class Message { } } - toCoseMessage(keys?: KeyPair) { - return CoseMessage.fromMessage(this, keys); + toCoseMessage(identity?: Identity) { + return CoseMessage.fromMessage(this, identity) } - toCborData(keys?: KeyPair) { - return this.toCoseMessage(keys).toCborData(); + async toCborData(identity?: Identity) { + return (await this.toCoseMessage(identity)).toCborData() } toString() { diff --git a/src/message/message.test.ts b/src/message/message.test.ts index 252ce58..95b8ac7 100644 --- a/src/message/message.test.ts +++ b/src/message/message.test.ts @@ -7,11 +7,11 @@ describe("Message", () => { expect(req).toHaveProperty("content"); }); - test("can be serialized/deserialized", () => { - const msg = { method: "info" }; - const req = Message.fromObject(msg); - const cbor = req.toCborData(); + test("can be serialized/deserialized", async () => { + const msg = { method: "info" } + const req = Message.fromObject(msg) + const cbor = await req.toCborData() - expect(Message.fromCborData(cbor)).toStrictEqual(req); - }); + expect(Message.fromCborData(cbor)).toStrictEqual(req) + }) }); diff --git a/src/network/network.ts b/src/network/network.ts index d9a7b7f..f04e321 100644 --- a/src/network/network.ts +++ b/src/network/network.ts @@ -1,4 +1,4 @@ -import { Address } from "../identity" +import { Address, Identity } from "../identity" import { KeyPair } from "../keys" import { Message } from "../message" import { CborData } from "../message/cbor" @@ -8,11 +8,11 @@ import { NetworkModule } from "./modules" export class Network { [k: string]: any url: string - keys: KeyPair | undefined + identity: Identity | undefined - constructor(url: string, keys?: KeyPair) { + constructor(url: string, identity?: Identity) { this.url = url - this.keys = keys + this.identity = identity } apply(modules: NetworkModule[]) { @@ -20,7 +20,7 @@ export class Network { } async send(req: Message) { - const cbor = req.toCborData(this.keys) + const cbor = await req.toCborData(this.identity) const reply = await this.sendEncoded(cbor) // @TODO: Verify response const res = Message.fromCborData(reply) @@ -40,8 +40,8 @@ export class Network { call(method: string, data?: any) { const req = Message.fromObject({ method, - from: this.keys?.publicKey - ? Address.fromPublicKey(this.keys.publicKey) + from: this.identity?.publicKey + ? Address.fromPublicKey(this.identity?.publicKey) : undefined, data, }) From 7f19de059c51cb6ac3e6491d6256753d5b26dd18 Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 25 Apr 2022 13:34:34 -0700 Subject: [PATCH 05/40] adds anon identity --- src/identity/anonymous/anonymous-identity.ts | 16 ++++++++++++++++ src/identity/anonymous/index.ts | 1 + 2 files changed, 17 insertions(+) create mode 100644 src/identity/anonymous/anonymous-identity.ts create mode 100644 src/identity/anonymous/index.ts diff --git a/src/identity/anonymous/anonymous-identity.ts b/src/identity/anonymous/anonymous-identity.ts new file mode 100644 index 0000000..e209791 --- /dev/null +++ b/src/identity/anonymous/anonymous-identity.ts @@ -0,0 +1,16 @@ +import { Identity } from "../types" +import { ANONYMOUS } from "../../message/cose" + +export class AnonymousIdentity extends Identity { + publicKey = ANONYMOUS + async sign() { + return ANONYMOUS + } + async verify() { + return false + } + + toJson() { + return this.publicKey + } +} diff --git a/src/identity/anonymous/index.ts b/src/identity/anonymous/index.ts new file mode 100644 index 0000000..f9451d9 --- /dev/null +++ b/src/identity/anonymous/index.ts @@ -0,0 +1 @@ +export { AnonymousIdentity } from "./anonymous-identity" From 59a7c7ddf9f7efe4ef6df00fd971b8bf960d1ed7 Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 25 Apr 2022 14:51:51 -0700 Subject: [PATCH 06/40] rename files --- ...9-key-pair-user-identity.ts => ed25519-key-pair-identity.ts} | 0 src/identity/ed25519/index.ts | 2 +- src/identity/webauthn/index.ts | 2 +- .../{webauthn-user-identity.ts => webauthn-identity.ts} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename src/identity/ed25519/{ed25519-key-pair-user-identity.ts => ed25519-key-pair-identity.ts} (100%) rename src/identity/webauthn/{webauthn-user-identity.ts => webauthn-identity.ts} (100%) diff --git a/src/identity/ed25519/ed25519-key-pair-user-identity.ts b/src/identity/ed25519/ed25519-key-pair-identity.ts similarity index 100% rename from src/identity/ed25519/ed25519-key-pair-user-identity.ts rename to src/identity/ed25519/ed25519-key-pair-identity.ts diff --git a/src/identity/ed25519/index.ts b/src/identity/ed25519/index.ts index 08e6be7..9c6ff32 100644 --- a/src/identity/ed25519/index.ts +++ b/src/identity/ed25519/index.ts @@ -1 +1 @@ -export * from "./ed25519-key-pair-user-identity" +export * from "./ed25519-key-pair-identity" diff --git a/src/identity/webauthn/index.ts b/src/identity/webauthn/index.ts index 5ae12a8..4f3cf1f 100644 --- a/src/identity/webauthn/index.ts +++ b/src/identity/webauthn/index.ts @@ -1 +1 @@ -export * from "./webauthn-user-identity" +export * from "./webauthn-identity" diff --git a/src/identity/webauthn/webauthn-user-identity.ts b/src/identity/webauthn/webauthn-identity.ts similarity index 100% rename from src/identity/webauthn/webauthn-user-identity.ts rename to src/identity/webauthn/webauthn-identity.ts From a5f0d7b0430567a6f31bdc4e8cf89bf292f065be Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 26 Apr 2022 10:01:59 -0700 Subject: [PATCH 07/40] remove optional --- src/message/cose.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/message/cose.ts b/src/message/cose.ts index e4c4d72..3576ccd 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -52,7 +52,7 @@ export class CoseMessage { identity?: Identity, ): Promise { const protectedHeader = this.getProtectedHeader( - identity ? identity?.publicKey : ANONYMOUS, + identity ? identity.publicKey : ANONYMOUS, ) const unprotectedHeader = this.getUnprotectedHeader() const content = message.content From 9f9c16d66d0740b4b7195599f475df6ede77e259 Mon Sep 17 00:00:00 2001 From: Tommy Date: Thu, 28 Apr 2022 09:41:21 -0700 Subject: [PATCH 08/40] getCoseKey --- package-lock.json | 259 ++++++++++++++++++ package.json | 3 + src/identity/address.ts | 11 +- src/identity/anonymous/anonymous-identity.ts | 12 +- .../ed25519/ed25519-key-pair-identity.ts | 11 + src/identity/types.ts | 3 + src/identity/webauthn/webauthn-identity.ts | 45 ++- src/message/cose.ts | 141 +++++----- src/network/network.ts | 7 +- 9 files changed, 405 insertions(+), 87 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba3a4a2..694cdcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "hasInstallScript": true, "dependencies": { + "@types/cose-js": "^0.8.0", "@types/crc": "^3.4.0", "@types/jest": "^27.0.3", "@types/node": "^12.20.37", @@ -19,6 +20,7 @@ "base32-encode": "^1.2.0", "bip39": "^3.0.4", "cbor": "^8.1.0", + "cose-js": "^0.8.4", "crc": "^3.8.0", "jest": "^27.4.5", "js-sha3": "^0.8.0", @@ -1014,6 +1016,14 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/cose-js": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@types/cose-js/-/cose-js-0.8.0.tgz", + "integrity": "sha512-eDA3px4atnv5EP5/SlLPHboh9qQNeqCNZ3myy7gNt2ydCQpglivg0lfkk99jhJUg+JuodXCzuQVcUBvrxamvuQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/crc": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@types/crc/-/crc-3.4.0.tgz", @@ -1250,6 +1260,11 @@ "node": ">=0.4.0" } }, + "node_modules/aes-cbc-mac": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/aes-cbc-mac/-/aes-cbc-mac-1.0.1.tgz", + "integrity": "sha512-F1U0qNBNyrW82LRdQvYWKOpljZFnJ9LBUGWNCzCBTC3/+Fki77KgDrfJ+rBVlCpcmMT3jDEGhG61RMVeyHAKog==" + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1324,6 +1339,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -1350,6 +1370,14 @@ "node": ">=8" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1495,6 +1523,11 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1515,6 +1548,11 @@ "node": ">=8" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, "node_modules/browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -1736,6 +1774,22 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/cose-js": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/cose-js/-/cose-js-0.8.4.tgz", + "integrity": "sha512-TYt82olRQS/iZyb/qchG4KZSnzVBlOVXJjTCCgwKZUIkqqFyUIA+JG8OQdX5+ZyiWLj9W118Kuf3/jII0Gb/Bg==", + "dependencies": { + "aes-cbc-mac": "^1.0.1", + "any-promise": "^1.3.0", + "cbor": "^8.1.0", + "elliptic": "^6.4.0", + "node-hkdf-sync": "^1.0.0", + "node-rsa": "^1.1.1" + }, + "engines": { + "node": ">=12.0" + } + }, "node_modules/crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", @@ -1926,6 +1980,20 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz", "integrity": "sha512-LxgUNeu3BVU7sXaKjUDD9xivocQLxFtq6wgERrutdY/yIOps3ODOZExK1jg8DTEg4U8TUCb5MLGeWFOYuxjF3Q==" }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -2248,6 +2316,14 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", + "engines": { + "node": "> 0.1.90" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2539,6 +2615,25 @@ "node": ">=4" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -3696,6 +3791,16 @@ "node": ">=6" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3730,6 +3835,17 @@ "node": ">= 6.0.0" } }, + "node_modules/node-hkdf-sync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-hkdf-sync/-/node-hkdf-sync-1.0.0.tgz", + "integrity": "sha1-ZX15hkHAA/kQN7aKNZXgW+c22Zo=", + "dependencies": { + "vows": "0.5.13" + }, + "engines": { + "node": ">= 0.6.5" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -3740,6 +3856,14 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" }, + "node_modules/node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", + "dependencies": { + "asn1": "^0.2.4" + } + }, "node_modules/nofilter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", @@ -4706,6 +4830,20 @@ "node": ">= 8" } }, + "node_modules/vows": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/vows/-/vows-0.5.13.tgz", + "integrity": "sha1-9v2e6cNtPyC9ZoBFXP+AkMSynN4=", + "dependencies": { + "eyes": ">=0.1.6" + }, + "bin": { + "vows": "bin/vows" + }, + "engines": { + "node": ">=0.2.6" + } + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -5655,6 +5793,14 @@ "@babel/types": "^7.3.0" } }, + "@types/cose-js": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@types/cose-js/-/cose-js-0.8.0.tgz", + "integrity": "sha512-eDA3px4atnv5EP5/SlLPHboh9qQNeqCNZ3myy7gNt2ydCQpglivg0lfkk99jhJUg+JuodXCzuQVcUBvrxamvuQ==", + "requires": { + "@types/node": "*" + } + }, "@types/crc": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@types/crc/-/crc-3.4.0.tgz", @@ -5828,6 +5974,11 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" }, + "aes-cbc-mac": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/aes-cbc-mac/-/aes-cbc-mac-1.0.1.tgz", + "integrity": "sha512-F1U0qNBNyrW82LRdQvYWKOpljZFnJ9LBUGWNCzCBTC3/+Fki77KgDrfJ+rBVlCpcmMT3jDEGhG61RMVeyHAKog==" + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -5876,6 +6027,11 @@ "color-convert": "^2.0.1" } }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -5896,6 +6052,14 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -6008,6 +6172,11 @@ } } }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6025,6 +6194,11 @@ "fill-range": "^7.0.1" } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, "browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -6189,6 +6363,19 @@ } } }, + "cose-js": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/cose-js/-/cose-js-0.8.4.tgz", + "integrity": "sha512-TYt82olRQS/iZyb/qchG4KZSnzVBlOVXJjTCCgwKZUIkqqFyUIA+JG8OQdX5+ZyiWLj9W118Kuf3/jII0Gb/Bg==", + "requires": { + "aes-cbc-mac": "^1.0.1", + "any-promise": "^1.3.0", + "cbor": "^8.1.0", + "elliptic": "^6.4.0", + "node-hkdf-sync": "^1.0.0", + "node-rsa": "^1.1.1" + } + }, "crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", @@ -6342,6 +6529,20 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz", "integrity": "sha512-LxgUNeu3BVU7sXaKjUDD9xivocQLxFtq6wgERrutdY/yIOps3ODOZExK1jg8DTEg4U8TUCb5MLGeWFOYuxjF3Q==" }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -6571,6 +6772,11 @@ "jest-message-util": "^27.5.1" } }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6791,6 +6997,25 @@ "safe-buffer": "^5.2.0" } }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -7661,6 +7886,16 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -7689,6 +7924,14 @@ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" }, + "node-hkdf-sync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-hkdf-sync/-/node-hkdf-sync-1.0.0.tgz", + "integrity": "sha1-ZX15hkHAA/kQN7aKNZXgW+c22Zo=", + "requires": { + "vows": "0.5.13" + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -7699,6 +7942,14 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" }, + "node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", + "requires": { + "asn1": "^0.2.4" + } + }, "nofilter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", @@ -8358,6 +8609,14 @@ } } }, + "vows": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/vows/-/vows-0.5.13.tgz", + "integrity": "sha1-9v2e6cNtPyC9ZoBFXP+AkMSynN4=", + "requires": { + "eyes": ">=0.1.6" + } + }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", diff --git a/package.json b/package.json index 02051b1..d75f0e8 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "main": "dist/index.js", "dependencies": { + "@types/cose-js": "^0.8.0", "@types/crc": "^3.4.0", "@types/jest": "^27.0.3", "@types/node": "^12.20.37", @@ -14,6 +15,7 @@ "base32-encode": "^1.2.0", "bip39": "^3.0.4", "cbor": "^8.1.0", + "cose-js": "^0.8.4", "crc": "^3.8.0", "jest": "^27.4.5", "js-sha3": "^0.8.0", @@ -25,6 +27,7 @@ "scripts": { "test": "jest", "build": "tsc", + "watch": "npm run clean && npm run build -- -w", "clean": "rm -rf ./dist", "postinstall": "tsc" }, diff --git a/src/identity/address.ts b/src/identity/address.ts index 82bc3d1..9a4d5e2 100644 --- a/src/identity/address.ts +++ b/src/identity/address.ts @@ -4,6 +4,7 @@ import crc from "crc" import { Key } from "../keys" import { CoseKey } from "../message/cose" +import { Identity } from "./types" export const ANON_IDENTITY = "maa" export class Address { @@ -21,8 +22,14 @@ export class Address { return new Address(Buffer.from(hex, "hex")) } - static fromPublicKey(key: Key): Address { - const coseKey = new CoseKey(key) + // static fromPublicKey(key: Key): Address { + // // const coseKey = new CoseKey(key) + // const coseKey = new CoseKey(key) + // return coseKey.toAddress() + // } + + static fromIdentity(i: Identity): Address { + const coseKey = i.getCoseKey() return coseKey.toAddress() } diff --git a/src/identity/anonymous/anonymous-identity.ts b/src/identity/anonymous/anonymous-identity.ts index e209791..b0d4d6e 100644 --- a/src/identity/anonymous/anonymous-identity.ts +++ b/src/identity/anonymous/anonymous-identity.ts @@ -1,5 +1,5 @@ import { Identity } from "../types" -import { ANONYMOUS } from "../../message/cose" +import { ANONYMOUS, CoseKey } from "../../message/cose" export class AnonymousIdentity extends Identity { publicKey = ANONYMOUS @@ -10,6 +10,16 @@ export class AnonymousIdentity extends Identity { 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() { return this.publicKey } diff --git a/src/identity/ed25519/ed25519-key-pair-identity.ts b/src/identity/ed25519/ed25519-key-pair-identity.ts index 6232ae6..44cf5e6 100644 --- a/src/identity/ed25519/ed25519-key-pair-identity.ts +++ b/src/identity/ed25519/ed25519-key-pair-identity.ts @@ -1,4 +1,5 @@ import { pki } from "node-forge" +import { CoseKey } from "../../message/cose" const ed25519 = pki.ed25519 import { KeyPairIdentity } from "../types" @@ -22,6 +23,16 @@ export class Ed25519KeyPairIdentity extends KeyPairIdentity { 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() { return { publicKey: this.publicKey, diff --git a/src/identity/types.ts b/src/identity/types.ts index bbb131f..5f58e1c 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -1,3 +1,5 @@ +import { CoseKey } from "../message/cose" + export interface Signer { sign(data: ArrayBuffer): Promise } @@ -11,6 +13,7 @@ export abstract class Identity implements Signer, Verifier { abstract toJson(): unknown abstract sign(data: ArrayBuffer): Promise abstract verify(data: ArrayBuffer): Promise + abstract getCoseKey(): CoseKey } export abstract class KeyPairIdentity extends Identity { diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index fde95b7..0b03f0b 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -1,18 +1,30 @@ import cbor from "cbor" +import { CoseKey } from "../../message/cose" import { Identity } from "../types" const CHALLENGE_BUFFER = new TextEncoder().encode("lifted") export class WebAuthnIdentity extends Identity { - publicKey: ArrayBuffer + publicKey: ArrayBuffer // x-coordinate rawId: ArrayBuffer + cosePublicKey: ArrayBuffer - constructor(publicKey: ArrayBuffer, rawId: ArrayBuffer) { + constructor(cosePublicKey: ArrayBuffer, rawId: ArrayBuffer) { super() - this.publicKey = publicKey + this.cosePublicKey = cosePublicKey + this.publicKey = this.getPublicKeyFromCoseKey(cosePublicKey) this.rawId = rawId } + static decode(publicKey: ArrayBuffer) { + return cbor.decodeAllSync(publicKey) + } + + private getPublicKeyFromCoseKey(cosePublicKey: ArrayBuffer): ArrayBuffer { + const decoded = cbor.decodeFirstSync(cosePublicKey) + return decoded.get(-2) + } + static async create(): Promise { const publicKeyCredential = await createPublicKeyCredential() const attestationResponse = @@ -33,7 +45,8 @@ export class WebAuthnIdentity extends Identity { const credential = await WebAuthnIdentity.getCredential(this.rawId, data) const signature = (credential.response as AuthenticatorAssertionResponse) .signature - return new Uint8Array(signature) + console.log({ signature }) + return signature } async verify(m: ArrayBuffer): Promise { @@ -60,10 +73,24 @@ export class WebAuthnIdentity extends Identity { return credential } - toJson(): { rawId: string; publicKey: ArrayBuffer } { + getCoseKey(): CoseKey { + // @ts-ignore + const c = new Map([ + [1, 1], // kty: OKP + [3, -8], // alg: EdDSA + [-1, 6], // crv: Ed25519 + [4, [2]], // key_ops: [verify] + [-2, this.publicKey], // x: publicKey + ]) + console.log({ getCoseKey: c }) + return new CoseKey(cbor.decode(this.cosePublicKey)) + return new CoseKey(c) + } + + toJson(): { rawId: string; cosePublicKey: ArrayBuffer } { return { rawId: Buffer.from(this.rawId).toString("base64"), - publicKey: this.publicKey, + cosePublicKey: this.cosePublicKey, } } } @@ -112,6 +139,8 @@ function getPublicKeyBytesFromAuthData(authData: ArrayBuffer): ArrayBuffer { // @ts-ignore idLenBytes.forEach((value, index) => dataView.setUint8(index, value)) const credentialIdLength = dataView.getUint16(0) - const publicKeyBytes = authData.slice(55 + credentialIdLength) - return publicKeyBytes + const cosePublicKey = authData.slice(55 + credentialIdLength) + const publicKeyObject = cbor.decodeFirstSync(cosePublicKey) + console.log({ publicKeyObject }) + return cosePublicKey } diff --git a/src/message/cose.ts b/src/message/cose.ts index 3576ccd..4169a4a 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -1,17 +1,19 @@ import cbor from "cbor"; import { sha3_224 } from "js-sha3"; -import { Address, Identity } from "../identity" +import { Address, AnonymousIdentity, Identity } from "../identity" import { Key } from "../keys" -import { Message } from "../message"; -import { CborData, CborMap, tag } from "./cbor"; +import { Message } from "../message" +import { CborData, CborMap, tag } from "./cbor" +const cose = require("cose-js") +import { HeaderParameters } from "cose-js" export const ANONYMOUS = Buffer.from([0x00]) const EMPTY = Buffer.alloc(0); export class CoseMessage { - protectedHeader: CborMap; - unprotectedHeader: CborMap; - content: CborMap; + protectedHeader: CborMap + unprotectedHeader: CborMap + content: CborMap signature: ArrayBuffer constructor( @@ -20,10 +22,10 @@ export class CoseMessage { content: CborMap, signature: ArrayBuffer, ) { - this.protectedHeader = protectedHeader; - this.unprotectedHeader = unprotectedHeader; - this.content = content; - this.signature = signature; + this.protectedHeader = protectedHeader + this.unprotectedHeader = unprotectedHeader + this.content = content + this.signature = signature } static fromCborData(data: CborData): CoseMessage { @@ -32,53 +34,56 @@ export class CoseMessage { 10000: (value: Uint8Array) => new Address(Buffer.from(value)), 1: (value: number) => tag(1, value), }, - }; - const cose = cbor.decodeFirstSync(data, decoders).value; - const protectedHeader = cbor.decodeFirstSync(cose[0]); - const unprotectedHeader = cose[1]; - const content = cbor.decodeFirstSync(cose[2], decoders).value; + } + const cose = cbor.decodeFirstSync(data, decoders).value + const protectedHeader = cbor.decodeFirstSync(cose[0]) + const unprotectedHeader = cose[1] + const content = cbor.decodeFirstSync(cose[2], decoders).value const signature = cose[3] return new CoseMessage( protectedHeader, unprotectedHeader, content, - signature - ); + signature, + ) } static async fromMessage( message: Message, - identity?: Identity, + identity: Identity = new AnonymousIdentity(), ): Promise { - const protectedHeader = this.getProtectedHeader( - identity ? identity.publicKey : ANONYMOUS, - ) + const protectedHeader = this._getProtectedHeader(identity) + // const protectedHeader = this.getProtectedHeader( + // identity ? identity.publicKey : ANONYMOUS, + // ) const unprotectedHeader = this.getUnprotectedHeader() const content = message.content const signature = identity ? await this.getSignature(protectedHeader, content, identity) - : EMPTY; + : EMPTY + console.log({ signature }) return new CoseMessage( protectedHeader, unprotectedHeader, content, - signature - ); + signature, + ) } - private static getProtectedHeader(publicKey: Key): CborMap { - const coseKey = new CoseKey(publicKey); - const protectedHeader = new Map(); - protectedHeader.set(1, -8); // alg: "Ed25519" - protectedHeader.set(4, coseKey.keyId); // kid: kid - protectedHeader.set("keyset", coseKey.toCborData()); - return protectedHeader; + private static _getProtectedHeader(identity: Identity): CborMap { + const coseKey = identity.getCoseKey() + console.log({ getProtectedHeader: coseKey }) + const protectedHeader = new Map() + protectedHeader.set(1, coseKey.key.get(3)) // alg + protectedHeader.set(4, coseKey.keyId) // kid: kid + protectedHeader.set("keyset", coseKey.toCborData()) + return protectedHeader } private static getUnprotectedHeader(): CborMap { - return new Map(); + return new Map() } private static async getSignature( @@ -86,32 +91,32 @@ export class CoseMessage { content: CborMap, identity: Identity, ): Promise { - const p = cbor.encodeCanonical(protectedHeader); - const payload = cbor.encode(tag(10001, content)); - const message = cbor.encodeCanonical(["Signature1", p, EMPTY, payload]); + const p = cbor.encodeCanonical(protectedHeader) + const payload = cbor.encode(tag(10001, content)) + const message = cbor.encodeCanonical(["Signature1", p, EMPTY, payload]) return await identity.sign(message) } private replacer(key: string, value: any) { if (value?.type === "Buffer") { - return Buffer.from(value.data).toString("hex"); + return Buffer.from(value.data).toString("hex") } else if (value instanceof Map) { - return Object.fromEntries(value.entries()); + return Object.fromEntries(value.entries()) } else if (typeof value === "bigint") { - return parseInt(value.toString()); + return parseInt(value.toString()) } else if (key === "hash") { - return Buffer.from(value).toString("hex"); + return Buffer.from(value).toString("hex") } else { - return value; + return value } } toCborData(): CborData { - const p = cbor.encodeCanonical(this.protectedHeader); - const u = this.unprotectedHeader; - const payload = cbor.encode(tag(10001, this.content)); - let sig = this.signature; - return cbor.encodeCanonical(tag(18, [p, u, payload, sig])); + const p = cbor.encodeCanonical(this.protectedHeader) + const u = this.unprotectedHeader + const payload = cbor.encode(tag(10001, this.content)) + let sig = this.signature + return cbor.encodeCanonical(tag(18, [p, u, payload, sig])) } toString(): string { @@ -123,49 +128,39 @@ export class CoseMessage { this.signature, ], this.replacer, - 2 - ); + 2, + ) } } export class CoseKey { - key: CborMap; - keyId: CborData; - private common: CborMap; - - constructor(publicKey: Key) { - this.common = this.getCommon(publicKey); - this.keyId = this.getKeyId(); - this.key = this.getKey(); - } - - private getCommon(publicKey: Key) { - const common = new Map(); - common.set(1, 1); // kty: OKP - common.set(3, -8); // alg: EdDSA - common.set(-1, 6); // crv: Ed25519 - common.set(4, [2]); // key_ops: [verify] - common.set(-2, publicKey); // x: publicKey - return common; + key: CborMap + keyId: CborData + private common: CborMap + + constructor(commonParams: Map = new Map()) { + this.common = commonParams + this.keyId = this.getKeyId() + this.key = this.getKey() } private getKeyId() { if (Buffer.compare(this.common.get(-2), ANONYMOUS) === 0) { - return ANONYMOUS; + return ANONYMOUS } - const keyId = new Map(this.common); - const pk = "01" + sha3_224(cbor.encodeCanonical(keyId)); - return Buffer.from(pk, "hex"); + const keyId = new Map(this.common) + const pk = "01" + sha3_224(cbor.encodeCanonical(keyId)) + return Buffer.from(pk, "hex") } private getKey() { - const key = new Map(this.common); - key.set(2, this.keyId); // kid: Key ID - return key; + const key = new Map(this.common) + key.set(2, this.keyId) // kid: Key ID + return key } toCborData(): CborData { - return cbor.encodeCanonical([this.key]); + return cbor.encodeCanonical([this.key]) } toAddress(): Address { diff --git a/src/network/network.ts b/src/network/network.ts index f04e321..078c745 100644 --- a/src/network/network.ts +++ b/src/network/network.ts @@ -37,11 +37,12 @@ export class Network { return Buffer.from(reply) } - call(method: string, data?: any) { + async call(method: string, data?: any) { const req = Message.fromObject({ method, - from: this.identity?.publicKey - ? Address.fromPublicKey(this.identity?.publicKey) + from: this.identity + ? // ? Address.fromPublicKey(this.identity?.publicKey) + await Address.fromIdentity(this.identity) : undefined, data, }) From 0eb86466c8a805ab7772098554bdbca59e7011bb Mon Sep 17 00:00:00 2001 From: Tommy Date: Thu, 28 Apr 2022 10:10:52 -0700 Subject: [PATCH 09/40] adds key_ops --- src/identity/webauthn/webauthn-identity.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index 0b03f0b..e5546c0 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -82,9 +82,10 @@ export class WebAuthnIdentity extends Identity { [4, [2]], // key_ops: [verify] [-2, this.publicKey], // x: publicKey ]) - console.log({ getCoseKey: c }) - return new CoseKey(cbor.decode(this.cosePublicKey)) - return new CoseKey(c) + let decoded = cbor.decode(this.cosePublicKey) + decoded.set(4, [2]) + console.log({ decoded }) + return new CoseKey(decoded) } toJson(): { rawId: string; cosePublicKey: ArrayBuffer } { From b1a9e082a81dfed5bab92d08199a652282a1567c Mon Sep 17 00:00:00 2001 From: Tommy Date: Thu, 28 Apr 2022 19:31:51 -0700 Subject: [PATCH 10/40] adds unprotected header for webauthn --- src/identity/address.ts | 6 ---- src/identity/types.ts | 7 +++-- src/identity/webauthn/webauthn-identity.ts | 34 ++++++++++------------ src/message/cose.ts | 31 +++++++------------- src/network/network.ts | 4 +-- tsconfig.json | 2 +- 6 files changed, 35 insertions(+), 49 deletions(-) diff --git a/src/identity/address.ts b/src/identity/address.ts index 9a4d5e2..87ca910 100644 --- a/src/identity/address.ts +++ b/src/identity/address.ts @@ -22,12 +22,6 @@ export class Address { return new Address(Buffer.from(hex, "hex")) } - // static fromPublicKey(key: Key): Address { - // // const coseKey = new CoseKey(key) - // const coseKey = new CoseKey(key) - // return coseKey.toAddress() - // } - static fromIdentity(i: Identity): Address { const coseKey = i.getCoseKey() return coseKey.toAddress() diff --git a/src/identity/types.ts b/src/identity/types.ts index 5f58e1c..f2f68fa 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -1,7 +1,7 @@ import { CoseKey } from "../message/cose" export interface Signer { - sign(data: ArrayBuffer): Promise + sign(data: ArrayBuffer): Promise } export interface Verifier { @@ -11,9 +11,12 @@ export interface Verifier { export abstract class Identity implements Signer, Verifier { abstract publicKey: ArrayBuffer abstract toJson(): unknown - abstract sign(data: ArrayBuffer): Promise + abstract sign(data: ArrayBuffer): Promise abstract verify(data: ArrayBuffer): Promise abstract getCoseKey(): CoseKey + async getUnprotectedHeader(_: ArrayBuffer): Promise> { + return new Map() + } } export abstract class KeyPairIdentity extends Identity { diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index e5546c0..daa634e 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -41,15 +41,11 @@ export class WebAuthnIdentity extends Identity { return new WebAuthnIdentity(publicKeyBytes, publicKeyCredential.rawId) } - async sign(data: ArrayBuffer): Promise { - const credential = await WebAuthnIdentity.getCredential(this.rawId, data) - const signature = (credential.response as AuthenticatorAssertionResponse) - .signature - console.log({ signature }) - return signature + async sign(_: ArrayBuffer): Promise { + return null } - async verify(m: ArrayBuffer): Promise { + async verify(_: ArrayBuffer): Promise { return false } @@ -73,18 +69,21 @@ export class WebAuthnIdentity extends Identity { return credential } + async getUnprotectedHeader(data: ArrayBuffer): Promise> { + const digest = await window.crypto.subtle.digest("SHA-512", data) + const cred = await WebAuthnIdentity.getCredential(this.rawId, digest) + const response = cred.response as AuthenticatorAssertionResponse + const m = new Map() + m.set("webauthn", true) + m.set("authData", response.authenticatorData) + m.set("clientData", Buffer.from(response.clientDataJSON).toString()) + m.set("signature", response.signature) + return m + } + getCoseKey(): CoseKey { - // @ts-ignore - const c = new Map([ - [1, 1], // kty: OKP - [3, -8], // alg: EdDSA - [-1, 6], // crv: Ed25519 - [4, [2]], // key_ops: [verify] - [-2, this.publicKey], // x: publicKey - ]) let decoded = cbor.decode(this.cosePublicKey) decoded.set(4, [2]) - console.log({ decoded }) return new CoseKey(decoded) } @@ -141,7 +140,6 @@ function getPublicKeyBytesFromAuthData(authData: ArrayBuffer): ArrayBuffer { idLenBytes.forEach((value, index) => dataView.setUint8(index, value)) const credentialIdLength = dataView.getUint16(0) const cosePublicKey = authData.slice(55 + credentialIdLength) - const publicKeyObject = cbor.decodeFirstSync(cosePublicKey) - console.log({ publicKeyObject }) + // const publicKeyObject = cbor.decodeFirstSync(cosePublicKey) return cosePublicKey } diff --git a/src/message/cose.ts b/src/message/cose.ts index 4169a4a..256b3ec 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -1,11 +1,8 @@ import cbor from "cbor"; import { sha3_224 } from "js-sha3"; import { Address, AnonymousIdentity, Identity } from "../identity" -import { Key } from "../keys" import { Message } from "../message" import { CborData, CborMap, tag } from "./cbor" -const cose = require("cose-js") -import { HeaderParameters } from "cose-js" export const ANONYMOUS = Buffer.from([0x00]) const EMPTY = Buffer.alloc(0); @@ -14,13 +11,13 @@ export class CoseMessage { protectedHeader: CborMap unprotectedHeader: CborMap content: CborMap - signature: ArrayBuffer + signature: ArrayBuffer | null constructor( protectedHeader: CborMap, unprotectedHeader: CborMap, content: CborMap, - signature: ArrayBuffer, + signature: ArrayBuffer | null, ) { this.protectedHeader = protectedHeader this.unprotectedHeader = unprotectedHeader @@ -54,16 +51,15 @@ export class CoseMessage { identity: Identity = new AnonymousIdentity(), ): Promise { const protectedHeader = this._getProtectedHeader(identity) - // const protectedHeader = this.getProtectedHeader( - // identity ? identity.publicKey : ANONYMOUS, - // ) - const unprotectedHeader = this.getUnprotectedHeader() const content = message.content + const cborContent = cbor.encode(tag(10001, message.content)) + const unprotectedHeader = await identity.getUnprotectedHeader(cborContent) + const signature = await this.getSignature( + protectedHeader, + cborContent, + identity, + ) - const signature = identity - ? await this.getSignature(protectedHeader, content, identity) - : EMPTY - console.log({ signature }) return new CoseMessage( protectedHeader, unprotectedHeader, @@ -82,17 +78,12 @@ export class CoseMessage { return protectedHeader } - private static getUnprotectedHeader(): CborMap { - return new Map() - } - private static async getSignature( protectedHeader: CborMap, - content: CborMap, + payload: ArrayBuffer, identity: Identity, - ): Promise { + ): Promise { const p = cbor.encodeCanonical(protectedHeader) - const payload = cbor.encode(tag(10001, content)) const message = cbor.encodeCanonical(["Signature1", p, EMPTY, payload]) return await identity.sign(message) } diff --git a/src/network/network.ts b/src/network/network.ts index 078c745..9101474 100644 --- a/src/network/network.ts +++ b/src/network/network.ts @@ -42,11 +42,11 @@ export class Network { method, from: this.identity ? // ? Address.fromPublicKey(this.identity?.publicKey) - await Address.fromIdentity(this.identity) + Address.fromIdentity(this.identity) : undefined, data, }) - return this.send(req) + return await this.send(req) } // @TODO: Move these methods to modules/base, modules/ledger, etc. diff --git a/tsconfig.json b/tsconfig.json index 8234c6e..b6a7f67 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "resolveJsonModule": true, "skipLibCheck": true, "strict": true, - "target": "es6" + "target": "ES2017" }, "include": ["**/*.d.ts", "src/**/*", "test/**/*"], "exclude": [ From 791ed7f281331a15106d9724af41bb5aef3a4e5b Mon Sep 17 00:00:00 2001 From: Tommy Date: Fri, 29 Apr 2022 08:26:36 -0700 Subject: [PATCH 11/40] fix tests. ledger.balance accepts an address --- src/identity/anonymous/anonymous-identity.ts | 4 ++-- src/identity/types.ts | 6 +++--- src/identity/webauthn/webauthn-identity.ts | 4 ++-- src/message/cose.ts | 13 ++++++------- src/network/modules/ledger/__tests__/ledger.test.ts | 2 +- src/network/modules/ledger/ledger.ts | 13 ++++++------- 6 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/identity/anonymous/anonymous-identity.ts b/src/identity/anonymous/anonymous-identity.ts index b0d4d6e..b48ac08 100644 --- a/src/identity/anonymous/anonymous-identity.ts +++ b/src/identity/anonymous/anonymous-identity.ts @@ -1,10 +1,10 @@ import { Identity } from "../types" -import { ANONYMOUS, CoseKey } from "../../message/cose" +import { ANONYMOUS, CoseKey, EMPTY } from "../../message/cose" export class AnonymousIdentity extends Identity { publicKey = ANONYMOUS async sign() { - return ANONYMOUS + return EMPTY } async verify() { return false diff --git a/src/identity/types.ts b/src/identity/types.ts index f2f68fa..4755fc3 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -1,7 +1,7 @@ import { CoseKey } from "../message/cose" export interface Signer { - sign(data: ArrayBuffer): Promise + sign(data: ArrayBuffer): Promise } export interface Verifier { @@ -11,10 +11,10 @@ export interface Verifier { export abstract class Identity implements Signer, Verifier { abstract publicKey: ArrayBuffer abstract toJson(): unknown - abstract sign(data: ArrayBuffer): Promise + abstract sign(data: ArrayBuffer): Promise abstract verify(data: ArrayBuffer): Promise abstract getCoseKey(): CoseKey - async getUnprotectedHeader(_: ArrayBuffer): Promise> { + async getUnprotectedHeader(_: ArrayBuffer): Promise> { return new Map() } } diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index daa634e..7499712 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -41,8 +41,8 @@ export class WebAuthnIdentity extends Identity { return new WebAuthnIdentity(publicKeyBytes, publicKeyCredential.rawId) } - async sign(_: ArrayBuffer): Promise { - return null + async sign(_: ArrayBuffer): Promise { + return new TextEncoder().encode("webauthn") } async verify(_: ArrayBuffer): Promise { diff --git a/src/message/cose.ts b/src/message/cose.ts index 256b3ec..d100d19 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -5,19 +5,19 @@ import { Message } from "../message" import { CborData, CborMap, tag } from "./cbor" export const ANONYMOUS = Buffer.from([0x00]) -const EMPTY = Buffer.alloc(0); +export const EMPTY = Buffer.alloc(0) export class CoseMessage { protectedHeader: CborMap unprotectedHeader: CborMap content: CborMap - signature: ArrayBuffer | null + signature: ArrayBuffer constructor( protectedHeader: CborMap, unprotectedHeader: CborMap, content: CborMap, - signature: ArrayBuffer | null, + signature: ArrayBuffer, ) { this.protectedHeader = protectedHeader this.unprotectedHeader = unprotectedHeader @@ -50,7 +50,7 @@ export class CoseMessage { message: Message, identity: Identity = new AnonymousIdentity(), ): Promise { - const protectedHeader = this._getProtectedHeader(identity) + const protectedHeader = this.getProtectedHeader(identity) const content = message.content const cborContent = cbor.encode(tag(10001, message.content)) const unprotectedHeader = await identity.getUnprotectedHeader(cborContent) @@ -68,9 +68,8 @@ export class CoseMessage { ) } - private static _getProtectedHeader(identity: Identity): CborMap { + private static getProtectedHeader(identity: Identity): CborMap { const coseKey = identity.getCoseKey() - console.log({ getProtectedHeader: coseKey }) const protectedHeader = new Map() protectedHeader.set(1, coseKey.key.get(3)) // alg protectedHeader.set(4, coseKey.keyId) // kid: kid @@ -82,7 +81,7 @@ export class CoseMessage { protectedHeader: CborMap, payload: ArrayBuffer, identity: Identity, - ): Promise { + ): Promise { const p = cbor.encodeCanonical(protectedHeader) const message = cbor.encodeCanonical(["Signature1", p, EMPTY, payload]) return await identity.sign(message) diff --git a/src/network/modules/ledger/__tests__/ledger.test.ts b/src/network/modules/ledger/__tests__/ledger.test.ts index ae5ea34..449e82c 100644 --- a/src/network/modules/ledger/__tests__/ledger.test.ts +++ b/src/network/modules/ledger/__tests__/ledger.test.ts @@ -54,7 +54,7 @@ describe("Ledger", () => { return mockLedgerBalanceResponseMessage }) const ledger = setupLedger(mockCall) - await ledger.balance(["abc", "def"]) + await ledger.balance(undefined, ["abc", "def"]) expect(mockCall).toHaveBeenCalledTimes(1) expect(mockCall).toHaveBeenCalledWith( "ledger.balance", diff --git a/src/network/modules/ledger/ledger.ts b/src/network/modules/ledger/ledger.ts index 0c24481..38e93c5 100644 --- a/src/network/modules/ledger/ledger.ts +++ b/src/network/modules/ledger/ledger.ts @@ -1,5 +1,5 @@ import cbor from "cbor" -import { Address } from "../../../identity" +import { Address, Identity } from "../../../identity" import { Message } from "../../../message" import type { NetworkModule } from "../types" @@ -73,7 +73,7 @@ export enum RangeType { interface Ledger extends NetworkModule { _namespace_: string info: () => Promise - balance: (symbols?: string[]) => Promise + balance: (address?: string, symbols?: string[]) => Promise mint: () => Promise burn: () => Promise send: (to: Address, amount: bigint, symbol: string) => Promise @@ -87,11 +87,10 @@ export const Ledger: Ledger = { const message = await this.call("ledger.info") return getLedgerInfo(message) }, - async balance(symbols?: string[]): Promise { - const res = await this.call( - "ledger.balance", - new Map([[1, symbols ? symbols : []]]), - ) + async balance(address?: string, symbols?: string[]): Promise { + const m = new Map([[1, symbols ?? []]]) + address && m.set(0, address) + const res = await this.call("ledger.balance", m) return getBalance(res) }, From f34557a9131e450890709a4e76833fb3b76bee43 Mon Sep 17 00:00:00 2001 From: Tommy Date: Fri, 29 Apr 2022 12:38:41 -0700 Subject: [PATCH 12/40] firefox doesn't work with eddsa webauthn --- src/identity/webauthn/webauthn-identity.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index 7499712..5663258 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -1,5 +1,5 @@ import cbor from "cbor" -import { CoseKey } from "../../message/cose" +import { CoseKey, EMPTY } from "../../message/cose" import { Identity } from "../types" const CHALLENGE_BUFFER = new TextEncoder().encode("lifted") @@ -17,7 +17,7 @@ export class WebAuthnIdentity extends Identity { } static decode(publicKey: ArrayBuffer) { - return cbor.decodeAllSync(publicKey) + return cbor.decodeFirstSync(publicKey) } private getPublicKeyFromCoseKey(cosePublicKey: ArrayBuffer): ArrayBuffer { @@ -42,7 +42,7 @@ export class WebAuthnIdentity extends Identity { } async sign(_: ArrayBuffer): Promise { - return new TextEncoder().encode("webauthn") + return EMPTY } async verify(_: ArrayBuffer): Promise { @@ -83,6 +83,7 @@ export class WebAuthnIdentity extends Identity { getCoseKey(): CoseKey { let decoded = cbor.decode(this.cosePublicKey) + console.log({ coseKey: decoded }) decoded.set(4, [2]) return new CoseKey(decoded) } @@ -125,6 +126,15 @@ async function createPublicKeyCredential() { alg: -8, // alg: -7, }, + { + /* + EdDSA -8 + ES256 -7 ECDSA w/ SHA-256 + */ + type: "public-key", + // alg: -8, + alg: -7, + }, ], } From 9bcd0ec01015f17d497610b7338230f4cdb51205 Mon Sep 17 00:00:00 2001 From: Tommy Date: Fri, 6 May 2022 20:29:40 -0700 Subject: [PATCH 13/40] CoseSign1 --- package-lock.json | 11 +++++ package.json | 1 + src/identity/types.ts | 15 +++++-- src/identity/webauthn/webauthn-identity.ts | 49 +++++++++++----------- src/message/cose.ts | 24 +++++------ 5 files changed, 59 insertions(+), 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index 694cdcf..493859a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,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", @@ -3518,6 +3519,11 @@ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" }, + "node_modules/js-sha512": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", + "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7676,6 +7682,11 @@ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" }, + "js-sha512": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", + "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index d75f0e8..dc747aa 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,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", diff --git a/src/identity/types.ts b/src/identity/types.ts index 4755fc3..5fff1b9 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -1,7 +1,10 @@ import { CoseKey } from "../message/cose" export interface Signer { - sign(data: ArrayBuffer): Promise + sign( + data: ArrayBuffer, + unprotectedHeader?: Map, + ): Promise } export interface Verifier { @@ -11,10 +14,16 @@ export interface Verifier { export abstract class Identity implements Signer, Verifier { abstract publicKey: ArrayBuffer abstract toJson(): unknown - abstract sign(data: ArrayBuffer): Promise + abstract sign( + data: ArrayBuffer, + unprotectedHeader: Map, + ): Promise abstract verify(data: ArrayBuffer): Promise abstract getCoseKey(): CoseKey - async getUnprotectedHeader(_: ArrayBuffer): Promise> { + async getUnprotectedHeader( + message: ArrayBuffer, + cborProtectedHeader: ArrayBuffer, + ): Promise> { return new Map() } } diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index 5663258..12e705b 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -1,8 +1,10 @@ import cbor from "cbor" import { CoseKey, EMPTY } from "../../message/cose" import { Identity } from "../types" +const sha512 = require("js-sha512") const CHALLENGE_BUFFER = new TextEncoder().encode("lifted") +const ONE_MINUTE = 60000 export class WebAuthnIdentity extends Identity { publicKey: ArrayBuffer // x-coordinate @@ -27,22 +29,24 @@ export class WebAuthnIdentity extends Identity { static async create(): Promise { const publicKeyCredential = await createPublicKeyCredential() - const attestationResponse = - publicKeyCredential.response as AuthenticatorResponse & - AuthenticatorAttestationResponse - + const attestationResponse = publicKeyCredential?.response + if (!(attestationResponse instanceof AuthenticatorAttestationResponse)) { + throw new Error("Must be AuthenticatorAttestationResponse") + } const attestationObj = cbor.decodeFirstSync( attestationResponse.attestationObject, ) const publicKeyBytes = getPublicKeyBytesFromAuthData( attestationObj.authData, ) - return new WebAuthnIdentity(publicKeyBytes, publicKeyCredential.rawId) } - async sign(_: ArrayBuffer): Promise { - return EMPTY + async sign( + _: ArrayBuffer, + unprotectedHeader: Map, + ): Promise { + return unprotectedHeader.get("signature") as ArrayBuffer } async verify(_: ArrayBuffer): Promise { @@ -56,7 +60,8 @@ export class WebAuthnIdentity extends Identity { let credential = (await window.navigator.credentials.get({ publicKey: { challenge: challenge ?? CHALLENGE_BUFFER, - timeout: 10000, + timeout: ONE_MINUTE, + userVerification: "discouraged", allowCredentials: [ { transports: ["nfc", "usb"], @@ -69,9 +74,13 @@ export class WebAuthnIdentity extends Identity { return credential } - async getUnprotectedHeader(data: ArrayBuffer): Promise> { - const digest = await window.crypto.subtle.digest("SHA-512", data) - const cred = await WebAuthnIdentity.getCredential(this.rawId, digest) + async getUnprotectedHeader( + data: ArrayBuffer, + cborProtectedHeader: ArrayBuffer, + ): Promise> { + const digest = sha512.arrayBuffer(data) + const challenge = cbor.encodeCanonical([cborProtectedHeader, digest]) + const cred = await WebAuthnIdentity.getCredential(this.rawId, challenge) const response = cred.response as AuthenticatorAssertionResponse const m = new Map() m.set("webauthn", true) @@ -83,7 +92,6 @@ export class WebAuthnIdentity extends Identity { getCoseKey(): CoseKey { let decoded = cbor.decode(this.cosePublicKey) - console.log({ coseKey: decoded }) decoded.set(4, [2]) return new CoseKey(decoded) } @@ -96,9 +104,9 @@ export class WebAuthnIdentity extends Identity { } } -async function createPublicKeyCredential() { +async function createPublicKeyCredential(challenge = CHALLENGE_BUFFER) { const publicKey: PublicKeyCredentialCreationOptions = { - challenge: CHALLENGE_BUFFER, + challenge, rp: { name: "lifted", @@ -114,25 +122,18 @@ async function createPublicKeyCredential() { authenticatorSelection: { authenticatorAttachment: "cross-platform", + userVerification: "discouraged", }, pubKeyCredParams: [ { - /* - EdDSA -8 - ES256 -7 ECDSA w/ SHA-256 - */ + // EdDSA -8 type: "public-key", alg: -8, - // alg: -7, }, { - /* - EdDSA -8 - ES256 -7 ECDSA w/ SHA-256 - */ + // ES256 -7 ECDSA w/ SHA-256 type: "public-key", - // alg: -8, alg: -7, }, ], diff --git a/src/message/cose.ts b/src/message/cose.ts index d100d19..5c1572d 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -51,14 +51,20 @@ export class CoseMessage { identity: Identity = new AnonymousIdentity(), ): Promise { const protectedHeader = this.getProtectedHeader(identity) + const cborProtectedHeader = cbor.encodeCanonical(protectedHeader) const content = message.content const cborContent = cbor.encode(tag(10001, message.content)) - const unprotectedHeader = await identity.getUnprotectedHeader(cborContent) - const signature = await this.getSignature( - protectedHeader, + const toBeSigned = cbor.encodeCanonical([ + "Signature1", + cborProtectedHeader, + EMPTY, + cborContent, + ]) + const unprotectedHeader = await identity.getUnprotectedHeader( cborContent, - identity, + cborProtectedHeader, ) + const signature = await identity.sign(toBeSigned, unprotectedHeader) return new CoseMessage( protectedHeader, @@ -77,16 +83,6 @@ export class CoseMessage { return protectedHeader } - private static async getSignature( - protectedHeader: CborMap, - payload: ArrayBuffer, - identity: Identity, - ): Promise { - const p = cbor.encodeCanonical(protectedHeader) - const message = cbor.encodeCanonical(["Signature1", p, EMPTY, payload]) - return await identity.sign(message) - } - private replacer(key: string, value: any) { if (value?.type === "Buffer") { return Buffer.from(value.data).toString("hex") From 6440adfcecb36a9b89d743b791906e113db1ac1a Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 9 May 2022 22:30:14 -0700 Subject: [PATCH 14/40] change challenge and content --- src/identity/types.ts | 7 ++++--- src/identity/webauthn/webauthn-identity.ts | 21 +++++++++++++++------ src/message/cose.ts | 9 +++++---- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/identity/types.ts b/src/identity/types.ts index 5fff1b9..a4f080e 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -1,10 +1,11 @@ +import { CborMap } from "../message/cbor" import { CoseKey } from "../message/cose" export interface Signer { sign( data: ArrayBuffer, unprotectedHeader?: Map, - ): Promise + ): Promise } export interface Verifier { @@ -17,11 +18,11 @@ export abstract class Identity implements Signer, Verifier { abstract sign( data: ArrayBuffer, unprotectedHeader: Map, - ): Promise + ): Promise abstract verify(data: ArrayBuffer): Promise abstract getCoseKey(): CoseKey async getUnprotectedHeader( - message: ArrayBuffer, + message: CborMap, cborProtectedHeader: ArrayBuffer, ): Promise> { return new Map() diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index 12e705b..e1402e3 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -1,4 +1,5 @@ import cbor from "cbor" +import CborMap from "cbor/types/lib/map" import { CoseKey, EMPTY } from "../../message/cose" import { Identity } from "../types" const sha512 = require("js-sha512") @@ -45,8 +46,9 @@ export class WebAuthnIdentity extends Identity { async sign( _: ArrayBuffer, unprotectedHeader: Map, - ): Promise { - return unprotectedHeader.get("signature") as ArrayBuffer + ): Promise { + return null + // return unprotectedHeader.get("signature") as ArrayBuffer } async verify(_: ArrayBuffer): Promise { @@ -75,11 +77,14 @@ export class WebAuthnIdentity extends Identity { } async getUnprotectedHeader( - data: ArrayBuffer, - cborProtectedHeader: ArrayBuffer, + data: CborMap, + protectedHeader: ArrayBuffer, ): Promise> { - const digest = sha512.arrayBuffer(data) - const challenge = cbor.encodeCanonical([cborProtectedHeader, digest]) + const c = new Map() + c.set(0, protectedHeader) + c.set(1, data) + // const digest = sha512.arrayBuffer(data) + const challenge = cbor.encode(c) const cred = await WebAuthnIdentity.getCredential(this.rawId, challenge) const response = cred.response as AuthenticatorAssertionResponse const m = new Map() @@ -96,6 +101,10 @@ export class WebAuthnIdentity extends Identity { return new CoseKey(decoded) } + getContent(content: CborMap) { + return sha512.arrayBuffer(cbor.encode(content)) + } + toJson(): { rawId: string; cosePublicKey: ArrayBuffer } { return { rawId: Buffer.from(this.rawId).toString("base64"), diff --git a/src/message/cose.ts b/src/message/cose.ts index 5c1572d..f53648b 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -11,13 +11,13 @@ export class CoseMessage { protectedHeader: CborMap unprotectedHeader: CborMap content: CborMap - signature: ArrayBuffer + signature: ArrayBuffer | null constructor( protectedHeader: CborMap, unprotectedHeader: CborMap, content: CborMap, - signature: ArrayBuffer, + signature: ArrayBuffer | null, ) { this.protectedHeader = protectedHeader this.unprotectedHeader = unprotectedHeader @@ -61,7 +61,7 @@ export class CoseMessage { cborContent, ]) const unprotectedHeader = await identity.getUnprotectedHeader( - cborContent, + content, cborProtectedHeader, ) const signature = await identity.sign(toBeSigned, unprotectedHeader) @@ -69,7 +69,8 @@ export class CoseMessage { return new CoseMessage( protectedHeader, unprotectedHeader, - content, + // @ts-ignore + identity?.getContent ? identity.getContent(content) : content, signature, ) } From 43500472bd125304be7fc9289cf305585407c4e8 Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 10 May 2022 10:43:53 -0700 Subject: [PATCH 15/40] cbor content --- src/identity/webauthn/webauthn-identity.ts | 17 ++++++----------- src/message/cose.ts | 3 +-- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index e1402e3..6ecc2ef 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -43,12 +43,8 @@ export class WebAuthnIdentity extends Identity { return new WebAuthnIdentity(publicKeyBytes, publicKeyCredential.rawId) } - async sign( - _: ArrayBuffer, - unprotectedHeader: Map, - ): Promise { - return null - // return unprotectedHeader.get("signature") as ArrayBuffer + async sign(): Promise { + return EMPTY } async verify(_: ArrayBuffer): Promise { @@ -82,7 +78,10 @@ export class WebAuthnIdentity extends Identity { ): Promise> { const c = new Map() c.set(0, protectedHeader) - c.set(1, data) + c.set( + 1, + Buffer.from(sha512.arrayBuffer(cbor.encode(data))).toString("base64"), + ) // const digest = sha512.arrayBuffer(data) const challenge = cbor.encode(c) const cred = await WebAuthnIdentity.getCredential(this.rawId, challenge) @@ -101,10 +100,6 @@ export class WebAuthnIdentity extends Identity { return new CoseKey(decoded) } - getContent(content: CborMap) { - return sha512.arrayBuffer(cbor.encode(content)) - } - toJson(): { rawId: string; cosePublicKey: ArrayBuffer } { return { rawId: Buffer.from(this.rawId).toString("base64"), diff --git a/src/message/cose.ts b/src/message/cose.ts index f53648b..2968d73 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -69,8 +69,7 @@ export class CoseMessage { return new CoseMessage( protectedHeader, unprotectedHeader, - // @ts-ignore - identity?.getContent ? identity.getContent(content) : content, + content, signature, ) } From 9f1aaa4f08473d724836161bd7acfb1209e87c9f Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 10 May 2022 11:03:49 -0700 Subject: [PATCH 16/40] unprotectedHeaders --- src/identity/types.ts | 4 ++-- src/identity/webauthn/webauthn-identity.ts | 10 +++------- src/message/cose.ts | 4 ++-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/identity/types.ts b/src/identity/types.ts index a4f080e..25dfdd9 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -22,8 +22,8 @@ export abstract class Identity implements Signer, Verifier { abstract verify(data: ArrayBuffer): Promise abstract getCoseKey(): CoseKey async getUnprotectedHeader( - message: CborMap, - cborProtectedHeader: ArrayBuffer, + cborMessageContent: ArrayBuffer, + protectedHeader: CborMap, ): Promise> { return new Map() } diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index 6ecc2ef..ec5a833 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -73,16 +73,12 @@ export class WebAuthnIdentity extends Identity { } async getUnprotectedHeader( - data: CborMap, - protectedHeader: ArrayBuffer, + cborMessageContent: ArrayBuffer, + protectedHeader: CborMap, ): Promise> { const c = new Map() c.set(0, protectedHeader) - c.set( - 1, - Buffer.from(sha512.arrayBuffer(cbor.encode(data))).toString("base64"), - ) - // const digest = sha512.arrayBuffer(data) + c.set(1, sha512.arrayBuffer(cborMessageContent).toString("base64")) const challenge = cbor.encode(c) const cred = await WebAuthnIdentity.getCredential(this.rawId, challenge) const response = cred.response as AuthenticatorAssertionResponse diff --git a/src/message/cose.ts b/src/message/cose.ts index 2968d73..4ceadce 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -61,8 +61,8 @@ export class CoseMessage { cborContent, ]) const unprotectedHeader = await identity.getUnprotectedHeader( - content, - cborProtectedHeader, + cborContent, + protectedHeader, ) const signature = await identity.sign(toBeSigned, unprotectedHeader) From efb52dcd1ef743536e5fcd8e982860a04ff894c6 Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 10 May 2022 11:23:53 -0700 Subject: [PATCH 17/40] cbor protected headers --- src/identity/types.ts | 2 +- src/identity/webauthn/webauthn-identity.ts | 4 ++-- src/message/cose.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/identity/types.ts b/src/identity/types.ts index 25dfdd9..9c8ef00 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -23,7 +23,7 @@ export abstract class Identity implements Signer, Verifier { abstract getCoseKey(): CoseKey async getUnprotectedHeader( cborMessageContent: ArrayBuffer, - protectedHeader: CborMap, + cborProtectedHeader: ArrayBuffer, ): Promise> { return new Map() } diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index ec5a833..a63c9cd 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -74,10 +74,10 @@ export class WebAuthnIdentity extends Identity { async getUnprotectedHeader( cborMessageContent: ArrayBuffer, - protectedHeader: CborMap, + cborProtectedHeader: ArrayBuffer, ): Promise> { const c = new Map() - c.set(0, protectedHeader) + c.set(0, cborProtectedHeader) c.set(1, sha512.arrayBuffer(cborMessageContent).toString("base64")) const challenge = cbor.encode(c) const cred = await WebAuthnIdentity.getCredential(this.rawId, challenge) diff --git a/src/message/cose.ts b/src/message/cose.ts index 4ceadce..8b29624 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -62,7 +62,7 @@ export class CoseMessage { ]) const unprotectedHeader = await identity.getUnprotectedHeader( cborContent, - protectedHeader, + cborProtectedHeader, ) const signature = await identity.sign(toBeSigned, unprotectedHeader) From 24d1cfd579ab7ecfb81c9e4b4c88674e9c777346 Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 10 May 2022 11:29:36 -0700 Subject: [PATCH 18/40] challenge --- src/identity/webauthn/webauthn-identity.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index a63c9cd..3ac7c44 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -78,7 +78,10 @@ export class WebAuthnIdentity extends Identity { ): Promise> { const c = new Map() c.set(0, cborProtectedHeader) - c.set(1, sha512.arrayBuffer(cborMessageContent).toString("base64")) + c.set( + 1, + Buffer.from(sha512.arrayBuffer(cborMessageContent)).toString("base64"), + ) const challenge = cbor.encode(c) const cred = await WebAuthnIdentity.getCredential(this.rawId, challenge) const response = cred.response as AuthenticatorAssertionResponse From 0e6e49478b80168beabe2b29288045a710abdfc9 Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 10 May 2022 14:31:20 -0700 Subject: [PATCH 19/40] cosepublickey --- src/identity/webauthn/webauthn-identity.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index 3ac7c44..02404ae 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -1,5 +1,4 @@ import cbor from "cbor" -import CborMap from "cbor/types/lib/map" import { CoseKey, EMPTY } from "../../message/cose" import { Identity } from "../types" const sha512 = require("js-sha512") @@ -37,10 +36,8 @@ export class WebAuthnIdentity extends Identity { const attestationObj = cbor.decodeFirstSync( attestationResponse.attestationObject, ) - const publicKeyBytes = getPublicKeyBytesFromAuthData( - attestationObj.authData, - ) - return new WebAuthnIdentity(publicKeyBytes, publicKeyCredential.rawId) + const cosePublicKey = getCosePublicKey(attestationObj.authData) + return new WebAuthnIdentity(cosePublicKey, publicKeyCredential.rawId) } async sign(): Promise { @@ -147,7 +144,7 @@ async function createPublicKeyCredential(challenge = CHALLENGE_BUFFER) { })) as PublicKeyCredential } -function getPublicKeyBytesFromAuthData(authData: ArrayBuffer): ArrayBuffer { +function getCosePublicKey(authData: ArrayBuffer): ArrayBuffer { const dataView = new DataView(new ArrayBuffer(2)) const idLenBytes = authData.slice(53, 55) // @ts-ignore From 0a4000677b240f470df5ff24b4f16c590cae7b0e Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 10 May 2022 14:38:33 -0700 Subject: [PATCH 20/40] remove dep --- package-lock.json | 242 ---------------------------------------------- package.json | 1 - 2 files changed, 243 deletions(-) diff --git a/package-lock.json b/package-lock.json index 493859a..fae3738 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,6 @@ "base32-encode": "^1.2.0", "bip39": "^3.0.4", "cbor": "^8.1.0", - "cose-js": "^0.8.4", "crc": "^3.8.0", "jest": "^27.4.5", "js-sha3": "^0.8.0", @@ -1261,11 +1260,6 @@ "node": ">=0.4.0" } }, - "node_modules/aes-cbc-mac": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/aes-cbc-mac/-/aes-cbc-mac-1.0.1.tgz", - "integrity": "sha512-F1U0qNBNyrW82LRdQvYWKOpljZFnJ9LBUGWNCzCBTC3/+Fki77KgDrfJ+rBVlCpcmMT3jDEGhG61RMVeyHAKog==" - }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1340,11 +1334,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -1371,14 +1360,6 @@ "node": ">=8" } }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1524,11 +1505,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" }, - "node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1549,11 +1525,6 @@ "node": ">=8" } }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, "node_modules/browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -1775,22 +1746,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "node_modules/cose-js": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/cose-js/-/cose-js-0.8.4.tgz", - "integrity": "sha512-TYt82olRQS/iZyb/qchG4KZSnzVBlOVXJjTCCgwKZUIkqqFyUIA+JG8OQdX5+ZyiWLj9W118Kuf3/jII0Gb/Bg==", - "dependencies": { - "aes-cbc-mac": "^1.0.1", - "any-promise": "^1.3.0", - "cbor": "^8.1.0", - "elliptic": "^6.4.0", - "node-hkdf-sync": "^1.0.0", - "node-rsa": "^1.1.1" - }, - "engines": { - "node": ">=12.0" - } - }, "node_modules/crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", @@ -1981,20 +1936,6 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz", "integrity": "sha512-LxgUNeu3BVU7sXaKjUDD9xivocQLxFtq6wgERrutdY/yIOps3ODOZExK1jg8DTEg4U8TUCb5MLGeWFOYuxjF3Q==" }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "node_modules/emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -2317,14 +2258,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/eyes": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", - "engines": { - "node": "> 0.1.90" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2616,25 +2549,6 @@ "node": ">=4" } }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "node_modules/html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -3797,16 +3711,6 @@ "node": ">=6" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3841,17 +3745,6 @@ "node": ">= 6.0.0" } }, - "node_modules/node-hkdf-sync": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-hkdf-sync/-/node-hkdf-sync-1.0.0.tgz", - "integrity": "sha1-ZX15hkHAA/kQN7aKNZXgW+c22Zo=", - "dependencies": { - "vows": "0.5.13" - }, - "engines": { - "node": ">= 0.6.5" - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -3862,14 +3755,6 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" }, - "node_modules/node-rsa": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", - "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", - "dependencies": { - "asn1": "^0.2.4" - } - }, "node_modules/nofilter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", @@ -4836,20 +4721,6 @@ "node": ">= 8" } }, - "node_modules/vows": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/vows/-/vows-0.5.13.tgz", - "integrity": "sha1-9v2e6cNtPyC9ZoBFXP+AkMSynN4=", - "dependencies": { - "eyes": ">=0.1.6" - }, - "bin": { - "vows": "bin/vows" - }, - "engines": { - "node": ">=0.2.6" - } - }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -5980,11 +5851,6 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" }, - "aes-cbc-mac": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/aes-cbc-mac/-/aes-cbc-mac-1.0.1.tgz", - "integrity": "sha512-F1U0qNBNyrW82LRdQvYWKOpljZFnJ9LBUGWNCzCBTC3/+Fki77KgDrfJ+rBVlCpcmMT3jDEGhG61RMVeyHAKog==" - }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -6033,11 +5899,6 @@ "color-convert": "^2.0.1" } }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -6058,14 +5919,6 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -6178,11 +6031,6 @@ } } }, - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6200,11 +6048,6 @@ "fill-range": "^7.0.1" } }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, "browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -6369,19 +6212,6 @@ } } }, - "cose-js": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/cose-js/-/cose-js-0.8.4.tgz", - "integrity": "sha512-TYt82olRQS/iZyb/qchG4KZSnzVBlOVXJjTCCgwKZUIkqqFyUIA+JG8OQdX5+ZyiWLj9W118Kuf3/jII0Gb/Bg==", - "requires": { - "aes-cbc-mac": "^1.0.1", - "any-promise": "^1.3.0", - "cbor": "^8.1.0", - "elliptic": "^6.4.0", - "node-hkdf-sync": "^1.0.0", - "node-rsa": "^1.1.1" - } - }, "crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", @@ -6535,20 +6365,6 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz", "integrity": "sha512-LxgUNeu3BVU7sXaKjUDD9xivocQLxFtq6wgERrutdY/yIOps3ODOZExK1jg8DTEg4U8TUCb5MLGeWFOYuxjF3Q==" }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -6778,11 +6594,6 @@ "jest-message-util": "^27.5.1" } }, - "eyes": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7003,25 +6814,6 @@ "safe-buffer": "^5.2.0" } }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -7897,16 +7689,6 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -7935,14 +7717,6 @@ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" }, - "node-hkdf-sync": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-hkdf-sync/-/node-hkdf-sync-1.0.0.tgz", - "integrity": "sha1-ZX15hkHAA/kQN7aKNZXgW+c22Zo=", - "requires": { - "vows": "0.5.13" - } - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -7953,14 +7727,6 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" }, - "node-rsa": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", - "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", - "requires": { - "asn1": "^0.2.4" - } - }, "nofilter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", @@ -8620,14 +8386,6 @@ } } }, - "vows": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/vows/-/vows-0.5.13.tgz", - "integrity": "sha1-9v2e6cNtPyC9ZoBFXP+AkMSynN4=", - "requires": { - "eyes": ">=0.1.6" - } - }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", diff --git a/package.json b/package.json index dc747aa..2183853 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "base32-encode": "^1.2.0", "bip39": "^3.0.4", "cbor": "^8.1.0", - "cose-js": "^0.8.4", "crc": "^3.8.0", "jest": "^27.4.5", "js-sha3": "^0.8.0", From 50edc040e6f679e31cca9c8a3cd9bb6db1dc9333 Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 10 May 2022 14:40:06 -0700 Subject: [PATCH 21/40] remove comment --- src/identity/webauthn/webauthn-identity.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index 02404ae..139d127 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -151,6 +151,5 @@ function getCosePublicKey(authData: ArrayBuffer): ArrayBuffer { idLenBytes.forEach((value, index) => dataView.setUint8(index, value)) const credentialIdLength = dataView.getUint16(0) const cosePublicKey = authData.slice(55 + credentialIdLength) - // const publicKeyObject = cbor.decodeFirstSync(cosePublicKey) return cosePublicKey } From e1ea49b14c46724590141fbe0aa86400a4687c1c Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 10 May 2022 14:44:17 -0700 Subject: [PATCH 22/40] remove dep --- package-lock.json | 17 ----------------- package.json | 1 - 2 files changed, 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index fae3738..55c0a88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "hasInstallScript": true, "dependencies": { - "@types/cose-js": "^0.8.0", "@types/crc": "^3.4.0", "@types/jest": "^27.0.3", "@types/node": "^12.20.37", @@ -1016,14 +1015,6 @@ "@babel/types": "^7.3.0" } }, - "node_modules/@types/cose-js": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@types/cose-js/-/cose-js-0.8.0.tgz", - "integrity": "sha512-eDA3px4atnv5EP5/SlLPHboh9qQNeqCNZ3myy7gNt2ydCQpglivg0lfkk99jhJUg+JuodXCzuQVcUBvrxamvuQ==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/crc": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@types/crc/-/crc-3.4.0.tgz", @@ -5670,14 +5661,6 @@ "@babel/types": "^7.3.0" } }, - "@types/cose-js": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@types/cose-js/-/cose-js-0.8.0.tgz", - "integrity": "sha512-eDA3px4atnv5EP5/SlLPHboh9qQNeqCNZ3myy7gNt2ydCQpglivg0lfkk99jhJUg+JuodXCzuQVcUBvrxamvuQ==", - "requires": { - "@types/node": "*" - } - }, "@types/crc": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@types/crc/-/crc-3.4.0.tgz", diff --git a/package.json b/package.json index 2183853..b0fd636 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "private": true, "main": "dist/index.js", "dependencies": { - "@types/cose-js": "^0.8.0", "@types/crc": "^3.4.0", "@types/jest": "^27.0.3", "@types/node": "^12.20.37", From 00f0172e814affcca321efa313fbf1fa93ad2e5d Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 10 May 2022 14:49:12 -0700 Subject: [PATCH 23/40] remove comment --- src/network/network.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/network/network.ts b/src/network/network.ts index 9101474..d71e8f3 100644 --- a/src/network/network.ts +++ b/src/network/network.ts @@ -40,10 +40,7 @@ export class Network { async call(method: string, data?: any) { const req = Message.fromObject({ method, - from: this.identity - ? // ? Address.fromPublicKey(this.identity?.publicKey) - Address.fromIdentity(this.identity) - : undefined, + from: this.identity ? Address.fromIdentity(this.identity) : undefined, data, }) return await this.send(req) From 09ff25368d6de6716754367cc9ac0f399a275b2c Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 10 May 2022 15:05:14 -0700 Subject: [PATCH 24/40] remove method --- src/identity/webauthn/webauthn-identity.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index 139d127..5a62527 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -18,10 +18,6 @@ export class WebAuthnIdentity extends Identity { this.rawId = rawId } - static decode(publicKey: ArrayBuffer) { - return cbor.decodeFirstSync(publicKey) - } - private getPublicKeyFromCoseKey(cosePublicKey: ArrayBuffer): ArrayBuffer { const decoded = cbor.decodeFirstSync(cosePublicKey) return decoded.get(-2) From 401260334415dc0ddab71da0edec370bb217e5c8 Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 10 May 2022 22:11:03 -0700 Subject: [PATCH 25/40] tests --- .../__tests__/ed25519-identity.test.ts | 41 +++++++++++ .../ed25519/ed25519-key-pair-identity.ts | 29 +++++++- .../__tests__/webauthn-identity.test.ts | 70 +++++++++++++++++++ src/identity/webauthn/webauthn-identity.ts | 4 +- 4 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 src/identity/ed25519/__tests__/ed25519-identity.test.ts create mode 100644 src/identity/webauthn/__tests__/webauthn-identity.test.ts diff --git a/src/identity/ed25519/__tests__/ed25519-identity.test.ts b/src/identity/ed25519/__tests__/ed25519-identity.test.ts new file mode 100644 index 0000000..2c6f317 --- /dev/null +++ b/src/identity/ed25519/__tests__/ed25519-identity.test.ts @@ -0,0 +1,41 @@ +import { Ed25519KeyPairIdentity } from "../ed25519-key-pair-identity" + +describe("keys", () => { + test("getSeedWords", () => { + const seedWords = Ed25519KeyPairIdentity.getMnemonic() + + expect(seedWords.split(" ")).toHaveLength(12) + }) + + test("fromSeedWords", () => { + const seedWords = Ed25519KeyPairIdentity.getMnemonic() + const badWords = "abandon abandon abandon" + + const alice = Ed25519KeyPairIdentity.fromMnemonic(seedWords) + const bob = Ed25519KeyPairIdentity.fromMnemonic(seedWords) + + expect(alice.privateKey).toStrictEqual(bob.privateKey) + expect(() => { + Ed25519KeyPairIdentity.fromMnemonic(badWords) + }).toThrow() + }) + + test("fromPem", () => { + 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) + + expect(alice.privateKey).toStrictEqual(bob.privateKey) + expect(() => { + Ed25519KeyPairIdentity.fromPem(badPem) + }).toThrow() + }) +}) diff --git a/src/identity/ed25519/ed25519-key-pair-identity.ts b/src/identity/ed25519/ed25519-key-pair-identity.ts index 44cf5e6..8353b0c 100644 --- a/src/identity/ed25519/ed25519-key-pair-identity.ts +++ b/src/identity/ed25519/ed25519-key-pair-identity.ts @@ -1,4 +1,5 @@ -import { pki } from "node-forge" +import forge, { pki } from "node-forge" +import * as bip39 from "bip39" import { CoseKey } from "../../message/cose" const ed25519 = pki.ed25519 import { KeyPairIdentity } from "../types" @@ -13,6 +14,32 @@ export class Ed25519KeyPairIdentity extends KeyPairIdentity { this.privateKey = privateKey } + static getMnemonic(): string { + return bip39.generateMnemonic() + } + + 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 { return ed25519.sign({ message: data as Uint8Array, diff --git a/src/identity/webauthn/__tests__/webauthn-identity.test.ts b/src/identity/webauthn/__tests__/webauthn-identity.test.ts new file mode 100644 index 0000000..d684d45 --- /dev/null +++ b/src/identity/webauthn/__tests__/webauthn-identity.test.ts @@ -0,0 +1,70 @@ +import cbor from "cbor" +import { tag } from "../../../message/cbor" +import { CoseKey } from "../../../message/cose" +import * as WebAuthnIdentityModule from "../webauthn-identity" + +const { WebAuthnIdentity } = WebAuthnIdentityModule + +describe("WebAuthnIdentity", () => { + it("should have static methods", () => { + expect(typeof WebAuthnIdentity.create).toBe("function") + expect(typeof WebAuthnIdentity.getCredential).toBe("function") + }) + it("getCoseKey()", () => { + const publicKeyMap = new Map([ + [1, 1], + [3, -8], + [-1, 6], + [-2, new Uint8Array(32)], + ]) + const identity = setup(publicKeyMap) + const coseKey = identity.getCoseKey() + expect(coseKey instanceof CoseKey).toBe(true) + }) + it("getUnprotectedHeader()", async () => { + const getCredentialMock = jest + .spyOn(WebAuthnIdentity, "getCredential") + .mockImplementationOnce(async () => { + return { + rawId: new ArrayBuffer(32), + response: { + authenticatorData: "mockAuthData", + clientDataJSON: Buffer.from("clientDataJSON"), + signature: "mockSignature", + } as AuthenticatorResponse, + } as PublicKeyCredential + }) + const cborMessageContent = cbor.encode( + tag( + 10001, + new Map([ + [1, "maexhjte7fss6cqg4hznyyqnn65pxdqrqbvjz6ocf7tl57zawf"], + [3, "ledger.info"], + ]), + ), + ) + const cborProtectedHeader = cbor.encode( + new Map([ + [1, "alg"], + [4, "kid"], + ]), + ) + const identity = setup() + const unprotectedHeader = await identity.getUnprotectedHeader( + cborMessageContent, + cborProtectedHeader, + ) + expect(getCredentialMock).toHaveBeenCalledTimes(1) + expect(unprotectedHeader.get("webauthn")).toBe(true) + expect(unprotectedHeader.get("authData")).toBe("mockAuthData") + expect(unprotectedHeader.get("clientData")).toBe("clientDataJSON") + expect(unprotectedHeader.get("signature")).toBe("mockSignature") + }) +}) + +function setup(publicKeyMap?: Map) { + const publicKey = Buffer.from(new ArrayBuffer(32)) + const cosePublicKey = cbor.encode(publicKeyMap ?? new Map([[-2, publicKey]])) + const rawId = new ArrayBuffer(32) + return new WebAuthnIdentity(cosePublicKey, rawId) +} diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index 5a62527..c6e4cad 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -7,7 +7,7 @@ const CHALLENGE_BUFFER = new TextEncoder().encode("lifted") const ONE_MINUTE = 60000 export class WebAuthnIdentity extends Identity { - publicKey: ArrayBuffer // x-coordinate + publicKey: ArrayBuffer rawId: ArrayBuffer cosePublicKey: ArrayBuffer @@ -100,7 +100,7 @@ export class WebAuthnIdentity extends Identity { } } -async function createPublicKeyCredential(challenge = CHALLENGE_BUFFER) { +export async function createPublicKeyCredential(challenge = CHALLENGE_BUFFER) { const publicKey: PublicKeyCredentialCreationOptions = { challenge, From bf050999e7ff74013ad8087959362dbeca451b0b Mon Sep 17 00:00:00 2001 From: Tommy Date: Wed, 11 May 2022 17:01:42 -0700 Subject: [PATCH 26/40] sign should return array buffer --- src/identity/types.ts | 4 ++-- src/message/cose.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/identity/types.ts b/src/identity/types.ts index 9c8ef00..9ae2981 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -5,7 +5,7 @@ export interface Signer { sign( data: ArrayBuffer, unprotectedHeader?: Map, - ): Promise + ): Promise } export interface Verifier { @@ -18,7 +18,7 @@ export abstract class Identity implements Signer, Verifier { abstract sign( data: ArrayBuffer, unprotectedHeader: Map, - ): Promise + ): Promise abstract verify(data: ArrayBuffer): Promise abstract getCoseKey(): CoseKey async getUnprotectedHeader( diff --git a/src/message/cose.ts b/src/message/cose.ts index 8b29624..5c1572d 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -11,13 +11,13 @@ export class CoseMessage { protectedHeader: CborMap unprotectedHeader: CborMap content: CborMap - signature: ArrayBuffer | null + signature: ArrayBuffer constructor( protectedHeader: CborMap, unprotectedHeader: CborMap, content: CborMap, - signature: ArrayBuffer | null, + signature: ArrayBuffer, ) { this.protectedHeader = protectedHeader this.unprotectedHeader = unprotectedHeader From 6d4ad3d23c0bc80863f6cc68764f388b758d62bf Mon Sep 17 00:00:00 2001 From: Tommy Date: Fri, 13 May 2022 09:33:39 -0700 Subject: [PATCH 27/40] updates types and getProtectedHeader --- src/identity/address.ts | 9 -------- src/identity/anonymous/anonymous-identity.ts | 16 ++++--------- .../ed25519/ed25519-key-pair-identity.ts | 10 ++++++-- src/identity/types.ts | 12 ++++++---- src/identity/webauthn/webauthn-identity.ts | 9 ++++++-- src/message/cose.ts | 23 +++++++++++++------ src/network/network.ts | 2 +- 7 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/identity/address.ts b/src/identity/address.ts index 87ca910..3b74a8c 100644 --- a/src/identity/address.ts +++ b/src/identity/address.ts @@ -2,10 +2,6 @@ import base32Decode from "base32-decode" import base32Encode from "base32-encode" import crc from "crc" -import { Key } from "../keys" -import { CoseKey } from "../message/cose" -import { Identity } from "./types" - export const ANON_IDENTITY = "maa" export class Address { bytes: Uint8Array @@ -22,11 +18,6 @@ export class Address { return new Address(Buffer.from(hex, "hex")) } - static fromIdentity(i: Identity): Address { - const coseKey = i.getCoseKey() - return coseKey.toAddress() - } - static fromString(string: string): Address { if (string === ANON_IDENTITY) { return new Address() diff --git a/src/identity/anonymous/anonymous-identity.ts b/src/identity/anonymous/anonymous-identity.ts index b48ac08..103c9c8 100644 --- a/src/identity/anonymous/anonymous-identity.ts +++ b/src/identity/anonymous/anonymous-identity.ts @@ -1,8 +1,8 @@ import { Identity } from "../types" -import { ANONYMOUS, CoseKey, EMPTY } from "../../message/cose" +import { EMPTY } from "../../message/cose" +import { Address } from "../address" export class AnonymousIdentity extends Identity { - publicKey = ANONYMOUS async sign() { return EMPTY } @@ -10,17 +10,11 @@ export class AnonymousIdentity extends Identity { 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) + async getAddress(): Promise
{ + return Address.anonymous() } toJson() { - return this.publicKey + return AnonymousIdentity.name } } diff --git a/src/identity/ed25519/ed25519-key-pair-identity.ts b/src/identity/ed25519/ed25519-key-pair-identity.ts index 8353b0c..288977b 100644 --- a/src/identity/ed25519/ed25519-key-pair-identity.ts +++ b/src/identity/ed25519/ed25519-key-pair-identity.ts @@ -2,9 +2,10 @@ import forge, { pki } from "node-forge" import * as bip39 from "bip39" import { CoseKey } from "../../message/cose" const ed25519 = pki.ed25519 -import { KeyPairIdentity } from "../types" +import { PrivateKeyIdentity } from "../types" +import { Address } from "../address" -export class Ed25519KeyPairIdentity extends KeyPairIdentity { +export class Ed25519KeyPairIdentity extends PrivateKeyIdentity { publicKey: ArrayBuffer privateKey: ArrayBuffer @@ -18,6 +19,11 @@ export class Ed25519KeyPairIdentity extends KeyPairIdentity { return bip39.generateMnemonic() } + async getAddress(): Promise
{ + const coseKey = this.getCoseKey() + return coseKey.toAddress() + } + static fromMnemonic(mnemonic: string): Ed25519KeyPairIdentity { const sanitized = mnemonic.trim().split(/\s+/g).join(" ") if (!bip39.validateMnemonic(sanitized)) { diff --git a/src/identity/types.ts b/src/identity/types.ts index 9ae2981..d87d094 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -1,5 +1,5 @@ -import { CborMap } from "../message/cbor" import { CoseKey } from "../message/cose" +import { Address } from "./address" export interface Signer { sign( @@ -13,14 +13,13 @@ export interface Verifier { } export abstract class Identity implements Signer, Verifier { - abstract publicKey: ArrayBuffer + abstract getAddress(): Promise
abstract toJson(): unknown abstract sign( data: ArrayBuffer, unprotectedHeader: Map, ): Promise abstract verify(data: ArrayBuffer): Promise - abstract getCoseKey(): CoseKey async getUnprotectedHeader( cborMessageContent: ArrayBuffer, cborProtectedHeader: ArrayBuffer, @@ -29,6 +28,11 @@ export abstract class Identity implements Signer, Verifier { } } -export abstract class KeyPairIdentity extends Identity { +export abstract class PublicKeyIdentity extends Identity { + abstract publicKey: ArrayBuffer + abstract getCoseKey(): CoseKey +} + +export abstract class PrivateKeyIdentity extends PublicKeyIdentity { abstract privateKey: ArrayBuffer } diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index c6e4cad..222a2ef 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -1,12 +1,13 @@ import cbor from "cbor" import { CoseKey, EMPTY } from "../../message/cose" -import { Identity } from "../types" +import { Address } from "../address" +import { PublicKeyIdentity } from "../types" const sha512 = require("js-sha512") const CHALLENGE_BUFFER = new TextEncoder().encode("lifted") const ONE_MINUTE = 60000 -export class WebAuthnIdentity extends Identity { +export class WebAuthnIdentity extends PublicKeyIdentity { publicKey: ArrayBuffer rawId: ArrayBuffer cosePublicKey: ArrayBuffer @@ -18,6 +19,10 @@ export class WebAuthnIdentity extends Identity { this.rawId = rawId } + async getAddress(): Promise
{ + return this.getCoseKey().toAddress() + } + private getPublicKeyFromCoseKey(cosePublicKey: ArrayBuffer): ArrayBuffer { const decoded = cbor.decodeFirstSync(cosePublicKey) return decoded.get(-2) diff --git a/src/message/cose.ts b/src/message/cose.ts index 5c1572d..eaf2915 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -1,12 +1,17 @@ import cbor from "cbor"; import { sha3_224 } from "js-sha3"; -import { Address, AnonymousIdentity, Identity } from "../identity" +import { + Address, + AnonymousIdentity, + Identity, + PrivateKeyIdentity, + PublicKeyIdentity, +} from "../identity" import { Message } from "../message" import { CborData, CborMap, tag } from "./cbor" export const ANONYMOUS = Buffer.from([0x00]) export const EMPTY = Buffer.alloc(0) - export class CoseMessage { protectedHeader: CborMap unprotectedHeader: CborMap @@ -74,12 +79,16 @@ export class CoseMessage { ) } - private static getProtectedHeader(identity: Identity): CborMap { - const coseKey = identity.getCoseKey() + private static getProtectedHeader( + identity: PublicKeyIdentity | PrivateKeyIdentity | Identity, + ): CborMap { const protectedHeader = new Map() - protectedHeader.set(1, coseKey.key.get(3)) // alg - protectedHeader.set(4, coseKey.keyId) // kid: kid - protectedHeader.set("keyset", coseKey.toCborData()) + if ("getCoseKey" in identity) { + const coseKey = identity.getCoseKey() + protectedHeader.set(1, coseKey.key.get(3)) // alg + protectedHeader.set(4, coseKey.keyId) // kid: kid + protectedHeader.set("keyset", coseKey.toCborData()) + } return protectedHeader } diff --git a/src/network/network.ts b/src/network/network.ts index d71e8f3..d9a988b 100644 --- a/src/network/network.ts +++ b/src/network/network.ts @@ -40,7 +40,7 @@ export class Network { async call(method: string, data?: any) { const req = Message.fromObject({ method, - from: this.identity ? Address.fromIdentity(this.identity) : undefined, + from: this.identity ? await this.identity.getAddress() : undefined, data, }) return await this.send(req) From 30952675fce946dbdc27168d4b1055b4a26ea87f Mon Sep 17 00:00:00 2001 From: Tommy Date: Fri, 13 May 2022 09:50:32 -0700 Subject: [PATCH 28/40] protected private key --- .../ed25519/__tests__/ed25519-identity.test.ts | 14 ++++++++++---- src/identity/ed25519/ed25519-key-pair-identity.ts | 2 +- src/identity/types.ts | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/identity/ed25519/__tests__/ed25519-identity.test.ts b/src/identity/ed25519/__tests__/ed25519-identity.test.ts index 2c6f317..da6f2e0 100644 --- a/src/identity/ed25519/__tests__/ed25519-identity.test.ts +++ b/src/identity/ed25519/__tests__/ed25519-identity.test.ts @@ -7,20 +7,23 @@ describe("keys", () => { expect(seedWords.split(" ")).toHaveLength(12) }) - test("fromSeedWords", () => { + test("fromSeedWords", async function () { const seedWords = Ed25519KeyPairIdentity.getMnemonic() const badWords = "abandon abandon abandon" const alice = Ed25519KeyPairIdentity.fromMnemonic(seedWords) const bob = Ed25519KeyPairIdentity.fromMnemonic(seedWords) - expect(alice.privateKey).toStrictEqual(bob.privateKey) + const aliceAddress = (await alice.getAddress()).toString() + const bobAddress = (await bob.getAddress()).toString() + + expect(aliceAddress).toStrictEqual(bobAddress) expect(() => { Ed25519KeyPairIdentity.fromMnemonic(badWords) }).toThrow() }) - test("fromPem", () => { + test("fromPem", async function () { const pem = ` -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEICT3i6WfLx4t3UF6R8aEfczyATc/jvqvOrNga2MJfA2R @@ -33,7 +36,10 @@ describe("keys", () => { const alice = Ed25519KeyPairIdentity.fromPem(pem) const bob = Ed25519KeyPairIdentity.fromPem(pem) - expect(alice.privateKey).toStrictEqual(bob.privateKey) + const aliceAddress = (await alice.getAddress()).toString() + const bobAddress = (await bob.getAddress()).toString() + + expect(aliceAddress).toStrictEqual(bobAddress) expect(() => { Ed25519KeyPairIdentity.fromPem(badPem) }).toThrow() diff --git a/src/identity/ed25519/ed25519-key-pair-identity.ts b/src/identity/ed25519/ed25519-key-pair-identity.ts index 288977b..3b14941 100644 --- a/src/identity/ed25519/ed25519-key-pair-identity.ts +++ b/src/identity/ed25519/ed25519-key-pair-identity.ts @@ -7,7 +7,7 @@ import { Address } from "../address" export class Ed25519KeyPairIdentity extends PrivateKeyIdentity { publicKey: ArrayBuffer - privateKey: ArrayBuffer + protected privateKey: ArrayBuffer constructor(publicKey: ArrayBuffer, privateKey: ArrayBuffer) { super() diff --git a/src/identity/types.ts b/src/identity/types.ts index d87d094..c3d609a 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -34,5 +34,5 @@ export abstract class PublicKeyIdentity extends Identity { } export abstract class PrivateKeyIdentity extends PublicKeyIdentity { - abstract privateKey: ArrayBuffer + protected abstract privateKey: ArrayBuffer } From 046d77e00b52b05e19958cd7a61f51df23dbaf25 Mon Sep 17 00:00:00 2001 From: Tommy Date: Wed, 18 May 2022 13:09:42 -0700 Subject: [PATCH 29/40] adds idStore --- src/identity/webauthn/webauthn-identity.ts | 16 ++--- src/network/modules/id-store/id-store.ts | 77 ++++++++++++++++++++++ src/network/modules/id-store/index.ts | 1 + src/network/modules/index.ts | 1 + 4 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 src/network/modules/id-store/id-store.ts create mode 100644 src/network/modules/id-store/index.ts diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index 222a2ef..a96f20a 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -15,7 +15,7 @@ export class WebAuthnIdentity extends PublicKeyIdentity { constructor(cosePublicKey: ArrayBuffer, rawId: ArrayBuffer) { super() this.cosePublicKey = cosePublicKey - this.publicKey = this.getPublicKeyFromCoseKey(cosePublicKey) + this.publicKey = WebAuthnIdentity.getPublicKeyFromCoseKey(cosePublicKey) this.rawId = rawId } @@ -23,7 +23,7 @@ export class WebAuthnIdentity extends PublicKeyIdentity { return this.getCoseKey().toAddress() } - private getPublicKeyFromCoseKey(cosePublicKey: ArrayBuffer): ArrayBuffer { + static getPublicKeyFromCoseKey(cosePublicKey: ArrayBuffer): ArrayBuffer { const decoded = cbor.decodeFirstSync(cosePublicKey) return decoded.get(-2) } @@ -93,7 +93,7 @@ export class WebAuthnIdentity extends PublicKeyIdentity { getCoseKey(): CoseKey { let decoded = cbor.decode(this.cosePublicKey) - decoded.set(4, [2]) + decoded.set(4, [2]) // key_ops: [verify] return new CoseKey(decoded) } @@ -127,11 +127,11 @@ export async function createPublicKeyCredential(challenge = CHALLENGE_BUFFER) { }, pubKeyCredParams: [ - { - // EdDSA -8 - type: "public-key", - alg: -8, - }, + // { + // // EdDSA -8 + // type: "public-key", + // alg: -8, + // }, { // ES256 -7 ECDSA w/ SHA-256 type: "public-key", diff --git a/src/network/modules/id-store/id-store.ts b/src/network/modules/id-store/id-store.ts new file mode 100644 index 0000000..cefdd3e --- /dev/null +++ b/src/network/modules/id-store/id-store.ts @@ -0,0 +1,77 @@ +import cbor from "cbor" +import { Message } from "../../../message" +import type { NetworkModule } from "../types" + +export type GetPhraseReturnType = ReturnType + +export type GetCredentialDataReturnType = ReturnType + +interface IdStore extends NetworkModule { + store: ( + address: string, + credId: ArrayBuffer, + credCosePublicKey: ArrayBuffer, + ) => Promise + getFromRecallPhrase: (phrase: string) => Promise + getFromAddress: (address: string) => Promise +} + +export const IdStore: IdStore = { + _namespace_: "idStore", + + async store( + address: string, + credId: ArrayBuffer, + credCosePublicKey: ArrayBuffer, + ): Promise { + const m = new Map() + m.set(0, address) + m.set(1, Buffer.from(credId)) + m.set(2, credCosePublicKey) + const message = await this.call("idstore.store", m) + return getPhrase(message) + }, + + async getFromRecallPhrase( + phrase: string, + ): Promise { + const val = phrase.trim().split(" ") + const message = await this.call( + "idstore.getFromRecallPhrase", + new Map([[0, val]]), + ) + return getCredentialData(message) + }, + + async getFromAddress(address: string): Promise { + const message = await this.call( + "idstore.getFromAddress", + new Map([[0, address]]), + ) + return getCredentialData(message) + }, +} + +function getPhrase(m: Message): { phrase: string } { + const result = { phrase: "" } + if (m.content.has(4)) { + const decoded = cbor.decodeFirstSync(m.content.get(4)) + result.phrase = decoded?.get(0)?.join(" ") + } + return result +} + +function getCredentialData(m: Message): { + credentialId?: ArrayBuffer + cosePublicKey?: ArrayBuffer +} { + const result = { credentialId: undefined, cosePublicKey: undefined } + if (m.content.has(4)) { + const decoded = cbor.decodeFirstSync(m.content.get(4)) + if (decoded.has(0) && decoded.has(1)) { + result.credentialId = decoded.get(0) + result.cosePublicKey = decoded.get(1) + } + } + return result +} diff --git a/src/network/modules/id-store/index.ts b/src/network/modules/id-store/index.ts new file mode 100644 index 0000000..865504a --- /dev/null +++ b/src/network/modules/id-store/index.ts @@ -0,0 +1 @@ +export * from "./id-store" diff --git a/src/network/modules/index.ts b/src/network/modules/index.ts index 129c955..d6de763 100644 --- a/src/network/modules/index.ts +++ b/src/network/modules/index.ts @@ -2,3 +2,4 @@ export * from "./ledger" export * from "./kvStore" export * from "./blockchain" export * from "./types" +export * from "./id-store" From 157402fb2ea26c57c893ec75e0f2f9c8a3271735 Mon Sep 17 00:00:00 2001 From: Tommy Date: Wed, 18 May 2022 15:25:10 -0700 Subject: [PATCH 30/40] adds idstore tests. use ecdsa for webauthn because eddsa will not work with firefox --- src/identity/webauthn/webauthn-identity.ts | 5 -- .../modules/id-store/__tests__/data.ts | 23 ++++++ .../id-store/__tests__/id-store.test.ts | 70 +++++++++++++++++++ 3 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 src/network/modules/id-store/__tests__/data.ts create mode 100644 src/network/modules/id-store/__tests__/id-store.test.ts diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index a96f20a..84b3297 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -127,11 +127,6 @@ export async function createPublicKeyCredential(challenge = CHALLENGE_BUFFER) { }, pubKeyCredParams: [ - // { - // // EdDSA -8 - // type: "public-key", - // alg: -8, - // }, { // ES256 -7 ECDSA w/ SHA-256 type: "public-key", diff --git a/src/network/modules/id-store/__tests__/data.ts b/src/network/modules/id-store/__tests__/data.ts new file mode 100644 index 0000000..a61c894 --- /dev/null +++ b/src/network/modules/id-store/__tests__/data.ts @@ -0,0 +1,23 @@ +import cbor from "cbor" +import { Message } from "../../../../message" + +export const mockStoreResponseContent = new Map([ + [4, cbor.encode(new Map([[0, ["recovery", "phrase"]]]))], +]) +export const mockStoreResponseMessage = new Message(mockStoreResponseContent) + +export const mockGetCredentialResponseContent = new Map([ + [ + 4, + cbor.encode( + // @ts-ignore + new Map([ + [0, new ArrayBuffer(32)], + [1, new ArrayBuffer(32)], + ]), + ), + ], +]) +export const mockGetCredentialResponseMessage = new Message( + mockGetCredentialResponseContent, +) diff --git a/src/network/modules/id-store/__tests__/id-store.test.ts b/src/network/modules/id-store/__tests__/id-store.test.ts new file mode 100644 index 0000000..ee25e88 --- /dev/null +++ b/src/network/modules/id-store/__tests__/id-store.test.ts @@ -0,0 +1,70 @@ +import { IdStore } from "../id-store" +import { + mockStoreResponseMessage, + mockGetCredentialResponseMessage, +} from "./data" + +describe("IdStore", () => { + describe("idstore.store()", () => { + it("returns symbols", async () => { + const mockCall = jest.fn(async () => { + return mockStoreResponseMessage + }) + const m = new Map() + m.set(0, "ma123") + m.set(1, Buffer.from(new ArrayBuffer(32))) + m.set(2, new ArrayBuffer(32)) + const idStore = setupIdStore(mockCall) + const res = await idStore.store( + "ma123", + new ArrayBuffer(32), + new ArrayBuffer(32), + ) + expect(mockCall).toHaveBeenCalledTimes(1) + expect(mockCall).toHaveBeenCalledWith("idstore.store", m) + expect(res).toEqual({ phrase: "recovery phrase" }) + }) + }) + describe("idstore.getFromRecallPhrase()", () => { + it("returns cosePublicKey and credentialId", async () => { + const mockCall = jest.fn(async () => { + return mockGetCredentialResponseMessage + }) + const m = new Map() + m.set(0, ["recall", "phrase"]) + const idStore = setupIdStore(mockCall) + const res = await idStore.getFromRecallPhrase("recall phrase") + expect(mockCall).toHaveBeenCalledTimes(1) + expect(mockCall).toHaveBeenCalledWith("idstore.getFromRecallPhrase", m) + expect(res).toEqual({ + credentialId: Buffer.from(new ArrayBuffer(32)), + cosePublicKey: Buffer.from(new ArrayBuffer(32)), + }) + }) + }) + describe("idstore.getFromAddress()", () => { + it("returns cosePublicKey and credentialId", async () => { + const mockCall = jest.fn(async () => { + return mockGetCredentialResponseMessage + }) + const m = new Map() + m.set(0, "ma123") + const idStore = setupIdStore(mockCall) + const res = await idStore.getFromAddress("ma123") + expect(mockCall).toHaveBeenCalledTimes(1) + expect(mockCall).toHaveBeenCalledWith("idstore.getFromAddress", m) + expect(res).toEqual({ + credentialId: Buffer.from(new ArrayBuffer(32)), + cosePublicKey: Buffer.from(new ArrayBuffer(32)), + }) + }) + }) +}) + +function setupIdStore(callImpl?: jest.Mock) { + const mockCall = callImpl ?? jest.fn() + return { + call: mockCall, + ...IdStore, + } +} From 95b82b0dc0456b825383ba1bd3210e4739e0d25e Mon Sep 17 00:00:00 2001 From: Tommy Date: Thu, 19 May 2022 10:36:55 -0700 Subject: [PATCH 31/40] cleanup and fix tests --- src/index.ts | 7 +++--- src/keys/index.ts | 47 ------------------------------------- src/keys/keys.test.ts | 41 -------------------------------- src/message/cose.test.ts | 10 +++++--- src/network/network.test.ts | 6 +++-- src/network/network.ts | 3 +-- 6 files changed, 15 insertions(+), 99 deletions(-) delete mode 100644 src/keys/index.ts delete mode 100644 src/keys/keys.test.ts diff --git a/src/index.ts b/src/index.ts index 3c52761..ee9382e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ -export * from "./identity"; -export * from "./keys"; -export * from "./message"; -export * from "./network"; \ No newline at end of file +export * from "./identity" +export * from "./message" +export * from "./network" diff --git a/src/keys/index.ts b/src/keys/index.ts deleted file mode 100644 index eb32473..0000000 --- a/src/keys/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import forge from "node-forge"; -import * as bip39 from "bip39"; - -const ed25519 = forge.pki.ed25519; - -export type Key = ArrayBuffer - -interface KeyPairParams { - privateKey: Key; - publicKey: Key; -} - -export class KeyPair { - privateKey: Key; - publicKey: Key; - - constructor(keys: KeyPairParams) { - this.privateKey = keys.privateKey; - this.publicKey = keys.publicKey; - } - - static getMnemonic(): string { - return bip39.generateMnemonic(); - } - - static fromMnemonic(mnemonic: string): KeyPair { - 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 KeyPair(keys); - } - - static fromPem(pem: string): KeyPair { - 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 keys; - } catch (e) { - throw new Error("Invalid PEM"); - } - } -} diff --git a/src/keys/keys.test.ts b/src/keys/keys.test.ts deleted file mode 100644 index d40aff9..0000000 --- a/src/keys/keys.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { KeyPair } from "../keys"; - -describe("keys", () => { - test("getSeedWords", () => { - const seedWords = KeyPair.getMnemonic(); - - expect(seedWords.split(" ")).toHaveLength(12); - }); - - test("fromSeedWords", () => { - const seedWords = KeyPair.getMnemonic(); - const badWords = "abandon abandon abandon"; - - const alice = KeyPair.fromMnemonic(seedWords); - const bob = KeyPair.fromMnemonic(seedWords); - - expect(alice.privateKey).toStrictEqual(bob.privateKey); - expect(() => { - KeyPair.fromMnemonic(badWords); - }).toThrow(); - }); - - test("fromPem", () => { - const pem = ` - -----BEGIN PRIVATE KEY----- - MC4CAQAwBQYDK2VwBCIEICT3i6WfLx4t3UF6R8aEfczyATc/jvqvOrNga2MJfA2R - -----END PRIVATE KEY-----`; - const badPem = ` - -----BEGIN PRIVATE CAT----- - MEOW - -----END PRIVATE CAT-----`; - - const alice = KeyPair.fromPem(pem); - const bob = KeyPair.fromPem(pem); - - expect(alice.privateKey).toStrictEqual(bob.privateKey); - expect(() => { - KeyPair.fromPem(badPem); - }).toThrow(); - }); -}); diff --git a/src/message/cose.test.ts b/src/message/cose.test.ts index 3fc5d15..426c8fd 100644 --- a/src/message/cose.test.ts +++ b/src/message/cose.test.ts @@ -1,7 +1,9 @@ +import { pki } from "node-forge" +import * as bip39 from "bip39" import { Message } from "../message"; -import { CoseMessage, CoseKey } from "./cose"; -import { KeyPair } from "../keys"; +import { CoseMessage } from "./cose" import { Ed25519KeyPairIdentity } from "../identity" +const ed25519 = pki.ed25519 describe("CoseMessage", () => { test("can make anonymous request", async () => { @@ -12,7 +14,9 @@ describe("CoseMessage", () => { }) test("can make signed request", async () => { - const keys = KeyPair.fromMnemonic(KeyPair.getMnemonic()) + const mnemonic = bip39.generateMnemonic() + const seed = bip39.mnemonicToSeedSync(mnemonic).slice(0, 32) + const keys = ed25519.generateKeyPair({ seed }) const identity = new Ed25519KeyPairIdentity(keys.publicKey, keys.privateKey) const signedMessage = Message.fromObject({ method: "info" }) const coseMessage = await CoseMessage.fromMessage(signedMessage, identity) diff --git a/src/network/network.test.ts b/src/network/network.test.ts index c58335b..94ea85c 100644 --- a/src/network/network.test.ts +++ b/src/network/network.test.ts @@ -1,6 +1,5 @@ import cbor from "cbor"; import { Network } from "../network"; -import { KeyPair } from "../keys"; import { tag } from "../message/cbor"; import { sha3_224 } from "js-sha3"; import { @@ -9,6 +8,7 @@ import { mockSymbolAddress2, } from "./modules/ledger/__tests__/data" import { Ledger } from "./modules"; +import { Ed25519KeyPairIdentity } from "../identity" const globalFetch = global.fetch; @@ -19,7 +19,9 @@ describe("network", () => { test("can get and set URL and KeyPair", () => { const testnet = new Network("http://example.com"); - const keys = KeyPair.fromMnemonic(KeyPair.getMnemonic()); + const keys = Ed25519KeyPairIdentity.fromMnemonic( + Ed25519KeyPairIdentity.getMnemonic(), + ) testnet.keys = keys; expect(testnet.url).toBe("http://example.com"); diff --git a/src/network/network.ts b/src/network/network.ts index d9a988b..d979c07 100644 --- a/src/network/network.ts +++ b/src/network/network.ts @@ -1,5 +1,4 @@ -import { Address, Identity } from "../identity" -import { KeyPair } from "../keys" +import { Identity } from "../identity" import { Message } from "../message" import { CborData } from "../message/cbor" import { applyMixins } from "../utils" From 921aebabc866b7f41e433cb8e84e119f6bd4c8a2 Mon Sep 17 00:00:00 2001 From: Tommy Date: Thu, 19 May 2022 21:30:06 -0700 Subject: [PATCH 32/40] adds async module --- .../modules/async/__tests__/async.test.ts | 64 ++++++++++++++++ src/network/modules/async/async.ts | 74 +++++++++++++++++++ src/network/modules/async/index.ts | 1 + src/network/network.ts | 6 +- 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 src/network/modules/async/__tests__/async.test.ts create mode 100644 src/network/modules/async/async.ts create mode 100644 src/network/modules/async/index.ts diff --git a/src/network/modules/async/__tests__/async.test.ts b/src/network/modules/async/__tests__/async.test.ts new file mode 100644 index 0000000..d1646b7 --- /dev/null +++ b/src/network/modules/async/__tests__/async.test.ts @@ -0,0 +1,64 @@ +import { Async } from "../async" +import { Message } from "../../../../message" +import { tag } from "../../../../message/cbor" +import cbor from "cbor" +import { Network } from "../../.." +import { AnonymousIdentity } from "../../../../identity" + +describe("Async", () => { + it("handleAsyncToken()", async () => { + let count = 2 + const mockCall = jest + .fn() + .mockImplementationOnce(async () => { + return new Message(new Map([[4, cbor.encode(new Map([[0, 0]]))]])) + }) + .mockImplementationOnce(async () => { + return new Message( + new Map([ + [ + 4, + cbor.encode( + //@ts-ignore + new Map([ + [0, 3], + [ + 1, + cbor.encode( + tag( + 10002, + new Map([ + [ + 4, + cbor.encode(new Map([[0, ["data", "returned"]]])), + ], + ]), + ), + ), + ], + ]), + ), + ], + ]), + ) + }) + const async = setupAsync(mockCall) + const n = new Network("/api", new AnonymousIdentity()) + n.call = mockCall + const res = (await async.handleAsyncToken( + new Message(new Map([[8, [[1, new ArrayBuffer(0)]]]])), + n, + )) as Message + const content = cbor.decode(res.content.get(4)) + expect(content instanceof Map).toBe(true) + expect(content.get(0)).toEqual(["data", "returned"]) + }) +}) + +function setupAsync(callImpl?: jest.Mock) { + const mockCall = callImpl ?? jest.fn() + return { + call: mockCall, + ...Async, + } +} diff --git a/src/network/modules/async/async.ts b/src/network/modules/async/async.ts new file mode 100644 index 0000000..0216e32 --- /dev/null +++ b/src/network/modules/async/async.ts @@ -0,0 +1,74 @@ +import cbor from "cbor" +import { AnonymousIdentity } from "../../../identity" +import { Message } from "../../../message" +import { Network } from "../../network" +import type { NetworkModule } from "../types" + +/** + const res = await Async.handleAsyncToken.call( + this, + new Network(this.url, new AnonymousIdentity()), + Message.fromCborData(reply), + ) + */ + +interface Async extends NetworkModule { + handleAsyncToken: (message: Message, n?: Network) => Promise +} + +export const Async: Async = { + _namespace_: "async", + + async handleAsyncToken(message: Message, n?: Network) { + const asyncToken = getAsyncToken(message) + if (asyncToken) { + return await fetchAsyncStatus( + n ?? new Network(this.url, new AnonymousIdentity()), + asyncToken, + ) + } + return message + }, +} + +function getAsyncToken(message: Message) { + let token = undefined + if (message.content.has(8)) { + const responseAttrs = message.content.get(8) + if (Array.isArray(responseAttrs)) { + let attr = responseAttrs.find(attr => { + if (Array.isArray(attr) && attr[0] === 1) { + return attr + } + }) + token = attr?.[1] + } + } + return token +} + +async function fetchAsyncStatus( + n: Network, + asyncToken: ArrayBuffer, +): Promise { + const res = (await n.call( + "async.status", + new Map([[0, asyncToken]]), + )) as Message + + if (res.content && res.content instanceof Map && res.content.has(4)) { + const content = res.content.get(4) + const decoded = cbor.decode(content) + if (decoded.has(0)) { + const asyncResult = decoded.get(0) + if (asyncResult === 3 && decoded.has(1)) { + const msg = cbor.decode(decoded.get(1)).value + return new Message(msg) + } else { + await new Promise(r => setTimeout(r, 250)) + return await fetchAsyncStatus(n, asyncToken) + } + } + } + return res +} diff --git a/src/network/modules/async/index.ts b/src/network/modules/async/index.ts new file mode 100644 index 0000000..ee10b57 --- /dev/null +++ b/src/network/modules/async/index.ts @@ -0,0 +1 @@ +export * from "./async" diff --git a/src/network/network.ts b/src/network/network.ts index d979c07..ffd3bf4 100644 --- a/src/network/network.ts +++ b/src/network/network.ts @@ -3,6 +3,7 @@ import { Message } from "../message" import { CborData } from "../message/cbor" import { applyMixins } from "../utils" import { NetworkModule } from "./modules" +import { Async } from "./modules/async" export class Network { [k: string]: any @@ -22,7 +23,10 @@ export class Network { const cbor = await req.toCborData(this.identity) const reply = await this.sendEncoded(cbor) // @TODO: Verify response - const res = Message.fromCborData(reply) + const res = await Async.handleAsyncToken.call( + this, + Message.fromCborData(reply), + ) return res } From 58f37dfff650679524d2bf89b181ad6a349003aa Mon Sep 17 00:00:00 2001 From: Tommy Date: Thu, 19 May 2022 21:33:13 -0700 Subject: [PATCH 33/40] remove comment --- src/network/modules/async/async.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/network/modules/async/async.ts b/src/network/modules/async/async.ts index 0216e32..7d6794f 100644 --- a/src/network/modules/async/async.ts +++ b/src/network/modules/async/async.ts @@ -4,14 +4,6 @@ import { Message } from "../../../message" import { Network } from "../../network" import type { NetworkModule } from "../types" -/** - const res = await Async.handleAsyncToken.call( - this, - new Network(this.url, new AnonymousIdentity()), - Message.fromCborData(reply), - ) - */ - interface Async extends NetworkModule { handleAsyncToken: (message: Message, n?: Network) => Promise } From 538864e7cab4c51bb4d6679f6421a75e52cdf0e0 Mon Sep 17 00:00:00 2001 From: Tommy Date: Fri, 20 May 2022 09:29:03 -0700 Subject: [PATCH 34/40] one minute polling --- src/network/modules/async/async.ts | 37 ++++++++++++++++++------------ 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/network/modules/async/async.ts b/src/network/modules/async/async.ts index 7d6794f..24b5622 100644 --- a/src/network/modules/async/async.ts +++ b/src/network/modules/async/async.ts @@ -4,6 +4,10 @@ import { Message } from "../../../message" import { Network } from "../../network" import type { NetworkModule } from "../types" +const ONE_MINUTE = 60000 +const ONE_SECOND = 1000 +const sleep = async (time: number) => new Promise(r => setTimeout(r, time)) + interface Async extends NetworkModule { handleAsyncToken: (message: Message, n?: Network) => Promise } @@ -43,24 +47,27 @@ async function fetchAsyncStatus( n: Network, asyncToken: ArrayBuffer, ): Promise { - const res = (await n.call( - "async.status", - new Map([[0, asyncToken]]), - )) as Message + const start = new Date().getTime() + let isDurationReached = false + let res = new Message(new Map()) - if (res.content && res.content instanceof Map && res.content.has(4)) { - const content = res.content.get(4) - const decoded = cbor.decode(content) - if (decoded.has(0)) { - const asyncResult = decoded.get(0) - if (asyncResult === 3 && decoded.has(1)) { - const msg = cbor.decode(decoded.get(1)).value - return new Message(msg) - } else { - await new Promise(r => setTimeout(r, 250)) - return await fetchAsyncStatus(n, asyncToken) + while (!isDurationReached) { + res = (await n.call("async.status", new Map([[0, asyncToken]]))) as Message + if (res?.content && res.content instanceof Map && res.content.has(4)) { + const content = res.content.get(4) + const decoded = cbor.decode(content) + if (decoded.has(0)) { + const asyncResult = decoded.get(0) + if (asyncResult === 3 && decoded.has(1)) { + const msg = cbor.decode(decoded.get(1)).value + return new Message(msg) + } } } + const now = new Date().getTime() + isDurationReached = now - start >= ONE_MINUTE + await sleep(ONE_SECOND) } + return res } From fa9e12ca653c5f206131e4ee0f5f1643a45ed94e Mon Sep 17 00:00:00 2001 From: Tommy Date: Fri, 20 May 2022 16:36:10 -0700 Subject: [PATCH 35/40] increase wait time on async. fix message responses now that everything is a map. adds types for Attributes and AsyncAttribute --- .../async-attribute/async-attribute.ts | 29 +++++++++++ .../attributes/async-attribute/index.ts | 1 + src/message/attributes/attributes.ts | 16 +++++++ src/message/attributes/index.ts | 3 ++ src/message/attributes/types.ts | 3 ++ src/message/cose.ts | 4 +- src/message/index.ts | 21 +++++++- .../modules/async/__tests__/async.test.ts | 2 +- src/network/modules/async/async.ts | 48 +++++++------------ src/network/modules/id-store/id-store.ts | 16 +++---- src/network/modules/ledger/__tests__/data.ts | 30 ++++++------ src/network/modules/ledger/ledger.ts | 30 ++++++------ 12 files changed, 129 insertions(+), 74 deletions(-) create mode 100644 src/message/attributes/async-attribute/async-attribute.ts create mode 100644 src/message/attributes/async-attribute/index.ts create mode 100644 src/message/attributes/attributes.ts create mode 100644 src/message/attributes/index.ts create mode 100644 src/message/attributes/types.ts diff --git a/src/message/attributes/async-attribute/async-attribute.ts b/src/message/attributes/async-attribute/async-attribute.ts new file mode 100644 index 0000000..3cd9ec7 --- /dev/null +++ b/src/message/attributes/async-attribute/async-attribute.ts @@ -0,0 +1,29 @@ +import { Attributes } from "../attributes" +import { ResponseAttributeTypes } from "../types" + +type AsyncToken = ArrayBuffer +type AsyncResponseAttribute = [number, AsyncToken] +export class AsyncAttribute { + attribute: AsyncResponseAttribute | undefined = undefined + constructor(attr: AsyncResponseAttribute) { + this.attribute = attr + } + getAttribute() { + return this.attribute + } + getToken() { + return this.getAttribute()?.[1] + } + static getFromAttributes(attrs: Attributes): AsyncAttribute | undefined { + const attr = attrs.getAttributes().find(attr => { + if ( + Array.isArray(attr) && + attr[0] === ResponseAttributeTypes.async && + attr[1] + ) { + return attr + } + }) + return attr ? new AsyncAttribute(attr as AsyncResponseAttribute) : undefined + } +} diff --git a/src/message/attributes/async-attribute/index.ts b/src/message/attributes/async-attribute/index.ts new file mode 100644 index 0000000..f7af2ee --- /dev/null +++ b/src/message/attributes/async-attribute/index.ts @@ -0,0 +1 @@ +export { AsyncAttribute } from "./async-attribute" diff --git a/src/message/attributes/attributes.ts b/src/message/attributes/attributes.ts new file mode 100644 index 0000000..8802fe8 --- /dev/null +++ b/src/message/attributes/attributes.ts @@ -0,0 +1,16 @@ +import { Message } from ".." + +export class Attributes { + private attributes + constructor(attrs: unknown[]) { + this.attributes = attrs + } + getAttributes() { + return this.attributes + } + static getFromMessage(m: Message): Attributes | undefined { + const attrs = m.getContent().get(8) + if (Array.isArray(attrs)) return new Attributes(attrs) + return undefined + } +} diff --git a/src/message/attributes/index.ts b/src/message/attributes/index.ts new file mode 100644 index 0000000..36f2914 --- /dev/null +++ b/src/message/attributes/index.ts @@ -0,0 +1,3 @@ +export { Attributes } from "./attributes" +export * from "./async-attribute" +export * from "./types" diff --git a/src/message/attributes/types.ts b/src/message/attributes/types.ts new file mode 100644 index 0000000..06dbd3e --- /dev/null +++ b/src/message/attributes/types.ts @@ -0,0 +1,3 @@ +export enum ResponseAttributeTypes { + async = 1, +} diff --git a/src/message/cose.ts b/src/message/cose.ts index eaf2915..1c41fe3 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -57,8 +57,8 @@ export class CoseMessage { ): Promise { const protectedHeader = this.getProtectedHeader(identity) const cborProtectedHeader = cbor.encodeCanonical(protectedHeader) - const content = message.content - const cborContent = cbor.encode(tag(10001, message.content)) + const content = message.getContent() + const cborContent = cbor.encode(tag(10001, content)) const toBeSigned = cbor.encodeCanonical([ "Signature1", cborProtectedHeader, diff --git a/src/message/index.ts b/src/message/index.ts index 29c3d7e..4ddb55f 100644 --- a/src/message/index.ts +++ b/src/message/index.ts @@ -3,6 +3,7 @@ import { Address, Identity } from "../identity" import { CborData, CborMap, tag } from "./cbor"; import { CoseMessage } from "./cose"; import { ManyError, SerializedManyError } from "./error"; +import { Attributes, AsyncAttribute } from "./attributes" interface MessageContent { version?: number; @@ -17,7 +18,25 @@ interface MessageContent { } export class Message { - constructor(public content: CborMap) {} + private content: CborMap + constructor(content: CborMap) { + this.content = content + } + + getContent() { + return this.content + } + + getAsyncToken(): ArrayBuffer | undefined { + const attributes = Attributes.getFromMessage(this) + return attributes + ? AsyncAttribute.getFromAttributes(attributes)?.getToken() + : undefined + } + + getPayload(): CborMap { + return cbor.decode(this.content?.get(4)) + } static fromObject(obj: MessageContent): Message { if (!obj.method) { diff --git a/src/network/modules/async/__tests__/async.test.ts b/src/network/modules/async/__tests__/async.test.ts index d1646b7..ae527f4 100644 --- a/src/network/modules/async/__tests__/async.test.ts +++ b/src/network/modules/async/__tests__/async.test.ts @@ -49,7 +49,7 @@ describe("Async", () => { new Message(new Map([[8, [[1, new ArrayBuffer(0)]]]])), n, )) as Message - const content = cbor.decode(res.content.get(4)) + const content = res.getPayload() expect(content instanceof Map).toBe(true) expect(content.get(0)).toEqual(["data", "returned"]) }) diff --git a/src/network/modules/async/async.ts b/src/network/modules/async/async.ts index 24b5622..ba9b2a6 100644 --- a/src/network/modules/async/async.ts +++ b/src/network/modules/async/async.ts @@ -16,57 +16,41 @@ export const Async: Async = { _namespace_: "async", async handleAsyncToken(message: Message, n?: Network) { - const asyncToken = getAsyncToken(message) - if (asyncToken) { - return await fetchAsyncStatus( - n ?? new Network(this.url, new AnonymousIdentity()), - asyncToken, - ) - } - return message + const asyncToken = message.getAsyncToken() + return asyncToken + ? await fetchAsyncStatus( + n ?? new Network(this.url, new AnonymousIdentity()), + asyncToken, + ) + : message }, } -function getAsyncToken(message: Message) { - let token = undefined - if (message.content.has(8)) { - const responseAttrs = message.content.get(8) - if (Array.isArray(responseAttrs)) { - let attr = responseAttrs.find(attr => { - if (Array.isArray(attr) && attr[0] === 1) { - return attr - } - }) - token = attr?.[1] - } - } - return token -} - async function fetchAsyncStatus( n: Network, asyncToken: ArrayBuffer, ): Promise { const start = new Date().getTime() + let waitTime = ONE_SECOND let isDurationReached = false let res = new Message(new Map()) while (!isDurationReached) { res = (await n.call("async.status", new Map([[0, asyncToken]]))) as Message - if (res?.content && res.content instanceof Map && res.content.has(4)) { - const content = res.content.get(4) - const decoded = cbor.decode(content) - if (decoded.has(0)) { - const asyncResult = decoded.get(0) - if (asyncResult === 3 && decoded.has(1)) { - const msg = cbor.decode(decoded.get(1)).value + const payload = res.getPayload() + if (payload) { + if (payload.has(0)) { + const asyncResult = payload.get(0) + if (asyncResult === 3 && payload.has(1)) { + const msg = cbor.decode(payload.get(1)).value return new Message(msg) } } } const now = new Date().getTime() isDurationReached = now - start >= ONE_MINUTE - await sleep(ONE_SECOND) + await sleep(waitTime) + waitTime *= 1.5 } return res diff --git a/src/network/modules/id-store/id-store.ts b/src/network/modules/id-store/id-store.ts index cefdd3e..dcaedfe 100644 --- a/src/network/modules/id-store/id-store.ts +++ b/src/network/modules/id-store/id-store.ts @@ -54,9 +54,9 @@ export const IdStore: IdStore = { function getPhrase(m: Message): { phrase: string } { const result = { phrase: "" } - if (m.content.has(4)) { - const decoded = cbor.decodeFirstSync(m.content.get(4)) - result.phrase = decoded?.get(0)?.join(" ") + const payload = m.getPayload() + if (payload) { + result.phrase = payload?.get(0)?.join(" ") } return result } @@ -66,11 +66,11 @@ function getCredentialData(m: Message): { cosePublicKey?: ArrayBuffer } { const result = { credentialId: undefined, cosePublicKey: undefined } - if (m.content.has(4)) { - const decoded = cbor.decodeFirstSync(m.content.get(4)) - if (decoded.has(0) && decoded.has(1)) { - result.credentialId = decoded.get(0) - result.cosePublicKey = decoded.get(1) + const payload = m.getPayload() + if (payload) { + if (payload.has(0) && payload.has(1)) { + result.credentialId = payload.get(0) + result.cosePublicKey = payload.get(1) } } return result diff --git a/src/network/modules/ledger/__tests__/data.ts b/src/network/modules/ledger/__tests__/data.ts index ee3dbe4..f36c1ad 100644 --- a/src/network/modules/ledger/__tests__/data.ts +++ b/src/network/modules/ledger/__tests__/data.ts @@ -86,13 +86,14 @@ export const mockLedgerListResponseContent = new Map([ [1, txnTime1], [ 2, - [ - 0, - tag(10000, Address1), - tag(10000, Address2), - txnSymbolAddress1, - 1, - ], + //@ts-ignore + new Map([ + [0, 0], + [1, tag(10000, Address1)], + [2, tag(10000, Address2)], + [3, txnSymbolAddress1], + [4, 1], + ]), ], ]), // @ts-ignore @@ -101,13 +102,14 @@ export const mockLedgerListResponseContent = new Map([ [1, txnTime2], [ 2, - [ - 0, - tag(10000, Address1), - tag(10000, Address2), - txnSymbolAddress2, - 2, - ], + //@ts-ignore + new Map([ + [0, 0], + [1, tag(10000, Address1)], + [2, tag(10000, Address2)], + [3, txnSymbolAddress2], + [4, 2], + ]), ], ]), ], diff --git a/src/network/modules/ledger/ledger.ts b/src/network/modules/ledger/ledger.ts index 38e93c5..c2b4ede 100644 --- a/src/network/modules/ledger/ledger.ts +++ b/src/network/modules/ledger/ledger.ts @@ -138,8 +138,8 @@ export const Ledger: Ledger = { export function getLedgerInfo(message: Message): LedgerInfo { const result: LedgerInfo = { symbols: new Map() } - if (message.content.has(4)) { - const decodedContent = cbor.decodeFirstSync(message.content.get(4)) + const decodedContent = message.getPayload() + if (decodedContent) { if (decodedContent.has(4)) { const symbols = decodedContent.get(4) @@ -159,8 +159,8 @@ export interface Balances { export function getBalance(message: Message): Balances { const result = { balances: new Map() } - if (message.content.has(4)) { - const messageContent = cbor.decodeFirstSync(message.content.get(4)) + const messageContent = message.getPayload() + if (messageContent) { if (messageContent.has(0)) { const symbolsToBalancesMap = messageContent.get(0) if (!(symbolsToBalancesMap instanceof Map)) return result @@ -176,9 +176,7 @@ export function getBalance(message: Message): Balances { function getTransactionsCount(message: Message) { return { - count: message?.content?.has(4) - ? cbor.decodeFirstSync(message.content.get(4))?.get(0) - : 0, + count: message?.getContent().has(4) ? message?.getPayload()?.get(0) : 0, } } @@ -187,13 +185,13 @@ function getTxnList(message: Message): TransactionsData { count: 0, transactions: [], } - if (message.content.has(4)) { - const decodedContent = cbor.decodeFirstSync(message.content.get(4)) + const decodedContent = message.getPayload() + if (decodedContent) { result.count = decodedContent.get(0) const transactions = decodedContent.get(1) result.transactions = transactions.map((t: Map) => { - let transactionData = t.get(2) as Array - const transactionType = transactionData[0] + let transactionData = t.get(2) as Map + const transactionType = transactionData.get(0) if (transactionType === 0) { return makeSendTransactionData(t) } @@ -203,11 +201,11 @@ function getTxnList(message: Message): TransactionsData { } function makeSendTransactionData(t: Map) { - let transactionData = t.get(2) as Array + let transactionData = t.get(2) as Map const id = t.get(0) as Uint8Array const time = t.get(1) - const from = transactionData[1] as { value: Uint8Array } - const to = transactionData[2] as { value: Uint8Array } + const from = transactionData.get(1) as { value: Uint8Array } + const to = transactionData.get(2) as { value: Uint8Array } const fromAddress = new Address(from.value as Buffer).toString() const toAddress = new Address(to.value as Buffer).toString() return { @@ -216,8 +214,8 @@ function makeSendTransactionData(t: Map) { type: TransactionType.send, from: fromAddress, to: toAddress, - symbolAddress: transactionData[3], - amount: BigInt(transactionData[4] as number), + symbolAddress: transactionData.get(3), + amount: BigInt(transactionData.get(4) as number), } } From 6d4dabfc97638d692f79573f246a4bce63aff1a8 Mon Sep 17 00:00:00 2001 From: Tommy Date: Fri, 20 May 2022 18:57:13 -0700 Subject: [PATCH 36/40] allow comms to authenticator through bluetooth --- src/identity/webauthn/webauthn-identity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index 84b3297..4b2c3a6 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -60,7 +60,7 @@ export class WebAuthnIdentity extends PublicKeyIdentity { userVerification: "discouraged", allowCredentials: [ { - transports: ["nfc", "usb"], + transports: ["nfc", "usb", "ble"], id: credentialId, type: "public-key", }, From 45e02260f91270ba0aecec96c8a47995eeae7ea9 Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 23 May 2022 10:19:23 -0700 Subject: [PATCH 37/40] fix symbol address not returned properly. fix tests --- package.json | 1 + src/network/modules/ledger/__tests__/data.ts | 20 ++++++++++++++++---- src/network/modules/ledger/ledger.ts | 20 ++++++++++---------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index b0fd636..0413e5a 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "jest": { "preset": "ts-jest", "testEnvironment": "node", + "maxWorkers": 1, "testMatch": [ "**/?(*.)+(spec|test).[jt]s?(x)" ], diff --git a/src/network/modules/ledger/__tests__/data.ts b/src/network/modules/ledger/__tests__/data.ts index f36c1ad..0f52a5b 100644 --- a/src/network/modules/ledger/__tests__/data.ts +++ b/src/network/modules/ledger/__tests__/data.ts @@ -65,8 +65,8 @@ export const expectedBalancesMap = { ]), } -const txnSymbolAddress1 = "oafw3bxrqe2jdcidvjlonloqcczvytrxr3fl4naybmign3uy6e" -const txnSymbolAddress2 = "oafxombm6axwsrcvymht5ss3chlpbks7sp7dvl2v7chnuzkyfj" +const txnSymbolAddress1 = "mafw3bxrqe2jdcidvjlonloqcczvytrxr3fl4naybmign3uy6e" +const txnSymbolAddress2 = "mafxombm6axwsrcvymht5ss3chlpbks7sp7dvl2v7chnuzkyfj" const txnTime1 = new Date() const txnTime2 = new Date() txnTime2.setMinutes(txnTime1.getMinutes() + 1) @@ -91,7 +91,13 @@ export const mockLedgerListResponseContent = new Map([ [0, 0], [1, tag(10000, Address1)], [2, tag(10000, Address2)], - [3, txnSymbolAddress1], + [ + 3, + tag( + 10000, + Address.fromString(txnSymbolAddress1).toBuffer(), + ), + ], [4, 1], ]), ], @@ -107,7 +113,13 @@ export const mockLedgerListResponseContent = new Map([ [0, 0], [1, tag(10000, Address1)], [2, tag(10000, Address2)], - [3, txnSymbolAddress2], + [ + 3, + tag( + 10000, + Address.fromString(txnSymbolAddress2).toBuffer(), + ), + ], [4, 2], ]), ], diff --git a/src/network/modules/ledger/ledger.ts b/src/network/modules/ledger/ledger.ts index c2b4ede..cc89da6 100644 --- a/src/network/modules/ledger/ledger.ts +++ b/src/network/modules/ledger/ledger.ts @@ -160,15 +160,13 @@ export interface Balances { export function getBalance(message: Message): Balances { const result = { balances: new Map() } const messageContent = message.getPayload() - if (messageContent) { - if (messageContent.has(0)) { - const symbolsToBalancesMap = messageContent.get(0) - if (!(symbolsToBalancesMap instanceof Map)) return result - for (const balanceEntry of symbolsToBalancesMap) { - const symbolAddress = new Address(balanceEntry[0].value).toString() - const balance = balanceEntry[1] - result.balances.set(symbolAddress, balance) - } + if (messageContent && messageContent.has(0)) { + const symbolsToBalancesMap = messageContent.get(0) + if (!(symbolsToBalancesMap instanceof Map)) return result + for (const balanceEntry of symbolsToBalancesMap) { + const symbolAddress = new Address(balanceEntry[0].value).toString() + const balance = balanceEntry[1] + result.balances.set(symbolAddress, balance) } } return result @@ -206,6 +204,8 @@ function makeSendTransactionData(t: Map) { const time = t.get(1) const from = transactionData.get(1) as { value: Uint8Array } const to = transactionData.get(2) as { value: Uint8Array } + const symbol = transactionData.get(3) as { value: Uint8Array } + const symbolAddress = new Address(symbol.value as Buffer).toString() const fromAddress = new Address(from.value as Buffer).toString() const toAddress = new Address(to.value as Buffer).toString() return { @@ -214,7 +214,7 @@ function makeSendTransactionData(t: Map) { type: TransactionType.send, from: fromAddress, to: toAddress, - symbolAddress: transactionData.get(3), + symbolAddress, amount: BigInt(transactionData.get(4) as number), } } From 2dac5c3a935af66a0fd598737bc6159ca2eae655 Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 23 May 2022 21:39:57 -0700 Subject: [PATCH 38/40] adds change requests from pr --- src/identity/anonymous/anonymous-identity.ts | 4 +- .../ed25519/ed25519-key-pair-identity.ts | 13 ++- src/identity/types.ts | 6 +- src/identity/webauthn/webauthn-identity.ts | 13 ++- src/message/cose.test.ts | 16 ++-- src/message/cose.ts | 3 +- .../modules/async/__tests__/async.test.ts | 48 +++++----- src/network/modules/async/async.ts | 90 +++++++++++++------ 8 files changed, 114 insertions(+), 79 deletions(-) diff --git a/src/identity/anonymous/anonymous-identity.ts b/src/identity/anonymous/anonymous-identity.ts index 103c9c8..d49208d 100644 --- a/src/identity/anonymous/anonymous-identity.ts +++ b/src/identity/anonymous/anonymous-identity.ts @@ -14,7 +14,7 @@ export class AnonymousIdentity extends Identity { return Address.anonymous() } - toJson() { - return AnonymousIdentity.name + toJSON(): { dataType: string } { + return { dataType: this.constructor.name } } } diff --git a/src/identity/ed25519/ed25519-key-pair-identity.ts b/src/identity/ed25519/ed25519-key-pair-identity.ts index 3b14941..3e17bf6 100644 --- a/src/identity/ed25519/ed25519-key-pair-identity.ts +++ b/src/identity/ed25519/ed25519-key-pair-identity.ts @@ -2,14 +2,14 @@ import forge, { pki } from "node-forge" import * as bip39 from "bip39" import { CoseKey } from "../../message/cose" const ed25519 = pki.ed25519 -import { PrivateKeyIdentity } from "../types" +import { PublicKeyIdentity } from "../types" import { Address } from "../address" -export class Ed25519KeyPairIdentity extends PrivateKeyIdentity { +export class Ed25519KeyPairIdentity extends PublicKeyIdentity { publicKey: ArrayBuffer protected privateKey: ArrayBuffer - constructor(publicKey: ArrayBuffer, privateKey: ArrayBuffer) { + protected constructor(publicKey: ArrayBuffer, privateKey: ArrayBuffer) { super() this.publicKey = publicKey this.privateKey = privateKey @@ -66,8 +66,13 @@ export class Ed25519KeyPairIdentity extends PrivateKeyIdentity { return new CoseKey(c) } - toJson() { + toJSON(): { + dataType: string + publicKey: ArrayBuffer + privateKey: ArrayBuffer + } { return { + dataType: this.constructor.name, publicKey: this.publicKey, privateKey: this.privateKey, } diff --git a/src/identity/types.ts b/src/identity/types.ts index c3d609a..b52b953 100644 --- a/src/identity/types.ts +++ b/src/identity/types.ts @@ -14,7 +14,7 @@ export interface Verifier { export abstract class Identity implements Signer, Verifier { abstract getAddress(): Promise
- abstract toJson(): unknown + abstract toJSON(): unknown abstract sign( data: ArrayBuffer, unprotectedHeader: Map, @@ -32,7 +32,3 @@ export abstract class PublicKeyIdentity extends Identity { abstract publicKey: ArrayBuffer abstract getCoseKey(): CoseKey } - -export abstract class PrivateKeyIdentity extends PublicKeyIdentity { - protected abstract privateKey: ArrayBuffer -} diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index 4b2c3a6..b443b84 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -1,11 +1,11 @@ import cbor from "cbor" +import { ONE_MINUTE } from "../../const" import { CoseKey, EMPTY } from "../../message/cose" import { Address } from "../address" import { PublicKeyIdentity } from "../types" const sha512 = require("js-sha512") const CHALLENGE_BUFFER = new TextEncoder().encode("lifted") -const ONE_MINUTE = 60000 export class WebAuthnIdentity extends PublicKeyIdentity { publicKey: ArrayBuffer @@ -42,6 +42,9 @@ export class WebAuthnIdentity extends PublicKeyIdentity { } async sign(): Promise { + // This value ends up in the signature COSE field, but we don't + // use it for WebAuthn verification, so we simply set it to + // EMPTY. return EMPTY } @@ -97,8 +100,9 @@ export class WebAuthnIdentity extends PublicKeyIdentity { return new CoseKey(decoded) } - toJson(): { rawId: string; cosePublicKey: ArrayBuffer } { + toJSON(): { dataType: string; rawId: string; cosePublicKey: ArrayBuffer } { return { + dataType: this.constructor.name, rawId: Buffer.from(this.rawId).toString("base64"), cosePublicKey: this.cosePublicKey, } @@ -127,6 +131,11 @@ export async function createPublicKeyCredential(challenge = CHALLENGE_BUFFER) { }, pubKeyCredParams: [ + /** + * we only use this algo because the major browsers (chrome, firefox, safari, brave, edge) support this. + * for example, if we create a credential with eddsa algo on chrome, we wouldnt be able to use it from firefox + * because it doesn't support this algo. + */ { // ES256 -7 ECDSA w/ SHA-256 type: "public-key", diff --git a/src/message/cose.test.ts b/src/message/cose.test.ts index 426c8fd..86c2e90 100644 --- a/src/message/cose.test.ts +++ b/src/message/cose.test.ts @@ -1,6 +1,5 @@ import { pki } from "node-forge" -import * as bip39 from "bip39" -import { Message } from "../message"; +import { Message } from "../message" import { CoseMessage } from "./cose" import { Ed25519KeyPairIdentity } from "../identity" const ed25519 = pki.ed25519 @@ -14,10 +13,9 @@ describe("CoseMessage", () => { }) test("can make signed request", async () => { - const mnemonic = bip39.generateMnemonic() - const seed = bip39.mnemonicToSeedSync(mnemonic).slice(0, 32) - const keys = ed25519.generateKeyPair({ seed }) - const identity = new Ed25519KeyPairIdentity(keys.publicKey, keys.privateKey) + const identity = Ed25519KeyPairIdentity.fromMnemonic( + Ed25519KeyPairIdentity.getMnemonic(), + ) const signedMessage = Message.fromObject({ method: "info" }) const coseMessage = await CoseMessage.fromMessage(signedMessage, identity) @@ -32,8 +30,8 @@ describe("CoseMessage", () => { expect(deserialized.content).toStrictEqual(coseMessage.content) }) -}); +}) describe("CoseKey", () => { - test.skip("fromPublicKey", () => {}); -}); + test.skip("fromPublicKey", () => {}) +}) diff --git a/src/message/cose.ts b/src/message/cose.ts index 1c41fe3..f6266b2 100644 --- a/src/message/cose.ts +++ b/src/message/cose.ts @@ -4,7 +4,6 @@ import { Address, AnonymousIdentity, Identity, - PrivateKeyIdentity, PublicKeyIdentity, } from "../identity" import { Message } from "../message" @@ -80,7 +79,7 @@ export class CoseMessage { } private static getProtectedHeader( - identity: PublicKeyIdentity | PrivateKeyIdentity | Identity, + identity: PublicKeyIdentity | Identity, ): CborMap { const protectedHeader = new Map() if ("getCoseKey" in identity) { diff --git a/src/network/modules/async/__tests__/async.test.ts b/src/network/modules/async/__tests__/async.test.ts index ae527f4..4360f12 100644 --- a/src/network/modules/async/__tests__/async.test.ts +++ b/src/network/modules/async/__tests__/async.test.ts @@ -4,42 +4,24 @@ import { tag } from "../../../../message/cbor" import cbor from "cbor" import { Network } from "../../.." import { AnonymousIdentity } from "../../../../identity" +import { AsyncStatusResult } from ".." describe("Async", () => { it("handleAsyncToken()", async () => { - let count = 2 const mockCall = jest .fn() .mockImplementationOnce(async () => { - return new Message(new Map([[4, cbor.encode(new Map([[0, 0]]))]])) + return makeAsyncStatusPollResponseMessage(AsyncStatusResult.Unknown) }) .mockImplementationOnce(async () => { - return new Message( - new Map([ - [ - 4, - cbor.encode( - //@ts-ignore - new Map([ - [0, 3], - [ - 1, - cbor.encode( - tag( - 10002, - new Map([ - [ - 4, - cbor.encode(new Map([[0, ["data", "returned"]]])), - ], - ]), - ), - ), - ], - ]), - ), - ], - ]), + return makeAsyncStatusPollResponseMessage( + AsyncStatusResult.Done, + cbor.encode( + tag( + 10002, + new Map([[4, cbor.encode(new Map([[0, ["data", "returned"]]]))]]), + ), + ), ) }) const async = setupAsync(mockCall) @@ -62,3 +44,13 @@ function setupAsync(callImpl?: jest.Mock) { ...Async, } } + +function makeAsyncStatusPollResponseMessage( + statusResult: AsyncStatusResult, + payload?: ArrayBuffer, +) { + const result = new Map() + result.set(0, statusResult) + if (payload) result.set(1, payload) + return new Message(new Map([[4, cbor.encode(result)]])) +} \ No newline at end of file diff --git a/src/network/modules/async/async.ts b/src/network/modules/async/async.ts index ba9b2a6..291108b 100644 --- a/src/network/modules/async/async.ts +++ b/src/network/modules/async/async.ts @@ -1,11 +1,11 @@ import cbor from "cbor" +import { ONE_MINUTE, ONE_SECOND } from "../../../const" import { AnonymousIdentity } from "../../../identity" import { Message } from "../../../message" +import { CborMap } from "../../../message/cbor" import { Network } from "../../network" import type { NetworkModule } from "../types" -const ONE_MINUTE = 60000 -const ONE_SECOND = 1000 const sleep = async (time: number) => new Promise(r => setTimeout(r, time)) interface Async extends NetworkModule { @@ -18,7 +18,7 @@ export const Async: Async = { async handleAsyncToken(message: Message, n?: Network) { const asyncToken = message.getAsyncToken() return asyncToken - ? await fetchAsyncStatus( + ? await pollAsyncStatus( n ?? new Network(this.url, new AnonymousIdentity()), asyncToken, ) @@ -26,32 +26,68 @@ export const Async: Async = { }, } -async function fetchAsyncStatus( +export enum AsyncStatusResult { + Unknown = 0, + Queued = 1, + Processing = 2, + Done = 3, + Expired = 4, +} + +type AsyncStatusPayload = + | { result: AsyncStatusResult.Unknown } + | { result: AsyncStatusResult.Queued } + | { result: AsyncStatusResult.Processing } + | { result: AsyncStatusResult.Expired } + | { + result: AsyncStatusResult.Done + payload: ArrayBuffer + } +function parseAsyncStatusPayload(cbor: CborMap): AsyncStatusPayload { + const index = cbor.get(0) + if (typeof index != "number") { + throw new Error("Invalid async result") + } + if (!(index in AsyncStatusResult)) { + throw Error("Invalid async result") + } + const result = index as AsyncStatusResult + let payload = undefined + if (result === AsyncStatusResult.Done) { + payload = cbor.get(1) as ArrayBuffer + if (!payload || !Buffer.isBuffer(payload)) { + throw Error("Invalid async result") + } + } + return { result, payload } as AsyncStatusPayload +} +async function pollAsyncStatus( n: Network, asyncToken: ArrayBuffer, + options: { + timeoutInMsec?: number + waitTimeInMsec?: number + } = {}, ): Promise { - const start = new Date().getTime() - let waitTime = ONE_SECOND - let isDurationReached = false - let res = new Message(new Map()) - - while (!isDurationReached) { - res = (await n.call("async.status", new Map([[0, asyncToken]]))) as Message - const payload = res.getPayload() - if (payload) { - if (payload.has(0)) { - const asyncResult = payload.get(0) - if (asyncResult === 3 && payload.has(1)) { - const msg = cbor.decode(payload.get(1)).value - return new Message(msg) - } - } + const timeoutInMsec = options.timeoutInMsec ?? ONE_MINUTE + let waitTimeInMsec = options.waitTimeInMsec ?? ONE_SECOND + const end = +new Date() + timeoutInMsec + while (true) { + const res = (await n.call( + "async.status", + new Map([[0, asyncToken]]), + )) as Message + const result = parseAsyncStatusPayload(res.getPayload()) + switch (result.result) { + case AsyncStatusResult.Done: + return new Message(cbor.decode(result.payload).value) + case AsyncStatusResult.Expired: + throw new Error("Async Expired before getting a result") } - const now = new Date().getTime() - isDurationReached = now - start >= ONE_MINUTE - await sleep(waitTime) - waitTime *= 1.5 + if (Date.now() >= end) { + return res + } + await sleep(waitTimeInMsec) + waitTimeInMsec *= 1.5 } - - return res -} +} \ No newline at end of file From 32bfcff4e4e8ba3594ad640f7a3ea82ee932dca6 Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 24 May 2022 14:35:05 -0700 Subject: [PATCH 39/40] adds const file --- src/const.ts | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/const.ts diff --git a/src/const.ts b/src/const.ts new file mode 100644 index 0000000..54e4db2 --- /dev/null +++ b/src/const.ts @@ -0,0 +1,2 @@ +export const ONE_SECOND = 1000 +export const ONE_MINUTE = 60000 From 2e12ca775ddcbc2d245f7d3c2e9b6e7e6c0fcff8 Mon Sep 17 00:00:00 2001 From: Tommy Date: Wed, 25 May 2022 09:31:40 -0700 Subject: [PATCH 40/40] throw on identity verify --- src/identity/ed25519/ed25519-key-pair-identity.ts | 2 +- src/identity/webauthn/webauthn-identity.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/identity/ed25519/ed25519-key-pair-identity.ts b/src/identity/ed25519/ed25519-key-pair-identity.ts index 3e17bf6..b159ee9 100644 --- a/src/identity/ed25519/ed25519-key-pair-identity.ts +++ b/src/identity/ed25519/ed25519-key-pair-identity.ts @@ -53,7 +53,7 @@ export class Ed25519KeyPairIdentity extends PublicKeyIdentity { }) } async verify(m: ArrayBuffer): Promise { - return false + throw new Error("Method not implemented.") } getCoseKey(): CoseKey { diff --git a/src/identity/webauthn/webauthn-identity.ts b/src/identity/webauthn/webauthn-identity.ts index b443b84..8b84627 100644 --- a/src/identity/webauthn/webauthn-identity.ts +++ b/src/identity/webauthn/webauthn-identity.ts @@ -49,7 +49,7 @@ export class WebAuthnIdentity extends PublicKeyIdentity { } async verify(_: ArrayBuffer): Promise { - return false + throw new Error("Method not implemented.") } static async getCredential(