Skip to content

Commit

Permalink
add missing bolt11 files
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbosworth committed May 1, 2019
1 parent 1ff1b56 commit 3fb58cb
Show file tree
Hide file tree
Showing 14 changed files with 1,006 additions and 0 deletions.
37 changes: 37 additions & 0 deletions bolt11/address_version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const {networks} = require('bitcoinjs-lib');

const {p2pkh} = require('./conf/address_versions');
const {p2sh} = require('./conf/address_versions');

/** Address version
{
network: <Network Name String>
[prefix]: <Bech32 Prefix String>
version: <Bitcoinjs-lib Chain Address Version Number>
}
@throws
<Error>
@returns
{
version: <BOLT 11 Chain Address Version Number>
}
*/
module.exports = ({network, prefix, version}) => {
if (!!prefix) {
return {version};
}

switch (version) {
case networks[network].pubKeyHash:
return {version: p2pkh};

case networks[network].scriptHash:
return {version: p2sh};

default:
throw new Error('UnexpectedVersionToDeriveBoltOnChainAddressVersion');
}
};
28 changes: 28 additions & 0 deletions bolt11/chain_address_as_words.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const chainAddressDetails = require('./chain_address_details');
const hexAsWords = require('./hex_as_words');

/** Convert chain address to bech32 words
{
address: <Chain Address String>
network: <Network Name String>
}
@returns
{
words: [<Chain Address Word Number>]
}
*/
module.exports = ({address, network}) => {
if (!address) {
throw new Error('ExpectedAddressToGetWordsForChainAddress');
}

if (!network) {
throw new Error('ExpectedNetworkToGetWordsForChainAddress');
}

const {hash, version} = chainAddressDetails({address, network});

return {words: [version].concat(hexAsWords({hex: hash}).words)};
};
48 changes: 48 additions & 0 deletions bolt11/chain_address_details.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const {address} = require('bitcoinjs-lib');
const {networks} = require('bitcoinjs-lib');

const addressVersion = require('./address_version');

const base58 = n => { try { return address.fromBase58Check(n); } catch (e) {}};
const bech32 = n => { try { return address.fromBech32(n); } catch (e) {}};

/** Derive chain address details
{
address: <Chain Address String>
network: <Network Name String>
}
@throws
<Error> on invalid chain address
@returns
{
hash: <Address Data Hash Hex String>
version: <Witness or Address Version Number>
}
*/
module.exports = ({address, network}) => {
if (!address) {
throw new Error('ExpectedAddressToDeriveChainAddressDetails');
}

if (!network || !networks[network]) {
throw new Error('ExpectedNetworkToDeriveChainAddressDetails');
}

const details = base58(address) || bech32(address);

// Exit early: address does not parse as a bech32 or base58 address
if (!details) {
throw new Error('ExpectedValidAddressToDeriveChainDetails');
}

const {prefix} = details;
const {version} = details;

return {
hash: (details.data || details.hash).toString('hex'),
version: addressVersion({network, prefix, version}).version,
};
};
24 changes: 24 additions & 0 deletions bolt11/conf/multipliers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"multipliers": [
{
"letter": "",
"value": "100000000000"
},
{
"letter": "m",
"value": "100000000"
},
{
"letter": "n",
"value": "100"
},
{
"letter": "p",
"value": "10"
},
{
"letter": "u",
"value": "100000"
}
]
}
82 changes: 82 additions & 0 deletions bolt11/create_signed_request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const {createHash} = require('crypto');

const {encode} = require('bech32');
const {recover} = require('secp256k1');

const hexAsWords = require('./hex_as_words');
const wordsAsBuffer = require('./words_as_buffer');

const {isArray} = Array;
const {MAX_SAFE_INTEGER} = Number;
const padding = '0';
const recoveryFlags = [0, 1, 2, 3];

/** Assemble a signed payment request
{
destination: <Destination Public Key Hex String>
hrp: <Request Human Readable Part String>
signature: <Request Hash Signature Hex String>
tags: [<Request Tag Word Number>]
}
@throws
<Error>
@returns
{
request: <BOLT 11 Encoded Payment Request String>
}
*/
module.exports = ({destination, hrp, signature, tags}) => {
if (!destination) {
throw new Error('ExpectedDestinationForSignedPaymentRequest');
}

if (!hrp) {
throw new Error('ExpectedHrpForSignedPaymentRequest');
}

if (!signature) {
throw new Error('ExpectedRequestSignatureForSignedPaymentRequest');
}

try {
hexAsWords({hex: signature});
} catch (err) {
throw new Error('ExpectedValidSignatureHexForSignedPaymentRequest');
}

if (!isArray(tags)) {
throw new Error('ExpectedRequestTagsForSignedPaymentRequest');
}

const preimage = Buffer.concat([
Buffer.from(hrp, 'ascii'),
wordsAsBuffer({words: tags}),
]);

const hash = createHash('sha256').update(preimage).digest();

const destinationKey = Buffer.from(destination, 'hex');
const sig = Buffer.from(signature, 'hex');

// Find the recovery flag that works for this signature
const recoveryFlag = recoveryFlags.find(flag => {
try {
return recover(hash, sig, flag, true).equals(destinationKey);
} catch (err) {
return false;
}
});

if (recoveryFlag === undefined) {
throw new Error('ExpectedValidSignatureForSignedPaymentRequest');
}

const sigWords = hexAsWords({hex: signature + padding + recoveryFlag}).words;

const words = [].concat(tags).concat(sigWords);

return {request: encode(hrp, words, MAX_SAFE_INTEGER)};
};
Loading

0 comments on commit 3fb58cb

Please sign in to comment.