UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

301 lines (280 loc) 8.57 kB
import { KeyDeriver, KeyDeriverApi } from './KeyDeriver.js' import CachedKeyDeriver from './CachedKeyDeriver.js' import { Hash, ECDSA, BigNumber, Signature, Schnorr, PublicKey, Point, PrivateKey } from '../primitives/index.js' import { CreateHmacArgs, CreateHmacResult, CreateSignatureArgs, CreateSignatureResult, GetPublicKeyArgs, PubKeyHex, RevealCounterpartyKeyLinkageArgs, RevealCounterpartyKeyLinkageResult, RevealSpecificKeyLinkageArgs, RevealSpecificKeyLinkageResult, VerifyHmacArgs, VerifyHmacResult, VerifySignatureArgs, VerifySignatureResult, WalletDecryptArgs, WalletDecryptResult, WalletEncryptArgs, WalletEncryptResult } from './Wallet.interfaces.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?: KeyDeriverApi constructor (rootKeyOrKeyDeriver?: PrivateKey | 'anyone' | KeyDeriverApi) { if (typeof (rootKeyOrKeyDeriver as KeyDeriver).identityKey !== 'string') { rootKeyOrKeyDeriver = new CachedKeyDeriver( rootKeyOrKeyDeriver as PrivateKey | 'anyone' ) } this.keyDeriver = rootKeyOrKeyDeriver as KeyDeriverApi } async getPublicKey ( args: GetPublicKeyArgs ): Promise<{ publicKey: PubKeyHex }> { 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: RevealCounterpartyKeyLinkageArgs ): Promise<RevealCounterpartyKeyLinkageResult> { 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() ] as number[] 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: RevealSpecificKeyLinkageArgs ): Promise<RevealSpecificKeyLinkageResult> { 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], // Proof type 0, no proof provided 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: WalletEncryptArgs ): Promise<WalletEncryptResult> { 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) as number[] } } async decrypt ( args: WalletDecryptArgs, originator?: string): Promise<WalletDecryptResult> { 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) as number[] } } async createHmac ( args: CreateHmacArgs ): Promise<CreateHmacResult> { 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: VerifyHmacArgs ): Promise<VerifyHmacResult> { 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') as Error & { code: string } e.code = 'ERR_INVALID_HMAC' throw e } return { valid } } async createSignature ( args: CreateSignatureArgs ): Promise<CreateSignatureResult> { if ((args.hashToDirectlySign == null) && (args.data == null)) { throw new Error('args.data or args.hashToDirectlySign must be valid') } const hash: number[] = 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() as number[] } } async verifySignature ( args: VerifySignatureArgs ): Promise<VerifySignatureResult> { if ((args.hashToDirectlyVerify == null) && (args.data == null)) { throw new Error('args.data or args.hashToDirectlyVerify must be valid') } const hash: number[] = 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') as Error & { code: string } e.code = 'ERR_INVALID_SIGNATURE' throw e } return { valid } } } export default ProtoWallet