Example #1
0
    def test_total_voting_weight(self):
        deed = Deed()

        v = 0
        expected_total_voting_weight = 0
        for signee in self.board:
            v += 1
            expected_total_voting_weight += v
            deed.set_signee(signee, v)

        self.assertEqual(deed.total_votes, expected_total_voting_weight)
Example #2
0
    def test_warn_on_missing_amend_threshold(self):
        """Checks a warning is printed when creating a deed when amend threshold is specified"""
        deed = Deed()
        deed.set_signee(self.board[0], 1)
        deed.set_operation(Operation.transfer, 1)

        # Attempting to create un-amendable deed without explicitly requesting it is an error
        with self.assertRaises(InvalidDeedError):
            deed.validate()

        # Attempting to create un-amendable deed without explicitly requesting it is an error
        with self.assertRaises(InvalidDeedError):
            deed.to_json()
Example #3
0
    def create_default_deed(self):
        deed = Deed()

        # Add signees with total voting weight of 5
        for signee in self.board:
            deed.set_signee(signee, 1)

        deed.set_operation(Operation.amend, deed.total_votes)
        deed.set_operation(Operation.transfer, deed.total_votes - 1)

        return deed
Example #4
0
    def test_allow_missing_amend_threshold(self):
        """Checks successful deed operation if amend threshold is missing, but explicit permission has been granted"""
        deed = Deed()
        deed.set_signee(self.board[0], 1)
        deed.set_operation(Operation.transfer, 1)
        deed.require_amend = False

        # With explicit request, this should still be a warning
        with patch('logging.warning') as mock_warn:
            deed.validate()
            self.assertEqual(mock_warn.call_count, 1)

        # With explicit request, this should still be a warning
        with patch('logging.warning') as mock_warn:
            obj = deed.to_json()
            self.assertEqual(mock_warn.call_count, 1)

        thresholds = obj['thresholds']
        self.assertNotIn(str(Operation.amend), thresholds)
        self.assertIn(str(Operation.transfer), thresholds)
Example #5
0
    def test_create(self):
        """Test correct creation of deed"""
        deed = Deed()

        for signee in self.board:
            deed.set_signee(signee, 1)

        deed.set_operation(Operation.amend, 3)

        self.assertEqual(deed.total_votes, 4)
        self.assertEqual(deed.get_threshold(Operation.amend), 3)
        for signee in self.board:
            self.assertEqual(deed.get_signee(signee), 1)
Example #6
0
    def test_drop_signee(self):
        deed = Deed()
        deed.set_signee(self.address, 10)

        self.assertIn(Address(self.address), deed.signees)
        deed.remove_signee(self.address)
        self.assertNotIn(Address(self.address), deed.signees)
Example #7
0
 def test_deed(self):
     self.post_test(self.api.deed, 'deed', TokenTxFactory.deed, self.entity,
                    Deed(), 2000)
Example #8
0
def main():
    # create the APIs
    api = LedgerApi(HOST, PORT)

    # we generate an identity from a known key, which contains funds.
    multi_sig_identity = Entity.from_hex(
        "6e8339a0c6d51fc58b4365bf2ce18ff2698d2b8c40bb13fcef7e1ba05df18e4b")

    # generate a board to control multi-sig account, with variable voting weights
    board = [
        Entity.from_hex(
            "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8"
        ),
        Entity.from_hex(
            "4083a476c4872f25cb40839ac8d994924bcef12d83e2ba4bd3ed6c9705959860"
        ),
        Entity.from_hex(
            "20293422c4b5faefba3422ed436427f2d37f310673681e98ac8637b04e756de3"
        ),
        Entity.from_hex(
            "d5f10ad865fff147ae7fcfdc98b755452a27a345975c8b9b3433ff16f23495fb"
        ),
    ]

    voting_weights = {
        board[0]: 1,
        board[1]: 1,
        board[2]: 1,
        board[3]: 2,
    }

    # generate another entity as a target for transfers
    other_identity = Entity.from_hex(
        "7da0e3fa62a916238decd4f54d43301c809595d66dd469f82f29e076752b155c")

    # submit deed
    print("\nCreating deed...")
    deed = Deed()
    for sig, weight in voting_weights.items():
        deed.set_signee(sig, weight)
    deed.set_operation(Operation.amend, 4)
    deed.set_operation(Operation.transfer, 3)

    api.sync(api.tokens.deed(multi_sig_identity, deed, 500))

    # display balance before
    print("\nBefore remote-multisig transfer")
    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))
    print()

    # scatter/gather example
    print("Generating transaction and distributing to signers...")

    # add intended signers to transaction
    ref_tx = TokenTxFactory.transfer(multi_sig_identity,
                                     other_identity,
                                     250,
                                     20,
                                     signatories=board)
    api.set_validity_period(ref_tx)

    # make a reference payload that can be used in this script for validation
    reference_payload = ref_tx.encode_payload()

    # have signers individually sign transaction
    signed_txs = []
    for signer in board:
        # signer builds their own transaction to compare to note that each of the signers will need to agree on all
        # parts of the message including the validity period and the counter
        signer_tx = TokenTxFactory.transfer(multi_sig_identity,
                                            other_identity,
                                            250,
                                            20,
                                            signatories=board)
        signer_tx.counter = ref_tx.counter
        signer_tx.valid_until = ref_tx.valid_until
        signer_tx.valid_from = ref_tx.valid_from

        # sanity check each of the signers payload should match the reference payload
        assert signer_tx.encode_payload() == reference_payload

        # signers locally sign there version of the transaction
        signer_tx.sign(signer)

        # simulate distribution of signed partial transactions
        signed_txs.append(signer_tx.encode_partial())

    # gather and encode final transaction - this step in theory can be done by all the signers provided they are
    # received all the signature shares
    print("Gathering and combining signed transactions...")
    partial_txs = [Transaction.decode_partial(s)[1] for s in signed_txs]

    # merge them together into one fully signed transaction
    success, tx = Transaction.merge(partial_txs)
    assert success  # this indicates that all the signatures have been merged and that the transaction now validates

    # submit the transaction
    api.sync(api.submit_signed_tx(tx))

    print("\nAfter remote multisig-transfer")
    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))

    # round-robin example
    print("\nGenerating transaction and sending down the line of signers...")

    # create the basis for the transaction
    tx = TokenTxFactory.transfer(multi_sig_identity,
                                 other_identity,
                                 250,
                                 20,
                                 signatories=board)
    api.set_validity_period(tx)

    # serialize and send to be signed
    tx_payload = tx.encode_payload()

    # have signers individually sign transaction and pass on to next signer
    for signer in board:
        # build the target transaction
        signer_tx = Transaction.decode_payload(tx_payload)

        # Signer decodes payload to inspect transaction
        signer_tx.sign(signer)

        # ensure that when we merge the signers signature into the payload that it is correct
        assert tx.merge_signatures(signer_tx)

    # once all the partial signatures have been merged then it makes sense
    print("Collecting final signed transaction...")
    assert tx.is_valid()
    api.sync(api.submit_signed_tx(tx))

    print("\nAfter remote multisig-transfer")
    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))
Example #9
0
    def test_threshold_sanity(self):
        """Test that thresholds can be correctly set"""
        deed = Deed()

        # Add signees with total voting weight of 5
        deed.set_signee(self.board[0], 2)
        deed.set_signee(self.board[1], 2)
        deed.set_signee(self.board[2], 1)
        deed.set_operation(Operation.amend, 5)
        deed.validate()

        deed.set_operation(Operation.transfer, 5 + 1)
        # Setting any threshold higher than the voting weight should fail
        with self.assertRaises(InvalidDeedError):
            deed.validate()

        # Check threshold successfully set
        deed.set_operation(Operation.transfer, 5 - 1)
        deed.validate()

        self.assertEqual(deed.get_threshold(Operation.amend), 5)
        self.assertEqual(deed.get_threshold(Operation.transfer), 4)
Example #10
0
 def test_setting_invalid_threshold(self):
     deed = Deed()
     with self.assertRaises(ValueError):
         deed.set_operation(Operation.transfer, -2)
Example #11
0
 def test_setting_invalid_weight(self):
     deed = Deed()
     with self.assertRaises(ValueError):
         deed.set_signee(self.address, -10)
Example #12
0
    def test_inequality(self):
        deed = Deed()
        other = self.create_default_deed()
        other.set_operation(Operation.amend, 1)

        self.assertTrue(deed != other)
Example #13
0
 def test_from_json_string(self):
     deed = self.create_default_deed()
     obj = deed.to_json()
     deed_reconstituted = Deed.from_json(json.dumps(obj))
     self.assertEqual(deed, deed_reconstituted)
Example #14
0
 def test_from_json(self):
     """Test creation from obj - promary workflow"""
     deed = self.create_default_deed()
     obj = deed.to_json()
     deed_reconstituted = Deed.from_json(obj)
     self.assertEqual(deed, deed_reconstituted)
Example #15
0
def main():
    # create the APIs
    api = LedgerApi(HOST, PORT)

    # generate an identity from a known key, which contains funds.
    multi_sig_identity = Entity.from_hex(
        "6e8339a0c6d51fc58b4365bf2ce18ff2698d2b8c40bb13fcef7e1ba05df18e4b")
    # generate a board to control multi-sig account, with variable voting weights.
    # we use keys for accounts which already have funds.
    board = []
    board.append(
        Entity.from_hex(
            "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8")
    )
    board.append(
        Entity.from_hex(
            "4083a476c4872f25cb40839ac8d994924bcef12d83e2ba4bd3ed6c9705959860")
    )
    board.append(
        Entity.from_hex(
            "20293422c4b5faefba3422ed436427f2d37f310673681e98ac8637b04e756de3")
    )
    board.append(
        Entity.from_hex(
            "d5f10ad865fff147ae7fcfdc98b755452a27a345975c8b9b3433ff16f23495fb")
    )
    voting_weights = {
        board[0]: 1,
        board[1]: 1,
        board[2]: 1,
        board[3]: 2,
    }
    # generate another entity as a target for transfers
    other_identity = Entity.from_hex(
        "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8")

    print('Original balance of multi_sig_identity:',
          api.tokens.balance(multi_sig_identity))

    # transfers can happen normally without a deed
    print('\nSubmitting pre-deed transfer with original signature...')
    api.sync(api.tokens.transfer(multi_sig_identity, other_identity, 250, 20))

    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))

    # submit the original deed
    print("\nCreating deed...")
    deed = Deed()
    for sig, weight in voting_weights.items():
        deed.set_signee(sig, weight)
    # set our initial voting thresholds
    deed.set_operation(Operation.transfer, 2)
    deed.set_operation(Operation.amend, 4)

    api.sync(api.tokens.deed(multi_sig_identity, deed, 6000))

    # original address can no longer validate transfers
    print("\nTransfer with original signature should fail...")
    try:
        api.sync(
            api.tokens.transfer(multi_sig_identity, other_identity, 250, 20))
    except RuntimeError as e:
        print("Transaction failed as expected")
    else:
        print("Transaction succeeded, it shouldn't have")

    # sufficient voting power required to sign transfers
    print("\nSubmitting transfer with two signatures with total 2 votes...")
    print_signing_votes(voting_weights, board[:2])

    # since we now want to create a transaction which has only been signed by a subset of the board, we must use
    # the factory interface in order to build out the transaction we are after
    tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20,
                                 board[:2])
    tx.valid_until = api.tokens.current_block_number() + 100
    # the signatories to sign the transaction
    for signatory in board[:2]:
        tx.sign(signatory)

    api.sync(api.submit_signed_tx(tx))

    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))

    # some entities may have more voting power
    print("\nSubmitting transfer with single signature with 2 votes...")
    print_signing_votes(voting_weights, board[3])

    tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20,
                                 [board[3]])
    tx.valid_until = api.tokens.current_block_number() + 100
    tx.sign(board[3])
    api.sync(api.submit_signed_tx(tx))

    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))

    # amend the deed
    print("\nAmending deed to increase transfer threshold to 3 votes...")
    deed.set_operation(Operation.transfer, 3)
    tx = TokenTxFactory.deed(multi_sig_identity, deed, 400, board)
    tx.valid_until = api.tokens.current_block_number() + 100
    for member in board:
        tx.sign(member)
    api.sync(api.submit_signed_tx(tx))

    # single member no longer has enough voting power
    print("\nSingle member transfer with 2 votes should no longer succeed...")
    try:
        print_signing_votes(voting_weights, board[3])

        tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250,
                                     20, [board[3]])
        tx.valid_until = api.tokens.current_block_number() + 100
        tx.sign(board[3])
        api.sync(api.submit_signed_tx(tx))

    except RuntimeError as e:
        print("Transaction failed as expected")
    else:
        print("Transaction succeeded, it shouldn't have")

    # correct number of signatory votes
    print("\nSuccesful transaction with sufficient voting weight...")
    print_signing_votes(voting_weights, board[1:])

    tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20,
                                 board[1:])
    tx.valid_until = api.tokens.current_block_number() + 100
    for member in board[1:]:
        tx.sign(member)
    api.sync(api.submit_signed_tx(tx))

    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))

    # warning: if no amend threshold is set, future amendments are impossible
    print("\nAmending deed to remove threshold...")
    deed.remove_operation(Operation.amend)
    deed.require_amend = False

    tx = TokenTxFactory.deed(multi_sig_identity, deed, 400, board)
    tx.valid_until = api.tokens.current_block_number() + 100
    for member in board:
        tx.sign(member)
    api.sync(api.submit_signed_tx(tx))

    deed.set_operation(Operation.amend, 1)
    print("\nExpecting further amendment to fail...")
    try:
        tx = TokenTxFactory.deed(multi_sig_identity, deed, 400, board)
        tx.valid_until = api.tokens.current_block_number() + 100
        for member in board:
            tx.sign(member)
        api.sync(api.submit_signed_tx(tx))
    except RuntimeError as e:
        print("Transaction failed as expected")
    else:
        print("Transaction succeeded, it shouldn't have")