From 91fab32bd442dd473c6bf6398794acaf79f365b2 Mon Sep 17 00:00:00 2001 From: Robin Bryce Date: Thu, 3 Jul 2025 20:43:43 +0100 Subject: [PATCH 1/3] feat: demonstration of generating a receipt for a signed (ccf) scitt statement DEMONSTRATION CODE NOT PRODUCTION SAFE Given a local replica of any datatrails log (production or otherwise): - verify the locally replicated log state - generate an mmr leaf value using the first 24 bytes of the sha256 of the signed statement as the trie index extrabytes, and an idTimestamp representing the current time, and the raw bytes of the statement as the leaf content to be hashed. - add the leaf value to the in memory copy of the ledger tile (massif) - generate a consistency proof form the old, verified, state to the newly created state. - sign using an ephemeral ecdsa private key (or one providded on the cli) - write out the new (forked) ledger state: 1. receipt-{mmrIndex}.cbor - the scitt signed statement for the MMR draft ledger 2. checkpoint-{oldSize}-{newSize}.cbor - the signed checkpoint (from which self service receipts can be produced *without* the ephemeral private key) 3. fork-{oldSize}-{newSize}.bin the MMR draft leger tile data with the newly appended leaf. 4. the ephemeral private key in .cbor and .pem format (only if generated) 5. the ephemeral public key in .cbor and .pem format (only if generated) Note: only minimal testing has been performed at this point. --- amourystatement.go | 467 +++++++++++++++++++++++++++++++++++++++++++ app.go | 1 + append.go | 443 ++++++++++++++++++++++++++++++++++++++++ cfgmassif.go | 3 +- cfgreader.go | 2 +- cmd/veracity/main.go | 13 +- diag.go | 2 +- ecdsareadwrite.go | 268 +++++++++++++++++++++++++ go.mod | 56 +++--- go.sum | 119 ++++++----- 10 files changed, 1273 insertions(+), 101 deletions(-) create mode 100644 amourystatement.go create mode 100644 append.go create mode 100644 ecdsareadwrite.go diff --git a/amourystatement.go b/amourystatement.go new file mode 100644 index 0000000..ae71001 --- /dev/null +++ b/amourystatement.go @@ -0,0 +1,467 @@ +package veracity + +var AmourySignedStatement = []byte{ + 0xd2, 0x84, 0x59, 0x13, 0xf2, 0xa6, 0x01, 0x38, 0x25, 0x0f, 0xa4, 0x01, + 0x78, 0x5c, 0x64, 0x69, 0x64, 0x3a, 0x78, 0x35, 0x30, 0x39, 0x3a, 0x30, + 0x3a, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x3a, 0x49, 0x5f, 0x5f, 0x69, + 0x75, 0x4c, 0x32, 0x35, 0x6f, 0x58, 0x45, 0x56, 0x46, 0x64, 0x54, 0x50, + 0x5f, 0x61, 0x42, 0x4c, 0x78, 0x5f, 0x65, 0x54, 0x31, 0x52, 0x50, 0x48, + 0x62, 0x43, 0x51, 0x5f, 0x45, 0x43, 0x42, 0x51, 0x66, 0x59, 0x5a, 0x70, + 0x74, 0x39, 0x73, 0x3a, 0x3a, 0x65, 0x6b, 0x75, 0x3a, 0x31, 0x2e, 0x33, + 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x33, 0x31, 0x31, + 0x2e, 0x37, 0x36, 0x2e, 0x35, 0x39, 0x2e, 0x31, 0x2e, 0x31, 0x02, 0x78, + 0x26, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, + 0x6c, 0x2f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2f, + 0x70, 0x68, 0x69, 0x2d, 0x34, 0x2d, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x69, 0x6e, 0x67, 0x06, 0xc1, 0x1a, 0x68, 0x54, 0x89, 0xb3, 0x63, 0x73, + 0x76, 0x6e, 0x00, 0x18, 0x21, 0x83, 0x59, 0x06, 0x78, 0x30, 0x82, 0x06, + 0x74, 0x30, 0x82, 0x04, 0x5c, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x13, + 0x33, 0x00, 0x00, 0x00, 0x47, 0xa0, 0xab, 0xc0, 0xe5, 0xbd, 0x99, 0x39, + 0xb2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c, 0x05, 0x00, 0x30, 0x55, + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, + 0x53, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15, + 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, + 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x26, 0x30, + 0x24, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1d, 0x4d, 0x69, 0x63, 0x72, + 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x53, 0x43, 0x44, 0x20, 0x50, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x20, 0x52, 0x53, 0x41, 0x20, 0x43, + 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x32, 0x32, 0x30, 0x32, + 0x30, 0x34, 0x35, 0x34, 0x36, 0x5a, 0x17, 0x0d, 0x32, 0x36, 0x30, 0x32, + 0x31, 0x38, 0x32, 0x30, 0x34, 0x35, 0x34, 0x36, 0x5a, 0x30, 0x81, 0x81, + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, + 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, + 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, + 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, 0x65, 0x64, + 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, + 0x0a, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, + 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x22, 0x4d, + 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x53, 0x43, 0x44, + 0x20, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x20, 0x52, 0x53, + 0x41, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x30, 0x82, 0x01, + 0xa2, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x8f, 0x00, 0x30, 0x82, 0x01, + 0x8a, 0x02, 0x82, 0x01, 0x81, 0x00, 0xb6, 0x9a, 0x2e, 0xa4, 0xba, 0xce, + 0xfe, 0xaf, 0x9a, 0xc5, 0x63, 0xd2, 0xa9, 0x5d, 0x14, 0x69, 0xc9, 0x39, + 0x5f, 0xdb, 0x0a, 0x23, 0xad, 0xc7, 0x3f, 0x7c, 0x6e, 0x94, 0x50, 0x71, + 0x32, 0xea, 0xe6, 0xef, 0x33, 0x90, 0x58, 0x5c, 0xb6, 0xf6, 0x6f, 0xbd, + 0x70, 0x84, 0x49, 0x33, 0x35, 0xa4, 0xa9, 0x58, 0x02, 0xb8, 0xad, 0x37, + 0xb5, 0xa4, 0x13, 0x3f, 0x9e, 0xc0, 0x54, 0x2a, 0x83, 0x47, 0xf7, 0xa3, + 0xe3, 0xa6, 0x30, 0x46, 0x9d, 0x88, 0x42, 0xde, 0x4f, 0x2c, 0xea, 0x6e, + 0x4b, 0xbf, 0x96, 0x5e, 0x68, 0x36, 0xf8, 0x0b, 0x11, 0x31, 0x24, 0xed, + 0x8c, 0x75, 0x17, 0xd0, 0x27, 0x37, 0x49, 0x11, 0x9a, 0x37, 0x92, 0x2b, + 0xda, 0xc7, 0x5a, 0x6a, 0x6d, 0xc1, 0xdb, 0xec, 0x23, 0x54, 0x47, 0xd3, + 0x6e, 0x55, 0x64, 0x87, 0x3a, 0xf5, 0x64, 0x87, 0x3f, 0x84, 0x18, 0x99, + 0x91, 0x1e, 0x28, 0x75, 0x0b, 0x57, 0xe5, 0xfa, 0xf5, 0x1f, 0xfc, 0x52, + 0x1f, 0x79, 0x20, 0x6a, 0x9c, 0x0a, 0x24, 0x2b, 0xac, 0xf1, 0x7a, 0x7a, + 0x7f, 0xdc, 0x08, 0xa0, 0x33, 0x7b, 0x93, 0x6d, 0x14, 0x18, 0x38, 0x3b, + 0xa6, 0xe1, 0xee, 0xa8, 0x71, 0x2e, 0x81, 0x86, 0x2c, 0x69, 0x92, 0xc5, + 0x80, 0x27, 0x82, 0xb1, 0xb2, 0x80, 0xdc, 0x62, 0x86, 0x1d, 0xa4, 0x01, + 0x56, 0x3e, 0x08, 0x3e, 0x6b, 0xd5, 0x1a, 0x7a, 0x42, 0xd5, 0x74, 0x21, + 0x5a, 0x43, 0x39, 0x5d, 0x69, 0x90, 0x44, 0x77, 0x57, 0x99, 0xbf, 0x3a, + 0x21, 0x66, 0x87, 0xbc, 0xca, 0x86, 0x45, 0xa5, 0xc3, 0x38, 0xf3, 0xe4, + 0x42, 0xc7, 0xa5, 0x8d, 0x92, 0xa9, 0xc0, 0x14, 0x69, 0xc0, 0xa1, 0x2d, + 0xcc, 0x28, 0x43, 0xb5, 0xd7, 0x2b, 0x9e, 0xd4, 0xe2, 0x8b, 0x96, 0x71, + 0x0f, 0x6c, 0xff, 0xcb, 0xc8, 0x96, 0xcc, 0x35, 0x37, 0x5a, 0x79, 0x1a, + 0x2b, 0x2a, 0x45, 0xc5, 0xc3, 0x26, 0x5f, 0x03, 0x25, 0xe4, 0xdf, 0xd4, + 0xf7, 0xec, 0x1a, 0x30, 0xc1, 0xbe, 0xa0, 0xa5, 0x76, 0xa5, 0x02, 0x98, + 0xc0, 0x60, 0x0b, 0x34, 0x9b, 0x9f, 0xd7, 0x47, 0xe8, 0x92, 0xf1, 0xa5, + 0xa9, 0xeb, 0x03, 0x4f, 0x33, 0x9d, 0x54, 0x5d, 0x47, 0xde, 0xcc, 0x2e, + 0x02, 0xfa, 0x6b, 0xe2, 0x1a, 0x25, 0x79, 0x38, 0x44, 0xd7, 0x68, 0x91, + 0xe3, 0x2d, 0x60, 0x33, 0x80, 0x8b, 0x7c, 0x56, 0x5a, 0xf9, 0x49, 0x0c, + 0x94, 0x2d, 0x83, 0x3e, 0x51, 0x04, 0xef, 0xf3, 0x73, 0x42, 0x13, 0x0c, + 0xc6, 0x31, 0xf1, 0xb6, 0x6c, 0x4f, 0xb2, 0x0b, 0x0f, 0x1d, 0xd7, 0xfe, + 0x33, 0x3b, 0x77, 0x75, 0xa6, 0x6f, 0x1a, 0x35, 0x49, 0x08, 0x2c, 0x3c, + 0x30, 0xe2, 0x70, 0x32, 0xc8, 0x69, 0x3f, 0xb4, 0xf5, 0xb6, 0xdc, 0xe5, + 0x34, 0xf2, 0x6e, 0xa4, 0xf7, 0x73, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, + 0x82, 0x01, 0x8e, 0x30, 0x82, 0x01, 0x8a, 0x30, 0x0e, 0x06, 0x03, 0x55, + 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x07, 0x80, 0x30, + 0x16, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x0f, 0x30, 0x0d, 0x06, 0x0b, + 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x4c, 0x3b, 0x01, 0x01, 0x30, + 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xf3, 0x16, + 0x75, 0x55, 0x53, 0x35, 0x76, 0x3f, 0x69, 0xbe, 0xef, 0x1f, 0xd4, 0xab, + 0x22, 0x89, 0x1f, 0x7f, 0x9e, 0x18, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d, + 0x11, 0x04, 0x3e, 0x30, 0x3c, 0xa4, 0x3a, 0x30, 0x38, 0x31, 0x1e, 0x30, + 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, + 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, + 0x04, 0x05, 0x13, 0x0d, 0x34, 0x36, 0x39, 0x34, 0x35, 0x31, 0x2b, 0x35, + 0x30, 0x33, 0x37, 0x39, 0x30, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, + 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x55, 0xcd, 0x4d, 0x85, 0x6e, 0xcd, + 0x4a, 0x35, 0xc3, 0x8e, 0x3f, 0x72, 0x01, 0xba, 0xaa, 0x98, 0x19, 0x97, + 0x4b, 0xa7, 0x30, 0x5e, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x57, 0x30, + 0x55, 0x30, 0x53, 0xa0, 0x51, 0xa0, 0x4f, 0x86, 0x4d, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, + 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, + 0x69, 0x6f, 0x70, 0x73, 0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x4d, 0x69, 0x63, + 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x25, 0x32, 0x30, 0x53, 0x43, 0x44, + 0x25, 0x32, 0x30, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x25, + 0x32, 0x30, 0x52, 0x53, 0x41, 0x25, 0x32, 0x30, 0x43, 0x41, 0x2e, 0x63, + 0x72, 0x6c, 0x30, 0x6b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x01, 0x01, 0x04, 0x5f, 0x30, 0x5d, 0x30, 0x5b, 0x06, 0x08, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x4f, 0x68, 0x74, 0x74, 0x70, + 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, + 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, + 0x6f, 0x70, 0x73, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x4d, 0x69, + 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x25, 0x32, 0x30, 0x53, 0x43, + 0x44, 0x25, 0x32, 0x30, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, + 0x25, 0x32, 0x30, 0x52, 0x53, 0x41, 0x25, 0x32, 0x30, 0x43, 0x41, 0x2e, + 0x63, 0x72, 0x74, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, + 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, + 0x00, 0x8c, 0x4f, 0x3c, 0xdf, 0x9b, 0x0c, 0x36, 0x09, 0xfb, 0x6a, 0x31, + 0x7a, 0x7a, 0xa5, 0xf2, 0x36, 0x40, 0x67, 0x29, 0x4b, 0xec, 0xfc, 0x85, + 0xa8, 0x4d, 0xc6, 0xda, 0x46, 0x40, 0x1d, 0x7e, 0x92, 0x17, 0x3b, 0xfb, + 0x84, 0x6c, 0xcc, 0xd0, 0x4a, 0x14, 0x21, 0xf4, 0xd2, 0x5a, 0xa8, 0x44, + 0x94, 0xbe, 0x75, 0x82, 0x9e, 0x77, 0x2e, 0x74, 0x3c, 0x5f, 0xe6, 0x2b, + 0xd0, 0x9d, 0x07, 0xe4, 0x3b, 0xc5, 0x43, 0xec, 0x47, 0x7c, 0xfa, 0xaa, + 0x32, 0xd4, 0x1e, 0xd7, 0x0b, 0xf3, 0xb6, 0xb5, 0xd8, 0x12, 0x29, 0x76, + 0xa8, 0x74, 0x8d, 0xd4, 0x4c, 0xc2, 0xb3, 0x03, 0xce, 0x67, 0x43, 0x02, + 0x0b, 0xf2, 0x23, 0x77, 0x99, 0x3f, 0xa8, 0x20, 0x62, 0x79, 0xc4, 0xd3, + 0xbd, 0x40, 0x64, 0x91, 0x93, 0x6c, 0x74, 0xe5, 0xd8, 0xa4, 0x28, 0x34, + 0x1b, 0xf5, 0xe8, 0x10, 0xb3, 0xaa, 0xa1, 0x64, 0x09, 0xef, 0x72, 0xaf, + 0x6d, 0xfb, 0xce, 0x0e, 0x91, 0xe2, 0x7e, 0x8c, 0xc8, 0x28, 0x8a, 0x2f, + 0x3e, 0xe6, 0x89, 0x7d, 0x8a, 0x5f, 0xf9, 0x5e, 0x54, 0xb0, 0xf0, 0xc9, + 0x8e, 0x0c, 0xfc, 0x0d, 0x8b, 0xb4, 0x6c, 0x52, 0x12, 0x8c, 0x90, 0x94, + 0x22, 0x9b, 0x04, 0x80, 0x38, 0xad, 0xf7, 0x41, 0x18, 0x2c, 0x12, 0xe9, + 0x7a, 0x05, 0xba, 0x2d, 0x77, 0xf2, 0xc2, 0x96, 0xd8, 0x61, 0x8c, 0xd0, + 0x99, 0x47, 0xd7, 0xee, 0x1e, 0xb3, 0x42, 0x31, 0xda, 0x46, 0x1d, 0x9b, + 0x29, 0xfe, 0x36, 0x54, 0xe9, 0xa9, 0xd4, 0xc6, 0x7b, 0x8c, 0xb4, 0x21, + 0x48, 0xbd, 0x93, 0x50, 0xa3, 0x91, 0x33, 0x63, 0x67, 0x03, 0xbe, 0xe2, + 0x68, 0x93, 0x30, 0x5c, 0xda, 0x22, 0xbb, 0x80, 0xd7, 0xc0, 0x9c, 0x4b, + 0xf8, 0x4e, 0xb1, 0x3a, 0x79, 0x2a, 0x57, 0x67, 0xb5, 0x1e, 0xd0, 0xba, + 0xd7, 0x79, 0x6d, 0x2e, 0xf1, 0x7d, 0x9c, 0x9b, 0x43, 0xdd, 0xf2, 0x21, + 0x05, 0xb1, 0x59, 0x28, 0xdf, 0x7a, 0x3b, 0x5c, 0x46, 0x3f, 0x29, 0x33, + 0xf1, 0x28, 0x77, 0x85, 0xfb, 0x75, 0x5e, 0x89, 0xea, 0xbf, 0xe5, 0x12, + 0xe8, 0x29, 0x67, 0xb1, 0x06, 0x48, 0xd5, 0xb2, 0xf0, 0x78, 0xc4, 0xed, + 0x87, 0x9e, 0x71, 0x88, 0x32, 0x05, 0xf6, 0x1d, 0x34, 0x44, 0x4d, 0x26, + 0x01, 0xf4, 0xf6, 0x19, 0x83, 0x1d, 0x01, 0xc1, 0xa6, 0x80, 0xa2, 0x81, + 0x2e, 0x3a, 0x13, 0x49, 0xbd, 0xea, 0x8f, 0x2e, 0x08, 0x2f, 0xf2, 0x4f, + 0x69, 0xa9, 0x4b, 0x3e, 0x37, 0xcb, 0xc5, 0xb8, 0x19, 0x00, 0xa4, 0xab, + 0x9e, 0x61, 0xfc, 0x35, 0x8b, 0xd8, 0xba, 0xf4, 0x3a, 0x19, 0xab, 0xff, + 0x6f, 0x2a, 0x0a, 0x21, 0x37, 0x1e, 0x37, 0x52, 0x0b, 0xdc, 0x5a, 0x88, + 0x49, 0x5b, 0x8a, 0xea, 0x7d, 0xd4, 0x88, 0x50, 0x28, 0xaa, 0xb9, 0xad, + 0x3f, 0x90, 0x5f, 0x16, 0xd7, 0xe7, 0x9f, 0x21, 0xfe, 0x8a, 0x8c, 0x42, + 0x70, 0xdf, 0x2d, 0xc5, 0x83, 0x04, 0xb6, 0x96, 0xd6, 0x69, 0xff, 0x7b, + 0x6e, 0x30, 0xcd, 0xc2, 0xa0, 0x9b, 0xe4, 0xb0, 0xf4, 0x4a, 0x45, 0xdc, + 0x03, 0xea, 0xf2, 0x17, 0x90, 0xb8, 0x5f, 0x58, 0x97, 0x9d, 0x4f, 0x23, + 0xd9, 0xee, 0x4f, 0x29, 0x6d, 0x80, 0x4c, 0x63, 0x71, 0xdf, 0x20, 0x78, + 0x8c, 0xfd, 0x6b, 0x1b, 0x63, 0x48, 0xcd, 0xaa, 0xb2, 0x4f, 0x4b, 0x1f, + 0x3d, 0x94, 0x1b, 0xd9, 0xa0, 0x7f, 0xf2, 0x2e, 0xb0, 0xe1, 0xc0, 0xa9, + 0x52, 0x4f, 0xe6, 0xe3, 0x56, 0xb7, 0xed, 0xd0, 0x49, 0xd9, 0x91, 0x67, + 0x6a, 0xab, 0x6b, 0x8e, 0xca, 0xce, 0x65, 0xc2, 0x5b, 0xe4, 0xea, 0x12, + 0xf2, 0x9c, 0x26, 0xe4, 0xd6, 0xb3, 0xc8, 0xe1, 0xd2, 0xe3, 0x39, 0x4d, + 0xc1, 0x22, 0x50, 0x37, 0x2c, 0x69, 0x1b, 0xa3, 0xe5, 0x59, 0x06, 0xd5, + 0x30, 0x82, 0x06, 0xd1, 0x30, 0x82, 0x04, 0xb9, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x13, 0x33, 0x00, 0x00, 0x00, 0x03, 0x95, 0x84, 0x47, 0xff, + 0x89, 0xe8, 0x66, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c, 0x05, + 0x00, 0x30, 0x5f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, + 0x13, 0x02, 0x55, 0x53, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, + 0x0a, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, + 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27, 0x4d, + 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x53, 0x75, 0x70, + 0x70, 0x6c, 0x79, 0x20, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x20, 0x52, 0x53, + 0x41, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, + 0x32, 0x32, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x32, 0x31, 0x37, + 0x30, 0x30, 0x34, 0x35, 0x32, 0x33, 0x5a, 0x17, 0x0d, 0x34, 0x32, 0x30, + 0x32, 0x31, 0x37, 0x30, 0x30, 0x35, 0x35, 0x32, 0x33, 0x5a, 0x30, 0x55, + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, + 0x53, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15, + 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, + 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x26, 0x30, + 0x24, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1d, 0x4d, 0x69, 0x63, 0x72, + 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x53, 0x43, 0x44, 0x20, 0x50, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x20, 0x52, 0x53, 0x41, 0x20, 0x43, + 0x41, 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, + 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xab, 0xed, + 0x7f, 0xb5, 0x71, 0xbe, 0x8c, 0x73, 0xbe, 0xf1, 0xd7, 0xca, 0x9d, 0xf1, + 0x01, 0xd6, 0x74, 0x87, 0xbc, 0x8c, 0x42, 0x93, 0x4c, 0x9f, 0xca, 0x95, + 0x74, 0x6b, 0x4e, 0x47, 0xea, 0x72, 0x84, 0xb5, 0xa4, 0x16, 0xc1, 0x8c, + 0x42, 0x54, 0xd7, 0x0d, 0xed, 0x98, 0x7a, 0xcf, 0xa8, 0xee, 0x60, 0xb4, + 0x20, 0x44, 0x09, 0x33, 0x3d, 0xfd, 0x08, 0x9c, 0x48, 0x8c, 0x6e, 0x97, + 0x60, 0x84, 0x1f, 0x70, 0x5d, 0x82, 0x68, 0xef, 0xfa, 0x30, 0x2c, 0xd6, + 0xcf, 0x2b, 0x1b, 0x16, 0xf9, 0x53, 0x92, 0x86, 0x3f, 0x2c, 0xdf, 0xe0, + 0xd3, 0xf4, 0x65, 0x70, 0x9f, 0xc8, 0x74, 0x59, 0x5f, 0xd1, 0x55, 0x9c, + 0xbe, 0xe8, 0xe9, 0x64, 0xf8, 0x7d, 0x08, 0xb9, 0x44, 0x77, 0x41, 0xd2, + 0xf6, 0xa6, 0x05, 0x44, 0x03, 0xd1, 0x45, 0x03, 0xaf, 0xc1, 0xed, 0xcd, + 0x4f, 0x9b, 0x84, 0x77, 0x7f, 0x1f, 0x45, 0xb2, 0x9b, 0x67, 0xab, 0xc2, + 0x24, 0x6d, 0x9c, 0xfd, 0x8c, 0x47, 0x07, 0x22, 0x9b, 0x7a, 0x8a, 0x18, + 0x45, 0xea, 0x2f, 0x3e, 0x83, 0x69, 0x56, 0x9c, 0x5d, 0x68, 0x80, 0xd2, + 0xeb, 0x82, 0x1d, 0x80, 0x69, 0x7c, 0x99, 0x7f, 0xb2, 0x4c, 0xfc, 0x30, + 0xc0, 0xb1, 0xce, 0x7d, 0x1f, 0x84, 0xd9, 0x45, 0xa0, 0x9e, 0x74, 0x2a, + 0x80, 0xd6, 0x29, 0xd2, 0x10, 0x8c, 0xd9, 0x86, 0x7e, 0x27, 0x9c, 0xd4, + 0xd1, 0x06, 0x42, 0xc1, 0x9d, 0x49, 0x30, 0xb5, 0xd0, 0xf5, 0xe2, 0xb4, + 0xb0, 0x95, 0xb7, 0xb8, 0xf7, 0xe3, 0xee, 0x20, 0x3f, 0x93, 0x59, 0x39, + 0xee, 0x43, 0x77, 0x75, 0x26, 0x78, 0x3f, 0x88, 0x64, 0xa8, 0x65, 0x53, + 0x02, 0x7a, 0xc1, 0xcd, 0xaa, 0x19, 0xb0, 0x83, 0x4c, 0x90, 0x65, 0x49, + 0x6e, 0x01, 0x29, 0x7d, 0x23, 0xeb, 0x44, 0xb0, 0x4e, 0x92, 0xbe, 0x19, + 0x9a, 0x1e, 0xe6, 0xf0, 0xf8, 0xa0, 0x2f, 0xc0, 0x7c, 0xc4, 0x82, 0x74, + 0xd5, 0x3c, 0x75, 0x28, 0x19, 0x9f, 0x89, 0x60, 0x05, 0x1a, 0x65, 0x71, + 0xfb, 0xe3, 0x52, 0x63, 0xca, 0x05, 0xc5, 0x15, 0xbf, 0x0d, 0xd2, 0x9d, + 0xc1, 0x62, 0xeb, 0xe6, 0xcb, 0x82, 0xa4, 0x1d, 0x8e, 0x36, 0x31, 0x7b, + 0x2c, 0xdb, 0xf8, 0x03, 0x9b, 0xf8, 0x49, 0x9f, 0xb3, 0x60, 0x2c, 0x29, + 0x4d, 0xcf, 0x28, 0xbb, 0x13, 0xcf, 0x52, 0xd6, 0x52, 0x1b, 0xf7, 0xe4, + 0x95, 0x51, 0x05, 0xbd, 0xe5, 0xb7, 0xd2, 0x33, 0x09, 0xc1, 0x00, 0x1f, + 0xdb, 0xd5, 0xfc, 0xc0, 0x0b, 0x89, 0xd2, 0x9c, 0x2e, 0x59, 0xa3, 0xf6, + 0x3f, 0x38, 0x90, 0x4a, 0x89, 0xd1, 0xe1, 0x59, 0x91, 0x3f, 0x77, 0x0a, + 0xcf, 0xcf, 0x1a, 0x01, 0xb9, 0xb4, 0xce, 0x6c, 0xef, 0xc7, 0xea, 0x5d, + 0x4c, 0x25, 0xfd, 0x7c, 0x7f, 0xdc, 0x4e, 0xe6, 0x30, 0x12, 0xb8, 0xc9, + 0x03, 0x77, 0x7d, 0x1b, 0xbf, 0xf7, 0xb0, 0x31, 0x84, 0xfd, 0x00, 0x6a, + 0x92, 0x30, 0xbe, 0x36, 0x46, 0x48, 0xf1, 0x70, 0x9d, 0x9b, 0xa5, 0x2b, + 0xf1, 0x02, 0x0a, 0xe0, 0xb6, 0x99, 0x27, 0xf6, 0x41, 0x4f, 0xd4, 0x04, + 0x91, 0x71, 0x7b, 0xc4, 0xc2, 0xf4, 0x14, 0x17, 0xb7, 0x60, 0xb6, 0x16, + 0x93, 0x91, 0x76, 0xa5, 0xce, 0x1d, 0xdb, 0x02, 0x62, 0x9d, 0x92, 0x05, + 0xbc, 0x92, 0x6f, 0x2e, 0xf9, 0x00, 0xa7, 0xff, 0xe0, 0xb9, 0xa6, 0xed, + 0xeb, 0x00, 0x97, 0x4e, 0x3c, 0x47, 0x0f, 0x3d, 0x91, 0x92, 0x22, 0x67, + 0x9a, 0x2b, 0x5e, 0x48, 0xb3, 0xb4, 0xf4, 0x37, 0x90, 0x22, 0xf8, 0x04, + 0x19, 0x8e, 0xe7, 0x25, 0x32, 0xb2, 0xd6, 0x83, 0x30, 0x46, 0x86, 0xa5, + 0x1a, 0xb2, 0xf5, 0xe1, 0x80, 0xf8, 0x43, 0x23, 0x5a, 0xc1, 0xc6, 0xb3, + 0x06, 0xd1, 0x99, 0x43, 0x6d, 0x0d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, + 0x82, 0x01, 0x8e, 0x30, 0x82, 0x01, 0x8a, 0x30, 0x0e, 0x06, 0x03, 0x55, + 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, + 0x10, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01, + 0x04, 0x03, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, + 0x04, 0x16, 0x04, 0x14, 0x55, 0xcd, 0x4d, 0x85, 0x6e, 0xcd, 0x4a, 0x35, + 0xc3, 0x8e, 0x3f, 0x72, 0x01, 0xba, 0xaa, 0x98, 0x19, 0x97, 0x4b, 0xa7, + 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08, 0x30, + 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x19, 0x06, 0x09, 0x2b, + 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04, 0x0c, 0x1e, 0x0a, + 0x00, 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41, 0x30, 0x0f, + 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, + 0x01, 0x01, 0xff, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, + 0x30, 0x16, 0x80, 0x14, 0x0b, 0xb3, 0x68, 0x3b, 0xaf, 0xda, 0xaf, 0xee, + 0x70, 0xa5, 0x76, 0xd9, 0x21, 0xf7, 0xcc, 0x44, 0x16, 0x07, 0xd0, 0xf8, + 0x30, 0x6c, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x65, 0x30, 0x63, 0x30, + 0x61, 0xa0, 0x5f, 0xa0, 0x5d, 0x86, 0x5b, 0x68, 0x74, 0x74, 0x70, 0x3a, + 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, + 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x6f, + 0x70, 0x73, 0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x4d, 0x69, 0x63, 0x72, 0x6f, + 0x73, 0x6f, 0x66, 0x74, 0x25, 0x32, 0x30, 0x53, 0x75, 0x70, 0x70, 0x6c, + 0x79, 0x25, 0x32, 0x30, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x25, 0x32, 0x30, + 0x52, 0x53, 0x41, 0x25, 0x32, 0x30, 0x52, 0x6f, 0x6f, 0x74, 0x25, 0x32, + 0x30, 0x43, 0x41, 0x25, 0x32, 0x30, 0x32, 0x30, 0x32, 0x32, 0x2e, 0x63, + 0x72, 0x6c, 0x30, 0x79, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x01, 0x01, 0x04, 0x6d, 0x30, 0x6b, 0x30, 0x69, 0x06, 0x08, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x5d, 0x68, 0x74, 0x74, 0x70, + 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, + 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, + 0x6f, 0x70, 0x73, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x4d, 0x69, + 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x25, 0x32, 0x30, 0x53, 0x75, + 0x70, 0x70, 0x6c, 0x79, 0x25, 0x32, 0x30, 0x43, 0x68, 0x61, 0x69, 0x6e, + 0x25, 0x32, 0x30, 0x52, 0x53, 0x41, 0x25, 0x32, 0x30, 0x52, 0x6f, 0x6f, + 0x74, 0x25, 0x32, 0x30, 0x43, 0x41, 0x25, 0x32, 0x30, 0x32, 0x30, 0x32, + 0x32, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, + 0x00, 0x6f, 0xde, 0x61, 0xd6, 0x6b, 0xfa, 0x41, 0xbf, 0x6d, 0x1c, 0x94, + 0xc8, 0xe1, 0x8a, 0xc3, 0xca, 0xa7, 0xf4, 0x33, 0x04, 0xe4, 0x29, 0x26, + 0xcf, 0x7b, 0xe6, 0x21, 0xd7, 0x26, 0x75, 0x4f, 0x8b, 0x13, 0x74, 0xe7, + 0x17, 0x31, 0x01, 0x46, 0x8d, 0x45, 0x44, 0x5d, 0x6d, 0x74, 0xe7, 0x6a, + 0x0a, 0xae, 0x7c, 0xbe, 0xd1, 0xf9, 0x96, 0xec, 0x5a, 0xf2, 0x19, 0x25, + 0xe3, 0x0c, 0xaf, 0xbc, 0x08, 0xef, 0xd1, 0xa8, 0x69, 0xa6, 0xbf, 0xb6, + 0x50, 0x8e, 0xfd, 0xbf, 0x2a, 0x33, 0x28, 0x62, 0x02, 0xe2, 0xe7, 0x76, + 0xcc, 0x1a, 0x56, 0x82, 0xd9, 0xb1, 0x89, 0xf1, 0x6f, 0xe4, 0xac, 0x97, + 0xcb, 0xb9, 0x19, 0xca, 0xbb, 0xee, 0x69, 0x50, 0xe6, 0x47, 0x78, 0x70, + 0x02, 0x1a, 0x59, 0xc9, 0x37, 0xd2, 0xe9, 0x72, 0xf1, 0x75, 0x19, 0xec, + 0x0e, 0x5b, 0x03, 0xf7, 0x9a, 0x9d, 0xc3, 0xcf, 0x61, 0x04, 0xa7, 0xfc, + 0x97, 0xf4, 0x1f, 0x16, 0x10, 0xa4, 0x3c, 0x98, 0xb7, 0x04, 0xf7, 0xed, + 0x6f, 0x41, 0x35, 0x90, 0x54, 0x39, 0xa9, 0x4c, 0xe5, 0xe2, 0x34, 0xa7, + 0x80, 0x22, 0xb2, 0x4f, 0xc7, 0xdd, 0x5d, 0x90, 0x51, 0x74, 0x79, 0x47, + 0x8a, 0x5d, 0x75, 0x04, 0x9a, 0x4d, 0x9b, 0xb8, 0x1c, 0x27, 0x12, 0x50, + 0x7d, 0x85, 0x81, 0x5f, 0xe1, 0x03, 0x46, 0x93, 0x46, 0x4b, 0x46, 0x08, + 0xe7, 0xf7, 0x10, 0x84, 0xc1, 0x12, 0xdf, 0x98, 0xd8, 0x25, 0xf1, 0x86, + 0xa2, 0xcc, 0x3d, 0x0c, 0x50, 0x9f, 0x39, 0x1c, 0xe3, 0x46, 0x67, 0x35, + 0xce, 0x91, 0x15, 0x5d, 0x4a, 0xe7, 0x6e, 0x72, 0x43, 0xb5, 0xc8, 0xeb, + 0xa5, 0xe4, 0x33, 0xd0, 0x34, 0x20, 0xe8, 0xa5, 0x70, 0x3a, 0x34, 0xa4, + 0x12, 0x4b, 0xe3, 0xcc, 0xa9, 0x6d, 0x1f, 0x4f, 0x9b, 0x9d, 0x4d, 0x48, + 0x2a, 0xfa, 0xd2, 0xb9, 0x5c, 0xbc, 0x44, 0x55, 0x9c, 0x8b, 0x5b, 0xdd, + 0xac, 0x08, 0xf4, 0x23, 0xa6, 0x36, 0x25, 0xa0, 0x0b, 0x70, 0x4d, 0x34, + 0x2e, 0x1f, 0x3a, 0x04, 0x71, 0x98, 0x54, 0xaf, 0xcd, 0x64, 0x46, 0x50, + 0x00, 0x05, 0xe5, 0x08, 0xf4, 0x5a, 0x39, 0x09, 0x1c, 0x09, 0xac, 0x64, + 0xb9, 0x3d, 0x33, 0x35, 0x90, 0x74, 0x36, 0x9a, 0x54, 0xd7, 0x8f, 0x39, + 0x8c, 0x74, 0x7a, 0xee, 0x9e, 0xfc, 0x6d, 0xb0, 0x69, 0x5d, 0x27, 0xbd, + 0x2f, 0x27, 0xe9, 0x58, 0x5c, 0x01, 0xde, 0xae, 0xa3, 0xc9, 0xef, 0x4a, + 0x5b, 0x6b, 0x97, 0x8b, 0xfe, 0xf3, 0x4c, 0xf6, 0x01, 0xc9, 0x7d, 0x00, + 0xb5, 0xea, 0x15, 0xa3, 0xa2, 0x56, 0xe7, 0xa2, 0x57, 0x84, 0x82, 0xc2, + 0x5a, 0x6c, 0xc1, 0x8d, 0xb8, 0xfc, 0x59, 0x4c, 0xdc, 0xa3, 0xfb, 0x31, + 0x8f, 0x06, 0xed, 0x85, 0x3d, 0x16, 0xb4, 0xa0, 0xc0, 0x0c, 0xab, 0x8a, + 0x44, 0x46, 0xa1, 0x0b, 0x2d, 0x2d, 0x49, 0xeb, 0x2d, 0x0f, 0x70, 0xf9, + 0x5d, 0xc1, 0x88, 0x74, 0xcb, 0xd4, 0xf4, 0x10, 0x4b, 0x16, 0x09, 0x57, + 0xb5, 0x6d, 0x8b, 0x99, 0xd4, 0xc3, 0x7b, 0x89, 0x4b, 0x05, 0x2b, 0xae, + 0x4b, 0x64, 0xd0, 0xa0, 0x50, 0x70, 0xfc, 0x1a, 0x2a, 0x5d, 0xcb, 0x42, + 0x7b, 0xfb, 0x03, 0x7a, 0xbe, 0x53, 0x57, 0x17, 0x99, 0xe2, 0x1e, 0xf3, + 0x53, 0x9d, 0x2f, 0x72, 0xb0, 0x95, 0xef, 0x8c, 0x7e, 0xc4, 0x22, 0x38, + 0x5e, 0x95, 0x26, 0x5c, 0x8d, 0xee, 0xc8, 0xba, 0xe1, 0x11, 0x52, 0x61, + 0xd0, 0x2d, 0x37, 0x2f, 0x7a, 0x44, 0xf8, 0xd4, 0xe6, 0x20, 0x89, 0xbe, + 0xed, 0x99, 0x3c, 0xab, 0x93, 0x26, 0xae, 0x44, 0x3b, 0xa5, 0x5c, 0x24, + 0x25, 0xd4, 0xfb, 0x71, 0x6e, 0xd8, 0x82, 0x2a, 0xa4, 0xa0, 0x22, 0x0e, + 0x7b, 0x28, 0x1b, 0xfd, 0x45, 0x4f, 0x5f, 0x18, 0x56, 0x59, 0x05, 0xb3, + 0x30, 0x82, 0x05, 0xaf, 0x30, 0x82, 0x03, 0x97, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x10, 0x68, 0x28, 0xd5, 0x4c, 0x7e, 0x5c, 0xda, 0xbd, 0x43, + 0x39, 0xae, 0x0c, 0xc1, 0x5a, 0x2a, 0x35, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c, 0x05, 0x00, 0x30, 0x5f, + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, + 0x53, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15, + 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, + 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x30, 0x30, + 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27, 0x4d, 0x69, 0x63, 0x72, + 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, + 0x20, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x20, 0x52, 0x53, 0x41, 0x20, 0x52, + 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, 0x32, 0x32, 0x30, + 0x1e, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x32, 0x31, 0x37, 0x30, 0x30, 0x31, + 0x32, 0x33, 0x36, 0x5a, 0x17, 0x0d, 0x34, 0x37, 0x30, 0x32, 0x31, 0x37, + 0x30, 0x30, 0x32, 0x31, 0x30, 0x39, 0x5a, 0x30, 0x5f, 0x31, 0x0b, 0x30, + 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x1e, + 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15, 0x4d, 0x69, 0x63, + 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x13, 0x27, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, + 0x66, 0x74, 0x20, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x20, 0x43, 0x68, + 0x61, 0x69, 0x6e, 0x20, 0x52, 0x53, 0x41, 0x20, 0x52, 0x6f, 0x6f, 0x74, + 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, 0x32, 0x32, 0x30, 0x82, 0x02, 0x22, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, + 0x02, 0x82, 0x02, 0x01, 0x00, 0x9e, 0x25, 0x01, 0x66, 0x19, 0x1f, 0x64, + 0x34, 0xc1, 0x9d, 0x39, 0x23, 0x62, 0x1d, 0x71, 0x8b, 0x56, 0xea, 0x25, + 0xd2, 0x9b, 0x1f, 0xef, 0x27, 0x01, 0x82, 0xbf, 0x77, 0xd8, 0x94, 0x33, + 0x83, 0x18, 0x48, 0x9b, 0x50, 0x9e, 0x7b, 0x96, 0x64, 0xc2, 0xd0, 0xc3, + 0x5f, 0x45, 0xff, 0x32, 0x9c, 0xe8, 0x17, 0x17, 0xbd, 0x78, 0xed, 0x75, + 0x98, 0x5f, 0x3a, 0x06, 0x08, 0x2b, 0x1c, 0x37, 0x9b, 0x46, 0x4a, 0x90, + 0x0a, 0xb0, 0xaf, 0x46, 0x92, 0x3e, 0x33, 0x89, 0x2a, 0xfa, 0xb8, 0xe7, + 0x32, 0x63, 0xf3, 0x23, 0xc7, 0x6e, 0xd2, 0x14, 0xfb, 0x26, 0x58, 0xee, + 0xfe, 0x06, 0x84, 0x54, 0xfa, 0xc1, 0x1f, 0x37, 0xaa, 0xdb, 0xd4, 0xec, + 0x56, 0x2a, 0xbf, 0x49, 0xbd, 0xcc, 0xeb, 0x02, 0xed, 0xc6, 0x4e, 0xfc, + 0xac, 0x19, 0xb5, 0x12, 0x35, 0x69, 0x15, 0x89, 0x17, 0x4d, 0xa3, 0x68, + 0xea, 0x6c, 0x1e, 0x29, 0x9a, 0x09, 0xf3, 0xce, 0x7a, 0x21, 0xc6, 0x09, + 0xd1, 0x19, 0xea, 0x8f, 0x30, 0x46, 0x69, 0x3b, 0x68, 0x04, 0x2b, 0x7c, + 0x8a, 0x2d, 0xd6, 0x63, 0x5d, 0xea, 0x6d, 0xd6, 0x39, 0x9e, 0xbd, 0x06, + 0x3e, 0x5b, 0xee, 0x2f, 0x11, 0x5b, 0x28, 0x6b, 0xa7, 0x52, 0xa4, 0x68, + 0x5e, 0x4c, 0xa4, 0xea, 0xae, 0xce, 0x23, 0xbf, 0x4c, 0x36, 0x71, 0xda, + 0x81, 0x45, 0x50, 0x8e, 0xca, 0x86, 0xce, 0xff, 0x53, 0xc3, 0xb8, 0x43, + 0xb3, 0x24, 0xee, 0x07, 0x7a, 0xa2, 0xb4, 0xfa, 0xc7, 0x0a, 0x1d, 0x7b, + 0xc6, 0x52, 0x35, 0x31, 0xec, 0x08, 0x1f, 0x84, 0x80, 0x92, 0x5b, 0xf8, + 0xb1, 0xda, 0x39, 0xd6, 0xc9, 0xe7, 0xe5, 0x89, 0x04, 0x7e, 0x51, 0x7f, + 0xf4, 0xe6, 0x6a, 0x64, 0x47, 0x49, 0xea, 0xf8, 0xec, 0xa6, 0xf6, 0xa0, + 0x43, 0x53, 0xfe, 0xda, 0xc3, 0x23, 0x24, 0xd8, 0x25, 0xda, 0x13, 0x2c, + 0x2a, 0xb7, 0x3f, 0x94, 0xde, 0x77, 0x1c, 0x4c, 0x78, 0x1c, 0x6a, 0xf9, + 0x9a, 0x8f, 0xeb, 0x6a, 0x15, 0x77, 0x77, 0xad, 0x49, 0x84, 0xce, 0x10, + 0x40, 0xc7, 0x99, 0x48, 0x0f, 0xd5, 0x96, 0x1e, 0x80, 0x9c, 0x73, 0xa1, + 0x38, 0xa1, 0x03, 0x6f, 0xd3, 0x4d, 0x20, 0xd0, 0xb5, 0x43, 0xe4, 0xf7, + 0x2e, 0x78, 0x0f, 0x4e, 0xf7, 0xbc, 0xbf, 0x65, 0xda, 0x6d, 0x90, 0x0b, + 0x5b, 0xbf, 0xde, 0xea, 0x27, 0x27, 0x99, 0x64, 0xf8, 0x39, 0x7c, 0x73, + 0x3d, 0xd6, 0x21, 0xd2, 0xee, 0xd6, 0xf3, 0x53, 0x11, 0x2e, 0x55, 0xc3, + 0xdc, 0xea, 0xf1, 0x29, 0x57, 0xde, 0x51, 0xa1, 0x78, 0x73, 0x90, 0x0b, + 0x2f, 0xf5, 0xc9, 0x75, 0x36, 0xeb, 0x8d, 0xd2, 0x6d, 0x8e, 0x79, 0x5d, + 0xba, 0x1a, 0x38, 0xff, 0xdf, 0x19, 0x01, 0xa8, 0xd2, 0xc8, 0xd1, 0xd6, + 0xf2, 0xeb, 0x8a, 0xf5, 0x2e, 0xd1, 0xcc, 0x93, 0x13, 0x9b, 0x9c, 0x90, + 0x78, 0x65, 0x63, 0x79, 0x04, 0xc4, 0xf1, 0x9e, 0x9f, 0x8c, 0x3a, 0xf3, + 0x64, 0x0c, 0xfe, 0x98, 0x1d, 0x93, 0xe2, 0x8f, 0x56, 0xa5, 0x63, 0x53, + 0x23, 0xb8, 0x6e, 0x73, 0x16, 0x45, 0x1a, 0xb6, 0xf7, 0x7b, 0x0f, 0xcd, + 0xa4, 0x32, 0xff, 0x5a, 0xfe, 0x96, 0x8d, 0xe1, 0x87, 0x78, 0xdb, 0x70, + 0x83, 0xa8, 0x24, 0x85, 0x69, 0x20, 0xc2, 0x6d, 0x12, 0x0d, 0xe5, 0x79, + 0xf6, 0x2a, 0x59, 0xcf, 0xd6, 0xab, 0xe7, 0x81, 0xe6, 0xa0, 0xb1, 0x88, + 0x2d, 0x08, 0x8c, 0x0b, 0xb1, 0xcf, 0xd7, 0x6c, 0x36, 0xaf, 0x9e, 0xf9, + 0x03, 0x67, 0xd9, 0x41, 0x73, 0xa9, 0xab, 0x45, 0xb8, 0x71, 0x60, 0x58, + 0x18, 0xd4, 0x16, 0x2c, 0x65, 0xba, 0xd1, 0x05, 0xde, 0x92, 0xc5, 0x50, + 0x10, 0x11, 0x90, 0xce, 0x47, 0xcc, 0xfb, 0xaf, 0xbf, 0x23, 0xc0, 0x9f, + 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x67, 0x30, 0x65, 0x30, 0x0e, + 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, + 0x01, 0x86, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, + 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, + 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x0b, 0xb3, 0x68, 0x3b, 0xaf, 0xda, + 0xaf, 0xee, 0x70, 0xa5, 0x76, 0xd9, 0x21, 0xf7, 0xcc, 0x44, 0x16, 0x07, + 0xd0, 0xf8, 0x30, 0x10, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, + 0x37, 0x15, 0x01, 0x04, 0x03, 0x02, 0x01, 0x00, 0x30, 0x11, 0x06, 0x03, + 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, + 0x1d, 0x20, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x0c, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, 0x00, 0x48, + 0xc7, 0x37, 0xff, 0xff, 0xc1, 0x68, 0x57, 0xd7, 0x8b, 0x43, 0x66, 0x46, + 0x3a, 0x26, 0x6b, 0x2f, 0xe8, 0xfa, 0xde, 0x68, 0xa1, 0x8f, 0x47, 0xf1, + 0x3d, 0x34, 0x95, 0x7a, 0xda, 0x55, 0x31, 0xf4, 0x95, 0xd2, 0x38, 0x5f, + 0x2c, 0xba, 0x8f, 0xa5, 0x8d, 0x51, 0x31, 0x6a, 0x89, 0x55, 0x68, 0x6c, + 0x2b, 0x42, 0x64, 0x6a, 0x85, 0x24, 0xa0, 0x51, 0x03, 0xc7, 0xdd, 0xd1, + 0x72, 0x58, 0xed, 0x6c, 0x1e, 0x8c, 0xd8, 0x91, 0xc5, 0xe7, 0x49, 0x11, + 0x9d, 0x19, 0x7a, 0x37, 0x58, 0x1e, 0x77, 0x44, 0xfb, 0xc2, 0x08, 0x98, + 0x42, 0xc4, 0x4d, 0xe3, 0x9b, 0x8a, 0x0e, 0xcf, 0x40, 0x45, 0x4f, 0x1b, + 0x80, 0x70, 0x59, 0x8c, 0x93, 0x81, 0xe8, 0x0f, 0xd5, 0xc8, 0x26, 0x95, + 0xa9, 0xf7, 0x1f, 0x77, 0x06, 0xb8, 0xca, 0xef, 0x9c, 0xfb, 0xe8, 0x66, + 0xda, 0xe5, 0x39, 0xe0, 0xd2, 0xd2, 0x62, 0xc3, 0xa7, 0xd4, 0xb6, 0x18, + 0x9a, 0x27, 0x9b, 0x26, 0x50, 0x4a, 0x72, 0x97, 0xd5, 0xb3, 0x5b, 0x2a, + 0xa4, 0xfd, 0x5f, 0x2f, 0x7e, 0xe6, 0x62, 0xa3, 0x27, 0x66, 0x0c, 0xfa, + 0xd9, 0x19, 0xcc, 0x11, 0x1d, 0x31, 0xa8, 0x01, 0x52, 0x08, 0xe6, 0x54, + 0x0c, 0x99, 0x63, 0x2b, 0xea, 0xd8, 0x84, 0xd4, 0xb4, 0x08, 0x16, 0xef, + 0xbe, 0x4a, 0x5b, 0x88, 0x58, 0xf4, 0x06, 0x16, 0xa0, 0xeb, 0x7a, 0x5d, + 0xe1, 0xc7, 0x44, 0xd6, 0xbb, 0x2f, 0x55, 0x56, 0x25, 0xf0, 0x9e, 0x0c, + 0xe4, 0x0f, 0x12, 0xdb, 0xc0, 0x7f, 0xaf, 0x56, 0x5d, 0xc6, 0x89, 0x0e, + 0x71, 0xa9, 0x56, 0x12, 0xe4, 0xb9, 0x9c, 0xa8, 0x64, 0x1e, 0xb5, 0x47, + 0x95, 0x92, 0xae, 0xd0, 0x70, 0xc8, 0x93, 0x7d, 0x7c, 0x5a, 0x58, 0xf1, + 0x05, 0xf1, 0x4a, 0xb8, 0x6c, 0x72, 0x18, 0xa9, 0xae, 0x1f, 0x57, 0x99, + 0x26, 0x74, 0x66, 0xf5, 0x1d, 0x0f, 0xdf, 0x5d, 0xf0, 0xe7, 0x37, 0x5b, + 0x5f, 0xba, 0xf0, 0xb4, 0xef, 0xe4, 0x63, 0x07, 0x7e, 0x1f, 0x32, 0x18, + 0x69, 0xa9, 0x70, 0x5a, 0x92, 0xf9, 0x79, 0x9c, 0x58, 0xd4, 0x7e, 0xbf, + 0x72, 0x5d, 0x53, 0x46, 0x2b, 0x6e, 0xa3, 0x99, 0x60, 0xd6, 0x85, 0x8c, + 0x66, 0x77, 0x16, 0x76, 0xaf, 0xe2, 0xc5, 0x18, 0x5b, 0xe2, 0x5d, 0x08, + 0x36, 0xd6, 0x66, 0x37, 0x17, 0x65, 0xf0, 0x2e, 0xcf, 0xa1, 0xe5, 0xbc, + 0xe6, 0x8d, 0x0d, 0x65, 0xb4, 0x56, 0x53, 0x5d, 0x9f, 0xc8, 0xaf, 0x4e, + 0x6e, 0x51, 0xcf, 0x88, 0xbe, 0x92, 0xea, 0x30, 0xfb, 0x2c, 0xe7, 0x75, + 0x3f, 0x42, 0x60, 0xc4, 0x71, 0xe7, 0x97, 0x9f, 0x73, 0xc7, 0x9f, 0xca, + 0xd1, 0xb8, 0x6c, 0x23, 0xea, 0x50, 0x28, 0x1d, 0x0e, 0x43, 0xcc, 0xf5, + 0xa9, 0x1b, 0x40, 0xeb, 0xa6, 0x98, 0xe5, 0xe5, 0x0f, 0xc5, 0x92, 0x2f, + 0xa5, 0x96, 0xc7, 0xd7, 0xfa, 0x3c, 0x18, 0xee, 0x1d, 0x1b, 0x61, 0x03, + 0xfd, 0x86, 0xe7, 0x24, 0x41, 0x33, 0xbd, 0xd8, 0xf3, 0xb6, 0x60, 0x7c, + 0xf3, 0x1c, 0x82, 0x03, 0xd5, 0x60, 0xaf, 0xdf, 0xf4, 0x20, 0xa4, 0xe4, + 0x81, 0x06, 0x22, 0x5a, 0xcc, 0x85, 0x33, 0x7d, 0x64, 0xf8, 0xe4, 0xb8, + 0xbf, 0x80, 0x17, 0xd4, 0xfb, 0x21, 0x3f, 0x63, 0xae, 0xe7, 0x8f, 0xb7, + 0x17, 0x44, 0xec, 0x72, 0x2e, 0x35, 0xc9, 0x0b, 0xd0, 0x81, 0x1d, 0xe9, + 0x72, 0x03, 0x09, 0x41, 0xd9, 0xdf, 0x09, 0x48, 0xe6, 0xcd, 0xb7, 0xb2, + 0x1c, 0x60, 0x25, 0x19, 0x52, 0xf3, 0x3d, 0x12, 0x49, 0xed, 0x9d, 0x94, + 0x22, 0x8e, 0x71, 0x28, 0xf8, 0xc1, 0x07, 0x54, 0x73, 0xdd, 0x38, 0x08, + 0xb4, 0x85, 0x8f, 0x14, 0x6c, 0xaa, 0x00, 0xaf, 0x40, 0xab, 0xb5, 0x87, + 0xce, 0xb6, 0x39, 0x5c, 0x73, 0xf9, 0x90, 0x18, 0x22, 0x82, 0x2f, 0x58, + 0x20, 0x42, 0x81, 0x07, 0x43, 0xe2, 0x72, 0x4c, 0x0e, 0x4b, 0xae, 0x91, + 0x2c, 0x8b, 0x65, 0xc6, 0xd5, 0x23, 0x36, 0xd7, 0x44, 0x76, 0x1d, 0xb1, + 0x85, 0x03, 0xc3, 0x54, 0xe6, 0xf3, 0xad, 0xe4, 0x57, 0x19, 0x01, 0x02, + 0x38, 0x2a, 0x19, 0x01, 0x03, 0x78, 0x1c, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x6e, 0x64, 0x2e, 0x69, + 0x6e, 0x2d, 0x74, 0x6f, 0x74, 0x6f, 0x2b, 0x6a, 0x73, 0x6f, 0x6e, 0xa0, + 0x58, 0x30, 0x93, 0x52, 0xac, 0x02, 0x8d, 0x83, 0x03, 0xb1, 0xf1, 0xa3, + 0x57, 0xdb, 0xda, 0x7b, 0x8b, 0x0f, 0x1a, 0xd8, 0x8f, 0xa8, 0xbb, 0x5b, + 0x31, 0xe6, 0xa4, 0x02, 0xcc, 0x27, 0x82, 0x79, 0x48, 0x94, 0x04, 0x87, + 0x9a, 0xc2, 0x0e, 0x12, 0xaf, 0xb2, 0x30, 0x0e, 0xd6, 0xb8, 0xd1, 0xb9, + 0x24, 0x30, 0x59, 0x01, 0x80, 0x4a, 0x62, 0x13, 0x03, 0xff, 0x92, 0xb3, + 0x2f, 0x03, 0xe3, 0x66, 0x88, 0xaa, 0x1b, 0x55, 0xe8, 0x2a, 0x42, 0x08, + 0x2d, 0x75, 0xe4, 0xcc, 0x8f, 0x22, 0xa9, 0xea, 0x32, 0x80, 0x73, 0x89, + 0xce, 0x0c, 0x3d, 0xc5, 0xe8, 0xad, 0x0b, 0xe2, 0x12, 0x9a, 0xee, 0x02, + 0x37, 0xa4, 0x5d, 0xfc, 0x63, 0x57, 0x6b, 0x38, 0x0e, 0xbb, 0xd7, 0x22, + 0x14, 0x00, 0x86, 0x1d, 0x59, 0x41, 0xa5, 0xe5, 0x41, 0xde, 0x7e, 0xb8, + 0x6d, 0x92, 0x62, 0x42, 0x7a, 0xc3, 0x0d, 0xe4, 0xcc, 0x20, 0x65, 0xcb, + 0x65, 0xa8, 0x76, 0x28, 0x62, 0xe2, 0xf6, 0xce, 0x48, 0x0e, 0x22, 0x9b, + 0x3f, 0xc7, 0x02, 0xcd, 0x3c, 0x31, 0xba, 0x09, 0xe3, 0xdb, 0xe9, 0x21, + 0xc9, 0x7f, 0x33, 0xb1, 0xa0, 0x25, 0x73, 0x78, 0x31, 0xd8, 0x00, 0x8d, + 0x7a, 0x67, 0x1c, 0x7a, 0x03, 0xf8, 0x26, 0x4d, 0xbe, 0x03, 0x8e, 0x1d, + 0x01, 0x80, 0x8e, 0x1e, 0x5a, 0x54, 0x53, 0x17, 0xdb, 0x5c, 0xc8, 0x60, + 0xb3, 0x3a, 0xe7, 0x85, 0xa0, 0xa4, 0x16, 0xb6, 0x6c, 0xfd, 0x75, 0xcc, + 0x15, 0x25, 0x38, 0xa6, 0x70, 0x62, 0xd0, 0x70, 0x00, 0xfe, 0x4e, 0x74, + 0x70, 0xc6, 0x6a, 0x52, 0xe0, 0x0d, 0x5c, 0x28, 0x3f, 0x82, 0x0e, 0x2b, + 0x53, 0x61, 0x26, 0x2c, 0x2f, 0x93, 0xbb, 0x9c, 0x22, 0x63, 0x69, 0xc8, + 0x6d, 0xd2, 0x79, 0xe7, 0x4b, 0x63, 0x97, 0xe6, 0x59, 0x7b, 0x71, 0x6d, + 0x21, 0xa8, 0xa9, 0x4d, 0x25, 0x84, 0x70, 0x3d, 0x03, 0x1e, 0x54, 0xac, + 0x8e, 0xdb, 0x96, 0xa1, 0x34, 0x4b, 0x80, 0xda, 0xa3, 0x11, 0x13, 0x69, + 0x98, 0x23, 0x34, 0xbe, 0x93, 0x89, 0x50, 0xa8, 0x79, 0x39, 0x5f, 0xf2, + 0x50, 0x21, 0xa7, 0x9a, 0x01, 0x8e, 0x43, 0x31, 0xe0, 0x26, 0x09, 0xe2, + 0x07, 0x97, 0x3c, 0xc7, 0x31, 0x04, 0x2b, 0x2c, 0x60, 0xa3, 0xed, 0x91, + 0xfc, 0xd7, 0xd0, 0x30, 0xa6, 0x56, 0xf6, 0x67, 0xb3, 0x6d, 0x7c, 0xa5, + 0x86, 0xd0, 0x09, 0x91, 0x1b, 0xfc, 0xf9, 0xcb, 0x15, 0x5a, 0x13, 0x6e, + 0xd3, 0x6b, 0x53, 0xaf, 0x91, 0x0f, 0xaa, 0xf4, 0x0b, 0xb1, 0x56, 0x0f, + 0xbc, 0x76, 0x5e, 0xe3, 0xe2, 0xd8, 0x19, 0xd7, 0xe5, 0x4d, 0xcd, 0xbb, + 0x0a, 0x02, 0x0a, 0x25, 0x3e, 0xf4, 0x6c, 0xd6, 0xc9, 0x6c, 0x31, 0x4f, + 0x5a, 0xd4, 0xb6, 0xa2, 0x3e, 0x15, 0x10, 0x93, 0x00, 0xb1, 0xa7, 0x32, + 0xfc, 0x1d, 0x79, 0x5a, 0x16, 0xf6, 0x8d, 0x87, 0xce, 0x06, 0xa0, 0xf4, + 0xe9, 0x18, 0x8c, 0xec, 0xef, 0xe0, 0x10, 0x1c, 0xaa, 0x1f, 0xe7, 0xa1, + 0x85, 0x16, 0x32, 0xf3, 0xae, 0x4b, 0x3c, 0xf9, 0xf4, 0xdb, 0x38, 0xd9, + 0xb3, 0xef, 0xca, 0x8d, 0x76, 0x6c, 0x94, 0x62, 0x45, 0x9f, 0x35, 0x5f, + 0xf9, 0xb5, 0xae, 0x50, 0x4f, +} diff --git a/app.go b/app.go index db5599c..4d57f20 100644 --- a/app.go +++ b/app.go @@ -69,6 +69,7 @@ func AddCommands(app *cli.App, ikwid bool) *cli.App { app.Commands = append(app.Commands, NewNodeScanCmd()) app.Commands = append(app.Commands, NewFindTrieEntriesCmd()) app.Commands = append(app.Commands, NewFindMMREntriesCmd()) + app.Commands = append(app.Commands, NewAppendCmd()) } return app } diff --git a/append.go b/append.go new file mode 100644 index 0000000..508fb6c --- /dev/null +++ b/append.go @@ -0,0 +1,443 @@ +package veracity + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/binary" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "time" + + "github.com/fxamacker/cbor/v2" + "github.com/veraison/go-cose" + + dtcose "github.com/datatrails/go-datatrails-common/cose" + "github.com/datatrails/go-datatrails-common/logger" + "github.com/datatrails/go-datatrails-merklelog/massifs" + "github.com/datatrails/go-datatrails-merklelog/massifs/snowflakeid" + "github.com/datatrails/go-datatrails-merklelog/mmr" + "github.com/urfave/cli/v2" +) + +// coseSigner implements IdentifiableCoseSigner +type identifiableCoseSigner struct { + innerSigner cose.Signer + publicKey ecdsa.PublicKey +} + +func (s *identifiableCoseSigner) Algorithm() cose.Algorithm { + return s.innerSigner.Algorithm() +} + +func (s *identifiableCoseSigner) Sign(rand io.Reader, content []byte) ([]byte, error) { + return s.innerSigner.Sign(rand, content) +} + +func (s *identifiableCoseSigner) LatestPublicKey() (*ecdsa.PublicKey, error) { + return &s.publicKey, nil +} + +func (s *identifiableCoseSigner) PublicKey(ctx context.Context, kid string) (*ecdsa.PublicKey, error) { + return &s.publicKey, nil +} + +func (s *identifiableCoseSigner) KeyLocation() string { + return "robinbryce.me" +} + +func (s *identifiableCoseSigner) KeyIdentifier() string { + + // the returned kid needs to match the kid format of the keyvault key + return "location:robinbryce/version1" +} + +// NewAppendCmd appends an entry to a local ledger, optionally sealing it with a provided private key. +func NewAppendCmd() *cli.Command { + return &cli.Command{Name: "append", + Usage: "add an entry to a local ledger, optionally sealing it with a provided private key", + Flags: []cli.Flag{ + &cli.Uint64Flag{ + Name: "mmrindex", Aliases: []string{"i"}, + }, + &cli.Int64Flag{ + Name: "massif", Aliases: []string{"m"}, + Usage: "allow inspection of an arbitrary mmr index by explicitly specifying a massif index", + Value: -1, + }, + &cli.StringFlag{ + Name: "sealer-key", + Usage: "the sealer key to use for signing the entry, in cose .cbor. Only P-256, ES256 is supported. If --generate-sealer-key is set, this generated key will be written to this file.", + }, + &cli.StringFlag{ + Name: "trusted-sealer-key-pem", Aliases: []string{"s"}, + Usage: "verify the current seal using this pem file based public key", + }, + + &cli.StringFlag{ + Name: "receipt-file", Aliases: []string{"f"}, + Usage: "file name to write the receipt to, defaults to 'receipt-{mmrIndex}.cbor'", + }, + + &cli.BoolFlag{ + Name: "generate-sealer-key", Aliases: []string{"g"}, + Usage: "generate a new sealer key and write it to the sealer-key file. If the sealer-key file already exists, it will be overwritten. the default file name is 'ecdsa-key-private.cbor'.", + }, + }, + Action: func(cCtx *cli.Context) error { + var err error + + if !cCtx.IsSet("data-local") { + return errors.New("this command supports local replicas only, and requires --data-local") + } + + idState, err := snowflakeid.NewIDState(snowflakeid.Config{ + CommitmentEpoch: 1, + WorkerCIDR: "0.0.0.0/16", + PodIP: "10.0.0.1", + }) + if err != nil { + return fmt.Errorf("failed to create snowflake id state: %w", err) + } + + idTimestamp, err := idState.NextID() + if err != nil { + return fmt.Errorf("failed to generate snowflake id: %w", err) + } + + hasher := sha256.New() + hasher.Write(AmourySignedStatement) + // Take the first 24 bytes of the hash as the extra bytes + statementHash := hasher.Sum(nil) + extraBytes := statementHash[:expectedExtraBytesSize] + leafHash, err := mmrEntryVersion1(extraBytes, idTimestamp, AmourySignedStatement) + if err != nil { + return fmt.Errorf("failed to create mmr entry: %w", err) + } + fmt.Printf("%x statement-hash\n", statementHash) + fmt.Printf("%x leaf-hash\n", leafHash) + + cmd := &CmdCtx{} + + if err = cfgMassifReader(cmd, cCtx); err != nil { + return err + } + tenant := cCtx.String("tenant") + if tenant == "" { + fmt.Println("a tenant is required") + return nil + } + + if cmd.cborCodec, err = massifs.NewRootSignerCodec(); err != nil { + return err + } + + cache, err := massifs.NewLogDirCache(cmd.log, NewFileOpener()) + if err != nil { + return err + } + reader, err := massifs.NewLocalReader(logger.Sugar, cache) + if err != nil { + return err + } + + opts := []massifs.DirCacheOption{ + // massifs.WithDirCacheReplicaDir(cCtx.String("replicadir")), + // massifs.WithDirCacheReplicaDir(cCtx.String("data-local")), + massifs.WithDirCacheMassifLister(NewDirLister()), + massifs.WithDirCacheSealLister(NewDirLister()), + massifs.WithReaderOption(massifs.WithMassifHeight(uint8(cmd.massifHeight))), + massifs.WithReaderOption(massifs.WithSealGetter(&reader)), + massifs.WithReaderOption(massifs.WithCBORCodec(cmd.cborCodec)), + } + + // This will require that the remote seal is signed by the key + // provided here. If it is not, even if the seal is valid, the + // verification will fail with a suitable error. + pemString := cCtx.String("trusted-sealer-key-pem") + if pemString != "" { + pem, err := DecodeECDSAPublicString(pemString) + if err != nil { + return err + } + opts = append(opts, massifs.WithReaderOption(massifs.WithTrustedSealerPub(pem))) + } + + // For the localreader, the seal getter is the local reader itself. + // So we need to make use of ReplaceOptions on the cache, so we can + // provide the options after we have created the local reader. + cache.ReplaceOptions(opts...) + + verified, err := reader.GetHeadVerifiedContext(context.Background(), tenant) + if err != nil { + return err + } + + mmrSizeLast := verified.RangeCount() + fmt.Printf("%8d verified-size\n", mmrSizeLast) + verified.Tags = map[string]string{} + mmrIndex, err := verified.AddHashedLeaf(sha256.New(), idTimestamp, extraBytes, leafHash, []byte("scitt"), leafHash) + if err != nil { + return fmt.Errorf("failed to add hashed leaf: %w", err) + } + fmt.Printf("%8d mmrindex\n", mmrIndex) + // verified.CommitContext() + + var sealingKey *ecdsa.PrivateKey + if cCtx.IsSet("sealer-key") && !cCtx.Bool("generate-sealer-key") { + sealerKeyFile := cCtx.String("sealer-key") + if sealerKeyFile == "" { + return errors.New("sealer-key file is required") + } + sealingKey, err = ReadECDSAPrivateCose(sealerKeyFile, "P-256") + if err != nil { + return fmt.Errorf("failed to load sealer key from file %s: %w", sealerKeyFile, err) + } + } + if cCtx.IsSet("sealer-key-pem") && !cCtx.Bool("generate-sealer-key") { + if cCtx.IsSet("sealer-key") { + fmt.Printf("verifying with sealer-key-pem %s (in preference to sealer-key)", cCtx.String("sealer-key-pem")) + } + sealerKeyFile := cCtx.String("sealer-key-pem") + if sealerKeyFile == "" { + return errors.New("sealer-key file is required") + } + sealingKey, err = ReadECDSAPrivatePEM(sealerKeyFile) + if err != nil { + return fmt.Errorf("failed to load sealer key from file %s: %w", sealerKeyFile, err) + } + } + + if cCtx.Bool("generate-sealer-key") { + sealingKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + } + + alg, err := dtcose.CoseAlgForEC(sealingKey.PublicKey) + if err != nil { + return err + } + + coseSigner, err := cose.NewSigner(alg, sealingKey) + if err != nil { + return err + } + identifiableSigner := &identifiableCoseSigner{ + innerSigner: coseSigner, + publicKey: sealingKey.PublicKey, + } + + rootSigner := massifs.NewRootSigner("https://github.com/robinbryce/veracity", cmd.cborCodec) + + // TODO: account for filling a massif + mmrSizeCurrent := verified.RangeCount() + cp, err := mmr.IndexConsistencyProof(&verified.MassifContext, verified.MMRState.MMRSize-1, mmrSizeCurrent-1) + ok, peaksB, err := mmr.CheckConsistency( + verified, sha256.New(), + cp.MMRSizeA, cp.MMRSizeB, verified.MMRState.Peaks) + if !ok { + return fmt.Errorf("consistency check failed: verifie failed") + } + if err != nil { + return err + } + lastIdTimestamp := verified.GetLastIdTimestamp() + + state := massifs.MMRState{ + Version: int(massifs.MMRStateVersionCurrent), + MMRSize: mmrSizeCurrent, + Peaks: peaksB, + Timestamp: time.Now().UnixMilli(), + CommitmentEpoch: verified.MMRState.CommitmentEpoch, + IDTimestamp: lastIdTimestamp, + } + subject := massifs.TenantMassifBlobPath(tenant, uint64(verified.Start.MassifIndex)) + publicKey, err := identifiableSigner.LatestPublicKey() + if err != nil { + return fmt.Errorf("unable to get public key for signing key %w", err) + } + + keyIdentifier := identifiableSigner.KeyIdentifier() + data, err := rootSigner.Sign1(coseSigner, keyIdentifier, publicKey, subject, state, nil) + if err != nil { + return err + } + + // note that state is not verified here, but we just signed it so it is our droid + msg, state, err := massifs.DecodeSignedRoot(cmd.cborCodec, data) + if err != nil { + return err + } + + // this is a slightly tweaked variant of massifs.NewReceipt (go-datatrails-merklelog/massifs/mmriver.go) + + // because we added a single leaf, the size pre-add is the mmrIndex of the next leaf. + newStatementMMRIndex := verified.MMRState.MMRSize + proof, err := mmr.InclusionProof(&verified.MassifContext, state.MMRSize-1, newStatementMMRIndex) + if err != nil { + return fmt.Errorf( + "failed to generating inclusion proof: %d in MMR(%d), %v", + newStatementMMRIndex, verified.MMRState.MMRSize, err) + } + + peakIndex := mmr.PeakIndex(mmr.LeafCount(state.MMRSize), len(proof)) + // NOTE: The old-accumulator compatibility property, from + // https://eprint.iacr.org/2015/718.pdf, along with the COSE protected & + // unprotected buckets, is why we can just pre sign the receipts. + // As long as the receipt consumer is convinced of the logs consistency (not split view), + // it does not matter which accumulator state the receipt is signed against. + + var peaksHeader massifs.MMRStateReceipts + err = cbor.Unmarshal(msg.Headers.RawUnprotected, &peaksHeader) + if err != nil { + return fmt.Errorf( + "%w: failed decoding peaks header", err) + } + if peakIndex >= len(peaksHeader.PeakReceipts) { + return fmt.Errorf( + "%w: peaks header contains to few peak receipts", err) + } + + // This is an array of marshaled COSE_Sign1's + receiptMsg := peaksHeader.PeakReceipts[peakIndex] + signed, err := dtcose.NewCoseSign1MessageFromCBOR( + receiptMsg, dtcose.WithDecOptions(massifs.CheckpointDecOptions())) + if err != nil { + return fmt.Errorf( + "%w: failed to decode pre-signed receipt for: %d in MMR(%d)", + err, mmrIndex, state.MMRSize) + } + + // signed.Headers.RawProtected = nil + signed.Headers.RawUnprotected = nil + + verifiableProofs := massifs.MMRiverVerifiableProofs{ + InclusionProofs: []massifs.MMRiverInclusionProof{{ + Index: mmrIndex, + InclusionPath: proof}}, + } + + signed.Headers.Unprotected[massifs.VDSCoseReceiptProofsTag] = verifiableProofs + + receiptCbor, err := signed.MarshalCBOR() + if err != nil { + return fmt.Errorf("failed to marshal receipt: %w", err) + } + + receiptFileName := cCtx.String("receipt-file") + if receiptFileName == "" { + receiptFileName = fmt.Sprintf("receipt-%d.cbor", newStatementMMRIndex) + } + if err := os.WriteFile(receiptFileName, receiptCbor, os.FileMode(0644)); err != nil { + return fmt.Errorf("failed to write receipt file %s: %w", receiptFileName, err) + } + fmt.Printf("wrote receipt file %s\n", receiptFileName) + + forkFileName := filepath.Join(".", fmt.Sprintf("fork-%d-%d.bin", verified.MMRState.MMRSize-1, mmrSizeCurrent)) + if err := os.WriteFile(forkFileName, data, os.FileMode(0644)); err != nil { + return fmt.Errorf("failed to write log fork file %s: %w", forkFileName, err) + } + fmt.Printf("wrote forked log massif file %s\n", forkFileName) + + checkpointFileName := filepath.Join(".", fmt.Sprintf("checkpoint-%d.cbor", mmrSizeCurrent)) + if err := os.WriteFile(checkpointFileName, data, os.FileMode(0644)); err != nil { + return fmt.Errorf("failed to write checkpoint file %s: %w", checkpointFileName, err) + } + fmt.Printf("wrote checkpoint file %s\n", checkpointFileName) + if cCtx.Bool("generate-sealer-key") { + // write the sealer key to the sealer-key file + sealerKeyFile := cCtx.String("sealer-key") + if sealerKeyFile == "" { + sealerKeyFile = ECDSAPrivateDefaultFileName + } + if _, err := WriteECDSAPrivateCOSE(sealerKeyFile, sealingKey); err != nil { + return fmt.Errorf("failed to write sealer key to file %s: %w", sealerKeyFile, err) + } + fmt.Printf("wrote sealer key to file %s\n", sealerKeyFile) + sealerKeyFile = cCtx.String("sealer-key-pem") + if sealerKeyFile == "" { + sealerKeyFile = ECDSAPrivateDefaultPEMFileName + } + if err := WriteECDSAPrivatePEM(sealerKeyFile, sealingKey); err != nil { + return fmt.Errorf("failed to write sealer key to file %s: %w", sealerKeyFile, err) + } + fmt.Printf("wrote sealer key to file %s\n", sealerKeyFile) + if _, err := writeCoseECDSAPublicKey(sealerKeyFile, &sealingKey.PublicKey); err != nil { + return fmt.Errorf("failed to write sealer key to file %s: %w", sealerKeyFile, err) + } + fmt.Printf("wrote sealer key to file %s\n", sealerKeyFile) + + } + return nil + }, + } +} + +const ( + expectedExtraBytesSize = 24 +) + +// mmrEntryVersion1 gets the mmr entry for log entry version 1. +// +// mmr entry format for log entry version 1: +// +// H( domain | mmrSalt | serializedBytes ) +// +// where mmrSalt = extraBytes + idtimestamp +// +// NOTE: extraBytes is consistently 24 bytes on the trie value, so we pad/truncate extrabytes here +// to ensure its 24 bytes also. This allows greater consistency and ease of moving between mmrSalt and trieValue +func mmrEntryVersion1(extraBytes []byte, idtimestamp uint64, serializedBytes []byte) ([]byte, error) { + + hasher := sha256.New() + + // domain + hasher.Write([]byte{byte(LeafTypePlain)}) + + // mmrSalt + + // ensure extrabytes is 24 bytes long + extraBytes, err := consistentExtraBytesSize(extraBytes) + if err != nil { + return nil, err + } + hasher.Write(extraBytes) + + // convert idtimestamp to bytes + idTimestampBytes := make([]byte, 8) + binary.BigEndian.PutUint64(idTimestampBytes, idtimestamp) + hasher.Write(idTimestampBytes) + + // serializedBytes + hasher.Write(serializedBytes) + + return hasher.Sum(nil), nil +} + +// consistentExtraBytesSize ensures the given extraBytes is padded/truncated to exactly 24 bytes +func consistentExtraBytesSize(extraBytes []byte) ([]byte, error) { + + extraBytesSize := len(extraBytes) + + // larger size need to truncate + if extraBytesSize > expectedExtraBytesSize { + return nil, errors.New("extra bytes is too large, maximum extra bytes size is 24") + } + + // smaller size need to pad + if extraBytesSize < expectedExtraBytesSize { + tmp := make([]byte, expectedExtraBytesSize) + copy(tmp[:extraBytesSize], extraBytes) + return tmp, nil + } + + // goldilocks just right + return extraBytes, nil +} diff --git a/cfgmassif.go b/cfgmassif.go index 07bb63b..20c8229 100644 --- a/cfgmassif.go +++ b/cfgmassif.go @@ -58,7 +58,8 @@ func cfgMassifReader(cmd *CmdCtx, cCtx *cli.Context) error { cache, err := massifs.NewLogDirCache( logger.Sugar, NewFileOpener(), - massifs.WithDirCacheTenant(cCtx.String("tenant")), // may be empty string + massifs.WithDirCacheTenant(cCtx.String("tenant")), + // massifs.WithExplicitFilePaths(cCtx.String("tenant")), // may be empty string massifs.WithDirCacheMassifLister(NewDirLister()), massifs.WithDirCacheSealLister(NewDirLister()), massifs.WithReaderOption(massifs.WithMassifHeight(cmd.massifHeight)), diff --git a/cfgreader.go b/cfgreader.go index f62bf27..53080d2 100644 --- a/cfgreader.go +++ b/cfgreader.go @@ -80,7 +80,7 @@ func cfgReader(cmd *CmdCtx, cCtx *cli.Context, forceProdUrl bool) (azblob.Reader } cmd.readerURL = url - reader, err = azblob.NewReaderNoAuth(url, azblob.WithContainer(container), azblob.WithAccountName(account)) + reader, err = azblob.NewReaderNoAuth(cmd.log, url, azblob.WithContainer(container), azblob.WithAccountName(account)) if err != nil { return nil, fmt.Errorf("failed to connect to blob store: %v", err) } diff --git a/cmd/veracity/main.go b/cmd/veracity/main.go index c628338..6962340 100644 --- a/cmd/veracity/main.go +++ b/cmd/veracity/main.go @@ -4,7 +4,8 @@ import ( "fmt" "log" "os" - "strings" + + // "strings" "github.com/datatrails/veracity" ) @@ -27,11 +28,11 @@ func main() { versionString = fmt.Sprintf("%s %s", version, commit) } - ikwid := false - envikwid := os.Getenv("VERACITY_IKWID") - if envikwid == "1" || strings.ToLower(envikwid) == "true" { - ikwid = true - } + ikwid := true + // envikwid := os.Getenv("VERACITY_IKWID") + // if envikwid == "1" || strings.ToLower(envikwid) == "true" { + // ikwid = true + // } app := veracity.NewApp(versionString, ikwid) veracity.AddCommands(app, ikwid) if err := app.Run(os.Args); err != nil { diff --git a/diag.go b/diag.go index abd60ad..e3bfd35 100644 --- a/diag.go +++ b/diag.go @@ -65,7 +65,7 @@ func NewDiagCmd() *cli.Command { return err } tenant := cCtx.String("tenant") - if tenant == "" { + if tenant == "" && !cCtx.IsSet("data-local") { fmt.Println("a tenant is required to get diagnostics that require reading a blob") return nil } diff --git a/ecdsareadwrite.go b/ecdsareadwrite.go new file mode 100644 index 0000000..e349037 --- /dev/null +++ b/ecdsareadwrite.go @@ -0,0 +1,268 @@ +package veracity + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "math/big" + "os" + + dtcose "github.com/datatrails/go-datatrails-common/cose" + "github.com/fxamacker/cbor/v2" +) + +const ( + ECDSAPublicDefaultPEMFileName = "ecdsa-key-public.pem" + ECDSAPrivateDefaultPEMFileName = "ecdsa-key-private.pem" + ECDSAPublicDefaultFileName = "ecdsa-key-public.cbor" + ECDSAPrivateDefaultFileName = "ecdsa-key-private.cbor" + ECDSAPrivateDefaultPerm = 0600 // Default permission for private key file + ECDSAPublicDefaultPerm = 0644 // Default permission for private key file +) + +func ReadECDSAPublicCose( + fileName string, + expectedStandardCurve ...string, +) (*ecdsa.PublicKey, error) { + // Read the public key from the default file + data, err := os.ReadFile(fileName) + if err != nil { + return nil, fmt.Errorf("failed to read public key file: %w", err) + } + + publicKey, err := decodeECDSAPublicKey(data) + if err != nil { + return nil, fmt.Errorf("failed to decode public key: %w", err) + } + + if len(expectedStandardCurve) > 0 && + publicKey.Params().Name != expectedStandardCurve[0] { + return nil, fmt.Errorf("expected ECDSA public key with curve %s, got %s", + expectedStandardCurve[0], publicKey.Curve.Params().Name) + } + + return publicKey, nil +} + +func ReadECDSAPrivateCose( + fileName string, + expectedStandardCurve ...string, +) (*ecdsa.PrivateKey, error) { + // Read the private key from the default file + data, err := os.ReadFile(fileName) + if err != nil { + return nil, fmt.Errorf("failed to read private key file: %w", err) + } + privateKey, err := decodeECDSAPrivateKey(data, expectedStandardCurve...) + if err != nil { + return nil, fmt.Errorf("failed to decode private key: %w", err) + } + if len(expectedStandardCurve) > 0 && + privateKey.PublicKey.Params().Name != expectedStandardCurve[0] { + return nil, fmt.Errorf("expected ECDSA private key with curve %s, got %s", + expectedStandardCurve[0], privateKey.PublicKey.Curve.Params().Name) + } + return privateKey, nil +} + +func ReadECDSAPrivatePEM(filePath string) (*ecdsa.PrivateKey, error) { + pemData, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + + block, _ := pem.Decode(pemData) + if block == nil || block.Type != "EC PRIVATE KEY" { + return nil, errors.New("invalid PEM block or type") + } + + key, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + return key, nil +} + +// Serializes the key to PEM format +func encodeECDSAPrivateKeyToPEM(key *ecdsa.PrivateKey) ([]byte, error) { + der, err := x509.MarshalECPrivateKey(key) + if err != nil { + return nil, err + } + block := &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: der, + } + return pem.EncodeToMemory(block), nil +} + +// Writes PEM to a file with 0600 permissions +func WriteECDSAPrivatePEM(pemFile string, key *ecdsa.PrivateKey) error { + pemBytes, err := encodeECDSAPrivateKeyToPEM(key) + if err != nil { + return fmt.Errorf("PEM encoding failed: %w", err) + } + return os.WriteFile(pemFile, pemBytes, 0600) +} + +func WriteECDSAPublicCOSE( + pubFile string, + publicKey *ecdsa.PublicKey, +) (string, error) { + + var err error + + if _, err = writeCoseECDSAPublicKey(pubFile, publicKey); err != nil { + return "", err + } + return pubFile, nil +} + +func WriteECDSAPrivateCOSE( + privFile string, + privateKey *ecdsa.PrivateKey, +) (string, error) { + + var err error + + if _, err = writeCoseECDSAPrivateKey(privFile, privateKey); err != nil { + return "", err + } + return privFile, nil +} + +// Encode private key to COSE_Key format (as CBOR bytes) +func encodePrivateKeyToCOSE(key *ecdsa.PrivateKey) ([]byte, error) { + m := map[int]interface{}{ + dtcose.KeyTypeLabel: int64(dtcose.KeyTypeEC2), + dtcose.AlgorithmLabel: -7, // ES256 (ECDSA w/ SHA-256) + dtcose.ECCurveLabel: 1, // P-256 + dtcose.ECXLabel: key.PublicKey.X.Bytes(), + dtcose.ECYLabel: key.PublicKey.Y.Bytes(), + dtcose.ECDLabel: key.D.Bytes(), + } + return cbor.Marshal(m) +} + +// Encode public key to COSE_Key format (as CBOR bytes) +func encodePublicKeyToCOSE(key *ecdsa.PublicKey) ([]byte, error) { + m := map[int]interface{}{ + dtcose.KeyTypeLabel: int64(dtcose.KeyTypeEC2), + dtcose.AlgorithmLabel: -7, // ES256 (ECDSA w/ SHA-256) + dtcose.ECCurveLabel: 1, // P-256 + dtcose.ECXLabel: key.X.Bytes(), + dtcose.ECYLabel: key.Y.Bytes(), + } + return cbor.Marshal(m) +} + +func decodeECDSAPrivateKey( + data []byte, + expectedStandardCurve ...string, +) (*ecdsa.PrivateKey, error) { + var m map[int64]interface{} + if err := cbor.Unmarshal(data, &m); err != nil { + return nil, err + } + publicKey, err := decodeECDSAPublicKeyFromMap(m, expectedStandardCurve...) + if err != nil { + return nil, fmt.Errorf("failed to decode public key from map: %w", err) + } + + d := big.NewInt(0) + d.SetBytes(m[dtcose.ECDLabel].([]byte)) + + privateKey := &ecdsa.PrivateKey{ + PublicKey: *publicKey, + D: d, + } + return privateKey, nil +} + +func decodeECDSAPublicKey( + data []byte, + expectedStandardCurve ...string, +) (*ecdsa.PublicKey, error) { + + var m map[int64]interface{} + if err := cbor.Unmarshal(data, &m); err != nil { + return nil, err + } + return decodeECDSAPublicKeyFromMap(m, expectedStandardCurve...) +} + +func decodeECDSAPublicKeyFromMap( + m map[int64]interface{}, + expectedStandardCurve ...string, +) (*ecdsa.PublicKey, error) { + + ecKey, err := dtcose.NewECCoseKey(m) + genericKey, err := ecKey.PublicKey() + if err != nil { + return nil, fmt.Errorf("failed to get public key from COSE key: %w", err) + } + + publicKey, ok := genericKey.(*ecdsa.PublicKey) + if !ok { + return nil, fmt.Errorf("expected ECDSA public key, got %T", genericKey) + } + + if len(expectedStandardCurve) > 0 && + publicKey.Params().Name != expectedStandardCurve[0] { + return nil, fmt.Errorf("expected ECDSA public key with curve %s, got %s", + expectedStandardCurve[0], publicKey.Curve.Params().Name) + } + + return publicKey, nil +} + +func writeCoseECDSAPrivateKey( + fileName string, + privateKey *ecdsa.PrivateKey, + perms ...os.FileMode, +) ([]byte, error) { + + var err error + var data []byte + if data, err = encodePrivateKeyToCOSE(privateKey); err != nil { + return nil, err + } + + perm := os.FileMode(ECDSAPrivateDefaultPerm) // Default permission + if len(perms) > 0 { + perm = perms[0] + } + + // Save to file + if err := os.WriteFile(fileName, data, perm); err != nil { + return nil, err + } + return data, nil +} + +func writeCoseECDSAPublicKey( + fileName string, + publicKey *ecdsa.PublicKey, + perms ...os.FileMode, +) ([]byte, error) { + + var err error + var data []byte + if data, err = encodePublicKeyToCOSE(publicKey); err != nil { + return nil, err + } + + perm := os.FileMode(ECDSAPublicDefaultPerm) // Default permission + if len(perms) > 0 { + perm = perms[0] + } + + // Save to file + if err := os.WriteFile(fileName, data, perm); err != nil { + return nil, err + } + return data, nil +} diff --git a/go.mod b/go.mod index de9b167..5f6c31f 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,14 @@ module github.com/datatrails/veracity -go 1.23.0 +go 1.24.0 require ( - github.com/datatrails/go-datatrails-common v0.26.0 - github.com/datatrails/go-datatrails-common-api-gen v0.6.6 + github.com/datatrails/go-datatrails-common v0.30.0 + github.com/datatrails/go-datatrails-common-api-gen v0.8.0 github.com/datatrails/go-datatrails-logverification v0.4.3 - github.com/datatrails/go-datatrails-merklelog/massifs v0.4.0 - github.com/datatrails/go-datatrails-merklelog/mmr v0.2.0 - github.com/datatrails/go-datatrails-merklelog/mmrtesting v0.2.0 + github.com/datatrails/go-datatrails-merklelog/massifs v0.6.0 + github.com/datatrails/go-datatrails-merklelog/mmr v0.4.0 + github.com/datatrails/go-datatrails-merklelog/mmrtesting v0.4.0 github.com/datatrails/go-datatrails-serialization/eventsv1 v0.0.3 github.com/datatrails/go-datatrails-simplehash v0.0.5 github.com/gosuri/uiprogress v0.0.1 @@ -17,15 +17,20 @@ require ( golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 ) -// replace ( -// github.com/datatrails/go-datatrails-merklelog/massifs => ../go-datatrails-merklelog/massifs -// ) +replace ( + github.com/datatrails/go-datatrails-logverification => ../go-datatrails-logverification + github.com/datatrails/go-datatrails-merklelog/massifs => ../go-datatrails-merklelog/massifs + github.com/datatrails/go-datatrails-merklelog/mmr => ../go-datatrails-merklelog/mmr + github.com/datatrails/go-datatrails-merklelog/mmrtesting => ../go-datatrails-merklelog/mmrtesting + github.com/datatrails/go-datatrails-serialization/eventsv1 => ../go-datatrails-serialization/eventsv1 + github.com/datatrails/go-datatrails-simplehash => ../go-datatrails-simplehash +) require ( - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect - github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/gosuri/uilive v0.0.4 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -37,9 +42,7 @@ require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1 - github.com/Azure/go-amqp v1.0.5 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect @@ -53,33 +56,28 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dimchansky/utfbom v1.1.1 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/ldclabs/cose/go v0.0.0-20221214142927-d22c1cfc2154 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect - github.com/opentracing-contrib/go-stdlib v1.0.0 // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/openzipkin-contrib/zipkin-go-opentracing v0.5.0 // indirect - github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/stretchr/testify v1.10.0 - github.com/veraison/go-cose v1.1.0 // indirect + github.com/veraison/go-cose v1.1.0 github.com/x448/float16 v0.8.4 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect - google.golang.org/grpc v1.69.0-dev // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.71.1 // indirect google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 295dbf3..d400d89 100644 --- a/go.sum +++ b/go.sum @@ -2,16 +2,14 @@ github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1 h1:o/Ws6bEqMeKZUfj1RRm3mQ51O8JGU5w+Qdg2AhHib6A= -github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1/go.mod h1:6QAMYBAbQeeKX+REFJMZ1nFWu9XLw/PPcjYpuc9RDFs= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1 h1:QSdcrd/UFJv6Bp/CfoVf2SrENpFn9P6Yh8yb+xNhYMM= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1/go.mod h1:eZ4g6GUvXiGulfIbbhh1Xr4XwUYaYaWMqzGD/284wCA= -github.com/Azure/go-amqp v1.0.5 h1:po5+ljlcNSU8xtapHTe8gIc8yHxCzC03E8afH2g1ftU= -github.com/Azure/go-amqp v1.0.5/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= @@ -38,49 +36,43 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/datatrails/go-datatrails-common v0.26.0 h1:Ga8lWKnA57VDFStrrO9OR394jMvV7S9Ia92m/7tGehs= -github.com/datatrails/go-datatrails-common v0.26.0/go.mod h1:k/ub6fdHldXZ129gzxDZI0aifi/qyFyKlU2P5bAASBM= -github.com/datatrails/go-datatrails-common-api-gen v0.6.6 h1:Qbfnte1+ZQsi0XzbfmOuk/xziqbPyEC4nyl7SsQdGdg= -github.com/datatrails/go-datatrails-common-api-gen v0.6.6/go.mod h1:rTMGdMdu5M6mGpbXZy1D84cBTGE8JwsDH6BYh9LJlmA= -github.com/datatrails/go-datatrails-logverification v0.4.3 h1:E+VKGudFanjbdGFLjB6L+r/9hHEOVrutplB/uMS+hs0= -github.com/datatrails/go-datatrails-logverification v0.4.3/go.mod h1:pWS2YhTuQJH0F/OgufihhM49gbck2BeWDo5Fll4JYqk= -github.com/datatrails/go-datatrails-merklelog/massifs v0.4.0 h1:j0mPW+sJruxGD+L9x59zu4muCWcNQIHtGYFDw6ZWolw= -github.com/datatrails/go-datatrails-merklelog/massifs v0.4.0/go.mod h1:9PzDUZzIMSLWcf5iv0AbzYOz6IhIBlHMXPiU5S1mb00= -github.com/datatrails/go-datatrails-merklelog/mmr v0.2.0 h1:NUP0OUVixuyWf+Gmi/e3wS5JD4za7DU0gdWVAgqnI5c= -github.com/datatrails/go-datatrails-merklelog/mmr v0.2.0/go.mod h1:iLipg39Ce3U68NjXFxjxwxXR9U0T6Dm6pldJA47Lx8s= -github.com/datatrails/go-datatrails-merklelog/mmrtesting v0.2.0 h1:cv8JincUm3h/4hyVcuofPt5pAtOZ+KYLnsDdvQ3D6Lc= -github.com/datatrails/go-datatrails-merklelog/mmrtesting v0.2.0/go.mod h1:h8b1O0xAoMv2DsVQuo6vNyM4RLL2DlJCWJqgysX127w= -github.com/datatrails/go-datatrails-serialization/eventsv1 v0.0.3 h1:BLHfCXjzXUgr1knXE9XtZC+jNnf2orGEL+BTAWqSyp4= -github.com/datatrails/go-datatrails-serialization/eventsv1 v0.0.3/go.mod h1:9i6Tip2lIXwSZ3SxP7XEhU2eQ9zkpxhEBmPmlOGqv/8= -github.com/datatrails/go-datatrails-simplehash v0.0.5 h1:igu4QRYO87RQXrJlqSm3fgMA2Q0F4jglWqBlfvKrXKQ= -github.com/datatrails/go-datatrails-simplehash v0.0.5/go.mod h1:XuOwViwdL+dyz7fGYIjaByS1ElMFsrVI0goKX0bNimA= +github.com/datatrails/go-datatrails-common v0.30.0 h1:QA95OPWe/UiqjGVdbMTzlOohgVYzbIY9X5KzDT2bohc= +github.com/datatrails/go-datatrails-common v0.30.0/go.mod h1:kfRSYLC/AUhDd7XOK2Do+13HbBKh2NU6Mw83K7zwaxs= +github.com/datatrails/go-datatrails-common-api-gen v0.8.0 h1:vO+s0h1SZMQv89240Fxok/vsJU4Oo/jHO5rbOVnj/pA= +github.com/datatrails/go-datatrails-common-api-gen v0.8.0/go.mod h1:ekmas39HNTCa011DG54jaKTVFh9XpAYZqITE414bD9U= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= -github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= -github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -93,8 +85,8 @@ github.com/gosuri/uiprogress v0.0.1 h1:0kpv/XY/qTmFWl/SkaJykZXrBBzwwadmW8fRb7RJS github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -107,24 +99,15 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= -github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.5.0 h1:uhcF5Jd7rP9DVEL10Siffyepr6SvlKbUsjH5JpNCRi8= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.5.0/go.mod h1:+oCZ5GXXr7KPI/DNOQORPTq5AWHfALJj9c72b0+YsEY= -github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= -github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= +github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -149,6 +132,18 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBi github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/bencode v1.0.0 h1:zgop0Wu1nu4IexAZeCZ5qbsjU4O1vMrfCrVgUjbHVuA= github.com/zeebo/bencode v1.0.0/go.mod h1:Ct7CkrWIQuLWAy9M3atFHYq4kG9Ao/SsY5cdtCXmp9Y= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -160,8 +155,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -172,8 +167,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -188,8 +183,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -202,19 +197,19 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= -google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/grpc v1.69.0-dev h1:apWegzBczine6VjRA1FpkZ9LVAvNINTqDPbiRDD4D/g= -google.golang.org/grpc v1.69.0-dev/go.mod h1:2RINgKHklVDGHlkF/BfDsmIw0xdarBnd0YM+g7Fc0Fk= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= +google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -223,5 +218,3 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0= -nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= From 4532da0b875ebd72adc31d4fe5feb16ff110833c Mon Sep 17 00:00:00 2001 From: Robin Bryce Date: Mon, 7 Jul 2025 17:03:13 +0100 Subject: [PATCH 2/3] wip: various clean ups to "forked log" native scitt registration --- .golangci.yml | 201 ++++----- app.go | 2 +- append.go | 399 ++++++++++-------- cfgidstate.go | 25 ++ cfgmassif.go | 7 +- cmd/veracity/main.go | 2 +- cmdctx.go | 14 +- go.mod | 1 + ecdsareadwrite.go => keyio/ecdsareadwrite.go | 50 ++- sealerpubkey.go => keyio/sealerpubkey.go | 3 +- localreader.go => localmassifs/localreader.go | 2 +- localwriter.go => localmassifs/localwriter.go | 2 +- localmassifs/readverified.go | 91 ++++ mmriver/const.go | 6 + mmriver/mmrentryversion1.go | 82 ++++ replicatelogs.go | 29 +- scitt/mandatory.go | 149 +++++++ scitt/rfc9290.go | 120 ++++++ scitt/scitt.go | 78 ++++ tests/append/append_test.go | 30 ++ tests/append/suite_test.go | 23 + tests/replicatelogs/suite_test.go | 2 +- verifyincluded_test.go | 5 - 23 files changed, 970 insertions(+), 353 deletions(-) create mode 100644 cfgidstate.go rename ecdsareadwrite.go => keyio/ecdsareadwrite.go (84%) rename sealerpubkey.go => keyio/sealerpubkey.go (99%) rename localreader.go => localmassifs/localreader.go (98%) rename localwriter.go => localmassifs/localwriter.go (98%) create mode 100644 localmassifs/readverified.go create mode 100644 mmriver/const.go create mode 100644 mmriver/mmrentryversion1.go create mode 100644 scitt/mandatory.go create mode 100644 scitt/rfc9290.go create mode 100644 scitt/scitt.go create mode 100644 tests/append/append_test.go create mode 100644 tests/append/suite_test.go diff --git a/.golangci.yml b/.golangci.yml index ede7cf0..6ad95c5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,120 +1,7 @@ -linters-settings: - funlen: - lines: 350 - statements: 135 - depguard: - list-type: blacklist - packages: - # logging is allowed only by logutils.Log, logrus - # is allowed to use only in logutils package - - github.com/sirupsen/logrus - dupl: - threshold: 100 - errorlint: - # Check whether fmt.Errorf uses the %w verb for formatting errors. - # See the https://github.com/polyfloyd/go-errorlint for caveats. - # Default: true - errorf: false - # Permit more than 1 %w verb, valid per Go 1.20 (Requires errorf:true) - # Default: true - errorf-multi: true - # Check for plain type assertions and type switches. - # Default: true - asserts: true - # Check for plain error comparisons. - # Default: true - comparison: true - exhaustive: - # Program elements to check for exhaustiveness. - # Default: [ switch ] - check: - - switch - - map - # Check switch statements in generated files also. - # Default: false - check-generated: true - # Presence of "default" case in switch statements satisfies exhaustiveness, - # even if all enum members are not listed. - # Default: false - default-signifies-exhaustive: true - # Enum members matching the supplied regex do not have to be listed in - # switch statements to satisfy exhaustiveness. - # Default: "" - ignore-enum-members: "Example.+" - # Enum types matching the supplied regex do not have to be listed in - # switch statements to satisfy exhaustiveness. - # Default: "" - ignore-enum-types: "Example.+" - # Consider enums only in package scopes, not in inner scopes. - # Default: false - package-scope-only: true - # Only run exhaustive check on switches with "//exhaustive:enforce" comment. - # Default: false - explicit-exhaustive-switch: false - # Only run exhaustive check on map literals with "//exhaustive:enforce" comment. - # Default: false - explicit-exhaustive-map: false - gci: - local-prefixes: github.com/datatrails/go-datatrails-merklelog - goconst: - min-len: 2 - min-occurrences: 2 - gocritic: - enabled-tags: - - performance - - style - - experimental - disabled-checks: - - wrapperFunc - gocognit: - min-complexity: 75 - gocyclo: - min-complexity: 10 - goimports: - local-prefixes: github.com/golangci/golangci-lint - golint: - min-confidence: 0 - govet: - check-shadowing: true - settings: - printf: - funcs: - - Infof - - Warnf - - Errorf - - Fatalf - lll: - line-length: 500 - maligned: - suggest-new: true - misspell: - locale: UK +run: + timeout: 5m + build-tags: "golangcilint unit integration e2e azurite" -# depguard (control upstream repos) not needed -# dupl - see ticket #3095 -# funlen - it is to anoying for test code and this sort of subjective judgement is what PR reviews are for -# exhaustive - see ticket #3096 -# gci - disabled as confusing and not really useful -# gochecknoglobals - not really useful -# goconst - see ticket #3097 -# goerr113 - disabled see https://github.com/Djarvur/go-err113/issues/10 -# gofumpt - not useful - confusing messages -# gomnd - see ticket #3116 -# govet - see ticket #3117 -# nilreturn onwardis not yet evaluated... -# maligned - this guards against performance issues due to accessing -# mis-aligned structs. We don't have direct evidence of this being a -# real problem for us. We use a lot of generated code in our hot -# paths anyway (we have no control over there layout). Until we get -# direct evidence this is hurting us, we prefer our stucts layed out -# logically and don't want to have to nolint tag everything. -# -# misspell - expected UK spelling with misspell, but customer facing text needs to be US. -# tagalign - suppress until we can get a golang code formatter that will fix this (cosmetic) -# -# WARN: typecheck cannot be disabled as golang-ci uses it internally to detect uncompilable code. -# Unfortunately the src/azb2c package triggers this erroneously so we add it to skip-dirs below. -# linters: enable-all: true disable: @@ -132,8 +19,7 @@ linters: - exhaustruct - forbidigo - forcetypeassert - # DONT re-enable funlen please - - funlen + - funlen # DONT re-enable funlen please - gci - gochecknoglobals - goconst @@ -192,9 +78,82 @@ linters: - wsl - wrapcheck -run: - build-tags: - - golangcilint +linters-settings: + funlen: + lines: 350 + statements: 135 + + depguard: + list-type: blacklist + packages: + - github.com/sirupsen/logrus # only allowed in logutils + + dupl: + threshold: 100 + + errorlint: + errorf: false + errorf-multi: true + asserts: true + comparison: true + + exhaustive: + check: + - switch + - map + check-generated: true + default-signifies-exhaustive: true + ignore-enum-members: "Example.+" + ignore-enum-types: "Example.+" + package-scope-only: true + explicit-exhaustive-switch: false + explicit-exhaustive-map: false + + gci: + local-prefixes: github.com/datatrails/go-datatrails-merklelog + + goconst: + min-len: 2 + min-occurrences: 2 + + gocritic: + enabled-tags: + - performance + - style + - experimental + disabled-checks: + - wrapperFunc + + gocognit: + min-complexity: 75 + + gocyclo: + min-complexity: 10 + + goimports: + local-prefixes: github.com/golangci/golangci-lint + + golint: + min-confidence: 0 + + govet: + check-shadowing: true + settings: + printf: + funcs: + - Infof + - Warnf + - Errorf + - Fatalf + + lll: + line-length: 500 + + maligned: + suggest-new: true + + misspell: + locale: UK issues: exclude-rules: diff --git a/app.go b/app.go index 4d57f20..1b25d6a 100644 --- a/app.go +++ b/app.go @@ -1,3 +1,4 @@ +// Package veracity provides the main application for the Veracity CLI tool. package veracity import ( @@ -7,7 +8,6 @@ import ( ) func NewApp(version string, ikwid bool) *cli.App { - cli.VersionPrinter = func(cCtx *cli.Context) { fmt.Println(cCtx.App.Version) } diff --git a/append.go b/append.go index 508fb6c..55c5bda 100644 --- a/append.go +++ b/append.go @@ -6,22 +6,23 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/sha256" - "encoding/binary" "errors" "fmt" "io" "os" "path/filepath" + "strings" "time" "github.com/fxamacker/cbor/v2" "github.com/veraison/go-cose" - dtcose "github.com/datatrails/go-datatrails-common/cose" - "github.com/datatrails/go-datatrails-common/logger" + commoncose "github.com/datatrails/go-datatrails-common/cose" "github.com/datatrails/go-datatrails-merklelog/massifs" - "github.com/datatrails/go-datatrails-merklelog/massifs/snowflakeid" "github.com/datatrails/go-datatrails-merklelog/mmr" + "github.com/datatrails/veracity/keyio" + "github.com/datatrails/veracity/localmassifs" + "github.com/datatrails/veracity/scitt" "github.com/urfave/cli/v2" ) @@ -52,14 +53,14 @@ func (s *identifiableCoseSigner) KeyLocation() string { } func (s *identifiableCoseSigner) KeyIdentifier() string { - // the returned kid needs to match the kid format of the keyvault key return "location:robinbryce/version1" } // NewAppendCmd appends an entry to a local ledger, optionally sealing it with a provided private key. func NewAppendCmd() *cli.Command { - return &cli.Command{Name: "append", + return &cli.Command{ + Name: "append", Usage: "add an entry to a local ledger, optionally sealing it with a provided private key", Flags: []cli.Flag{ &cli.Uint64Flag{ @@ -75,126 +76,79 @@ func NewAppendCmd() *cli.Command { Usage: "the sealer key to use for signing the entry, in cose .cbor. Only P-256, ES256 is supported. If --generate-sealer-key is set, this generated key will be written to this file.", }, &cli.StringFlag{ - Name: "trusted-sealer-key-pem", Aliases: []string{"s"}, + Name: "sealer-key-pem", + Usage: "the sealer key to use for signing the entry, in PEM format. Only P-256, ES256 is supported. If --generate-sealer-key is set, this generated key will be written to this file.", + }, + &cli.StringFlag{ + Name: "sealer-public-key-pem", + Usage: "If set, and if the sealer key is generated, the public key in PEM format is saved to this file.", + }, + + &cli.StringFlag{ + Name: "trusted-sealer-key-pem", Usage: "verify the current seal using this pem file based public key", }, &cli.StringFlag{ - Name: "receipt-file", Aliases: []string{"f"}, + Name: "receipt-file", Usage: "file name to write the receipt to, defaults to 'receipt-{mmrIndex}.cbor'", }, + &cli.StringFlag{ + Name: "signed-statement", + Usage: "read statement to register from this file. if statements-dir is also set, this statement is registered first, then all statements in the directory are registered.", + }, + &cli.StringFlag{ + Name: "statements-dir", + Usage: "read statements to register from this directory. the statements are added in lexical filename order", + }, + &cli.BoolFlag{ - Name: "generate-sealer-key", Aliases: []string{"g"}, - Usage: "generate a new sealer key and write it to the sealer-key file. If the sealer-key file already exists, it will be overwritten. the default file name is 'ecdsa-key-private.cbor'.", + Name: "generate-sealer-key", + Usage: "generate an ephemeral sealer key and write it to the sealer-key file. If the sealer-key file already exists, it will be overwritten. the default file name is 'ecdsa-key-private.cbor'.", + }, + &cli.StringFlag{ + Name: "massifs-dir", + Usage: "the directory to read the massifs from.", + }, + &cli.StringFlag{ + Name: "seals-dir", + Usage: "the directory to read the massif seals from.", }, }, Action: func(cCtx *cli.Context) error { var err error + cmd := &CmdCtx{} if !cCtx.IsSet("data-local") { return errors.New("this command supports local replicas only, and requires --data-local") } - - idState, err := snowflakeid.NewIDState(snowflakeid.Config{ - CommitmentEpoch: 1, - WorkerCIDR: "0.0.0.0/16", - PodIP: "10.0.0.1", - }) - if err != nil { - return fmt.Errorf("failed to create snowflake id state: %w", err) - } - - idTimestamp, err := idState.NextID() + err = cfgLogging(cmd, cCtx) if err != nil { - return fmt.Errorf("failed to generate snowflake id: %w", err) + return fmt.Errorf("failed to configure logging: %w", err) } - - hasher := sha256.New() - hasher.Write(AmourySignedStatement) - // Take the first 24 bytes of the hash as the extra bytes - statementHash := hasher.Sum(nil) - extraBytes := statementHash[:expectedExtraBytesSize] - leafHash, err := mmrEntryVersion1(extraBytes, idTimestamp, AmourySignedStatement) + err = cfgIDState(cmd, cCtx) if err != nil { - return fmt.Errorf("failed to create mmr entry: %w", err) + return fmt.Errorf("failed to configure id state: %w", err) } - fmt.Printf("%x statement-hash\n", statementHash) - fmt.Printf("%x leaf-hash\n", leafHash) - - cmd := &CmdCtx{} - - if err = cfgMassifReader(cmd, cCtx); err != nil { - return err - } - tenant := cCtx.String("tenant") - if tenant == "" { - fmt.Println("a tenant is required") - return nil - } - if cmd.cborCodec, err = massifs.NewRootSignerCodec(); err != nil { return err } - cache, err := massifs.NewLogDirCache(cmd.log, NewFileOpener()) - if err != nil { - return err - } - reader, err := massifs.NewLocalReader(logger.Sugar, cache) - if err != nil { - return err - } - - opts := []massifs.DirCacheOption{ - // massifs.WithDirCacheReplicaDir(cCtx.String("replicadir")), - // massifs.WithDirCacheReplicaDir(cCtx.String("data-local")), - massifs.WithDirCacheMassifLister(NewDirLister()), - massifs.WithDirCacheSealLister(NewDirLister()), - massifs.WithReaderOption(massifs.WithMassifHeight(uint8(cmd.massifHeight))), - massifs.WithReaderOption(massifs.WithSealGetter(&reader)), - massifs.WithReaderOption(massifs.WithCBORCodec(cmd.cborCodec)), - } - - // This will require that the remote seal is signed by the key - // provided here. If it is not, even if the seal is valid, the - // verification will fail with a suitable error. - pemString := cCtx.String("trusted-sealer-key-pem") - if pemString != "" { - pem, err := DecodeECDSAPublicString(pemString) - if err != nil { - return err - } - opts = append(opts, massifs.WithReaderOption(massifs.WithTrustedSealerPub(pem))) - } - - // For the localreader, the seal getter is the local reader itself. - // So we need to make use of ReplaceOptions on the cache, so we can - // provide the options after we have created the local reader. - cache.ReplaceOptions(opts...) - - verified, err := reader.GetHeadVerifiedContext(context.Background(), tenant) - if err != nil { + if err = cfgMassifReader(cmd, cCtx); err != nil { return err } - mmrSizeLast := verified.RangeCount() - fmt.Printf("%8d verified-size\n", mmrSizeLast) - verified.Tags = map[string]string{} - mmrIndex, err := verified.AddHashedLeaf(sha256.New(), idTimestamp, extraBytes, leafHash, []byte("scitt"), leafHash) - if err != nil { - return fmt.Errorf("failed to add hashed leaf: %w", err) - } - fmt.Printf("%8d mmrindex\n", mmrIndex) - // verified.CommitContext() - + // + // Read or generate a key to seal the forked log + // var sealingKey *ecdsa.PrivateKey if cCtx.IsSet("sealer-key") && !cCtx.Bool("generate-sealer-key") { sealerKeyFile := cCtx.String("sealer-key") if sealerKeyFile == "" { return errors.New("sealer-key file is required") } - sealingKey, err = ReadECDSAPrivateCose(sealerKeyFile, "P-256") + sealingKey, err = keyio.ReadECDSAPrivateCose(sealerKeyFile, "P-256") if err != nil { return fmt.Errorf("failed to load sealer key from file %s: %w", sealerKeyFile, err) } @@ -207,7 +161,7 @@ func NewAppendCmd() *cli.Command { if sealerKeyFile == "" { return errors.New("sealer-key file is required") } - sealingKey, err = ReadECDSAPrivatePEM(sealerKeyFile) + sealingKey, err = keyio.ReadECDSAPrivatePEM(sealerKeyFile) if err != nil { return fmt.Errorf("failed to load sealer key from file %s: %w", sealerKeyFile, err) } @@ -220,7 +174,36 @@ func NewAppendCmd() *cli.Command { } } - alg, err := dtcose.CoseAlgForEC(sealingKey.PublicKey) + // + // Read the head of a locally replicated production datatrails ledger + // + readerCfg, err := localmassifs.NewReaderDefaultConfig( + cmd.log, + cCtx.String("massifs-dir"), + cCtx.String("seals-dir"), + ) + if err != nil { + return fmt.Errorf("failed to create massif reader config: %w", err) + } + verified, err := localmassifs.ReadVerifiedHeadMassif(readerCfg) + if err != nil { + return fmt.Errorf("failed to read verified head massif: %w", err) + } + + mmrSizeLast := verified.RangeCount() + fmt.Printf("%8d verified-size\n", mmrSizeLast) + verified.Tags = map[string]string{} + + // + // Add a batch of statements, including the in-toto from ammoury + // + statements, err := addStatements(cmd, cCtx, &verified.MassifContext) + if err != nil { + return err + } + fmt.Printf("%d statements registered\n", len(statements)) + + alg, err := commoncose.CoseAlgForEC(sealingKey.PublicKey) if err != nil { return err } @@ -234,21 +217,32 @@ func NewAppendCmd() *cli.Command { publicKey: sealingKey.PublicKey, } + // + // Seal a checkpoint for the locally forked ledger with a made up sealing key + // Receipts are rooted at a checkpoint accumulator state. + // rootSigner := massifs.NewRootSigner("https://github.com/robinbryce/veracity", cmd.cborCodec) // TODO: account for filling a massif mmrSizeCurrent := verified.RangeCount() cp, err := mmr.IndexConsistencyProof(&verified.MassifContext, verified.MMRState.MMRSize-1, mmrSizeCurrent-1) + if err != nil { + return err + } + + // To create the checkpoint, we first check that the current state + // contains the previously verified state. This necessarily produces + // the proof material which we can then include with the new checkpoint. ok, peaksB, err := mmr.CheckConsistency( verified, sha256.New(), cp.MMRSizeA, cp.MMRSizeB, verified.MMRState.Peaks) if !ok { - return fmt.Errorf("consistency check failed: verifie failed") + return fmt.Errorf("consistency check failed: verify failed") } if err != nil { return err } - lastIdTimestamp := verified.GetLastIdTimestamp() + lastIDTimestamp := verified.GetLastIdTimestamp() state := massifs.MMRState{ Version: int(massifs.MMRStateVersionCurrent), @@ -256,9 +250,28 @@ func NewAppendCmd() *cli.Command { Peaks: peaksB, Timestamp: time.Now().UnixMilli(), CommitmentEpoch: verified.MMRState.CommitmentEpoch, - IDTimestamp: lastIdTimestamp, - } - subject := massifs.TenantMassifBlobPath(tenant, uint64(verified.Start.MassifIndex)) + IDTimestamp: lastIDTimestamp, + } + + // + // Read and decode the checkpoint + // + // Given a signed checkpoint, receipts can be self served for any + // element included in the MMR before that checkpoint. Leaves from + // the massif corresponding to the massif need no other data. + // Leaves from earlier massifs *may* need the earlier massif, but + // often don't (its deterministic and computable when and which + // earlier massifs are needed for an arbitrary mmrIndex) + // + // It is never necessary to have more than two massifs in order to + // produce a receipt against the latest checkpoint. + // + // There is no particular reason to re-fresh, or even save, receipts + // if you have a trustworthy store of checkpoints. + // + mmrStatement := statements[0] + // A more appropriate subject would be the identity of the log ... + subject := fmt.Sprintf("fork-%d-%d.bin", verified.MMRState.MMRSize-1, mmrSizeCurrent) publicKey, err := identifiableSigner.LatestPublicKey() if err != nil { return fmt.Errorf("unable to get public key for signing key %w", err) @@ -276,17 +289,22 @@ func NewAppendCmd() *cli.Command { return err } - // this is a slightly tweaked variant of massifs.NewReceipt (go-datatrails-merklelog/massifs/mmriver.go) - - // because we added a single leaf, the size pre-add is the mmrIndex of the next leaf. - newStatementMMRIndex := verified.MMRState.MMRSize - proof, err := mmr.InclusionProof(&verified.MassifContext, state.MMRSize-1, newStatementMMRIndex) + // + // Generate the inclusion proof, note that we don't actually need + // the leaf hash to do this. So *anyone* can obtain a receipt for + // *any* leaf at any time, given only the specific massif (tile) + // that leaf was registered in. and its associated checkpoint. + // + proof, err := mmr.InclusionProof(&verified.MassifContext, state.MMRSize-1, mmrStatement.LeafIndex) if err != nil { return fmt.Errorf( "failed to generating inclusion proof: %d in MMR(%d), %v", - newStatementMMRIndex, verified.MMRState.MMRSize, err) + mmrStatement.LeafIndex, verified.MMRState.MMRSize, err) } + // + // Locate the pre-signed receipt for the accumulator peak containing the leaf. + // peakIndex := mmr.PeakIndex(mmr.LeafCount(state.MMRSize), len(proof)) // NOTE: The old-accumulator compatibility property, from // https://eprint.iacr.org/2015/718.pdf, along with the COSE protected & @@ -307,25 +325,31 @@ func NewAppendCmd() *cli.Command { // This is an array of marshaled COSE_Sign1's receiptMsg := peaksHeader.PeakReceipts[peakIndex] - signed, err := dtcose.NewCoseSign1MessageFromCBOR( - receiptMsg, dtcose.WithDecOptions(massifs.CheckpointDecOptions())) + signed, err := commoncose.NewCoseSign1MessageFromCBOR( + receiptMsg, commoncose.WithDecOptions(massifs.CheckpointDecOptions())) if err != nil { return fmt.Errorf( - "%w: failed to decode pre-signed receipt for: %d in MMR(%d)", - err, mmrIndex, state.MMRSize) + "%w: failed to decode pre-signed receipt for MMR(%d)", + err, state.MMRSize) } - // signed.Headers.RawProtected = nil + // + // Make the MMR draft receipt by attaching the inclusion proof to the Unprotected header + // signed.Headers.RawUnprotected = nil verifiableProofs := massifs.MMRiverVerifiableProofs{ InclusionProofs: []massifs.MMRiverInclusionProof{{ - Index: mmrIndex, - InclusionPath: proof}}, + Index: mmrStatement.LeafIndex, + InclusionPath: proof, + }}, } signed.Headers.Unprotected[massifs.VDSCoseReceiptProofsTag] = verifiableProofs + // + // Save the receipt to a file + // receiptCbor, err := signed.MarshalCBOR() if err != nil { return fmt.Errorf("failed to marshal receipt: %w", err) @@ -333,13 +357,17 @@ func NewAppendCmd() *cli.Command { receiptFileName := cCtx.String("receipt-file") if receiptFileName == "" { - receiptFileName = fmt.Sprintf("receipt-%d.cbor", newStatementMMRIndex) + receiptFileName = fmt.Sprintf("receipt-%d.cbor", mmrStatement.LeafIndex) } if err := os.WriteFile(receiptFileName, receiptCbor, os.FileMode(0644)); err != nil { return fmt.Errorf("failed to write receipt file %s: %w", receiptFileName, err) } fmt.Printf("wrote receipt file %s\n", receiptFileName) + // + // A bunch of persistence conveniences for the sake of the demo + // + forkFileName := filepath.Join(".", fmt.Sprintf("fork-%d-%d.bin", verified.MMRState.MMRSize-1, mmrSizeCurrent)) if err := os.WriteFile(forkFileName, data, os.FileMode(0644)); err != nil { return fmt.Errorf("failed to write log fork file %s: %w", forkFileName, err) @@ -355,89 +383,116 @@ func NewAppendCmd() *cli.Command { // write the sealer key to the sealer-key file sealerKeyFile := cCtx.String("sealer-key") if sealerKeyFile == "" { - sealerKeyFile = ECDSAPrivateDefaultFileName + sealerKeyFile = keyio.ECDSAPrivateDefaultFileName } - if _, err := WriteECDSAPrivateCOSE(sealerKeyFile, sealingKey); err != nil { + if _, err := keyio.WriteECDSAPrivateCOSE(sealerKeyFile, sealingKey); err != nil { return fmt.Errorf("failed to write sealer key to file %s: %w", sealerKeyFile, err) } fmt.Printf("wrote sealer key to file %s\n", sealerKeyFile) sealerKeyFile = cCtx.String("sealer-key-pem") if sealerKeyFile == "" { - sealerKeyFile = ECDSAPrivateDefaultPEMFileName - } - if err := WriteECDSAPrivatePEM(sealerKeyFile, sealingKey); err != nil { - return fmt.Errorf("failed to write sealer key to file %s: %w", sealerKeyFile, err) + sealerKeyFile = keyio.ECDSAPrivateDefaultPEMFileName } - fmt.Printf("wrote sealer key to file %s\n", sealerKeyFile) - if _, err := writeCoseECDSAPublicKey(sealerKeyFile, &sealingKey.PublicKey); err != nil { + if err := keyio.WriteECDSAPrivatePEM(sealerKeyFile, sealingKey); err != nil { return fmt.Errorf("failed to write sealer key to file %s: %w", sealerKeyFile, err) } fmt.Printf("wrote sealer key to file %s\n", sealerKeyFile) + sealerKeyFile = cCtx.String("sealer-public-key-pem") + if sealerKeyFile != "" { + if _, err := keyio.WriteCoseECDSAPublicKey(sealerKeyFile, &sealingKey.PublicKey); err != nil { + return fmt.Errorf("failed to write sealer key to file %s: %w", sealerKeyFile, err) + } + fmt.Printf("wrote sealer public key to file %s\n", sealerKeyFile) + } } return nil }, } } -const ( - expectedExtraBytesSize = 24 -) - -// mmrEntryVersion1 gets the mmr entry for log entry version 1. -// -// mmr entry format for log entry version 1: -// -// H( domain | mmrSalt | serializedBytes ) -// -// where mmrSalt = extraBytes + idtimestamp -// -// NOTE: extraBytes is consistently 24 bytes on the trie value, so we pad/truncate extrabytes here -// to ensure its 24 bytes also. This allows greater consistency and ease of moving between mmrSalt and trieValue -func mmrEntryVersion1(extraBytes []byte, idtimestamp uint64, serializedBytes []byte) ([]byte, error) { - - hasher := sha256.New() +// addStatements adds the signed statements to the massif and returns the leaf +// indices of the added statements. +// If a specific statement is specified via --signed-statement, then it is +// added first. THose discovered from --statement-dir are added in lexical +// filename order. +func addStatements(cmd *CmdCtx, cCtx *cli.Context, massif *massifs.MassifContext) ([]scitt.MMRStatement, error) { + var fileNames []string + var statements []scitt.MMRStatement + + if cCtx.String("signed-statement") != "" { + fileNames = append(fileNames, cCtx.String("signed-statement")) + } - // domain - hasher.Write([]byte{byte(LeafTypePlain)}) + if cCtx.String("statements-dir") != "" { + files, err := listFilesWithSuffix(cCtx.String("statements-dir"), ".cbor") + if err != nil { + return nil, err + } + fileNames = append(fileNames, files...) + } + if len(fileNames) == 0 { + return nil, fmt.Errorf("no signed statements found, please specify --signed-statement or --statements-dir or both") + } - // mmrSalt + for _, fileName := range fileNames { + mmrStatement, err := readStatementFromFile(fileName, cmd) + if err != nil { + return nil, fmt.Errorf("failed to read signed statement from file %s: %w", cCtx.String("signed-statement"), err) + } + mmrStatement.LeafIndex = massif.RangeCount() - 1 + + _, err = massif.AddHashedLeaf( + sha256.New(), + mmrStatement.IDTimestamp, + mmrStatement.ExtraBytes, + // use the issuer as the origin log id, which isn't quite right, but is close enough for this demo + []byte(mmrStatement.Claims.Issuer), + []byte("scitt"), + mmrStatement.LeafHash, + ) + if err != nil { + return nil, fmt.Errorf("failed to add hashed leaf: %w", err) + } + + statements = append(statements, *mmrStatement) + + fmt.Printf("index : %d\n", mmrStatement.LeafIndex) + fmt.Printf(" issuer : %s\n", mmrStatement.Claims.Issuer) + fmt.Printf(" leaf-hash : %x\n", mmrStatement.LeafHash) + fmt.Printf(" statement-hash: %x\n", mmrStatement.Hash) + } + return statements, nil +} - // ensure extrabytes is 24 bytes long - extraBytes, err := consistentExtraBytesSize(extraBytes) +func readStatementFromFile(fileName string, cmd *CmdCtx) (*scitt.MMRStatement, error) { + mmrStatement, cpd, err := scitt.NewMMRStatementFromFile(fileName, cmd, scitt.RegistrationPolicyVerified()) if err != nil { - return nil, err + if cpd != nil && cpd.Instance != scitt.ProblemInstanceConfirmationMissing { + return nil, fmt.Errorf("%w: failed reading and checking signed statement: %s", err, cpd.Detail) + } + // for demo purposes, because we do not support x509 + mmrStatement, cpd, err = scitt.NewMMRStatementFromFile(fileName, cmd, scitt.RegistrationPolicyUnverified()) + if err != nil { + return nil, fmt.Errorf("%w: failed reading and checking signed statement: %s", err, cpd.Detail) + } + err = nil + cpd = nil } - hasher.Write(extraBytes) - - // convert idtimestamp to bytes - idTimestampBytes := make([]byte, 8) - binary.BigEndian.PutUint64(idTimestampBytes, idtimestamp) - hasher.Write(idTimestampBytes) - - // serializedBytes - hasher.Write(serializedBytes) - - return hasher.Sum(nil), nil + return mmrStatement, nil } -// consistentExtraBytesSize ensures the given extraBytes is padded/truncated to exactly 24 bytes -func consistentExtraBytesSize(extraBytes []byte) ([]byte, error) { - - extraBytesSize := len(extraBytes) - - // larger size need to truncate - if extraBytesSize > expectedExtraBytesSize { - return nil, errors.New("extra bytes is too large, maximum extra bytes size is 24") +func listFilesWithSuffix(dir, suffix string) ([]string, error) { + entries, err := os.ReadDir(dir) + if err != nil { + return nil, err } - // smaller size need to pad - if extraBytesSize < expectedExtraBytesSize { - tmp := make([]byte, expectedExtraBytesSize) - copy(tmp[:extraBytesSize], extraBytes) - return tmp, nil + var matches []string + for _, entry := range entries { + if entry.Type().IsRegular() && strings.HasSuffix(entry.Name(), suffix) { + matches = append(matches, filepath.Join(dir, entry.Name())) + } } - - // goldilocks just right - return extraBytes, nil + return matches, nil } diff --git a/cfgidstate.go b/cfgidstate.go new file mode 100644 index 0000000..5560cb8 --- /dev/null +++ b/cfgidstate.go @@ -0,0 +1,25 @@ +package veracity + +import ( + "github.com/datatrails/go-datatrails-merklelog/massifs/snowflakeid" + "github.com/urfave/cli/v2" +) + +// cfgIDState initialises an idTimestamp generator +// of enabled workarounds. +func cfgIDState(cmd *CmdCtx, cCtx *cli.Context) error { + var err error + cmd.commitmentEpoch = 1 // the default, and correct until the unix epoch changes + if cCtx.IsSet("commitment-epoch") { + cmd.commitmentEpoch = uint8(cCtx.Uint64("commitment-epoch")) + } + + cmd.idState, err = snowflakeid.NewIDState(snowflakeid.Config{ + CommitmentEpoch: cmd.commitmentEpoch, + // There is no reason to override these for local use. + WorkerCIDR: "0.0.0.0/16", + PodIP: "10.0.0.127", + }) + + return err +} diff --git a/cfgmassif.go b/cfgmassif.go index 20c8229..043d31a 100644 --- a/cfgmassif.go +++ b/cfgmassif.go @@ -6,6 +6,7 @@ import ( "github.com/datatrails/go-datatrails-common/logger" "github.com/datatrails/go-datatrails-merklelog/massifs" + "github.com/datatrails/veracity/localmassifs" "github.com/urfave/cli/v2" ) @@ -57,11 +58,11 @@ func cfgMassifReader(cmd *CmdCtx, cCtx *cli.Context) error { // not automatically derived. cache, err := massifs.NewLogDirCache( logger.Sugar, - NewFileOpener(), + localmassifs.NewFileOpener(), massifs.WithDirCacheTenant(cCtx.String("tenant")), // massifs.WithExplicitFilePaths(cCtx.String("tenant")), // may be empty string - massifs.WithDirCacheMassifLister(NewDirLister()), - massifs.WithDirCacheSealLister(NewDirLister()), + massifs.WithDirCacheMassifLister(localmassifs.NewDirLister()), + massifs.WithDirCacheSealLister(localmassifs.NewDirLister()), massifs.WithReaderOption(massifs.WithMassifHeight(cmd.massifHeight)), massifs.WithReaderOption(massifs.WithCBORCodec(codec)), ) diff --git a/cmd/veracity/main.go b/cmd/veracity/main.go index 6962340..9c7ba8c 100644 --- a/cmd/veracity/main.go +++ b/cmd/veracity/main.go @@ -1,3 +1,4 @@ +// Package main provides the entry point for the Veracity CLI application. package main import ( @@ -21,7 +22,6 @@ var ( ) func main() { - versionString := "unknown" if version != "" { // versionString = fmt.Sprintf("%s %s %s", version, commit, buildDate) diff --git a/cmdctx.go b/cmdctx.go index 0fd7b10..14358fd 100644 --- a/cmdctx.go +++ b/cmdctx.go @@ -2,10 +2,12 @@ package veracity import ( "context" + "fmt" "github.com/datatrails/go-datatrails-common/cbor" "github.com/datatrails/go-datatrails-common/logger" "github.com/datatrails/go-datatrails-merklelog/massifs" + "github.com/datatrails/go-datatrails-merklelog/massifs/snowflakeid" ) // MassifGetter gets a specific massif based on the massifIndex given for a tenant log @@ -37,7 +39,7 @@ type MassifReader interface { type CmdCtx struct { log logger.Logger // storer *azblob.Storer - //reader azblob.Reader + // reader azblob.Reader massifReader MassifReader readerURL string cborCodec cbor.CBORCodec @@ -46,9 +48,19 @@ type CmdCtx struct { massifHeight uint8 + commitmentEpoch uint8 + idState *snowflakeid.IDState + bugs map[string]bool } +func (cmd *CmdCtx) NextID() (uint64, error) { + if cmd.idState == nil { + return 0, fmt.Errorf("idState not initialized, cannot generate next ID") + } + return cmd.idState.NextID() +} + // Clone returns a copy of the CmdCtx with only those members that are safe to share copied. // Those are: // - log - the result of cfgLogging diff --git a/go.mod b/go.mod index 5f6c31f..318e910 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( ) replace ( + github.com/datatrails/go-datatrails-common => ../go-datatrails-common github.com/datatrails/go-datatrails-logverification => ../go-datatrails-logverification github.com/datatrails/go-datatrails-merklelog/massifs => ../go-datatrails-merklelog/massifs github.com/datatrails/go-datatrails-merklelog/mmr => ../go-datatrails-merklelog/mmr diff --git a/ecdsareadwrite.go b/keyio/ecdsareadwrite.go similarity index 84% rename from ecdsareadwrite.go rename to keyio/ecdsareadwrite.go index e349037..9f87d42 100644 --- a/ecdsareadwrite.go +++ b/keyio/ecdsareadwrite.go @@ -1,4 +1,4 @@ -package veracity +package keyio import ( "crypto/ecdsa" @@ -9,7 +9,7 @@ import ( "math/big" "os" - dtcose "github.com/datatrails/go-datatrails-common/cose" + commoncose "github.com/datatrails/go-datatrails-common/cose" "github.com/fxamacker/cbor/v2" ) @@ -112,10 +112,9 @@ func WriteECDSAPublicCOSE( pubFile string, publicKey *ecdsa.PublicKey, ) (string, error) { - var err error - if _, err = writeCoseECDSAPublicKey(pubFile, publicKey); err != nil { + if _, err = WriteCoseECDSAPublicKey(pubFile, publicKey); err != nil { return "", err } return pubFile, nil @@ -125,10 +124,9 @@ func WriteECDSAPrivateCOSE( privFile string, privateKey *ecdsa.PrivateKey, ) (string, error) { - var err error - if _, err = writeCoseECDSAPrivateKey(privFile, privateKey); err != nil { + if _, err = WriteCoseECDSAPrivateKey(privFile, privateKey); err != nil { return "", err } return privFile, nil @@ -136,25 +134,25 @@ func WriteECDSAPrivateCOSE( // Encode private key to COSE_Key format (as CBOR bytes) func encodePrivateKeyToCOSE(key *ecdsa.PrivateKey) ([]byte, error) { - m := map[int]interface{}{ - dtcose.KeyTypeLabel: int64(dtcose.KeyTypeEC2), - dtcose.AlgorithmLabel: -7, // ES256 (ECDSA w/ SHA-256) - dtcose.ECCurveLabel: 1, // P-256 - dtcose.ECXLabel: key.PublicKey.X.Bytes(), - dtcose.ECYLabel: key.PublicKey.Y.Bytes(), - dtcose.ECDLabel: key.D.Bytes(), + m := map[int64]interface{}{ + commoncose.KeyTypeLabel: int64(commoncose.KeyTypeEC2), + commoncose.AlgorithmLabel: -7, // ES256 (ECDSA w/ SHA-256) + commoncose.ECCurveLabel: 1, // P-256 + commoncose.ECXLabel: key.PublicKey.X.Bytes(), + commoncose.ECYLabel: key.PublicKey.Y.Bytes(), + commoncose.ECDLabel: key.D.Bytes(), } return cbor.Marshal(m) } // Encode public key to COSE_Key format (as CBOR bytes) func encodePublicKeyToCOSE(key *ecdsa.PublicKey) ([]byte, error) { - m := map[int]interface{}{ - dtcose.KeyTypeLabel: int64(dtcose.KeyTypeEC2), - dtcose.AlgorithmLabel: -7, // ES256 (ECDSA w/ SHA-256) - dtcose.ECCurveLabel: 1, // P-256 - dtcose.ECXLabel: key.X.Bytes(), - dtcose.ECYLabel: key.Y.Bytes(), + m := map[int64]interface{}{ + commoncose.KeyTypeLabel: int64(commoncose.KeyTypeEC2), + commoncose.AlgorithmLabel: -7, // ES256 (ECDSA w/ SHA-256) + commoncose.ECCurveLabel: 1, // P-256 + commoncose.ECXLabel: key.X.Bytes(), + commoncose.ECYLabel: key.Y.Bytes(), } return cbor.Marshal(m) } @@ -173,7 +171,7 @@ func decodeECDSAPrivateKey( } d := big.NewInt(0) - d.SetBytes(m[dtcose.ECDLabel].([]byte)) + d.SetBytes(m[commoncose.ECDLabel].([]byte)) privateKey := &ecdsa.PrivateKey{ PublicKey: *publicKey, @@ -186,7 +184,6 @@ func decodeECDSAPublicKey( data []byte, expectedStandardCurve ...string, ) (*ecdsa.PublicKey, error) { - var m map[int64]interface{} if err := cbor.Unmarshal(data, &m); err != nil { return nil, err @@ -198,8 +195,11 @@ func decodeECDSAPublicKeyFromMap( m map[int64]interface{}, expectedStandardCurve ...string, ) (*ecdsa.PublicKey, error) { + ecKey, err := commoncose.NewECCoseKey(m) + if err != nil { + return nil, fmt.Errorf("failed to get public key from COSE key: %w", err) + } - ecKey, err := dtcose.NewECCoseKey(m) genericKey, err := ecKey.PublicKey() if err != nil { return nil, fmt.Errorf("failed to get public key from COSE key: %w", err) @@ -219,12 +219,11 @@ func decodeECDSAPublicKeyFromMap( return publicKey, nil } -func writeCoseECDSAPrivateKey( +func WriteCoseECDSAPrivateKey( fileName string, privateKey *ecdsa.PrivateKey, perms ...os.FileMode, ) ([]byte, error) { - var err error var data []byte if data, err = encodePrivateKeyToCOSE(privateKey); err != nil { @@ -243,12 +242,11 @@ func writeCoseECDSAPrivateKey( return data, nil } -func writeCoseECDSAPublicKey( +func WriteCoseECDSAPublicKey( fileName string, publicKey *ecdsa.PublicKey, perms ...os.FileMode, ) ([]byte, error) { - var err error var data []byte if data, err = encodePublicKeyToCOSE(publicKey); err != nil { diff --git a/sealerpubkey.go b/keyio/sealerpubkey.go similarity index 99% rename from sealerpubkey.go rename to keyio/sealerpubkey.go index b60f2f3..bc33f30 100644 --- a/sealerpubkey.go +++ b/keyio/sealerpubkey.go @@ -1,4 +1,4 @@ -package veracity +package keyio import ( "crypto/ecdsa" @@ -49,7 +49,6 @@ func DecodeECDSAPublicPEM(data []byte) (*ecdsa.PublicKey, error) { // material presented as a single, base64 encoded, string. This is typically // more convenient for command line and environment vars func DecodeECDSAPublicString(data string) (*ecdsa.PublicKey, error) { - keyData, err := base64.StdEncoding.DecodeString(data) if err != nil { return nil, err diff --git a/localreader.go b/localmassifs/localreader.go similarity index 98% rename from localreader.go rename to localmassifs/localreader.go index a5049cb..f205247 100644 --- a/localreader.go +++ b/localmassifs/localreader.go @@ -1,4 +1,4 @@ -package veracity +package localmassifs import ( "bufio" diff --git a/localwriter.go b/localmassifs/localwriter.go similarity index 98% rename from localwriter.go rename to localmassifs/localwriter.go index 9d63494..f91debb 100644 --- a/localwriter.go +++ b/localmassifs/localwriter.go @@ -1,4 +1,4 @@ -package veracity +package localmassifs import ( "io" diff --git a/localmassifs/readverified.go b/localmassifs/readverified.go new file mode 100644 index 0000000..db39aa5 --- /dev/null +++ b/localmassifs/readverified.go @@ -0,0 +1,91 @@ +// Package localmassifs provides functionality to read and verify massifs from a local filesystem. +package localmassifs + +import ( + "context" + "fmt" + + "github.com/datatrails/go-datatrails-common/cbor" + "github.com/datatrails/go-datatrails-common/logger" + "github.com/datatrails/go-datatrails-merklelog/massifs" +) + +type ReaderOptions struct { + log logger.Logger + MassifsDir string + SealsDir string + MassifHeight uint8 + CBORCodec cbor.CBORCodec +} + +func NewReaderDefaultConfig(log logger.Logger, massifsDir, sealsDir string) (ReaderOptions, error) { + + var err error + options := ReaderOptions{ + MassifsDir: massifsDir, + SealsDir: sealsDir, + MassifHeight: 14, + } + if options.CBORCodec, err = massifs.NewRootSignerCodec(); err != nil { + return ReaderOptions{}, err + } + return options, nil +} + +// ReadVerifiedHeadMassif reads the verified head massif from the specified directory +func ReadVerifiedHeadMassif(options ReaderOptions, baseOpts ...massifs.DirCacheOption) (*massifs.VerifiedContext, error) { + + var err error + + opts := []massifs.DirCacheOption{ + massifs.WithDirCacheMassifLister(NewDirLister()), + massifs.WithDirCacheSealLister(NewDirLister()), + massifs.WithReaderOption(massifs.WithMassifHeight(options.MassifHeight)), + massifs.WithReaderOption(massifs.WithCBORCodec(options.CBORCodec)), + } + opts = append(opts, baseOpts...) + + cache, err := massifs.NewLogDirCache(options.log, NewFileOpener(), opts...) + if err != nil { + return nil, err + } + + reader, err := massifs.NewLocalReader(options.log, cache) + if err != nil { + return nil, err + } + + cache.ReplaceOptions(opts...) + + massifCache, err := cache.ReadMassifDirEntry(options.MassifsDir) + if err != nil { + return nil, fmt.Errorf("failed to read massif dir entry: %w", err) + } + massifInfo := massifCache.GetInfo() + fmt.Printf("Read massif %d to %d from %s\n", massifInfo.FirstMassifIndex, massifInfo.HeadMassifIndex, massifInfo.Directory) + + sealCache, err := cache.ReadSealDirEntry(options.SealsDir) + if err != nil { + return nil, fmt.Errorf("failed to read massif dir entry: %w", err) + } + sealInfo := sealCache.GetInfo() + fmt.Printf("Read seals for massifs %d to %d from %s\n", sealInfo.FirstMassifIndex, sealInfo.HeadMassifIndex, sealInfo.Directory) + if err != nil { + return nil, fmt.Errorf("failed to read massif dir entry: %w", err) + } + + mc, err := cache.ReadMassif(massifInfo.Directory, uint64(massifInfo.HeadMassifIndex)) + if err != nil { + return nil, fmt.Errorf("failed to read massif %d from %s: %w", massifInfo.HeadMassifIndex, massifInfo.Directory, err) + } + + seal, err := cache.ReadSeal(sealInfo.Directory, uint64(massifInfo.HeadMassifIndex)) + if err != nil { + return nil, fmt.Errorf("failed to read seal for massif %d from %s: %w", massifInfo.HeadMassifIndex, sealInfo.Directory, err) + } + verified, err := reader.VerifyContext(context.Background(), *mc, massifs.WithCheckpoint(&seal.Sign1Message, &seal.MMRState)) + if err != nil { + return nil, fmt.Errorf("failed to read massif %d from %s: %w", massifInfo.HeadMassifIndex, massifInfo.Directory, err) + } + return verified, nil +} diff --git a/mmriver/const.go b/mmriver/const.go new file mode 100644 index 0000000..a79754f --- /dev/null +++ b/mmriver/const.go @@ -0,0 +1,6 @@ +package mmriver + +const ( + LeafTypePlain = uint8(0) + expectedExtraBytesSize = 24 +) diff --git a/mmriver/mmrentryversion1.go b/mmriver/mmrentryversion1.go new file mode 100644 index 0000000..bbf3233 --- /dev/null +++ b/mmriver/mmrentryversion1.go @@ -0,0 +1,82 @@ +// Package mmriver works with the datatrails ledger based on draft-bryce-cose-receipts-mmr-profile +package mmriver + +import ( + "crypto/sha256" + "encoding/binary" + "errors" +) + +// MMREntryVersion1 gets the mmr entry for log entry version 1. +// mmr entry format for log entry version 1: +// +// H( domain | mmrSalt | serializedBytes ) +// +// where mmrSalt = extraBytes + idtimestamp +// +// NOTE: extraBytes is consistently 24 bytes on the trie value, so we pad/truncate extrabytes here +// to ensure its 24 bytes also. This allows greater consistency and ease of moving between mmrSalt and trieValue +func MMREntryVersion1(extraBytes []byte, idtimestamp uint64, serializedBytes []byte) ([]byte, error) { + hasher := sha256.New() + + // domain + hasher.Write([]byte{byte(LeafTypePlain)}) + + // mmrSalt + + // ensure extrabytes is 24 bytes long + extraBytes, err := ConsistentExtraBytesSize(extraBytes) + if err != nil { + return nil, err + } + hasher.Write(extraBytes) + + // convert idtimestamp to bytes + idTimestampBytes := make([]byte, 8) + binary.BigEndian.PutUint64(idTimestampBytes, idtimestamp) + hasher.Write(idTimestampBytes) + + // serializedBytes + hasher.Write(serializedBytes) + + return hasher.Sum(nil), nil +} + +func TrimExtraBytes(extraBytes []byte) []byte { + extraBytesSize := len(extraBytes) + + // larger size need to truncate + if extraBytesSize > expectedExtraBytesSize { + extraBytes = extraBytes[:expectedExtraBytesSize] + } + + // smaller size need to pad + if extraBytesSize < expectedExtraBytesSize { + tmp := make([]byte, expectedExtraBytesSize) + copy(tmp[:extraBytesSize], extraBytes) + return tmp + } + + // goldilocks just right + return extraBytes +} + +// consistentExtraBytesSize ensures the given extraBytes is padded/truncated to exactly 24 bytes +func ConsistentExtraBytesSize(extraBytes []byte) ([]byte, error) { + extraBytesSize := len(extraBytes) + + // larger size need to truncate + if extraBytesSize > expectedExtraBytesSize { + return nil, errors.New("extra bytes is too large, maximum extra bytes size is 24") + } + + // smaller size need to pad + if extraBytesSize < expectedExtraBytesSize { + tmp := make([]byte, expectedExtraBytesSize) + copy(tmp[:extraBytesSize], extraBytes) + return tmp, nil + } + + // goldilocks just right + return extraBytes, nil +} diff --git a/replicatelogs.go b/replicatelogs.go index f36f576..1bfb401 100644 --- a/replicatelogs.go +++ b/replicatelogs.go @@ -17,6 +17,8 @@ import ( "github.com/datatrails/go-datatrails-merklelog/massifs" "github.com/datatrails/go-datatrails-merklelog/massifs/watcher" "github.com/datatrails/go-datatrails-merklelog/mmr" + "github.com/datatrails/veracity/keyio" + "github.com/datatrails/veracity/localmassifs" "github.com/gosuri/uiprogress" "github.com/urfave/cli/v2" "golang.org/x/exp/rand" @@ -179,7 +181,6 @@ By default transient errors are re-tried without limit, and if the error is 429, // replicateChanges replicate the changes for the provided slice of tenants. // Paralelism is limited by breaking the total changes into smaller slices and calling this function func replicateChanges(cCtx *cli.Context, cmd *CmdCtx, changes []TenantMassif, progress Progresser) error { - var wg sync.WaitGroup errChan := make(chan error, len(changes)) // buffered so it doesn't block @@ -249,7 +250,6 @@ func replicateChanges(cCtx *cli.Context, cmd *CmdCtx, changes []TenantMassif, pr } func initReplication(cCtx *cli.Context, cmd *CmdCtx, change TenantMassif) (*VerifiedReplica, uint32, uint32, error) { - replicator, err := NewVerifiedReplica(cCtx, cmd.Clone()) if err != nil { return nil, 0, 0, err @@ -268,7 +268,6 @@ func defaultRetryDelay(_ error) time.Duration { } func newProgressor(cCtx *cli.Context, barName string, increments int) Progresser { - if !cCtx.Bool("progress") { return NewNoopProgress() } @@ -292,7 +291,6 @@ type VerifiedReplica struct { func NewVerifiedReplica( cCtx *cli.Context, cmd *CmdCtx, ) (*VerifiedReplica, error) { - dataUrl := cCtx.String("data-url") reader, err := cfgReader(cmd, cCtx, dataUrl == "") if err != nil { @@ -307,7 +305,7 @@ func NewVerifiedReplica( return nil, fmt.Errorf("massif height must be less than 256") } - cache, err := massifs.NewLogDirCache(logger.Sugar, NewFileOpener()) + cache, err := massifs.NewLogDirCache(logger.Sugar, localmassifs.NewFileOpener()) if err != nil { return nil, err } @@ -318,8 +316,8 @@ func NewVerifiedReplica( opts := []massifs.DirCacheOption{ massifs.WithDirCacheReplicaDir(cCtx.String("replicadir")), - massifs.WithDirCacheMassifLister(NewDirLister()), - massifs.WithDirCacheSealLister(NewDirLister()), + massifs.WithDirCacheMassifLister(localmassifs.NewDirLister()), + massifs.WithDirCacheSealLister(localmassifs.NewDirLister()), massifs.WithReaderOption(massifs.WithMassifHeight(uint8(massifHeight))), massifs.WithReaderOption(massifs.WithSealGetter(&localReader)), massifs.WithReaderOption(massifs.WithCBORCodec(cmd.cborCodec)), @@ -330,7 +328,7 @@ func NewVerifiedReplica( // verification will fail with a suitable error. pemString := cCtx.String("sealer-key") if pemString != "" { - pem, err := DecodeECDSAPublicString(pemString) + pem, err := keyio.DecodeECDSAPublicString(pemString) if err != nil { return nil, err } @@ -349,7 +347,7 @@ func NewVerifiedReplica( return &VerifiedReplica{ cCtx: cCtx, log: logger.Sugar, - writeOpener: NewFileWriteOpener(), + writeOpener: localmassifs.NewFileWriteOpener(), localReader: &localReader, remoteReader: &remoteReader, rootReader: &cmd.rootReader, @@ -364,8 +362,8 @@ func NewVerifiedReplica( // interesting. func (v *VerifiedReplica) ReplicateVerifiedUpdates( ctx context.Context, - tenantIdentity string, startMassif, endMassif uint32) error { - + tenantIdentity string, startMassif, endMassif uint32, +) error { isNilOrNotFound := func(err error) bool { if err == nil { return true @@ -381,7 +379,6 @@ func (v *VerifiedReplica) ReplicateVerifiedUpdates( // on demand promotion of a v0 state to a v1 state, for compatibility with the consistency check. trustedBaseState := func(local *massifs.VerifiedContext) (massifs.MMRState, error) { - if local.MMRState.Version > int(massifs.MMRStateVersion0) { return local.MMRState, nil } @@ -447,7 +444,6 @@ func (v *VerifiedReplica) ReplicateVerifiedUpdates( // dealt with, local is always the predecessor. if local != nil { - // Start from the next massif after the last verified massif and do not // re-verify massifs we have already verified and replicated, if startMassif > local.Start.MassifIndex+1 { @@ -525,8 +521,8 @@ func (v *VerifiedReplica) ReplicateVerifiedUpdates( // This method has no side effects in the case where the remote and the local // are verified to be identical, the original local instance is retained. func (v *VerifiedReplica) replicateVerifiedContext( - local *massifs.VerifiedContext, remote *massifs.VerifiedContext) (*massifs.VerifiedContext, error) { - + local *massifs.VerifiedContext, remote *massifs.VerifiedContext, +) (*massifs.VerifiedContext, error) { if local == nil { return nil, v.localReader.ReplaceVerifiedContext(remote, v.writeOpener) } @@ -573,7 +569,6 @@ func (v *VerifiedReplica) replicateVerifiedContext( } func verifiedStateEqual(a *massifs.VerifiedContext, b *massifs.VerifiedContext) bool { - var err error // There is no difference in the log format between the two versions currently supported. @@ -661,7 +656,6 @@ func newWatchConfig(cCtx *cli.Context, cmd *CmdCtx) (WatchConfig, error) { } func readTenantMassifChanges(ctx context.Context, cCtx *cli.Context, cmd *CmdCtx) ([]TenantMassif, error) { - if cCtx.IsSet("latest") { // This is because people get tripped up with the `veracity watch -z 90000h | veracity replicate-logs` idiom, // Its such a common use case that we should just make it work. @@ -704,7 +698,6 @@ func readTenantMassifChanges(ctx context.Context, cCtx *cli.Context, cmd *CmdCtx } func NewPrefetchingSealReader(ctx context.Context, sealGetter massifs.SealGetter, tenantIdentity string, massifIndex uint32) (*prefetchingSealReader, error) { - msg, state, err := sealGetter.GetSignedRoot(ctx, tenantIdentity, massifIndex) if err != nil { return nil, err diff --git a/scitt/mandatory.go b/scitt/mandatory.go new file mode 100644 index 0000000..78d3331 --- /dev/null +++ b/scitt/mandatory.go @@ -0,0 +1,149 @@ +package scitt + +import ( + "errors" + "fmt" + + "github.com/datatrails/go-datatrails-common/cose" + commoncose "github.com/datatrails/go-datatrails-common/cose" +) + +const ( + ProblemTitleServiceSpecific = "Service Specific" + ProblemTitleOperationNotFound = "Operation Not Found" + ProblemTitleOperationFailed = "Operation Failed" + ProblemTitleTransient = "Transient Service Issue" + ProblemTitleRejected = "Rejected" + ProblemTitleToManyRequests = "To Many Requests" + ProblemTitleConfirmationMissing = "Confirmation Missing" + ProblemInstanceRejectedByRegistrationPolicy = "urn:ietf:params:scitt:error:signed-statement:rejected-by-registration-policy" + ProblemInstanceConfirmationMissing = "urn:ietf:params:scitt:error:signed-statement:confirmation-missing" + ProblemInstanceToManyRequests = "urn:ietf:params:scitt:error:tooManyRequests" + ProblemInstanceTransientAndInternal = "urn:ietf:params:scitt:error:transient-and-internal" + ProblemInstanceServiceSpecific = "urn:ietf:params:scitt:error:service-specific" + ProblemInstanceNotFound = "urn:ietf:params:scitt:error:notFound" +) + +// mandatory checks required of any transparency service on registration + +type CheckedStatement struct { + Claims *cose.CWTClaims + Statement *cose.CoseSign1Message +} + +type RegistrationPolicy struct { + RequireCNFPublic bool + // We do not support x509 verification at this time + RequireX509 bool + AllowUnverified bool +} + +// RegistrationPolicyUnverified returns a RegistrationPolicy that allows unverified statements. +// And can be used to obtain decoded statements that otherwise pass the mandatory checks. +func RegistrationPolicyUnverified() RegistrationPolicy { + return RegistrationPolicy{ + RequireCNFPublic: false, + RequireX509: false, + AllowUnverified: true, + } +} + +func RegistrationPolicyVerified() RegistrationPolicy { + return RegistrationPolicy{ + RequireCNFPublic: true, + RequireX509: false, + AllowUnverified: false, + } +} + +func RegistrationMandatoryChecks( + signedStatement []byte, + policy RegistrationPolicy, +) (CheckedStatement, *ConciseProblemDetails) { + if policy.RequireX509 { + return CheckedStatement{}, &ConciseProblemDetails{ + Title: ProblemTitleRejected, + Detail: "Signed Statement not accepted by the current Registration Policy. X509 verification is not supported", + Instance: ProblemInstanceRejectedByRegistrationPolicy, + ResponseCode: CoAPBadRequest, + } + } + + // cbor decode statement + statement, err := commoncose.NewCoseSign1MessageFromCBOR(signedStatement) + + if err != nil { + return CheckedStatement{}, &ConciseProblemDetails{ + Title: ProblemTitleRejected, + Detail: fmt.Sprintf("Signed Statement not accepted by the current Registration Policy. Not a valid COSE Sign1 message: %v", err), + Instance: ProblemInstanceRejectedByRegistrationPolicy, + ResponseCode: CoAPBadRequest, + } + } + // Begin: Mandatory Registration checks + + // verify cose_sign1 message: + // + // Per - https://ietf-wg-scitt.github.io/draft-ietf-scitt-architecture/draft-ietf-scitt-architecture.html#section-4.1.1.1 + // Registration "MUST, at a minimum, syntactically check the Issuer of the Signed Statement by cryptographically verifying the COSE signature according to" + + err = statement.VerifyWithCWTPublicKey(nil) + + // if the error is because there is no cwt issuer, ensure we communicate that + if errors.Is(err, commoncose.ErrCWTClaimsNoIssuer) { + return CheckedStatement{}, &ConciseProblemDetails{ + Title: ProblemTitleRejected, + Detail: "Signed Statement not accepted by the current Registration Policy. issuer claim not present in CWT", + Instance: ProblemInstanceRejectedByRegistrationPolicy, + ResponseCode: CoAPBadRequest, + } + } + + if errors.Is(err, commoncose.ErrCWTClaimsNoSubject) { + return CheckedStatement{}, &ConciseProblemDetails{ + Title: ProblemTitleRejected, + Detail: "Signed Statement not accepted by the current Registration Policy. subject claim not present in CWT", + Instance: ProblemInstanceRejectedByRegistrationPolicy, + ResponseCode: CoAPBadRequest, + } + } + + // if the error is because there is no cwt verification key, ensure we communicate that + if errors.Is(err, commoncose.ErrCWTClaimsNoCNF) { + + if policy.RequireCNFPublic || !policy.AllowUnverified { + return CheckedStatement{}, &ConciseProblemDetails{ + Title: ProblemTitleConfirmationMissing, + Detail: fmt.Sprintf("Signed Statement did not contain proof of possession: %v", err), + Instance: ProblemInstanceConfirmationMissing, + ResponseCode: CoAPBadRequest, + } + } + err = nil + } + + if err != nil { + return CheckedStatement{}, &ConciseProblemDetails{ + Title: ProblemTitleRejected, + Detail: fmt.Sprintf("Signed Statement not accepted by the current Registration Policy. Verification failed: %v", err), + Instance: ProblemInstanceRejectedByRegistrationPolicy, + ResponseCode: CoAPBadRequest, + } + } + + cwtClaims, err := statement.CWTClaimsFromProtectedHeader() + if err != nil { + return CheckedStatement{}, &ConciseProblemDetails{ + Title: ProblemTitleRejected, + Detail: fmt.Sprintf("Signed Statement not accepted by the current Registration Policy. CWT Claims missing or invalid: %v", err), + Instance: ProblemInstanceRejectedByRegistrationPolicy, + ResponseCode: CoAPBadRequest, + } + } + // END: Mandatory Registration checks + + return CheckedStatement{ + Claims: cwtClaims, + Statement: statement, + }, nil +} diff --git a/scitt/rfc9290.go b/scitt/rfc9290.go new file mode 100644 index 0000000..fcaf636 --- /dev/null +++ b/scitt/rfc9290.go @@ -0,0 +1,120 @@ +package scitt + +import ( + "github.com/fxamacker/cbor/v2" +) + +// public scitt support for https://www.rfc-editor.org/rfc/rfc9290.html + +const ( + RFC9290MediaType = "application/concise-problem-details+cbor" +) + +const ( + + // success coap codes https://www.rfc-editor.org/rfc/rfc7252#section-12.1.2 + CoAPCreated = 201 + CoAPDeleted = 202 + CoAPValid = 203 + CoAPChanged = 204 + CoAPContent = 205 + + // non-success coap codes per https://www.rfc-editor.org/rfc/rfc7252#section-12.1.2 + + CoAPBadRequest = 400 + CoAPUnauthorized = 401 + CoAPBadOption = 402 + CoAPForbidden = 403 + CoAPNotFound = 404 + CoAPMethodNotAllowed = 405 + CoAPNotAcceptable = 406 + CoAPPreConditionFailed = 412 + CoAPRequestEntityToLarge = 413 + CoAPUnsupportedContentFormat = 415 + CoAPInternalServerError = 500 + CoAPNotImplemented = 501 + CoAPBadGateway = 502 + CoAPServiceUnavailable = 503 + CoAPGatewayTimeout = 504 + CoAPProxyingNotSupported = 505 +) + +var ( + // Note: this value is established by code in the test TestProblemDetailsWriteResponseError + ProblemDetailsEncodingError = []byte{ + 163, 32, 116, 101, 114, 114, 111, 114, 32, 101, 110, 99, 111, 100, + 105, 110, 103, 32, 101, 114, 114, 111, 114, 33, 120, 58, 84, 104, + 105, 115, 32, 105, 115, 32, 97, 32, 115, 101, 114, 118, 101, 114, + 32, 101, 114, 114, 111, 114, 32, 101, 110, 99, 111, 100, 105, 110, + 103, 32, 116, 104, 101, 32, 112, 114, 111, 98, 108, 101, 109, 32, + 100, 101, 116, 97, 105, 108, 115, 32, 105, 116, 115, 101, 108, 102, + 35, 25, 1, 244, + } + + problemDetailsEncodingError = ConciseProblemDetails{ + Title: "error encoding error", + Detail: "This is a server error encoding the problem details itself", + ResponseCode: CoAPInternalServerError, + } + + CoAPResponseCodes = map[uint]bool{ + CoAPCreated: true, + CoAPDeleted: true, + CoAPValid: true, + CoAPChanged: true, + CoAPContent: true, + CoAPBadRequest: true, + CoAPUnauthorized: true, + CoAPBadOption: true, + CoAPForbidden: true, + CoAPNotFound: true, + CoAPMethodNotAllowed: true, + CoAPNotAcceptable: true, + CoAPPreConditionFailed: true, + CoAPRequestEntityToLarge: true, + CoAPUnsupportedContentFormat: true, + CoAPInternalServerError: true, + CoAPNotImplemented: true, + CoAPBadGateway: true, + CoAPServiceUnavailable: true, + CoAPGatewayTimeout: true, + CoAPProxyingNotSupported: true, + } +) + +// ConciseProblemDetails encodes information about an error according to RFC 9260 +// See https://www.rfc-editor.org/rfc/rfc9290.html +type ConciseProblemDetails struct { + Title string `cbor:"-1,keyasint,omitempty"` + Detail string `cbor:"-2,keyasint,omitempty"` + Instance string `cbor:"-3,keyasint,omitempty"` + ResponseCode uint64 `cbor:"-4,keyasint,omitempty"` + BaseUri string `cbor:"-5,keyasint,omitempty"` + BaseRtl string `cbor:"-6,keyasint,omitempty"` +} + +func (p ConciseProblemDetails) MustMarshalCBOR() []byte { + content, err := cbor.Marshal(p) + if err != nil { + content = ProblemDetailsEncodingError + } + + return content +} + +// ProblemDetailsMarshal marshals a problem details from the +// provided arguments If there is an error marshaling the error, a pre encoded +// problem details for that situation is returned +func ProblemDetailsMarshal(title, detail string, responseCode uint64) []byte { + problem := ConciseProblemDetails{ + Title: title, + Detail: detail, + ResponseCode: responseCode, + } + content, err := cbor.Marshal(&problem) + if err != nil { + content = ProblemDetailsEncodingError + } + + return content +} diff --git a/scitt/scitt.go b/scitt/scitt.go new file mode 100644 index 0000000..52abad6 --- /dev/null +++ b/scitt/scitt.go @@ -0,0 +1,78 @@ +// Package scitt signed statement conveniences +package scitt + +import ( + "crypto/sha256" + "errors" + "fmt" + "os" + + "github.com/datatrails/veracity/mmriver" +) + +const ( + ExtraBytesSize = 24 +) + +// MMRStatement prepares the details necessary for registering a signed +// statement on a localy forked datatrails ledger replica +type MMRStatement struct { + CheckedStatement + // Content is the signed statement raw cbor bytes exactly as read or provide + Content []byte + // Hash is th sha256 hash of the Statement + Hash []byte + // LeafHash is the MMR ledger defined leaf hash that is added to the ledger + LeafHash []byte + // ExtraBytes are the application contribution to the leaf hash. In the case + // of this pseudo scitt support, it is the first 24 bytes of the Hash + ExtraBytes []byte + // The IDTimestamp that contributed to the leaf hash. + IDTimestamp uint64 + LeafIndex uint64 +} + +type idTimetampGenerator interface { + NextID() (uint64, error) +} + +func NewMMRStatementFromFile(fileName string, idState idTimetampGenerator, policy RegistrationPolicy) (*MMRStatement, *ConciseProblemDetails, error) { + m := &MMRStatement{} + + content, err := os.ReadFile(fileName) + if err != nil { + return nil, nil, fmt.Errorf("failed to read file %s: %w", fileName, err) + } + + var cpd *ConciseProblemDetails + m.CheckedStatement, cpd = RegistrationMandatoryChecks(content, policy) + if cpd != nil { + return nil, cpd, fmt.Errorf("failed mandatory registration checks: %s", cpd.Detail) + } + + m.Content = content + hasher := sha256.New() + n, err := hasher.Write(m.Content) + if err != nil { + return nil, nil, err + } + if n != len(m.Content) { + return nil, nil, errors.New("hashed to few bytes") + } + m.Hash = hasher.Sum(nil) + + // Could use the hash bytes for content addressibility, but its primarily a scitt demo so use subject, but only the first 24 bytes + // m.ExtraBytes = m.Hash[:ExtraBytesSize] + m.ExtraBytes = mmriver.TrimExtraBytes([]byte(m.Claims.Subject)) + + m.IDTimestamp, err = idState.NextID() + if err != nil { + return nil, nil, fmt.Errorf("failed to generate snowflake id: %w", err) + } + + m.LeafHash, err = mmriver.MMREntryVersion1(m.ExtraBytes, m.IDTimestamp, m.Content) + if err != nil { + return nil, nil, err + } + return m, nil, nil +} diff --git a/tests/append/append_test.go b/tests/append/append_test.go new file mode 100644 index 0000000..1d8f391 --- /dev/null +++ b/tests/append/append_test.go @@ -0,0 +1,30 @@ +package append + +import ( + "os" + + "github.com/datatrails/veracity" +) + +// Test that +func (s *AppendCmdSuite) TestAppendCCFSignedStatement() { + // replicaDir := s.T().TempDir() + + var err error + + err = os.Chdir("/Users/robin/Desktop/personal/ietf/data") + s.Require().NoError(err, "should be able to change directory to /Users/robin/Desktop/ietf/data") + + app := veracity.NewApp("tests", true) + veracity.AddCommands(app, true) + + err = app.Run([]string{ + "veracity", + "-t", "tenant/6a009b40-eb55-4159-81f0-69024f89f53c", + // "-l", "v1/mmrs/tenant/6a009b40-eb55-4159-81f0-69024f89f53c/0/massifs/0000000000000000.log", + "-l", "/Users/robin/Desktop/personal/ietf/data", + // "append" , "--generate-sealer-key", + "append", "--sealer-key", "ecdsa-key-private.cbor", "--signed-statement", "in-toto.json.hashenvelope.cose.empty_uhdr", + }) + s.NoError(err) +} diff --git a/tests/append/suite_test.go b/tests/append/suite_test.go new file mode 100644 index 0000000..1541438 --- /dev/null +++ b/tests/append/suite_test.go @@ -0,0 +1,23 @@ +// Package replicatelogs provides a test suite for the ReplicateLogs command. +package append + +import ( + "testing" + + "github.com/datatrails/veracity/tests" + "github.com/stretchr/testify/suite" +) + +type AppendCmdSuite struct { + tests.IntegrationTestSuite +} + +func (s *AppendCmdSuite) SetupSuite() { + s.IntegrationTestSuite.SetupSuite() + // ensure we have the azurite config in the env for all the tests so that --envauth always uses the emulator + s.EnsureAzuriteEnv() +} + +func TestAppendCmdSuite(t *testing.T) { + suite.Run(t, new(AppendCmdSuite)) +} diff --git a/tests/replicatelogs/suite_test.go b/tests/replicatelogs/suite_test.go index 78b4438..771a4d1 100644 --- a/tests/replicatelogs/suite_test.go +++ b/tests/replicatelogs/suite_test.go @@ -1,3 +1,4 @@ +// Package replicatelogs provides a test suite for the ReplicateLogs command. package verifyconsistency import ( @@ -18,6 +19,5 @@ func (s *ReplicateLogsCmdSuite) SetupSuite() { } func TestReplicateLogsCmdSuite(t *testing.T) { - suite.Run(t, new(ReplicateLogsCmdSuite)) } diff --git a/verifyincluded_test.go b/verifyincluded_test.go index ba87bea..dac31c9 100644 --- a/verifyincluded_test.go +++ b/verifyincluded_test.go @@ -20,7 +20,6 @@ import ( // the first entry is a known assetsv2 events // the seconds entry is a known eventsv1 event func testMassifContext(t *testing.T) *massifs.MassifContext { - start := massifs.MassifStart{ MassifHeight: 3, } @@ -80,19 +79,16 @@ type fakeMassifGetter struct { // // one assetsv2 event entry and one eventsv1 entry func NewFakeMassifGetter(t *testing.T) *fakeMassifGetter { - massifContext := testMassifContext(t) return &fakeMassifGetter{ t: t, massifContext: massifContext, } - } // NewFakeMassifGetterInvalidRoot creates a new massif getter that has an incorrect massif root func NewFakeMassifGetterInvalidRoot(t *testing.T) *fakeMassifGetter { - massifContext := testMassifContext(t) // a massif context with 2 entries has its root at index 2 @@ -172,7 +168,6 @@ func TestVerifyAssetsV2Event(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - logTenant, err := test.event.LogTenant() require.Nil(t, err) From ccce8b560af00e0655807eb1f24073aff204741e Mon Sep 17 00:00:00 2001 From: Robin Bryce Date: Thu, 10 Jul 2025 19:35:20 +0100 Subject: [PATCH 3/3] fix: off by one determining the mmr index of new statements --- append.go | 83 ++++++++++++++++++++++++++++-------- localmassifs/localreader.go | 23 +++++++++- localmassifs/readverified.go | 20 ++++++--- scitt/scitt.go | 5 ++- 4 files changed, 104 insertions(+), 27 deletions(-) diff --git a/append.go b/append.go index 55c5bda..80cdf1f 100644 --- a/append.go +++ b/append.go @@ -1,6 +1,7 @@ package veracity import ( + "bytes" "context" "crypto/ecdsa" "crypto/elliptic" @@ -190,8 +191,8 @@ func NewAppendCmd() *cli.Command { return fmt.Errorf("failed to read verified head massif: %w", err) } - mmrSizeLast := verified.RangeCount() - fmt.Printf("%8d verified-size\n", mmrSizeLast) + mmrSizeOrig := verified.RangeCount() + fmt.Printf("%8d verified-size\n", mmrSizeOrig) verified.Tags = map[string]string{} // @@ -202,6 +203,14 @@ func NewAppendCmd() *cli.Command { return err } fmt.Printf("%d statements registered\n", len(statements)) + mmrSizeNew := verified.RangeCount() + peakHashesNew, err := mmr.PeakHashes(&verified.MassifContext, mmrSizeNew-1) + if err != nil { + return err + } + for i, peak := range peakHashesNew { + fmt.Printf("peak[%d]: %x\n", i, peak) + } alg, err := commoncose.CoseAlgForEC(sealingKey.PublicKey) if err != nil { @@ -232,7 +241,8 @@ func NewAppendCmd() *cli.Command { // To create the checkpoint, we first check that the current state // contains the previously verified state. This necessarily produces - // the proof material which we can then include with the new checkpoint. + // and verifies the new accumulator which we can then include with + // the new checkpoint. ok, peaksB, err := mmr.CheckConsistency( verified, sha256.New(), cp.MMRSizeA, cp.MMRSizeB, verified.MMRState.Peaks) @@ -295,11 +305,11 @@ func NewAppendCmd() *cli.Command { // *any* leaf at any time, given only the specific massif (tile) // that leaf was registered in. and its associated checkpoint. // - proof, err := mmr.InclusionProof(&verified.MassifContext, state.MMRSize-1, mmrStatement.LeafIndex) + proof, err := mmr.InclusionProof(&verified.MassifContext, state.MMRSize-1, mmrStatement.MMRIndexLeaf) if err != nil { return fmt.Errorf( "failed to generating inclusion proof: %d in MMR(%d), %v", - mmrStatement.LeafIndex, verified.MMRState.MMRSize, err) + mmrStatement.MMRIndexLeaf, verified.MMRState.MMRSize, err) } // @@ -333,6 +343,15 @@ func NewAppendCmd() *cli.Command { err, state.MMRSize) } + // To avoid creating invalid receipts due to bugs in this demo code, check the root matches the appropriate peak. + root := mmr.IncludedRoot(sha256.New(), mmrStatement.MMRIndexLeaf, mmrStatement.LeafHash, proof) + + if !bytes.Equal(root, peaksB[peakIndex]) { + return fmt.Errorf( + "%w: root %x of leaf %d in MMR(%d) does not match peak %d %x", + ErrVerifyInclusionFailed, root, mmrStatement.MMRIndexLeaf, state.MMRSize, peakIndex, state.Peaks[peakIndex]) + } + // // Make the MMR draft receipt by attaching the inclusion proof to the Unprotected header // @@ -340,13 +359,25 @@ func NewAppendCmd() *cli.Command { verifiableProofs := massifs.MMRiverVerifiableProofs{ InclusionProofs: []massifs.MMRiverInclusionProof{{ - Index: mmrStatement.LeafIndex, + Index: mmrStatement.MMRIndexLeaf, InclusionPath: proof, }}, } - signed.Headers.Unprotected[massifs.VDSCoseReceiptProofsTag] = verifiableProofs + tagOriginSubject := int64(-257) + tagOriginIssuer := tagOriginSubject - 1 + tagLeafHash := tagOriginSubject - 2 + tagIDTimestamp := tagOriginSubject - 3 + tagExtraBytes := tagOriginSubject - 4 + signed.Headers.Unprotected[massifs.VDSCoseReceiptProofsTag] = verifiableProofs + // these values would usually be provided by the application, or obtained directly from any replica. + // the unprotected headers are not signed, and are intended for this sort of convenience. + signed.Headers.Unprotected[tagOriginIssuer] = mmrStatement.Claims.Issuer + signed.Headers.Unprotected[tagOriginSubject] = mmrStatement.Claims.Subject + signed.Headers.Unprotected[tagIDTimestamp] = mmrStatement.IDTimestamp + signed.Headers.Unprotected[tagExtraBytes] = mmrStatement.ExtraBytes + signed.Headers.Unprotected[tagLeafHash] = mmrStatement.LeafHash // // Save the receipt to a file // @@ -357,7 +388,7 @@ func NewAppendCmd() *cli.Command { receiptFileName := cCtx.String("receipt-file") if receiptFileName == "" { - receiptFileName = fmt.Sprintf("receipt-%d.cbor", mmrStatement.LeafIndex) + receiptFileName = fmt.Sprintf("receipt-%d.cbor", mmrStatement.MMRIndexLeaf) } if err := os.WriteFile(receiptFileName, receiptCbor, os.FileMode(0644)); err != nil { return fmt.Errorf("failed to write receipt file %s: %w", receiptFileName, err) @@ -399,12 +430,13 @@ func NewAppendCmd() *cli.Command { fmt.Printf("wrote sealer key to file %s\n", sealerKeyFile) sealerKeyFile = cCtx.String("sealer-public-key-pem") - if sealerKeyFile != "" { - if _, err := keyio.WriteCoseECDSAPublicKey(sealerKeyFile, &sealingKey.PublicKey); err != nil { - return fmt.Errorf("failed to write sealer key to file %s: %w", sealerKeyFile, err) - } - fmt.Printf("wrote sealer public key to file %s\n", sealerKeyFile) + if sealerKeyFile == "" { + sealerKeyFile = keyio.ECDSAPublicDefaultPEMFileName } + if _, err := keyio.WriteCoseECDSAPublicKey(sealerKeyFile, &sealingKey.PublicKey); err != nil { + return fmt.Errorf("failed to write sealer key to file %s: %w", sealerKeyFile, err) + } + fmt.Printf("wrote sealer public key to file %s\n", sealerKeyFile) } return nil }, @@ -440,7 +472,9 @@ func addStatements(cmd *CmdCtx, cCtx *cli.Context, massif *massifs.MassifContext if err != nil { return nil, fmt.Errorf("failed to read signed statement from file %s: %w", cCtx.String("signed-statement"), err) } - mmrStatement.LeafIndex = massif.RangeCount() - 1 + + // the *next* index to be added is the current *count* + mmrStatement.MMRIndexLeaf = massif.RangeCount() _, err = massif.AddHashedLeaf( sha256.New(), @@ -457,10 +491,23 @@ func addStatements(cmd *CmdCtx, cCtx *cli.Context, massif *massifs.MassifContext statements = append(statements, *mmrStatement) - fmt.Printf("index : %d\n", mmrStatement.LeafIndex) - fmt.Printf(" issuer : %s\n", mmrStatement.Claims.Issuer) - fmt.Printf(" leaf-hash : %x\n", mmrStatement.LeafHash) - fmt.Printf(" statement-hash: %x\n", mmrStatement.Hash) + fmt.Printf("index : %d\n", mmrStatement.MMRIndexLeaf) + fmt.Printf(" issuer : %s\n", mmrStatement.Claims.Issuer) + fmt.Printf(" idtimestamp : %x\n", mmrStatement.IDTimestamp) + fmt.Printf(" extraBytes : %x\n", mmrStatement.ExtraBytes) + fmt.Printf(" leaf-hash : %x\n", mmrStatement.LeafHash) + fmt.Printf(" statement-hash : %x\n", mmrStatement.Hash) + fmt.Printf(" node count : %d\n", (len(massif.Data)-int(massif.LogStart()))/32) + + value, err := massif.Get(mmrStatement.MMRIndexLeaf) + if err != nil { + return nil, fmt.Errorf("failed to get leaf value for index %d: %w", mmrStatement.MMRIndexLeaf, err) + } + if !bytes.Equal(value, mmrStatement.LeafHash) { + // this will mean a bug in the hacked up code if it catches + return nil, fmt.Errorf("leaf hash %x does not match expected value %x for index %d", + value, mmrStatement.LeafHash, mmrStatement.MMRIndexLeaf) + } } return statements, nil } diff --git a/localmassifs/localreader.go b/localmassifs/localreader.go index f205247..b5612dc 100644 --- a/localmassifs/localreader.go +++ b/localmassifs/localreader.go @@ -6,6 +6,7 @@ import ( "io" "os" "path/filepath" + "strings" "github.com/datatrails/go-datatrails-merklelog/massifs" ) @@ -49,11 +50,17 @@ func (o *StdinOpener) Open(string) (io.ReadCloser, error) { // Utilities to remove the os dependencies from the MassifReader type OsDirLister struct{} +type SuffixDirLister struct { + OsDirLister + Suffix string +} func NewDirLister() massifs.DirLister { return &OsDirLister{} } - +func NewSuffixDirLister(suffix string) massifs.DirLister { + return &SuffixDirLister{Suffix: suffix} +} func (*OsDirLister) ListFiles(name string) ([]string, error) { dpath, err := filepath.Abs(name) if err != nil { @@ -72,3 +79,17 @@ func (*OsDirLister) ListFiles(name string) ([]string, error) { } return result, nil } + +func (s *SuffixDirLister) ListFiles(name string) ([]string, error) { + found, err := s.OsDirLister.ListFiles(name) + if err != nil { + return nil, err + } + var matched []string + for _, f := range found { + if strings.HasSuffix(f, s.Suffix) { + matched = append(matched, f) + } + } + return matched, nil +} diff --git a/localmassifs/readverified.go b/localmassifs/readverified.go index db39aa5..443f702 100644 --- a/localmassifs/readverified.go +++ b/localmassifs/readverified.go @@ -38,8 +38,8 @@ func ReadVerifiedHeadMassif(options ReaderOptions, baseOpts ...massifs.DirCacheO var err error opts := []massifs.DirCacheOption{ - massifs.WithDirCacheMassifLister(NewDirLister()), - massifs.WithDirCacheSealLister(NewDirLister()), + massifs.WithDirCacheMassifLister(NewSuffixDirLister(".log")), + massifs.WithDirCacheSealLister(NewSuffixDirLister(".sth")), massifs.WithReaderOption(massifs.WithMassifHeight(options.MassifHeight)), massifs.WithReaderOption(massifs.WithCBORCodec(options.CBORCodec)), } @@ -64,12 +64,20 @@ func ReadVerifiedHeadMassif(options ReaderOptions, baseOpts ...massifs.DirCacheO massifInfo := massifCache.GetInfo() fmt.Printf("Read massif %d to %d from %s\n", massifInfo.FirstMassifIndex, massifInfo.HeadMassifIndex, massifInfo.Directory) - sealCache, err := cache.ReadSealDirEntry(options.SealsDir) - if err != nil { - return nil, fmt.Errorf("failed to read massif dir entry: %w", err) + var sealCache massifs.DirCacheEntry + + if options.MassifsDir == options.SealsDir { + sealCache = massifCache + err = cache.FindSealFiles(options.MassifsDir) + } else { + sealCache, err = cache.ReadSealDirEntry(options.SealsDir) + if err != nil { + return nil, fmt.Errorf("failed to read massif dir entry: %w", err) + } } + sealInfo := sealCache.GetInfo() - fmt.Printf("Read seals for massifs %d to %d from %s\n", sealInfo.FirstMassifIndex, sealInfo.HeadMassifIndex, sealInfo.Directory) + fmt.Printf("Read seals for massifs %d to %d from %s\n", sealInfo.FirstSealIndex, sealInfo.HeadSealIndex, sealInfo.Directory) if err != nil { return nil, fmt.Errorf("failed to read massif dir entry: %w", err) } diff --git a/scitt/scitt.go b/scitt/scitt.go index 52abad6..c815eb2 100644 --- a/scitt/scitt.go +++ b/scitt/scitt.go @@ -28,8 +28,8 @@ type MMRStatement struct { // of this pseudo scitt support, it is the first 24 bytes of the Hash ExtraBytes []byte // The IDTimestamp that contributed to the leaf hash. - IDTimestamp uint64 - LeafIndex uint64 + IDTimestamp uint64 + MMRIndexLeaf uint64 } type idTimetampGenerator interface { @@ -69,6 +69,7 @@ func NewMMRStatementFromFile(fileName string, idState idTimetampGenerator, polic if err != nil { return nil, nil, fmt.Errorf("failed to generate snowflake id: %w", err) } + // m.IDTimestamp = 0 // XXX: temporary stabilize the hash m.LeafHash, err = mmriver.MMREntryVersion1(m.ExtraBytes, m.IDTimestamp, m.Content) if err != nil {