cardano-crypto.js
Advanced tools
Comparing version 4.0.2 to 4.1.0
175
index.js
const bip39 = require('bip39-light') | ||
const cbor = require('cbor') | ||
@@ -8,2 +9,3 @@ const Module = require('./lib.js') | ||
const pbkdf2 = require('./utils/pbkdf2') | ||
const CborIndefiniteLengthArray = require('./utils/CborIndefiniteLengthArray') | ||
@@ -54,2 +56,27 @@ const HARDENED_THRESHOLD = 0x80000000 | ||
function validateMnemonicWords(input) { | ||
const wordlist = bip39.wordlists.EN | ||
const words = input.split(' ') | ||
const valid = words.reduce((result, word) => { | ||
return result && wordlist.indexOf(word) !== -1 | ||
}, true) | ||
if (!valid) { | ||
throw new Error('Invalid mnemonic words') | ||
} | ||
} | ||
function validatePaperWalletMnemonic(input) { | ||
validateMnemonicWords(input) | ||
const mnemonicLength = input.split(' ').length | ||
if (mnemonicLength !== 27) { | ||
throw Error( | ||
`Paper Wallet Mnemonic must be 27 words, got ${mnemonicLength} instead` | ||
) | ||
} | ||
} | ||
function cborEncodeBuffer(input) { | ||
@@ -494,2 +521,137 @@ validateBuffer(input) | ||
async function decodePaperWalletMnemonic(paperWalletMnemonic) { | ||
validatePaperWalletMnemonic(paperWalletMnemonic) | ||
const paperWalletMnemonicAsList = paperWalletMnemonic.split(' ') | ||
const mnemonicScrambledPart = paperWalletMnemonicAsList.slice(0, 18).join(' ') | ||
const mnemonicPassphrasePart = paperWalletMnemonicAsList.slice(18, 27).join(' ') | ||
const passphrase = await mnemonicToPaperWalletPassphrase(mnemonicPassphrasePart) | ||
const unscrambledMnemonic = await paperWalletUnscrambleStrings(passphrase, mnemonicScrambledPart) | ||
return unscrambledMnemonic | ||
} | ||
async function mnemonicToPaperWalletPassphrase(mnemonic, password) { | ||
const mnemonicBuffer = Buffer.from(mnemonic, 'utf8') | ||
const salt = `mnemonic${password || ''}` | ||
const saltBuffer = Buffer.from(salt, 'utf8') | ||
return (await pbkdf2(mnemonicBuffer, saltBuffer, 2048, 32, 'sha512')).toString('hex') | ||
} | ||
/* taken from https://github.com/input-output-hk/rust-cardano/blob/08796d9f100f417ff30549b297bd20b249f87809/cardano/src/paperwallet.rs */ | ||
async function paperWalletUnscrambleStrings(passphrase, mnemonic) { | ||
const input = Buffer.from(bip39.mnemonicToEntropy(mnemonic), 'hex') | ||
const saltLength = 8 | ||
if (saltLength >= input.length) { | ||
throw Error('unscrambleStrings: Input is too short') | ||
} | ||
const outputLength = input.length - saltLength | ||
const output = await pbkdf2(passphrase, input.slice(0, saltLength), 10000, outputLength, 'sha512') | ||
for (let i = 0; i < outputLength; i++) { | ||
output[i] = output[i] ^ input[saltLength + i] | ||
} | ||
return bip39.entropyToMnemonic(output) | ||
} | ||
async function xpubToHdPassphrase(xpub) { | ||
validateBuffer(xpub, 64) | ||
return pbkdf2(xpub, 'address-hashing', 500, 32, 'sha512') | ||
} | ||
function packAddress(derivationPath, xpub, hdPassphrase, derivationScheme) { | ||
validateBuffer(xpub, 64) | ||
validateDerivationScheme(derivationScheme) | ||
if (derivationScheme === 1) { | ||
validateArray(derivationPath) | ||
validateBuffer(hdPassphrase, 32) | ||
} | ||
let addressPayload, addressAttributes | ||
if (derivationPath.length > 0 && derivationScheme === 1) { | ||
addressPayload = encryptDerivationPath(derivationPath, hdPassphrase) | ||
addressAttributes = new Map([[1, cbor.encode(addressPayload)]]) | ||
} else { | ||
addressPayload = Buffer.from([]) | ||
addressAttributes = new Map() | ||
} | ||
const addressRoot = getAddressHash([ | ||
0, | ||
[0, xpub], | ||
addressPayload.length > 0 ? new Map([[1, cbor.encode(addressPayload)]]) : new Map(), | ||
]) | ||
const addressType = 0 // Public key address | ||
const addressData = [addressRoot, addressAttributes, addressType] | ||
const addressDataEncoded = cbor.encode(addressData) | ||
return base58.encode( | ||
cbor.encode([new cbor.Tagged(24, addressDataEncoded), crc32(addressDataEncoded)]) | ||
) | ||
} | ||
function unpackAddress(address, hdPassphrase) { | ||
// we decode the address from the base58 string | ||
// and then we strip the 24 CBOR data tags (the "[0].value" part) | ||
const addressAsBuffer = cbor.decode(base58.decode(address))[0].value | ||
const addressData = cbor.decode(addressAsBuffer) | ||
const attributes = addressData[1] | ||
const payload = cbor.decode(attributes.get(1)) | ||
let derivationPath | ||
try { | ||
derivationPath = decryptDerivationPath(payload, hdPassphrase) | ||
} catch (e) { | ||
throw new Error('Unable to get derivation path from address') | ||
} | ||
if (derivationPath && derivationPath.length > 2) { | ||
throw Error('Invalid derivation path length, should be at most 2') | ||
} | ||
return { | ||
derivationPath, | ||
} | ||
} | ||
function getAddressHash(input) { | ||
// eslint-disable-next-line camelcase | ||
const firstHash = sha3_256(cbor.encode(input)) | ||
return blake2b(firstHash, 28) | ||
} | ||
function encryptDerivationPath(derivationPath, hdPassphrase) { | ||
const serializedDerivationPath = cbor.encode(new CborIndefiniteLengthArray(derivationPath)) | ||
return chacha20poly1305Encrypt( | ||
serializedDerivationPath, | ||
hdPassphrase, | ||
Buffer.from('serokellfore') | ||
) | ||
} | ||
function decryptDerivationPath(addressPayload, hdPassphrase) { | ||
const decipheredDerivationPath = chacha20poly1305Decrypt( | ||
addressPayload, | ||
hdPassphrase, | ||
Buffer.from('serokellfore') | ||
) | ||
try { | ||
return cbor.decode(Buffer.from(decipheredDerivationPath)) | ||
} catch (err) { | ||
debugLog(err) | ||
throw NamedError('AddressDecodingException', 'incorrect address or passphrase') | ||
} | ||
} | ||
module.exports = { | ||
@@ -500,11 +662,14 @@ derivePublic, | ||
verify, | ||
sha3_256, | ||
chacha20poly1305Encrypt, | ||
chacha20poly1305Decrypt, | ||
blake2b, | ||
walletSecretFromMnemonic, | ||
decodePaperWalletMnemonic, | ||
xpubToHdPassphrase, | ||
packAddress, | ||
unpackAddress, | ||
cardanoMemoryCombine, | ||
blake2b, | ||
base58, | ||
crc32, | ||
scrypt, | ||
_sha3_256: sha3_256, | ||
_chacha20poly1305Decrypt: chacha20poly1305Decrypt, | ||
_chacha20poly1305Encrypt: chacha20poly1305Encrypt, | ||
} |
{ | ||
"name": "cardano-crypto.js", | ||
"version": "4.0.2", | ||
"version": "4.1.0", | ||
"description": "input-output-hk/cardano-crypto compiled to pure javascript using Emscripten", | ||
@@ -29,4 +29,5 @@ "main": "index.js", | ||
"dependencies": { | ||
"bip39-light": "^1.0.7" | ||
"bip39-light": "^1.0.7", | ||
"cbor": "^4.1.1" | ||
} | ||
} |
@@ -6,3 +6,3 @@ # cardano-crypto.js | ||
compiled to pure javascript using Emscripten. This is a collection of cryptolibraries useful for doing Cardano cryptography, eliminating the need for many dependencies. | ||
compiled to pure javascript using Emscripten. This is a collection of cryptolibraries and functions useful for working with Cardano cryptocurrency, eliminating the need for many dependencies. | ||
@@ -49,7 +49,10 @@ # examples | ||
* `Buffer derivePublic(Buffer parentExtPubKey, int index, int derivationScheme)` | ||
* `Buffer decodePaperWalletMnemonic(string paperWalletMnemonic)` | ||
* `Buffer xpubToHdPassphrase(Buffer xpub)` | ||
* `string packAddress(Array[int] derivationPath, Buffer xpub, Buffer hdPassphrase, int derivationScheme)` | ||
* `string unpackAddress(string address, Buffer hdPassphrase)` | ||
* `Buffer blake2b(Buffer input, outputLen)` | ||
* `Buffer sha3_256(Buffer input)` | ||
* `Buffer chacha20poly1305Encrypt(Buffer input, Buffer key, Buffer nonce)` | ||
* `Buffer chacha20poly1305Decrypt(Buffer input, Buffer key, Buffer nonce)` | ||
* `Buffer cardanoMemoryCombine(Buffer input, String password)` | ||
* `[base58](https://www.npmjs.com/package/base58)` | ||
* `[scrypt](https://www.npmjs.com/package/scrypt-async)` | ||
@@ -56,0 +59,0 @@ We encourage you to take a look `at test/index.js` to see how the functions above should be used. |
@@ -20,2 +20,5 @@ var test = require('tape') | ||
var sampleScryptDerivedKey = '5012b74fca8ec8a4a0a62ffdeeee959d' | ||
var samplePaperWalletMnemonic = | ||
'force usage medal chapter start myself odor ripple concert aspect wink melt afford lounge smart bulk way hazard burden type broken defense city announce reward same tumble' | ||
var sampleDecodedPaperWalletMnemonic = 'swim average antenna there trap nice good stereo lion safe next brief' | ||
@@ -135,3 +138,3 @@ test('wallet secret from mnemonic V1', async function(t) { | ||
t.equals( | ||
lib.sha3_256(message).toString('hex'), | ||
lib._sha3_256(message).toString('hex'), | ||
'98b05e27eab982f4d108694a5ab636d68cc898e4af98980516fe2560b13e53a9', | ||
@@ -150,3 +153,3 @@ 'should properly compute sha3_256 hash' | ||
t.equals( | ||
lib.chacha20poly1305Encrypt(message, key, nonce, true).toString('hex'), | ||
lib._chacha20poly1305Encrypt(message, key, nonce, true).toString('hex'), | ||
expectedEncryptionResult.toString('hex'), | ||
@@ -157,3 +160,3 @@ 'should properly encrypt with chacha20poly1305' | ||
t.equals( | ||
lib.chacha20poly1305Decrypt(expectedEncryptionResult, key, nonce, false).toString('hex'), | ||
lib._chacha20poly1305Decrypt(expectedEncryptionResult, key, nonce, false).toString('hex'), | ||
message.toString('hex'), | ||
@@ -203,1 +206,10 @@ 'should properly decrypt with chacha20poly1305' | ||
}) | ||
test('paper wallet mnemonic decoding', async function (t) { | ||
t.plan(1) | ||
t.equals( | ||
await lib.decodePaperWalletMnemonic(samplePaperWalletMnemonic), | ||
sampleDecodedPaperWalletMnemonic, | ||
'should properly decode paper wallet mnemonic' | ||
) | ||
}) |
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
276550
11
2209
68
2
+ Addedcbor@^4.1.1
+ Addedbignumber.js@9.1.2(transitive)
+ Addedcbor@4.3.0(transitive)
+ Addedcommander@3.0.2(transitive)
+ Addeddelimit-stream@0.1.0(transitive)
+ Addedjson-text-sequence@0.1.1(transitive)
+ Addednofilter@1.0.4(transitive)