Comparing version 1.1.0 to 1.1.1
@@ -60,3 +60,67 @@ /// <reference types="node" /> | ||
}; | ||
/** | ||
* Validates a given Bitcoin address. | ||
* This method checks if the provided Bitcoin address is valid by attempting to decode it | ||
* for different Bitcoin networks: mainnet, testnet, and regtest. The method uses the | ||
* bitcoinjs-lib's address module for decoding. | ||
* | ||
* The process is as follows: | ||
* 1. Attempt to decode the address for the Bitcoin mainnet. If decoding succeeds, | ||
* the method returns true, indicating the address is valid for mainnet. | ||
* 2. If the first step fails, catch the resulting error and attempt to decode the | ||
* address for the Bitcoin testnet. If decoding succeeds, the method returns true, | ||
* indicating the address is valid for testnet. | ||
* 3. If the second step fails, catch the resulting error and attempt to decode the | ||
* address for the Bitcoin regtest network. If decoding succeeds, the method returns | ||
* true, indicating the address is valid for regtest. | ||
* 4. If all attempts fail, the method returns false, indicating the address is not valid | ||
* for any of the checked networks. | ||
* | ||
* @param address The Bitcoin address to validate. | ||
* @return boolean Returns true if the address is valid for any of the Bitcoin networks, | ||
* otherwise returns false. | ||
*/ | ||
static isValidBitcoinAddress(address: string): boolean; | ||
/** | ||
* Compresses an uncompressed public key using the elliptic curve secp256k1. | ||
* This method takes a public key in its uncompressed form and returns a compressed | ||
* representation of the public key. Elliptic curve public keys can be represented in | ||
* a shorter form known as compressed format which saves space and still retains the | ||
* full public key's capabilities. The method uses the elliptic library to convert the | ||
* uncompressed public key into its compressed form. | ||
* | ||
* The steps involved in the process are: | ||
* 1. Initialize a new elliptic curve instance for the secp256k1 curve. | ||
* 2. Create a key pair object from the uncompressed public key buffer. | ||
* 3. Extract the compressed public key from the key pair object. | ||
* 4. Return the compressed public key as a Buffer object. | ||
* | ||
* @param uncompressedPublicKey A Buffer containing the uncompressed public key. | ||
* @return Buffer Returns a Buffer containing the compressed public key. | ||
* @throws Error Throws an error if the provided public key cannot be compressed, | ||
* typically indicating that the key is not valid. | ||
*/ | ||
static compressPublicKey(uncompressedPublicKey: Buffer): Buffer; | ||
/** | ||
* Uncompresses a given public key using the elliptic curve secp256k1. | ||
* This method accepts a compressed public key and attempts to convert it into its | ||
* uncompressed form. Public keys are often compressed to save space, but certain | ||
* operations require the full uncompressed key. This method uses the elliptic | ||
* library to perform the conversion. | ||
* | ||
* The function operates as follows: | ||
* 1. Initialize a new elliptic curve instance using secp256k1. | ||
* 2. Attempt to create a key pair from the compressed public key buffer. | ||
* 3. Extract the uncompressed public key from the key pair object. | ||
* 4. Return the uncompressed public key as a Buffer object. | ||
* If the compressed public key provided is invalid and cannot be uncompressed, | ||
* the method will throw an error with a descriptive message. | ||
* | ||
* @param compressedPublicKey A Buffer containing the compressed public key. | ||
* @return Buffer The uncompressed public key as a Buffer. | ||
* @throws Error Throws an error if the provided public key cannot be uncompressed, | ||
* typically indicating that the key is not valid. | ||
*/ | ||
static uncompressPublicKey(compressedPublicKey: Buffer): Buffer; | ||
} | ||
export default Address; |
@@ -27,2 +27,3 @@ "use strict"; | ||
// Import dependency | ||
const elliptic_1 = require("elliptic"); | ||
const bitcoin = __importStar(require("bitcoinjs-lib")); | ||
@@ -218,4 +219,116 @@ /** | ||
} | ||
/** | ||
* Validates a given Bitcoin address. | ||
* This method checks if the provided Bitcoin address is valid by attempting to decode it | ||
* for different Bitcoin networks: mainnet, testnet, and regtest. The method uses the | ||
* bitcoinjs-lib's address module for decoding. | ||
* | ||
* The process is as follows: | ||
* 1. Attempt to decode the address for the Bitcoin mainnet. If decoding succeeds, | ||
* the method returns true, indicating the address is valid for mainnet. | ||
* 2. If the first step fails, catch the resulting error and attempt to decode the | ||
* address for the Bitcoin testnet. If decoding succeeds, the method returns true, | ||
* indicating the address is valid for testnet. | ||
* 3. If the second step fails, catch the resulting error and attempt to decode the | ||
* address for the Bitcoin regtest network. If decoding succeeds, the method returns | ||
* true, indicating the address is valid for regtest. | ||
* 4. If all attempts fail, the method returns false, indicating the address is not valid | ||
* for any of the checked networks. | ||
* | ||
* @param address The Bitcoin address to validate. | ||
* @return boolean Returns true if the address is valid for any of the Bitcoin networks, | ||
* otherwise returns false. | ||
*/ | ||
static isValidBitcoinAddress(address) { | ||
try { | ||
// Attempt to decode the address using bitcoinjs-lib's address module at mainnet | ||
bitcoin.address.toOutputScript(address, bitcoin.networks.bitcoin); | ||
return true; // If decoding succeeds, the address is valid | ||
} | ||
catch (error) { } | ||
try { | ||
// Attempt to decode the address using bitcoinjs-lib's address module at testnet | ||
bitcoin.address.toOutputScript(address, bitcoin.networks.testnet); | ||
return true; // If decoding succeeds, the address is valid | ||
} | ||
catch (error) { } | ||
try { | ||
// Attempt to decode the address using bitcoinjs-lib's address module at regtest | ||
bitcoin.address.toOutputScript(address, bitcoin.networks.regtest); | ||
return true; // If decoding succeeds, the address is valid | ||
} | ||
catch (error) { } | ||
return false; // Probably not a valid address | ||
} | ||
/** | ||
* Compresses an uncompressed public key using the elliptic curve secp256k1. | ||
* This method takes a public key in its uncompressed form and returns a compressed | ||
* representation of the public key. Elliptic curve public keys can be represented in | ||
* a shorter form known as compressed format which saves space and still retains the | ||
* full public key's capabilities. The method uses the elliptic library to convert the | ||
* uncompressed public key into its compressed form. | ||
* | ||
* The steps involved in the process are: | ||
* 1. Initialize a new elliptic curve instance for the secp256k1 curve. | ||
* 2. Create a key pair object from the uncompressed public key buffer. | ||
* 3. Extract the compressed public key from the key pair object. | ||
* 4. Return the compressed public key as a Buffer object. | ||
* | ||
* @param uncompressedPublicKey A Buffer containing the uncompressed public key. | ||
* @return Buffer Returns a Buffer containing the compressed public key. | ||
* @throws Error Throws an error if the provided public key cannot be compressed, | ||
* typically indicating that the key is not valid. | ||
*/ | ||
static compressPublicKey(uncompressedPublicKey) { | ||
// Initialize elliptic curve | ||
const ec = new elliptic_1.ec('secp256k1'); | ||
// Try to compress the provided public key | ||
try { | ||
// Create a key pair from the uncompressed public key buffer | ||
const keyPair = ec.keyFromPublic(Buffer.from(uncompressedPublicKey)); | ||
// Get the compressed public key as a Buffer | ||
const compressedPublicKey = Buffer.from(keyPair.getPublic(true, 'array')); | ||
return compressedPublicKey; | ||
} | ||
catch (err) { | ||
throw new Error('Fails to compress the provided public key. Please check if the provided key is a valid uncompressed public key.'); | ||
} | ||
} | ||
/** | ||
* Uncompresses a given public key using the elliptic curve secp256k1. | ||
* This method accepts a compressed public key and attempts to convert it into its | ||
* uncompressed form. Public keys are often compressed to save space, but certain | ||
* operations require the full uncompressed key. This method uses the elliptic | ||
* library to perform the conversion. | ||
* | ||
* The function operates as follows: | ||
* 1. Initialize a new elliptic curve instance using secp256k1. | ||
* 2. Attempt to create a key pair from the compressed public key buffer. | ||
* 3. Extract the uncompressed public key from the key pair object. | ||
* 4. Return the uncompressed public key as a Buffer object. | ||
* If the compressed public key provided is invalid and cannot be uncompressed, | ||
* the method will throw an error with a descriptive message. | ||
* | ||
* @param compressedPublicKey A Buffer containing the compressed public key. | ||
* @return Buffer The uncompressed public key as a Buffer. | ||
* @throws Error Throws an error if the provided public key cannot be uncompressed, | ||
* typically indicating that the key is not valid. | ||
*/ | ||
static uncompressPublicKey(compressedPublicKey) { | ||
// Initialize elliptic curve | ||
const ec = new elliptic_1.ec('secp256k1'); | ||
// Try to uncompress the provided public key | ||
try { | ||
// Create a key pair from the compressed public key buffer | ||
const keyPair = ec.keyFromPublic(Buffer.from(compressedPublicKey)); | ||
// Get the compressed public key as a Buffer | ||
const uncompressedPublicKey = Buffer.from(keyPair.getPublic(false, 'array')); | ||
return uncompressedPublicKey; | ||
} | ||
catch (err) { | ||
throw new Error('Fails to uncompress the provided public key. Please check if the provided key is a valid compressed public key.'); | ||
} | ||
} | ||
} | ||
exports.default = Address; | ||
//# sourceMappingURL=Address.js.map |
@@ -26,2 +26,22 @@ /** | ||
/** | ||
* Wraps the Bitcoin message verification process to avoid throwing exceptions. | ||
* This method attempts to verify a BIP-137 message using the provided address and | ||
* signature. It encapsulates the verification process within a try-catch block, | ||
* catching any errors that occur during verification and returning false instead | ||
* of allowing the exception to propagate. | ||
* | ||
* The process is as follows: | ||
* 1. The `bitcoinjs-message.verify` function is called with the message, address, | ||
* and signature provided in Base64 encoding. | ||
* 2. If the verification is successful, the method returns true. | ||
* 3. If any error occurs during the verification, the method catches the error | ||
* and returns false, signaling an unsuccessful verification. | ||
* | ||
* @param message The Bitcoin message to be verified. | ||
* @param address The Bitcoin address to which the message is allegedly signed. | ||
* @param signatureBase64 The Base64 encoded signature corresponding to the message. | ||
* @return boolean Returns true if the message is successfully verified, otherwise false. | ||
*/ | ||
private static bitcoinMessageVerifyWrap; | ||
/** | ||
* Compute the hash to be signed for a given P2WPKH BIP-322 toSign transaction. | ||
@@ -28,0 +48,0 @@ * @param toSignTx PSBT instance of the toSign transaction |
@@ -50,2 +50,6 @@ "use strict"; | ||
static verifySignature(signerAddress, message, signatureBase64) { | ||
// Check whether the given signerAddress is valid | ||
if (!helpers_1.Address.isValidBitcoinAddress(signerAddress)) { | ||
throw new Error("Invalid Bitcoin address is provided."); | ||
} | ||
// Handle legacy BIP-137 signature | ||
@@ -150,43 +154,92 @@ // For P2PKH address, assume the signature is also a legacy signature | ||
static verifyBIP137Signature(signerAddress, message, signatureBase64) { | ||
if (helpers_1.Address.isP2PKH(signerAddress)) { | ||
return bitcoinMessage.verify(message, signerAddress, signatureBase64); | ||
// Recover the public key associated with the signature | ||
const publicKeySignedRaw = helpers_1.BIP137.derivePubKey(message, signatureBase64); | ||
// Compress and uncompress the public key if necessary | ||
let publicKeySignedUncompressed; | ||
let publicKeySigned; | ||
if (publicKeySignedRaw.byteLength === 65) { | ||
publicKeySignedUncompressed = publicKeySignedRaw; // The key recovered is an uncompressed key | ||
publicKeySigned = helpers_1.Address.compressPublicKey(publicKeySignedRaw); | ||
} | ||
else { | ||
// Recover the public key associated with the signature | ||
const publicKeySigned = helpers_1.BIP137.derivePubKey(message, signatureBase64); | ||
// Set the equivalent legacy address to prepare for validation from bitcoinjs-message | ||
const legacySigningAddress = helpers_1.Address.convertPubKeyIntoAddress(publicKeySigned, 'p2pkh').mainnet; | ||
// Make sure that public key recovered corresponds to the claimed signing address | ||
if (helpers_1.Address.isP2SH(signerAddress)) { | ||
// Assume it is a P2SH-P2WPKH address, derive a P2SH-P2WPKH address based on the public key recovered | ||
const p2shAddressDerived = helpers_1.Address.convertPubKeyIntoAddress(publicKeySigned, 'p2sh-p2wpkh'); | ||
// Assert that the derived address is identical to the claimed signing address | ||
if (p2shAddressDerived.mainnet !== signerAddress && p2shAddressDerived.testnet !== signerAddress) { | ||
return false; // Derived address did not match with the claimed signing address | ||
} | ||
publicKeySignedUncompressed = helpers_1.Address.uncompressPublicKey(publicKeySignedRaw); | ||
publicKeySigned = publicKeySignedRaw; // The key recovered is a compressed key | ||
} | ||
// Obtain the equivalent signing address in all address types (except taproot) to prepare for validation from bitcoinjs-message | ||
// Taproot address is not needed since technically BIP-137 signatures does not support taproot address | ||
const p2pkhSigningAddressUncompressed = helpers_1.Address.convertPubKeyIntoAddress(publicKeySignedUncompressed, 'p2pkh').mainnet; | ||
const p2pkhSigningAddressCompressed = helpers_1.Address.convertPubKeyIntoAddress(publicKeySigned, 'p2pkh').mainnet; | ||
const p2shSigningAddress = helpers_1.Address.convertPubKeyIntoAddress(publicKeySigned, 'p2sh-p2wpkh').mainnet; | ||
const p2wpkhSigningAddress = helpers_1.Address.convertPubKeyIntoAddress(publicKeySigned, 'p2wpkh').mainnet; | ||
// Make sure that public key recovered corresponds to the claimed signing address | ||
if (helpers_1.Address.isP2PKH(signerAddress)) { | ||
// Derive P2PKH address from both the uncompressed raw public key, and the compressed public key | ||
const p2pkhAddressDerivedUncompressed = helpers_1.Address.convertPubKeyIntoAddress(publicKeySignedUncompressed, 'p2pkh'); | ||
const p2pkhAddressDerivedCompressed = helpers_1.Address.convertPubKeyIntoAddress(publicKeySigned, 'p2pkh'); | ||
// Assert that the derived address is identical to the claimed signing address | ||
if (p2pkhAddressDerivedUncompressed.mainnet !== signerAddress && p2pkhAddressDerivedUncompressed.testnet !== signerAddress && | ||
p2pkhAddressDerivedCompressed.mainnet !== signerAddress && p2pkhAddressDerivedCompressed.testnet !== signerAddress) { | ||
return false; // Derived address did not match with the claimed signing address | ||
} | ||
else if (helpers_1.Address.isP2WPKH(signerAddress)) { | ||
// Assume it is a P2WPKH address, derive a P2WPKH address based on the public key recovered | ||
const p2wpkhAddressDerived = helpers_1.Address.convertPubKeyIntoAddress(publicKeySigned, 'p2wpkh'); | ||
// Assert that the derived address is identical to the claimed signing address | ||
if (p2wpkhAddressDerived.mainnet !== signerAddress && p2wpkhAddressDerived.testnet !== signerAddress) { | ||
return false; // Derived address did not match with the claimed signing address | ||
} | ||
} | ||
else if (helpers_1.Address.isP2SH(signerAddress)) { | ||
// Assume it is a P2SH-P2WPKH address, derive a P2SH-P2WPKH address based on the public key recovered | ||
const p2shAddressDerived = helpers_1.Address.convertPubKeyIntoAddress(publicKeySigned, 'p2sh-p2wpkh'); | ||
// Assert that the derived address is identical to the claimed signing address | ||
if (p2shAddressDerived.mainnet !== signerAddress && p2shAddressDerived.testnet !== signerAddress) { | ||
return false; // Derived address did not match with the claimed signing address | ||
} | ||
else if (helpers_1.Address.isP2TR(signerAddress)) { | ||
// Assume it is a P2TR address, derive a P2TR address based on the public key recovered | ||
const p2trAddressDerived = helpers_1.Address.convertPubKeyIntoAddress(publicKeySigned, 'p2tr'); | ||
// Assert that the derived address is identical to the claimed signing address | ||
if (p2trAddressDerived.mainnet !== signerAddress && p2trAddressDerived.testnet !== signerAddress) { | ||
return false; // Derived address did not match with the claimed signing address | ||
} | ||
} | ||
else if (helpers_1.Address.isP2WPKH(signerAddress)) { | ||
// Assume it is a P2WPKH address, derive a P2WPKH address based on the public key recovered | ||
const p2wpkhAddressDerived = helpers_1.Address.convertPubKeyIntoAddress(publicKeySigned, 'p2wpkh'); | ||
// Assert that the derived address is identical to the claimed signing address | ||
if (p2wpkhAddressDerived.mainnet !== signerAddress && p2wpkhAddressDerived.testnet !== signerAddress) { | ||
return false; // Derived address did not match with the claimed signing address | ||
} | ||
else { | ||
return false; // Unsupported address type | ||
} | ||
else { | ||
// Assume it is a P2TR address, derive a P2TR address based on the public key recovered | ||
const p2trAddressDerived = helpers_1.Address.convertPubKeyIntoAddress(publicKeySigned, 'p2tr'); | ||
// Assert that the derived address is identical to the claimed signing address | ||
if (p2trAddressDerived.mainnet !== signerAddress && p2trAddressDerived.testnet !== signerAddress) { | ||
return false; // Derived address did not match with the claimed signing address | ||
} | ||
// Validate the signature using bitcoinjs-message if address assertion succeeded | ||
return bitcoinMessage.verify(message, legacySigningAddress, signatureBase64); | ||
} | ||
// Validate the signature using bitcoinjs-message if address assertion succeeded | ||
// Accept the signature if it originates from any address derivable from the public key | ||
const validity = (this.bitcoinMessageVerifyWrap(message, p2pkhSigningAddressUncompressed, signatureBase64) || | ||
this.bitcoinMessageVerifyWrap(message, p2pkhSigningAddressCompressed, signatureBase64) || | ||
this.bitcoinMessageVerifyWrap(message, p2shSigningAddress, signatureBase64) || | ||
this.bitcoinMessageVerifyWrap(message, p2wpkhSigningAddress, signatureBase64)); | ||
return validity; | ||
} | ||
/** | ||
* Wraps the Bitcoin message verification process to avoid throwing exceptions. | ||
* This method attempts to verify a BIP-137 message using the provided address and | ||
* signature. It encapsulates the verification process within a try-catch block, | ||
* catching any errors that occur during verification and returning false instead | ||
* of allowing the exception to propagate. | ||
* | ||
* The process is as follows: | ||
* 1. The `bitcoinjs-message.verify` function is called with the message, address, | ||
* and signature provided in Base64 encoding. | ||
* 2. If the verification is successful, the method returns true. | ||
* 3. If any error occurs during the verification, the method catches the error | ||
* and returns false, signaling an unsuccessful verification. | ||
* | ||
* @param message The Bitcoin message to be verified. | ||
* @param address The Bitcoin address to which the message is allegedly signed. | ||
* @param signatureBase64 The Base64 encoded signature corresponding to the message. | ||
* @return boolean Returns true if the message is successfully verified, otherwise false. | ||
*/ | ||
static bitcoinMessageVerifyWrap(message, address, signatureBase64) { | ||
try { | ||
return bitcoinMessage.verify(message, address, signatureBase64); | ||
} | ||
catch (err) { | ||
return false; // Instead of throwing, just return false | ||
} | ||
} | ||
/** | ||
* Compute the hash to be signed for a given P2WPKH BIP-322 toSign transaction. | ||
@@ -193,0 +246,0 @@ * @param toSignTx PSBT instance of the toSign transaction |
{ | ||
"name": "bip322-js", | ||
"version": "1.1.0", | ||
"version": "1.1.1", | ||
"description": "A Javascript library that provides utility functions related to the BIP-322 signature scheme", | ||
@@ -22,6 +22,10 @@ "main": "dist/index.js", | ||
"author": "Ken Sze <acken2@outlook.com>", | ||
"repository": "github:ACken2/bip322-js", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/ACken2/bip322-js.git" | ||
}, | ||
"license": "MIT", | ||
"devDependencies": { | ||
"@types/chai": "^4.3.5", | ||
"@types/elliptic": "^6.4.18", | ||
"@types/mocha": "^10.0.1", | ||
@@ -43,2 +47,3 @@ "@types/node": "^20.2.5", | ||
"ecpair": "^2.1.0", | ||
"elliptic": "^6.5.5", | ||
"fast-sha256": "^1.3.0", | ||
@@ -45,0 +50,0 @@ "secp256k1": "^5.0.0" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
117789
1669
0
7
12
+ Addedelliptic@^6.5.5