@@ -4,67 +4,109 @@ import * as utxolib from '@bitgo/utxo-lib';
44
55import { DescriptorMap , toDescriptorMap } from '../core/descriptor' ;
66
7- import { DescriptorBuilder , parseDescriptor } from './builder' ;
8- import { NamedDescriptor } from './NamedDescriptor' ;
9-
10- export type DescriptorValidationPolicy = { allowedTemplates : DescriptorBuilder [ 'name' ] [ ] } | 'allowAll' ;
7+ import { parseDescriptor } from './builder' ;
8+ import { hasValidSignature , NamedDescriptor } from './NamedDescriptor' ;
119
1210export type KeyTriple = Triple < utxolib . BIP32Interface > ;
1311
14- function isDescriptorWithTemplate (
15- d : Descriptor ,
16- name : DescriptorBuilder [ 'name' ] ,
17- walletKeys : Triple < utxolib . BIP32Interface >
18- ) : boolean {
19- const parsed = parseDescriptor ( d ) ;
20- if ( parsed . name !== name ) {
21- return false ;
22- }
23- if ( parsed . keys . length !== walletKeys . length ) {
24- return false ;
12+ export interface DescriptorValidationPolicy {
13+ name : string ;
14+ validate ( d : Descriptor , walletKeys : KeyTriple , signatures : string [ ] ) : boolean ;
15+ }
16+
17+ export const policyAllowAll : DescriptorValidationPolicy = {
18+ name : 'allowAll' ,
19+ validate : ( ) => true ,
20+ } ;
21+
22+ export function getValidatorDescriptorTemplate ( name : string ) : DescriptorValidationPolicy {
23+ return {
24+ name : 'descriptorTemplate(' + name + ')' ,
25+ validate ( d : Descriptor , walletKeys : KeyTriple ) : boolean {
26+ const parsed = parseDescriptor ( d ) ;
27+ return (
28+ parsed . name === name &&
29+ parsed . keys . length === walletKeys . length &&
30+ parsed . keys . every ( ( k , i ) => k . toBase58 ( ) === walletKeys [ i ] . neutered ( ) . toBase58 ( ) )
31+ ) ;
32+ } ,
33+ } ;
34+ }
35+
36+ export function getValidatorEvery ( validators : DescriptorValidationPolicy [ ] ) : DescriptorValidationPolicy {
37+ return {
38+ name : 'every(' + validators . map ( ( v ) => v . name ) . join ( ',' ) + ')' ,
39+ validate ( d : Descriptor , walletKeys : KeyTriple , signatures : string [ ] ) : boolean {
40+ return validators . every ( ( v ) => v . validate ( d , walletKeys , signatures ) ) ;
41+ } ,
42+ } ;
43+ }
44+
45+ export function getValidatorSome ( validators : DescriptorValidationPolicy [ ] ) : DescriptorValidationPolicy {
46+ return {
47+ name : 'some(' + validators . map ( ( v ) => v . name ) . join ( ',' ) + ')' ,
48+ validate ( d : Descriptor , walletKeys : KeyTriple , signatures : string [ ] ) : boolean {
49+ return validators . some ( ( v ) => v . validate ( d , walletKeys , signatures ) ) ;
50+ } ,
51+ } ;
52+ }
53+
54+ export function getValidatorOneOfTemplates ( names : string [ ] ) : DescriptorValidationPolicy {
55+ return getValidatorSome ( names . map ( getValidatorDescriptorTemplate ) ) ;
56+ }
57+
58+ export function getValidatorSignedByUserKey ( ) : DescriptorValidationPolicy {
59+ return {
60+ name : 'signedByUser' ,
61+ validate ( d : Descriptor , walletKeys : KeyTriple , signatures : string [ ] ) : boolean {
62+ // the first key is the user key, by convention
63+ return hasValidSignature ( d , walletKeys [ 0 ] , signatures ) ;
64+ } ,
65+ } ;
66+ }
67+
68+ export class DescriptorPolicyValidationError extends Error {
69+ constructor ( descriptor : Descriptor , policy : DescriptorValidationPolicy ) {
70+ super ( `Descriptor ${ descriptor . toString ( ) } does not match policy ${ policy . name } ` ) ;
2571 }
26- return parsed . keys . every ( ( k , i ) => k . toBase58 ( ) === walletKeys [ i ] . toBase58 ( ) ) ;
2772}
2873
2974export function assertDescriptorPolicy (
3075 descriptor : Descriptor ,
3176 policy : DescriptorValidationPolicy ,
32- walletKeys : Triple < utxolib . BIP32Interface >
77+ walletKeys : KeyTriple ,
78+ signatures : string [ ]
3379) : void {
34- if ( policy === 'allowAll' ) {
35- return ;
36- }
37-
38- if ( 'allowedTemplates' in policy ) {
39- const allowed = policy . allowedTemplates ;
40- if ( ! allowed . some ( ( t ) => isDescriptorWithTemplate ( descriptor , t , walletKeys ) ) ) {
41- throw new Error ( `Descriptor ${ descriptor . toString ( ) } does not match any allowed template` ) ;
42- }
80+ if ( ! policy . validate ( descriptor , walletKeys , signatures ) ) {
81+ throw new DescriptorPolicyValidationError ( descriptor , policy ) ;
4382 }
44-
45- throw new Error ( `Unknown descriptor validation policy: ${ policy } ` ) ;
4683}
4784
4885export function toDescriptorMapValidate (
4986 descriptors : NamedDescriptor [ ] ,
5087 walletKeys : KeyTriple ,
5188 policy : DescriptorValidationPolicy
5289) : DescriptorMap {
53- const map = toDescriptorMap ( descriptors ) ;
54- for ( const descriptor of map . values ( ) ) {
55- assertDescriptorPolicy ( descriptor , policy , walletKeys ) ;
56- }
57- return map ;
90+ return toDescriptorMap (
91+ descriptors . map ( ( namedDescriptor ) => {
92+ const d = Descriptor . fromString ( namedDescriptor . value , 'derivable' ) ;
93+ assertDescriptorPolicy ( d , policy , walletKeys , namedDescriptor . signatures ?? [ ] ) ;
94+ return { name : namedDescriptor . name , value : d } ;
95+ } )
96+ ) ;
5897}
5998
6099export function getPolicyForEnv ( env : EnvironmentName ) : DescriptorValidationPolicy {
61100 switch ( env ) {
62101 case 'adminProd' :
63102 case 'prod' :
64- return {
65- allowedTemplates : [ 'Wsh2Of3' , 'Wsh2Of3CltvDrop' , 'ShWsh2Of3CltvDrop' ] ,
66- } ;
103+ return getValidatorSome ( [
104+ // allow all 2-of-3-ish descriptors where the keys match the wallet keys
105+ getValidatorOneOfTemplates ( [ 'Wsh2Of3' , 'Wsh2Of3CltvDrop' , 'ShWsh2Of3CltvDrop' ] ) ,
106+ // allow all descriptors signed by the user key
107+ getValidatorSignedByUserKey ( ) ,
108+ ] ) ;
67109 default :
68- return 'allowAll' ;
110+ return policyAllowAll ;
69111 }
70112}
0 commit comments