github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/stellar/bundle/boxer_test.go (about)

     1  package bundle
     2  
     3  import (
     4  	"context"
     5  	"crypto/sha256"
     6  	"encoding/base64"
     7  	"errors"
     8  	"os"
     9  	"testing"
    10  
    11  	"github.com/davecgh/go-spew/spew"
    12  	"github.com/keybase/client/go/libkb"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  	"github.com/keybase/client/go/protocol/stellar1"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  const v2 = stellar1.BundleVersion_V2
    19  
    20  func testBundle(t *testing.T) stellar1.Bundle {
    21  	secretKey := stellar1.SecretKey("SDGCPMBQHYAIWM3PQOEKWICDMLVT7REJ24J26QEYJYGB6FJRPTKDULQX")
    22  	newBundle, err := New(secretKey, "test")
    23  	require.NoError(t, err)
    24  	newBundle.Accounts[0].IsPrimary = true
    25  	return *newBundle
    26  }
    27  
    28  func TestBundleRoundtrip(t *testing.T) {
    29  	m := libkb.NewMetaContext(context.Background(), nil)
    30  
    31  	ring := newPukRing()
    32  	pukSeed, pukGen := ring.makeGen(t, 1)
    33  	bundle := testBundle(t)
    34  	t.Logf("puk seed (hex): %v", toB64(pukSeed[:]))
    35  	t.Logf("puk gen: %v", pukGen)
    36  
    37  	boxed, err := BoxAndEncode(&bundle, pukGen, pukSeed)
    38  	require.NoError(t, err)
    39  	t.Logf("outer enc b64: %v", boxed.EncParentB64)
    40  	t.Logf("outer vis b64: %v", boxed.VisParentB64)
    41  	t.Logf("enc.N b64: %v", toB64(boxed.EncParent.N[:]))
    42  	t.Logf("enc.E b64: %v", toB64(boxed.EncParent.E))
    43  	require.Equal(t, v2, boxed.FormatVersionParent)
    44  	require.NotEmpty(t, boxed.VisParentB64)
    45  	require.NotEmpty(t, boxed.EncParentB64)
    46  	require.Len(t, boxed.AcctBundles, 1)
    47  	require.True(t, len(boxed.EncParentB64) > 100)
    48  	require.NotZero(t, boxed.EncParent.N)
    49  	require.Equal(t, 2, boxed.EncParent.V)
    50  	require.True(t, len(boxed.EncParent.E) > 100)
    51  	require.Equal(t, pukGen, boxed.EncParent.Gen)
    52  
    53  	bundle2, version, decodedPukGen, accountGens, err := DecodeAndUnbox(m, ring, boxed.toBundleEncodedB64())
    54  	require.NoError(t, err)
    55  	require.Equal(t, v2, version)
    56  	require.Equal(t, pukGen, decodedPukGen)
    57  	require.Nil(t, bundle2.Prev)
    58  	require.NotNil(t, bundle2.OwnHash)
    59  	require.Equal(t, bundle.Revision, bundle2.Revision)
    60  	require.Equal(t, len(bundle.Accounts), len(bundle2.Accounts))
    61  	for i, acct := range bundle.Accounts {
    62  		acct2 := bundle2.Accounts[i]
    63  		require.Equal(t, acct.AccountID, acct2.AccountID)
    64  		require.Equal(t, acct.Mode, acct2.Mode)
    65  		require.Equal(t, acct.Name, acct2.Name)
    66  		require.Equal(t, acct.IsPrimary, acct2.IsPrimary)
    67  		require.Equal(t, acct.AcctBundleRevision, acct2.AcctBundleRevision)
    68  		signers1 := bundle.AccountBundles[acct.AccountID].Signers
    69  		signers2 := bundle2.AccountBundles[acct2.AccountID].Signers
    70  		require.Equal(t, signers1, signers2)
    71  		require.True(t, len(signers2) == 1) // exactly one signer
    72  		require.True(t, len(signers2[0]) > 0)
    73  		require.Equal(t, keybase1.PerUserKeyGeneration(1), accountGens[acct.AccountID])
    74  	}
    75  }
    76  
    77  func TestBundlePrevs(t *testing.T) {
    78  	m := libkb.NewMetaContext(context.Background(), nil)
    79  	ring := newPukRing()
    80  	pukSeed, pukGen := ring.makeGen(t, 1)
    81  	b1 := testBundle(t)
    82  
    83  	// encode and decode b1 to populate OwnHash
    84  	b1Boxed, err := BoxAndEncode(&b1, pukGen, pukSeed)
    85  	require.NoError(t, err)
    86  	b1Decoded, _, _, _, err := DecodeAndUnbox(m, ring, b1Boxed.toBundleEncodedB64())
    87  	require.NoError(t, err)
    88  
    89  	// make a change, and verify hashes are correct
    90  	b2 := b1Decoded.DeepCopy()
    91  	b2.Accounts[0].Name = "apples"
    92  	b2.Prev = b1Decoded.OwnHash
    93  	b2.OwnHash = nil
    94  	b2.Revision++
    95  	b2Boxed, err := BoxAndEncode(&b2, pukGen, pukSeed)
    96  	require.NoError(t, err)
    97  	b2Decoded, _, _, _, err := DecodeAndUnbox(m, ring, b2Boxed.toBundleEncodedB64())
    98  	require.NoError(t, err)
    99  	require.Equal(t, "apples", b2Decoded.Accounts[0].Name, "change carried thru")
   100  	require.NotNil(t, b2Decoded.Prev)
   101  	require.Equal(t, b2Decoded.Prev, b1Decoded.OwnHash, "b2 prevs to b1")
   102  
   103  	// change the keys and do it again
   104  	pukSeed, pukGen = ring.makeGen(t, 2)
   105  	b3 := b2Decoded.DeepCopy()
   106  	b3.Accounts[0].Name = "bananas"
   107  	b3.Prev = b2Decoded.OwnHash
   108  	b3.OwnHash = nil
   109  	b3.Revision++
   110  	b3Boxed, err := BoxAndEncode(&b3, pukGen, pukSeed)
   111  	require.NoError(t, err)
   112  	b3Decoded, _, bundleGen, accountGens, err := DecodeAndUnbox(m, ring, b3Boxed.toBundleEncodedB64())
   113  	require.NoError(t, err)
   114  	require.Equal(t, "bananas", b3Decoded.Accounts[0].Name, "change carried thru")
   115  	require.NotNil(t, b3Decoded.Prev)
   116  	require.Equal(t, b3Decoded.Prev, b2Decoded.OwnHash, "b3 prevs to b2")
   117  	require.Equal(t, keybase1.PerUserKeyGeneration(2), bundleGen)
   118  	for _, acct := range b3Decoded.Accounts {
   119  		require.Equal(t, keybase1.PerUserKeyGeneration(2), accountGens[acct.AccountID])
   120  	}
   121  }
   122  
   123  func TestBundleRoundtripCorruptionEnc(t *testing.T) {
   124  	m := libkb.NewMetaContext(context.Background(), nil)
   125  	bundle := testBundle(t)
   126  	ring := newPukRing()
   127  	pukSeed, pukGen := ring.makeGen(t, 4)
   128  
   129  	boxed, err := BoxAndEncode(&bundle, pukGen, pukSeed)
   130  	require.NoError(t, err)
   131  	replaceWith := "a"
   132  	if boxed.EncParentB64[85] == 'a' {
   133  		replaceWith = "b"
   134  	}
   135  	boxed.EncParentB64 = boxed.EncParentB64[:85] + replaceWith + boxed.EncParentB64[86:]
   136  
   137  	_, _, _, _, err = DecodeAndUnbox(m, ring, boxed.toBundleEncodedB64())
   138  	require.Error(t, err)
   139  	require.Contains(t, err.Error(), "stellar bundle secret box open failed")
   140  }
   141  
   142  func TestBundleRoundtripCorruptionVis(t *testing.T) {
   143  	m := libkb.NewMetaContext(context.Background(), nil)
   144  	bundle := testBundle(t)
   145  	ring := newPukRing()
   146  	pukSeed, pukGen := ring.makeGen(t, 3)
   147  
   148  	boxed, err := BoxAndEncode(&bundle, pukGen, pukSeed)
   149  	require.NoError(t, err)
   150  	replaceWith := "a"
   151  	if boxed.VisParentB64[85] == 'a' {
   152  		replaceWith = "b"
   153  	}
   154  	boxed.VisParentB64 = boxed.VisParentB64[:85] + replaceWith + boxed.VisParentB64[86:]
   155  
   156  	_, _, _, _, err = DecodeAndUnbox(m, ring, boxed.toBundleEncodedB64())
   157  	require.Error(t, err)
   158  	require.Contains(t, err.Error(), "visible hash mismatch")
   159  }
   160  
   161  func TestBoxAndEncodeCatchesMalformedBundles(t *testing.T) {
   162  	bundle := testBundle(t)
   163  	ring := newPukRing()
   164  	pukSeed, pukGen := ring.makeGen(t, 3)
   165  
   166  	// put a different account and secret in the AccountBundle
   167  	newAcctID, newSecret, err := randomStellarKeypair()
   168  	require.NoError(t, err)
   169  	newAB := map[stellar1.AccountID]stellar1.AccountBundle{
   170  		newAcctID: {
   171  			AccountID: newAcctID,
   172  			Signers:   []stellar1.SecretKey{newSecret},
   173  		},
   174  	}
   175  	bundle.AccountBundles = newAB
   176  
   177  	// encode should error because the bundle is invalid
   178  	_, err = BoxAndEncode(&bundle, pukGen, pukSeed)
   179  	require.Contains(t, err.Error(), "account in AccountBundles not in Accounts")
   180  }
   181  
   182  type accountCan struct {
   183  	accountID stellar1.AccountID
   184  	encB64    string
   185  }
   186  type canned struct {
   187  	pukSeedB64   string
   188  	pukGen       int
   189  	encParentB64 string
   190  	visParentB64 string
   191  	accounts     []accountCan
   192  }
   193  
   194  func (c *canned) puk(t *testing.T) (puk libkb.PerUserKeySeed) {
   195  	bs, err := base64.StdEncoding.DecodeString(c.pukSeedB64)
   196  	require.NoError(t, err)
   197  	require.Equal(t, len(puk), len(bs))
   198  	copy(puk[:], bs)
   199  	return puk
   200  }
   201  
   202  func (c *canned) gen() keybase1.PerUserKeyGeneration {
   203  	return keybase1.PerUserKeyGeneration(c.pukGen)
   204  }
   205  
   206  func (c *canned) toBundleEncodedB64() BundleEncoded {
   207  	benc := BundleEncoded{
   208  		EncParent:   c.encParentB64,
   209  		VisParent:   c.visParentB64,
   210  		AcctBundles: make(map[stellar1.AccountID]string),
   211  	}
   212  	for _, acct := range c.accounts {
   213  		benc.AcctBundles[acct.accountID] = acct.encB64
   214  	}
   215  	return benc
   216  }
   217  
   218  func (c *canned) ring(t *testing.T) *pukRing {
   219  	pukSeed := c.puk(t)
   220  	pukGen := c.gen()
   221  	return &pukRing{
   222  		map[keybase1.PerUserKeyGeneration]libkb.PerUserKeySeed{
   223  			pukGen: pukSeed,
   224  		},
   225  	}
   226  }
   227  
   228  var cans = []canned{
   229  	// this one is valid
   230  	{"R81SkpClcSUPMzch6UAstOhS+hbZi4R43HzbRiLQ46o=",
   231  		3,
   232  		"hKFlxPCoH32GB0er08rZF1B2sfZNME3/35sFKWBVSCQTFKiTAuGSe2mK9AKCWMLkXcTvoJojtyu56hBwGbXQCqqSL8eY1sb8UGG7SsuvNBr27hzUtosJmb9tCT0uikOY8YFPYAtWbmqHB9QvqeBEtysd7ZDDJPG0cJ9lckvj9rSAE/wuhcVlHMAWfbOvGvOLDf56VVK46Ms7bGTSedTKHj8IPpF48RF1GrDlvZQRgqD8ydwtqMGZ1ZkqF+DKKXaEQaIhY47L50Ynna7Qzm8ZCEujsuo5W3EKtZtY6XG0RYx7AzdhkXKzFVDmINVHxkZbQi66QpWjZ2VuA6FuxBh9I1ef+UMA9u3rOYAqPzeVm6hlam5ZX62hdgI=",
   233  		"g6hhY2NvdW50c5KFqWFjY291bnRJRNk4R0FXWjdIVlBLUkdDSDJLUDY0NzVYVjZIQTJDQUY0NE1YV1dFNVJLVjRMTU1HQjZGTk5TRVBOUEWyYWNjdEJ1bmRsZVJldmlzaW9uAbFlbmNBY2N0QnVuZGxlSGFzaMQgBgkhqzuIynMJrhIeuSOPTCoS5QutvwDXZr7fHVuCmpipaXNQcmltYXJ5w6Rtb2RlAYWpYWNjb3VudElE2ThHQlBMTEhPS1BSRlNDRTZGUDZEMzdBVVVOWUNKMlRTUE9ITUhBUjU0VEJETFdXUElaQ0Q0UkwzR7JhY2N0QnVuZGxlUmV2aXNpb24BsWVuY0FjY3RCdW5kbGVIYXNoxCCbBuOilM5oKBvC1JaEzJoq8l1W1picV5MxkTEOrPEnpalpc1ByaW1hcnnCpG1vZGUBpHByZXbAqHJldmlzaW9uAQ==",
   234  		[]accountCan{
   235  			{"GAWZ7HVPKRGCH2KP6475XV6HA2CAF44MXWWE5RKV4LMMGB6FNNSEPNPE", "hKFlxKUc3fqMOAYv9m6ycpnpQA4CSriSQoPVNbvsXXEgv4WsixkaTThkKGMlYuWvTJdHeqPdYXo/Mw156xq8MaIbzDTriFplFNcLhNYdi8f1ViHqvVIecX2frU/BOtIsAlqknnhl4+Z1u2kUnZYI5pZRvUV5H1loSWC4tBmEoCgK1S6XrLx1POOIiKkH8EFMXVrB6+BjbieW1w8HTXNp0jWbKkq+QoKz8MCjZ2VuA6FuxBibtfMD40wYqnfPsGePn1RphpzcEqv7mZehdgE="},
   236  			{"GBPLLHOKPRFSCE6FP6D37AUUNYCJ2TSPOHMHAR54TBDLWWPIZCD4RL3G", "hKFlxKX3zqweGMWyl4vOC8ht0jngDnTEpWqGBePh7okF9S003QStjI9Td1d+urqj/k4etwgLiPO8LHaaQ7o2WAwuSXakHJVJ2xIq+T+MYnoobbx5sArk4wsg7AWTX+uw/rXoyN7P9ZAgDSH+4oZ6/0j8dwR6RAoc9c+dog8xnW3eTkeSlj2KYx6XnEOOlOcCBCKsZePi9eoN92CxvB5MRN6tR7w3PH4yynqjZ2VuA6FuxBha4JmmVyysST3avwMxayHfTExp7tnLwHOhdgE="},
   237  		}},
   238  	// bad decryption puk
   239  	{"1111111111111111111111111111111111111111111=",
   240  		3,
   241  		"hKFlxPCoH32GB0er08rZF1B2sfZNME3/35sFKWBVSCQTFKiTAuGSe2mK9AKCWMLkXcTvoJojtyu56hBwGbXQCqqSL8eY1sb8UGG7SsuvNBr27hzUtosJmb9tCT0uikOY8YFPYAtWbmqHB9QvqeBEtysd7ZDDJPG0cJ9lckvj9rSAE/wuhcVlHMAWfbOvGvOLDf56VVK46Ms7bGTSedTKHj8IPpF48RF1GrDlvZQRgqD8ydwtqMGZ1ZkqF+DKKXaEQaIhY47L50Ynna7Qzm8ZCEujsuo5W3EKtZtY6XG0RYx7AzdhkXKzFVDmINVHxkZbQi66QpWjZ2VuA6FuxBh9I1ef+UMA9u3rOYAqPzeVm6hlam5ZX62hdgI=",
   242  		"g6hhY2NvdW50c5KFqWFjY291bnRJRNk4R0FXWjdIVlBLUkdDSDJLUDY0NzVYVjZIQTJDQUY0NE1YV1dFNVJLVjRMTU1HQjZGTk5TRVBOUEWyYWNjdEJ1bmRsZVJldmlzaW9uAbFlbmNBY2N0QnVuZGxlSGFzaMQgBgkhqzuIynMJrhIeuSOPTCoS5QutvwDXZr7fHVuCmpipaXNQcmltYXJ5w6Rtb2RlAYWpYWNjb3VudElE2ThHQlBMTEhPS1BSRlNDRTZGUDZEMzdBVVVOWUNKMlRTUE9ITUhBUjU0VEJETFdXUElaQ0Q0UkwzR7JhY2N0QnVuZGxlUmV2aXNpb24BsWVuY0FjY3RCdW5kbGVIYXNoxCCbBuOilM5oKBvC1JaEzJoq8l1W1picV5MxkTEOrPEnpalpc1ByaW1hcnnCpG1vZGUBpHByZXbAqHJldmlzaW9uAQ==",
   243  		[]accountCan{
   244  			{"GAWZ7HVPKRGCH2KP6475XV6HA2CAF44MXWWE5RKV4LMMGB6FNNSEPNPE", "hKFlxKUc3fqMOAYv9m6ycpnpQA4CSriSQoPVNbvsXXEgv4WsixkaTThkKGMlYuWvTJdHeqPdYXo/Mw156xq8MaIbzDTriFplFNcLhNYdi8f1ViHqvVIecX2frU/BOtIsAlqknnhl4+Z1u2kUnZYI5pZRvUV5H1loSWC4tBmEoCgK1S6XrLx1POOIiKkH8EFMXVrB6+BjbieW1w8HTXNp0jWbKkq+QoKz8MCjZ2VuA6FuxBibtfMD40wYqnfPsGePn1RphpzcEqv7mZehdgE="},
   245  			{"GBPLLHOKPRFSCE6FP6D37AUUNYCJ2TSPOHMHAR54TBDLWWPIZCD4RL3G", "hKFlxKX3zqweGMWyl4vOC8ht0jngDnTEpWqGBePh7okF9S003QStjI9Td1d+urqj/k4etwgLiPO8LHaaQ7o2WAwuSXakHJVJ2xIq+T+MYnoobbx5sArk4wsg7AWTX+uw/rXoyN7P9ZAgDSH+4oZ6/0j8dwR6RAoc9c+dog8xnW3eTkeSlj2KYx6XnEOOlOcCBCKsZePi9eoN92CxvB5MRN6tR7w3PH4yynqjZ2VuA6FuxBha4JmmVyysST3avwMxayHfTExp7tnLwHOhdgE="},
   246  		}},
   247  	// this one has two primary accounts
   248  	{"zZZijzv+D622csZjyzgZHt/avWYJaHH0S42rO29uBh4=",
   249  		3,
   250  		"hKFlxPBByHsjg6VR42RdYX5UsALFZ5XTG348GsP+J1ubWC2Iv+49/NjZtexC9rqQaIVU0yz/oKmJfpBBlB6m3EDjkca/5yszpDPf1WKPQR7tzJMAPpwmbXKC3dWZGO/elRgvGiH3rvq1SVMn5Od20Gkn81rn0w4M2VtiXl23dUqgTPV3zxgnWYgi+qz2MYBQUOCDiIXRQCEoz9uryF36GuI0RhmM5r14zfPTo2Ru6hDqN2FN17aJ/D7xTBiIdQUAlN6cUZS/nQEEEwdxJmlFTzXgoIR7puO0sC9Q1PWMKTfRrCzhUkV/VVyWEMZiQR1VbWii58OjZ2VuA6FuxBh7HP8NWh2qAIdc8bX/gka07BmIGJ6N1BGhdgI=",
   251  		"g6hhY2NvdW50c5KFqWFjY291bnRJRNk4R0FXWjdIVlBLUkdDSDJLUDY0NzVYVjZIQTJDQUY0NE1YV1dFNVJLVjRMTU1HQjZGTk5TRVBOUEWyYWNjdEJ1bmRsZVJldmlzaW9uAbFlbmNBY2N0QnVuZGxlSGFzaMQgYsYYlXdhMYNv+TjdE9jLU/9InY7g9UFovmMZyVX43SipaXNQcmltYXJ5w6Rtb2RlAYWpYWNjb3VudElE2ThHQlBMTEhPS1BSRlNDRTZGUDZEMzdBVVVOWUNKMlRTUE9ITUhBUjU0VEJETFdXUElaQ0Q0UkwzR7JhY2N0QnVuZGxlUmV2aXNpb24BsWVuY0FjY3RCdW5kbGVIYXNoxCAHEbHL2jsIn5lJJCTkKvM24zKMf+Cu7k52bSSNCaOXsqlpc1ByaW1hcnnDpG1vZGUBpHByZXbAqHJldmlzaW9uAQ==",
   252  		[]accountCan{
   253  			{"GAWZ7HVPKRGCH2KP6475XV6HA2CAF44MXWWE5RKV4LMMGB6FNNSEPNPE", "hKFlxKXNijkx4xEPPfrWDSmKzCvDxsaGaqOU+AGRJ21tXT/YiIYVy+Xhqn8ZDA8q7b6NhOLvQQKXao8RaVTyJz2ZPfF4JFdhtB4NW2FvVibNShKFMhpiB2JKKLQ1pe5SWTctKeCMySQEOSuRPYw5h1agWuFSO6G41bjc6tqxSN1Dy3X0NNiTSlV2t+vTKEIxPpslUkraHzX7QzLx0L/UHWgTNSKlDQUFoP2jZ2VuA6FuxBiN3rakOKyhdiI11EnaQh+DJr+OaiDpCwOhdgE="},
   254  			{"GBPLLHOKPRFSCE6FP6D37AUUNYCJ2TSPOHMHAR54TBDLWWPIZCD4RL3G", "hKFlxKU/uVJvah1L9M9DXNzqfHkCafEpxeVPaZ+qi7/yxVrYPxaERZe3vtVpSSj/ubXOCps8PQdNGxryD9IpOHc7nz+a+jfGCrl1j5ka6cLaTtVRVPULm4zpFmtj3AG3OMMx3SERt+9nzwCzFMOhsWkF0qsUJmJb147619qHyYXVX9xBhfadKOnam91qDpeezjfkIJNfsc6wNz1Gq/lnw3NFJsWJKhLRPoyjZ2VuA6FuxBjyJpAx80CdDilh3Aa4kpgr2XpPm90HpwShdgE="},
   255  		}},
   256  	// this one has version 1
   257  	{"WkgRG8Kn+kJ+9E3UOr+2AL/28+1FAHFcuwaXFNn63Bc=",
   258  		3,
   259  		"hKFlxPDOMmhJALH9j+DVPBfgO5o5XoR0e0Wzoohc38H98QuRiDvZdlMSXXVmnXaeESLsdFVvmNBX7LNj8AQ3tsisxuGzUEPnDCIvBQOVqWb0YzCg8hvT5TxuxFaFYr+b7JP+/8JaDfO2ZMZHmwh0bYSy+cveFjmJQu9dqJPrFkaI5M2qE3k9V2d9RDn279l+/tKkXaTADI5si9e8+6ZwccuD+w8YTBhF6pu92Ums9sYwlu1NJhljnzrpnyZHwlkPEuz9bx6gc9flSsTsM+F14z+1/3Mw7dK6/5o3heU6Dp5DRVyYzDm89+Y380nqsdswUItkpDCjZ2VuA6FuxBghv5/O3avrFmYsqX/yOIimwsQV24wATFmhdgE=",
   260  		"g6hhY2NvdW50c5KFqWFjY291bnRJRNk4R0FXWjdIVlBLUkdDSDJLUDY0NzVYVjZIQTJDQUY0NE1YV1dFNVJLVjRMTU1HQjZGTk5TRVBOUEWyYWNjdEJ1bmRsZVJldmlzaW9uAbFlbmNBY2N0QnVuZGxlSGFzaMQgJ46Gf8Q34Oz+SY5WASuyNRbtK9amEwfZeh+cwYMkhu6paXNQcmltYXJ5w6Rtb2RlAYWpYWNjb3VudElE2ThHQlBMTEhPS1BSRlNDRTZGUDZEMzdBVVVOWUNKMlRTUE9ITUhBUjU0VEJETFdXUElaQ0Q0UkwzR7JhY2N0QnVuZGxlUmV2aXNpb24BsWVuY0FjY3RCdW5kbGVIYXNoxCBT04slGTYXYwS3F/NIMocy4hzfdbd6QbuAcu+fQLdk16lpc1ByaW1hcnnCpG1vZGUBpHByZXbAqHJldmlzaW9uAQ==",
   261  		[]accountCan{
   262  			{"GBPLLHOKPRFSCE6FP6D37AUUNYCJ2TSPOHMHAR54TBDLWWPIZCD4RL3G", "hKFlxKVmAT4zpHkRMmxvopFs5dNpPdbLP4Xbuv2vSb3Nb5v+X+5mJCOy+/viCSlFabN0hiLvRA9SNdbhmB0nGGEqr4KTpwI1Igi9kpDHct4WfaO5JKxM9z/c4CKEU+Yp83MwhrfvINFMu/9hvxWfYpIISSQUelfJExn1j+IHaTQje4+bpetdZ8L8aaq0i1JslDBhzuTSut1vDJTOs5IaFUdjmNWlIMFWB8+jZ2VuA6FuxBhp5v8W3hvbFv5pD0YMwazOaadZabL+w9ahdgE="},
   263  			{"GAWZ7HVPKRGCH2KP6475XV6HA2CAF44MXWWE5RKV4LMMGB6FNNSEPNPE", "hKFlxKWaCv93H++U6REJYFrDJ7lIkxkIWxQui3TnWgknVao+Ch2mwBXwlrtJIfTLtisMiDe41Rhg5W2/MVuUahM4SP4/7bch/jaco294xDvt9qSy7gV3Tn6y3kQ4D9sbydP5fTX5b/2TbAsYaxVnNBI9gmWz9WnA1i/oMUVb+z64MdWgBFsYb+3NKq+ckOFBx7lWz06W84XVwFOQkfEa9Z1lHO7dZnnbYR2jZ2VuA6FuxBjfxSfGM/YdSvKrrFMopBR2ZqN+/Ekg3WqhdgE="},
   264  		}},
   265  	// parent visible hash mismatch
   266  	{"33o4U14XU4NFNg99xV3DrW9+JFqux+Qy+9bKPO0CZqw=",
   267  		3,
   268  		"hKFlxPB8pYYB1ikZVIbr42li9xe7uWpnwXj6UzvNl84o1a9BQp32tMp9Os5STGSinCxeKJWThWDEaIkQAhbsIWM5I2f/y5Bakw8qPKdXnPvXzIFdIAKPaL5YOfeH4YqINUy1vLKtmXE4oNN/Re1GI2JJfLHoiwGdfkZ5BwNf8HBMPo+7NQoK72vhmawKoVZ/mhLZWhyWg2o2WLxnI6SvB23zr9S8DuzorjdFyPjMKLoW1cQEvjL89XC3Gh0jVQqtiyY4ztsezSaaK631EgCggKax9TgOMcadtjbSDak4Z4369iPxdPDarifQEH3tCgfQEqfm/FajZ2VuA6FuxBhNSgpvLKdBI1+uyn+AxVitwsUWFOvFv1OhdgI=",
   269  		"g6hhY2NvdW50c5KFqWFjY291bnRJRNk4R0FXWjdIVlBLUkdDSDJLUDY0NzVYVjZIQTJDQUY0NE1YV1dFNVJLVjRMTU1HQjZGTk5TRVBOUEWyYWNjdEJ1bmRsZVJldmlzaW9uAbFlbmNBY2N0QnVuZGxlSGFzaMQgitIh2ZzE+Ee+B5sOTGh6Zykb3HzJm0AGEStUlbqv3gipaXNQcmltYXJ5w6Rtb2RlAYWpYWNjb3VudElE2ThHQlBMTEhPS1BSRlNDRTZGUDZEMzdBVVVOWUNKMlRTUE9ITUhBUjU0VEJETFdXUElaQ0Q0UkwzR7JhY2N0QnVuZGxlUmV2aXNpb24BsWVuY0FjY3RCdW5kbGVIYXNoxCBha3OfRg0rSijM9oc4jHDzkf6U1T1QA/70ZYyOCclIbqlpc1ByaW1hcnnCpG1vZGUBpHByZXbAqHJldmlzaW9uAQ==",
   270  		[]accountCan{
   271  			{"GAWZ7HVPKRGCH2KP6475XV6HA2CAF44MXWWE5RKV4LMMGB6FNNSEPNPE", "hKFlxKVK+UYzZvupFCxmCET7llfg+lz0WgoZjM1A4QwfTrn0SQiyJWUnq25ZHS4yCCkMHt0RZ9nkeNKLdBCW/zbGWl1PazWmRhHrQtnWNxYKn0loHacMxo8XPZrYMJxOzMmAQERYAdKLCCKbX7aSw0fM2f0O0vUakB6G9iMuBkSqLjnItRrw7IDq1nVKfrBKWGA7QndnA+cLU5QRnlc4X3tGnQLqqKiWLDajZ2VuA6FuxBjM4wCrCdDp+PUJMQaXZfid1okMls3wI3ahdgE="},
   272  			{"GBPLLHOKPRFSCE6FP6D37AUUNYCJ2TSPOHMHAR54TBDLWWPIZCD4RL3G", "hKFlxKURh2JrnpBAnRNW2lDvx63Sd5bMMqUvI5bkvkn2CNWcMn+FcPRCK+75EZWv0Jnq+KT6Xp0r4Qm8INe27bLQJa5XOB8JB04XY5zGaAwHPY3hddUUmTFfn9CmY46SGY4bSQ5xejPIlsl+VBLgnM+4ZiuRp3o13YNjn9tBdWOHrJmb7c+err863f43Ttw6L1XxbOvwl81VIA/auHkw1znXr8D2ZleOP92jZ2VuA6FuxBgOiuJnWDcJvlZxaI6MyQCMI3leSfE54GWhdgE="},
   273  		}},
   274  	// account-level hash mismatch
   275  	{"AdhjUfTyNZvZnyWuLrXCJ2XgpfErFwyqmRkg8ZEjAHg=",
   276  		3,
   277  		"hKFlxPAxslJzbyrnYaG4LJUi0hw9jKh6GaM99keZJb7K36pN1whf/bN5iFxrC9J0KQeW7OQAXEp973OdfO2azxzzh3if/SQ9wQN00XQMWLN2Cb0Je98+z9wFWsWdh918Rit5x5hNi61PvTUxQCsahGeO1BqWJxvC3P3XzwBBg2CaesB07KfDZSB5kBP6mruluGgATE4WLmk8LmoyQL4CA6DYpRaWl5Xr6e5tAj5JFzd9wnSCIiKPukONnJqszqfZaF+ZVUznQ1q9MfjIM3huQrOb7wC/NPKtoM+xsTPgCjmIfLZBwY6lD8xiUdaTNGFH6zNvHdCjZ2VuA6FuxBgpHteB4lY/BqjThfRNbn/TNKRfvl0cXv2hdgI=",
   278  		"g6hhY2NvdW50c5KFqWFjY291bnRJRNk4R0FXWjdIVlBLUkdDSDJLUDY0NzVYVjZIQTJDQUY0NE1YV1dFNVJLVjRMTU1HQjZGTk5TRVBOUEWyYWNjdEJ1bmRsZVJldmlzaW9uAbFlbmNBY2N0QnVuZGxlSGFzaMQgH3M1uaaaaa1r684bDx18YGFfqAPCRhgktz/Y3lBUPAapaXNQcmltYXJ5w6Rtb2RlAYWpYWNjb3VudElE2ThHQlBMTEhPS1BSRlNDRTZGUDZEMzdBVVVOWUNKMlRTUE9ITUhBUjU0VEJETFdXUElaQ0Q0UkwzR7JhY2N0QnVuZGxlUmV2aXNpb24BsWVuY0FjY3RCdW5kbGVIYXNoxCAxAujlppppoT+KYBEoHY76Uq1v9rPcf4lbhFW4v+p14alpc1ByaW1hcnnCpG1vZGUBpHByZXbAqHJldmlzaW9uAQ==",
   279  		[]accountCan{
   280  			{"GAWZ7HVPKRGCH2KP6475XV6HA2CAF44MXWWE5RKV4LMMGB6FNNSEPNPE", "hKFlxKWW2fR2MgA3VlejatZlBJ057v0+YFBdSQGuh3V1LkA4XDCYx3fC8+N9FZN7HOlQd1eBYO4gT/7wgg3fk9k2K9BVHCbXAJeKiv8DMV9SbJ7ZWQnXG9BAT1ZQApv4BBVMWTvY9uhao07IU5amC58KC6xlaRZg1BwkRuk4H83ahdvMXWLpzFMlwobtjoD0tiG7u6YGAw3Bpr2N5JUDxdjUAHbdCUSl0BOjZ2VuA6FuxBgk5XMAnRJBS1C7J7g82tAmH85+b2JMWgehdgE="},
   281  			{"GBPLLHOKPRFSCE6FP6D37AUUNYCJ2TSPOHMHAR54TBDLWWPIZCD4RL3G", "hKFlxKUSt6xJveOZtTct6TMN9kBo5/1qmoFIHtpCLJzkYrnu0ciOODqDtwnUmHlbYfT7GjRdyN2jo7xfdw6D2jtTKAyCCGUbehlXLFP2x0+wNo6oiUCCiP5/uk3SuR/QfFtrh1aussrZgx2GzW76ZLEXlVKm6tQ+B+/ARQHeQobUaB2jpTrB9HwsO/ZhEllEyWDtRMtaOzlBl3yXPo9e8E41Wq3mFXQRbMWjZ2VuA6FuxBiKSBv9DEiFTGGcAGUcimPbFWeIFm0n99GhdgE="},
   282  		}},
   283  }
   284  
   285  func TestCanningFacility(t *testing.T) {
   286  	if os.Getenv("KEYBASE_CANNING_FACILITY") != "1" {
   287  		t.Skip("this is not really a test but a tool for creating cans for tests")
   288  	}
   289  	a1 := stellar1.AccountID("GAWZ7HVPKRGCH2KP6475XV6HA2CAF44MXWWE5RKV4LMMGB6FNNSEPNPE")
   290  	s1 := stellar1.SecretKey("SBV2JNAJA65LMCZ5HYDXAYWRQK25CD2DZB25YZVNX3OLPALN2EVKO2V2")
   291  	a2 := stellar1.AccountID("GBPLLHOKPRFSCE6FP6D37AUUNYCJ2TSPOHMHAR54TBDLWWPIZCD4RL3G")
   292  	s2 := stellar1.SecretKey("SDZJUFUKEQQU77DCF2VH72XB4S427EGKF6BSOSPUIKLTBCTCMXQQ7JU5")
   293  	bundleLocal := stellar1.Bundle{
   294  		Revision: 1,
   295  		Prev:     nil,
   296  		Accounts: []stellar1.BundleEntry{{
   297  			AccountID:          a1,
   298  			Mode:               stellar1.AccountMode_USER,
   299  			IsPrimary:          true,
   300  			Name:               "p1",
   301  			AcctBundleRevision: 1,
   302  			EncAcctBundleHash:  nil,
   303  		}, {
   304  			AccountID:          a2,
   305  			Mode:               stellar1.AccountMode_USER,
   306  			IsPrimary:          false,
   307  			Name:               "p2",
   308  			AcctBundleRevision: 1,
   309  			EncAcctBundleHash:  nil,
   310  		}},
   311  		AccountBundles: map[stellar1.AccountID]stellar1.AccountBundle{
   312  			a1: {
   313  				AccountID: a1,
   314  				Signers:   []stellar1.SecretKey{s1},
   315  			},
   316  			a2: {
   317  				AccountID: a2,
   318  				Signers:   []stellar1.SecretKey{s2},
   319  			},
   320  		},
   321  	}
   322  	ring := newPukRing()
   323  	pukSeed, pukGen := ring.makeGen(t, 3)
   324  	boxed, err := BoxAndEncode(&bundleLocal, pukGen, pukSeed)
   325  	require.NoError(t, err)
   326  	t.Logf(spew.Sdump(boxed))
   327  	t.Logf("puk seed: %v", toB64(pukSeed[:]))
   328  	t.Logf("puk gen: %v", pukGen)
   329  	t.Logf("nonce: %v", toB64(boxed.EncParent.N[:]))
   330  	t.Logf("enc E: %v", toB64(boxed.EncParent.E))
   331  	t.Logf("\nEncParentB64: %v", boxed.EncParentB64)
   332  	t.Logf("VisParentB64: %v\n", boxed.VisParentB64)
   333  	for acctID, encodedAcct := range boxed.AcctBundles {
   334  		t.Logf("account: %v, EncB64: %v", acctID, encodedAcct.EncB64)
   335  	}
   336  	cipherpack, err := base64.StdEncoding.DecodeString(boxed.EncParentB64)
   337  	require.NoError(t, err)
   338  	encHash := sha256.Sum256(cipherpack)
   339  	t.Logf("actual own hash: %v", toB64(encHash[:]))
   340  
   341  	// decode it back again and take a look,
   342  	// especially for generating expected errors
   343  	benc := BundleEncoded{
   344  		EncParent:   boxed.EncParentB64,
   345  		VisParent:   boxed.VisParentB64,
   346  		AcctBundles: make(map[stellar1.AccountID]string),
   347  	}
   348  	for acctID, encodedAcct := range boxed.AcctBundles {
   349  		benc.AcctBundles[acctID] = encodedAcct.EncB64
   350  	}
   351  	m := libkb.NewMetaContext(context.Background(), nil)
   352  	decodedBundle, _, _, _, err := DecodeAndUnbox(m, ring, benc)
   353  	t.Logf("decoded: %+v, err: %v", decodedBundle, err)
   354  }
   355  
   356  func toB64(b []byte) string {
   357  	return base64.StdEncoding.EncodeToString(b)
   358  }
   359  
   360  func TestCanned(t *testing.T) {
   361  	m := libkb.NewMetaContext(context.Background(), nil)
   362  
   363  	// valid can
   364  	c := cans[0]
   365  	bundle, _, _, _, err := DecodeAndUnbox(m, c.ring(t), c.toBundleEncodedB64())
   366  	require.NoError(t, err)
   367  	require.Equal(t, "yJwcMuMxwpFuxt0A+7zYT2iev/1wVB5OeNdJzDSlBDo=", toB64(bundle.OwnHash))
   368  	// hashes match for the first account
   369  	a1BundleHash := bundle.AccountBundles["GAWZ7HVPKRGCH2KP6475XV6HA2CAF44MXWWE5RKV4LMMGB6FNNSEPNPE"].OwnHash
   370  	require.Equal(t, "BgkhqzuIynMJrhIeuSOPTCoS5QutvwDXZr7fHVuCmpg=", toB64(a1BundleHash))
   371  	require.Equal(t, "BgkhqzuIynMJrhIeuSOPTCoS5QutvwDXZr7fHVuCmpg=", toB64(bundle.Accounts[0].EncAcctBundleHash))
   372  	// hashes match for the second account
   373  	a2BundleHash := bundle.AccountBundles["GBPLLHOKPRFSCE6FP6D37AUUNYCJ2TSPOHMHAR54TBDLWWPIZCD4RL3G"].OwnHash
   374  	require.Equal(t, "mwbjopTOaCgbwtSWhMyaKvJdVtaYnFeTMZExDqzxJ6U=", toB64(a2BundleHash))
   375  	require.Equal(t, "mwbjopTOaCgbwtSWhMyaKvJdVtaYnFeTMZExDqzxJ6U=", toB64(bundle.Accounts[1].EncAcctBundleHash))
   376  }
   377  
   378  func TestCantOpenWithTheWrongKey(t *testing.T) {
   379  	m := libkb.NewMetaContext(context.Background(), nil)
   380  
   381  	c := cans[1]
   382  	pukSeed := c.puk(t)
   383  	pukGen := c.gen()
   384  	ring := &pukRing{
   385  		map[keybase1.PerUserKeyGeneration]libkb.PerUserKeySeed{
   386  			pukGen: pukSeed,
   387  		},
   388  	}
   389  	_, _, _, _, err := DecodeAndUnbox(m, ring, c.toBundleEncodedB64())
   390  	require.Error(t, err)
   391  	require.Contains(t, err.Error(), "secret box open failed")
   392  }
   393  
   394  func TestCannedUnboxInvariantViolationMultiplePrimary(t *testing.T) {
   395  	m := libkb.NewMetaContext(context.Background(), nil)
   396  
   397  	c := cans[2]
   398  	_, _, _, _, err := DecodeAndUnbox(m, c.ring(t), c.toBundleEncodedB64())
   399  	require.Error(t, err)
   400  	require.Contains(t, err.Error(), "multiple primary accounts")
   401  }
   402  
   403  func TestCannedCryptV1(t *testing.T) {
   404  	m := libkb.NewMetaContext(context.Background(), nil)
   405  
   406  	c := cans[3]
   407  	_, _, _, _, err := DecodeAndUnbox(m, c.ring(t), c.toBundleEncodedB64())
   408  	require.Error(t, err)
   409  	require.Contains(t, err.Error(), "stellar secret bundle encryption version 1 has been retired")
   410  }
   411  
   412  func TestCannedBundleHashMismatch(t *testing.T) {
   413  	m := libkb.NewMetaContext(context.Background(), nil)
   414  
   415  	c := cans[4]
   416  	_, _, _, _, err := DecodeAndUnbox(m, c.ring(t), c.toBundleEncodedB64())
   417  	require.Error(t, err)
   418  	require.Contains(t, err.Error(), "corrupted bundle: visible hash mismatch")
   419  }
   420  
   421  func TestCannedAccountHashMismatch(t *testing.T) {
   422  	m := libkb.NewMetaContext(context.Background(), nil)
   423  
   424  	c := cans[5]
   425  	_, _, _, _, err := DecodeAndUnbox(m, c.ring(t), c.toBundleEncodedB64())
   426  	require.Error(t, err)
   427  	require.Contains(t, err.Error(), "account bundle and parent entry hash mismatch")
   428  }
   429  
   430  // TestBoxAccountBundle checks boxing an account bundle and that DecodeAndUnbox
   431  // gets back to the initial bundle.
   432  func TestBoxAccountBundle(t *testing.T) {
   433  	b, err := NewInitial("abc")
   434  	require.NoError(t, err)
   435  	require.NotNil(t, b)
   436  
   437  	ring := newPukRing()
   438  	seed, gen := ring.makeGen(t, 1)
   439  	boxed, err := BoxAndEncode(b, gen, seed)
   440  	require.NoError(t, err)
   441  	require.NotNil(t, boxed, "BoxAndEncode() should return something")
   442  	require.Equal(t, stellar1.BundleVersion_V2, boxed.FormatVersionParent, "should be V2")
   443  	require.NotEmpty(t, boxed.VisParentB64)
   444  	require.NotEmpty(t, boxed.EncParentB64)
   445  	require.Equal(t, 2, boxed.EncParent.V)
   446  	require.NotEmpty(t, boxed.EncParent.E)
   447  	require.NotZero(t, boxed.EncParent.N)
   448  	require.Equal(t, gen, boxed.EncParent.Gen)
   449  	require.Len(t, boxed.AcctBundles, 1)
   450  
   451  	m := libkb.NewMetaContext(context.Background(), nil)
   452  	bundle, version, pukGen, accountGens, err := DecodeAndUnbox(m, ring, boxed.toBundleEncodedB64())
   453  	require.NoError(t, err)
   454  	require.NotNil(t, bundle)
   455  	require.Equal(t, stellar1.BundleVersion_V2, version)
   456  	require.Len(t, bundle.Accounts, 1)
   457  	require.Equal(t, stellar1.AccountMode_USER, bundle.Accounts[0].Mode)
   458  	require.Equal(t, pukGen, keybase1.PerUserKeyGeneration(1))
   459  	acctBundle, ok := bundle.AccountBundles[bundle.Accounts[0].AccountID]
   460  	require.True(t, ok)
   461  	acctBundleOriginal, ok := b.AccountBundles[bundle.Accounts[0].AccountID]
   462  	require.True(t, ok)
   463  	require.Equal(t, acctBundle.Signers[0], acctBundleOriginal.Signers[0])
   464  	for _, acct := range bundle.Accounts {
   465  		require.Equal(t, keybase1.PerUserKeyGeneration(1), accountGens[acct.AccountID])
   466  	}
   467  }
   468  
   469  // pukRing is a convenience type for puks in these tests.
   470  type pukRing struct {
   471  	puks map[keybase1.PerUserKeyGeneration]libkb.PerUserKeySeed
   472  }
   473  
   474  func newPukRing() *pukRing {
   475  	return &pukRing{puks: make(map[keybase1.PerUserKeyGeneration]libkb.PerUserKeySeed)}
   476  }
   477  
   478  func (p *pukRing) makeGen(t *testing.T, gen int) (libkb.PerUserKeySeed, keybase1.PerUserKeyGeneration) {
   479  	puk, err := libkb.GeneratePerUserKeySeed()
   480  	require.NoError(t, err)
   481  	pgen := keybase1.PerUserKeyGeneration(gen)
   482  	p.puks[pgen] = puk
   483  	return puk, pgen
   484  }
   485  
   486  // SeedByGeneration makes pukRing implement PukFinder.
   487  func (p *pukRing) SeedByGeneration(m libkb.MetaContext, generation keybase1.PerUserKeyGeneration) (libkb.PerUserKeySeed, error) {
   488  	puk, ok := p.puks[generation]
   489  	if ok {
   490  		return puk, nil
   491  	}
   492  	return libkb.PerUserKeySeed{}, errors.New("not found")
   493  }