From 02db2a1c01f1f5ff269d3f8cc37b503dc810b5d8 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 3 Jan 2020 16:27:36 +1100 Subject: [PATCH] refactor and use hashicorp's shamir implementation --- example/diary_1_of_5.horcrux | Bin 875 -> 889 bytes example/diary_2_of_5.horcrux | Bin 875 -> 889 bytes example/diary_3_of_5.horcrux | Bin 869 -> 883 bytes example/diary_4_of_5.horcrux | Bin 775 -> 789 bytes example/diary_5_of_5.horcrux | Bin 775 -> 789 bytes main.go | 182 ++++++++------------- pkg/multiplexing/multiplexing.go | 85 ++++++++++ pkg/shamir/shamir.go | 262 +++++++++++++++++++++++++++++++ pkg/shamir/tables.go | 79 ++++++++++ 9 files changed, 489 insertions(+), 119 deletions(-) create mode 100644 pkg/multiplexing/multiplexing.go create mode 100644 pkg/shamir/shamir.go create mode 100644 pkg/shamir/tables.go diff --git a/example/diary_1_of_5.horcrux b/example/diary_1_of_5.horcrux index 6e41ac35ecb19e301ffaacca7cc1356da45ca35f..f87073c9c30aefb7509edfc9c77b0d9af5b54f43 100644 GIT binary patch delta 524 zcmV+n0`vXr2KffCc>#YpFfukQB57`9Wq2YwF)SikFEiE8IPefTDEiDS?7FH+U`L#plZwYG(v_yaXR{Q z%%P1V1Kn-H^Vy9Hc&Gj+b!|v7`d**ffbA`=UO90Q1vh9pu9xQB_emp&9#;0x4z#K z&1(sfSJN$kwaw%*noE9`SIi<2hwshQKZ#+tV}71j_b&YR$0_Tyf~2^lFVWSGZOVlQ zS>2u4!*$ZH`*HtZ2ep1*7 z4!%vGXQzt~uH}=vM)S`22FG~ej*8DkfPbmtk44r3b=y$OYCR=@7;miM0`T7n{yyrb z|M5D!5&uY#YjH#jydB57`9Wq2YwF)SiSs*Pf3Xd%B7;0XU8b*Jciz4e|={I?6z4MFyf!+@+ytaSsQ}#eDO_p`2PgtHn zc_LD?M!Wvt{5~VqxK^%j$l<5;JJH#$)F+h}xkJ6qj6Zt61DZUSD@2@oxj?q?_zMx? zM=o{J?0C0g!OV=M7)a0nM$K{9GRGB%_-1zPTfRPFzi48CVu*d|UoG$c3#!G};s;yZ zM6ese5Fd+I)&GAiLUPOR<08i?uq9lV3c}a%-QBU`vAcYfNX|>XFZdoX1EYd1;3n)) z+10VoJM+*;^umOsrECTQZ{OsZvy!GKwL({*zpgQ5cfYIbL;NMFdC(v#Ard-z-UHDr z-&*UhU)^tq&a)EAVaSD==#?dmk7zfF77{#qPZgd}vH5k_*Tge7eOhQ<~jK5%x%X zsA59o2h~cZKXkKNX-fvIyy`+w&3>g_FuJe)(scvwV;35S+MPZ-rV>p&G-Qj^&O_@` diff --git a/example/diary_2_of_5.horcrux b/example/diary_2_of_5.horcrux index 1a0bfe6ff535ca8e5f396f7c0fbc5c4403f8dc62..941f8479e94a4ed825c124d7e9e8a210bba83982 100644 GIT binary patch delta 524 zcmV+n0`vXr2KffCc>#YpFfukQB57`9Wq2YwGAtr=Z**a7B04oJB6MhSWpik6Y-A!j zH7p`)WqC$&VP|b+Zge6#B1&d#PfT?(PF8SlHAzQMF;;gsXjnLDFiSZ!HBfL%V@7aN zGI>r{Oj1N}b0U2TEiE8IPefTDEiDQ$X>A9F_0iVyCQ>%>k4=A4E-ctMk1&ZADx3#k zoszRvG-#r+(ln-BbAZ52_a*cD5?fq%dHE|wZ0$+h3+{pb9X3INwhTRYW!yiVtC>Te z=0lj2lA;D5#HZVF<2QidUms?g95|qBGw|zIm{Ry=7AK+&_F-;HT~mA7Zl*roVp=N+BhA(#PNtP^d?8Dw#89 zBaL@yN2ytAqAZpx1VW^COw`rrLP&x}#BV$P;A=Hb{6_(b4l`hQA!7T`x0qogM0&2? zI<3uBqCqU{uACx7?D?f9{O26)EFb?$ArJS`6^lNTCS4CM>ZnvB^BsKf$I?vrzp<+@ O5;FI7I4=a^;cc;h@8+)n delta 509 zcmV#YjH#jydB57`9Wq2YwGAtr=Z**a7B04oJB5P%NMsi_iZDnqB zB03^@Ms0a$c13S%aZWZtSwUfBbY^E+RC6{$LTPnIN>Eo>Nl!63a#2ZFFk?L;eF`lt zAVN<>Ss*Pf3f>&MCJhbWc5`kLb)h_p)G(C*n@c^je2oE&x8HvS75w^pKF=v~=y_E# z7)x|`@3cT6(wc*@l`e~6I&JAH&ER(B;$~MIy`ZqDjWyEhj`Y)D5lf7`yO6s=%McY< zE5a44GhC+Z4bZwlS3(q$h`tdJJN@st^L98m?qtze=R{hS^PMUdjt*y!mg_43U)}#{ zAt`(%twH-OTzr3Z#2wFV-{@-IV^iPkL?>ak%iUihbmt#8;B?# z59$nlUeCI1Ydu7P4`M)@DFVf%u{U&jiWeBm7ld)d{Bs)=HkCD)IK?;eB}Nwca@8-k z22f7-+4}--gc-I2$^rC-7P8=8d(9@srH$;Rm%#=*)sl68eibFZ6LfD_xJoUg!jxSL zCho<(k1#f2T%Ir9cK5GYCKGS_G>?4Lfxf$~0T8->2+P%B0lvt{usl=0lA*g<9)1Zl zP2Ctnb>u;*b<4i2gY!jsZF@04SJ*{Pq9A$4-*o{>9$RdthagnaDe~dzVv_<;!sp=# diff --git a/example/diary_3_of_5.horcrux b/example/diary_3_of_5.horcrux index 03ef6fce4c3af5983bc05652b1e43eacbad3c454..d0dbcb50d2408cf23d3b2673372f18cdba5b20f6 100644 GIT binary patch delta 517 zcmV+g0{Z>s2J;56c>#YpFfukQB57`9Wq2YwGb|!>Z**a7B04oJB6MhSWpik6Y-A!j zH7p`)WqC$&VP|b+Zge6#B6&kXI88(|c5i1kPcThlYei~AFilTocX2soPEblOLQHB# zPA_XjZdP|~P$GQ_EiE8IPefTDEiDS4DGqo;@8xnJ}NRC=}h8=2A z@jC@-%aFv^1y}xlXEdR~#tp_70;J*L66k!F@G^cSSv_m7Qy(J_#2emswrVPX;8j>l zd`;IUg16b^S15lHw^^hTJ8u2})#*8gJ(-~*Z0U%(ii zx2)cd3viuYo}beU^DjV4$n;=fEkei~e1#I%;?3dCo#v>g-lE&ZgD=BZS@%9?p8I82 z660~8htzdeOgJb*K+@%PYao76KziylggDEWEM5#&u@`+pXy-arfLGJ{b+C5K1ajmG zF88C~SF3`x4T77!T~~Jg-+M-#YjH#jydB57`9Wq2YwGb|!>Z**a7B04oJB5P%NMsi_iZDnqB zB03^sIczyLWmZsScSSs*Pf3YNIXz0Sy^lG7I6Hb35h0!ZN^=#dzEsq0s zSj2?=9tlUX=O*sQ^k|+mtlV9r9Az#wBwox$57L}Z{8G5JyV7W5xpXdMEDD8l4o|%^ zfm^`Z2MUnzq7=gUc*7T){Pwx$6p}SoC4V1DGc{r5dBWpus@fmubh5$u-^kca<0$K$ z;~AfcPgiLS8q0s78CL5^rPqkp!Vy^lS5;t=n$ zeNmDU@mqtAe?SW-+QNJP|K&u26aQeG>k##|iNusU4~}nob`gf4VOR;mfB@vf6@`Fzke@=gEMKCQ)6+^V~NA))f1?Z=IH#YpFfukQB57`9Wq2YwG%O-?Z**a7B04oJB6MhSWpik6Y-A!j zH7p`)WqC$&VP|b+Zge6#A~SJuWH?ZAQ(A0Nd0}@(O*cnwOKVU>M{hE9T1ie+OhYwp zS5$OYR!eqgOd@>>EiE8IPefTDEiDRM86ro&tHq%I(czL%1Dk*4H%Ss9;U-mBMS2C+ zUR7L|zOM(7Qm_X+j_D-vv*32BN#qSMz*ca}n*252%$E6U>>eaecjF#rH?ww?07W@#+^pgHE`l+5$oQ+EzBYjSEr=80?TmEu|!UCFudc1lVe z_;&3MhW>4(2kL($ngsx#q~X*r5hyNdt- delta 409 zcmV;K0cQS{28RZ)c>#YjH#jydB57`9Wq2YwG%O-?Z**a7B04oJB5P%NMsi_iZDnqB zB03^-R#-webYwGEWqD9CWmz~iYd1o0I9g$4Vt83*dSq!Ss*Pf3P<2_oWq=JnA9X%DowMPLiY3i-cDpu9ZiJEG%`8TA!EQ~V5wvaq1_ql&tV$iP+sRaXDW~rOWfIfgq=k|B znoWDX^lCK`Qwgb>@5FwJP~8*?^cByH90I*2BAdX zoX&n+1Yg{X;F6|aZ`vujd}8iAdNf2Z1(WT$v&A;18s&e{sIgTl`iUt<@%bC5^4FP^ z%-Eo;nk+MNKp(ibzNjly_+|ehoxjaNv^1kQh9Sio8VwO_b!<^^cS}&@AZVflM)O=~ D#YpFfukQB57`9Wq2YwH7p`@Z**a7B04oJB6MhSWpik6Y-A!j zH7p`)WqC$&VP|b+Zge6#B35i{GjmfyWp`&UHF8=jNJTGhc6CujM^1G@V?%dKZ%!{a zcyTyVF>Fg>HzIusEiE8IPefTDEiDTCVA|`he$u7UG4agD^F)6|PZ!yV=}P`9*lDuc zKNKx z2Dxy%Dpchx9G-vH_DSq$f>)2m^;2c*X${stb04}y0}iF_^4%5PPeUrju7c+~XU56^ zY!KK`YetzQDj&k=H^DrG*N&aja;>!tj1p@WZ>#n}B)UWO8a)z?n5L}@c!9-@`Ze{A z+tj)DJTt(yuRS0XTsu0@y?{BA%T(V28c3_5-9zSj8G0W``#~xYKTALNBM&VBFZ+?^ R5+?A7p=F^OL`G#YjH#jydB57`9Wq2YwH7p`@Z**a7B04oJB5P%NMsi_iZDnqB zB03^SYh*_=T2M83Rx(0)Gc{#NQ9(j_cxqu*OGq_mH$iYWT3IwoT17=Tb2vRBeF`lt zAVN<>Ss*Pf3h$150Z#**l5V(GuVwELw;!VHbl^Ym#n^{@AK-t3O6@(ZYa!<9PT}7Z zV!N$!1YxiN8f!Tv&W_Wkv1})ivRn~=68i7sY~8)ZT&Ef!%PP)qAZQJ?02{yu1o9c2$yCY?^;8_-fgQJt;|ZgYyr3${uB{lC z6gwk*-dZdn(+7W)ezj0^&;5B3(IhEqdXa1~{jLpUp6AKpnv)xF&bb7)ZHQ=mcr3*y z06VvJ&PXuF+3UHQERleM92kPoyuevPMmOkU%^RP9jYXa28?_lFi9x b { - return b - } - return a -} - -type demultiplexer struct { - writers []*os.File - writerIndex int - bytesWritten int -} - -func (d *demultiplexer) nextWriter() { - d.writerIndex++ - if d.writerIndex > len(d.writers)-1 { - d.writerIndex = 0 - } - d.bytesWritten = 0 -} - -func (d *demultiplexer) Write(p []byte) (int, error) { - totalN := 0 - for totalN < len(p) { - remainingBytes := len(p) - totalN - remainingBytesForWriter := BYTE_QUOTA - d.bytesWritten - n, err := d.writers[d.writerIndex].Write(p[totalN : totalN+min(remainingBytesForWriter, remainingBytes)]) - d.bytesWritten += n - totalN += n - if err != nil { - return totalN, err - } - if remainingBytesForWriter-n <= 0 { - d.nextWriter() - } - } - - return totalN, nil -} - -type multiplexer struct { - readers []*os.File - readerIndex int - bytesRead int -} - -func (m *multiplexer) nextReader() { - m.readerIndex++ - if m.readerIndex > len(m.readers)-1 { - m.readerIndex = 0 - } - m.bytesRead = 0 -} - -func (m *multiplexer) Read(p []byte) (int, error) { - totalN := 0 - for totalN < len(p) { - remainingBytes := len(p) - totalN - remainingBytesForReader := BYTE_QUOTA - m.bytesRead - buf := make([]byte, min(remainingBytes, remainingBytesForReader)) - n, err := m.readers[m.readerIndex].Read(buf) - p = append(p[0:totalN], buf[0:n]...) - totalN += n - m.bytesRead += n - if err != nil { - return totalN, err - } - if remainingBytesForReader-n <= 0 { - m.nextReader() - } - } - - return totalN, nil -} - func header(index int, total int) string { return fmt.Sprintf(`# THIS FILE IS A HORCRUX. # IT IS ONE OF %d HORCRUXES THAT EACH CONTAIN PART OF AN ORIGINAL FILE. @@ -241,6 +189,7 @@ func bind(dir string) error { var originalFilename string var timestamp int64 var total int + var threshold int horcruxes := []horcruxHeader{} horcruxFiles := []*os.File{} @@ -287,6 +236,7 @@ func bind(dir string) error { originalFilename = header.OriginalFilename timestamp = header.Timestamp total = header.Total + threshold = header.Threshold } else { if header.OriginalFilename != originalFilename || header.Timestamp != timestamp { return errors.New("All horcruxes in the given directory must have the same original filename and timestamp.") @@ -301,27 +251,35 @@ func bind(dir string) error { return errors.New("No horcruxes in directory") } - // check that we have the total. - if len(horcruxes) < total { - horcruxIndices := make([]string, len(horcruxes)) - for i, h := range horcruxes { - horcruxIndices[i] = strconv.Itoa(h.Index) + // check that we have the threshold. + if len(horcruxes) < threshold { + return errors.New(fmt.Sprintf("You do not have all the required horcruxes. There are %d required to resurrect the original file. You only have %d", threshold, len(horcruxes))) + } + + keyFragments := make([][]byte, len(horcruxes)) + for i := range keyFragments { + keyFragments[i] = horcruxes[i].KeyFragment + } + + key, err := shamir.Combine(keyFragments) + if err != nil { + return err + } + + var r io.Reader + if total == threshold { + // sort by index + orderedHorcruxes := make([]horcruxHeader, len(horcruxes)) + for _, h := range horcruxes { + orderedHorcruxes[h.Index-1] = h } - return errors.New(fmt.Sprintf("You do not have all the required horcruxes. There are %d in total. You only have horcrux(es) %s", total, strings.Join(horcruxIndices, ","))) + r = &multiplexing.Multiplexer{Readers: horcruxFiles} + } else { + r = horcruxFiles[0] // arbitrarily read from the first horcrux: they all contain the same contents } - // sort by index - orderedHorcruxes := make([]horcruxHeader, len(horcruxes)) - for _, h := range horcruxes { - orderedHorcruxes[h.Index-1] = h - } - - // now we just need to concatenate the contents together then decrypt everything with the first to the last key - var r io.Reader = &multiplexer{readers: horcruxFiles} - for i := range orderedHorcruxes { - r = cryptoReader(r, orderedHorcruxes[len(orderedHorcruxes)-1-i].KeyFragment) - } + r = cryptoReader(r, key) newFilename := originalFilename if fileExists(originalFilename) { @@ -370,17 +328,3 @@ func prompt(message string, args ...interface{}) string { input, _ := reader.ReadString('\n') return strings.TrimSpace(input) } - -func splitIntoEqualPartsBytes(s []byte, n int) [][]byte { - sliceLength := len(s) / n - slices := make([][]byte, n) - for i := range slices { - if i == n-1 { - slices[i] = s[i*sliceLength:] - } else { - slices[i] = s[i*sliceLength : (i+1)*sliceLength] - } - } - - return slices -} diff --git a/pkg/multiplexing/multiplexing.go b/pkg/multiplexing/multiplexing.go new file mode 100644 index 0000000..182cf2d --- /dev/null +++ b/pkg/multiplexing/multiplexing.go @@ -0,0 +1,85 @@ +package multiplexing + +// when we set the threshold to the number of horcruxes, we can save space by +// dividing the encrypted contents equally across each horcrux. +// This file contains a multiplexer/Demultiplexer for reading/writing with +// multiplexed content + +import "os" + +const BYTE_QUOTA = 100 + +func min(a int, b int) int { + if a > b { + return b + } + return a +} + +type Demultiplexer struct { + Writers []*os.File + writerIndex int + bytesWritten int +} + +func (d *Demultiplexer) nextWriter() { + d.writerIndex++ + if d.writerIndex > len(d.Writers)-1 { + d.writerIndex = 0 + } + d.bytesWritten = 0 +} + +func (d *Demultiplexer) Write(p []byte) (int, error) { + totalN := 0 + for totalN < len(p) { + remainingBytes := len(p) - totalN + remainingBytesForWriter := BYTE_QUOTA - d.bytesWritten + n, err := d.Writers[d.writerIndex].Write(p[totalN : totalN+min(remainingBytesForWriter, remainingBytes)]) + d.bytesWritten += n + totalN += n + if err != nil { + return totalN, err + } + if remainingBytesForWriter-n <= 0 { + d.nextWriter() + } + } + + return totalN, nil +} + +type Multiplexer struct { + Readers []*os.File + readerIndex int + bytesRead int +} + +func (m *Multiplexer) nextReader() { + m.readerIndex++ + if m.readerIndex > len(m.Readers)-1 { + m.readerIndex = 0 + } + m.bytesRead = 0 +} + +func (m *Multiplexer) Read(p []byte) (int, error) { + totalN := 0 + for totalN < len(p) { + remainingBytes := len(p) - totalN + remainingBytesForReader := BYTE_QUOTA - m.bytesRead + buf := make([]byte, min(remainingBytes, remainingBytesForReader)) + n, err := m.Readers[m.readerIndex].Read(buf) + p = append(p[0:totalN], buf[0:n]...) + totalN += n + m.bytesRead += n + if err != nil { + return totalN, err + } + if remainingBytesForReader-n <= 0 { + m.nextReader() + } + } + + return totalN, nil +} diff --git a/pkg/shamir/shamir.go b/pkg/shamir/shamir.go new file mode 100644 index 0000000..ebb45d7 --- /dev/null +++ b/pkg/shamir/shamir.go @@ -0,0 +1,262 @@ +// adapted from https://github.com/hashicorp/vault/blob/master/shamir/shamir.go + +package shamir + +import ( + "crypto/rand" + "crypto/subtle" + "fmt" + mathrand "math/rand" + "time" +) + +const ( + // ShareOverhead is the byte size overhead of each share + // when using Split on a secret. This is caused by appending + // a one byte tag to the share. + ShareOverhead = 1 +) + +// polynomial represents a polynomial of arbitrary degree +type polynomial struct { + coefficients []uint8 +} + +// makePolynomial constructs a random polynomial of the given +// degree but with the provided intercept value. +func makePolynomial(intercept, degree uint8) (polynomial, error) { + // Create a wrapper + p := polynomial{ + coefficients: make([]byte, degree+1), + } + + // Ensure the intercept is set + p.coefficients[0] = intercept + + // Assign random co-efficients to the polynomial + if _, err := rand.Read(p.coefficients[1:]); err != nil { + return p, err + } + + return p, nil +} + +// evaluate returns the value of the polynomial for the given x +func (p *polynomial) evaluate(x uint8) uint8 { + // Special case the origin + if x == 0 { + return p.coefficients[0] + } + + // Compute the polynomial value using Horner's method. + degree := len(p.coefficients) - 1 + out := p.coefficients[degree] + for i := degree - 1; i >= 0; i-- { + coeff := p.coefficients[i] + out = add(mult(out, x), coeff) + } + return out +} + +// interpolatePolynomial takes N sample points and returns +// the value at a given x using a lagrange interpolation. +func interpolatePolynomial(x_samples, y_samples []uint8, x uint8) uint8 { + limit := len(x_samples) + var result, basis uint8 + for i := 0; i < limit; i++ { + basis = 1 + for j := 0; j < limit; j++ { + if i == j { + continue + } + num := add(x, x_samples[j]) + denom := add(x_samples[i], x_samples[j]) + term := div(num, denom) + basis = mult(basis, term) + } + group := mult(y_samples[i], basis) + result = add(result, group) + } + return result +} + +// div divides two numbers in GF(2^8) +func div(a, b uint8) uint8 { + if b == 0 { + // leaks some timing information but we don't care anyways as this + // should never happen, hence the panic + panic("divide by zero") + } + + var goodVal, zero uint8 + log_a := logTable[a] + log_b := logTable[b] + diff := (int(log_a) - int(log_b)) % 255 + if diff < 0 { + diff += 255 + } + + ret := expTable[diff] + + // Ensure we return zero if a is zero but aren't subject to timing attacks + goodVal = ret + + if subtle.ConstantTimeByteEq(a, 0) == 1 { + ret = zero + } else { + ret = goodVal + } + + return ret +} + +// mult multiplies two numbers in GF(2^8) +func mult(a, b uint8) (out uint8) { + var goodVal, zero uint8 + log_a := logTable[a] + log_b := logTable[b] + sum := (int(log_a) + int(log_b)) % 255 + + ret := expTable[sum] + + // Ensure we return zero if either a or b are zero but aren't subject to + // timing attacks + goodVal = ret + + if subtle.ConstantTimeByteEq(a, 0) == 1 { + ret = zero + } else { + ret = goodVal + } + + if subtle.ConstantTimeByteEq(b, 0) == 1 { + ret = zero + } else { + // This operation does not do anything logically useful. It + // only ensures a constant number of assignments to thwart + // timing attacks. + goodVal = zero + } + + return ret +} + +// add combines two numbers in GF(2^8) +// This can also be used for subtraction since it is symmetric. +func add(a, b uint8) uint8 { + return a ^ b +} + +// Split takes an arbitrarily long secret and generates a `parts` +// number of shares, `threshold` of which are required to reconstruct +// the secret. The parts and threshold must be at least 2, and less +// than 256. The returned shares are each one byte longer than the secret +// as they attach a tag used to reconstruct the secret. +func Split(secret []byte, parts, threshold int) ([][]byte, error) { + // Sanity check the input + if parts < threshold { + return nil, fmt.Errorf("parts cannot be less than threshold") + } + if parts > 255 { + return nil, fmt.Errorf("parts cannot exceed 255") + } + if threshold < 2 { + return nil, fmt.Errorf("threshold must be at least 2") + } + if threshold > 255 { + return nil, fmt.Errorf("threshold cannot exceed 255") + } + if len(secret) == 0 { + return nil, fmt.Errorf("cannot split an empty secret") + } + + // Generate random list of x coordinates + mathrand.Seed(time.Now().UnixNano()) + xCoordinates := mathrand.Perm(255) + + // Allocate the output array, initialize the final byte + // of the output with the offset. The representation of each + // output is {y1, y2, .., yN, x}. + out := make([][]byte, parts) + for idx := range out { + out[idx] = make([]byte, len(secret)+1) + out[idx][len(secret)] = uint8(xCoordinates[idx]) + 1 + } + + // Construct a random polynomial for each byte of the secret. + // Because we are using a field of size 256, we can only represent + // a single byte as the intercept of the polynomial, so we must + // use a new polynomial for each byte. + for idx, val := range secret { + p, err := makePolynomial(val, uint8(threshold-1)) + if err != nil { + return nil, err + } + + // Generate a `parts` number of (x,y) pairs + // We cheat by encoding the x value once as the final index, + // so that it only needs to be stored once. + for i := 0; i < parts; i++ { + x := uint8(xCoordinates[i]) + 1 + y := p.evaluate(x) + out[i][idx] = y + } + } + + // Return the encoded secrets + return out, nil +} + +// Combine is used to reverse a Split and reconstruct a secret +// once a `threshold` number of parts are available. +func Combine(parts [][]byte) ([]byte, error) { + // Verify enough parts provided + if len(parts) < 2 { + return nil, fmt.Errorf("less than two parts cannot be used to reconstruct the secret") + } + + // Verify the parts are all the same length + firstPartLen := len(parts[0]) + if firstPartLen < 2 { + return nil, fmt.Errorf("parts must be at least two bytes") + } + for i := 1; i < len(parts); i++ { + if len(parts[i]) != firstPartLen { + return nil, fmt.Errorf("all parts must be the same length") + } + } + + // Create a buffer to store the reconstructed secret + secret := make([]byte, firstPartLen-1) + + // Buffer to store the samples + x_samples := make([]uint8, len(parts)) + y_samples := make([]uint8, len(parts)) + + // Set the x value for each sample and ensure no x_sample values are the same, + // otherwise div() can be unhappy + checkMap := map[byte]bool{} + for i, part := range parts { + samp := part[firstPartLen-1] + if exists := checkMap[samp]; exists { + return nil, fmt.Errorf("duplicate part detected") + } + checkMap[samp] = true + x_samples[i] = samp + } + + // Reconstruct each byte + for idx := range secret { + // Set the y value for each sample + for i, part := range parts { + y_samples[i] = part[idx] + } + + // Interpolate the polynomial and compute the value at 0 + val := interpolatePolynomial(x_samples, y_samples, 0) + + // Evaluate the 0th value to get the intercept + secret[idx] = val + } + return secret, nil +} diff --git a/pkg/shamir/tables.go b/pkg/shamir/tables.go new file mode 100644 index 0000000..b394210 --- /dev/null +++ b/pkg/shamir/tables.go @@ -0,0 +1,79 @@ +// adapted from https://github.com/hashicorp/vault/blob/master/shamir/tables.go + +package shamir + +// Tables taken from http://www.samiam.org/galois.html +// They use 0xe5 (229) as the generator + +var ( + // logTable provides the log(X)/log(g) at each index X + logTable = [256]uint8{ + 0x00, 0xff, 0xc8, 0x08, 0x91, 0x10, 0xd0, 0x36, + 0x5a, 0x3e, 0xd8, 0x43, 0x99, 0x77, 0xfe, 0x18, + 0x23, 0x20, 0x07, 0x70, 0xa1, 0x6c, 0x0c, 0x7f, + 0x62, 0x8b, 0x40, 0x46, 0xc7, 0x4b, 0xe0, 0x0e, + 0xeb, 0x16, 0xe8, 0xad, 0xcf, 0xcd, 0x39, 0x53, + 0x6a, 0x27, 0x35, 0x93, 0xd4, 0x4e, 0x48, 0xc3, + 0x2b, 0x79, 0x54, 0x28, 0x09, 0x78, 0x0f, 0x21, + 0x90, 0x87, 0x14, 0x2a, 0xa9, 0x9c, 0xd6, 0x74, + 0xb4, 0x7c, 0xde, 0xed, 0xb1, 0x86, 0x76, 0xa4, + 0x98, 0xe2, 0x96, 0x8f, 0x02, 0x32, 0x1c, 0xc1, + 0x33, 0xee, 0xef, 0x81, 0xfd, 0x30, 0x5c, 0x13, + 0x9d, 0x29, 0x17, 0xc4, 0x11, 0x44, 0x8c, 0x80, + 0xf3, 0x73, 0x42, 0x1e, 0x1d, 0xb5, 0xf0, 0x12, + 0xd1, 0x5b, 0x41, 0xa2, 0xd7, 0x2c, 0xe9, 0xd5, + 0x59, 0xcb, 0x50, 0xa8, 0xdc, 0xfc, 0xf2, 0x56, + 0x72, 0xa6, 0x65, 0x2f, 0x9f, 0x9b, 0x3d, 0xba, + 0x7d, 0xc2, 0x45, 0x82, 0xa7, 0x57, 0xb6, 0xa3, + 0x7a, 0x75, 0x4f, 0xae, 0x3f, 0x37, 0x6d, 0x47, + 0x61, 0xbe, 0xab, 0xd3, 0x5f, 0xb0, 0x58, 0xaf, + 0xca, 0x5e, 0xfa, 0x85, 0xe4, 0x4d, 0x8a, 0x05, + 0xfb, 0x60, 0xb7, 0x7b, 0xb8, 0x26, 0x4a, 0x67, + 0xc6, 0x1a, 0xf8, 0x69, 0x25, 0xb3, 0xdb, 0xbd, + 0x66, 0xdd, 0xf1, 0xd2, 0xdf, 0x03, 0x8d, 0x34, + 0xd9, 0x92, 0x0d, 0x63, 0x55, 0xaa, 0x49, 0xec, + 0xbc, 0x95, 0x3c, 0x84, 0x0b, 0xf5, 0xe6, 0xe7, + 0xe5, 0xac, 0x7e, 0x6e, 0xb9, 0xf9, 0xda, 0x8e, + 0x9a, 0xc9, 0x24, 0xe1, 0x0a, 0x15, 0x6b, 0x3a, + 0xa0, 0x51, 0xf4, 0xea, 0xb2, 0x97, 0x9e, 0x5d, + 0x22, 0x88, 0x94, 0xce, 0x19, 0x01, 0x71, 0x4c, + 0xa5, 0xe3, 0xc5, 0x31, 0xbb, 0xcc, 0x1f, 0x2d, + 0x3b, 0x52, 0x6f, 0xf6, 0x2e, 0x89, 0xf7, 0xc0, + 0x68, 0x1b, 0x64, 0x04, 0x06, 0xbf, 0x83, 0x38} + + // expTable provides the anti-log or exponentiation value + // for the equivalent index + expTable = [256]uint8{ + 0x01, 0xe5, 0x4c, 0xb5, 0xfb, 0x9f, 0xfc, 0x12, + 0x03, 0x34, 0xd4, 0xc4, 0x16, 0xba, 0x1f, 0x36, + 0x05, 0x5c, 0x67, 0x57, 0x3a, 0xd5, 0x21, 0x5a, + 0x0f, 0xe4, 0xa9, 0xf9, 0x4e, 0x64, 0x63, 0xee, + 0x11, 0x37, 0xe0, 0x10, 0xd2, 0xac, 0xa5, 0x29, + 0x33, 0x59, 0x3b, 0x30, 0x6d, 0xef, 0xf4, 0x7b, + 0x55, 0xeb, 0x4d, 0x50, 0xb7, 0x2a, 0x07, 0x8d, + 0xff, 0x26, 0xd7, 0xf0, 0xc2, 0x7e, 0x09, 0x8c, + 0x1a, 0x6a, 0x62, 0x0b, 0x5d, 0x82, 0x1b, 0x8f, + 0x2e, 0xbe, 0xa6, 0x1d, 0xe7, 0x9d, 0x2d, 0x8a, + 0x72, 0xd9, 0xf1, 0x27, 0x32, 0xbc, 0x77, 0x85, + 0x96, 0x70, 0x08, 0x69, 0x56, 0xdf, 0x99, 0x94, + 0xa1, 0x90, 0x18, 0xbb, 0xfa, 0x7a, 0xb0, 0xa7, + 0xf8, 0xab, 0x28, 0xd6, 0x15, 0x8e, 0xcb, 0xf2, + 0x13, 0xe6, 0x78, 0x61, 0x3f, 0x89, 0x46, 0x0d, + 0x35, 0x31, 0x88, 0xa3, 0x41, 0x80, 0xca, 0x17, + 0x5f, 0x53, 0x83, 0xfe, 0xc3, 0x9b, 0x45, 0x39, + 0xe1, 0xf5, 0x9e, 0x19, 0x5e, 0xb6, 0xcf, 0x4b, + 0x38, 0x04, 0xb9, 0x2b, 0xe2, 0xc1, 0x4a, 0xdd, + 0x48, 0x0c, 0xd0, 0x7d, 0x3d, 0x58, 0xde, 0x7c, + 0xd8, 0x14, 0x6b, 0x87, 0x47, 0xe8, 0x79, 0x84, + 0x73, 0x3c, 0xbd, 0x92, 0xc9, 0x23, 0x8b, 0x97, + 0x95, 0x44, 0xdc, 0xad, 0x40, 0x65, 0x86, 0xa2, + 0xa4, 0xcc, 0x7f, 0xec, 0xc0, 0xaf, 0x91, 0xfd, + 0xf7, 0x4f, 0x81, 0x2f, 0x5b, 0xea, 0xa8, 0x1c, + 0x02, 0xd1, 0x98, 0x71, 0xed, 0x25, 0xe3, 0x24, + 0x06, 0x68, 0xb3, 0x93, 0x2c, 0x6f, 0x3e, 0x6c, + 0x0a, 0xb8, 0xce, 0xae, 0x74, 0xb1, 0x42, 0xb4, + 0x1e, 0xd3, 0x49, 0xe9, 0x9c, 0xc8, 0xc6, 0xc7, + 0x22, 0x6e, 0xdb, 0x20, 0xbf, 0x43, 0x51, 0x52, + 0x66, 0xb2, 0x76, 0x60, 0xda, 0xc5, 0xf3, 0xf6, + 0xaa, 0xcd, 0x9a, 0xa0, 0x75, 0x54, 0x0e, 0x01} +)