From 5500d1c0ef1059b2ea009ddb7aad13d8c99c893b Mon Sep 17 00:00:00 2001 From: Andrew Toth Date: Fri, 3 Jan 2025 16:14:06 -0500 Subject: [PATCH] Add silent payment psbt fields --- src/cjs/lib/converter/index.cjs | 26 ++++++ .../output/silentPaymentOutputLabel.cjs | 48 ++++++++++ .../converter/output/silentPaymentV0Info.cjs | 65 ++++++++++++++ .../converter/shared/silentPaymentDleq.cjs | 74 ++++++++++++++++ .../shared/silentPaymentEcdhShare.cjs | 77 ++++++++++++++++ src/cjs/lib/parser/fromBuffer.cjs | 52 +++++++++++ src/cjs/lib/typeFields.cjs | 18 +++- src/esm/lib/converter/index.d.ts | 32 +++++++ src/esm/lib/converter/index.js | 18 +++- .../output/silentPaymentOutputLabel.d.ts | 6 ++ .../output/silentPaymentOutputLabel.js | 31 +++++++ .../converter/output/silentPaymentV0Info.d.ts | 6 ++ .../converter/output/silentPaymentV0Info.js | 48 ++++++++++ .../converter/shared/silentPaymentDleq.d.ts | 8 ++ .../lib/converter/shared/silentPaymentDleq.js | 60 +++++++++++++ .../shared/silentPaymentEcdhShare.d.ts | 8 ++ .../shared/silentPaymentEcdhShare.js | 63 +++++++++++++ src/esm/lib/interfaces.d.ts | 21 +++++ src/esm/lib/parser/fromBuffer.js | 44 ++++++++++ src/esm/lib/typeFields.d.ts | 12 ++- src/esm/lib/typeFields.js | 18 +++- ts_src/lib/converter/index.ts | 18 +++- .../output/silentPaymentOutputLabel.ts | 36 ++++++++ .../converter/output/silentPaymentV0Info.ts | 58 ++++++++++++ .../lib/converter/shared/silentPaymentDleq.ts | 85 ++++++++++++++++++ .../shared/silentPaymentEcdhShare.ts | 88 +++++++++++++++++++ ts_src/lib/interfaces.ts | 28 +++++- ts_src/lib/parser/fromBuffer.ts | 44 ++++++++++ ts_src/lib/typeFields.ts | 17 +++- 29 files changed, 1100 insertions(+), 9 deletions(-) create mode 100644 src/cjs/lib/converter/output/silentPaymentOutputLabel.cjs create mode 100644 src/cjs/lib/converter/output/silentPaymentV0Info.cjs create mode 100644 src/cjs/lib/converter/shared/silentPaymentDleq.cjs create mode 100644 src/cjs/lib/converter/shared/silentPaymentEcdhShare.cjs create mode 100644 src/esm/lib/converter/output/silentPaymentOutputLabel.d.ts create mode 100644 src/esm/lib/converter/output/silentPaymentOutputLabel.js create mode 100644 src/esm/lib/converter/output/silentPaymentV0Info.d.ts create mode 100644 src/esm/lib/converter/output/silentPaymentV0Info.js create mode 100644 src/esm/lib/converter/shared/silentPaymentDleq.d.ts create mode 100644 src/esm/lib/converter/shared/silentPaymentDleq.js create mode 100644 src/esm/lib/converter/shared/silentPaymentEcdhShare.d.ts create mode 100644 src/esm/lib/converter/shared/silentPaymentEcdhShare.js create mode 100644 ts_src/lib/converter/output/silentPaymentOutputLabel.ts create mode 100644 ts_src/lib/converter/output/silentPaymentV0Info.ts create mode 100644 ts_src/lib/converter/shared/silentPaymentDleq.ts create mode 100644 ts_src/lib/converter/shared/silentPaymentEcdhShare.ts diff --git a/src/cjs/lib/converter/index.cjs b/src/cjs/lib/converter/index.cjs index f94d2ad..322d547 100644 --- a/src/cjs/lib/converter/index.cjs +++ b/src/cjs/lib/converter/index.cjs @@ -27,10 +27,22 @@ const tapLeafScript = __importStar(require('./input/tapLeafScript.cjs')); const tapMerkleRoot = __importStar(require('./input/tapMerkleRoot.cjs')); const tapScriptSig = __importStar(require('./input/tapScriptSig.cjs')); const witnessUtxo = __importStar(require('./input/witnessUtxo.cjs')); +const silentPaymentOutputLabel = __importStar( + require('./output/silentPaymentOutputLabel.cjs'), +); +const silentPaymentV0Info = __importStar( + require('./output/silentPaymentV0Info.cjs'), +); const tapTree = __importStar(require('./output/tapTree.cjs')); const bip32Derivation = __importStar(require('./shared/bip32Derivation.cjs')); const checkPubkey = __importStar(require('./shared/checkPubkey.cjs')); const redeemScript = __importStar(require('./shared/redeemScript.cjs')); +const silentPaymentDleq = __importStar( + require('./shared/silentPaymentDleq.cjs'), +); +const silentPaymentEcdhShare = __importStar( + require('./shared/silentPaymentEcdhShare.cjs'), +); const tapBip32Derivation = __importStar( require('./shared/tapBip32Derivation.cjs'), ); @@ -41,6 +53,12 @@ const globals = { globalXpub, // pass an Array of key bytes that require pubkey beside the key checkPubkey: checkPubkey.makeChecker([]), + silentPaymentEcdhShare: silentPaymentEcdhShare.makeConverter( + typeFields_js_1.GlobalTypes.GLOBAL_SP_ECDH_SHARE, + ), + silentPaymentDleq: silentPaymentDleq.makeConverter( + typeFields_js_1.GlobalTypes.GLOBAL_SP_DLEQ, + ), }; exports.globals = globals; const inputs = { @@ -74,6 +92,12 @@ const inputs = { typeFields_js_1.InputTypes.TAP_INTERNAL_KEY, ), tapMerkleRoot, + silentPaymentEcdhShare: silentPaymentEcdhShare.makeConverter( + typeFields_js_1.InputTypes.SP_ECDH_SHARE, + ), + silentPaymentDleq: silentPaymentDleq.makeConverter( + typeFields_js_1.InputTypes.SP_DLEQ, + ), }; exports.inputs = inputs; const outputs = { @@ -96,5 +120,7 @@ const outputs = { tapInternalKey: tapInternalKey.makeConverter( typeFields_js_1.OutputTypes.TAP_INTERNAL_KEY, ), + silentPaymentV0Info, + silentPaymentOutputLabel, }; exports.outputs = outputs; diff --git a/src/cjs/lib/converter/output/silentPaymentOutputLabel.cjs b/src/cjs/lib/converter/output/silentPaymentOutputLabel.cjs new file mode 100644 index 0000000..3c99671 --- /dev/null +++ b/src/cjs/lib/converter/output/silentPaymentOutputLabel.cjs @@ -0,0 +1,48 @@ +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; + return result; + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const typeFields_js_1 = require('../../typeFields.cjs'); +const tools = __importStar(require('uint8array-tools')); +function decode(keyVal) { + if (keyVal.key[0] !== typeFields_js_1.OutputTypes.SP_V0_LABEL) { + throw new Error( + 'Decode Error: could not decode silentPaymentOutputLabel with key 0x' + + tools.toHex(keyVal.key), + ); + } + return Number(tools.readUInt32(keyVal.value, 0, 'LE')); +} +exports.decode = decode; +function encode(data) { + const key = Uint8Array.from([typeFields_js_1.OutputTypes.SP_V0_LABEL]); + const value = new Uint8Array(4); + tools.writeUInt32(value, 0, data, 'LE'); + return { + key, + value, + }; +} +exports.encode = encode; +exports.expected = 'number'; +function check(data) { + return typeof data === 'number'; +} +exports.check = check; +function canAdd(currentData, newData) { + return ( + !!currentData && + !!newData && + currentData.silentPaymentOutputLabel === undefined + ); +} +exports.canAdd = canAdd; diff --git a/src/cjs/lib/converter/output/silentPaymentV0Info.cjs b/src/cjs/lib/converter/output/silentPaymentV0Info.cjs new file mode 100644 index 0000000..1d1c228 --- /dev/null +++ b/src/cjs/lib/converter/output/silentPaymentV0Info.cjs @@ -0,0 +1,65 @@ +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; + return result; + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const typeFields_js_1 = require('../../typeFields.cjs'); +const tools = __importStar(require('uint8array-tools')); +const isValidPubKey = pubkey => + pubkey.length === 33 && [2, 3].includes(pubkey[0]); +function decode(keyVal) { + if (keyVal.key[0] !== typeFields_js_1.OutputTypes.SP_V0_INFO) { + throw new Error( + 'Decode Error: could not decode silentPaymentV0Info with key 0x' + + tools.toHex(keyVal.key), + ); + } + if (keyVal.value.length !== 66) { + throw new Error('Decode Error: SP_V0_INFO is not proper length'); + } + const scanKey = keyVal.value.slice(0, 33); + if (!isValidPubKey(scanKey)) { + throw new Error('Decode Error: SP_V0_INFO scanKey is not a valid pubkey'); + } + const spendKey = keyVal.value.slice(33); + if (!isValidPubKey(spendKey)) { + throw new Error('Decode Error: SP_V0_INFO spendKey is not a valid pubkey'); + } + return { + scanKey, + spendKey, + }; +} +exports.decode = decode; +function encode(data) { + const key = new Uint8Array([typeFields_js_1.OutputTypes.SP_V0_INFO]); + return { + key, + value: Uint8Array.from([...data.scanKey, ...data.spendKey]), + }; +} +exports.encode = encode; +exports.expected = '{ scanKey: Uint8Array; spendKey: Uint8Array; }'; +function check(data) { + return ( + data.scanKey instanceof Uint8Array && + data.spendKey instanceof Uint8Array && + isValidPubKey(data.scanKey) && + isValidPubKey(data.spendKey) + ); +} +exports.check = check; +function canAdd(currentData, newData) { + return ( + !!currentData && !!newData && currentData.silentPaymentV0Info === undefined + ); +} +exports.canAdd = canAdd; diff --git a/src/cjs/lib/converter/shared/silentPaymentDleq.cjs b/src/cjs/lib/converter/shared/silentPaymentDleq.cjs new file mode 100644 index 0000000..9db44ae --- /dev/null +++ b/src/cjs/lib/converter/shared/silentPaymentDleq.cjs @@ -0,0 +1,74 @@ +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; + return result; + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const tools = __importStar(require('uint8array-tools')); +const isValidPubKey = pubkey => + pubkey.length === 33 && [2, 3].includes(pubkey[0]); +function makeConverter(TYPE_BYTE, isValidPubkey = isValidPubKey) { + function decode(keyVal) { + if (keyVal.key[0] !== TYPE_BYTE) { + throw new Error( + 'Decode Error: could not decode silentPaymentDleq with key 0x' + + tools.toHex(keyVal.key), + ); + } + const scanKey = keyVal.key.slice(1); + if (!isValidPubkey(scanKey)) { + throw new Error( + 'Decode Error: silentPaymentDleq has invalid scanKey in key 0x' + + tools.toHex(keyVal.key), + ); + } + if (keyVal.value.length !== 64) { + throw new Error('Decode Error: silentPaymentDleq not a 64-byte proof'); + } + return { + scanKey, + proof: keyVal.value, + }; + } + function encode(data) { + const head = Uint8Array.from([TYPE_BYTE]); + const key = tools.concat([head, data.scanKey]); + return { + key, + value: data.proof, + }; + } + const expected = '{ scanKey: Uint8Array; proof: Uint8Array; }'; + function check(data) { + return ( + data.scanKey instanceof Uint8Array && + data.proof instanceof Uint8Array && + isValidPubkey(data.scanKey) && + data.proof.length === 64 + ); + } + function canAddToArray(array, item, dupeSet) { + const dupeString = tools.toHex(item.scanKey); + if (dupeSet.has(dupeString)) return false; + dupeSet.add(dupeString); + return ( + array.filter(v => tools.compare(v.scanKey, item.scanKey) === 0).length === + 0 + ); + } + return { + decode, + encode, + check, + expected, + canAddToArray, + }; +} +exports.makeConverter = makeConverter; diff --git a/src/cjs/lib/converter/shared/silentPaymentEcdhShare.cjs b/src/cjs/lib/converter/shared/silentPaymentEcdhShare.cjs new file mode 100644 index 0000000..ade9683 --- /dev/null +++ b/src/cjs/lib/converter/shared/silentPaymentEcdhShare.cjs @@ -0,0 +1,77 @@ +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; + return result; + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const tools = __importStar(require('uint8array-tools')); +const isValidDERKey = pubkey => + (pubkey.length === 33 && [2, 3].includes(pubkey[0])) || + (pubkey.length === 65 && 4 === pubkey[0]); +function makeConverter(TYPE_BYTE, isValidPubkey = isValidDERKey) { + function decode(keyVal) { + if (keyVal.key[0] !== TYPE_BYTE) { + throw new Error( + 'Decode Error: could not decode silentPaymentEcdhShare with key 0x' + + tools.toHex(keyVal.key), + ); + } + const scanKey = keyVal.key.slice(1); + if (!isValidPubkey(scanKey)) { + throw new Error( + 'Decode Error: silentPaymentEcdhShare has invalid scanKey in key 0x' + + tools.toHex(keyVal.key), + ); + } + if (!isValidPubkey(keyVal.value)) { + throw new Error( + 'Decode Error: silentPaymentEcdhShare not a 33-byte pubkey', + ); + } + return { + scanKey, + share: keyVal.value, + }; + } + function encode(data) { + const head = Uint8Array.from([TYPE_BYTE]); + const key = tools.concat([head, data.scanKey]); + return { + key, + value: data.share, + }; + } + const expected = '{ scanKey: Uint8Array; share: Uint8Array; }'; + function check(data) { + return ( + data.scanKey instanceof Uint8Array && + data.share instanceof Uint8Array && + isValidPubkey(data.scanKey) && + isValidPubkey(data.share) + ); + } + function canAddToArray(array, item, dupeSet) { + const dupeString = tools.toHex(item.scanKey); + if (dupeSet.has(dupeString)) return false; + dupeSet.add(dupeString); + return ( + array.filter(v => tools.compare(v.scanKey, item.scanKey) === 0).length === + 0 + ); + } + return { + decode, + encode, + check, + expected, + canAddToArray, + }; +} +exports.makeConverter = makeConverter; diff --git a/src/cjs/lib/parser/fromBuffer.cjs b/src/cjs/lib/parser/fromBuffer.cjs index 2492341..d599185 100644 --- a/src/cjs/lib/parser/fromBuffer.cjs +++ b/src/cjs/lib/parser/fromBuffer.cjs @@ -172,6 +172,22 @@ function psbtFromKeyVals( } globalMap.globalXpub.push(convert.globals.globalXpub.decode(keyVal)); break; + case typeFields_js_1.GlobalTypes.GLOBAL_SP_ECDH_SHARE: + if (globalMap.silentPaymentEcdhShare === undefined) { + globalMap.silentPaymentEcdhShare = []; + } + globalMap.silentPaymentEcdhShare.push( + convert.globals.silentPaymentEcdhShare.decode(keyVal), + ); + break; + case typeFields_js_1.GlobalTypes.GLOBAL_SP_DLEQ: + if (globalMap.silentPaymentDleq === undefined) { + globalMap.silentPaymentDleq = []; + } + globalMap.silentPaymentDleq.push( + convert.globals.silentPaymentDleq.decode(keyVal), + ); + break; default: // This will allow inclusion during serialization. if (!globalMap.unknownKeyVals) globalMap.unknownKeyVals = []; @@ -330,6 +346,22 @@ function psbtFromKeyVals( ); input.tapMerkleRoot = convert.inputs.tapMerkleRoot.decode(keyVal); break; + case typeFields_js_1.InputTypes.SP_ECDH_SHARE: + if (input.silentPaymentEcdhShare === undefined) { + input.silentPaymentEcdhShare = []; + } + input.silentPaymentEcdhShare.push( + convert.inputs.silentPaymentEcdhShare.decode(keyVal), + ); + break; + case typeFields_js_1.InputTypes.SP_DLEQ: + if (input.silentPaymentDleq === undefined) { + input.silentPaymentDleq = []; + } + input.silentPaymentDleq.push( + convert.inputs.silentPaymentDleq.decode(keyVal), + ); + break; default: // This will allow inclusion during serialization. if (!input.unknownKeyVals) input.unknownKeyVals = []; @@ -397,6 +429,26 @@ function psbtFromKeyVals( convert.outputs.tapBip32Derivation.decode(keyVal), ); break; + case typeFields_js_1.OutputTypes.SP_V0_INFO: + checkKeyBuffer( + 'output', + keyVal.key, + typeFields_js_1.OutputTypes.SP_V0_INFO, + ); + output.silentPaymentV0Info = convert.outputs.silentPaymentV0Info.decode( + keyVal, + ); + break; + case typeFields_js_1.OutputTypes.SP_V0_LABEL: + checkKeyBuffer( + 'output', + keyVal.key, + typeFields_js_1.OutputTypes.SP_V0_LABEL, + ); + output.silentPaymentOutputLabel = convert.outputs.silentPaymentOutputLabel.decode( + keyVal, + ); + break; default: if (!output.unknownKeyVals) output.unknownKeyVals = []; output.unknownKeyVals.push(keyVal); diff --git a/src/cjs/lib/typeFields.cjs b/src/cjs/lib/typeFields.cjs index f4b1d12..fd28554 100644 --- a/src/cjs/lib/typeFields.cjs +++ b/src/cjs/lib/typeFields.cjs @@ -4,8 +4,16 @@ var GlobalTypes; (function(GlobalTypes) { GlobalTypes[(GlobalTypes['UNSIGNED_TX'] = 0)] = 'UNSIGNED_TX'; GlobalTypes[(GlobalTypes['GLOBAL_XPUB'] = 1)] = 'GLOBAL_XPUB'; + GlobalTypes[(GlobalTypes['GLOBAL_SP_ECDH_SHARE'] = 7)] = + 'GLOBAL_SP_ECDH_SHARE'; + GlobalTypes[(GlobalTypes['GLOBAL_SP_DLEQ'] = 8)] = 'GLOBAL_SP_DLEQ'; })((GlobalTypes = exports.GlobalTypes || (exports.GlobalTypes = {}))); -exports.GLOBAL_TYPE_NAMES = ['unsignedTx', 'globalXpub']; +exports.GLOBAL_TYPE_NAMES = [ + 'unsignedTx', + 'globalXpub', + 'silentPaymentEcdhShare', + 'silentPaymentDleq', +]; var InputTypes; (function(InputTypes) { InputTypes[(InputTypes['NON_WITNESS_UTXO'] = 0)] = 'NON_WITNESS_UTXO'; @@ -25,6 +33,8 @@ var InputTypes; 'TAP_BIP32_DERIVATION'; InputTypes[(InputTypes['TAP_INTERNAL_KEY'] = 23)] = 'TAP_INTERNAL_KEY'; InputTypes[(InputTypes['TAP_MERKLE_ROOT'] = 24)] = 'TAP_MERKLE_ROOT'; + InputTypes[(InputTypes['SP_ECDH_SHARE'] = 29)] = 'SP_ECDH_SHARE'; + InputTypes[(InputTypes['SP_DLEQ'] = 30)] = 'SP_DLEQ'; })((InputTypes = exports.InputTypes || (exports.InputTypes = {}))); exports.INPUT_TYPE_NAMES = [ 'nonWitnessUtxo', @@ -43,6 +53,8 @@ exports.INPUT_TYPE_NAMES = [ 'tapBip32Derivation', 'tapInternalKey', 'tapMerkleRoot', + 'silentPaymentEcdhShare', + 'silentPaymentDleq', ]; var OutputTypes; (function(OutputTypes) { @@ -53,6 +65,8 @@ var OutputTypes; OutputTypes[(OutputTypes['TAP_TREE'] = 6)] = 'TAP_TREE'; OutputTypes[(OutputTypes['TAP_BIP32_DERIVATION'] = 7)] = 'TAP_BIP32_DERIVATION'; + OutputTypes[(OutputTypes['SP_V0_INFO'] = 9)] = 'SP_V0_INFO'; + OutputTypes[(OutputTypes['SP_V0_LABEL'] = 10)] = 'SP_V0_LABEL'; })((OutputTypes = exports.OutputTypes || (exports.OutputTypes = {}))); exports.OUTPUT_TYPE_NAMES = [ 'redeemScript', @@ -61,4 +75,6 @@ exports.OUTPUT_TYPE_NAMES = [ 'tapInternalKey', 'tapTree', 'tapBip32Derivation', + 'silentPaymentV0Info', + 'silentPaymentV0Label', ]; diff --git a/src/esm/lib/converter/index.d.ts b/src/esm/lib/converter/index.d.ts index f152688..022aa40 100644 --- a/src/esm/lib/converter/index.d.ts +++ b/src/esm/lib/converter/index.d.ts @@ -11,11 +11,27 @@ import * as tapLeafScript from './input/tapLeafScript.js'; import * as tapMerkleRoot from './input/tapMerkleRoot.js'; import * as tapScriptSig from './input/tapScriptSig.js'; import * as witnessUtxo from './input/witnessUtxo.js'; +import * as silentPaymentOutputLabel from './output/silentPaymentOutputLabel.js'; +import * as silentPaymentV0Info from './output/silentPaymentV0Info.js'; import * as tapTree from './output/tapTree.js'; declare const globals: { unsignedTx: typeof unsignedTx; globalXpub: typeof globalXpub; checkPubkey: (keyVal: import("../interfaces.js").KeyValue) => Uint8Array | undefined; + silentPaymentEcdhShare: { + decode: (keyVal: import("../interfaces.js").KeyValue) => import("../interfaces.js").SilentPaymentEcdhShare; + encode: (data: import("../interfaces.js").SilentPaymentEcdhShare) => import("../interfaces.js").KeyValue; + check: (data: any) => data is import("../interfaces.js").SilentPaymentEcdhShare; + expected: string; + canAddToArray: (array: import("../interfaces.js").SilentPaymentEcdhShare[], item: import("../interfaces.js").SilentPaymentEcdhShare, dupeSet: Set) => boolean; + }; + silentPaymentDleq: { + decode: (keyVal: import("../interfaces.js").KeyValue) => import("../interfaces.js").SilentPaymentDleq; + encode: (data: import("../interfaces.js").SilentPaymentDleq) => import("../interfaces.js").KeyValue; + check: (data: any) => data is import("../interfaces.js").SilentPaymentDleq; + expected: string; + canAddToArray: (array: import("../interfaces.js").SilentPaymentDleq[], item: import("../interfaces.js").SilentPaymentDleq, dupeSet: Set) => boolean; + }; }; declare const inputs: { nonWitnessUtxo: typeof nonWitnessUtxo; @@ -65,6 +81,20 @@ declare const inputs: { canAdd: (currentData: any, newData: any) => boolean; }; tapMerkleRoot: typeof tapMerkleRoot; + silentPaymentEcdhShare: { + decode: (keyVal: import("../interfaces.js").KeyValue) => import("../interfaces.js").SilentPaymentEcdhShare; + encode: (data: import("../interfaces.js").SilentPaymentEcdhShare) => import("../interfaces.js").KeyValue; + check: (data: any) => data is import("../interfaces.js").SilentPaymentEcdhShare; + expected: string; + canAddToArray: (array: import("../interfaces.js").SilentPaymentEcdhShare[], item: import("../interfaces.js").SilentPaymentEcdhShare, dupeSet: Set) => boolean; + }; + silentPaymentDleq: { + decode: (keyVal: import("../interfaces.js").KeyValue) => import("../interfaces.js").SilentPaymentDleq; + encode: (data: import("../interfaces.js").SilentPaymentDleq) => import("../interfaces.js").KeyValue; + check: (data: any) => data is import("../interfaces.js").SilentPaymentDleq; + expected: string; + canAddToArray: (array: import("../interfaces.js").SilentPaymentDleq[], item: import("../interfaces.js").SilentPaymentDleq, dupeSet: Set) => boolean; + }; }; declare const outputs: { bip32Derivation: { @@ -104,5 +134,7 @@ declare const outputs: { expected: string; canAdd: (currentData: any, newData: any) => boolean; }; + silentPaymentV0Info: typeof silentPaymentV0Info; + silentPaymentOutputLabel: typeof silentPaymentOutputLabel; }; export { globals, inputs, outputs }; diff --git a/src/esm/lib/converter/index.js b/src/esm/lib/converter/index.js index a6e0dd5..b0d74d8 100644 --- a/src/esm/lib/converter/index.js +++ b/src/esm/lib/converter/index.js @@ -1,4 +1,4 @@ -import { InputTypes, OutputTypes } from '../typeFields.js'; +import { GlobalTypes, InputTypes, OutputTypes } from '../typeFields.js'; import * as globalXpub from './global/globalXpub.js'; import * as unsignedTx from './global/unsignedTx.js'; import * as finalScriptSig from './input/finalScriptSig.js'; @@ -12,10 +12,14 @@ import * as tapLeafScript from './input/tapLeafScript.js'; import * as tapMerkleRoot from './input/tapMerkleRoot.js'; import * as tapScriptSig from './input/tapScriptSig.js'; import * as witnessUtxo from './input/witnessUtxo.js'; +import * as silentPaymentOutputLabel from './output/silentPaymentOutputLabel.js'; +import * as silentPaymentV0Info from './output/silentPaymentV0Info.js'; import * as tapTree from './output/tapTree.js'; import * as bip32Derivation from './shared/bip32Derivation.js'; import * as checkPubkey from './shared/checkPubkey.js'; import * as redeemScript from './shared/redeemScript.js'; +import * as silentPaymentDleq from './shared/silentPaymentDleq.js'; +import * as silentPaymentEcdhShare from './shared/silentPaymentEcdhShare.js'; import * as tapBip32Derivation from './shared/tapBip32Derivation.js'; import * as tapInternalKey from './shared/tapInternalKey.js'; import * as witnessScript from './shared/witnessScript.js'; @@ -24,6 +28,12 @@ const globals = { globalXpub, // pass an Array of key bytes that require pubkey beside the key checkPubkey: checkPubkey.makeChecker([]), + silentPaymentEcdhShare: silentPaymentEcdhShare.makeConverter( + GlobalTypes.GLOBAL_SP_ECDH_SHARE, + ), + silentPaymentDleq: silentPaymentDleq.makeConverter( + GlobalTypes.GLOBAL_SP_DLEQ, + ), }; const inputs = { nonWitnessUtxo, @@ -48,6 +58,10 @@ const inputs = { ), tapInternalKey: tapInternalKey.makeConverter(InputTypes.TAP_INTERNAL_KEY), tapMerkleRoot, + silentPaymentEcdhShare: silentPaymentEcdhShare.makeConverter( + InputTypes.SP_ECDH_SHARE, + ), + silentPaymentDleq: silentPaymentDleq.makeConverter(InputTypes.SP_DLEQ), }; const outputs = { bip32Derivation: bip32Derivation.makeConverter(OutputTypes.BIP32_DERIVATION), @@ -59,5 +73,7 @@ const outputs = { ), tapTree, tapInternalKey: tapInternalKey.makeConverter(OutputTypes.TAP_INTERNAL_KEY), + silentPaymentV0Info, + silentPaymentOutputLabel, }; export { globals, inputs, outputs }; diff --git a/src/esm/lib/converter/output/silentPaymentOutputLabel.d.ts b/src/esm/lib/converter/output/silentPaymentOutputLabel.d.ts new file mode 100644 index 0000000..6d6ba2c --- /dev/null +++ b/src/esm/lib/converter/output/silentPaymentOutputLabel.d.ts @@ -0,0 +1,6 @@ +import { KeyValue, SilentPaymentOutputLabel } from '../../interfaces'; +export declare function decode(keyVal: KeyValue): SilentPaymentOutputLabel; +export declare function encode(data: SilentPaymentOutputLabel): KeyValue; +export declare const expected = "number"; +export declare function check(data: any): data is SilentPaymentOutputLabel; +export declare function canAdd(currentData: any, newData: any): boolean; diff --git a/src/esm/lib/converter/output/silentPaymentOutputLabel.js b/src/esm/lib/converter/output/silentPaymentOutputLabel.js new file mode 100644 index 0000000..d3ed9bd --- /dev/null +++ b/src/esm/lib/converter/output/silentPaymentOutputLabel.js @@ -0,0 +1,31 @@ +import { OutputTypes } from '../../typeFields.js'; +import * as tools from 'uint8array-tools'; +export function decode(keyVal) { + if (keyVal.key[0] !== OutputTypes.SP_V0_LABEL) { + throw new Error( + 'Decode Error: could not decode silentPaymentOutputLabel with key 0x' + + tools.toHex(keyVal.key), + ); + } + return Number(tools.readUInt32(keyVal.value, 0, 'LE')); +} +export function encode(data) { + const key = Uint8Array.from([OutputTypes.SP_V0_LABEL]); + const value = new Uint8Array(4); + tools.writeUInt32(value, 0, data, 'LE'); + return { + key, + value, + }; +} +export const expected = 'number'; +export function check(data) { + return typeof data === 'number'; +} +export function canAdd(currentData, newData) { + return ( + !!currentData && + !!newData && + currentData.silentPaymentOutputLabel === undefined + ); +} diff --git a/src/esm/lib/converter/output/silentPaymentV0Info.d.ts b/src/esm/lib/converter/output/silentPaymentV0Info.d.ts new file mode 100644 index 0000000..baf5ebe --- /dev/null +++ b/src/esm/lib/converter/output/silentPaymentV0Info.d.ts @@ -0,0 +1,6 @@ +import { SilentPaymentV0Info, KeyValue } from '../../interfaces'; +export declare function decode(keyVal: KeyValue): SilentPaymentV0Info; +export declare function encode(data: SilentPaymentV0Info): KeyValue; +export declare const expected = "{ scanKey: Uint8Array; spendKey: Uint8Array; }"; +export declare function check(data: any): data is SilentPaymentV0Info; +export declare function canAdd(currentData: any, newData: any): boolean; diff --git a/src/esm/lib/converter/output/silentPaymentV0Info.js b/src/esm/lib/converter/output/silentPaymentV0Info.js new file mode 100644 index 0000000..f809438 --- /dev/null +++ b/src/esm/lib/converter/output/silentPaymentV0Info.js @@ -0,0 +1,48 @@ +import { OutputTypes } from '../../typeFields.js'; +import * as tools from 'uint8array-tools'; +const isValidPubKey = pubkey => + pubkey.length === 33 && [2, 3].includes(pubkey[0]); +export function decode(keyVal) { + if (keyVal.key[0] !== OutputTypes.SP_V0_INFO) { + throw new Error( + 'Decode Error: could not decode silentPaymentV0Info with key 0x' + + tools.toHex(keyVal.key), + ); + } + if (keyVal.value.length !== 66) { + throw new Error('Decode Error: SP_V0_INFO is not proper length'); + } + const scanKey = keyVal.value.slice(0, 33); + if (!isValidPubKey(scanKey)) { + throw new Error('Decode Error: SP_V0_INFO scanKey is not a valid pubkey'); + } + const spendKey = keyVal.value.slice(33); + if (!isValidPubKey(spendKey)) { + throw new Error('Decode Error: SP_V0_INFO spendKey is not a valid pubkey'); + } + return { + scanKey, + spendKey, + }; +} +export function encode(data) { + const key = new Uint8Array([OutputTypes.SP_V0_INFO]); + return { + key, + value: Uint8Array.from([...data.scanKey, ...data.spendKey]), + }; +} +export const expected = '{ scanKey: Uint8Array; spendKey: Uint8Array; }'; +export function check(data) { + return ( + data.scanKey instanceof Uint8Array && + data.spendKey instanceof Uint8Array && + isValidPubKey(data.scanKey) && + isValidPubKey(data.spendKey) + ); +} +export function canAdd(currentData, newData) { + return ( + !!currentData && !!newData && currentData.silentPaymentV0Info === undefined + ); +} diff --git a/src/esm/lib/converter/shared/silentPaymentDleq.d.ts b/src/esm/lib/converter/shared/silentPaymentDleq.d.ts new file mode 100644 index 0000000..dceeafa --- /dev/null +++ b/src/esm/lib/converter/shared/silentPaymentDleq.d.ts @@ -0,0 +1,8 @@ +import { SilentPaymentDleq, KeyValue } from '../../interfaces'; +export declare function makeConverter(TYPE_BYTE: number, isValidPubkey?: (pubkey: Uint8Array) => boolean): { + decode: (keyVal: KeyValue) => SilentPaymentDleq; + encode: (data: SilentPaymentDleq) => KeyValue; + check: (data: any) => data is SilentPaymentDleq; + expected: string; + canAddToArray: (array: SilentPaymentDleq[], item: SilentPaymentDleq, dupeSet: Set) => boolean; +}; diff --git a/src/esm/lib/converter/shared/silentPaymentDleq.js b/src/esm/lib/converter/shared/silentPaymentDleq.js new file mode 100644 index 0000000..3a74df1 --- /dev/null +++ b/src/esm/lib/converter/shared/silentPaymentDleq.js @@ -0,0 +1,60 @@ +import * as tools from 'uint8array-tools'; +const isValidPubKey = pubkey => + pubkey.length === 33 && [2, 3].includes(pubkey[0]); +export function makeConverter(TYPE_BYTE, isValidPubkey = isValidPubKey) { + function decode(keyVal) { + if (keyVal.key[0] !== TYPE_BYTE) { + throw new Error( + 'Decode Error: could not decode silentPaymentDleq with key 0x' + + tools.toHex(keyVal.key), + ); + } + const scanKey = keyVal.key.slice(1); + if (!isValidPubkey(scanKey)) { + throw new Error( + 'Decode Error: silentPaymentDleq has invalid scanKey in key 0x' + + tools.toHex(keyVal.key), + ); + } + if (keyVal.value.length !== 64) { + throw new Error('Decode Error: silentPaymentDleq not a 64-byte proof'); + } + return { + scanKey, + proof: keyVal.value, + }; + } + function encode(data) { + const head = Uint8Array.from([TYPE_BYTE]); + const key = tools.concat([head, data.scanKey]); + return { + key, + value: data.proof, + }; + } + const expected = '{ scanKey: Uint8Array; proof: Uint8Array; }'; + function check(data) { + return ( + data.scanKey instanceof Uint8Array && + data.proof instanceof Uint8Array && + isValidPubkey(data.scanKey) && + data.proof.length === 64 + ); + } + function canAddToArray(array, item, dupeSet) { + const dupeString = tools.toHex(item.scanKey); + if (dupeSet.has(dupeString)) return false; + dupeSet.add(dupeString); + return ( + array.filter(v => tools.compare(v.scanKey, item.scanKey) === 0).length === + 0 + ); + } + return { + decode, + encode, + check, + expected, + canAddToArray, + }; +} diff --git a/src/esm/lib/converter/shared/silentPaymentEcdhShare.d.ts b/src/esm/lib/converter/shared/silentPaymentEcdhShare.d.ts new file mode 100644 index 0000000..65de35a --- /dev/null +++ b/src/esm/lib/converter/shared/silentPaymentEcdhShare.d.ts @@ -0,0 +1,8 @@ +import { SilentPaymentEcdhShare, KeyValue } from '../../interfaces'; +export declare function makeConverter(TYPE_BYTE: number, isValidPubkey?: (pubkey: Uint8Array) => boolean): { + decode: (keyVal: KeyValue) => SilentPaymentEcdhShare; + encode: (data: SilentPaymentEcdhShare) => KeyValue; + check: (data: any) => data is SilentPaymentEcdhShare; + expected: string; + canAddToArray: (array: SilentPaymentEcdhShare[], item: SilentPaymentEcdhShare, dupeSet: Set) => boolean; +}; diff --git a/src/esm/lib/converter/shared/silentPaymentEcdhShare.js b/src/esm/lib/converter/shared/silentPaymentEcdhShare.js new file mode 100644 index 0000000..e1c46c6 --- /dev/null +++ b/src/esm/lib/converter/shared/silentPaymentEcdhShare.js @@ -0,0 +1,63 @@ +import * as tools from 'uint8array-tools'; +const isValidDERKey = pubkey => + (pubkey.length === 33 && [2, 3].includes(pubkey[0])) || + (pubkey.length === 65 && 4 === pubkey[0]); +export function makeConverter(TYPE_BYTE, isValidPubkey = isValidDERKey) { + function decode(keyVal) { + if (keyVal.key[0] !== TYPE_BYTE) { + throw new Error( + 'Decode Error: could not decode silentPaymentEcdhShare with key 0x' + + tools.toHex(keyVal.key), + ); + } + const scanKey = keyVal.key.slice(1); + if (!isValidPubkey(scanKey)) { + throw new Error( + 'Decode Error: silentPaymentEcdhShare has invalid scanKey in key 0x' + + tools.toHex(keyVal.key), + ); + } + if (!isValidPubkey(keyVal.value)) { + throw new Error( + 'Decode Error: silentPaymentEcdhShare not a 33-byte pubkey', + ); + } + return { + scanKey, + share: keyVal.value, + }; + } + function encode(data) { + const head = Uint8Array.from([TYPE_BYTE]); + const key = tools.concat([head, data.scanKey]); + return { + key, + value: data.share, + }; + } + const expected = '{ scanKey: Uint8Array; share: Uint8Array; }'; + function check(data) { + return ( + data.scanKey instanceof Uint8Array && + data.share instanceof Uint8Array && + isValidPubkey(data.scanKey) && + isValidPubkey(data.share) + ); + } + function canAddToArray(array, item, dupeSet) { + const dupeString = tools.toHex(item.scanKey); + if (dupeSet.has(dupeString)) return false; + dupeSet.add(dupeString); + return ( + array.filter(v => tools.compare(v.scanKey, item.scanKey) === 0).length === + 0 + ); + } + return { + decode, + encode, + check, + expected, + canAddToArray, + }; +} diff --git a/src/esm/lib/interfaces.d.ts b/src/esm/lib/interfaces.d.ts index 9a5112e..47349b4 100644 --- a/src/esm/lib/interfaces.d.ts +++ b/src/esm/lib/interfaces.d.ts @@ -18,6 +18,8 @@ export interface PsbtGlobal extends PsbtGlobalUpdate { } export interface PsbtGlobalUpdate { globalXpub?: GlobalXpub[]; + silentPaymentEcdhShare?: SilentPaymentEcdhShare[]; + silentPaymentDleq?: SilentPaymentDleq[]; } export interface PsbtInput extends PsbtInputUpdate { unknownKeyVals?: KeyValue[]; @@ -39,6 +41,8 @@ export interface PsbtInputUpdate { tapBip32Derivation?: TapBip32Derivation[]; tapInternalKey?: TapInternalKey; tapMerkleRoot?: TapMerkleRoot; + silentPaymentEcdhShare?: SilentPaymentEcdhShare[]; + silentPaymentDleq?: SilentPaymentDleq[]; } export interface PsbtInputExtended extends PsbtInput { [index: string]: any; @@ -53,6 +57,9 @@ export interface PsbtOutputUpdate { tapBip32Derivation?: TapBip32Derivation[]; tapTree?: TapTree; tapInternalKey?: TapInternalKey; + script?: OutputScript; + silentPaymentV0Info?: SilentPaymentV0Info; + silentPaymentOutputLabel?: SilentPaymentOutputLabel; } export interface PsbtOutputExtended extends PsbtOutput { [index: string]: any; @@ -83,6 +90,7 @@ export declare type FinalScriptSig = Uint8Array; export declare type FinalScriptWitness = Uint8Array; export declare type PorCommitment = string; export declare type TapKeySig = Uint8Array; +export declare type OutputScript = Uint8Array; export interface TapScriptSig extends PartialSig { leafHash: Uint8Array; } @@ -111,4 +119,17 @@ export declare type TransactionIOCountGetter = (txBuffer: Uint8Array) => { }; export declare type TransactionVersionSetter = (version: number, txBuffer: Uint8Array) => Uint8Array; export declare type TransactionLocktimeSetter = (locktime: number, txBuffer: Uint8Array) => Uint8Array; +export interface SilentPaymentEcdhShare { + scanKey: Uint8Array; + share: Uint8Array; +} +export interface SilentPaymentDleq { + scanKey: Uint8Array; + proof: Uint8Array; +} +export interface SilentPaymentV0Info { + scanKey: Uint8Array; + spendKey: Uint8Array; +} +export declare type SilentPaymentOutputLabel = number; export {}; diff --git a/src/esm/lib/parser/fromBuffer.js b/src/esm/lib/parser/fromBuffer.js index 1df0911..af9b23e 100644 --- a/src/esm/lib/parser/fromBuffer.js +++ b/src/esm/lib/parser/fromBuffer.js @@ -153,6 +153,22 @@ export function psbtFromKeyVals( } globalMap.globalXpub.push(convert.globals.globalXpub.decode(keyVal)); break; + case GlobalTypes.GLOBAL_SP_ECDH_SHARE: + if (globalMap.silentPaymentEcdhShare === undefined) { + globalMap.silentPaymentEcdhShare = []; + } + globalMap.silentPaymentEcdhShare.push( + convert.globals.silentPaymentEcdhShare.decode(keyVal), + ); + break; + case GlobalTypes.GLOBAL_SP_DLEQ: + if (globalMap.silentPaymentDleq === undefined) { + globalMap.silentPaymentDleq = []; + } + globalMap.silentPaymentDleq.push( + convert.globals.silentPaymentDleq.decode(keyVal), + ); + break; default: // This will allow inclusion during serialization. if (!globalMap.unknownKeyVals) globalMap.unknownKeyVals = []; @@ -267,6 +283,22 @@ export function psbtFromKeyVals( checkKeyBuffer('input', keyVal.key, InputTypes.TAP_MERKLE_ROOT); input.tapMerkleRoot = convert.inputs.tapMerkleRoot.decode(keyVal); break; + case InputTypes.SP_ECDH_SHARE: + if (input.silentPaymentEcdhShare === undefined) { + input.silentPaymentEcdhShare = []; + } + input.silentPaymentEcdhShare.push( + convert.inputs.silentPaymentEcdhShare.decode(keyVal), + ); + break; + case InputTypes.SP_DLEQ: + if (input.silentPaymentDleq === undefined) { + input.silentPaymentDleq = []; + } + input.silentPaymentDleq.push( + convert.inputs.silentPaymentDleq.decode(keyVal), + ); + break; default: // This will allow inclusion during serialization. if (!input.unknownKeyVals) input.unknownKeyVals = []; @@ -318,6 +350,18 @@ export function psbtFromKeyVals( convert.outputs.tapBip32Derivation.decode(keyVal), ); break; + case OutputTypes.SP_V0_INFO: + checkKeyBuffer('output', keyVal.key, OutputTypes.SP_V0_INFO); + output.silentPaymentV0Info = convert.outputs.silentPaymentV0Info.decode( + keyVal, + ); + break; + case OutputTypes.SP_V0_LABEL: + checkKeyBuffer('output', keyVal.key, OutputTypes.SP_V0_LABEL); + output.silentPaymentOutputLabel = convert.outputs.silentPaymentOutputLabel.decode( + keyVal, + ); + break; default: if (!output.unknownKeyVals) output.unknownKeyVals = []; output.unknownKeyVals.push(keyVal); diff --git a/src/esm/lib/typeFields.d.ts b/src/esm/lib/typeFields.d.ts index e8cbcbe..95d85b5 100644 --- a/src/esm/lib/typeFields.d.ts +++ b/src/esm/lib/typeFields.d.ts @@ -1,6 +1,8 @@ export declare enum GlobalTypes { UNSIGNED_TX = 0, - GLOBAL_XPUB = 1 + GLOBAL_XPUB = 1, + GLOBAL_SP_ECDH_SHARE = 7, + GLOBAL_SP_DLEQ = 8 } export declare const GLOBAL_TYPE_NAMES: string[]; export declare enum InputTypes { @@ -19,7 +21,9 @@ export declare enum InputTypes { TAP_LEAF_SCRIPT = 21, TAP_BIP32_DERIVATION = 22, TAP_INTERNAL_KEY = 23, - TAP_MERKLE_ROOT = 24 + TAP_MERKLE_ROOT = 24, + SP_ECDH_SHARE = 29, + SP_DLEQ = 30 } export declare const INPUT_TYPE_NAMES: string[]; export declare enum OutputTypes { @@ -28,6 +32,8 @@ export declare enum OutputTypes { BIP32_DERIVATION = 2, TAP_INTERNAL_KEY = 5, TAP_TREE = 6, - TAP_BIP32_DERIVATION = 7 + TAP_BIP32_DERIVATION = 7, + SP_V0_INFO = 9, + SP_V0_LABEL = 10 } export declare const OUTPUT_TYPE_NAMES: string[]; diff --git a/src/esm/lib/typeFields.js b/src/esm/lib/typeFields.js index 23cc1e3..f1dbc50 100644 --- a/src/esm/lib/typeFields.js +++ b/src/esm/lib/typeFields.js @@ -2,8 +2,16 @@ export var GlobalTypes; (function(GlobalTypes) { GlobalTypes[(GlobalTypes['UNSIGNED_TX'] = 0)] = 'UNSIGNED_TX'; GlobalTypes[(GlobalTypes['GLOBAL_XPUB'] = 1)] = 'GLOBAL_XPUB'; + GlobalTypes[(GlobalTypes['GLOBAL_SP_ECDH_SHARE'] = 7)] = + 'GLOBAL_SP_ECDH_SHARE'; + GlobalTypes[(GlobalTypes['GLOBAL_SP_DLEQ'] = 8)] = 'GLOBAL_SP_DLEQ'; })(GlobalTypes || (GlobalTypes = {})); -export const GLOBAL_TYPE_NAMES = ['unsignedTx', 'globalXpub']; +export const GLOBAL_TYPE_NAMES = [ + 'unsignedTx', + 'globalXpub', + 'silentPaymentEcdhShare', + 'silentPaymentDleq', +]; export var InputTypes; (function(InputTypes) { InputTypes[(InputTypes['NON_WITNESS_UTXO'] = 0)] = 'NON_WITNESS_UTXO'; @@ -23,6 +31,8 @@ export var InputTypes; 'TAP_BIP32_DERIVATION'; InputTypes[(InputTypes['TAP_INTERNAL_KEY'] = 23)] = 'TAP_INTERNAL_KEY'; InputTypes[(InputTypes['TAP_MERKLE_ROOT'] = 24)] = 'TAP_MERKLE_ROOT'; + InputTypes[(InputTypes['SP_ECDH_SHARE'] = 29)] = 'SP_ECDH_SHARE'; + InputTypes[(InputTypes['SP_DLEQ'] = 30)] = 'SP_DLEQ'; })(InputTypes || (InputTypes = {})); export const INPUT_TYPE_NAMES = [ 'nonWitnessUtxo', @@ -41,6 +51,8 @@ export const INPUT_TYPE_NAMES = [ 'tapBip32Derivation', 'tapInternalKey', 'tapMerkleRoot', + 'silentPaymentEcdhShare', + 'silentPaymentDleq', ]; export var OutputTypes; (function(OutputTypes) { @@ -51,6 +63,8 @@ export var OutputTypes; OutputTypes[(OutputTypes['TAP_TREE'] = 6)] = 'TAP_TREE'; OutputTypes[(OutputTypes['TAP_BIP32_DERIVATION'] = 7)] = 'TAP_BIP32_DERIVATION'; + OutputTypes[(OutputTypes['SP_V0_INFO'] = 9)] = 'SP_V0_INFO'; + OutputTypes[(OutputTypes['SP_V0_LABEL'] = 10)] = 'SP_V0_LABEL'; })(OutputTypes || (OutputTypes = {})); export const OUTPUT_TYPE_NAMES = [ 'redeemScript', @@ -59,4 +73,6 @@ export const OUTPUT_TYPE_NAMES = [ 'tapInternalKey', 'tapTree', 'tapBip32Derivation', + 'silentPaymentV0Info', + 'silentPaymentV0Label', ]; diff --git a/ts_src/lib/converter/index.ts b/ts_src/lib/converter/index.ts index 2cae70f..2389b00 100644 --- a/ts_src/lib/converter/index.ts +++ b/ts_src/lib/converter/index.ts @@ -1,4 +1,4 @@ -import { InputTypes, OutputTypes } from '../typeFields.js'; +import { GlobalTypes, InputTypes, OutputTypes } from '../typeFields.js'; import * as globalXpub from './global/globalXpub.js'; import * as unsignedTx from './global/unsignedTx.js'; @@ -15,11 +15,15 @@ import * as tapMerkleRoot from './input/tapMerkleRoot.js'; import * as tapScriptSig from './input/tapScriptSig.js'; import * as witnessUtxo from './input/witnessUtxo.js'; +import * as silentPaymentOutputLabel from './output/silentPaymentOutputLabel.js'; +import * as silentPaymentV0Info from './output/silentPaymentV0Info.js'; import * as tapTree from './output/tapTree.js'; import * as bip32Derivation from './shared/bip32Derivation.js'; import * as checkPubkey from './shared/checkPubkey.js'; import * as redeemScript from './shared/redeemScript.js'; +import * as silentPaymentDleq from './shared/silentPaymentDleq.js'; +import * as silentPaymentEcdhShare from './shared/silentPaymentEcdhShare.js'; import * as tapBip32Derivation from './shared/tapBip32Derivation.js'; import * as tapInternalKey from './shared/tapInternalKey.js'; import * as witnessScript from './shared/witnessScript.js'; @@ -29,6 +33,12 @@ const globals = { globalXpub, // pass an Array of key bytes that require pubkey beside the key checkPubkey: checkPubkey.makeChecker([]), + silentPaymentEcdhShare: silentPaymentEcdhShare.makeConverter( + GlobalTypes.GLOBAL_SP_ECDH_SHARE, + ), + silentPaymentDleq: silentPaymentDleq.makeConverter( + GlobalTypes.GLOBAL_SP_DLEQ, + ), }; const inputs = { @@ -54,6 +64,10 @@ const inputs = { ), tapInternalKey: tapInternalKey.makeConverter(InputTypes.TAP_INTERNAL_KEY), tapMerkleRoot, + silentPaymentEcdhShare: silentPaymentEcdhShare.makeConverter( + InputTypes.SP_ECDH_SHARE, + ), + silentPaymentDleq: silentPaymentDleq.makeConverter(InputTypes.SP_DLEQ), }; const outputs = { @@ -66,6 +80,8 @@ const outputs = { ), tapTree, tapInternalKey: tapInternalKey.makeConverter(OutputTypes.TAP_INTERNAL_KEY), + silentPaymentV0Info, + silentPaymentOutputLabel, }; export { globals, inputs, outputs }; diff --git a/ts_src/lib/converter/output/silentPaymentOutputLabel.ts b/ts_src/lib/converter/output/silentPaymentOutputLabel.ts new file mode 100644 index 0000000..8d33cca --- /dev/null +++ b/ts_src/lib/converter/output/silentPaymentOutputLabel.ts @@ -0,0 +1,36 @@ +import { KeyValue, SilentPaymentOutputLabel } from '../../interfaces'; +import { OutputTypes } from '../../typeFields.js'; +import * as tools from 'uint8array-tools'; + +export function decode(keyVal: KeyValue): SilentPaymentOutputLabel { + if (keyVal.key[0] !== OutputTypes.SP_V0_LABEL) { + throw new Error( + 'Decode Error: could not decode silentPaymentOutputLabel with key 0x' + + tools.toHex(keyVal.key), + ); + } + return Number(tools.readUInt32(keyVal.value, 0, 'LE')); +} + +export function encode(data: SilentPaymentOutputLabel): KeyValue { + const key = Uint8Array.from([OutputTypes.SP_V0_LABEL]); + const value = new Uint8Array(4); + tools.writeUInt32(value, 0, data, 'LE'); + return { + key, + value, + }; +} + +export const expected = 'number'; +export function check(data: any): data is SilentPaymentOutputLabel { + return typeof data === 'number'; +} + +export function canAdd(currentData: any, newData: any): boolean { + return ( + !!currentData && + !!newData && + currentData.silentPaymentOutputLabel === undefined + ); +} diff --git a/ts_src/lib/converter/output/silentPaymentV0Info.ts b/ts_src/lib/converter/output/silentPaymentV0Info.ts new file mode 100644 index 0000000..c16b9b8 --- /dev/null +++ b/ts_src/lib/converter/output/silentPaymentV0Info.ts @@ -0,0 +1,58 @@ +import { SilentPaymentV0Info, KeyValue } from '../../interfaces'; +import { OutputTypes } from '../../typeFields.js'; +import * as tools from 'uint8array-tools'; + +const isValidPubKey = (pubkey: Uint8Array): boolean => + pubkey.length === 33 && [2, 3].includes(pubkey[0]); + +export function decode(keyVal: KeyValue): SilentPaymentV0Info { + if (keyVal.key[0] !== OutputTypes.SP_V0_INFO) { + throw new Error( + 'Decode Error: could not decode silentPaymentV0Info with key 0x' + + tools.toHex(keyVal.key), + ); + } + + if (keyVal.value.length !== 66) { + throw new Error('Decode Error: SP_V0_INFO is not proper length'); + } + + const scanKey = keyVal.value.slice(0, 33); + if (!isValidPubKey(scanKey)) { + throw new Error('Decode Error: SP_V0_INFO scanKey is not a valid pubkey'); + } + + const spendKey = keyVal.value.slice(33); + if (!isValidPubKey(spendKey)) { + throw new Error('Decode Error: SP_V0_INFO spendKey is not a valid pubkey'); + } + + return { + scanKey, + spendKey, + }; +} + +export function encode(data: SilentPaymentV0Info): KeyValue { + const key = new Uint8Array([OutputTypes.SP_V0_INFO]); + return { + key, + value: Uint8Array.from([...data.scanKey, ...data.spendKey]), + }; +} + +export const expected = '{ scanKey: Uint8Array; spendKey: Uint8Array; }'; +export function check(data: any): data is SilentPaymentV0Info { + return ( + data.scanKey instanceof Uint8Array && + data.spendKey instanceof Uint8Array && + isValidPubKey(data.scanKey) && + isValidPubKey(data.spendKey) + ); +} + +export function canAdd(currentData: any, newData: any): boolean { + return ( + !!currentData && !!newData && currentData.silentPaymentV0Info === undefined + ); +} diff --git a/ts_src/lib/converter/shared/silentPaymentDleq.ts b/ts_src/lib/converter/shared/silentPaymentDleq.ts new file mode 100644 index 0000000..ddf1cde --- /dev/null +++ b/ts_src/lib/converter/shared/silentPaymentDleq.ts @@ -0,0 +1,85 @@ +import { SilentPaymentDleq, KeyValue } from '../../interfaces'; +import * as tools from 'uint8array-tools'; + +const isValidPubKey = (pubkey: Uint8Array): boolean => + pubkey.length === 33 && [2, 3].includes(pubkey[0]); + +export function makeConverter( + TYPE_BYTE: number, + isValidPubkey = isValidPubKey, +): { + decode: (keyVal: KeyValue) => SilentPaymentDleq; + encode: (data: SilentPaymentDleq) => KeyValue; + check: (data: any) => data is SilentPaymentDleq; + expected: string; + canAddToArray: ( + array: SilentPaymentDleq[], + item: SilentPaymentDleq, + dupeSet: Set, + ) => boolean; +} { + function decode(keyVal: KeyValue): SilentPaymentDleq { + if (keyVal.key[0] !== TYPE_BYTE) { + throw new Error( + 'Decode Error: could not decode silentPaymentDleq with key 0x' + + tools.toHex(keyVal.key), + ); + } + const scanKey = keyVal.key.slice(1); + if (!isValidPubkey(scanKey)) { + throw new Error( + 'Decode Error: silentPaymentDleq has invalid scanKey in key 0x' + + tools.toHex(keyVal.key), + ); + } + if (keyVal.value.length !== 64) { + throw new Error('Decode Error: silentPaymentDleq not a 64-byte proof'); + } + return { + scanKey, + proof: keyVal.value, + }; + } + + function encode(data: SilentPaymentDleq): KeyValue { + const head = Uint8Array.from([TYPE_BYTE]); + const key = tools.concat([head, data.scanKey]); + + return { + key, + value: data.proof, + }; + } + + const expected = '{ scanKey: Uint8Array; proof: Uint8Array; }'; + function check(data: any): data is SilentPaymentDleq { + return ( + data.scanKey instanceof Uint8Array && + data.proof instanceof Uint8Array && + isValidPubkey(data.scanKey) && + data.proof.length === 64 + ); + } + + function canAddToArray( + array: SilentPaymentDleq[], + item: SilentPaymentDleq, + dupeSet: Set, + ): boolean { + const dupeString = tools.toHex(item.scanKey); + if (dupeSet.has(dupeString)) return false; + dupeSet.add(dupeString); + return ( + array.filter(v => tools.compare(v.scanKey, item.scanKey) === 0).length === + 0 + ); + } + + return { + decode, + encode, + check, + expected, + canAddToArray, + }; +} diff --git a/ts_src/lib/converter/shared/silentPaymentEcdhShare.ts b/ts_src/lib/converter/shared/silentPaymentEcdhShare.ts new file mode 100644 index 0000000..f382bb0 --- /dev/null +++ b/ts_src/lib/converter/shared/silentPaymentEcdhShare.ts @@ -0,0 +1,88 @@ +import { SilentPaymentEcdhShare, KeyValue } from '../../interfaces'; +import * as tools from 'uint8array-tools'; + +const isValidDERKey = (pubkey: Uint8Array): boolean => + (pubkey.length === 33 && [2, 3].includes(pubkey[0])) || + (pubkey.length === 65 && 4 === pubkey[0]); + +export function makeConverter( + TYPE_BYTE: number, + isValidPubkey = isValidDERKey, +): { + decode: (keyVal: KeyValue) => SilentPaymentEcdhShare; + encode: (data: SilentPaymentEcdhShare) => KeyValue; + check: (data: any) => data is SilentPaymentEcdhShare; + expected: string; + canAddToArray: ( + array: SilentPaymentEcdhShare[], + item: SilentPaymentEcdhShare, + dupeSet: Set, + ) => boolean; +} { + function decode(keyVal: KeyValue): SilentPaymentEcdhShare { + if (keyVal.key[0] !== TYPE_BYTE) { + throw new Error( + 'Decode Error: could not decode silentPaymentEcdhShare with key 0x' + + tools.toHex(keyVal.key), + ); + } + const scanKey = keyVal.key.slice(1); + if (!isValidPubkey(scanKey)) { + throw new Error( + 'Decode Error: silentPaymentEcdhShare has invalid scanKey in key 0x' + + tools.toHex(keyVal.key), + ); + } + if (!isValidPubkey(keyVal.value)) { + throw new Error( + 'Decode Error: silentPaymentEcdhShare not a 33-byte pubkey', + ); + } + return { + scanKey, + share: keyVal.value, + }; + } + + function encode(data: SilentPaymentEcdhShare): KeyValue { + const head = Uint8Array.from([TYPE_BYTE]); + const key = tools.concat([head, data.scanKey]); + + return { + key, + value: data.share, + }; + } + + const expected = '{ scanKey: Uint8Array; share: Uint8Array; }'; + function check(data: any): data is SilentPaymentEcdhShare { + return ( + data.scanKey instanceof Uint8Array && + data.share instanceof Uint8Array && + isValidPubkey(data.scanKey) && + isValidPubkey(data.share) + ); + } + + function canAddToArray( + array: SilentPaymentEcdhShare[], + item: SilentPaymentEcdhShare, + dupeSet: Set, + ): boolean { + const dupeString = tools.toHex(item.scanKey); + if (dupeSet.has(dupeString)) return false; + dupeSet.add(dupeString); + return ( + array.filter(v => tools.compare(v.scanKey, item.scanKey) === 0).length === + 0 + ); + } + + return { + decode, + encode, + check, + expected, + canAddToArray, + }; +} diff --git a/ts_src/lib/interfaces.ts b/ts_src/lib/interfaces.ts index d01e834..f95e163 100644 --- a/ts_src/lib/interfaces.ts +++ b/ts_src/lib/interfaces.ts @@ -14,7 +14,7 @@ export interface Transaction { // This function will modify the internal state of the transaction. addInput(objectArg: any): void; // Same as addInput. But with adding an output. For Bitcoin scriptPubkey - // and value are all that should be needed. + // or silentPayment and value are all that should be needed. addOutput(objectArg: any): void; // This is primarily used when serializing the PSBT to a binary. // You can implement caching behind the scenes if needed and clear the cache @@ -34,6 +34,8 @@ export interface PsbtGlobal extends PsbtGlobalUpdate { export interface PsbtGlobalUpdate { globalXpub?: GlobalXpub[]; + silentPaymentEcdhShare?: SilentPaymentEcdhShare[]; + silentPaymentDleq?: SilentPaymentDleq[]; } export interface PsbtInput extends PsbtInputUpdate { @@ -57,6 +59,8 @@ export interface PsbtInputUpdate { tapBip32Derivation?: TapBip32Derivation[]; tapInternalKey?: TapInternalKey; tapMerkleRoot?: TapMerkleRoot; + silentPaymentEcdhShare?: SilentPaymentEcdhShare[]; + silentPaymentDleq?: SilentPaymentDleq[]; } export interface PsbtInputExtended extends PsbtInput { @@ -74,6 +78,9 @@ export interface PsbtOutputUpdate { tapBip32Derivation?: TapBip32Derivation[]; tapTree?: TapTree; tapInternalKey?: TapInternalKey; + script?: OutputScript; + silentPaymentV0Info?: SilentPaymentV0Info; + silentPaymentOutputLabel?: SilentPaymentOutputLabel; } export interface PsbtOutputExtended extends PsbtOutput { @@ -118,6 +125,8 @@ export type PorCommitment = string; export type TapKeySig = Uint8Array; +export type OutputScript = Uint8Array; + export interface TapScriptSig extends PartialSig { leafHash: Uint8Array; } @@ -165,3 +174,20 @@ export type TransactionLocktimeSetter = ( locktime: number, txBuffer: Uint8Array, ) => Uint8Array; + +export interface SilentPaymentEcdhShare { + scanKey: Uint8Array; + share: Uint8Array; +} + +export interface SilentPaymentDleq { + scanKey: Uint8Array; + proof: Uint8Array; +} + +export interface SilentPaymentV0Info { + scanKey: Uint8Array; + spendKey: Uint8Array; +} + +export type SilentPaymentOutputLabel = number; diff --git a/ts_src/lib/parser/fromBuffer.ts b/ts_src/lib/parser/fromBuffer.ts index 14a0e62..3156eaf 100644 --- a/ts_src/lib/parser/fromBuffer.ts +++ b/ts_src/lib/parser/fromBuffer.ts @@ -193,6 +193,22 @@ export function psbtFromKeyVals( } globalMap.globalXpub.push(convert.globals.globalXpub.decode(keyVal)); break; + case GlobalTypes.GLOBAL_SP_ECDH_SHARE: + if (globalMap.silentPaymentEcdhShare === undefined) { + globalMap.silentPaymentEcdhShare = []; + } + globalMap.silentPaymentEcdhShare.push( + convert.globals.silentPaymentEcdhShare.decode(keyVal), + ); + break; + case GlobalTypes.GLOBAL_SP_DLEQ: + if (globalMap.silentPaymentDleq === undefined) { + globalMap.silentPaymentDleq = []; + } + globalMap.silentPaymentDleq.push( + convert.globals.silentPaymentDleq.decode(keyVal), + ); + break; default: // This will allow inclusion during serialization. if (!globalMap.unknownKeyVals) globalMap.unknownKeyVals = []; @@ -310,6 +326,22 @@ export function psbtFromKeyVals( checkKeyBuffer('input', keyVal.key, InputTypes.TAP_MERKLE_ROOT); input.tapMerkleRoot = convert.inputs.tapMerkleRoot.decode(keyVal); break; + case InputTypes.SP_ECDH_SHARE: + if (input.silentPaymentEcdhShare === undefined) { + input.silentPaymentEcdhShare = []; + } + input.silentPaymentEcdhShare.push( + convert.inputs.silentPaymentEcdhShare.decode(keyVal), + ); + break; + case InputTypes.SP_DLEQ: + if (input.silentPaymentDleq === undefined) { + input.silentPaymentDleq = []; + } + input.silentPaymentDleq.push( + convert.inputs.silentPaymentDleq.decode(keyVal), + ); + break; default: // This will allow inclusion during serialization. if (!input.unknownKeyVals) input.unknownKeyVals = []; @@ -363,6 +395,18 @@ export function psbtFromKeyVals( convert.outputs.tapBip32Derivation.decode(keyVal), ); break; + case OutputTypes.SP_V0_INFO: + checkKeyBuffer('output', keyVal.key, OutputTypes.SP_V0_INFO); + output.silentPaymentV0Info = convert.outputs.silentPaymentV0Info.decode( + keyVal, + ); + break; + case OutputTypes.SP_V0_LABEL: + checkKeyBuffer('output', keyVal.key, OutputTypes.SP_V0_LABEL); + output.silentPaymentOutputLabel = convert.outputs.silentPaymentOutputLabel.decode( + keyVal, + ); + break; default: if (!output.unknownKeyVals) output.unknownKeyVals = []; output.unknownKeyVals.push(keyVal); diff --git a/ts_src/lib/typeFields.ts b/ts_src/lib/typeFields.ts index f09ed0e..01d4e0a 100644 --- a/ts_src/lib/typeFields.ts +++ b/ts_src/lib/typeFields.ts @@ -1,8 +1,15 @@ export enum GlobalTypes { UNSIGNED_TX, GLOBAL_XPUB, + GLOBAL_SP_ECDH_SHARE = 0x07, + GLOBAL_SP_DLEQ, } -export const GLOBAL_TYPE_NAMES = ['unsignedTx', 'globalXpub']; +export const GLOBAL_TYPE_NAMES = [ + 'unsignedTx', + 'globalXpub', + 'silentPaymentEcdhShare', + 'silentPaymentDleq', +]; export enum InputTypes { NON_WITNESS_UTXO, @@ -21,6 +28,8 @@ export enum InputTypes { TAP_BIP32_DERIVATION, // multiple OK, key contains x-only pubkey TAP_INTERNAL_KEY, TAP_MERKLE_ROOT, + SP_ECDH_SHARE = 0x1d, // multiple OK, key contains silent payment scanKey + SP_DLEQ, // multiple OK, key contains silent payment scanKey } export const INPUT_TYPE_NAMES = [ 'nonWitnessUtxo', @@ -39,6 +48,8 @@ export const INPUT_TYPE_NAMES = [ 'tapBip32Derivation', 'tapInternalKey', 'tapMerkleRoot', + 'silentPaymentEcdhShare', + 'silentPaymentDleq', ]; export enum OutputTypes { @@ -48,6 +59,8 @@ export enum OutputTypes { TAP_INTERNAL_KEY = 0x05, TAP_TREE, TAP_BIP32_DERIVATION, // multiple OK, key contains x-only pubkey + SP_V0_INFO = 0x09, + SP_V0_LABEL, } export const OUTPUT_TYPE_NAMES = [ 'redeemScript', @@ -56,4 +69,6 @@ export const OUTPUT_TYPE_NAMES = [ 'tapInternalKey', 'tapTree', 'tapBip32Derivation', + 'silentPaymentV0Info', + 'silentPaymentV0Label', ];