@bsv/sdk
Version: 
BSV Blockchain Software Development Kit
182 lines • 7.71 kB
JavaScript
import CachedKeyDeriver from './CachedKeyDeriver.js';
import { Hash, ECDSA, BigNumber, Signature, Schnorr, PublicKey, Point } from '../primitives/index.js';
/**
 * A ProtoWallet is precursor to a full wallet, capable of performing all foundational cryptographic operations.
 * It can derive keys, create signatures, facilitate encryption and HMAC operations, and reveal key linkages.
 *
 * However, ProtoWallet does not create transactions, manage outputs, interact with the blockchain,
 * enable the management of identity certificates, or store any data. It is also not concerned with privileged keys.
 */
export class ProtoWallet {
    keyDeriver;
    constructor(rootKeyOrKeyDeriver) {
        if (typeof rootKeyOrKeyDeriver.identityKey !== 'string') {
            rootKeyOrKeyDeriver = new CachedKeyDeriver(rootKeyOrKeyDeriver);
        }
        this.keyDeriver = rootKeyOrKeyDeriver;
    }
    async getPublicKey(args) {
        if (args.identityKey) {
            if (this.keyDeriver == null) {
                throw new Error('keyDeriver is undefined');
            }
            return { publicKey: this.keyDeriver.rootKey.toPublicKey().toString() };
        }
        else {
            if (args.protocolID == null || args.keyID == null || args.keyID === '') {
                throw new Error('protocolID and keyID are required if identityKey is false or undefined.');
            }
            const keyDeriver = this.keyDeriver ??
                (() => {
                    throw new Error('keyDeriver is undefined');
                })();
            return {
                publicKey: keyDeriver
                    .derivePublicKey(args.protocolID, args.keyID, args.counterparty ?? 'self', args.forSelf)
                    .toString()
            };
        }
    }
    async revealCounterpartyKeyLinkage(args) {
        const { publicKey: identityKey } = await this.getPublicKey({
            identityKey: true
        });
        if (this.keyDeriver == null) {
            throw new Error('keyDeriver is undefined');
        }
        const linkage = this.keyDeriver.revealCounterpartySecret(args.counterparty);
        const linkageProof = new Schnorr().generateProof(this.keyDeriver.rootKey, this.keyDeriver.rootKey.toPublicKey(), PublicKey.fromString(args.counterparty), Point.fromDER(linkage));
        const linkageProofBin = [
            ...linkageProof.R.encode(true),
            ...linkageProof.SPrime.encode(true),
            ...linkageProof.z.toArray()
        ];
        const revelationTime = new Date().toISOString();
        const { ciphertext: encryptedLinkage } = await this.encrypt({
            plaintext: linkage,
            protocolID: [2, 'counterparty linkage revelation'],
            keyID: revelationTime,
            counterparty: args.verifier
        });
        const { ciphertext: encryptedLinkageProof } = await this.encrypt({
            plaintext: linkageProofBin,
            protocolID: [2, 'counterparty linkage revelation'],
            keyID: revelationTime,
            counterparty: args.verifier
        });
        return {
            prover: identityKey,
            verifier: args.verifier,
            counterparty: args.counterparty,
            revelationTime,
            encryptedLinkage,
            encryptedLinkageProof
        };
    }
    async revealSpecificKeyLinkage(args) {
        const { publicKey: identityKey } = await this.getPublicKey({
            identityKey: true
        });
        if (this.keyDeriver == null) {
            throw new Error('keyDeriver is undefined');
        }
        const linkage = this.keyDeriver.revealSpecificSecret(args.counterparty, args.protocolID, args.keyID);
        const { ciphertext: encryptedLinkage } = await this.encrypt({
            plaintext: linkage,
            protocolID: [
                2,
                `specific linkage revelation ${args.protocolID[0]} ${args.protocolID[1]}`
            ],
            keyID: args.keyID,
            counterparty: args.verifier
        });
        const { ciphertext: encryptedLinkageProof } = await this.encrypt({
            plaintext: [0],
            protocolID: [
                2,
                `specific linkage revelation ${args.protocolID[0]} ${args.protocolID[1]}`
            ],
            keyID: args.keyID,
            counterparty: args.verifier
        });
        return {
            prover: identityKey,
            verifier: args.verifier,
            counterparty: args.counterparty,
            protocolID: args.protocolID,
            keyID: args.keyID,
            encryptedLinkage,
            encryptedLinkageProof,
            proofType: 0
        };
    }
    async encrypt(args) {
        if (this.keyDeriver == null) {
            throw new Error('keyDeriver is undefined');
        }
        const key = this.keyDeriver.deriveSymmetricKey(args.protocolID, args.keyID, args.counterparty ?? 'self');
        return { ciphertext: key.encrypt(args.plaintext) };
    }
    async decrypt(args, originator) {
        if (this.keyDeriver == null) {
            throw new Error('keyDeriver is undefined');
        }
        const key = this.keyDeriver.deriveSymmetricKey(args.protocolID, args.keyID, args.counterparty ?? 'self');
        return { plaintext: key.decrypt(args.ciphertext) };
    }
    async createHmac(args) {
        if (this.keyDeriver == null) {
            throw new Error('keyDeriver is undefined');
        }
        const key = this.keyDeriver.deriveSymmetricKey(args.protocolID, args.keyID, args.counterparty ?? 'self');
        return { hmac: Hash.sha256hmac(key.toArray(), args.data) };
    }
    async verifyHmac(args) {
        if (this.keyDeriver == null) {
            throw new Error('keyDeriver is undefined');
        }
        const key = this.keyDeriver.deriveSymmetricKey(args.protocolID, args.keyID, args.counterparty ?? 'self');
        const valid = Hash.sha256hmac(key.toArray(), args.data).toString() ===
            args.hmac.toString();
        if (!valid) {
            const e = new Error('HMAC is not valid');
            e.code = 'ERR_INVALID_HMAC';
            throw e;
        }
        return { valid };
    }
    async createSignature(args) {
        if ((args.hashToDirectlySign == null) && (args.data == null)) {
            throw new Error('args.data or args.hashToDirectlySign must be valid');
        }
        const hash = args.hashToDirectlySign ?? Hash.sha256(args.data ?? []);
        const keyDeriver = this.keyDeriver ??
            (() => {
                throw new Error('keyDeriver is undefined');
            })();
        const key = keyDeriver.derivePrivateKey(args.protocolID, args.keyID, args.counterparty ?? 'anyone');
        return {
            signature: ECDSA.sign(new BigNumber(hash), key, true).toDER()
        };
    }
    async verifySignature(args) {
        if ((args.hashToDirectlyVerify == null) && (args.data == null)) {
            throw new Error('args.data or args.hashToDirectlyVerify must be valid');
        }
        const hash = args.hashToDirectlyVerify ?? Hash.sha256(args.data ?? []);
        const keyDeriver = this.keyDeriver ??
            (() => {
                throw new Error('keyDeriver is undefined');
            })();
        const key = keyDeriver.derivePublicKey(args.protocolID, args.keyID, args.counterparty ?? 'self', args.forSelf);
        const valid = ECDSA.verify(new BigNumber(hash), Signature.fromDER(args.signature), key);
        if (!valid) {
            const e = new Error('Signature is not valid');
            e.code = 'ERR_INVALID_SIGNATURE';
            throw e;
        }
        return { valid };
    }
}
export default ProtoWallet;
//# sourceMappingURL=ProtoWallet.js.map