import { cborBackend } from "cbor-rpc"; import { CardanoKey } from "../lib/crypto/ed25519.browser"; import { CardanoKeyAsync } from "../lib/crypto/ed25519-async"; import { AlgorithmId, CoseKey } from "./CoseKey"; import { Buffer } from "buffer"; export { AlgorithmId, KeyType,CoseKey } from "./CoseKey"; /** * @see https://developers.cardano.org/docs/governance/cardano-improvement-proposals/cip-0008/ * @see https://datatracker.ietf.org/doc/html/rfc8152 */ type SignatureContext = "Signature1"; export interface CoseSignResult { coseSignature: CoseSign1, key: CoseKey toCip8Json: () => Cip8SignResult } export interface Cip8SignResult { signature: String; key: String } type ProtectedSerialized = Uint8Array; // Class representing header map with methods for setting, getting, and serialization export class HeaderMap { private readonly map: Map; constructor(map?: Map) { this.map = map ? map : new Map(); } setAlgorithmId(value: AlgorithmId) { this.map.set(1, value); } setHeader(key: any, value: any) { this.map.set(key, value); } serialize(): Uint8Array { return cborBackend.encode(this.map); } getData() { return this.map; } private static fromMap() { return new HeaderMap(); } } // Class representing headers with protected and unprotected parts export class Headers { constructor( public readonly protectedHeaders: ProtectedSerialized, public readonly unprotectedHeaders: HeaderMap ) { } } export class CoseSign1 { /** * * protected: This is as described in Section 3. * unprotected: This is as described in Section 3. * payload: This is as described in Section 4.1. * signature: This field contains the computed signature value. The * type of the field is a bstr. */ constructor( public readonly headers: Headers, public readonly payload: Buffer, public readonly rawSignature: Buffer ) {} /** * The CDDL fragment that represents the above text for COSE_Sign1 * follows. * COSE_Sign1 = [ * Headers, * payload : bstr / nil, * signature : bstr * ] */ toBytes(): Buffer { const data = [ this.headers.protectedHeaders, this.headers.unprotectedHeaders.getData(), this.payload, this.rawSignature, ]; return cborBackend.encode(data); } public async verify(edKey: CardanoKey | CardanoKeyAsync): Promise { const sigStructure = ["Signature1", this.headers.protectedHeaders, Buffer.from(""), this.payload]; const signedData = cborBackend.encode(sigStructure); return await edKey.verify(signedData, this.rawSignature); } public getAddress(): Buffer | undefined { const protectedHeaders = cborBackend.decode(Buffer.from(this.headers.protectedHeaders)) as Map; const address = protectedHeaders.get("address"); if (!address) { return undefined; } return Buffer.from(address); } public static fromBytes(rawSignature: Buffer): CoseSign1 { const data: any = cborBackend.decode(rawSignature); return new CoseSign1(new Headers(data[0], new HeaderMap(data[1])), data[2], data[3]); } public static builder(headers: Headers, payload: Buffer) { return new CoseSign1Builder(headers, payload); } } // Builder class for COSE_Sign1 structure export class CoseSign1Builder { constructor( private readonly headers: Headers, private readonly payload: Buffer ) { } // Create SigStructure instance for signing makeDataToSign(): Buffer { return cborBackend.encode([ "Signature1", this.headers.protectedHeaders, Buffer.from(new Uint8Array(0)), this.payload, ]); } build(signedSignatureStruc: Uint8Array) { return new CoseSign1(this.headers, this.payload, Buffer.from(signedSignatureStruc)); } } export async function cip8Sign(address: Buffer, privKey: CardanoKey | CardanoKeyAsync, payload: Buffer): Promise { const protectedHeaders = new HeaderMap(); protectedHeaders.setAlgorithmId(AlgorithmId.EDSA); protectedHeaders.setHeader("address", address); const protectedSerialized = protectedHeaders.serialize(); const unprotectedHeaders = new HeaderMap(); unprotectedHeaders.setHeader("hashed", false); const headers = new Headers(protectedSerialized, unprotectedHeaders); const builder = new CoseSign1Builder(headers, payload); const toSign = builder.makeDataToSign(); const signedSigStruc = await privKey.sign(Buffer.from(toSign)); const coseSign1 = builder.build(signedSigStruc); const coseKey = privKey.toCoseKey(); return { coseSignature: coseSign1, key: coseKey, toCip8Json: () => { return { signature: Buffer.from(coseSign1.toBytes()).toString("hex"), key: Buffer.from(coseKey.toBytes()).toString("hex"), }; } } }