Skip to content

Instantly share code, notes, and snippets.

@bshambaugh
Created November 24, 2022 19:15
Show Gist options
  • Save bshambaugh/b4373b0625ce7d504f26c337241c83ee to your computer and use it in GitHub Desktop.
Save bshambaugh/b4373b0625ce7d504f26c337241c83ee to your computer and use it in GitHub Desktop.
remoteT4 copy .ts
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