@bsv/sdk
Version:
BSV Blockchain Software Development Kit
182 lines • 7.7 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) {
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