mirror of
https://github.com/transatoshi-mw/grin-web-wallet.git
synced 2025-10-06 15:52:47 +00:00
1195 lines
30 KiB
JavaScript
Executable File
1195 lines
30 KiB
JavaScript
Executable File
// Use strict
|
|
"use strict";
|
|
|
|
|
|
// Classes
|
|
|
|
// Crypto class
|
|
class Crypto {
|
|
|
|
// Public
|
|
|
|
// Derive child key
|
|
static deriveChildKey(extendedPrivateKey, paths, useBip39 = false) {
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Get secret key and chain code from extended private key
|
|
var secretKey = extendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH);
|
|
var chainCode = extendedPrivateKey.subarray(Crypto.CHAIN_CODE_LENGTH);
|
|
|
|
// Set update values
|
|
var updateValues = new Promise(function(resolve, reject) {
|
|
|
|
// Resolve secret key and chain code
|
|
resolve([
|
|
|
|
// Copy secret key
|
|
new Uint8Array(secretKey),
|
|
|
|
// Copy chain code
|
|
new Uint8Array(chainCode)
|
|
]);
|
|
});
|
|
|
|
// Initialize updating values
|
|
var updatingValues = [updateValues];
|
|
|
|
// Go through all of the paths
|
|
for(let i = 0; i < paths["length"]; ++i) {
|
|
|
|
// Set update to run after the previous update
|
|
updateValues = updateValues.then(function(values) {
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Get updated secret key and chain code
|
|
var updatedSecretKey = values[Crypto.SECRET_KEY_INDEX];
|
|
var updatedChainCode = values[Crypto.CHAIN_CODE_INDEX];
|
|
|
|
// Return deriving the next secret key and chain code using the path
|
|
return Crypto.deriveSecretKeyAndChainCode(updatedSecretKey, updatedChainCode, paths[i], useBip39).then(function(updatedValues) {
|
|
|
|
// Securely clear updated secret key and chain code
|
|
updatedSecretKey.fill(0);
|
|
updatedChainCode.fill(0);
|
|
|
|
// Resolve updated values
|
|
resolve(updatedValues);
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Securely clear secret key and chain code
|
|
updatedSecretKey.fill(0);
|
|
updatedChainCode.fill(0);
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Return Promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
|
|
// Append updating value to list
|
|
updatingValues.push(updateValues);
|
|
}
|
|
|
|
// Return updating all values
|
|
return Promise.all(updatingValues).then(function(values) {
|
|
|
|
// Get updated secret key and chain code
|
|
var updatedSecretKey = values[values["length"] - 1][Crypto.SECRET_KEY_INDEX];
|
|
var updatedChainCode = values[values["length"] - 1][Crypto.CHAIN_CODE_INDEX];
|
|
|
|
// Initialize new extended private key
|
|
var newExtendedPrivateKey = Common.mergeArrays([
|
|
|
|
// Updated secret key
|
|
updatedSecretKey,
|
|
|
|
// Chain code
|
|
updatedChainCode
|
|
]);
|
|
|
|
// Securely clear updated secret key and chain code
|
|
updatedSecretKey.fill(0);
|
|
updatedChainCode.fill(0);
|
|
|
|
// Check if new extended private key's secret key is a valid secret key
|
|
if(Secp256k1Zkp.isValidSecretKey(newExtendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH)) === true) {
|
|
|
|
// Resolve new extended private key
|
|
resolve(newExtendedPrivateKey);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Securely clear new extended private key
|
|
newExtendedPrivateKey.fill(0);
|
|
|
|
// Reject error
|
|
reject("Invalid extended private key.");
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Commit
|
|
static commit(extendedPrivateKey, amount, identifier, switchType) {
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Return deriving secret key from extended private key, amount, identifier, and switch type
|
|
return Crypto.deriveSecretKey(extendedPrivateKey, amount, identifier, switchType).then(function(secretKey) {
|
|
|
|
// Check if performing Pedersen commit with the secret key and amount was successful
|
|
var commit = Secp256k1Zkp.pedersenCommit(secretKey, amount.toFixed());
|
|
|
|
if(commit !== Secp256k1Zkp.OPERATION_FAILED) {
|
|
|
|
// Resolve commit
|
|
resolve(commit);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Securely clear secret key
|
|
secretKey.fill(0);
|
|
|
|
// Reject error
|
|
reject("Performing Pedersen commit failed.");
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Commit amount
|
|
static commitAmount(amount) {
|
|
|
|
// Check if performing Pedersen commit with zero blinding factor and amount was successful
|
|
var commit = Secp256k1Zkp.pedersenCommit(Crypto.ZERO_BLINDING_FACTOR, amount.toFixed());
|
|
|
|
if(commit !== Secp256k1Zkp.OPERATION_FAILED) {
|
|
|
|
// Return commit
|
|
return commit;
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Throw error
|
|
throw "Performing Pedersen commit failed.";
|
|
}
|
|
}
|
|
|
|
// Proof
|
|
static proof(extendedPrivateKey, amount, identifier, switchType, proofBuilder) {
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Return getting commit from extended private key, amount, identifier, and switch type
|
|
return Crypto.commit(extendedPrivateKey, amount, identifier, switchType).then(function(commit) {
|
|
|
|
// Return deriving secret key from extended private key, amount, identifier, and switch type
|
|
return Crypto.deriveSecretKey(extendedPrivateKey, amount, identifier, switchType).then(function(secretKey) {
|
|
|
|
// Return getting rewind nonce from the commit
|
|
return proofBuilder.rewindNonce(commit).then(function(rewindNonce) {
|
|
|
|
// Return getting private nonce from the commit
|
|
return proofBuilder.privateNonce(commit).then(function(privateNonce) {
|
|
|
|
// Try
|
|
try {
|
|
|
|
// Get proof message from identifier and switch type
|
|
var message = proofBuilder.proofMessage(identifier, switchType);
|
|
}
|
|
|
|
// Catch errors
|
|
catch(error) {
|
|
|
|
// Securely clear secret key
|
|
secretKey.fill(0);
|
|
|
|
// Securely clear rewind nonce
|
|
rewindNonce.fill(0);
|
|
|
|
// Securely clear private nonce
|
|
privateNonce.fill(0);
|
|
|
|
// Reject error
|
|
reject("Getting proof message failed.");
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Check if creating bulletproof with the secret key, amount, rewind nonce, private nonce, and message was successful
|
|
var bulletproof = Secp256k1Zkp.createBulletproof(secretKey, amount.toFixed(), rewindNonce, privateNonce, new Uint8Array([]), message);
|
|
|
|
if(bulletproof !== Secp256k1Zkp.OPERATION_FAILED) {
|
|
|
|
// Securely clear secret key
|
|
secretKey.fill(0);
|
|
|
|
// Securely clear rewind nonce
|
|
rewindNonce.fill(0);
|
|
|
|
// Securely clear private nonce
|
|
privateNonce.fill(0);
|
|
|
|
// Resolve bulletproof
|
|
resolve(bulletproof);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Securely clear secret key
|
|
secretKey.fill(0);
|
|
|
|
// Securely clear rewind nonce
|
|
rewindNonce.fill(0);
|
|
|
|
// Securely clear private nonce
|
|
privateNonce.fill(0);
|
|
|
|
// Reject error
|
|
reject("Getting bulletproof failed.");
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Securely clear secret key
|
|
secretKey.fill(0);
|
|
|
|
// Securely clear rewind nonce
|
|
rewindNonce.fill(0);
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Securely clear secret key
|
|
secretKey.fill(0);
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Derive secret key
|
|
static deriveSecretKey(extendedPrivateKey, amount, identifier, switchType) {
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Initialize paths
|
|
var paths = new Uint32Array(identifier.getDepth());
|
|
|
|
// Go through all the identifier's paths
|
|
for(var i = 0; i < identifier.getDepth(); ++i) {
|
|
|
|
// Set path to identifier's path
|
|
paths[i] = identifier.getPaths()[i];
|
|
}
|
|
|
|
// Return deriving the child key at the path from the extended private key
|
|
return Crypto.deriveChildKey(extendedPrivateKey, paths).then(function(childKey) {
|
|
|
|
// Get secret key from the child key
|
|
var secretKey = new Uint8Array(childKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH));
|
|
|
|
// Securely clear child key
|
|
childKey.fill(0);
|
|
|
|
// Check if switch type is none
|
|
if(switchType === Crypto.SWITCH_TYPE_NONE)
|
|
|
|
// Resolve secret key
|
|
resolve(secretKey);
|
|
|
|
// Otherwise check if switch type is regular
|
|
else if(switchType === Crypto.SWITCH_TYPE_REGULAR) {
|
|
|
|
// Check if getting blind switch of secret key and amount failed
|
|
var blindSwitch = Secp256k1Zkp.blindSwitch(secretKey, amount.toFixed());
|
|
|
|
if(blindSwitch === Secp256k1Zkp.OPERATION_FAILED) {
|
|
|
|
// Securely clear secret key
|
|
secretKey.fill(0);
|
|
|
|
// Reject error
|
|
reject("Performing blind switch failed.");
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Securely clear secret key
|
|
secretKey.fill(0);
|
|
|
|
// Check if blind switch is a valid secret key
|
|
if(Secp256k1Zkp.isValidSecretKey(blindSwitch) === true) {
|
|
|
|
// Resolve blind switch
|
|
resolve(blindSwitch);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Securely clear blind switch
|
|
blindSwitch.fill(0);
|
|
|
|
// Reject error
|
|
reject("Blind switch isn't a valid secret key.");
|
|
}
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Securely clear secret key
|
|
secretKey.fill(0);
|
|
|
|
// Reject error
|
|
reject("Invalid switch type.");
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Signature message
|
|
static signatureMessage(data) {
|
|
|
|
// Check if creating message was successful
|
|
var message = Blake2b.compute(Crypto.SINGLE_SIGNER_MESSAGE_LENGTH, data, new Uint8Array([]));
|
|
|
|
if(message !== Blake2b.OPERATION_FAILED) {
|
|
|
|
// Return message
|
|
return message;
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Throw error
|
|
throw "Creating signature message failed.";
|
|
}
|
|
}
|
|
|
|
// Root public key
|
|
static rootPublicKey(extendedPrivateKey) {
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Get secret key from extended private key
|
|
var secretKey = extendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH);
|
|
|
|
// Check if getting public key from secret key was successful
|
|
var publicKey = Secp256k1Zkp.publicKeyFromSecretKey(secretKey);
|
|
|
|
if(publicKey !== Secp256k1Zkp.OPERATION_FAILED) {
|
|
|
|
// Resolve public key
|
|
resolve(publicKey);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Reject error
|
|
reject("Getting public key failed.");
|
|
}
|
|
});
|
|
}
|
|
|
|
// Address key
|
|
static addressKey(extendedPrivateKey, index) {
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Check wallet type
|
|
switch(Consensus.getWalletType()) {
|
|
|
|
// MWC wallet
|
|
case Consensus.MWC_WALLET_TYPE:
|
|
|
|
// Return deriving root key from extended private key, address key amount, root identifier, and regular switch type
|
|
return Crypto.deriveSecretKey(extendedPrivateKey, Crypto.ADDRESS_KEY_AMOUNT, new Identifier(Identifier.ROOT_SERIALIZED_IDENTIFIER), Crypto.SWITCH_TYPE_REGULAR).then(function(rootKey) {
|
|
|
|
// Return creating crypto key from address key seed
|
|
return crypto["subtle"].importKey("raw", (new TextEncoder()).encode(Crypto.ADDRESS_KEY_SEED), {
|
|
|
|
// Name
|
|
"name": Crypto.ADDRESS_KEY_ENCRYPTION_ALGORITHM,
|
|
|
|
// Hash
|
|
"hash": {
|
|
|
|
// Name
|
|
"name": Crypto.ADDRESS_KEY_DIGEST_ALGORITHM
|
|
}
|
|
}, false, [
|
|
|
|
// Sign
|
|
"sign"
|
|
|
|
]).then(function(cryptoKey) {
|
|
|
|
// Return creating address extended private key from signing the root key
|
|
return crypto["subtle"].sign(Crypto.ADDRESS_KEY_ENCRYPTION_ALGORITHM, cryptoKey, rootKey).then(function(addressExtendedPrivateKey) {
|
|
|
|
// Securely clear root key
|
|
rootKey.fill(0);
|
|
|
|
// Get address extended private key in correct format
|
|
addressExtendedPrivateKey = new Uint8Array(addressExtendedPrivateKey);
|
|
|
|
// Get secret key and chain code from address extended private key
|
|
var secretKey = addressExtendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH);
|
|
var chainCode = addressExtendedPrivateKey.subarray(Crypto.CHAIN_CODE_LENGTH);
|
|
|
|
// Check if secret key is a valid secret key
|
|
if(Secp256k1Zkp.isValidSecretKey(secretKey) === true) {
|
|
|
|
// Return updating the next secret key and chain code using the index
|
|
return Crypto.deriveSecretKeyAndChainCode(secretKey, chainCode, index).then(function(value) {
|
|
|
|
// Securely clear address extended private key
|
|
addressExtendedPrivateKey.fill(0);
|
|
|
|
// Securely clear the chain code
|
|
value[Crypto.CHAIN_CODE_INDEX].fill(0);
|
|
|
|
// Resolve the secret key
|
|
resolve(value[Crypto.SECRET_KEY_INDEX]);
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Securely clear address extended private key
|
|
addressExtendedPrivateKey.fill(0);
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Securely clear address extended private key
|
|
addressExtendedPrivateKey.fill(0);
|
|
|
|
// Reject error
|
|
reject("Secret key is not a valid secret key.");
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Securely clear root key
|
|
rootKey.fill(0);
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Securely clear root key
|
|
rootKey.fill(0);
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
|
|
// GRIN or EPIC wallet
|
|
case Consensus.GRIN_WALLET_TYPE:
|
|
case Consensus.EPIC_WALLET_TYPE:
|
|
|
|
// Create child identifier
|
|
var childIdentifier = new Identifier().getChild();
|
|
childIdentifier.getPaths()[1] = 1;
|
|
childIdentifier.getPaths()[childIdentifier.getDepth() - 1] = index;
|
|
|
|
// Return deriving root key from extended private key, child identifier, and no switch type
|
|
return Crypto.deriveSecretKey(extendedPrivateKey, new BigNumber(0), childIdentifier, Crypto.SWITCH_TYPE_NONE).then(function(rootKey) {
|
|
|
|
// Check if getting secret key from root key was successful
|
|
var secretKey = Blake2b.compute(Crypto.SECP256K1_SECRET_KEY_LENGTH, rootKey, new Uint8Array([]));
|
|
|
|
if(secretKey !== Blake2b.OPERATION_FAILED) {
|
|
|
|
// Securely clear root key
|
|
rootKey.fill(0);
|
|
|
|
// Check if secret key is a valid secret key
|
|
if(Secp256k1Zkp.isValidSecretKey(secretKey) === true) {
|
|
|
|
// Resolve the secret key
|
|
resolve(secretKey);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Securely clear secret key
|
|
secretKey.fill(0);
|
|
|
|
// Reject error
|
|
reject("Secret key is not a valid secret key.");
|
|
}
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Securely clear root key
|
|
rootKey.fill(0);
|
|
|
|
// Reject error
|
|
reject("Getting secret key from root key failed.");
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Login key
|
|
static loginKey(extendedPrivateKey) {
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Create child identifier
|
|
var childIdentifier = new Identifier().getChild();
|
|
childIdentifier.getPaths()[1] = 2;
|
|
|
|
// Return deriving root key from extended private key, child identifier, and no switch type
|
|
return Crypto.deriveSecretKey(extendedPrivateKey, new BigNumber(0), childIdentifier, Crypto.SWITCH_TYPE_NONE).then(function(rootKey) {
|
|
|
|
// Check if getting secret key from root key was successful
|
|
var secretKey = Blake2b.compute(Crypto.SECP256K1_SECRET_KEY_LENGTH, rootKey, new Uint8Array([]));
|
|
|
|
if(secretKey !== Blake2b.OPERATION_FAILED) {
|
|
|
|
// Securely clear root key
|
|
rootKey.fill(0);
|
|
|
|
// Check if secret key is a valid secret key
|
|
if(Secp256k1Zkp.isValidSecretKey(secretKey) === true) {
|
|
|
|
// Resolve the secret key
|
|
resolve(secretKey);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Securely clear secret key
|
|
secretKey.fill(0);
|
|
|
|
// Reject error
|
|
reject("Secret key is not a valid secret key.");
|
|
}
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Securely clear root key
|
|
rootKey.fill(0);
|
|
|
|
// Reject error
|
|
reject("Getting secret key from root key failed.");
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
// AES decrypt
|
|
static aesDecrypt(encryptedData, key) {
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Return importing the key
|
|
return crypto["subtle"].importKey("raw", key, {
|
|
|
|
// Name
|
|
"name": Crypto.AES_ALGORITHM
|
|
|
|
}, false, [
|
|
|
|
// Decrypt
|
|
"decrypt"
|
|
|
|
]).then(function(cryptoKey) {
|
|
|
|
// Return decrypting the encrypted data using the AES key
|
|
return crypto["subtle"].decrypt({
|
|
|
|
// Name
|
|
"name": Crypto.AES_ALGORITHM,
|
|
|
|
// Initialization vector
|
|
"iv": (new Uint8Array(Crypto.AES_INITIALIZATION_VECTOR_SIZE)).fill(0)
|
|
|
|
}, cryptoKey, encryptedData).then(function(decryptedData) {
|
|
|
|
// TODO Securely clear cryptoKey
|
|
|
|
// Resolve decrypted data
|
|
resolve(new Uint8Array(decryptedData));
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// TODO Securely clear cryptoKey
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Switch type none
|
|
static get SWITCH_TYPE_NONE() {
|
|
|
|
// Return switch type none
|
|
return 0;
|
|
}
|
|
|
|
// Switch type regular
|
|
static get SWITCH_TYPE_REGULAR() {
|
|
|
|
// Return switch type regular
|
|
return Crypto.SWITCH_TYPE_NONE + 1;
|
|
}
|
|
|
|
// Secp256k1 secret key length
|
|
static get SECP256K1_SECRET_KEY_LENGTH() {
|
|
|
|
// Return secp256k1 secret key length
|
|
return 32;
|
|
}
|
|
|
|
// Secp256k1 public key length
|
|
static get SECP256K1_PUBLIC_KEY_LENGTH() {
|
|
|
|
// Return secp256k1 public key length
|
|
return 33;
|
|
}
|
|
|
|
// Blinding factor length
|
|
static get BLINDING_FACTOR_LENGTH() {
|
|
|
|
// Return blinding factor length
|
|
return 32;
|
|
}
|
|
|
|
// Commit length
|
|
static get COMMIT_LENGTH() {
|
|
|
|
// Return commit length
|
|
return 33;
|
|
}
|
|
|
|
// Single-signer signature length
|
|
static get SINGLE_SIGNER_SIGNATURE_LENGTH() {
|
|
|
|
// Return single-signer signature length
|
|
return 64;
|
|
}
|
|
|
|
// Single-signer message length
|
|
static get SINGLE_SIGNER_MESSAGE_LENGTH() {
|
|
|
|
// Return single-signer message length
|
|
return 32;
|
|
}
|
|
|
|
// Proof length
|
|
static get PROOF_LENGTH() {
|
|
|
|
// Return proof length
|
|
return 675;
|
|
}
|
|
|
|
// Zero blinding factor
|
|
static get ZERO_BLINDING_FACTOR() {
|
|
|
|
// No zero blinding factor
|
|
return (new Uint8Array(Crypto.BLINDING_FACTOR_LENGTH)).fill(0);
|
|
}
|
|
|
|
// Zero secret key
|
|
static get ZERO_SECRET_KEY() {
|
|
|
|
// No zero blinding factor
|
|
return (new Uint8Array(Crypto.SECP256K1_SECRET_KEY_LENGTH)).fill(0);
|
|
}
|
|
|
|
// Ed25519 secret key length
|
|
static get ED25519_SECRET_KEY_LENGTH() {
|
|
|
|
// Return Ed25519 secret key length
|
|
return 32;
|
|
}
|
|
|
|
// Ed25519 public key length
|
|
static get ED25519_PUBLIC_KEY_LENGTH() {
|
|
|
|
// Return Ed25519 public key length
|
|
return 32;
|
|
}
|
|
|
|
// X25519 secret key length
|
|
static get X25519_SECRET_KEY_LENGTH() {
|
|
|
|
// Return X25519 secret key length
|
|
return 32;
|
|
}
|
|
|
|
// X25519 public key length
|
|
static get X25519_PUBLIC_KEY_LENGTH() {
|
|
|
|
// Return X25519 public key length
|
|
return 32;
|
|
}
|
|
|
|
// Ed25519 signature length
|
|
static get ED25519_SIGNATURE_LENGTH() {
|
|
|
|
// Return Ed25519 signature length
|
|
return 64;
|
|
}
|
|
|
|
// Maximum message hash signature length
|
|
static get MAXIMUM_MESSAGE_HASH_SIGNATURE_LENGTH() {
|
|
|
|
// Return maximum message hash signature length
|
|
return 72;
|
|
}
|
|
|
|
// Nonce length
|
|
static get NONCE_LENGTH() {
|
|
|
|
// Return nonce length
|
|
return 32;
|
|
}
|
|
|
|
// Tau x length
|
|
static get TAU_X_LENGTH() {
|
|
|
|
// Return tau x length
|
|
return 32;
|
|
}
|
|
|
|
// AES key length
|
|
static get AES_KEY_LENGTH() {
|
|
|
|
// Return AES key length
|
|
return 32;
|
|
}
|
|
|
|
// Private
|
|
|
|
// Derive secret key and chain code
|
|
static deriveSecretKeyAndChainCode(secretKey, chainCode, path, useBip39 = false) {
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Return creating crypto key from chain code
|
|
return crypto["subtle"].importKey("raw", chainCode, {
|
|
|
|
// Name
|
|
"name": Crypto.CHAIN_CODE_ENCRYPTION_ALGORITHM,
|
|
|
|
// Hash
|
|
"hash": {
|
|
|
|
// Name
|
|
"name": Crypto.CHAIN_CODE_DIGEST_ALGORITHM
|
|
}
|
|
}, false, [
|
|
|
|
// Sign
|
|
"sign"
|
|
|
|
]).then(function(cryptoKey) {
|
|
|
|
// Check if path is hardened
|
|
if(Identifier.isPathHardened(path) === true) {
|
|
|
|
// Get hash value from secret key
|
|
var hashValue = Common.mergeArrays([
|
|
|
|
// Zero
|
|
new Uint8Array([0]),
|
|
|
|
// Secret key
|
|
secretKey
|
|
]);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Check if getting hash value as public key from secret key failed
|
|
var hashValue = Secp256k1Zkp.publicKeyFromSecretKey(secretKey);
|
|
|
|
if(hashValue === Secp256k1Zkp.OPERATION_FAILED) {
|
|
|
|
// TODO Securely clear cryptoKey
|
|
|
|
// Reject error
|
|
reject("Getting public key from secret key failed.");
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Create secret key and chain code
|
|
var createSecretKeyAndChainCode = function(hashValueCandidate) {
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Append path to hash value candidate
|
|
var pathBuffer = new ArrayBuffer(Uint32Array["BYTES_PER_ELEMENT"]);
|
|
|
|
var pathBufferView = new DataView(pathBuffer);
|
|
pathBufferView.setUint32(0, path, false);
|
|
|
|
var newHashValue = Common.mergeArrays([
|
|
|
|
// Hash value candidate
|
|
hashValueCandidate,
|
|
|
|
// Path
|
|
new Uint8Array(pathBuffer)
|
|
]);
|
|
|
|
// Securely clear hash value candidate
|
|
hashValueCandidate.fill(0);
|
|
|
|
// Return creating new extended private key from signing the new hash value
|
|
return crypto["subtle"].sign(Crypto.CHAIN_CODE_ENCRYPTION_ALGORITHM, cryptoKey, newHashValue).then(function(extendedPrivateKey) {
|
|
|
|
// Securely clear new hash value
|
|
newHashValue.fill(0);
|
|
|
|
// Get extended private key in correct format
|
|
extendedPrivateKey = new Uint8Array(extendedPrivateKey);
|
|
|
|
// Get new secret key from the extended private key
|
|
var newSecretKey = extendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH);
|
|
|
|
// Check if new secret key is a valid secret key or using BIP39 and new secret key is zero
|
|
if(Secp256k1Zkp.isValidSecretKey(newSecretKey) === true || (useBip39 === true && Common.arraysAreEqualTimingSafe(newSecretKey, Crypto.ZERO_SECRET_KEY) === true)) {
|
|
|
|
// Check if adding old secret key to the new secret key was successful
|
|
var updatedSecretKey = Secp256k1Zkp.secretKeyTweakAdd(newSecretKey, secretKey);
|
|
|
|
if(updatedSecretKey !== Secp256k1Zkp.OPERATION_FAILED) {
|
|
|
|
// Get updated chain code from the extended private key
|
|
var updatedChainCode = new Uint8Array(extendedPrivateKey.subarray(Crypto.SECP256K1_SECRET_KEY_LENGTH));
|
|
|
|
// Securely clear extended private key
|
|
extendedPrivateKey.fill(0);
|
|
|
|
// Resolve new extended private key
|
|
resolve([
|
|
|
|
// Updated secret key
|
|
updatedSecretKey,
|
|
|
|
// Updated chain code
|
|
updatedChainCode
|
|
]);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Check if using BIP39
|
|
if(useBip39 === true) {
|
|
|
|
// Set next hash value candidate
|
|
var nextHashValueCandidate = Common.mergeArrays([
|
|
|
|
// One
|
|
new Uint8Array([1]),
|
|
|
|
// New chain code
|
|
extendedPrivateKey.subarray(Crypto.SECP256K1_SECRET_KEY_LENGTH)
|
|
]);
|
|
|
|
// Securely clear extended private key
|
|
extendedPrivateKey.fill(0);
|
|
|
|
// Return creating secret key and chain code
|
|
return createSecretKeyAndChainCode(nextHashValueCandidate).then(function(secretKeyAndChainCode) {
|
|
|
|
// Resolve secret key and chain code
|
|
resolve(secretKeyAndChainCode);
|
|
|
|
// Catch error
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Securely clear extended private key
|
|
extendedPrivateKey.fill(0);
|
|
|
|
// Reject error
|
|
reject("Performing secret key tweak add failed.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Check if using BIP39
|
|
if(useBip39 === true) {
|
|
|
|
// Set next hash value candidate
|
|
var nextHashValueCandidate = Common.mergeArrays([
|
|
|
|
// One
|
|
new Uint8Array([1]),
|
|
|
|
// New chain code
|
|
extendedPrivateKey.subarray(Crypto.SECP256K1_SECRET_KEY_LENGTH)
|
|
]);
|
|
|
|
// Securely clear extended private key
|
|
extendedPrivateKey.fill(0);
|
|
|
|
// Return creating secret key and chain code
|
|
return createSecretKeyAndChainCode(nextHashValueCandidate).then(function(secretKeyAndChainCode) {
|
|
|
|
// Resolve secret key and chain code
|
|
resolve(secretKeyAndChainCode);
|
|
|
|
// Catch error
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Securely clear extended private key
|
|
extendedPrivateKey.fill(0);
|
|
|
|
// Reject error
|
|
reject("New secret key isn't a valid secret key or using BIP39 and new secret key isn't zero.");
|
|
}
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Securely clear new hash value
|
|
newHashValue.fill(0);
|
|
|
|
// Reject error
|
|
reject("Creating new extended private key failed.");
|
|
});
|
|
});
|
|
};
|
|
|
|
// Return creating secret key and chain code
|
|
return createSecretKeyAndChainCode(hashValue).then(function(secretKeyAndChainCode) {
|
|
|
|
// TODO Securely clear cryptoKey
|
|
|
|
// Resolve secret key and chain code
|
|
resolve(secretKeyAndChainCode);
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// TODO Securely clear cryptoKey
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject("Creating crypto key failed.");
|
|
});
|
|
});
|
|
}
|
|
|
|
// Chain code length
|
|
static get CHAIN_CODE_LENGTH() {
|
|
|
|
// Return chain code length
|
|
return 32;
|
|
}
|
|
|
|
// Secret key index
|
|
static get SECRET_KEY_INDEX() {
|
|
|
|
// Return secret key index
|
|
return 0;
|
|
}
|
|
|
|
// Chain code index
|
|
static get CHAIN_CODE_INDEX() {
|
|
|
|
// Return chain code index
|
|
return Crypto.SECRET_KEY_INDEX + 1;
|
|
}
|
|
|
|
// Chain code encryption algorithm
|
|
static get CHAIN_CODE_ENCRYPTION_ALGORITHM() {
|
|
|
|
// Return chain code encryption algorithm
|
|
return "HMAC";
|
|
}
|
|
|
|
// Chain code digest algorithm
|
|
static get CHAIN_CODE_DIGEST_ALGORITHM() {
|
|
|
|
// Return chain code digest algorithm
|
|
return "SHA-512";
|
|
}
|
|
|
|
// Address key amount
|
|
static get ADDRESS_KEY_AMOUNT() {
|
|
|
|
// Return address key amount
|
|
return new BigNumber(713);
|
|
}
|
|
|
|
// Address key seed
|
|
static get ADDRESS_KEY_SEED() {
|
|
|
|
// Return address key seed
|
|
return "Grinbox_seed";
|
|
}
|
|
|
|
// Address key encryption algorithm
|
|
static get ADDRESS_KEY_ENCRYPTION_ALGORITHM() {
|
|
|
|
// Return address key encryption algorithm
|
|
return "HMAC";
|
|
}
|
|
|
|
// Address key digest algorithm
|
|
static get ADDRESS_KEY_DIGEST_ALGORITHM() {
|
|
|
|
// Return address key digest algorithm
|
|
return "SHA-512";
|
|
}
|
|
|
|
// AES algorithm
|
|
static get AES_ALGORITHM() {
|
|
|
|
// Return AES algorithm
|
|
return "AES-CBC";
|
|
}
|
|
|
|
// AES initialization vector size
|
|
static get AES_INITIALIZATION_VECTOR_SIZE() {
|
|
|
|
// Return AES initialization vector size
|
|
return 16;
|
|
}
|
|
}
|
|
|
|
|
|
// Main function
|
|
|
|
// Set global object's crypto
|
|
globalThis["Crypto"] = Crypto;
|