示例#1
0
class OracleTest(unittest.TestCase):
    def setUp(self):
        self.wallet_private_key = MasterKey.from_seed("aaa-2015-02-10".encode('utf8'))
        self.tx_db = dict()
        self.input_tx = Tx.tx_from_hex("0100000001d7e5d290d1363f9a3a1ee992d729f5e2f6938539e1eb6fd98ddd32f5211b66b8010000006a473044022043ac09592090ec32e75fe104aa97e87d31852d23ee17595659ea82e9e177822b0220727a37d1f93a088a99f907f924b92f2938b3a1e5093af32ee854382275fe06c1012103070454c3e8fea7c8e7e4a9c4d4a15e7e3088a0555e2ed303ec25d0f9bb0a75a6ffffffff02e09304000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f27387d2906406000000001976a9149fe455808b8f32c84f4c96db7865cfb2475bffbc88ac00000000")
        self.tx_db[self.input_tx.hash()] = self.input_tx
        self.account = make_multisig_account()
        self.oracle = Oracle(self.account, tx_db=self.tx_db)

    def make_partially_signed_tx(self):
        f = io.BytesIO(h2b(
            "01000000019cb9e92cd3f91087852382150f19b5d99259be47106d860055d1afb8110022250000000000ffffffff01d06c04000000000017a914f155ba65bdb30930da320ec51a0d6c913dfce06b8700000000e09304000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f27387"))
        unsigned = Tx.parse(f)
        unsigned.parse_unspents(f)
        script = self.account.script_for_path(TEST_PATH)
        child_key = self.wallet_private_key.subkey_for_path(TEST_PATH)
        local_sign(unsigned, [script], [child_key])
        return unsigned

    def test_sign_request(self):
        # generated with `tx -i 34DjTcNWGReJV4xx7R1AWK7FTz3xMwMcjA  3Ph5UGYHCyvYFQifw76T8iqKL9EkGKDBMz/100000 -o tx.hex`
        unsigned = self.make_partially_signed_tx()
        req = self.oracle._create_oracle_request([TEST_PATH], [], None, unsigned)
        self.assertEqual("01000000019cb9e92cd3f91087852382150f19b5d99259be47106d860055d1afb81100222500000000b500473044022042b1b79675985a46e021c056708420f0bade9cdc4b336b55c53d0f22488f34e40220795cbd8291f083ea32eb29e8ace895852823611927b9ba7e94a333f022f5dd4301004c69522102fa0e06db47e8924274c670503238db30367d11ccaca00d385ac370fed93578d2210379014532a465b19fcf1ead9921488274821fd58178542b2aa54007bcc5a29d34210381c235ee18d9e85e3b28200200df3a2276c6b9473f18946ef8740ccaebfa4b1e53aeffffffff01d06c04000000000017a914f155ba65bdb30930da320ec51a0d6c913dfce06b8700000000",
                         req['transaction']['bytes'])

    def make_partially_signed_tx_with_change(self):
        # generated with `tx -i 34DjTcNWGReJV4xx7R1AWK7FTz3xMwMcjA  3Ph5UGYHCyvYFQifw76T8iqKL9EkGKDBMz/90000 34DjTcNWGReJV4xx7R1AWK7FTz3xMwMcjA/200000 -o tx1.hex`
        # signing would be with `digital_oracle sign P:aaa-2015-02-10 P:bbb tx1.hex -i 0/0/1 -c - 0/0/1`
        f = io.BytesIO(h2b(
            "01000000019cb9e92cd3f91087852382150f19b5d99259be47106d860055d1afb8110022250000000000ffffffff02905f01000000000017a914f155ba65bdb30930da320ec51a0d6c913dfce06b87400d03000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f2738700000000e09304000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f27387"))
        unsigned = Tx.parse(f)
        unsigned.parse_unspents(f)
        script = self.account.script_for_path(TEST_PATH)
        child_key = self.wallet_private_key.subkey_for_path(TEST_PATH)
        local_sign(unsigned, [script], [child_key])
        return unsigned

    def test_sign_request_change(self):
        unsigned = self.make_partially_signed_tx_with_change()
        req = self.oracle._create_oracle_request([TEST_PATH], [None, TEST_PATH], None, unsigned)
        self.assertEqual("01000000019cb9e92cd3f91087852382150f19b5d99259be47106d860055d1afb81100222500000000b600483045022100a90e9e7e4ddc7de0e8f39166e40819a8c99a1b68389cf0e6e3518d592e3e271502201eab29e5069988188886827a31248faacecb723c28da228626b1e77f080a2e7701004c69522102fa0e06db47e8924274c670503238db30367d11ccaca00d385ac370fed93578d2210379014532a465b19fcf1ead9921488274821fd58178542b2aa54007bcc5a29d34210381c235ee18d9e85e3b28200200df3a2276c6b9473f18946ef8740ccaebfa4b1e53aeffffffff02905f01000000000017a914f155ba65bdb30930da320ec51a0d6c913dfce06b87400d03000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f2738700000000",
                         req['transaction']['bytes'])
        JSON = '{"walletAgent": "multisig-core-0.01", "transaction": {"outputChainPaths": [null, "0/0/1"], "bytes": "01000000019cb9e92cd3f91087852382150f19b5d99259be47106d860055d1afb81100222500000000b600483045022100a90e9e7e4ddc7de0e8f39166e40819a8c99a1b68389cf0e6e3518d592e3e271502201eab29e5069988188886827a31248faacecb723c28da228626b1e77f080a2e7701004c69522102fa0e06db47e8924274c670503238db30367d11ccaca00d385ac370fed93578d2210379014532a465b19fcf1ead9921488274821fd58178542b2aa54007bcc5a29d34210381c235ee18d9e85e3b28200200df3a2276c6b9473f18946ef8740ccaebfa4b1e53aeffffffff02905f01000000000017a914f155ba65bdb30930da320ec51a0d6c913dfce06b87400d03000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f2738700000000", "inputScripts": ["522102fa0e06db47e8924274c670503238db30367d11ccaca00d385ac370fed93578d2210379014532a465b19fcf1ead9921488274821fd58178542b2aa54007bcc5a29d34210381c235ee18d9e85e3b28200200df3a2276c6b9473f18946ef8740ccaebfa4b1e53ae"], "masterKeys": ["xpub661MyMwAqRbcFqtR38s6kVQudQxKpHzNJyWEXmz2TnuDoR8FpZR7EuL158B5QDaYvxCfp3LAEa8VwdtxNgKHNha4JKqGrqkzBGboJFwgyrR", "xpub661MyMwAqRbcGmRK6wKJrfMXoenZ86PMUfBWNvmmp5c51PyyzjY7yJL9venRUYqmSqNo7iGqHbVWkTVYzY2drw57vr45iHxV7NsAqF4ZWg5"], "chainPaths": ["0/0/1"], "inputTransactions": ["0100000001d7e5d290d1363f9a3a1ee992d729f5e2f6938539e1eb6fd98ddd32f5211b66b8010000006a473044022043ac09592090ec32e75fe104aa97e87d31852d23ee17595659ea82e9e177822b0220727a37d1f93a088a99f907f924b92f2938b3a1e5093af32ee854382275fe06c1012103070454c3e8fea7c8e7e4a9c4d4a15e7e3088a0555e2ed303ec25d0f9bb0a75a6ffffffff02e09304000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f27387d2906406000000001976a9149fe455808b8f32c84f4c96db7865cfb2475bffbc88ac00000000"]}}'
        self.maxDiff = None
        self.assertEqual(json.loads(json.dumps(req)), json.loads(JSON))

    def test_sign(self):
        self._request = None
        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code": 200,
                "content": json.dumps({"result": "success", "now": "2010-01-01 00:00:00Z", "spendId": "aaa"}).encode("utf8")
            }

        with HTTMock(digitaloracle_mock):
            unsigned = self.make_partially_signed_tx_with_change()
            payto = self.account.payto_for_path(TEST_PATH)
            verifications = {"otp": "123456"}
            res = self.oracle.sign_with_paths(unsigned, [TEST_PATH], [None, TEST_PATH], verifications=verifications)
            req = json.loads(self._request.body)
            self.assertEqual(req['transaction']['chainPaths'], ["0/0/1"])
            self.assertEqual(req['transaction']['outputChainPaths'], [None, "0/0/1"])
            self.assertEqual(req['verifications'], verifications)
            self.assertEqual("01000000019cb9e92cd3f91087852382150f19b5d99259be47106d860055d1afb81100222500000000b600483045022100a90e9e7e4ddc7de0e8f39166e40819a8c99a1b68389cf0e6e3518d592e3e271502201eab29e5069988188886827a31248faacecb723c28da228626b1e77f080a2e7701004c69522102fa0e06db47e8924274c670503238db30367d11ccaca00d385ac370fed93578d2210379014532a465b19fcf1ead9921488274821fd58178542b2aa54007bcc5a29d34210381c235ee18d9e85e3b28200200df3a2276c6b9473f18946ef8740ccaebfa4b1e53aeffffffff02905f01000000000017a914f155ba65bdb30930da320ec51a0d6c913dfce06b87400d03000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f2738700000000",
                             req['transaction']['bytes'])
            self.assertEqual(res.spend_id, "aaa")

    def test_sign_fail(self):
        self._request = None
        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code": 400,
                "content": json.dumps({"error": "failed"}).encode("utf8")
            }

        with HTTMock(digitaloracle_mock):
            unsigned = self.make_partially_signed_tx_with_change()
            payto = self.account.payto_for_path(TEST_PATH)
            try:
                self.oracle.sign_with_paths(unsigned, [TEST_PATH], [None, TEST_PATH])
                self.fail()
            except OracleError as e:
                pass #expected

    def test_sign_defer(self):
        self._request = None
        until = "2010-01-01 00:01:00Z"

        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code": 200,
                "content": json.dumps({"result": "deferred",
                                       "now": "2010-01-01 00:00:00Z",
                                       "spendId": "aaa",
                                       "deferral": {"reason": "delay", "until": until, "verifications":["otp"]}}).encode("utf8")
            }

        with HTTMock(digitaloracle_mock):
            unsigned = self.make_partially_signed_tx_with_change()
            payto = self.account.payto_for_path(TEST_PATH)
            try:
                self.oracle.sign_with_paths(unsigned, [TEST_PATH], [None, TEST_PATH])
                self.fail()
            except OracleDeferralException as e:
                self.assertEquals(e.until, dateutil.parser.parse(until))
                self.assertEquals(e.verifications, ["otp"])

    def test_sign_rejected(self):
        self._request = None
        until = "2010-01-01 00:01:00Z"

        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code": 200,
                "content": json.dumps({"result": "rejected",
                                       "now": "2010-01-01 00:00:00Z"}).encode("utf8")
            }

        with HTTMock(digitaloracle_mock):
            unsigned = self.make_partially_signed_tx_with_change()
            payto = self.account.payto_for_path(TEST_PATH)
            try:
                self.oracle.sign_with_paths(unsigned, [TEST_PATH], [None, TEST_PATH])
                self.fail()
            except OracleRejectionException as e:
                pass # expected

    def test_sign_lockout(self):
        self._request = None
        until = "2010-01-01 00:01:00Z"

        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code": 200,
                "content": json.dumps({"result": "locked",
                                       "now": "2010-01-01 00:00:00Z"}).encode("utf8")
            }

        with HTTMock(digitaloracle_mock):
            unsigned = self.make_partially_signed_tx_with_change()
            payto = self.account.payto_for_path(TEST_PATH)
            try:
                self.oracle.sign_with_paths(unsigned, [TEST_PATH], [None, TEST_PATH])
                self.fail()
            except OracleLockoutException as e:
                pass # expected

    def test_create(self):
        new_account = make_incomplete_multisig_account()
        oracle = Oracle(new_account)
        calls = ['email']
        parameters = {
            "levels": [
                {"asset": "BTC", "period": 60, "value": 0.001},
                {"delay": 0, "calls": calls}
            ]
        }
        self._request = None

        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code": 200,
                "content": json.dumps({"result": "success", "now": "2010-01-01 00:00:00Z", "keys": {"default": [oracle_key.hwif()]}}).encode('utf8')
            }

        with HTTMock(digitaloracle_mock):
            personal_info = PersonalInformation(email="*****@*****.**")
            oracle.create(parameters, personal_info)
            self.assertTrue(new_account.complete)
            self.assertEqual(self.account.address(111), new_account.address(111))

    def test_verify(self):
        new_account = make_incomplete_multisig_account()
        oracle = Oracle(new_account)
        calls = ['email']
        self._request = None
        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code": 200,
                "content": json.dumps({"result": "success", "now": "2010-01-01 00:00:00Z"}).encode("utf8")
            }

        with HTTMock(digitaloracle_mock):
            personal_info = PersonalInformation(email="*****@*****.**", phone="+14155551212")
            oracle.verify_personal_information(personal_info, call="phone", callback="http://a.com/")
示例#2
0
def main():
    parser = argparse.ArgumentParser(
        description='DigitalOracle HD multisig command line utility',
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument('-e', '--email', help='e-mail for create')
    parser.add_argument(
        '-p',
        '--phone',
        help='phone number for create - in the format +14155551212')
    parser.add_argument('-n',
                        '--network',
                        default='BTC',
                        choices=NETWORK_NAMES)
    parser.add_argument('-i',
                        "--inputpath",
                        nargs='+',
                        help='HD subkey path for each input (example: 0/2/15)')
    parser.add_argument(
        '-c',
        "--changepath",
        nargs='+',
        help=
        'HD subkey path for each change output (example: 0/2/15) or a dash for non-change outputs'
    )
    parser.add_argument(
        '-s',
        "--spendid",
        help=
        'an additional hex string to disambiguate spends to the same address')
    parser.add_argument(
        '-u',
        "--baseurl",
        help=
        'the API endpoint, defaults to the sandbox - https://s.digitaloracle.co/'
    )
    parser.add_argument('-v',
                        "--verbose",
                        default=0,
                        action="count",
                        help="Verbosity, use more -v flags for more verbosity")
    parser.add_argument('command', help="""a command""")
    parser.add_argument('item', nargs='+', help="""a key""")
    parser.epilog = textwrap.dedent("""
    Items:
     * P:wallet_passphrase - a secret for deriving an HD hierarchy with private keys
     * xpub - an account extended public key for deriving an HD hierarchy with public keys only
     * FILE.bin - unsigned transaction binary
     * FILE.hex - unsigned transaction hex

    Commands:
     * dump - dump the public subkeys
     * create - create Oracle account based on the supplied leading key with with any additional keys
     * address - get the deposit address for a subkey path
     * sign - sign a transaction, tx.bin or tx.hex must be supplied. Only one subkey path is supported.

    Notes:
     * --subkey is applicable for the address and sign actions, but not the create action
    """)
    args = parser.parse_args()

    keys = []
    txs = []
    for item in args.item:
        key = None
        tx = None
        if item.startswith('P:'):
            s = item[2:]
            key = BIP32Node.from_master_secret(s.encode('utf8'),
                                               netcode=args.network)
            keys.append(key)
        else:
            try:
                key = Key.from_text(item)
                keys.append(key)
            except encoding.EncodingError:
                pass
        if key is None:
            if os.path.exists(item):
                try:
                    with open(item, "rb") as f:
                        if f.name.endswith("hex"):
                            f = io.BytesIO(
                                codecs.getreader("hex_codec")(f).read())
                        tx = Tx.parse(f)
                        txs.append(tx)
                        try:
                            tx.parse_unspents(f)
                        except Exception as ex:
                            pass
                        continue
                except Exception as ex:
                    print('could not parse %s %s' % (item, ex),
                          file=sys.stderr)
                    pass
        if tx is None and key is None:
            print('could not understand item %s' % (item, ))

    account = MultisigAccount(keys, complete=False)
    oracle = Oracle(account, tx_db=get_tx_db(), base_url=args.baseurl)
    oracle.verbose = args.verbose

    if args.command == 'dump':
        sub_keys = [
            key.subkey_for_path(path or "")
            for key, path in izip_longest(keys, args.inputpath)
        ]
        for key in sub_keys:
            print(key.wallet_key(as_private=False))
    elif args.command == 'create':
        calls = []
        if args.email:
            calls.append('email')
        if args.phone:
            calls.append('phone')
        parameters = {
            "levels": [{
                "asset": "BTC",
                "period": 60,
                "value": 0.001
            }, {
                "delay": 0,
                "calls": calls
            }]
        }
        oracle.create(parameters, email=args.email, phone=args.phone)
    elif args.command == 'address':
        oracle.get()
        path = args.inputpath[0] if args.inputpath else ""
        payto = account.payto_for_path(path)
        if args.verbose > 0:
            sub_keys = [key.subkey_for_path(path) for key in account.keys]
            print("* account keys")
            print(account.keys)
            print("* child keys")
            for key in sub_keys:
                print(key.wallet_key(as_private=False))
            print("* address")
        print(payto.address(netcode=args.network))
    elif args.command == 'sign':
        oracle.get()
        scripts = [account.script_for_path(path) for path in args.inputpath]
        change_paths = [
            None if path == '-' else path for path in (args.changepath or [])
        ]
        for tx in txs:
            print(tx.id())
            print(b2h(stream_to_bytes(tx.stream)))
            sub_keys = [
                keys[0].subkey_for_path(path) for path in args.inputpath
            ]
            # sign locally
            local_sign(tx, scripts, sub_keys)
            # have Oracle sign
            result = oracle.sign_with_paths(tx,
                                            args.inputpath,
                                            change_paths,
                                            spend_id=args.spendid)
            print("Result:")
            print(result)
            if 'transaction' in result:
                print("Hex serialized transaction:")
                print(b2h(stream_to_bytes(result['transaction'].stream)))
    else:
        print('unknown command %s' % (args.command, ), file=sys.stderr)
示例#3
0
class OracleTest(unittest.TestCase):
    def setUp(self):
        self.wallet_private_key = MasterKey.from_seed(
            "aaa-2015-02-10".encode('utf8'))
        self.tx_db = dict()
        self.input_tx = Tx.tx_from_hex(
            "0100000001d7e5d290d1363f9a3a1ee992d729f5e2f6938539e1eb6fd98ddd32f5211b66b8010000006a473044022043ac09592090ec32e75fe104aa97e87d31852d23ee17595659ea82e9e177822b0220727a37d1f93a088a99f907f924b92f2938b3a1e5093af32ee854382275fe06c1012103070454c3e8fea7c8e7e4a9c4d4a15e7e3088a0555e2ed303ec25d0f9bb0a75a6ffffffff02e09304000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f27387d2906406000000001976a9149fe455808b8f32c84f4c96db7865cfb2475bffbc88ac00000000"
        )
        self.tx_db[self.input_tx.hash()] = self.input_tx
        self.account = make_multisig_account()
        self.oracle = Oracle(self.account, tx_db=self.tx_db)

    def make_partially_signed_tx(self):
        f = io.BytesIO(
            h2b("01000000019cb9e92cd3f91087852382150f19b5d99259be47106d860055d1afb8110022250000000000ffffffff01d06c04000000000017a914f155ba65bdb30930da320ec51a0d6c913dfce06b8700000000e09304000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f27387"
                ))
        unsigned = Tx.parse(f)
        unsigned.parse_unspents(f)
        script = self.account.script_for_path(TEST_PATH)
        child_key = self.wallet_private_key.subkey_for_path(TEST_PATH)
        local_sign(unsigned, [script], [child_key])
        return unsigned

    def test_pointers(self):
        self.assertEqual([self.oracle], self.account.oracles)

    def test_sign_request(self):
        # generated with `tx -i 34DjTcNWGReJV4xx7R1AWK7FTz3xMwMcjA  3Ph5UGYHCyvYFQifw76T8iqKL9EkGKDBMz/100000 -o tx.hex`
        unsigned = self.make_partially_signed_tx()
        req = self.oracle._create_oracle_request([TEST_PATH], [], None,
                                                 unsigned)
        self.assertEqual(
            "01000000019cb9e92cd3f91087852382150f19b5d99259be47106d860055d1afb81100222500000000b500473044022042b1b79675985a46e021c056708420f0bade9cdc4b336b55c53d0f22488f34e40220795cbd8291f083ea32eb29e8ace895852823611927b9ba7e94a333f022f5dd4301004c69522102fa0e06db47e8924274c670503238db30367d11ccaca00d385ac370fed93578d2210379014532a465b19fcf1ead9921488274821fd58178542b2aa54007bcc5a29d34210381c235ee18d9e85e3b28200200df3a2276c6b9473f18946ef8740ccaebfa4b1e53aeffffffff01d06c04000000000017a914f155ba65bdb30930da320ec51a0d6c913dfce06b8700000000",
            req['transaction']['bytes'])

    def make_partially_signed_tx_with_change(self):
        # generated with `tx -i 34DjTcNWGReJV4xx7R1AWK7FTz3xMwMcjA  3Ph5UGYHCyvYFQifw76T8iqKL9EkGKDBMz/90000 34DjTcNWGReJV4xx7R1AWK7FTz3xMwMcjA/200000 -o tx1.hex`
        # signing would be with `digital_oracle sign P:aaa-2015-02-10 P:bbb tx1.hex -i 0/0/1 -c - 0/0/1`
        f = io.BytesIO(
            h2b("01000000019cb9e92cd3f91087852382150f19b5d99259be47106d860055d1afb8110022250000000000ffffffff02905f01000000000017a914f155ba65bdb30930da320ec51a0d6c913dfce06b87400d03000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f2738700000000e09304000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f27387"
                ))
        unsigned = Tx.parse(f)
        unsigned.parse_unspents(f)
        script = self.account.script_for_path(TEST_PATH)
        child_key = self.wallet_private_key.subkey_for_path(TEST_PATH)
        local_sign(unsigned, [script], [child_key])
        return unsigned

    def test_sign_request_change(self):
        unsigned = self.make_partially_signed_tx_with_change()
        req = self.oracle._create_oracle_request([TEST_PATH],
                                                 [None, TEST_PATH], None,
                                                 unsigned)
        self.assertEqual(
            "01000000019cb9e92cd3f91087852382150f19b5d99259be47106d860055d1afb81100222500000000b600483045022100a90e9e7e4ddc7de0e8f39166e40819a8c99a1b68389cf0e6e3518d592e3e271502201eab29e5069988188886827a31248faacecb723c28da228626b1e77f080a2e7701004c69522102fa0e06db47e8924274c670503238db30367d11ccaca00d385ac370fed93578d2210379014532a465b19fcf1ead9921488274821fd58178542b2aa54007bcc5a29d34210381c235ee18d9e85e3b28200200df3a2276c6b9473f18946ef8740ccaebfa4b1e53aeffffffff02905f01000000000017a914f155ba65bdb30930da320ec51a0d6c913dfce06b87400d03000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f2738700000000",
            req['transaction']['bytes'])
        JSON = '{"walletAgent": "multisig-core-0.01", "transaction": {"outputChainPaths": [null, "0/0/1"], "bytes": "01000000019cb9e92cd3f91087852382150f19b5d99259be47106d860055d1afb81100222500000000b600483045022100a90e9e7e4ddc7de0e8f39166e40819a8c99a1b68389cf0e6e3518d592e3e271502201eab29e5069988188886827a31248faacecb723c28da228626b1e77f080a2e7701004c69522102fa0e06db47e8924274c670503238db30367d11ccaca00d385ac370fed93578d2210379014532a465b19fcf1ead9921488274821fd58178542b2aa54007bcc5a29d34210381c235ee18d9e85e3b28200200df3a2276c6b9473f18946ef8740ccaebfa4b1e53aeffffffff02905f01000000000017a914f155ba65bdb30930da320ec51a0d6c913dfce06b87400d03000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f2738700000000", "inputScripts": ["522102fa0e06db47e8924274c670503238db30367d11ccaca00d385ac370fed93578d2210379014532a465b19fcf1ead9921488274821fd58178542b2aa54007bcc5a29d34210381c235ee18d9e85e3b28200200df3a2276c6b9473f18946ef8740ccaebfa4b1e53ae"], "masterKeys": ["xpub661MyMwAqRbcFqtR38s6kVQudQxKpHzNJyWEXmz2TnuDoR8FpZR7EuL158B5QDaYvxCfp3LAEa8VwdtxNgKHNha4JKqGrqkzBGboJFwgyrR", "xpub661MyMwAqRbcGmRK6wKJrfMXoenZ86PMUfBWNvmmp5c51PyyzjY7yJL9venRUYqmSqNo7iGqHbVWkTVYzY2drw57vr45iHxV7NsAqF4ZWg5"], "chainPaths": ["0/0/1"], "inputTransactions": ["0100000001d7e5d290d1363f9a3a1ee992d729f5e2f6938539e1eb6fd98ddd32f5211b66b8010000006a473044022043ac09592090ec32e75fe104aa97e87d31852d23ee17595659ea82e9e177822b0220727a37d1f93a088a99f907f924b92f2938b3a1e5093af32ee854382275fe06c1012103070454c3e8fea7c8e7e4a9c4d4a15e7e3088a0555e2ed303ec25d0f9bb0a75a6ffffffff02e09304000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f27387d2906406000000001976a9149fe455808b8f32c84f4c96db7865cfb2475bffbc88ac00000000"]}}'
        self.maxDiff = None
        self.assertEqual(json.loads(json.dumps(req)), json.loads(JSON))

    def test_sign(self):
        self._request = None

        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code":
                200,
                "content":
                json.dumps({
                    "result": "success",
                    "now": "2010-01-01 00:00:00Z",
                    "spendId": "aaa"
                }).encode("utf8")
            }

        with HTTMock(digitaloracle_mock):
            unsigned = self.make_partially_signed_tx_with_change()
            payto = self.account.payto_for_path(TEST_PATH)
            verifications = {"otp": "123456"}
            res = self.oracle.sign_with_paths(unsigned, [TEST_PATH],
                                              [None, TEST_PATH],
                                              verifications=verifications)
            req = json.loads(self._request.body)
            self.assertEqual(req['transaction']['chainPaths'], ["0/0/1"])
            self.assertEqual(req['transaction']['outputChainPaths'],
                             [None, "0/0/1"])
            self.assertEqual(req['verifications'], verifications)
            self.assertEqual(
                "01000000019cb9e92cd3f91087852382150f19b5d99259be47106d860055d1afb81100222500000000b600483045022100a90e9e7e4ddc7de0e8f39166e40819a8c99a1b68389cf0e6e3518d592e3e271502201eab29e5069988188886827a31248faacecb723c28da228626b1e77f080a2e7701004c69522102fa0e06db47e8924274c670503238db30367d11ccaca00d385ac370fed93578d2210379014532a465b19fcf1ead9921488274821fd58178542b2aa54007bcc5a29d34210381c235ee18d9e85e3b28200200df3a2276c6b9473f18946ef8740ccaebfa4b1e53aeffffffff02905f01000000000017a914f155ba65bdb30930da320ec51a0d6c913dfce06b87400d03000000000017a9141bbf6712630dd01fab4e70ac91a06925d138f2738700000000",
                req['transaction']['bytes'])
            self.assertEqual(res.spend_id, "aaa")

    def test_sign_fail(self):
        self._request = None

        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code": 400,
                "content": json.dumps({
                    "error": "failed"
                }).encode("utf8")
            }

        with HTTMock(digitaloracle_mock):
            unsigned = self.make_partially_signed_tx_with_change()
            payto = self.account.payto_for_path(TEST_PATH)
            try:
                self.oracle.sign_with_paths(unsigned, [TEST_PATH],
                                            [None, TEST_PATH])
                self.fail()
            except OracleError as e:
                pass  #expected

    def test_sign_defer(self):
        self._request = None
        until = "2010-01-01 00:01:00Z"

        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code":
                200,
                "content":
                json.dumps({
                    "result": "deferred",
                    "now": "2010-01-01 00:00:00Z",
                    "spendId": "aaa",
                    "deferral": {
                        "reason": "delay",
                        "until": until,
                        "verifications": ["otp"]
                    }
                }).encode("utf8")
            }

        with HTTMock(digitaloracle_mock):
            unsigned = self.make_partially_signed_tx_with_change()
            payto = self.account.payto_for_path(TEST_PATH)
            try:
                self.oracle.sign_with_paths(unsigned, [TEST_PATH],
                                            [None, TEST_PATH])
                self.fail()
            except OracleDeferralException as e:
                self.assertEquals(e.until, dateutil.parser.parse(until))
                self.assertEquals(e.verifications, ["otp"])

    def test_sign_rejected(self):
        self._request = None
        until = "2010-01-01 00:01:00Z"

        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code":
                200,
                "content":
                json.dumps({
                    "result": "rejected",
                    "now": "2010-01-01 00:00:00Z"
                }).encode("utf8")
            }

        with HTTMock(digitaloracle_mock):
            unsigned = self.make_partially_signed_tx_with_change()
            payto = self.account.payto_for_path(TEST_PATH)
            try:
                self.oracle.sign_with_paths(unsigned, [TEST_PATH],
                                            [None, TEST_PATH])
                self.fail()
            except OracleRejectionException as e:
                pass  # expected

    def test_sign_lockout(self):
        self._request = None
        until = "2010-01-01 00:01:00Z"

        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code":
                200,
                "content":
                json.dumps({
                    "result": "locked",
                    "now": "2010-01-01 00:00:00Z"
                }).encode("utf8")
            }

        with HTTMock(digitaloracle_mock):
            unsigned = self.make_partially_signed_tx_with_change()
            payto = self.account.payto_for_path(TEST_PATH)
            try:
                self.oracle.sign_with_paths(unsigned, [TEST_PATH],
                                            [None, TEST_PATH])
                self.fail()
            except OracleLockoutException as e:
                pass  # expected

    def test_create(self):
        new_account = make_incomplete_multisig_account()
        oracle = Oracle(new_account)
        calls = ['email']
        parameters = {
            "levels": [{
                "asset": "BTC",
                "period": 60,
                "value": 0.001
            }, {
                "delay": 0,
                "calls": calls
            }]
        }
        self._request = None

        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code":
                200,
                "content":
                json.dumps({
                    "result": "success",
                    "now": "2010-01-01 00:00:00Z",
                    "keys": {
                        "default": [oracle_key.hwif()]
                    }
                }).encode('utf8')
            }

        with HTTMock(digitaloracle_mock):
            personal_info = PersonalInformation(email="*****@*****.**")
            oracle.create(parameters, personal_info)
            self.assertTrue(new_account.complete)
            self.assertEqual(self.account.address(111),
                             new_account.address(111))

    def test_verify(self):
        new_account = make_incomplete_multisig_account()
        oracle = Oracle(new_account)
        calls = ['email']
        self._request = None

        def digitaloracle_mock(url, request):
            self._request = request
            return {
                "status_code":
                200,
                "content":
                json.dumps({
                    "result": "success",
                    "now": "2010-01-01 00:00:00Z"
                }).encode("utf8")
            }

        with HTTMock(digitaloracle_mock):
            personal_info = PersonalInformation(email="*****@*****.**",
                                                phone="+14155551212")
            oracle.verify_personal_information(personal_info,
                                               call="phone",
                                               callback="http://a.com/")
示例#4
0
def main():
    parser = argparse.ArgumentParser(
        description="DigitalOracle HD multisig command line utility",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument("-e", "--email", help="e-mail for create")
    parser.add_argument("-p", "--phone", help="phone number for create - in the format +14155551212")
    parser.add_argument("-n", "--network", default="BTC", choices=NETWORK_NAMES)
    parser.add_argument("-i", "--inputpath", nargs="+", help="HD subkey path for each input (example: 0/2/15)")
    parser.add_argument(
        "-c",
        "--changepath",
        nargs="+",
        help="HD subkey path for each change output (example: 0/2/15) or a dash for non-change outputs",
    )
    parser.add_argument("-s", "--spendid", help="an additional hex string to disambiguate spends to the same address")
    parser.add_argument(
        "-u", "--baseurl", help="the API endpoint, defaults to the sandbox - https://s.digitaloracle.co/"
    )
    parser.add_argument(
        "-v", "--verbose", default=0, action="count", help="Verbosity, use more -v flags for more verbosity"
    )
    parser.add_argument("command", help="""a command""")
    parser.add_argument("item", nargs="+", help="""a key""")
    parser.epilog = textwrap.dedent(
        """
    Items:
     * P:wallet_passphrase - a secret for deriving an HD hierarchy with private keys
     * xpub - an account extended public key for deriving an HD hierarchy with public keys only
     * FILE.bin - unsigned transaction binary
     * FILE.hex - unsigned transaction hex

    Commands:
     * dump - dump the public subkeys
     * create - create Oracle account based on the supplied leading key with with any additional keys
     * address - get the deposit address for a subkey path
     * sign - sign a transaction, tx.bin or tx.hex must be supplied. Only one subkey path is supported.

    Notes:
     * --subkey is applicable for the address and sign actions, but not the create action
    """
    )
    args = parser.parse_args()

    keys = []
    txs = []
    for item in args.item:
        key = None
        tx = None
        if item.startswith("P:"):
            s = item[2:]
            key = BIP32Node.from_master_secret(s.encode("utf8"), netcode=args.network)
            keys.append(key)
        else:
            try:
                key = Key.from_text(item)
                keys.append(key)
            except encoding.EncodingError:
                pass
        if key is None:
            if os.path.exists(item):
                try:
                    with open(item, "rb") as f:
                        if f.name.endswith("hex"):
                            f = io.BytesIO(codecs.getreader("hex_codec")(f).read())
                        tx = Tx.parse(f)
                        txs.append(tx)
                        try:
                            tx.parse_unspents(f)
                        except Exception as ex:
                            pass
                        continue
                except Exception as ex:
                    print("could not parse %s %s" % (item, ex), file=sys.stderr)
                    pass
        if tx is None and key is None:
            print("could not understand item %s" % (item,))

    account = MultisigAccount(keys, complete=False)
    oracle = Oracle(account, tx_db=get_tx_db(), base_url=args.baseurl)
    oracle.verbose = args.verbose

    if args.command == "dump":
        sub_keys = [key.subkey_for_path(path or "") for key, path in izip_longest(keys, args.inputpath)]
        for key in sub_keys:
            print(key.wallet_key(as_private=False))
    elif args.command == "create":
        calls = []
        if args.email:
            calls.append("email")
        if args.phone:
            calls.append("phone")
        parameters = {"levels": [{"asset": "BTC", "period": 60, "value": 0.001}, {"delay": 0, "calls": calls}]}
        oracle.create(parameters, email=args.email, phone=args.phone)
    elif args.command == "address":
        oracle.get()
        path = args.inputpath[0] if args.inputpath else ""
        payto = account.payto_for_path(path)
        if args.verbose > 0:
            sub_keys = [key.subkey_for_path(path) for key in account.keys]
            print("* account keys")
            print(account.keys)
            print("* child keys")
            for key in sub_keys:
                print(key.wallet_key(as_private=False))
            print("* address")
        print(payto.address(netcode=args.network))
    elif args.command == "sign":
        oracle.get()
        scripts = [account.script_for_path(path) for path in args.inputpath]
        change_paths = [None if path == "-" else path for path in (args.changepath or [])]
        for tx in txs:
            print(tx.id())
            print(b2h(stream_to_bytes(tx.stream)))
            sub_keys = [keys[0].subkey_for_path(path) for path in args.inputpath]
            # sign locally
            local_sign(tx, scripts, sub_keys)
            # have Oracle sign
            result = oracle.sign_with_paths(tx, args.inputpath, change_paths, spend_id=args.spendid)
            print("Result:")
            print(result)
            if "transaction" in result:
                print("Hex serialized transaction:")
                print(b2h(stream_to_bytes(result["transaction"].stream)))
    else:
        print("unknown command %s" % (args.command,), file=sys.stderr)