Created
November 24, 2022 19:15
-
-
Save bshambaugh/b4373b0625ce7d504f26c337241c83ee to your computer and use it in GitHub Desktop.
remoteT4 copy .ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Signer } from 'did-jwt' | |
import { createJWS } from 'did-jwt' | |
import stringify from 'fast-json-stable-stringify' | |
import type { | |
AuthParams, | |
CreateJWSParams, | |
DIDMethodName, | |
DIDProviderMethods, | |
DIDProvider, | |
GeneralJWS, | |
} from 'dids' | |
import { RPCError, createHandler } from 'rpc-utils' | |
import type { HandlerMethods, RPCRequest, RPCResponse, SendRequestFunc } from 'rpc-utils' | |
import { encodeDIDfromHexString, compressedKeyInHexfromRaw, didKeyURLtoPubKeyHex } from 'did-key-creator' | |
import { toString } from 'uint8arrays/to-string' | |
import * as http from 'http' | |
import * as WebSocket from 'websocket-stream' | |
//var WebSocketServer = require('ws').Server | |
//import { Server as WebSocketStream } from 'websocket-stream' | |
import * as nist_weierstrauss from 'nist-weierstrauss' | |
import {octetPoint} from 'nist-weierstrauss' | |
//import { P256Provider } from 'key-did-provider-p256' | |
import KeyResolver from 'key-did-resolver' | |
import { DID } from 'dids' | |
import {fromString} from 'uint8arrays' | |
import {hash} from '@stablelib/sha256' | |
import * as DNS from 'dns' | |
import * as OS from 'os' | |
import * as u8a from 'uint8arrays' | |
/// will adding a ceramic client cause it to 'splode due to a second http connection? | |
import { CeramicClient } from '@ceramicnetwork/http-client' | |
import {TileDocument} from '@ceramicnetwork/stream-tile' | |
const API_URL = "https://ceramic-clay.3boxlabs.com" // need to replace this with a local ceramic node for softAP | |
const ceramic = new CeramicClient(API_URL) | |
const server = http.createServer(); | |
const websocketServer = WebSocket.createServer({server: server}) | |
DNS.lookup(OS.hostname(), function (err, add, fam) { | |
console.log('addr: '+add); | |
}) | |
websocketServer.on('stream',function(stream,request) { | |
//stream.read(); | |
stream.setEncoding('utf8'); | |
const did = 'did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv'; | |
setTimeout(function(){ | |
(async function() { | |
const provider = await P256Provider.build(stream,did); | |
console.log(provider); | |
const didObject = new DID({ provider , resolver: KeyResolver.getResolver() }) | |
console.log(didObject); | |
// something funny happens around here....there is not a correct format for JWS | |
// also run your code that gets the signature and public key alone to make sure the hardware is working.... | |
const auth = await didObject.authenticate() | |
console.log('auth is'); | |
console.log(auth); | |
// this stuff may be a bit beyold the scope | |
ceramic.did = didObject | |
ceramic.did.setProvider(provider) | |
// use the didObject in your standard ceramic things | |
const schema = { | |
"$schema": "http://json-schema.org/draft-07/schema#", | |
"title": "Reward", | |
"type": "object", | |
"properties": { | |
"title": { "type": "string" }, | |
"message": { "type": "string" } | |
}, | |
"required": [ | |
"message", | |
"title" | |
] | |
} | |
const metadata = { | |
controllers: [ceramic.did.id] // this will set yourself as the controller of the schema | |
} | |
const rewardSchema = await TileDocument.create(ceramic, schema, metadata) | |
const reward = await TileDocument.create(ceramic, { | |
title: 'Hello', | |
message: 'world!' | |
}, { | |
controllers: [ceramic.did.id], | |
family: 'Rewards', | |
schema: rewardSchema.commitId.toString(), | |
}) | |
console.log('the reward.state is:') | |
console.log(reward.state) | |
console.log('the reward is') | |
console.log(reward) | |
console.log(reward.commitId.toString()); | |
console.log(reward.content.title); | |
})(); | |
},250); | |
}) | |
server.listen(3000); | |
async function getSignature(stream,data: string | Uint8Array) { | |
// getSignature should take a sha256hash as a hex string....or convert a uint8array to a hexstring | |
// I think that the string needs to be sha256ed before it gets signed....? | |
if(data.constructor === Uint8Array) { | |
console.log(data); | |
console.log('fed and uint8array'); | |
const signature = await signatureLogic(stream,data); | |
console.log('the signature is: '+signature) | |
return signature; | |
} else if (data.constructor === String) { | |
console.log('here is the signature') | |
console.log(data); | |
const u8toSign = u8a.fromString(data,'ascii') | |
console.log(u8toSign); | |
console.log('fed a string'); | |
const signature = await signatureLogic(stream,u8toSign); | |
console.log('the signature is: '+signature) | |
return signature; | |
} | |
} | |
async function signatureLogic(stream,data: Uint8Array) { | |
const input = hash(data); | |
console.log(input); | |
const inputHex = u8a.toString(input,'hex'); | |
console.log(inputHex); | |
stream.write('2'+'1200'+inputHex); | |
let result = (await waitForEvent(stream,'data')).toString(); | |
console.log('signatureresult'); | |
console.log(result); | |
console.log(signatureResultToUint8Array(result)); | |
let resultExit = bytesToBase64url(signatureResultToUint8Array(result)) | |
return resultExit; | |
} | |
export function signatureResultToUint8Array(a: string): Uint8Array { | |
// splot a string, and get the second half | |
const myArray = a.split(","); | |
if(myArray[0] == 'signature') { | |
const hex = myArray[1]; | |
const result = u8a.fromString(hex,'hex') | |
return result; | |
} else { | |
return undefined; | |
} | |
} | |
export function bytesToBase64url(b: Uint8Array): string { | |
return u8a.toString(b, 'base64url') | |
} | |
// I think that I have to close some listeners here....because I get to the maxListner limit | |
async function waitForEvent(emitter, event): Promise<string> { | |
return new Promise((resolve, reject) => { | |
emitter.once(event, resolve); | |
emitter.once("error", reject); | |
emitter.removeAllListeners("error"); /// I hope this is correct, it seems to stop the code from complaining about the maxListenerLimit being exceeded | |
}); | |
} | |
function remoteP256Signer(stream): Signer { | |
return async (payload: string | Uint8Array): Promise<string> => { | |
return await getSignature(stream,payload); | |
} | |
} | |
const sign = async ( | |
payload: Record<string, any> | string, | |
stream, | |
did: string, | |
protectedHeader: Record<string, any> = {} | |
) => { | |
const kid = `${did}#${did.split(':')[2]}` | |
// const kid = `${did}` | |
const signer = remoteP256Signer(stream) // see remoteP256Signer.ts // const signer = EdDSASigner(secretKey) | |
const header = toStableObject(Object.assign(protectedHeader, { kid, alg: 'ES256' })) /// see https://datatracker.ietf.org/doc/html/rfc7518 | |
return createJWS(typeof payload === 'string' ? payload : toStableObject(payload), signer, header) | |
} | |
function toStableObject(obj: Record<string, any>): Record<string, any> { | |
return JSON.parse(stringify(obj)) as Record<string, any> | |
} | |
function toGeneralJWS(jws: string): GeneralJWS { | |
const [protectedHeader, payload, signature] = jws.split('.') | |
return { | |
payload, | |
signatures: [{ protected: protectedHeader, signature }], | |
} | |
} | |
interface Context { | |
did: string, | |
stream: any | |
} | |
const didMethods: HandlerMethods<Context, DIDProviderMethods> = { | |
did_authenticate: async ({ did, stream }, params: AuthParams) => { | |
const response = await sign( | |
{ | |
did, | |
aud: params.aud, | |
nonce: params.nonce, | |
paths: params.paths, | |
exp: Math.floor(Date.now() / 1000) + 600, // expires 10 min from now | |
}, | |
stream, | |
did | |
) | |
return toGeneralJWS(response) | |
}, | |
did_createJWS: async ({ did, stream}, params: CreateJWSParams & { did: string }) => { | |
const requestDid = params.did.split('#')[0] | |
if (requestDid !== did) throw new RPCError(4100, `Unknown DID: ${did}`) | |
const jws = await sign(params.payload, stream, did, params.protected) | |
// const jws = await sign(params.payload, did, secretKey, params.protected) | |
return { jws: toGeneralJWS(jws) } | |
}, | |
did_decryptJWE: async () => { | |
throw new RPCError(4100, 'Decryption not supported') | |
}, | |
} | |
export class P256Provider implements DIDProvider { | |
_handle: SendRequestFunc<DIDProviderMethods> | |
private constructor(stream,did) { | |
const handler = createHandler<Context, DIDProviderMethods>(didMethods) | |
// this code has to check whether the did is valid for the remote ... but does the logic have to be here?? | |
this._handle = async (msg) => await handler({ did, stream }, msg) | |
} | |
public static async build(stream,did): Promise<P256Provider> { | |
const newDID = await matchDIDKeyWithRemote(did,stream); | |
did = newDID; | |
return new P256Provider(stream,did); | |
} | |
get isDidProvider(): boolean { | |
return true | |
} | |
async send<Name extends DIDMethodName>( | |
msg: RPCRequest<DIDProviderMethods, Name> | |
): Promise<RPCResponse<DIDProviderMethods, Name> | null> { | |
return await this._handle(msg) | |
} | |
} | |
async function matchDIDKeyWithRemote(didkeyURL: string,stream: any) : Promise<string> { | |
const compressedPublicKey = didKeyURLtoPubKeyHex(didkeyURL); | |
const publicKey = nist_weierstrauss.nist_weierstrauss_common.publicKeyIntToUint8ArrayPointPair(nist_weierstrauss.secp256r1.ECPointDecompress(fromString(compressedPublicKey,'hex'))) | |
const publicRawKey = octetToRaw(publicKey) | |
// console.log(publicRawKey); | |
let result = await matchPublicKeyWithRemote(publicRawKey,stream) | |
if(result.length > 1) { | |
return rpcToDID(result); | |
} else { | |
return didkeyURL; | |
} | |
} | |
async function matchPublicKeyWithRemote(publicKey: string,stream: any) : Promise<string> { | |
let rpcPayload = '0'+'1200'+publicKey; | |
stream.write(rpcPayload); | |
// console.log('rpcpaylod'+rpcPayload); | |
let result = await waitForEvent(stream,'data'); | |
// console.log('result is:'+result); | |
return result; | |
} | |
function octetToRaw(publicKey: octetPoint) { | |
return toString(publicKey.xOctet,'hex')+toString(publicKey.yOctet,'hex') | |
} | |
function rpcToDID(response) : string { | |
let result = response.split(','); | |
//compressedKeyInHexfromRaw(result[1]) | |
// return result[1]; | |
const multicodecName = 'p256-pub'; | |
return encodeDIDfromHexString(multicodecName,compressedKeyInHexfromRaw(result[1])) | |
} | |
export async function getPublicKey(stream) : Promise<string> { | |
/// look at the RPC call to get the public key | |
let rpcPayload = '1'+'1200'; | |
stream.write(rpcPayload); | |
let preResult = await waitForEvent(stream,'data'); | |
console.log(preResult); | |
let result = preResult.split(','); | |
return result[1]; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment