add pdf command, show block time in verify command
This commit is contained in:
374
cmd/ots/pdf.go
Normal file
374
cmd/ots/pdf.go
Normal file
@@ -0,0 +1,374 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.intruders.space/public/opentimestamps"
|
||||
"git.intruders.space/public/opentimestamps/ots"
|
||||
"git.intruders.space/public/opentimestamps/verifyer"
|
||||
"github.com/jung-kurt/gofpdf"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
// PDF command flags
|
||||
pdfOutput string
|
||||
pdfIncludeContent bool
|
||||
pdfTitle string
|
||||
pdfComment string
|
||||
pdfEsploraURL string
|
||||
)
|
||||
|
||||
// pdfCmd represents the pdf command
|
||||
var pdfCmd = &cobra.Command{
|
||||
Use: "pdf [flags] <file> <file.ots>",
|
||||
Short: "Generate a PDF certificate for a timestamp",
|
||||
Long: `Generate a PDF certificate that explains how to verify the timestamp.
|
||||
The PDF includes all necessary information such as hashes, operations,
|
||||
and instructions for manual verification.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: runPdfCmd,
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Local flags for the pdf command
|
||||
pdfCmd.Flags().StringVarP(&pdfOutput, "output", "o", "", "Output PDF filename (default: original filename with .pdf extension)")
|
||||
pdfCmd.Flags().BoolVar(&pdfIncludeContent, "include-content", false, "Include the original file content in the PDF (for text files only)")
|
||||
pdfCmd.Flags().StringVar(&pdfTitle, "title", "OpenTimestamps Certificate", "Title for the PDF document")
|
||||
pdfCmd.Flags().StringVar(&pdfComment, "comment", "", "Additional comment to include in the certificate")
|
||||
pdfCmd.Flags().StringVar(&pdfEsploraURL, "esplora", "https://blockstream.info/api", "URL of Esplora API for fetching block information")
|
||||
}
|
||||
|
||||
func runPdfCmd(cmd *cobra.Command, args []string) {
|
||||
filePath := args[0]
|
||||
otsPath := args[1]
|
||||
|
||||
// Determine output file path
|
||||
outputPath := pdfOutput
|
||||
if outputPath == "" {
|
||||
outputPath = filePath + ".certificate.pdf"
|
||||
}
|
||||
|
||||
// Read the original file
|
||||
fileData, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
slog.Error("Failed to read file", "file", filePath, "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Compute the file digest
|
||||
fileDigest := sha256.Sum256(fileData)
|
||||
slog.Debug("Computed file digest", "digest", hex.EncodeToString(fileDigest[:]))
|
||||
|
||||
// Read and parse the OTS file
|
||||
otsData, err := os.ReadFile(otsPath)
|
||||
if err != nil {
|
||||
slog.Error("Failed to read OTS file", "file", otsPath, "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
timestampFile, err := opentimestamps.ReadFromFile(otsData)
|
||||
if err != nil {
|
||||
slog.Error("Failed to parse OTS file", "file", otsPath, "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check if the digests match
|
||||
if !bytes.Equal(fileDigest[:], timestampFile.Digest) {
|
||||
slog.Warn("Warning: File digest doesn't match timestamp digest",
|
||||
"file_digest", hex.EncodeToString(fileDigest[:]),
|
||||
"timestamp_digest", hex.EncodeToString(timestampFile.Digest))
|
||||
}
|
||||
|
||||
// Extract Bitcoin attestations
|
||||
bitcoinSeqs := timestampFile.GetBitcoinAttestedSequences()
|
||||
if len(bitcoinSeqs) == 0 {
|
||||
slog.Warn("No Bitcoin attestations found in timestamp, certificate will be for pending timestamps")
|
||||
}
|
||||
|
||||
// Create Bitcoin interface for block time lookup
|
||||
bitcoin := verifyer.NewEsploraClient(pdfEsploraURL, 30*time.Second)
|
||||
slog.Info("Using Esplora API for block information", "url", pdfEsploraURL)
|
||||
|
||||
// Generate the PDF
|
||||
err = generateVerificationPDF(
|
||||
outputPath,
|
||||
filePath,
|
||||
fileData,
|
||||
fileDigest[:],
|
||||
timestampFile,
|
||||
bitcoinSeqs,
|
||||
bitcoin,
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error("Failed to generate PDF", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
slog.Info("PDF certificate generated successfully", "file", outputPath)
|
||||
}
|
||||
|
||||
func generateVerificationPDF(outputPath, filePath string, fileData []byte, fileDigest []byte,
|
||||
timestamp *ots.File, bitcoinSeqs []ots.Sequence, bitcoin verifyer.Bitcoin) error {
|
||||
|
||||
// Create a new PDF
|
||||
pdf := gofpdf.New("P", "mm", "A4", "")
|
||||
pdf.SetTitle(pdfTitle, true)
|
||||
pdf.SetAuthor("OpenTimestamps", true)
|
||||
pdf.SetCreator("OpenTimestamps CLI", true)
|
||||
|
||||
// Set margins
|
||||
pdf.SetMargins(25, 25, 25)
|
||||
pdf.SetAutoPageBreak(true, 25)
|
||||
|
||||
// Add first page - Certificate
|
||||
pdf.AddPage()
|
||||
|
||||
// Title
|
||||
pdf.SetFont("Helvetica", "B", 24)
|
||||
pdf.CellFormat(0, 12, pdfTitle, "", 1, "C", false, 0, "")
|
||||
pdf.Ln(10)
|
||||
|
||||
// Timestamp information section
|
||||
pdf.SetFont("Helvetica", "B", 18)
|
||||
pdf.CellFormat(0, 10, "Timestamp Information", "", 1, "L", false, 0, "")
|
||||
pdf.Ln(5)
|
||||
|
||||
pdf.SetFont("Helvetica", "B", 12)
|
||||
pdf.CellFormat(0, 8, "Filename:", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 12)
|
||||
pdf.CellFormat(0, 8, filepath.Base(filePath), "", 1, "L", false, 0, "")
|
||||
pdf.Ln(2)
|
||||
|
||||
// Timestamp date
|
||||
pdf.SetFont("Helvetica", "B", 12)
|
||||
if len(bitcoinSeqs) > 0 {
|
||||
// Find the earliest Bitcoin attestation
|
||||
var earliestHeight uint64
|
||||
var earliestBlockTime time.Time
|
||||
|
||||
for _, seq := range bitcoinSeqs {
|
||||
att := seq.GetAttestation()
|
||||
if earliestHeight == 0 || att.BitcoinBlockHeight < earliestHeight {
|
||||
earliestHeight = att.BitcoinBlockHeight
|
||||
|
||||
// Fetch the block time from the blockchain
|
||||
blockHash, err := bitcoin.GetBlockHash(int64(att.BitcoinBlockHeight))
|
||||
if err == nil {
|
||||
blockHeader, err := bitcoin.GetBlockHeader(blockHash)
|
||||
if err == nil {
|
||||
earliestBlockTime = time.Unix(int64(blockHeader.Timestamp.Unix()), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show Bitcoin timestamp
|
||||
pdf.CellFormat(0, 8, "Timestamp Created:", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 12)
|
||||
|
||||
if !earliestBlockTime.IsZero() {
|
||||
// If we successfully retrieved the block time, display it
|
||||
pdf.CellFormat(0, 8, fmt.Sprintf("Bitcoin Block #%d (%s)",
|
||||
earliestHeight, earliestBlockTime.Format("Jan 02, 2006 15:04:05 UTC")),
|
||||
"", 1, "L", false, 0, "")
|
||||
} else {
|
||||
// Fallback to just showing the block height
|
||||
pdf.CellFormat(0, 8, fmt.Sprintf("Bitcoin Block #%d", earliestHeight), "", 1, "L", false, 0, "")
|
||||
}
|
||||
} else {
|
||||
// Pending attestation
|
||||
pdf.CellFormat(0, 8, "Status:", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 12)
|
||||
pdf.CellFormat(0, 8, "Pending confirmation in the Bitcoin blockchain", "", 1, "L", false, 0, "")
|
||||
}
|
||||
pdf.Ln(2)
|
||||
|
||||
// File hash
|
||||
pdf.SetFont("Helvetica", "B", 12)
|
||||
pdf.CellFormat(0, 8, "File SHA-256 Digest:", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Courier", "", 10)
|
||||
pdf.CellFormat(0, 8, hex.EncodeToString(fileDigest), "", 1, "L", false, 0, "")
|
||||
pdf.Ln(5)
|
||||
|
||||
// Include comment if provided
|
||||
if pdfComment != "" {
|
||||
pdf.SetFont("Helvetica", "B", 12)
|
||||
pdf.CellFormat(0, 8, "Comment:", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 12)
|
||||
pdf.MultiCell(0, 8, pdfComment, "", "L", false)
|
||||
pdf.Ln(5)
|
||||
}
|
||||
|
||||
// Include file content if requested and looks like text
|
||||
if pdfIncludeContent && isTextFile(fileData) && len(fileData) < 5000 {
|
||||
pdf.SetFont("Helvetica", "B", 12)
|
||||
pdf.CellFormat(0, 8, "File Content:", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Courier", "", 10)
|
||||
content := string(fileData)
|
||||
content = strings.ReplaceAll(content, "\r\n", "\n")
|
||||
pdf.MultiCell(0, 5, content, "", "L", false)
|
||||
pdf.Ln(5)
|
||||
}
|
||||
|
||||
// Add Bitcoin transaction information for each attestation
|
||||
if len(bitcoinSeqs) > 0 {
|
||||
pdf.SetFont("Helvetica", "B", 18)
|
||||
pdf.CellFormat(0, 10, "Bitcoin Attestations", "", 1, "L", false, 0, "")
|
||||
pdf.Ln(5)
|
||||
|
||||
for i, seq := range bitcoinSeqs {
|
||||
att := seq.GetAttestation()
|
||||
|
||||
pdf.SetFont("Helvetica", "B", 14)
|
||||
pdf.CellFormat(0, 8, fmt.Sprintf("Attestation #%d", i+1), "", 1, "L", false, 0, "")
|
||||
pdf.Ln(2)
|
||||
|
||||
// Get block details
|
||||
blockHash, err := bitcoin.GetBlockHash(int64(att.BitcoinBlockHeight))
|
||||
blockTime := "Unknown"
|
||||
if err == nil {
|
||||
blockHeader, err := bitcoin.GetBlockHeader(blockHash)
|
||||
if err == nil {
|
||||
blockTime = blockHeader.Timestamp.Format("Jan 02, 2006 15:04:05 UTC")
|
||||
}
|
||||
}
|
||||
|
||||
pdf.SetFont("Helvetica", "B", 12)
|
||||
pdf.CellFormat(0, 8, "Bitcoin Block Height:", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 12)
|
||||
pdf.CellFormat(0, 8, fmt.Sprintf("%d", att.BitcoinBlockHeight), "", 1, "L", false, 0, "")
|
||||
|
||||
pdf.SetFont("Helvetica", "B", 12)
|
||||
pdf.CellFormat(0, 8, "Block Time:", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 12)
|
||||
pdf.CellFormat(0, 8, blockTime, "", 1, "L", false, 0, "")
|
||||
|
||||
pdf.SetFont("Helvetica", "B", 12)
|
||||
pdf.CellFormat(0, 8, "Block Hash:", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Courier", "", 10)
|
||||
if blockHash != nil {
|
||||
pdf.CellFormat(0, 8, blockHash.String(), "", 1, "L", false, 0, "")
|
||||
} else {
|
||||
pdf.CellFormat(0, 8, "Unable to retrieve", "", 1, "L", false, 0, "")
|
||||
}
|
||||
|
||||
// In a full implementation, we would show the Bitcoin merkle root and transaction ID
|
||||
// This would require additional API calls to get that information
|
||||
|
||||
// Show operations sequence
|
||||
pdf.SetFont("Helvetica", "B", 12)
|
||||
pdf.CellFormat(0, 8, "Verification Operations:", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Courier", "", 10)
|
||||
|
||||
// Format the operations
|
||||
var operationsText string
|
||||
merkleRoot, _ := seq.Compute(timestamp.Digest)
|
||||
|
||||
operationsText += fmt.Sprintf("Starting with digest: %s\n", hex.EncodeToString(timestamp.Digest))
|
||||
|
||||
for j, inst := range seq {
|
||||
if inst.Operation != nil {
|
||||
if inst.Operation.Binary {
|
||||
operationsText += fmt.Sprintf("%d. %s %s\n", j+1, inst.Operation.Name, hex.EncodeToString(inst.Argument))
|
||||
} else {
|
||||
operationsText += fmt.Sprintf("%d. %s\n", j+1, inst.Operation.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operationsText += fmt.Sprintf("\nResulting merkle root: %s\n", hex.EncodeToString(merkleRoot))
|
||||
|
||||
pdf.MultiCell(0, 5, operationsText, "", "L", false)
|
||||
pdf.Ln(5)
|
||||
}
|
||||
}
|
||||
|
||||
// Add second page - Verification Instructions
|
||||
pdf.AddPage()
|
||||
pdf.SetFont("Helvetica", "B", 18)
|
||||
pdf.CellFormat(0, 10, "Verification Instructions", "", 1, "L", false, 0, "")
|
||||
pdf.Ln(5)
|
||||
|
||||
// Instructions
|
||||
addVerificationInstructions(pdf)
|
||||
|
||||
// Save the PDF
|
||||
return pdf.OutputFileAndClose(outputPath)
|
||||
}
|
||||
|
||||
func addVerificationInstructions(pdf *gofpdf.Fpdf) {
|
||||
pdf.SetFont("Helvetica", "B", 14)
|
||||
pdf.CellFormat(0, 8, "1. Compute the SHA-256 digest of your file", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 12)
|
||||
pdf.MultiCell(0, 6, `To verify this timestamp, first compute the SHA-256 digest of your original file. You can use various tools for this:
|
||||
|
||||
- On Linux/macOS: Use the command "shasum -a 256 filename"
|
||||
- On Windows: Use tools like PowerShell's "Get-FileHash" or third-party tools
|
||||
- Online: Various websites can calculate file hashes, but only use trusted sources for sensitive files
|
||||
|
||||
Verify that the digest matches the one shown in this certificate.`, "", "L", false)
|
||||
pdf.Ln(5)
|
||||
|
||||
pdf.SetFont("Helvetica", "B", 14)
|
||||
pdf.CellFormat(0, 8, "2. Verify the operations sequence", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 12)
|
||||
pdf.MultiCell(0, 6, `The operations sequence shown in this certificate transforms the file digest into a value that was stored in the Bitcoin blockchain. To verify manually:
|
||||
|
||||
- Start with the file's SHA-256 digest
|
||||
- Apply each operation in sequence as shown in the certificate
|
||||
- For "append" operations, add the shown bytes to the end of the current value
|
||||
- For "prepend" operations, add the shown bytes to the beginning of the current value
|
||||
- For "sha256" operations, compute the SHA-256 hash of the current value
|
||||
- The final result should match the merkle root shown in the certificate`, "", "L", false)
|
||||
pdf.Ln(5)
|
||||
|
||||
pdf.SetFont("Helvetica", "B", 14)
|
||||
pdf.CellFormat(0, 8, "3. Verify the Bitcoin attestation", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 12)
|
||||
pdf.MultiCell(0, 6, `For full verification, check that the merkle root was recorded in the Bitcoin blockchain:
|
||||
|
||||
- Find the Bitcoin block at the height shown in the certificate
|
||||
- The block's merkle root or transaction should contain the computed value
|
||||
- You can use block explorers like blockstream.info or blockchain.com
|
||||
- The timestamp is confirmed when the merkle root appears in the blockchain`, "", "L", false)
|
||||
pdf.Ln(5)
|
||||
|
||||
pdf.SetFont("Helvetica", "B", 14)
|
||||
pdf.CellFormat(0, 8, "4. Use the OpenTimestamps verifier", "", 1, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 12)
|
||||
pdf.MultiCell(0, 6, `For easier verification, use the OpenTimestamps verification tools:
|
||||
|
||||
- Command line: Use "ots verify <file> <file.ots>"
|
||||
- Online: Visit https://opentimestamps.org and upload your file and .ots timestamp
|
||||
- The tools will perform all verification steps automatically`, "", "L", false)
|
||||
|
||||
// Add footer with generation time
|
||||
pdf.Ln(10)
|
||||
pdf.SetFont("Helvetica", "I", 10)
|
||||
pdf.CellFormat(0, 6, fmt.Sprintf("Certificate generated at: %s", time.Now().Format(time.RFC3339)), "", 1, "C", false, 0, "")
|
||||
pdf.CellFormat(0, 6, "OpenTimestamps - https://opentimestamps.org", "", 1, "C", false, 0, "")
|
||||
}
|
||||
|
||||
// isTextFile tries to determine if a file is likely to be text
|
||||
func isTextFile(data []byte) bool {
|
||||
// Simple heuristic: if the file doesn't contain too many non-printable characters,
|
||||
// it's probably text
|
||||
nonPrintable := 0
|
||||
for _, b := range data {
|
||||
if (b < 32 || b > 126) && b != 9 && b != 10 && b != 13 {
|
||||
nonPrintable++
|
||||
}
|
||||
}
|
||||
|
||||
// Allow up to 1% non-printable characters
|
||||
return nonPrintable <= len(data)/100
|
||||
}
|
||||
@@ -33,6 +33,7 @@ func init() {
|
||||
rootCmd.AddCommand(verifyCmd)
|
||||
rootCmd.AddCommand(upgradeCmd)
|
||||
rootCmd.AddCommand(infoCmd)
|
||||
rootCmd.AddCommand(pdfCmd)
|
||||
}
|
||||
|
||||
// setupLogging configures the global logger based on the flags
|
||||
|
||||
@@ -2,8 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.intruders.space/public/opentimestamps"
|
||||
@@ -137,8 +139,21 @@ func runVerifyCmd(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
att := seq.GetAttestation()
|
||||
|
||||
// Get the block time
|
||||
blockHash, err := bitcoin.GetBlockHash(int64(att.BitcoinBlockHeight))
|
||||
blockTime := ""
|
||||
if err == nil {
|
||||
blockHeader, err := bitcoin.GetBlockHeader(blockHash)
|
||||
if err == nil {
|
||||
// Format the block time
|
||||
blockTime = fmt.Sprintf(" (%s)", blockHeader.Timestamp.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
|
||||
slog.Info("Verification successful",
|
||||
"block_height", att.BitcoinBlockHeight)
|
||||
"block_height", att.BitcoinBlockHeight,
|
||||
"block_time", strings.TrimPrefix(blockTime, " "))
|
||||
|
||||
if tx != nil {
|
||||
slog.Info("Bitcoin transaction", "txid", tx.TxHash().String())
|
||||
|
||||
BIN
fixtures/flatearthers-united.txt.certificate.pdf
Normal file
BIN
fixtures/flatearthers-united.txt.certificate.pdf
Normal file
Binary file not shown.
1
go.mod
1
go.mod
@@ -5,6 +5,7 @@ go 1.24.2
|
||||
require (
|
||||
github.com/btcsuite/btcd v0.24.2
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
|
||||
github.com/jung-kurt/gofpdf v1.16.2
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
|
||||
9
go.sum
9
go.sum
@@ -1,4 +1,5 @@
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
||||
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
|
||||
@@ -63,6 +64,9 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=
|
||||
github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -73,9 +77,12 @@ github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
@@ -83,6 +90,7 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
@@ -95,6 +103,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
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/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
|
||||
Reference in New Issue
Block a user